vivo 互联网服务器团队 - Wang Zhi一、业务背景
使用RocketMQ 或者Kafaka等消息队列来存储上报的消息,但是消费侧需要考虑在业务进程中按照游戏维度进行聚合,其中技术细节涉及按照游戏维度进行拆分,在满足消息时效性和批量性的前提下触发上报。在这种方案下消息中间件扮演的角色本质上消息的中转站,没有解决任何业务场景中提及的游戏维度拆分、批量性和时效性。方案二
在方案一的基础上,寻求一种技术方案来解决游戏维度的消息分组、批量消费 、时效性。通过Redis的list结构来实现队列(进一步要求实现定长队列)来解决游戏维度的消息分组;通过Redis的list支持的Lrange来实现批量消费;通过业务侧的多线程来解决时效问题,针对高频的游戏使用单独的线程池进行处理,上述两个手段能够保证消费速度大于生产速度。方案对比
对比两种方案后决定使用Redis的实现了一个伪消息中间件:整体的技术方案如下图所示:
- 通过List对象实现定长队列来保存游戏维度的行为消息(以游戏作为key的List对象来保存用户行为);
- 通过List来保存所有的存在行为数据的游戏列表;
- 通过Set来进行去重判断来保证2中的List对象的唯一性。
步骤一:游戏维度的某行为数据PUSH到游戏维度的队列当中。消费过程
步骤二:判断游戏是否在游戏的集合Set中,如果在就直接返回,如果不在进行步骤三。
步骤三:往游戏列表中PUSH游戏。
步骤一:从游戏对象的列表中循环取出一款游戏。三、技术原理
步骤二:通过步骤一获取的游戏对象去该游戏对象的行为数据队列中批量获取数据处理。
3.2 List 对象
- Lua 脚本内多个命令以原子性的方式执行,保证了命令执行的线程安全。
- Lua 脚本结合List命令实现定长队列,实现批量消费。
- Lua 脚本仅支持单个key的操作,不支持多key的操作。
- List的基础命令包括计算List的长度,移除数据,添加数据,整体命令的复杂度都在O(N)的常量时间。
- 整合上述三个命令,我们能保证实现固定长度的队列,通过判断队列长度是否达到定长结合新增队列元素和移除队列元素来完成。
3.3 Set 对象
- List的基础命令包括批量返回数据和裁剪数据,整体命令的复杂度都在O(N)的常量时间。
- 整合上述两个命令,我们能够批量消费数据并移除队列数据,通过LRANGE批量返回数据并通过LTRIM保留剩余数据。
- 通过整合llen+rpush+lpop三个命令实现定长队列。
- 通过lua脚本保证上述命令的原子性执行。
- 通过整合lrange+ltrim两个命令实现消息的批量消费。
- 通过lua脚本保证上述命令的原子性执行。
4.3 注意事项
- 整体的执行流程如上图所示,核心理念通过lua脚本的原子性保证了数据获取(Lrange)和数据裁剪(Ltrim)的原子性执行。
- 整体的消费流程选择pull模式,通过多线程循环轮询可消费的队列进行消费。与借助于redis的pub/sub的通知机制实现消费流程的push模式相比,pull模式成本更低效果更佳。
欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) | Powered by Discuz! X3.4 |