钉钉告警群发出rpc调用超时,服务健康检查自动重启。检查CAT发现老年代内存居高步下,pinpoint如下图频繁fullGC 最终将导致服务崩溃重启。
step1: 老年代内存一直居高步下,初步判定是有对象一直没有被回收且一直在增长所以导致内存被耗尽频繁FULL GC。内存出问题就应该从分析内存下手,由于加入健康检查,当服务在30s内无反应会自动重启服务。因此之前设置的
HeapDumpOnOutOfMemoryError 发生OOM自动dump内存将无效,只能等待下一次内存疯涨后在进行内存抓取分析。
step2: 在等待4天后,老年代内存达到600M,还是在一直增长。 进服务器dump下内存后,用分析工具进行分析:如图所示 :
其中 ConcurrentHashMap 停留在堆中的内存粗略达到457M,占用率高达80% 因此判定应该是该集合一直在增长且未回收。 可以继续查看该集合中存放东西,发现是如下sql 语句:
缓存中停留sql语句:
select count(0) from (SELECT
cc.`coupon_code_id`,
cc.`coupon_code`,
cc.`coupon_id`,
cc.`gain_user_id`,
cc.`source_code_id`,
cc.`business_id`,
cc.`business_time`,
cc.`coupon_status`,
cc.`coupon_rise_amount`,
cc.`coupon_rise_times`,
cc.`coupon_rise_limit_percent`,
cc.`coupon_rise_gradient_percent`,
cc.`start_time`,
cc.`expire_time`,
cc.`create_time`,
cc.`update_time`,
c.`coupon_type`,
cc.`coupon_base_amount`,
cc.`coupon_workflow_status`,
cc.`coupon_exchange_way`,
c.`coupon_quantity`,
c.`coupon_status` as `coupon_lot_status`,
c.`coupon_issuer_id`,
c.`coupon_issuer_role`,
c.`issuer_channel`,
c.`branch_id`,
c.`carrier_id`,
c.`estate_id`,
c.`project_id`,
c.`market_id`,
c.`tax_payer`,
IF
( cc.`coupon_status` = 5 or cc.`coupon_status` = 8 , 99, 0 ) AS `seq`,
CASE cc.`coupon_status`
WHEN 1 THEN 1
WHEN 2 THEN 2
WHEN 3 THEN 3
WHEN 6 THEN 4
WHEN 7 THEN 5
WHEN 4 THEN 6
WHEN 8 THEN 7
WHEN 5 THEN 8
ELSE 9
END as `couponStatusOrder`
FROM
bac_coupon_code cc
JOIN bac_coupon c ON cc.coupon_id = c.coupon_id
where
1=1 and cc.is_deleted = 0 and c.is_deleted = 0
and c.project_id in
(
22401
)
and cc.`coupon_status` in
(
1
,
4
,
7
,
3
,
6
)
and cc.`gain_user_id` in
(
4778579
)
and c.`coupon_type` = ?
order by
seq ASC,
cc.`update_time` DESC) tmp_count
step4: 根据sql语句,及溢出对象,可以判定为是 pageHelp.sqlParser 造成的。 跟踪源码可以看到
大致意思是:将sql语句进行标准化解析,如果无法进行标准化解析那么就会放到缓存中。 可以看到上边SQL 语句中,是识别不了 IF 导致的解析失败。可以得到结论:
每一次执行该sql 分页时,由于查询条件不同 比如根据项目id、用户id 等会有不同的sql 全部放到缓存中势必会扩张该缓存,直到将查询条件穷举完为止
step5: 确定好问题后,网上搜索验证是否有类似问题 https://gitee.com/free/Mybatis_PageHelper/issues/131