简介
这里的并发不是高并发,只是将正式环境的一小段流量同时打到我的自测环境。一个请求同时多次发送,真正意义上并发处理同一个数据,主要需求是保证数据幂等性和正确性。
主要技术是用分布式缓存做多次相同请求的幂等处理和用有限状态机来解决MQ消息的不保证有序。
场景
k8s集群可以进行事件监听,静儿这次使用了一个美团内网线下的小集群。把这个小集群的对node节点和pod节点的监听事件发送到MQ,3台服务器在同时工作。也就是说一个事件会被重复收到三次。其中两台机器事件发送基本上是同时的,剩下一台是我自己的电脑。因为在家里,连着vpn,连接公司内网大概有2.5ms的延时。
在MQ事件的接收端,美团内部监控系统CAT上看到数据如下:
问题
当一个请求被重复在并发和有延迟的情况下会被重复收到。k8s自身也会短时间发送一些相同的请求。这些重复的请求在不考虑重复执行的副作用前提下,每次都同样的方式执行,后端的压力也会非常大。如果考虑重复执行的副作用,就是说重复的请求不幂等,数据不准确了,整个服务就非常糟糕了。
另外,不管是MQ还是k8s事件,接收处理事件的服务都不能保证先收到的事件是先产生的。多台机器的情况下,就算是先产生的,也会因为不同机器的处理速度不一样,导致后产生的事件先被执行。
解决
如果下面解决方法中的概念不是很清楚,可以先看5W,再回来看方法。
使用分布式缓存解决请求去重的问题
首先考虑对请求处理的维度。比如静儿举例的场景中,对node节点的watch事件,可以用node作为缓存的key,用node需要处理的关键字段作为value。如果请求中的信息与缓存中的一致,则直接返回不处理。不一致则先更新缓存为最新值,再进行处理。
注意,因为保证消息不会被并发处理。刚才的缓存值获取get和重设put操作都是用分布式锁进行了加锁的。
使用有限状态机解决乱序的问题
之所以有「乱序」这个定义,说明系统本身是消息的顺序是有要求的。顺着这个思路来考虑,可以顺理成章的得到解决方案:设定好原本的一个介绍顺序。如果收到事件A则执行事件B。如果收到事件B则执行事件C。如果没收到事件A先收到了事件B,则把MQ消息打回去重发。在打回去的阶段事件A收到处理完了,这时候再收到事件B就可以继续执行了。
刚才说的这种执行顺序的方法就是有限状态机。有限状态机在程序里的实现主要有两种。一种是通过switch case的方法。就是如果A则B的最容易理解的实现。通常被称为“可执行代码”方式。
另外一种方式是:如果每种状态要做的处理比较复杂。用switch case比较难看。就可以将处理方法抽象成一个类。将这些类的实例放到映射表里。这种实现通常被称为“被动数据”方式。
5W
Q:为什么MQ消息不保证有序?
A:因为MQ消息在服务器上是分区存储的,每个分区自己是有序的。分区被接收端消费的时候。一般也是多个接收端一起消费。中间的每个环节都是只能保证局部有序。如果想全局有序。就需要分区只有一个,并且接收端服务器是单点,而且一次只处理一个请求。
Q:MQ的使用上还有什么关键注意点?
A:一般情况下MQ除了不保证消息有序还不保证消息不重复。因为在「网络不可达」的情况下,MQ不能确认消息接收方收到了消息必然会重试。重试除了本文讲的幂等处理外,还可以采用每个消息有唯一的ID+去重表实现。
Q:什么是分布式缓存?
A:分布式缓存有时候也叫「集中式缓存」。是相对于「本地缓存」而言的。因为在目前的多服务器部署(分布式)时代。「本地缓存」这种将信息存储于当前服务器上,其他服务器无法感知。这时候采用一个专门的缓存服务器就可以解决这个问题。这就是分布式缓存了。
Q:什么是有限状态机?
A:有限状态机也称为FSM(Finite State Machine),是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。FSM可以把模型的多状态、多状态建的转换条件解耦。可以使维护变得容易,代码也更加具有可读性。
总结
并发问题对系统的影响
系统压力造成的可用性问题、多个请求同时对同一个数据产生作用产生的数据准确性问题。
解决方案
可用性问题可以从设计上在业务逻辑层之前对数据去重,例如可以使用分布式锁,让真正耗时的业务逻辑对相同的多个请求只执行一次。
数据准确性问题可以通过有限状态机保证不论收到请求顺序如何,都按照正确的逻辑来执行。