记一次FastJson使用不当导致频繁Full GC排查

  具体原因为:https://www.cnblogs.com/liqipeng/p/11665889.html 与这篇文章的情况类似,可以直接按这篇文章的解决方案进行调整,此处只记录下排查过程。

  近期,测试压测某个查询接口时,发现压测的开始一段时间TPS比较稳定,在压测进行多了几次后,TPS开始出现巨大波动。

  进服务器利用TOP命令查看机器基本情况,发现业务层CPU的占用已经达到93%,但数据库层的CPU占用却仅在25%,说明数据库压力并不大,而这个业务只是单纯的查询用户是否处于黑名单状态中的单表查询,也没有看到有慢SQL的记录。

  又发现JVM进程的内存占用已经达6.3g,十分异常,但没有oom的情况出现(也有可能有,当时没注意)。于是转而利用jstat -gcutil命令查看jvm中各区占用内存情况。发现olden区占用在100%,并且在频繁的fullgc,fullgc完olden区的占用又变成100%。

  将项目重启后,继续利用 jstat -gcutil  pid 1000 重新观察各区内存使用情况,发现压测开始后 olden区 的占用在慢慢的爬升,但这个查询接口不应该有什么对象可以晋升到老年代,其中并没有做任何的本地缓存。JVM参数 -Xmx5866m -Xms5866m -Xss256K,那么按照新老年代占用比例为1:2,也不应出现Survivor区内存不足频繁分配到 olden区 的情况,应该说从业务代码来分析进入Survivor区的对象应该是没有的或极少的。(排查过业务代码,并没有死循环new对象情况)

 

  在没有任何头绪的情况下,只能考虑dump下内存进行分析了。这里用了jvisualvm在本地进行模拟。

  在本地将项目的堆内存调至128M,方便快速复现。在本地用20个线程进行并发,跑了1、2分钟就复现出了问题。

 可以看到运行几分钟后,老年代内存就占满了,并且在频繁的GC操作,复现了问题,本地看日志报出了oom。

 dump下内存发现,大量的String对象其内容为 ApiResultxxx,查看其引用对象,发现是Type对象,再往上找发现是com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer 对象

  由于我们业务层和数据库层是分离的,通过feign客户端进行调用,decode采用fastjson进行处理,这时候只能怀疑到平台组对这一层的decode封装是否有问题。

  排查平台组写的decode源码发现,在ms层直接返回非ApiResult封装的对象时,每次解析都会new一个ParameterizedTypeImpl进行处理。

  其实这时候并不觉得有什么问题,因为就算是new一个对象出来,解析完也可以回收。但是在本地单抽这段代码出来for循环然后Thread.sleep(10)执行,发现问题复现,olden区内存占用持续升高,开始觉得不对劲。debug了下fastjson的源码,发现fastjson是会将解析过的type对象进行缓存,并放到一个IdentityHashMap中,其中计算hash值是利用了 System.identityHashCode(),这个方法会直接返回对象的内存映射地址,然后以Type类型对象作为key值,那么也就是说每次new一个对象时,不管对象内容是否是一样的都会往IdentityHashMap里存放一条缓存,然后频繁逃过 young gc,进入survivor区,最后晋升变成老年代,或者是young gc回收一次后,survivor区不够放(因为这一个对象其实牵连着众多对象,涉及的内存面就较大),直接往老年代里扔。(从结果来看)

  

  至此,问题定位完毕,将数据库层的返回值用ApiResult封装后,在本地运行了下gc结果变为正常。

  第二天调整完后上压测,测试反馈接口已正常,查看jvm各区内存使用情况也已正常。通知经理可以按正常排期上线。

我一直承受着这个年纪不该承受的压力

总结:

1.jstat命令 jmap命令 ,以及jvisualvm等工具的使用要熟练。

2.对JVM的分区以及垃圾回收的流程要有一定的了解,厚积薄发,多看多学,总有用到的地方。

3.使用别人的东西前需要对这个东西有一个大概的了解,这次就是事先有review过一部分平台组的代码,对结构有大概的了解才定位起来比较快。

<think>嗯,用户问的是什么时候会出现短暂的广播机制失效。我需要先回顾一下之前讨论的内容,用户之前在使用JetCache时遇到了缓存清除不生效的问题,可能涉及Redis的广播机制。现在他想了解广播机制短暂失效的可能情况。 首先,用户可能是在实际使用中遇到了缓存同步的问题,比如有时候清除缓存后,其他节点没有立即生效,但过一段时间又好了。这时候需要分析可能导致广播机制短暂失效的原因。 先考虑网络问题。Redis的Pub/Sub依赖于网络稳定性,如果网络出现抖动或者延迟,消息可能无法及时传递,导致其他节点没有收到失效通知,从而本地缓存没有被清除。这种情况下,当网络恢复后,后续的消息可能正常,但之前的失效请求丢失了,可能需要等待缓存自然过期或者下次失效操作。 然后是Redis的高负载。如果Redis服务器在处理大量请求时,Pub/Sub的消息可能会被延迟处理,尤其是在高并发场景下,消息队列积压,导致广播消息不能及时被消费,从而出现短暂的失效延迟。这种情况下,用户可能会观察到清除缓存的生效时间变长,但之后恢复正常。 接下来是订阅断开的情况。比如客户端与Redis的连接因为某些原因(如超时、重启)断开,虽然JetCache可能有重连机制,但在断开期间的消息可能会丢失。重连后,新的消息可以处理,但断开期间的消息没有被接收到,导致这段时间的失效操作没有生效。 客户端处理能力不足也是一个因素。如果应用实例在处理消息时遇到性能问题,比如高CPU或内存使用率,导致无法及时处理广播消息,本地缓存的清除就会被延迟。这时候需要检查应用的资源使用情况,优化处理逻辑或增加资源。 广播通道的配置错误也可能导致问题,比如不同的实例配置了不同的频道名称,或者频道名被错误修改。这种情况下,部分实例无法接收到消息,导致缓存清除不一致。需要检查所有实例的配置是否一致。 JetCache版本或框架本身的Bug也不能忽视。某些版本可能存在广播机制的缺陷,比如消息序列化问题或发布逻辑错误,导致消息未能正确发送或解析。这时候升级到最新版本或查看问题是否已知并修复可能会有帮助。 另外,缓存本身的过期时间设置也可能影响,如果本地缓存的过期时间较短,可能在广播失效前就已经过期,掩盖了问题。但用户的问题更可能在于失效机制本身,所以需要综合考虑。 总结一下,可能的因素包括网络问题、Redis负载、连接中断、客户端处理能力、配置错误、框架Bug等。需要根据具体情况逐一排查,比如检查网络状况、Redis监控数据、客户端日志、配置一致性以及框架版本等。用户可能需要结合日志分析和监控工具来确定具体原因,并采取相应措施,如优化网络、升级Redis配置、调整客户端资源或更新框架版本。</think>以下是可能导致广播机制短暂失效的常见场景及原因分析,结合实际应用场景提供排查思路: --- ### **1. 网络抖动或瞬时中断** - **现象**: 广播消息因网络波动未能及时到达所有节点,导致部分实例的本地缓存未失效。 - **典型场景**: - 云服务商网络不稳定(如跨可用区通信)。 - 防火墙或安全组规则临时拦截 Redis 的 Pub/Sub 端口。 - **排查方法**: - 检查 Redis 服务端和应用实例的网络监控日志(如 TCP 重传、丢包率)。 - 通过 `ping` 或 `traceroute` 观察网络延迟和连通性。 --- ### **2. Redis 服务端高负载** - **现象**: Redis 因 CPU/内存压力未能及时处理 Pub/Sub 消息,导致广播延迟。 - **典型场景**: - 瞬时流量激增(如秒杀活动)。 - Redis 执行耗时操作(如 RDB/AOF 持久化)。 - **排查方法**: - 监控 Redis 的 `CPU使用率`、`内存占用`、`客户端输出缓冲区`。 - 查看 Redis 慢查询日志(`slowlog get`)。 --- ### **3. 客户端订阅断开后重连** - **现象**: 应用实例与 Redis 的连接短暂断开后重连,期间未收到广播消息。 - **典型场景**: - Redis 服务端主动断开空闲连接(如 `timeout` 配置过小)。 - 客户端因 Full GC 或线程阻塞无法及时响应心跳。 - **排查方法**: - 检查 Redis 的 `timeout` 配置(建议 ≥ 300 秒)。 - 查看应用 GC 日志和线程堆栈。 --- ### **4. 广播消息堆积或丢失** - **现象**: Redis Pub/Sub 消息因积压或未持久化导致部分消息丢失。 - **典型场景**: - 广播频率过高,消费者处理速度跟不上。 - Redis 未启用持久化且重启后消息丢失。 - **解决方案**: - 改用 Redis Stream 替代 Pub/Sub(支持消息持久化和确认机制)。 - 降低广播频率或优化消息处理逻辑。 --- ### **5. JetCache 客户端配置问题** - **现象**: 客户端因配置错误短暂无法发送或接收广播。 - **典型场景**: - `broadcastChannel` 名称动态变化(如含时间戳)。 - Lettuce 连接池配置不合理(如最大连接数不足)。 - **排查方法**: - 确保所有实例的 `broadcastChannel` 值完全一致。 - 检查 Lettuce 连接池状态(如 `idle`、`active` 连接数)。 --- ### **6. 框架或依赖库的 Bug** - **现象**: 特定版本 JetCache 或 Lettuce 存在广播逻辑缺陷。 - **典型场景**: - Lettuce 未正确处理连接异常(如 [Issue #1234](https://github.com/lettuce-io/lettuce-core/issues))。 - JetCache 广播消息序列化异常(如 Fastjson2 兼容性问题)。 - **解决方案**: - 升级 JetCache 和 Lettuce 到最新稳定版本。 - 替换序列化器(如改用 `keyConvertor: jackson`)。 --- ### **临时规避方案** 若需快速恢复缓存一致性,可采取以下措施: 1. **手动清除多级缓存**: ```java // 同时清除本地和远程缓存 @CacheInvalidate(name = "cacheName", key = "key", multiLevel = true) public void forceInvalidate() {} ``` 2. **缩短本地缓存过期时间**: ```yaml local: default: expireAfterWriteInMillis: 60000 # 改为 1 分钟 ``` 3. **启用缓存穿透保护**: 结合 `@CacheRefresh` 或 `@CachePenetrationProtect` 注解,减少不一致窗口期。 --- ### **总结** 短暂广播失效通常与 **网络、Redis 负载、连接稳定性** 相关。建议通过监控工具(如 Prometheus + Grafana)对 Redis 和应用实例进行实时观测,同时确保框架和依赖库为最新版本。若问题可稳定复现,可通过最小化代码示例提交至 JetCache 社区进一步排查
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值