这次分享是在混合云场景下,基于websocket长连接,实现Server-Client(多个)架构模式中,云服务需要维护客户端的状态,但是云端维护的状态可能和实际的客户端的状态不一致,可能就会导致一些奇怪的事情发生,比较有意思的一个问题吧,非常不容易发现的一个问题,必须需要一个合适的契机才可以去发现。前面直接描述问题和解决方案,后面用一定的篇幅详细讲述一下怎么发现的这个问题。
如下图:
比较简单,但也比较重要,这是看懂本问题以及解决的基本知识,但是仅仅描述和这个问题相关的内容,其它内容不进行扩展:
客户端的在线状态特别重要,云端所有的业务操作都将可能依赖于数据库中存储的客户端状态。直接来看,有两种异常:
假在线的问题,影响倒不是特别大。
假离线的问题,影响还是蛮严重的,我们现在发现的也是这个问题,同时前人估计也是发现了这个假离线的问题,想了一些办法,在底层的调用逻辑里面,关于这个假离线做了一定程度的容错处理。比如发现是离线的,就实时的通过长连接去判断一次,到底是不是真的离线的话,不是的话,业务照常走,同时自动把数据库状态修复一下。
知道有这么个事情,但是不知道怎么发生的,也更不知道怎么去解决了,提供容错率是最好的解决方案了。这也是解决问题里面非常有效的办法了,从侧面去补偿,要得就是很快解决问题,但不一定是最合理的方案。
上面说过几个点,再来简述一下:
看着好像没有什么问题?
本来想举个形象的例子,但是网络这块有些事情可能不完全清楚,所以担心举的例子表达偏了,直接结论吧,重点展示3种场景。
如下图,关于图片就不详细解释了,仔细看一下即可:
接下来再按照时间的趋势,从客户端、服务端、数据库状态以及客户端状态是否异常4个角度去分析一下:
在分析阶段,其实已经得到了这个结论,更准确地说,其实是猜想。基于这种猜想,也许有解决方案,但是似乎并没有验证方案呀,作为一个程序猿,没法测试的bug,那肯定是不行的。
上面最难搞的肯定就是那个网络波动,本地该去怎么模拟呢?很快就有了答案,模拟步骤如下:
不错,问题复现了。
当时也是这样来证明上述猜想的。不过还有一个点,显示离线,万一是真离线呢,最好能够手段证明真的假离线,我们之前做了状态修正的功能,能够把假离线置为在线,所以也可以验证。
想要解决一个问题,就要彻底地明确问题,已知的问题以及解决方案如下:
从某种程度上来说,发现问题的过程,比上面的更重要,也更复杂,需要从已知的仅有的各种数据上去分析,再加上自己过硬的专业知识,可能发现特别不可思议的事情,甚至可能需要突破固有的思维。
想要发现问题,就必须有一个契机,一个可能和问题本身没有直接关系的现象,本次的就是。
客户端所处的网络环境是比较复杂的,所以会经常离线,因此我们做了监听到离线后,就会给相关人发送离线通知邮件,主要逻辑如下:
上线这个功能以后,我们发现每天都会有400次左右的离线记录。客户端大约1500个。说实话,离线的次数还是非常多的。而且注意一点,基本只有超过离线5分钟的话,才会记录一次。而且专门找了一些离线次数特别多的客户,一天有几十次,甚至打电话咨询了一下,最近有没有什么异常,答案是:没有。
我们所统计的记录和我们所认知的那样,以及和实际情况发现对不上了。
1+2可以推断出来的和实际的现象不符合,这个时候基本上已经意识到,哪里出了什么问题。客户的反馈应该没有问题,因为一旦有问题,肯定会联系我们的,但实际上没有找。
不知道是哪里的问题,但肯定是离线相关的,所以一定要从这个地方出发,关于离线,有这么几个事实:
基于上述,我联系了一家离线次数比较多的客户,收集了某一天的日志,同时把我们服务端的日志,从中找出这个客户的客户端的在线和离线日志。
从日志里面,我发现这么几个现象:
第三个现象,其实已经和已有的知识,相悖了。前面提过,发送邮件的机制是:离线了,并且过了5分钟,还是离线的,才会发送。这个发送邮件了,意味当时库里面的状态一定是离线的!但是事实就是,在也没有离线了。
那么问题来了:数据库状态改成了离线,而且是改错了。
当时的想法就是,当他重新连上的时候,一定会改成在线的,但是5分钟后,为什么又会变成了离线呢?所以一定有什么地方改了,思前想后,找到产品所有可能修改地方,但是那些都是由触发机制的,感觉从目前的现象来看,只有一网络波动,就会导致这个事情的发生,所以,当时这个问题就卡在这里了,想不明白了。
一般想不明白的时候,说明已经进入歪道了,得换个脑子,或者找其他人一起过来讨论。我选择了后者,先说了一下我的所有发现以及问题。这个时候,同事注意到了一个关键的现象:
每次离线和重新连上,服务端的日志,都是先在线,后离线。
怎么会是这样呢?肯定是先离线,后在线呀。先在线,后离线,时间差了一丢丢而已。这个非常重要的发现!
我刚开始再分析的时候,我也发现了,因为时间相差不大,不到1分钟,所以我很草率地认为是服务器时间不一致导致的,因为服务端是集群,日志都是随机打在节点上面的。这一点误导了自己!
接下来找了好几个先在线,后离线的情况。把它们全放在一起,做了对比:
2020-08-17 10:35:54,518 INFO com.test.Handler - client: xxx is OFFLINE now
2020-08-17 10:36:28,772 INFO com.test.Handler - client: xxx is ONLINE now
2020-08-17 11:12:04,747 INFO com.test.Handler - client: xxx is ONLINE now
2020-08-17 11:12:34,569 INFO com.test.Handler - client: xxx is OFFLINE now
2020-08-17 11:44:04,227 INFO com.test.Handler - client: xxx is ONLINE now
2020-08-17 11:44:32,707 INFO com.test.Handler - client: xxx is OFFLINE now
时间差的范围刚好是30秒左右!当发现这个的时候,30秒,我非常熟悉呀,是服务端定义的客户端心跳的最大间隔。这一下子就立马把所有的事情串了起来,得出了前文提到的所有的猜想,然后进行了模拟和验证。
解决问题的思路很重要,多做总结和反思,会提高自己解决问题的能力,不断地去优化自己的思考方式。
这个问题困扰了大半年了,这次才因为邮件的问题,和这件事情本身没有啥问题,为契机,才发现了这个问题。
这次的问题是,客户端离线了,客户端发现了,服务端没有发现。
那么有没有另外一种情况,服务端发现了,但是客户端没有发现呢。
还真的有,这种情况非常极端吧,从网络层面上,我这边还没有特别好的解释,线上一千多家客户,目前我们只发现了一家出现了,这个现象比假离线的问题更加严重,客户端没有发现,意味着,不会重连,不重连的话,那一直就是断开,这个就需要另外一种心跳机制去做了。也可以解决。
有空的话,也可以分享一下这个过程,也算是比较复杂,和今天的现象是另外一回事。