zoukankan      html  css  js  c++  java
  • 微信高并发抢红包秒杀实战案例

    前言

    群里有小伙伴咨询微信红包的架构,对于我来说,显然是不知道的,但是写一个相对高并发的抢红包案例还是完全可以的。

    架构设计

    业务流程

    • 老板发红包,此时缓存初始化红包个数,红包金额(单位分),并异步入库。

    • 抢红包,判断缓存剩余红包金额,剩余金额大于零则抢到红包,否则手慢了,红包派完了

    • 拆红包,根据 redPacketId 获取分布式锁,如果获取到锁,红包个数减一,如果剩余红包个数大于零抢红包成功、否则失败。成功则计算红包金额,缓存总红包金额减去抢到的红包金额,异步入库、异步到账。

    数据库设计

    • 红包信息表
    CREATE TABLE `red_racket` (
       `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
       `red_packet_id` bigint(20) NOT NULL COMMENT '红包唯一ID',
       `total_amount` int(11) NOT NULL COMMENT '红包金额单位分',
       `total_packet` int(11) NOT NULL COMMENT '红包个数',
       `type` int(11) NOT NULL COMMENT '红包类型',
       `create_time` datetime DEFAULT NULL COMMENT '创建时间',
       `version` int(11) NOT NULL COMMENT '版本号',
       PRIMARY KEY (`id`)
     ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC COMMENT='红包信息表'
    
    • 抢红包记录表
    CREATE TABLE `red_packet_record` (
       `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
       `amount` int(11) NOT NULL COMMENT '抢到红包的金额',
       `red_packet_id` bigint(20) NOT NULL COMMENT '红包ID',
       `uid` int(11) NOT NULL COMMENT '抢到红包用户的用户标识',
       `create_time` datetime DEFAULT NULL COMMENT '创建时间',
       PRIMARY KEY (`id`)
     ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC COMMENT='抢红包记录表'
    

    代码案例

    老板发了10个红包一共200人民币,100个人同时抢红包,伪代码分别为拆红包和抢红包相关业务逻辑。

    模拟抢红包伪代码:

       /**
         * 抢红包 拆红包 抢到不一定能拆到
         * @param redPacketId
         * @return
         */
        @ApiOperation(value="抢红包二",nickname="爪哇笔记")
        @PostMapping("/startTwo")
        public Result startTwo(long redPacketId){
            int skillNum = 100;
            final CountDownLatch latch = new CountDownLatch(skillNum);//N个抢红包
            /**
             * 初始化红包数据,抢红包拦截
             */
            redisUtil.cacheValue(redPacketId+"-num",10);
            /**
             * 初始化红包金额,单位为分
             */
            redisUtil.cacheValue(redPacketId+"-money",20000);
            /**
             * 模拟100个用户抢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);
                        }
                    }else{
                        /**
                         * 直接显示手慢了,红包派完了
                         */
                        //LOGGER.info("用户{}手慢了,红包派完了",userId);
                    }
                    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();
        }
    

    业务层拆红包:

        @Override
        @Transactional
        public Result startTwoSeckil(long redPacketId, int userId) {
            Integer money = 0;
            boolean res=false;
            try {
                /**
                 * 获取锁 保证红包数量和计算红包金额的原子性操作
                 */
                res = RedissLockUtil.tryLock(redPacketId+"", TimeUnit.SECONDS, 3, 10);
                if(res){
                    long restPeople = redisUtil.decr(redPacketId+"-num",1);
                    if(restPeople>=0){
                        /**
                         * 如果是最后一人
                         */
                        if(restPeople==0){
                            money = Integer.parseInt(redisUtil.getValue(redPacketId+"-money").toString());
                        }else{
                            Integer restMoney = Integer.parseInt(redisUtil.getValue(redPacketId+"-money").toString());
                            Random random = new Random();
                            //随机范围:[1,剩余人均金额的两倍]
                            money = random.nextInt((int) (restMoney / (restPeople+1) * 2 - 1)) + 1;
                        }
                        redisUtil.decr(redPacketId+"-money",money);
                        /**
                         * 异步入库
                         */
                        RedPacketRecord record = new RedPacketRecord();
                        record.setMoney(money);
                        record.setRedPacketId(redPacketId);
                        record.setUid(userId);
                        record.setCreateTime(new Timestamp(System.currentTimeMillis()));
                        saveRecord(record);
                        /**
                         * 异步入账
                         */
                    }else{
                        return Result.error("手慢了,红包派完了");
                    }
                }else{
                    /**
                     * 获取锁失败相当于抢红包失败
                     */
                     return Result.error("手慢了,红包派完了");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                if(res){//释放锁
                    RedissLockUtil.unlock(redPacketId+"");
                }
            }
            return Result.ok(money);
        }
    

    演示

    Application中有接口演示说明,你可以在抢红包 Red Packet Controller接口中输入任何参数进行测试,也可以配合数据库稍加修改即可作为生产环境的抢红包功能模块。

    源码

    https://gitee.com/52itstyle/spring-boot-seckill

  • 相关阅读:
    HTML DOM 06 节点关系
    HTML DOM 05 事件(三)
    HTML DOM 05 事件(二)
    HTML DOM 05 事件(一)
    html DOM 04 样式
    html DOM 03 节点的属性
    html DOM 02 获取节点
    html DOM 01 节点概念
    JavaScript 29 计时器
    JavaScript 28 弹出框
  • 原文地址:https://www.cnblogs.com/smallSevens/p/12269699.html
Copyright © 2011-2022 走看看