微信红包业务,发红包之后如果24小时之内没有被领取完就自动过期失效。
架构设计 业务流程-
老板发红包,此时缓存初始化红包个数,红包金额(单位分),并异步入库。
-
红包数据入延迟队列,唯一标识+失效时间
-
红包数据出延迟队列,根据唯一标识清空红包缓存数据、异步更新数据库、异步退回红包金额
这里我们使用Java内置的DelayQueue来实现,DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。
老板发了10个红包一共200人民币,假装只有9个人抢红包。
发红包,缓存数据进入延迟队列:
@ApiOperation(value="抢红包三",nickname="爪哇笔记")
@PostMapping("/startThree")
public Result startThree(long redPacketId){
int skillNum = 9;
final CountDownLatch latch = new CountDownLatch(skillNum);//N个抢红包
redisUtil.cachevalue(redPacketId+"-num",10);
redisUtil.cachevalue(redPacketId+"-money",20000);
RedPacketMessage message = new RedPacketMessage(redPacketId,24);
RedPacketQueue.getQueue().produce(message);
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();
}
return Result.ok();
}
红包队列消息:
public class RedPacketMessage implements Delayed {
private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final long DELAY_MS = 1000L * 3;
private final long redPacketId;
private final long timestamp;
private final long expire;
private final String description;
public RedPacketMessage(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));
}
public RedPacketMessage(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));
}
public long getRedPacketId() {
return redPacketId;
}
public long getTimestamp() {
return timestamp;
}
public long getExpire() {
return expire;
}
public String getDescription() {
return description;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
}
}
红包延迟队列:
public class RedPacketQueue {
private static DelayQueue queue = new DelayQueue<>();
private RedPacketQueue(){}
private static class SingletonHolder{
private static RedPacketQueue queue = new RedPacketQueue();
}
//单例队列
public static RedPacketQueue getQueue(){
return SingletonHolder.queue;
}
public Boolean produce(RedPacketMessage message){
return queue.add(message);
}
public RedPacketMessage consume() throws InterruptedException {
return queue.take();
}
}
红包延迟队列过期消费,监听任务:
@Component("redPacket")
public class TaskRunner implements ApplicationRunner {
private final static Logger LOGGER = LoggerFactory.getLogger(TaskRunner.class);
@Autowired
private RedisUtil redisUtil;
ExecutorService executorService = Executors.newSingleThreadExecutor(r -> {
Thread thread = new Thread(r);
thread.setName("RedPacketDelayWorker");
thread.setDaemon(true);
return thread;
});
@Override
public void run(ApplicationArguments var){
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



