项目描述

最近做的一个项目有这么一个需求:需要生成一个唯一的11位的就餐码(类似于订单号的概念),就餐码的规则是:一共是11位的数字,前面6位是日期比如2019年07月20就是190720,后面五位是随机数且不能是自增的,不然容易让人看出一天的单量。

解决方案

五位随机数不能用随机生成的,不然可能不唯一,所以想到了预生成的方案:
采用redis

  • 随机数生成

先生成10000~99999共9万个数(从1万开始是懒得再前面补0了),然后打乱分别 存入redis的list数据结构 90个key每个key存1000个数。取的时候通过LINDEX进行读取。

        List<String> numList=new ArrayList<>();         //90万个数 每个redis key 1000个数,要存90个key.         for (int i=10000;i<=99999;i++){             numList.add(String.valueOf(i));          }         //打乱顺序         Collections.shuffle(numList);         //生成key         for (int j=10;j<=99;j++){             String redisKey="qrcode:"+j;             List<String> newList= test.subList((j-10)*1000,(j-10)*1000 + 1000);            jedisCluster.rpush(redisKey,newList.toArray(new String[newList.size()]));         }

这样每个key的index值就是0~999,key就是qrcode:10/qrcode:11/qrcode:12.../qrcode:99.

  • 计数key

再使用一个key来计数每次生成一个就餐码就加1,值也从10000开始,计数的前两位用来表示该取哪个key,后三位代表key的索引。比如现在计数记到12151那就是取上面生成的qrcode:12 key里索引为151的value,然后当计数到99999时再从10000重新计数,这样保证一天有9万个随机数可以使用且不会取到相同的随机数。这样可以解决一天最多9万单数量级的业务,后面一天百万级同理可以扩充成6位7位等。

先初始化:

jedisCluster.set(qrcode:incr,9999); 

示例

      public String getOneQrCode() {         Long incr = jedisCluster.incr("qrcode:incr");         //测试环境生成到19999         int maxIncr=19999         //int maxIncr = 99999;         //后期单量过猛时需要考虑--并发风险导致的就餐码重复 todo         if (incr == maxIncr) {             jedisCluster.set("qrcode:incr", String.valueOf(10000));            }         System.out.println("incr:"+incr);         //取前两位         String key = incr.toString().substring(0, 2);         //取后三位作为list里的index         Integer index = NumberUtil.getIntValue(incr.toString().substring(2));         //获得5位随机数         String qrcode = jedisCluster.lIndex("qrcode:"+ key, index);         return qrcode;     }

并发风险

当计数到最大值时,需要重置计数key(qrcode:incr)为10000会有线程不安全的问题。
我们先编写一个并发方法单元测试一下:
测试环境由于只生成10000个随机数,maxincr=19999,所以
我们先把计数的key设置成接近maxincr来进行并发测试,设置成19997后获取2个qrcode将进行重置成10000.

jedisCluster.set(qrcode:incr,19997);

开启5个线程并发测试:

private static final int threadNum=5;     //倒计数器,用于模拟高并发     private CountDownLatch countDownLatch=new CountDownLatch(threadNum);     @Test     public void benchmark() {         Thread[] threads=<