赞
踩
工作中遇到一个关于直播间用户给直播间发红包,以及当前直播间观众领取该红包的项目需求
对于发红包 >>>>>> 抢红包 这中间的有几点关键的点
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='抢红包记录表,抢一个红包插入一条记录';
红包队列设计链路
基于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落库更新
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。