• 通过redis实现的一个抢红包流程,仅做模拟【上】


    建议结合下一篇一起看

    下一篇

    数据结构+基础设施

    数据结构

    这里通过spring-data-jpa+mysql实现DB部分的处理,其中有lombok的参与

    @MappedSuperclass
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class BaseEntity {//公共基础实体字段
        @Id //标识主键 公用主键
        @GeneratedValue //递增序列
        private Long id;
        @Column(updatable = false) //不允许修改
        @CreationTimestamp //创建时自动赋值
        private Date createTime;
        @UpdateTimestamp //修改时自动修改
        private Date updateTime;
    }
    @Entity //标识这是个jpa数据库实体类
    @Table
    @Data   //lombok getter setter tostring
    @ToString(callSuper = true) //覆盖tostring 包含父类的字段
    @Slf4j  //SLF4J log
    @Builder //biulder模式
    @NoArgsConstructor //无参构造函数
    @AllArgsConstructor  //全参构造函数
    @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
    public class RedPacketInfo extends BaseEntity implements Serializable {//红包信息表
        private String red_packet_id;//红包ID
        private int total_amount;//总金额
        private int total_packet;//总红包数
        private int remaining_amount;//剩余金额
        private  int remaining_packet;//剩余红包数
        private String user_id;//发红包用户ID
    }
    @Entity //标识这是个jpa数据库实体类
    @Table
    @Data   //lombok getter setter tostring
    @ToString(callSuper = true) //覆盖tostring 包含父类的字段
    @Slf4j  //SLF4J log
    @Builder //biulder模式
    @NoArgsConstructor //无参构造函数
    @AllArgsConstructor  //全参构造函数
    @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
    public class RedPacketRecord extends BaseEntity implements Serializable {//抢红包记录表
        private int amount;
        private String red_packet_id;
        private String user_id;
    }

    REDIS数据结构

    REDIS对于一个红包存储3部分信息:

    1、KEY:红包ID+_TAL_PACKET VALUE:红包剩余数量

    2、KEY:红包ID+_TOTAL_AMOUNT VALUE:红包剩余金额

    3、KEY:红包ID+_lock VALUE:红包分布式锁

    操作REDIS基础方法

      private static final TimeUnit SECONDS = TimeUnit.SECONDS;
        private static final long DEFAULT_TOMEOUT = 5;
        private static final int SLEEPTIME = 50;
    
        /**
         * 获取分布式锁  2019
         * @param lockKey
         * @param timeout
         * @param unit
         */
        public boolean getLock(String lockKey, String value, long timeout, TimeUnit unit){
            boolean lock = false;
            while (!lock) {
                //设置key自己的超时时间
                lock = redisTemplate.opsForValue().setIfAbsent(lockKey, value,timeout,unit);
                if (lock) { // 已经获取了这个锁 直接返回已经获得锁的标识
                    return lock;
                }
                try {
                    //暂停50ms,重新循环
                    Thread.sleep(SLEEPTIME);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return lock;
        }
    
        /**
         * 按照默认方式获得分布式锁  2019
         * @param lockKey
         * @return
         */
        public boolean getLock(String lockKey){
            return getLock(lockKey,String.valueOf(new Date().getTime()),DEFAULT_TOMEOUT,SECONDS);
        }

    /**
     * 获取指定 key 的值
     *
     * @param key
     * @return
     */
    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
    
    /**
         * 设置指定 key 的值
         *
         * @param key
         * @param value
         */
    public void set(String key, String value) {
         redisTemplate.opsForValue().set(key, value);
    }

    DAO

    public interface RedPacketInfoRepository extends JpaRepository<RedPacketInfo, Long> {
        @Query("select o from RedPacketInfo o where o.red_packet_id=:redPacketId")
        public RedPacketInfo findByRedPacketId(@Param("redPacketId") String redPacketId);
    }
    public interface RedPacketRecordRepository extends JpaRepository<RedPacketRecord,Long> {
    }

    配置

    @Component
    @EnableAsync//开启异步注解,回写处
    public class RedPacketConfig implements ApplicationRunner {
      //启动自动发一个红包 @Autowired RedPacketService redPacketService; @Override
    public void run(ApplicationArguments args) throws Exception { String userId = "001"; redPacketService.handOut(userId,10000,20); } /** * 引入随机数组件 * @return */ @Bean public RandomValuePropertySource randomValuePropertySource(){ return new RandomValuePropertySource("RedPackeRandom"); } }

    发红包

    发红包通常没有特别需要处理高并发的点

     /**
         * 发红包
         * @param userId
         * @param total_amount 单位为分,不允许有小数点
         * @param tal_packet
         * @return
         */
        public RedPacketInfo handOut(String userId,int total_amount,int tal_packet){
            RedPacketInfo redPacketInfo = new RedPacketInfo();
            redPacketInfo.setRed_packet_id(genRedPacketId(userId));
            redPacketInfo.setTotal_amount(total_amount);
            redPacketInfo.setTotal_packet(tal_packet);
            redPacketInfo.setRemaining_amount(total_amount);
            redPacketInfo.setRemaining_packet(tal_packet);
            redPacketInfo.setUser_id(userId);
            redPacketInfoRepository.save(redPacketInfo);
    
            redisUtil.set(redPacketInfo.getRed_packet_id()+TAL_PACKET, tal_packet+"");
            redisUtil.set(redPacketInfo.getRed_packet_id()+TOTAL_AMOUNT, total_amount+"");
    
            return redPacketInfo;
        }
    /**
         * 组织红包ID
         * @return
         */
        private String genRedPacketId(String userId){
            String redpacketId = userId+"_"+new Date().getTime()+"_"+redisUtil.incrBy("redpacketid",1);
            return redpacketId;
        }

    抢红包

    详见代码注释

    /**
         * 抢红包
         * @param userId
         * @param redPacketId
         * @return
         */
        public GrabResult grab(String userId, String redPacketId){
            Date begin = new Date();
            String msg = "红包已经被抢完!";
            boolean resultFlag = false;
            double amountdb = 0.00;
    
            try{
                //抢红包的过程必须保证原子性,此处加分布式锁
                if(redisUtil.getLock(redPacketId+"_lock")) {
                    RedPacketRecord redPacketRecord = new RedPacketRecord().builder().red_packet_id(redPacketId)
                            .user_id(userId).build();
                    //如果没有红包了,则返回
                    if (Integer.parseInt(redisUtil.get(redPacketId + TAL_PACKET)) <= 0) {
                    }else {
                        //抢红包过程
                        //获取剩余金额 单位分
                        int remaining_amount = Integer.parseInt(redisUtil.get(redPacketId + TOTAL_AMOUNT));
                        //获取剩余红包数
                        int remaining_packet = Integer.parseInt(redisUtil.get(redPacketId + TAL_PACKET));
                        //计算本次抢红包金额
                        //计算公式:remaining_amount/remaining_packet*2
                        //如果只剩下一个红包,则余额全由这次的人获得
                        int amount = remaining_amount;
                        if (remaining_packet != 1) {
                            int maxAmount = remaining_amount / remaining_packet * 2;
                            amount = Integer.parseInt(randomValuePropertySource.getProperty("random.int[0," + maxAmount + "]").toString());
                        }
                        //与redis进行incrBy应该原子,并且2次与redis交互还有一定性能消耗,通过lua脚本实现更为妥当
                        redisUtil.incrBy(redPacketId + TAL_PACKET, -1);
                        redisUtil.incrByFloat(redPacketId + TOTAL_AMOUNT, -amount);
                        //准备返回结果
                        redPacketRecord.setAmount(amount);
                        amountdb = amount / 100.00;
                        msg = "恭喜你抢到红包,红包金额" + amountdb + "元!";
                        resultFlag = true;
                        //异步记账
                        try {
                            redPacketCallBackService.callback(userId, redPacketId,
                                    Integer.parseInt(redisUtil.get(redPacketId + TAL_PACKET)),
                                    Integer.parseInt(redisUtil.get(redPacketId + TOTAL_AMOUNT)),
                                    amount);
                        } catch (Exception e) {
                            log.error(e.getMessage(), e);
                        }
                    }
                }
            }finally {
                //解锁redis分布式锁
                redisUtil.unLock(redPacketId+"_lock");
            }
            Date end = new Date();
            System.out.println(msg+",剩余红包:"+redisUtil.get(redPacketId + TAL_PACKET)+"个,本次抢红包消耗:"+(end.getTime()-begin.getTime())+"毫秒");
            return new GrabResult().builder().msg(msg).resultFlag(resultFlag).amount(amountdb).red_packet_id(redPacketId).user_id(userId).build();
    
        }

    异步入账

    /**
     * @program: redis
     * @description: 回写信息
     * @author: X-Pacific zhang
     * @create: 2019-04-30 11:36
     **/
    @Service
    public class RedPacketCallBackService {
        @Autowired
        private RedPacketInfoRepository redPacketInfoRepository;
    
        @Autowired
        private RedPacketRecordRepository redPacketRecordRepository;
        /**
         * 回写红包信息表、抢红包表
         */
        @Async
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void callback(String userId,String redPacketId,int remaining_packet,int remaining_amount,int amount) throws Exception {
            //校验
            RedPacketInfo redPacketInfo = redPacketInfoRepository.findByRedPacketId(redPacketId);
            if(redPacketInfo.getRemaining_packet() <= 0 || redPacketInfo.getRemaining_amount() < amount){
                throw new Exception("红包余额错误,本次抢红包失败!");
            }
            //先更新红包信息表
            redPacketInfo.setRemaining_packet(remaining_packet);
            redPacketInfo.setRemaining_amount(remaining_amount);
            redPacketInfoRepository.save(redPacketInfo);
            //新增抢红包信息
            RedPacketRecord redPacketRecord = new RedPacketRecord().builder()
                    .user_id(userId).red_packet_id(redPacketId).amount(amount).build();
            redPacketRecordRepository.save(redPacketRecord);
        }
    }

    测试抢红包

      @Test
        public void testConcurrent(){
            String redPacketId = "001_1556677154968_19";
    //        System.out.println(redPacketInfoRepository.findByRedPacketId("001_1556619425512_5"));
            Date begin = new Date();
            for(int i = 0;i < 200;i++) {
            Thread thread = new Thread(() -> {
                String userId = "user_" + randomValuePropertySource.getProperty("random.int(10000)").toString();
                redPacketService.grab(userId, redPacketId);
            });
            thread.start();
        }
        Date end = new Date();
            System.out.println("合计消耗:"+(end.getTime() - begin.getTime()));
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

  • 相关阅读:
    chrome扩展及应用开发 李喆pdf完整版
    Chrome插件(扩展)开发资料
    Fiddler下载地址
    如果没有 Android 世界会是什么样子?
    一张图告诉你:Android系统哪代强?
    Android开发的16条小经验总结
    Android上实现MVP模式的途径
    Android事件总线还能怎么玩?
    Android性能优化典范(二)
    安卓listView实现下拉刷新上拉加载滑动仿QQ的删除功能
  • 原文地址:https://www.cnblogs.com/zxporz/p/10799335.html
Copyright © 2020-2023  润新知