想必大家应该都点过红包雨,就是各大电商大促时候都会搞的那个活动。
那具体如何实现红包雨的功能呢?我们来看下勇哥的分享。
我服务的一家电商公司也加入了这次热潮,技术团队研发了直播答题功能。答题结束之后,红包会以红包雨的形式落下,用户点击屏幕上落下的红包,若抢到红包,红包会以现金的形式进入用户账户。
红包雨是一个典型的高并发场景,短时间内有海量请求访问服务端,技术团队为了让系统运行顺畅,抢红包采用了基于 Redis + Lua 脚本的设计方案。
1 整体流程
我们分析下抢红包的整体流程 :
-
运营系统配置红包雨活动总金额以及红包个数,提前计算出各个红包的金额并存储到 Redis 中;
-
抢红包雨界面,用户点击屏幕上落下的红包,发起抢红包请求;
-
TCP 网关接收抢红包请求后,调用答题系统抢红包 dubbo 服务,抢红包服务本质上就是执行 Lua 脚本,将结果通过 TCP 网关返回给前端;
-
用户若抢到红包,异步任务会从 Redis 中 获取抢得的红包信息,调用余额系统,将金额返回到用户账户。
2 红包 Redis 设计
抢红包有如下规则:
-
同一活动,用户只能抢红包一次 ;
-
红包数量有限,一个红包只能被一个用户抢到。
如下图,我们设计三种数据类型:
-
运营预分配红包列表 ;
队列元素 json 数据格式 :
{
//红包编号
redPacketId : '365628617880842241'
//红包金额
amount : '12.21'
}
-
用户红包领取记录列表;
队列元素 json 数据格式:
{
//红包编号
redPacketId : '365628617880842241'
//红包金额
amount : '12.21',
//用户编号
userId : '265628617882842248'
}
-
用户红包防重 Hash 表;
抢红包 Redis 操作流程 :
-
通过 hexist 命令判断红包领取记录防重 Hash 表中用户是否领取过红包 ,若用户未领取过红包,流程继续;
-
从运营预分配红包列表 rpop 出一条红包数据 ;
-
操作红包领取记录防重 Hash 表 ,调用 HSET 命令存储用户领取记录;
-
将红包领取信息 lpush 进入用户红包领取记录列表。
抢红包的过程 ,需要重点关注如下几点 :
-
执行多个命令,是否可以保证原子性 , 若一个命令执行失败,是否可以回滚;
-
在执行过程中,高并发场景下,是否可以保持隔离性;
-
后面的步骤依赖前面步骤的结果。
Redis 支持两种模式 : 事务模式 和 Lua 脚本,接下来,我们一一展开。
3 事务原理
Redis 的事务包含如下命令:
序号 | 命令及描述 |
---|---|
1 | MULTI 标记一个事务块的开始。 |
2 | EXEC 执行所有事务块内的命令。 |
3 | DISCARD 取消事务,放弃执行事务块内的所有命令。 |
4 | WATCH key [key ...] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 |
5 | UNWATCH 取消 WATCH 命令对所有 key 的监视。 |
事务包含三个阶段:
-
事务开启,使用 MULTI , 该命令标志着执行该命令的客户端从非事务状态切换至事务状态 ;
-
命令入队,MULTI 开启事务之后,客户端的命令并不会被立即执行,而是放入一个事务队列 ;
-
执行事务或者丢弃。如果收到 EXEC 的命令,事务队列里的命令将会被执行 ,如果是 DISCARD 则事务被丢弃。
下面展示一个事务的例子。
redis> MULTI
OK
redis> SET msg "hello world"
QUEUED
redis> GET msg
QUEUED
redis> EXEC
1) OK
1) hello world
这里有一个疑问?在开启事务的时候,Redis key 可以被修改吗?
在事务执行 EXEC 命令之前 ,Redis key 依然可以被修改。
在事务开启之前,我们可以 watch 命令监听 Redis key 。在事务执行之前,我们修改 key 值 ,事务执行失败,返回 nil 。