博主结合实际开发的游戏项目来说明:
一、服务之间解耦
游戏系统通常有角色服务、物品服务、任务服务等,
每个游戏都会有任务面板,用来引导玩家体验游戏、提高留存率等等,
在消息队列相关的软件还没出来的时候,
比如一个任务是要求玩家达到10级,那么玩家在升到10级的时候,角色服务需要RPC调用任务服务的某个接口,来告诉任务服务说角色达到了10级,其实角色服务并不关心任务相关的业务内容,这样做的话,就将角色服务和任务服务耦合到了一起。
然后如果还有一个奖励服务,策划说玩家达到10级的时候,系统赠送一些奖励,那么角色服务又需要在升到10级的时候,RPC调用奖励服务的某个接口,让奖励服务下发奖励,这样角色服务又和奖励服务耦合在了一起。
我们之所以将系统拆成角色服务、任务服务、奖励服务,就是为了让各个服务只需要关注自己的业务,而不必受到其他服务的影响。像上面的例子,如果策划改主意了,说不要奖励了,那么程序员又要去角色服务把这个RPC调用奖励服务的接口给删除掉。明明是不要奖励了,但是代码的调整却要角色服务来调整,这样服务的业务边界就显得有些混杂,不够清晰。
如果我们使用了消息队列,那么上面情况就变成了:
玩家达到10级,角色服务往消息队列中推送一个消息“玩家10级了”,就不管其他的了,然后任务服务和奖励服务,根据自己的需求,去订阅消息队列中相关的消息,这就达到了服务之间解耦的目的。这个时候策划说不要奖励了,只需要奖励服务不再订阅“玩家达到10级”这个消息就行了,角色服务不用变动,相应的,如果有新的服务需要这个消息,也不用新增RPC调用,直接订阅这个消息就好了。
二、异步操作
实质是将同步执行的业务拆成可以异步并行的流程。
比如游戏中有商城,玩家购买一个商品时,会使用优惠券,扣除金币,且购买后可以得到商城积分和商品。那么玩家一次完整的同步(串行)购买流程就是(从点击确认购买开始):扣除优惠券数量(20ms) -> 扣除金币(20ms) -> 增加商城积分(20ms) -> 增加物品(20ms),我们假设每个步骤都是20ms,那么总计需要 4 * 20ms = 80ms。
那现在我们根据业务,将这个流程拆解 为异步(并行)流程:
玩家购买商品,点击确认购买后:将这个消息放入消息队列,优惠券服务、角色服务、商城服务、物品服务同时收到这个消息,
优惠券和金币同时扣除,且同时间增加商城积分,最后再单独执行增加物品,也就是前面三个步骤同时进行,耗时20ms,加上最后的增加物品(20ms),总计40ms,这就直接提升了2倍的效率。
当然,这样需要有事务的保证,其中一环除了问题,其他环节也是需要回滚的,具体的实现方案,需要结合实际应用场景进行考虑,是要进行同步还是异步,是否利大于弊。(游戏设计中,一般是先扣除,再新增,避免刷金币之类的漏洞被利用,损害公司利益。)
三、削峰
游戏有时候因为机房故障或者其他原因导致玩家卡顿、无法登录等事故时,往往需要在服务正常后通过邮件表示歉意并发放一些补偿,那么这个时候由于受影响的玩家体量是巨大的,假设有1万人需要收到这封邮件(实际可能多得多),那么我们的后台邮件系统真的要给自己制造一次并发量为1W的请求给自己添堵吗?显示不合适。
那么我们这个时候可以利用消息队列,将生成的1万封邮件放入消息队列中,然后邮件服务订阅这个发送补偿邮件的消息,根据自己服务的能力,去消费这些消息,这样就不会造成在某一时间大并发的请求造成服务器资源消耗过大甚至异常等情况。
削峰的实质就是将不需要实时让用户感知到的行为通过存储消息队列,延长执行时间来换取服务器性能稳定。所谓的时间换空间。
当然,这样的队列我们也可以简单的通过代码来实现,但是这样的话,我们就不仅仅需要考虑业务,还要考虑消息的存储、持久化、可用性、可靠性等等问题,这些也是消息队列单独被抽出来做成中间件的原因了。