项目描述

最近做的一个项目有这么一个需求:需要生成一个唯一的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=new Thread[threadNum];
for (int i = 0; i <threadNum ; i++) {
final int j=i;
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("qrcode"+getOneQrCode());
}
});
threads[i]=thread;
thread.start(); countDownLatch.countDown();
}
for (Thread thread :threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

5个线程并发测试的结果:

  1. 对qrcode:incr进行get返回的结果是10000.
  2. 获取的结果为:

由于并发导致5个线程都先执行到

 Long incr = jedisCluster.incr("qrcode:incr");

最终incr的值分别为19998/19999/20000/20001/20002.所以后面三个计数的key为20,由于测试环境只生成到了qrcode:19,所以返回的是null。

解决

所以判断到达maxincr并重置成10000时应该是原子操作。所以这里采用lua脚本的方式执行。

Redis使用lua脚本

版本:自2.6.0起可用。

时间复杂度:取决于执行的脚本。

使用Lua脚本的好处:

  • 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。

    原子操作。redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。
  • 复用。客户端发送的脚本会永久存在redis中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。
  • redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。

所以对获取qrcode进行改造:

public String getOneQrcodeLua(){
String lua="local key = KEYS[1]\n" +
"local incr=redis.call('incr',key) \n"+
"if incr == tonumber(ARGV[1]) \n" +
"then\n" +
" redis.call('set',key,ARGV[2])\n" +
" return incr\n" +
"else\n" +
" return incr\n" +
"end";
List<String> keys = new ArrayList<>();
keys.add("qrcode:incr");
List<String> argv = new ArrayList<>();
argv.add("19999");
argv.add("10000");
Object o= jedisCluster.eval(lua,keys,argv);
// System.out.println("incr"+o);
//取前两位
String key = o.toString().substring(0, 2);
//取后三位作为list里的index
Integer index = NumberUtil.getIntValue(o.toString().substring(2));
//获得5位随机数
String qrcode = jedisCluster.lIndex("qrcode:"+ key, index);
return qrcode;
}

5个线程并发测试的结果:

  1. 对qrcode:incr进行get返回的结果是10003.
  2. 获取的结果为:

一切正常。

参考

https://redisbook.readthedocs.io/en/latest/feature/scripting.html

http://doc.redisfans.com/script/eval.html

我的博客地址

我的博客地址

采用redis生成唯一且随机的订单号的更多相关文章

  1. MSSQL高并发下生成连续不重复的订单号

    一.确定需求 只要做过开发的基本上都有做过订单,只要做过订单的基本上都要涉及生成订单号,可能项目订单号生成规则都不一样,但是大多数规则都是连续增长. 所以假如给你一个这样的需求,在高并发下,以天为单位 ...

  2. MSSQL 高并发下生成连续不重复的订单号

    参考: https://www.cnblogs.com/h-change/p/6699683.html 这里在数据库层面生成的,经测试确实不会重复. 附上自己修改后的版本,这里也可以预先生成一年的记录 ...

  3. 支付宝支付集成过程中如何生成商户订单号(out_trade_no)

    out_trade_no是指商户网站唯一订单号,在商户端唯一,每个商户订单号会对应一个支付宝订单号 ,此订单号由珊瑚自己生成,商户订单号要求64个字符以内.可包含字母.数字.下划线:需保证在商户端不重 ...

  4. Java代码生成16位纯数字的订单号

    //生成16位唯一性的订单号 public static void getUUID(){ //随机生成一位整数 int random = (int) (Math.random()*9+1); Stri ...

  5. 高并发分布式系统中生成全局唯一(订单号)Id js返回上一页并刷新、返回上一页、自动刷新页面 父页面操作嵌套iframe子页面的HTML标签元素 .net判断System.Data.DataRow中是否包含某列 .Net使用system.Security.Cryptography.RNGCryptoServiceProvider类与System.Random类生成随机数

    高并发分布式系统中生成全局唯一(订单号)Id   1.GUID数据因毫无规律可言造成索引效率低下,影响了系统的性能,那么通过组合的方式,保留GUID的10个字节,用另6个字节表示GUID生成的时间(D ...

  6. mysql使用触发器生成唯一订单号,

    需求:订单号唯一,并且期望是时间格式加其他字符串, 实现:采用触发机制,在新增时根据新增id值加1作为订单生成的随机且确定唯一的数,因为id唯一: 遇到问题:新增时不能提前知道id值, 解决:取到当前 ...

  7. php订单生成唯一Id

    一般用到一个函数: uniqid(prefix,more_entropy) 参数 描述 prefix 可选.为 ID 规定前缀.如果两个脚本恰好在相同的微秒生成 ID,该参数很有用. more_ent ...

  8. C#生成唯一不重复订单号帮助类

    1.使用场景 通常,在做一些表单的功能时,需要生成唯一不重复的订单单号,本文提供的帮助类可以适合大多数场景的单号生成使用,拿来即用,方便快捷无重复.而且,在高并发的情况下也是可以使用的. 之前看到有人 ...

  9. PHP生成随机数;订单号唯一

    //8-12位随机数 function makeRand($num=){ $strand = (; if(strlen($strand)<$num){ $strand = str_pad($st ...

随机推荐

  1. 通过 Channel 实现 Goroutine Pool

    最近用到了 Go 从 Excel 导数据到服务器内部 用的是 http 请求 但是发现一个问题 从文件读取之后 新开 Goroutine 会无限制新增 导致全部卡在初始化请求 于是乎就卡死了 问题模拟 ...

  2. tensorflow中的batch_norm以及tf.control_dependencies和tf.GraphKeys.UPDATE_OPS的探究

    https://blog.csdn.net/huitailangyz/article/details/85015611#

  3. 设计模式(C#)——09外观模式

    推荐阅读:  我的CSDN  我的博客园  QQ群:704621321 前言       在软件开发过程中,客户端程序经常会与复杂系统的内部子系统进行耦合,从而导致客户端程序随着子系统的变化而变化,然 ...

  4. IO核心子系统

    IO核心子系统 一.IO层次结构 IO实现普遍采用了层次式的结构.其基本思想与计算机网络中的层次结构相同:将系统IO的功能组织成一系列的层次,每一层完成整个系统功能的一个子集,其实现依赖于下层完成更原 ...

  5. Java集合框架之List接口浅析

    Java集合框架之List接口浅析 一.List综述: 毫无疑问List接口位于java.util包下,继承自 Collection接口 存储元素的特点: 有序可重复(有序:即存进去是什么顺序,取出来 ...

  6. [Python] 通过采集两万条数据,对《无名之辈》影评分析

    一.说明 本文主要讲述采集猫眼电影用户评论进行分析,相关爬虫采集程序可以爬取多个电影评论. 运行环境:Win10/Python3.5. 分析工具:jieba.wordcloud.pyecharts.m ...

  7. 快速掌握SPSS数据分析

      SPSS难吗?无非就是数据类型的区别后,就能理解应该用什么样的分析方法,对应着分析方法无非是找一些参考资料进行即可.甚至在线网页SPSS软件直接可以将数据分析结果指标人工智能地分析出来,这有多难呢 ...

  8. openSession 与 getCurrentSession的区别

    1.openSession 每一次获得的是一个全新的session对象,而getCurrentSession获得的是与当前线程绑定的session对象 package cn.kiwifly.view; ...

  9. xsd to javabean

    1. cd jdk/bin 2. xjc -p com.skyecho.product.air.ibe.api.model.airfaredisplay d:/temp/OTA_AirFareDisp ...

  10. cogs 313. [POI2001] 和平委员会(2-SAT

    http://cogs.pro:8080/cogs/problem/problem.php?pid=pyzQimjkj 题意:有n个集合,每个集合有俩元素,要从n个中各选一个放一堆,但是有的俩不能同时 ...