基于Redis的流水号生成方案

优秀的流水号, 应该是唯一的/ 可读的/ 灵活的/ 高效的.

常见的流水号生成方式

  1. 时间戳 Java8以上支持纳秒了 1 秒钟等于 10 的 9 次方纳秒. 很完美. 但是理论上唯一性不足. 如果加上机器id, 管理又比较麻烦.
1
2
3
4
5
6
7
8
@Test
public void test03() {
Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String formattedDate = sdf.format(now);
long nano1 = System.nanoTime() % 10000000000L;
System.out.println(formattedDate + "." + String.format("%05d", nano1));
}
  1. 通过数据库记录. 每次+n. 加号和查询必须加事务. 不然会导致重号.多应用同时取号, 并发时都存在瓶颈. 高效性不行.
  2. uuid 丑+长
  3. 雪花算法 不可读 如果加上时间和类型又太长了

https://www.junjun.fun/23_04_25_serial_number_gen_time_randon

  • 因此, 都不太妙. 考虑到分布式取号情况, 只能找个介质把当前号存起来. 数据库已经排除了.
  • redisTemplate.opsForValue().increment可以自动返回增加后的值, 不存在并发加事务的情况, 非常合适.

基于Redis的流水号生成方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

@Configuration
public class RedisGen {

@Resource
public RedisTemplate redisTemplate;

public String getId(String key, Long delta) {
try {
// delta为空默认值1
if (null == delta) {
delta = 1L;
}
// 生成12位的时间戳(每秒使用新的时间戳当key)
String timeStamp = new SimpleDateFormat("yyMMddHHmmss").format(new Date());
// 获得redis-key
String newKey = key + ":" + timeStamp;
// 获取自增值(时间戳+自定义key)
Long increment = redisTemplate.opsForValue().increment(newKey, delta);
// 设置时间戳生成的key的有效期为2秒
redisTemplate.expire(newKey, 2, TimeUnit.SECONDS);
// 如果超过99999ops 会超出id长度 迭代重新取一下
if (increment.equals(10000L)) {
return getId(key, null);
}
// 获取订单号,时间戳 + 唯一自增Id( 4位数,不过前方补0)
return timeStamp + String.format("%04d", increment);
} catch (Exception e) {
// redis 宕机时采用时间戳加随机数
String timeStamp = new SimpleDateFormat("yyMMddHHmmssSSS").format(new Date());
Random random = new Random();
//12位时间戳到 + 4位随机数
timeStamp += (random.nextInt(10) + "");
return timeStamp;
}
}
}