# 分布式系统的唯一id生成算法

在分库分表之后就会遇到一个问题,就是id咋生成?

# 解决思路

1.不能每个表是相同的id,自增肯定不行 2.要全局生成唯一id,然后插入各个表中,保证每个表内的某个id,全局唯一

# 1.独立数据库自增id

系统每次要生成一个id,都是往一个独立库的一个独立表里插入一条没有什么业务含义的数据,然后 获得一个数据库自增的一个id。拿到这个id之后再往对应的分库分表里去写入

好处:简单 缺点:单裤生成自增,高并发就会遇到瓶颈

# 2.uuid

本地用uuid生成一个全局唯一的id

好处:每个系统本地生成,不需要基于数据库来 缺点:uuid太长,主键性能太差,不适用于主键

# 3.获取系统当前时间

一般是当前时间跟很多其它的业务字段拼接起来,作为一个id。如:时间戳+用户id+业务含义编码 缺点:高并发,不太适合

# 4.snowflake算法的思想分析

思想:使用一个64bit的long型的数字作为全局唯一id,这64个bit中,其中1个bit是不用的,然后 用其中的41bit作为毫秒数,用10bit作为工作机器id,12bit作为序列号。

public class IdWorker {

   private long workerId; // 这个就是代表了机器id
   private long datacenterId; // 这个就是代表了机房id
   private long sequence; // 这个就是代表了一毫秒内生成的多个id的最新序号

   public IdWorker(long workerId, long datacenterId, long sequence) {

       // sanity check for workerId
       // 这儿不就检查了一下,要求就是你传递进来的机房id和机器id不能超过32,不能小于0
       if (workerId > maxWorkerId || workerId < 0) {
           
           throw new IllegalArgumentException(
               String.format("worker Id can't be greater than %d or less than 0",maxWorkerId));
       }
       
       if (datacenterId > maxDatacenterId || datacenterId < 0) {
       
           throw new IllegalArgumentException(
               String.format("datacenter Id can't be greater than %d or less than 0",maxDatacenterId));
       }

       this.workerId = workerId;
       this.datacenterId = datacenterId;
       this.sequence = sequence;
   }

   private long twepoch = 1288834974657L;

   private long workerIdBits = 5L;
   private long datacenterIdBits = 5L;
   
   // 这个是二进制运算,就是5 bit最多只能有31个数字,也就是说机器id最多只能是32以内
   private long maxWorkerId = -1L ^ (-1L << workerIdBits); 

   // 这个是一个意思,就是5 bit最多只能有31个数字,机房id最多只能是32以内
   private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); 
   private long sequenceBits = 12L;

   private long workerIdShift = sequenceBits;
   private long datacenterIdShift = sequenceBits + workerIdBits;
   private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
   private long sequenceMask = -1L ^ (-1L << sequenceBits);

   private long lastTimestamp = -1L;

   public long getWorkerId(){
       return workerId;
   }

   public long getDatacenterId() {
       return datacenterId;
   }

   public long getTimestamp() {
       return System.currentTimeMillis();
   }

   // 这个是核心方法,通过调用nextId()方法,让当前这台机器上的snowflake算法程序生成一个全局唯一的id
   public synchronized long nextId() {

       // 这儿就是获取当前时间戳,单位是毫秒
       long timestamp = timeGen();

       if (timestamp < lastTimestamp) {
           System.err.printf(
               "clock is moving backwards. Rejecting requests until %d.", lastTimestamp);
           throw new RuntimeException(
               String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
                             lastTimestamp - timestamp));
       }

       
       // 下面是说假设在同一个毫秒内,又发送了一个请求生成一个id
       // 这个时候就得把seqence序号给递增1,最多就是4096
       if (lastTimestamp == timestamp) {
       
           // 这个意思是说一个毫秒内最多只能有4096个数字,无论你传递多少进来,
           //这个位运算保证始终就是在4096这个范围内,避免你自己传递个sequence超过了4096这个范围
           sequence = (sequence + 1) & sequenceMask; 

           if (sequence == 0) {
               timestamp = tilNextMillis(lastTimestamp);
           }
       
       } else {
           sequence = 0;
       }

       // 这儿记录一下最近一次生成id的时间戳,单位是毫秒
       lastTimestamp = timestamp;

       // 这儿就是最核心的二进制位运算操作,生成一个64bit的id
       // 先将当前时间戳左移,放到41 bit那儿;将机房id左移放到5 bit那儿;将机器id左移放到5 bit那儿;将序号放最后12 bit
       // 最后拼接起来成一个64 bit的二进制数字,转换成10进制就是个long型
       return ((timestamp - twepoch) << timestampLeftShift) |
               (datacenterId << datacenterIdShift) |
               (workerId << workerIdShift) | sequence;
   }

   private long tilNextMillis(long lastTimestamp) {
       
       long timestamp = timeGen();
       
       while (timestamp <= lastTimestamp) {
           timestamp = timeGen();
       }
       return timestamp;
   }

   private long timeGen(){
       return System.currentTimeMillis();
   }

   //---------------测试---------------
   public static void main(String[] args) {
       
       IdWorker worker = new IdWorker(1,1,1);
       
       for (int i = 0; i < 30; i++) {
           System.out.println(worker.nextId());
       }
   }
}
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

# 5.snowflake算法改进

64个bit中,代表机房的那5个bit,可以使用业务表名称来替代,比如用00001代表的是订单表。

这样就可以做到,snowflake算法系统的每一台机器,对一个业务表,在某一毫秒内,可以生成一个唯一的id,一毫秒内生成很多id,用最后12个bit来区分序号对待。

全量分析

评 论:

上次更新时间: 11/20/2019, 9:16:07 AM