这是一个来自于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='红包明细表';
这里有个小小的疑问,就是为什么需要提前给红包明细表生成数据呢?事实上考虑到如果红包明细信息是在抢红包中去创建那么会使整个抢红包流程变得比较慢,所以采用 预先分配数据的方案。
1、对于步骤1查询是否抢光的流程,可以增加java本地内存和redis缓存的优化。在java内存中存储一个字段表示是否已经抢光,抢光则直接返回抢光的提示,这样就不需要操作数据库。在运营创建红包活动后同步红包活动的信息到redis内存中,这样可以不读取mysql数据库,直接使用redis的数据进行校验活动是否开始,而是否抢光的判断就不进行了,直接根据获取明细表中的未更新用户编号数据校验是否抢光了。
2、第三步和第四步之间可能由于实例崩溃导致更新了明细表却没更新活动表,所以当查询到明细表用户编号为空的数据集合为空时则表示红包已经抢光了。则设置java内存中是否抢光的标识为抢光。
3、由于明细表是高并发且做了读写分离,所以明细表的更新操作要根据版本号做乐观锁操作,最终都要根据返回值判断是否更新成功。
4、之所以提前生成明细数据是使用了高性能设计中的预计算的处理方案来提高系统的性能。