A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

前言
微信红包业务,发红包之后如果24小时之内没有被领取完就自动过期失效。
架构设计
业务流程
  • 老板发红包,此时缓存初始化红包个数,红包金额(单位分),并异步入库。
  • 红包数据入延迟队列,唯一标识+失效时间
  • 红包数据出延迟队列,根据唯一标识清空红包缓存数据、异步更新数据库、异步退回红包金额

代码案例
这里我们使用Java内置的DelayQueue来实现,DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。
老板发了10个红包一共200人民币,假装只有9个人抢红包。
发红包,缓存数据进入延迟队列:
   /**     * 有人没抢 红包发多了     * 红包进入延迟队列     * 实现过期失效     * @param redPacketId     * @return     */    @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);        /**         * 加入延迟队列 24s秒过期         */        RedPacketMessage message = new RedPacketMessage(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();        }        return Result.ok();    }
红包队列消息:
/** * 红包队列消息 */public class RedPacketMessage implements Delayed {    private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");    /**     * 默认延迟3秒     */    private static final long DELAY_MS = 1000L * 3;    /**     * 红包 ID     */    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<RedPacketMessage> queue = new DelayQueue<>();    /**     * 私有的默认构造子,保证外界无法直接实例化     */    private RedPacketQueue(){}    /**     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例     * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载     */    private static class SingletonHolder{        /**         * 静态初始化器,由JVM来保证线程安全         */        private  static RedPacketQueue queue = new RedPacketQueue();    }    //单例队列    public static RedPacketQueue getQueue(){        return SingletonHolder.queue;    }    /**     * 生产入队     * 1、执行加锁操作     * 2、把元素添加到优先级队列中     * 3、查看元素是否为队首     * 4、如果是队首的话,设置leader为空,唤醒所有等待的队列     * 5、释放锁     */    public  Boolean  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、执行解锁操作     */    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

以上文章转载于网络,有问题欢迎联系学姐

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马