关于抢红包设计的几个思考

 

关于抢红包设计的几个思考


工作中遇到一个关于直播间用户给直播间发红包,以及当前直播间观众领取该红包的项目需求

对于发红包  >>>>>> 抢红包 这中间的有几点关键的点
1、对于直播间发送红包,需要一个红包队列支持

2、抢红包出现的并发问题

1⃣️发送红包至直播间,多人发送,只需要一个队列即可解决所有红包的顺序问题,采用redis队列

那用什么队列比较合适最好

2⃣️抢红包怎么控制并发问题?

先设计数据库模型

红包规格

CREATE TABLE `red_packet_info` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`red_packet_id` varchar(32) NOT NULL COMMENT COMMENT '红包id',
`total_packet` int(11) NOT NULL DEFAULT 0 COMMENT '红包总个数',
`total_packet_detail` varchar(64) NOT NULL DEFAULT 0 COMMENT '红包详情',
`remaining_packet` int(11) NOT NULL DEFAULT 0 COMMENT '剩余红包个数',
`remaining_packet_detail` varchar(64) NOT NULL DEFAULT 0 COMMENT '剩余红包详情',
`user_id` bigint(64) NOT NULL COMMENT '新建红包用户的用户标识',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) ) ,
UNIQUE KEY `uk_red_packet_id` (`red_packet_id`),
ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='红包信息表,新建一个红包插入一条记录';

红包领取记录表

CREATE TABLE `red_packet_record` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`red_packet_id` varchar(32) NOT NULL COMMENT COMMENT '红包id',
`award_id` varchar(32) NOT NULL DEFAULT '0' COMMENT '抢到礼物id',
`award_name` varchar(32) NOT NULL DEFAULT '0' COMMENT '抢到礼物名称',
`user_id` int(20) NOT NULL COMMENT '抢到红包用户的用户标识',
`user_name` varchar(32) NOT NULL DEFAULT '0' COMMENT '抢到红包的用户的用户名',
`user_head` varchar(256) NOT NULL DEFAULT '0' COMMENT '抢到红包的用户的头像',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) ),
KEY `uk_red_packet_id_user_id`(`red_packet_id`,`user_id`) USING BTREE,
ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='抢红包记录表,抢一个红包插入一条记录';

红包队列设计链路

阻塞队列(Blocking Queue)

基于Redis的Redisson分布式无界阻塞队列(Blocking Queue)结构的RBlockingQueue Java对象实现了java.util.concurrent.BlockingQueue接口。

尽管RBlockingQueue对象无初始大小(边界)限制,但对象的最大容量受Redis限制,最大元素数量是4 294 967 295个。

放入数据:
  offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,
    则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)
  offer(E o, long timeout, TimeUnit unit),可以设定等待的时间,如果在指定的时间内,还不能往队列中
    加入BlockingQueue,则返回失败。
  put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断
    直到BlockingQueue里面有空间再继续。

获取数据:
  poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,
    取不到时返回null;
  poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,
    队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
  take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到
    BlockingQueue有新的数据被加入; 
  drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数), 
    通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。

对于红包队列最好使用有界队列,但是redis的Blocking Queue是有最大容量限制,不用考虑会撑爆内存的问题

一、添加红包队列

        RBlockingQueue<String> queue = redissonClient.getBlockingQueue(userId);
        boolean isOffer = queue.offer(redPacketId);
        if(!isOffer){
            log.error("redPacket queue is full","红包队列已满");
        }
        return queue.size();

获取队列首个

take是阻塞方法,采用peek

RBlockingQueue<String> queue = blockingQueueInfo(liveId);

        List<String> curRedPacketList = new ArrayList<>();
        Iterator<String> listOfRedPacket = queue.iterator();
        while (listOfRedPacket.hasNext()){
            curRedPacketList.add(listOfRedPacket.next());
        }
        String headElement = queue.peek();

二、抢红包

针对抢红包的过程,其实很简单,控制好红包的拆分以及用户重复领取就可以了,当然抢的过程要保持好的性能。

性能瓶颈:1、抢红包      原子操作,保证红包个数小于0的时候扣减失败

                  2、拆红包     CAS落库更新

猜你喜欢

转载自blog.csdn.net/weixin_39082432/article/details/112682182
今日推荐