文章目录
- 背景
- 表结构设计
- 实现流程
- 流程图
- 流程解析
- 流程优化
背景
这是一个来自于NFT电商项目,这是个营销策略的需求,为了快狠准,短期内刺激消费,拉动销售增加购买量。运营人员可以在平台创建红包,并且设置该红包的发放时间段、个数、总金额、金额分摊策略(0-平均、1-随机)、NFT系列(0-不分系列),红包可作为NFT购买时的抵用券。然后用户可以在某个页面看到红包活动,对于进行中的红包活动,用户可以点击抢红包,获取红包,最后,用户可以查看自己抢到的红包。整个业务流程不复杂,难点在于抢红包这个行为可能有很高的并发。所以,系统设计的优化点主要关注在抢红包这个行为上。
抢红包的特点是有短时间段内的高并发,且也是读多写少的场景。
该特征与抢购的特征一样,所以也可以使用限流+缓存的方式来做架构设计。我们这里主要介绍业务流程。
由于查看红包过于简单,所以这里不讨论。这里主要关注创建红包、抢红包两种实现。
创建红包:运营设置红包总金额、总数量、有效时间段、金额分摊策略、NFT系列ID
抢红包:用户从总红包中随机获得一定金额
表结构设计
红包活动表,用于记录每次发放的红包整体信息。需要在每次
CREATE TABLE `t_redpack_activity`
(
`id` bigint(20) NOT NULL COMMENT '主键',
`nft_series_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'nft系列表ID,0-不分系列',
`total_amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '总金额',
`surplus_amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '剩余金额',
`total` bigint(20) NOT NULL DEFAULT '0' COMMENT '红包总数',
`surplus_total` bigint(20) NOT NULL DEFAULT '0' COMMENT '红包剩余总数',
`status` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '红包活动状态 1-有效; 2-失效',
`version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本号',
`start_time` bigint(13) unsigned NOT NULL DEFAULT '0' COMMENT '开始时间',
`end_time` bigint(13) unsigned NOT NULL DEFAULT '0' COMMENT '结束时间',
`create_time` bigint(13) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
`create_user` varchar(32) COLLATE utf8mb4_bin NOT NULL DEFAULT ' ' COMMENT '创建人',
`update_time` bigint(13) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
`update_user` varchar(32) COLLATE utf8mb4_bin NOT NULL DEFAULT ' ' COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='红包活动表';
明细表,用于记录红包被谁领取,红包的金额
CREATE TABLE `t_redpack_detail`
(
`id` bigint(20) NOT NULL COMMENT '主键',
`activity_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '红包活动ID',
`amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '金额',
`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '用户编号',
`status` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '红包状态 1可用 2不可用',
`version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本号',
`create_time` bigint(13) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
`update_time` bigint(13) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='红包明细表';
这里有个小小的疑问,就是为什么需要提前给红包明细表生成数据呢?事实上考虑到如果红包明细信息是在抢红包中去创建那么会使整个抢红包流程变得比较慢,所以采用 预先分配数据的方案。
实现流程
流程图
流程解析
- 首先声明由于这里使用分库分表、读写分离,所以数据库的所有操作都是无事务的操作。
- 客户端获取到红包活动数据,根据开始时间和和结束时间判断显示的状态(活动中;活动结束;已抢光)
- 客户端请求服务端抢红包接口
- 服务端根据用户ID、活动ID做校验。
– 根据用户ID判断该用户是否领取过红包,领取过返回客户端提示已领取;未领取进行活动的校验
– 根据活动ID,查询红包活动表查看活动是否进行中,是否抢光,是否有效。有校验不通过的返回客户端相关提示,校验通过继续下面的流程 - 查询出红包明细表中所有用户编号为空的记录乱序后选取前5个放入java内存队列中,从队列中取出一条记录,更新它的用户编号、更新时间。更新失败,则去下一个,都失败则提示让用户自己重试;成功则返回成功信息,并显示获取的金额。
- 更新红包活动表的剩余金额和剩余红包数字段
流程优化
1、对于步骤1查询是否抢光的流程,可以增加java本地内存和redis缓存的优化。在java内存中存储一个字段表示是否已经抢光,抢光则直接返回抢光的提示,这样就不需要操作数据库。在运营创建红包活动后同步红包活动的信息到redis内存中,这样可以不读取mysql数据库,直接使用redis的数据进行校验活动是否开始,而是否抢光的判断就不进行了,直接根据获取明细表中的未更新用户编号数据校验是否抢光了。
2、第三步和第四步之间可能由于实例崩溃导致更新了明细表却没更新活动表,所以当查询到明细表用户编号为空的数据集合为空时则表示红包已经抢光了。则设置java内存中是否抢光的标识为抢光。
3、由于明细表是高并发且做了读写分离,所以明细表的更新操作要根据版本号做乐观锁操作,最终都要根据返回值判断是否更新成功。
4、之所以提前生成明细数据是使用了高性能设计中的预计算的处理方案来提高系统的性能。