在 IM 系统中,“红包” 是一种很常见的应用。在前面的文章 (见IM专题:IM需求分析模型)中,我们曾分析过,“红包” 属于扩展功能需求,“消息” 属于基础功能需求,红包功能的实现需要基于消息功能的实现。
在文章(见IM专题:分层架构IM系统(12)—消息收发逻辑实现)中,我们深入分析过消息的收发流程;在 IM 的服务化架构中,消息收发逻辑与在分层架构中类似,见下图,我们快速回顾一下。
图中箭头表示数据流向。IM 服务化架构中,消息收发也包括三个阶段:
一、生产消息阶段
客户端 uid=101基于与入口服务 tcp-entry 之间的 tcp 长连接,发送消息到 tcp-entry;tcp-entry 不处理任何业务逻辑,将消息通过 RPC 转发到 消息服务 msg-logic;msg-logic 首先通过调用 msg-das 原子服务将消息保存到消息库,再通过调用 contacts-das 原子服务将联系人信息保存到联系人库;最后,msg-logic 回复 tcp-entry,tcp-entry 回复客户端;生产消息阶段结束。
二、推送消息阶段
msg-logic 通过调用 cache-das 原子服务访问 redis 缓存,判断客户端 uid=102 是否在线:如果用户 uid=102 离线,msg-logic 通过 MQ 通知 offline-extlogic 进行离线消息的用户触达逻辑;如果用户 uid=102 在线,msg-logic 通过 RPC 将消息推送到 入口服务 ws-entry,由 ws-entry 通过与客户端 uid=102 之间的 websocket 长连接,将消息推送到客户端 uid=102;推送消息阶段结束。
三、确认消息阶段
客户端 uid=102 接收到消息后,回复 ack 包到 ws-entry,ws-entry 转发 ack 包到 msg-logic,msg-logic 通过时间轮逻辑(见IM专题:分层架构IM系统(13)—时间轮方案实现)处理所有消息接收方回复的 ack 包;若 msg-logic 没有及时收到 ack 包,则仍然会通过 MQ 通知 offline-extlogic 进行离线消息的用户触达逻辑;确认消息阶段结束。
基于消息的收发逻辑,红包实现方案应该如何设计呢?
红包是一个类秒杀的高并发应用,尤其是在一个非常活跃的群里,群成员会在一个非常集中的时间内进行访问操作,系统会出现一个瞬时的高吞吐。
红包方案设计的关键就在于如何解决高吞吐的问题;在高吞吐量的请求中,大部分的请求是读的操作,只有少量的抢到红包的请求才会有写的操作;对于抗高吞吐读的常用策略,一般会使用缓存。红包实现方案见下图。
红包是一种特殊的消息类型,它的特殊性在于消息内容是一个H5页面。红包逻辑包括两个阶段:发红包和收红包,在操作上,收红包包括抢红包和拆红包。
一、发红包
如图,群成员 uid=101 向群里发送红包。
- 客户端 uid=101 发送红包群消息,客户端 uid=102、uid=103、uid=104 分别收到了这个红包群消息;这个逻辑与群消息逻辑完全一致(见IM专题:分层架构IM系统(14)—群消息逻辑实现);
- IM-Server(具体地说是 msg-logic 服务)将红包消息发送给 MQ,由红包服务来消费;
- 红包服务先将红包数据写入数据库,再将数据写入缓存,数据库与缓存数据保持一致;
- 数据库中 “红包计数表” 包括 (总金额,总个数,剩余金额,剩余个数)4个关键字段;
- 缓存中 “红包计数记录” 包括 (剩余金额,剩余个数)2个关键数值。
二、收红包
看到红包的群成员,点击红包的 H5 页面,该页面包括 “抢红包” 和 “拆红包” 两步操作,由红包服务分别响应抢红包和拆红包;“抢红包” 和 “拆红包” 的两步操作,既从业务体验上增加了趣味性,也为降低数据库的访问压力准备了设计空间。
- 在响应 “抢红包” 的操作时,红包服务读取缓存记录,判断 “剩余个数” ,若为 0,则显示红包被抢光;在活跃的群里面,大部分群成员在抢红包的过程中,止步于此;
- 在响应 “拆红包” 的操作时,红包服务会首先再读取一次缓存,毕竟从抢到红包到拆红包这个间隙,红包个数是在快速锐减的;此时若 “剩余个数” > 0,红包服务会去更新数据库;
- 流程能到更新数据库这一步,不代表一定能抢到红包,因为缓存和数据库不是一个原子性操作,当缓存中表示有剩余红包时,往往会有多个客户端流量流向数据库对数据库进行并发访问;不过无序担心,此时访问数据库的流量经过前两次缓存的抵挡,已经非常少了;为了避免红包的 “超卖” 问题,更新数据的 SQL 语句需要加上必要的条件:update 红包计数表 set 剩余金额=剩余金额-红包金额,剩余个数=剩余个数-1 where 剩余个数 >=1;
- 当更新数据库成功时,则表示红包已到手;此时红包服务去更新缓存:set 剩余金额 剩余金额-红包金额;set 剩余个数 剩余个数-1 ;更新后的缓存数据会抵御后面大部分千军万马的抢红包流量。
最后,总结文中关键:
- 红包逻辑包括发红包和收红包两个阶段;
- 发红包阶段,红包服务先写数据库,再写缓存;
- 数据库红包计数表结构为:(总金额,总个数,剩余金额,剩余个数),缓存红包计数记录结构为:(剩余金额,剩余个数);
- 收红包阶段,包括抢红包和拆红包两步操作;
- 抢红包操作,红包服务会读缓存数据;
- 拆红包操作,红包服务会先读缓存,再更新数据库,最后更新缓存。
思考两个问题:
1、整个红包逻辑中,任何一个节点挂掉后,整个系统的可用性如何保证?
2、在春节这样全民欢乐的时刻,全国会有许许多多的发红包的活跃群,整个系统如何抵御高吞吐,数据库如何降低高负载?
作者:棕生
来源:架构之魂
原文:https://mp.weixin.qq.com/s/qOjrZqTOgnqvX-l-pN7rcQ
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。