1. 背景
生产环境的服务器突然访问无响应,而我的服务是用来接收上游推送数据用的,功能也是最近新上线的;
2. 定位原因
- 访问系统的version方法,也无响应,服务器日志也没有输出http请求的日志;登录服务器查看服务的状态正常;
- 初步怀疑会不会是程序问题引起的频繁FullGC问题,查看内存使用情况也正常
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 8589934592 (8192.0MB)
NewSize = 1073741824 (1024.0MB)
MaxNewSize = 1073741824 (1024.0MB)
OldSize = 7516192768 (7168.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 966393856 (921.625MB)
used = 133072768 (126.9080810546875MB)
free = 833321088 (794.7169189453125MB)
13.770034564458157% used
Eden Space:
capacity = 859045888 (819.25MB)
used = 115850424 (110.48357391357422MB)
free = 743195464 (708.7664260864258MB)
13.48594127721382% used
From Space:
capacity = 107347968 (102.375MB)
used = 17222344 (16.42450714111328MB)
free = 90125624 (85.95049285888672MB)
16.043474618914072% used
To Space:
capacity = 107347968 (102.375MB)
used = 0 (0.0MB)
free = 107347968 (102.375MB)
0.0% used
concurrent mark-sweep generation:
capacity = 7516192768 (7168.0MB)
used = 76538880 (72.9931640625MB)
free = 7439653888 (7095.0068359375MB)
1.018319811139788% used
通过上面的堆信息可看出服务的JVM使用情况正常;
排查服务日志时发现从某一时间点之后,服务一个请求都没有收到,只好查一下当前服务的请求数:
netstat -an |grep -i 8035 |wc -l
节点1:
节点2:
我们服务是双节点部署,容器是jetty,可见大部分的请求的状态已经变为CLOSE_WAIT;
查看监控系统上的等待tcp情况:
补充COLSE_WAIT是什么
客户端向服务端发送断开连接请求,这是客供端发送一个FIN给服务端;
服务端收到FIN后回应ACK,此时服务端就会处于CLOSE_WAIT状态;
服务端业务处理完毕之后,服务端向客户端发送FIN;
客户端返回给服务器一个确认ACK状态;
这是一个正常的挥手过程,而我们大量的请求状态处于CLOSE_WAIT,可能的原因就是我们服务正在处理业务没有完毕,造成了大量请求阻塞,而我上游系统的http请求也会有超时限制,所以超时之后,上游系统就会发送断开连接FIN;
好了,回到问题排查的过程中;
既然可能是程序问题造成请求一直处理业务处理中无法结束,通过服务日志排查代码问题;
接口的逻辑大致如下:
从上图可以看出有一个明显的问题,事务的控制范围过大;
隐藏着的另外一个bug就是分布式锁这块;
从现有的日志中看出,上游传数据已经违背了当初的规则,当初说接口是一次传送一个主行记录和多行明细,而上游上线前因为需求变化改为了一个主行一个明细循环并发调用我们的接口; 当接口传送的都是同一个仓库下的信息时
这会导致:
因为是同一个仓库,分布式锁的key正好是仓库为维度的,这会导致线程A在修改完库存之后,数据库对当前库存记录加TX锁,在进行同步下游系统时,线程B抢占到了分布式锁,但修改库存时,会导致无法提交事务,因为上个请求的TX锁没有提交事务;
当并发量一大起来,线程A的减少可用量请求可能一直获取不到锁,就导致它的更新库存sql卡死,造成后续修改相同记录的请求全部阻塞;
3. 修复
问题确定之后,找到对应的开发RD告知相应的解析方案,把大事务拆分成小事务;后续接口数据的接口也切换到MQ中去;(上游系统也在联系请求他们修改接口的调用方式,目前他们是一个主单有1000行明细,就循环调用我们系统1000次,相关于我们是1000的并发,而仓库地点又是相同,导致大量的获取分布式锁阻塞)
另外是分布式锁这块,这块最近遇到的问题比较多,后面整理出文章专门说明,分布式锁的选型非常重要;(redis分布式锁不适合我们的业务,正在调研切换为zookeeper);