幂等性设计
- 使用唯一id;
- 对于处理过的消息进行保存,收到消息后,根据消息的唯一id更新查询,判断消息是否处理过;
看到过一些方案,基本的思路都是基于消息确认机制。
首先,是消息丢失会丢在哪些可能的环节?
-
发送方 到 消息队列 之间出问题
-
消息队列内部出问题,比如持久化出问题
-
消费方消费的时候出问题
这里要结合使用的消息队列来考虑下了:
-
发送方调用向消息队列发送的接口,是否支持ack机制。如果发送失败了,可以尝试重试。(重试的话,需要保证消费的消息是支持防止重复的)
-
消息队列现在已经比较成熟了,一般不会出现内部丢消息。step2可以先忽略。(或者是使用姿势不对,比如需要重启,当时没有配置持久化;)
整理下看到过的一些思路:
方案零:
- 利用消息队列的有序性来验证是否有消息丢失。原理非常简单,在发送端,给每个发出的消息附加一个连续递增的序号,然后在接受端来检查这个序号的连续性。
方案一:
- 发送方发送消息之后,挂一个timer,如果在timer到期之后,还没有收到rsp,那就认为出现的了超时。超时有两种可能,第一种是消息在路上丢掉了,第二种是收消息的一方挂了,或者是消息堆积了,没有来得及处理。
方案二: (这个也是对账的一个基本思路)
-
发送方在发送的时候,在db中插入一条记录;消息的接收方在收到消息之后,从db查询到这条记录,然后更新db的状态位。
这样做后,即使出现了消息丢失,也可以通过定时任务扫描出一段时间内未完成的消息,进行重新投递.
发送方太快,收消息方太慢。速度不匹配。
- 发送方降低速度;
- 收消息方的平滑配置不合理,处理的太慢;
- 收消息方 多开实例,优化性能热点,加快消息消费 等
消息a要在消息b之前被消费? (比如,在游戏中,创建新地图的消息需要在 玩家进入新地图之前被处理; 或者,在电商中,用户下单后,扣款的消息要在发货的消息之后被处理)
大概的思路是:
-
让消息a和消息b发到同一个queue中,串行化处理。
-
让消息a和消息b走同一条消息转发的链路
分布式事务是一个后台开发的比较头疼的问题。在实践中,有很多这样的场景。比如用户某个操作,需要在进程a上扣10元,需要在进程b发上奖励积分。
如果顺序执行的话,很有可能在a上扣除10元成功后,在b上发送失败。失败的原因会有很多种,比如积分达到上限,或 超时(这种最坑 从调用方的角度,甚至不知道是失败还是成功,如果冒然重试的话,有可能出现多发)。
如果从整体上看的话,当然是系统这个跨进程的操作本身是原子的,要不都成功,要不都失败。业内有些方案来解决这个问题,但是这些方案不能照抄,要考虑下是否可以结合自己的业务落地。
通用的解决方案的核心思想就是2pc。将一个事务分为协调者,和执行者。同时,分成两个阶段,准备阶段(check阶段)和提交阶段(ack阶段)。当执行一个事务的时候,协调者通知执行者,开始执行准备阶段。
结合我们刚才的那个例子,具体步骤就是:
-
在进程a上查看用户是否可以成功扣除10元,但是不会正在的扣除,只是check;
-
在进程b上查看用户是否可以成功加积分。(同样也是 不会真的加,只是check)
如果都check成功后,进入下一步,执行阶段。结合上面的例子,具体的步骤是:
-
进程a开始扣10元
-
进程b开始发积分
看起来很完美,一切顺利。不过对于很多游戏的后台来说,有个比较困难的问题,游戏里面要修改角色的数据,为了性能是不会是直接修改db的,而是修改缓存。通过write back的机制,再将缓存的数据写入到db。
那这样问题就来了,这里并没有一个很好的机制去“锁定资源”。比如,当我们check的时候,角色身上有20元,可以check扣款成功。当时在异步的过程中,这里有可能会有其他的请求进来,把给角色扣了15元。甚至角色都有可能传送走了,或者是下线。对于这种业务的后台来说,很难去锁定资源(锁定角色 或者说是锁定角色身上的这10元)。
因此,顺着这个思路往下想解决办法,可能会有两种。分别是:
-
克服些困难,去给他锁上。
-
在check的时候,先与扣除,如果后面失败了,在补发。
-
方案1的问题是:
比如在执行这个分布式的事务中,我们锁定了角色,不允许它传送,不允许下线,不允许其他业务去给他扣钱。
那可能会带来很多问题,如果这个分布式事务超时了,正常情况下最多锁定50毫秒事务就执行完毕了;但是,超时的时候,锁定了30秒,还没收到解锁的rsp。那这种会block其他非常多的业务。其他的业务怎么去补?
比如其他的业务是玩法结束,踢人,地图销毁。那这个带锁的角色,要怎么办?卡在里面30秒。这种方案耦合了太多的业务逻辑,实现起来有很多坑,非常复杂。
-
方案2的问题是:
如果失败要补发的时候,角色下线了,或者是传送走了,怎么办?
这里虽然是问题,当时不会很大。可以提供一个通用的补偿机制,比如发离线奖励邮件等。或者是离线消息,下次登录的时候补偿等。
除此之外,这里还要考虑玩家主动获利的场景。刚才的例子正好玩家不会主动获利,如果事务是先给玩家发10元,再扣除一个道具。
那么,肯定会有玩家卡bug,比如在获得10元奖励后,立即下线。这样就扣不到自己的道具了。所以,再设计分布式事务的时候,需要考虑不要让玩家去主动获利。