分布式id生成器

释放双眼,带上耳机,听听看~!

在高并发或者分表分库情况下保证数据id的幂等性,经常用到的解决方案有以下几种。
1:微软公司通用唯一识别码(UUID)
2:Twitter公司雪花算法(SnowFlake)
3:基于数据库的id自增
4:对id进行缓存
这里我们以snowflake算法为例
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号,最后还有一个符号位,永远是0。
分布式id生成器
整个结构是64位,所以我们在Java中可以使用long来进行存储。该算法实现基本就是二进制操作,单机每秒内理论上最多可以生成1024*(2^12),也就是409.6万个ID(1024 X 4096 = 4194304)

snowFlake算法的优点

1:生成ID时不依赖于DB,完全在内存生成,高性能高可用。
2:ID呈趋势递增,后续插入索引树的时候性能较好。

SnowFlake算法的缺点

依赖于系统时钟的一致性。如果某台机器的系统时钟回拨,有可能造成ID冲突,或者ID乱序

算法代码如下


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
125
126
127
128
129
130
131
132
133
1public class SnowflakeIdWorker {
2
3    /** 开始时间截 (2019-08-06) */
4    private final long twepoch = 1565020800000L;
5
6    /** 机器id所占的位数 */
7    private final long workerIdBits = 5L;
8
9    /** 数据标识id所占的位数 */
10    private final long datacenterIdBits = 5L;
11
12    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
13    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
14
15    /** 支持的最大数据标识id,结果是31 */
16    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
17
18    /** 序列在id中占的位数 */
19    private final long sequenceBits = 12L;
20
21    /** 机器ID向左移12位 */
22    private final long workerIdShift = sequenceBits;
23
24    /** 数据标识id向左移17位(12+5) */
25    private final long datacenterIdShift = sequenceBits + workerIdBits;
26
27    /** 时间截向左移22位(5+5+12) */
28    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
29
30    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
31    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
32
33    /** 工作机器ID(0~31) */
34    private long workerId;
35
36    /** 数据中心ID(0~31) */
37    private long datacenterId;
38
39    /** 毫秒内序列(0~4095) */
40    private long sequence = 0L;
41
42    /** 上次生成ID的时间截 */
43    private long lastTimestamp = -1L;
44
45     //==============================Constructors====================
46    /**
47     * 构造函数
48     * @param workerId 工作ID (0~31)
49     * @param datacenterId 数据中心ID (0~31)
50     */
51    public SnowflakeIdWorker(long workerId, long datacenterId) {
52        if (workerId > maxWorkerId || workerId < 0) {
53            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
54        }
55        if (datacenterId > maxDatacenterId || datacenterId < 0) {
56            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
57        }
58        this.workerId = workerId;
59        this.datacenterId = datacenterId;
60    }
61
62    // ==============================Methods=================================
63    /**
64     * 获得下一个ID (该方法是线程安全的)
65     * @return SnowflakeId
66     */
67    public synchronized long nextId() {
68        long timestamp = timeGen();
69
70        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
71        if (timestamp < lastTimestamp) {
72            throw new RuntimeException(
73                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
74        }
75
76        //如果是同一时间生成的,则进行毫秒内序列
77        if (lastTimestamp == timestamp) {
78            sequence = (sequence + 1) & sequenceMask;
79            //毫秒内序列溢出
80            if (sequence == 0) {
81                //阻塞到下一个毫秒,获得新的时间戳
82                timestamp = tilNextMillis(lastTimestamp);
83            }
84        }
85        //时间戳改变,毫秒内序列重置
86        else {
87            sequence = 0L;
88        }
89
90        //上次生成ID的时间截
91        lastTimestamp = timestamp;
92
93        //移位并通过或运算拼到一起组成64位的ID
94        return ((timestamp - twepoch) << timestampLeftShift) //
95                | (datacenterId << datacenterIdShift) //
96                | (workerId << workerIdShift) //
97                | sequence;
98    }
99
100    /**
101     * 阻塞到下一个毫秒,直到获得新的时间戳
102     * @param lastTimestamp 上次生成ID的时间截
103     * @return 当前时间戳
104     */
105    protected long tilNextMillis(long lastTimestamp) {
106        long timestamp = timeGen();
107        while (timestamp <= lastTimestamp) {
108            timestamp = timeGen();
109        }
110        return timestamp;
111    }
112
113    /**
114     * 返回以毫秒为单位的当前时间
115     * @return 当前时间(毫秒)
116     */
117    protected long timeGen() {
118        return System.currentTimeMillis();
119    }
120
121    //==============================Test=============================================
122    /** 测试 */
123    public static void main(String[] args) {
124        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
125        for (int i = 0; i < 1000; i++) {
126            long id = idWorker.nextId();
127            System.out.println(Long.toBinaryString(id));
128            System.out.println(id);
129        }
130    }
131}
132
133

快速使用snowflake算法只需以下几步

  • 引入hutool依赖


1
2
3
4
5
6
7
1<dependency>
2    <groupId>cn.hutool</groupId>
3    <artifactId>hutool-captcha</artifactId>
4    <version>${hutool.version}</version>
5</dependency>
6
7
  • ID 生成器


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
1public class IdGenerator {
2
3    private long workerId = 0;
4
5    @PostConstruct
6    void init() {
7        try {
8            workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
9            log.info("当前机器 workerId: {}", workerId);
10        } catch (Exception e) {
11            log.warn("获取机器 ID 失败", e);
12            workerId = NetUtil.getLocalhost().hashCode();
13            log.info("当前机器 workerId: {}", workerId);
14        }
15    }
16
17    /**
18     * 获取一个批次号,形如 2019071015301361000101237
19     * <p>
20     * 数据库使用 char(25) 存储
21     *
22     * @param tenantId 租户ID,5 位
23     * @param module   业务模块ID,2 位
24     * @return 返回批次号
25     */
26    public synchronized String batchId(int tenantId, int module) {
27        String prefix = DateTime.now().toString(DatePattern.PURE_DATETIME_MS_PATTERN);
28        return prefix + tenantId + module + RandomUtil.randomNumbers(3);
29    }
30
31    @Deprecated
32    public synchronized String getBatchId(int tenantId, int module) {
33        return batchId(tenantId, module);
34    }
35
36    /**
37     * 生成的是不带-的字符串,类似于:b17f24ff026d40949c85a24f4f375d42
38     *
39     * @return
40     */
41    public String simpleUUID() {
42        return IdUtil.simpleUUID();
43    }
44
45    /**
46     * 生成的UUID是带-的字符串,类似于:a5c8a5e8-df2b-4706-bea4-08d0939410e3
47     *
48     * @return
49     */
50    public String randomUUID() {
51        return IdUtil.randomUUID();
52    }
53
54    private Snowflake snowflake = IdUtil.createSnowflake(workerId, 1);
55
56    public synchronized long snowflakeId() {
57        return snowflake.nextId();
58    }
59
60    public synchronized long snowflakeId(long workerId, long dataCenterId) {
61        Snowflake snowflake = IdUtil.createSnowflake(workerId, dataCenterId);
62        return snowflake.nextId();
63    }
64
65    /**
66     * 生成类似:5b9e306a4df4f8c54a39fb0c
67     * <p>
68     * ObjectId 是 MongoDB 数据库的一种唯一 ID 生成策略,
69     * 是 UUID version1 的变种,详细介绍可见:服务化框架-分布式 Unique ID 的生成方法一览。
70     *
71     * @return
72     */
73    public String objectId() {
74        return ObjectId.next();
75    }
76
77}
78
79
  • 测试类


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
1public class IdGeneratorTest {
2
3    @Autowired
4    private IdGenerator idGenerator;
5
6    @Test
7    public void testBatchId() {
8        for (int i = 0; i < 100; i++) {
9            String batchId = idGenerator.batchId(1001, 100);
10            log.info("批次号: {}", batchId);
11        }
12    }
13
14    @Test
15    public void testSimpleUUID() {
16        for (int i = 0; i < 100; i++) {
17            String simpleUUID = idGenerator.simpleUUID();
18            log.info("simpleUUID: {}", simpleUUID);
19        }
20    }
21
22    @Test
23    public void testRandomUUID() {
24        for (int i = 0; i < 100; i++) {
25            String randomUUID = idGenerator.randomUUID();
26            log.info("randomUUID: {}", randomUUID);
27        }
28    }
29
30    @Test
31    public void testObjectID() {
32        for (int i = 0; i < 100; i++) {
33            String objectId = idGenerator.objectId();
34            log.info("objectId: {}", objectId);
35        }
36    }
37
38    @Test
39    public void testSnowflakeId() {
40        ExecutorService executorService = Executors.newFixedThreadPool(20);
41        for (int i = 0; i < 20; i++) {
42            executorService.execute(() -> {
43                log.info("分布式 ID: {}", idGenerator.snowflakeId());
44            });
45        }
46        executorService.shutdown();
47    }
48
49}
50
51

在项目中我们只需要注入 @Autowired private IdGenerator idGenerator;即可

然后设置id order.setId(idGenerator.snowflakeId() + “”);

给TA打赏
共{{data.count}}人
人已打赏
安全网络

CDN安全市场到2022年价值76.3亿美元

2018-2-1 18:02:50

安全经验

排名前十的开源安全项目

2016-9-9 11:12:22

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索