点击上方“码农沉思录”,选择“设为星标”
优质文章,及时送达
微信红包业务,发红包之后如果24小时之内没有被领取完就自动过期失效。
这里我们使用Java
内置的DelayQueue
来实现,DelayQueue
是一个无界的BlockingQueue
,用于放置实现了Delayed
接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。
老板发了10个红包一共200人民币,假装只有9个人抢红包。
发红包,缓存数据进入延迟队列:
/**
* 有人没抢 红包发多了
* 红包进入延迟队列
* 实现过期失效
* @param redPacketId
* @return
*/
@ApiOperation(value="抢红包三",nickname="爪哇笔记")
@PostMapping("/startThree")
publicResult startThree(long redPacketId){
int skillNum = 9;
finalCountDownLatch latch = newCountDownLatch(skillNum);//N个抢红包
/**
* 初始化红包数据,抢红包拦截
*/
redisUtil.cacheValue(redPacketId+"-num",10);
/**
* 初始化红包金额,单位为分
*/
redisUtil.cacheValue(redPacketId+"-money",20000);
/**
* 加入延迟队列 24s秒过期
*/
RedPacketMessage message = newRedPacketMessage(redPacketId,24);
RedPacketQueue.getQueue().produce(message);
/**
* 模拟 9个用户抢10个红包
*/
for(int i=1;i<=skillNum;i++){
int userId = i;
Runnable task = () -> {
/**
* 抢红包 判断剩余金额
*/
Integer money = (Integer) redisUtil.getValue(redPacketId+"-money");
if(money>0){
Result result = redPacketService.startTwoSeckil(redPacketId,userId);
if(result.get("code").toString().equals("500")){
LOGGER.info("用户{}手慢了,红包派完了",userId);
}else{
Double amount = DoubleUtil.divide(Double.parseDouble(result.get("msg").toString()), (double) 100);
LOGGER.info("用户{}抢红包成功,金额:{}", userId,amount);
}
}
latch.countDown();
};
executor.execute(task);
}
try{
latch.await();
Integer restMoney = Integer.parseInt(redisUtil.getValue(redPacketId+"-money").toString());
LOGGER.info("剩余金额:{}",restMoney);
} catch(InterruptedException e) {
e.printStackTrace();
}
returnResult.ok();
}
红包队列消息:
/**
* 红包队列消息
*/
publicclassRedPacketMessageimplementsDelayed{
privatestaticfinalDateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 默认延迟3秒
*/
privatestaticfinallong DELAY_MS = 1000L* 3;
/**
* 红包 ID
*/
privatefinallong redPacketId;
/**
* 创建时间戳
*/
privatefinallong timestamp;
/**
* 过期时间
*/
privatefinallong expire;
/**
* 描述信息
*/
privatefinalString description;
publicRedPacketMessage(long redPacketId, long expireSeconds) {
this.redPacketId = redPacketId;
this.timestamp = System.currentTimeMillis();
this.expire = this.timestamp + expireSeconds * 1000L;
this.description = String.format("红包[%s]-创建时间为:%s,超时时间为:%s", redPacketId,
LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault()).format(F),
LocalDateTime.ofInstant(Instant.ofEpochMilli(expire), ZoneId.systemDefault()).format(F));
}
publicRedPacketMessage(long redPacketId) {
this.redPacketId = redPacketId;
this.timestamp = System.currentTimeMillis();
this.expire = this.timestamp + DELAY_MS;
this.description = String.format("红包[%s]-创建时间为:%s,超时时间为:%s", redPacketId,
LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault()).format(F),
LocalDateTime.ofInstant(Instant.ofEpochMilli(expire), ZoneId.systemDefault()).format(F));
}
publiclong getRedPacketId() {
return redPacketId;
}
publiclong getTimestamp() {
return timestamp;
}
publiclong getExpire() {
return expire;
}
publicString getDescription() {
return description;
}
@Override
publiclong getDelay(TimeUnit unit) {
return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
publicint compareTo(Delayed o) {
return(int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
}
}
红包延迟队列:
/**
* 红包延迟队列
*/
publicclassRedPacketQueue{
/** 用于多线程间下单的队列 */
privatestaticDelayQueue<RedPacketMessage> queue = newDelayQueue<>();
/**
* 私有的默认构造子,保证外界无法直接实例化
*/
privateRedPacketQueue(){}
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
*/
privatestaticclassSingletonHolder{
/**
* 静态初始化器,由JVM来保证线程安全
*/
privatestaticRedPacketQueue queue = newRedPacketQueue();
}
//单例队列
publicstaticRedPacketQueue getQueue(){
returnSingletonHolder.queue;
}
/**
* 生产入队
* 1、执行加锁操作
* 2、把元素添加到优先级队列中
* 3、查看元素是否为队首
* 4、如果是队首的话,设置leader为空,唤醒所有等待的队列
* 5、释放锁
*/
publicBoolean produce(RedPacketMessage message){
return queue.add(message);
}
/**
* 消费出队
* 1、执行加锁操作
* 2、取出优先级队列元素q的队首
* 3、如果元素q的队首/队列为空,阻塞请求
* 4、如果元素q的队首(first)不为空,获得这个元素的delay时间值
* 5、如果first的延迟delay时间值为0的话,说明该元素已经到了可以使用的时间,调用poll方法弹出该元素,跳出方法
* 6、如果first的延迟delay时间值不为0的话,释放元素first的引用,避免内存泄露
* 7、判断leader元素是否为空,不为空的话阻塞当前线程
* 8、如果leader元素为空的话,把当前线程赋值给leader元素,然后阻塞delay的时间,即等待队首到达可以出队的时间,在finally块中释放leader元素的引用
* 9、循环执行从1~8的步骤
* 10、如果leader为空并且优先级队列不为空的情况下(判断还有没有其他后续节点),调用signal通知其他的线程
* 11、执行解锁操作
*/
publicRedPacketMessage consume() throwsInterruptedException{
return queue.take();
}
}
红包延迟队列过期消费,监听任务:
/**
* 红包延迟队列过期消费
*/
@Component("redPacket")
publicclassTaskRunnerimplementsApplicationRunner{
privatefinalstaticLogger LOGGER = LoggerFactory.getLogger(TaskRunner.class);
@Autowired
privateRedisUtil redisUtil;
ExecutorService executorService = Executors.newSingleThreadExecutor(r -> {
Thread thread = newThread(r);
thread.setName("RedPacketDelayWorker");
thread.setDaemon(true);
return thread;
});
@Override
publicvoid run(ApplicationArgumentsvar){
executorService.execute(() -> {
while(true) {
try{
RedPacketMessage message = RedPacketQueue.getQueue().consume();
if(message!=null){
long redPacketId = message.getRedPacketId();
LOGGER.info("红包{}过期了",redPacketId);
/**
* 获取剩余红包个数以及金额
*/
int num = (int) redisUtil.getValue(redPacketId+"-num");
int restMoney = (int) redisUtil.getValue(redPacketId+"-money");
LOGGER.info("剩余红包个数{},剩余红包金额{}",num,restMoney);
/**
* 清空红包数据
*/
redisUtil.removeValue(redPacketId+"-num");
redisUtil.removeValue(redPacketId+"-money");
/**
* 异步更新数据库、异步退回红包金额
*/
}
} catch(InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
淘宝订单到期,下单成功后60s之后给用户发送短信通知,限时支付、缓存系统等等。
在 Application
中有接口演示说明,你可以在抢红包 Red Packet Controller
接口中输入任何参数进行测试,也可以配合数据库稍加修改即可作为生产环境的抢红包功能模块。
https://gitee.com/52itstyle/spring-boot-seckill