高并发情况下,如何生成分布式全局id

1.使用UUID生成全局id

UUID是一个字符串而且没有顺序,所以不适合做主键,可以 做 token 使用。

利用全球唯一UUID生成订单号 UUID基本概念: UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。

UUID组成部分:当前日期和时间+时钟序列+随机数+全局唯一的IEEE机器识别号 全局唯一的IEEE机器识别号:如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。

 

优点: 简单,代码方便 生成ID性能非常好,基本不会有性能问题 全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对

缺点: 没有排序,无法保证趋势递增 UUID往往是使用字符串存储,查询的效率比较低 存储空间比较大,如果是海量数据库,就需要考虑存储量的问题。 传输数据量大

一般UUID在生成Token领域使用比较多

2.基于数据库自增或者序列生成全局id

如果使用数据库id自增生成订单号的话,如果数据库是集群的话 则有可能生成相同的订单号。

所以如果我们使用数据库id 自增做为全局 id 的话我们需要设置步长,步长表示每次自动增长的数量。

举栗子:

假如现在有 3 台 MySql 数据库做集群,mysql1,mysql2,mysql3。

设置mysql1数据库id初始值为0,mysql数据库id初始值为1,mysql3数据库id初始值为2。我们这时候需要设置步长为3,

mysql1每次自增结果为  0,3,6,9,12,15

mysql2每次自动结果为  1,4,7,10,13,16

mysql3每次自动结果为  2,5,8,11,14,17

........

这就是最后产生自增的结果,但是这种方法还有一个缺点,就是如果后期增加数据库服务器集群数量的话,mysql 步长无法扩展。所以使用这种方法生成全局id,需要前期确定好mysql数据库集群的数量,不然那到后期扩展集群数量会导致生成步长规则发生改变,可能会产生重复的id。

在数据库集群环境下,默认自增方式存在问题,因为都是从1开始自增,可能会存在重复,应该设置每台节点自增步长不同。

查询自增的步长

SHOW VARIABLES LIKE 'auto_increment%'

修改自增的步长

SET @@auto_increment_increment=10;

修改起始值

SET @@auto_increment_offset=5;

 

3.基于redis生成全局id

因为Redis是单线的,天生保证原子性,可以使用Redis的原子操作 INCR和INCRBY来实现

优点: 不依赖于数据库,灵活方便,且性能优于数据库。 数字ID天然排序,对分页或者需要排序的结果很有帮助。

缺点: 如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。 需要编码和配置的工作量比较大。

注意:在Redis集群情况下,同样和Redis一样需要设置不同的增长步长,同时key一定要设置有效期 可以使用Redis集群来获取更高的吞吐量。

假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。

各个Redis生成的ID为:

A:1,6,11,16,21

B:2,7,12,17,22

C:3,8,13,18,23

D:4,9,14,19,24

E:5,10,15,20,25

比较适合使用Redis来生成每天从0开始的流水号。

比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加。 如果生成的订单号超过自增增长的话,可以采用前缀+自增+并且设置有效期。

使用 redis 实现全局id生成,使用自动填充的方法实现。

 1 package com.example.controller;
 2 
 3 import java.text.SimpleDateFormat;
 4 import java.util.Date;
 5 
 6 import org.springframework.beans.factory.annotation.Autowired;
 7 import org.springframework.data.redis.core.RedisTemplate;
 8 import org.springframework.data.redis.support.atomic.RedisAtomicLong;
 9 import org.springframework.web.bind.annotation.RequestMapping;
10 import org.springframework.web.bind.annotation.RestController;
11 
12 /**
13  * 基于Redis 实现分布式全局id
14  */
15 @RestController
16 public class OrderController {
17 
18     @Autowired
19     private RedisTemplate redisTemplate;
20 
21     // 基于Redis 实现分布式全局id原理
22 
23     /**
24      * 15 18位 前缀=当前日期=2018112921303030-5位自增id(高并发请下 先天性安全) 00001<br>
25      * 00010<br>
26      * 00100<br>
27      * 01000<br>
28      * 11000<br>
29      * 在相同毫秒情况下,最多只能生成10万-1=99999订单号<br>
30      * 假设:双11每毫秒99万笔 <br>
31      * 提前生成号订单号码存放在redis中
32      * <p>
33      * 9.9万*1000=900万<br>
34      * 考虑失效时间问题 24小时
35      *
36      * @return
37      */
38     @RequestMapping("/order")
39     public String order(String key) {
40         RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
41         for (int i = 0; i < 100; i++) {
42             long incrementAndGet = redisAtomicLong.incrementAndGet();
43             //1$5 表示是5位数 0表示以0填充 d 表示正数
44             String orderId = prefix() + "-" + String.format("%1$05d", incrementAndGet);
45             String orderSQL = "insert into orderNumber value('" + orderId + "');";
46             System.out.println(orderSQL);
47         }
48 
49         return "success";
50     }
51 
52     
53     @RequestMapping("/order1")
54     public String order1(String key) {
55         RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
56         // // 起始值
57         redisAtomicLong.set(1);
58         // 设置步长加10
59         redisAtomicLong.addAndGet(9);
60         return redisAtomicLong.incrementAndGet() + "";
61     }
62 
63     public static String prefix() {
64         String temp_str = "";
65         Date dt = new Date();
66         // 最后的aa表示“上午”或“下午” HH表示24小时制 如果换成hh表示12小时制
67         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
68         temp_str = sdf.format(dt);
69         return temp_str;
70     }
71 
72 }

 

4.基于Twitter的雪花算法(snowflake)生成全局ID

  1 package com.example.controller;
  2 
  3 /**
  4  * Twitter_Snowflake<br>
  5  * SnowFlake的结构如下(每部分用-分开):<br>
  6  * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 -
  7  * 000000000000 <br>
  8  * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
  9  * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
 10  * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。
 11  * 41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 12  * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
 13  * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
 14  * 加起来刚好64位,为一个Long型。<br>
 15  * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,
 16  * SnowFlake每秒能够产生26万ID左右。
 17  */
 18 public class SnowflakeIdWorker {
 19 
 20     // ==============================Fields===========================================
 21     /** 开始时间截 (2015-01-01) */
 22     private final long twepoch = 1420041600000L;
 23 
 24     /** 机器id所占的位数 */
 25     private final long workerIdBits = 5L;
 26 
 27     /** 数据标识id所占的位数 */
 28     private final long datacenterIdBits = 5L;
 29 
 30     /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
 31     private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
 32 
 33     /** 支持的最大数据标识id,结果是31 */
 34     private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
 35 
 36     /** 序列在id中占的位数 */
 37     private final long sequenceBits = 12L;
 38 
 39     /** 机器ID向左移12位 */
 40     private final long workerIdShift = sequenceBits;
 41 
 42     /** 数据标识id向左移17位(12+5) */
 43     private final long datacenterIdShift = sequenceBits + workerIdBits;
 44 
 45     /** 时间截向左移22位(5+5+12) */
 46     private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
 47 
 48     /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
 49     private final long sequenceMask = -1L ^ (-1L << sequenceBits);
 50 
 51     /** 工作机器ID(0~31) */
 52     private long workerId;
 53 
 54     /** 数据中心ID(0~31) */
 55     private long datacenterId;
 56 
 57     /** 毫秒内序列(0~4095) */
 58     private long sequence = 0L;
 59 
 60     /** 上次生成ID的时间截 */
 61     private long lastTimestamp = -1L;
 62 
 63     // ==============================Constructors=====================================
 64     /**
 65      * 构造函数
 66      * 
 67      * @param workerId
 68      *            工作ID (0~31)
 69      * @param datacenterId
 70      *            数据中心ID (0~31)
 71      */
 72     public SnowflakeIdWorker(long workerId, long datacenterId) {
 73         if (workerId > maxWorkerId || workerId < 0) {
 74             throw new IllegalArgumentException(
 75                     String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
 76         }
 77         if (datacenterId > maxDatacenterId || datacenterId < 0) {
 78             throw new IllegalArgumentException(
 79                     String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
 80         }
 81         this.workerId = workerId;
 82         this.datacenterId = datacenterId;
 83     }
 84 
 85     // ==============================Methods==========================================
 86     /**
 87      * 获得下一个ID (该方法是线程安全的)
 88      * 
 89      * @return SnowflakeId
 90      */
 91     public synchronized long nextId() {
 92         long timestamp = timeGen();
 93 
 94         // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
 95         if (timestamp < lastTimestamp) {
 96             throw new RuntimeException(String.format(
 97                     "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
 98         }
 99 
100         // 如果是同一时间生成的,则进行毫秒内序列
101         if (lastTimestamp == timestamp) {
102             sequence = (sequence + 1) & sequenceMask;
103             // 毫秒内序列溢出
104             if (sequence == 0) {
105                 // 阻塞到下一个毫秒,获得新的时间戳
106                 timestamp = tilNextMillis(lastTimestamp);
107             }
108         }
109         // 时间戳改变,毫秒内序列重置
110         else {
111             sequence = 0L;
112         }
113 
114         // 上次生成ID的时间截
115         lastTimestamp = timestamp;
116 
117         // 移位并通过或运算拼到一起组成64位的ID
118         return ((timestamp - twepoch) << timestampLeftShift) //
119                 | (datacenterId << datacenterIdShift) //
120                 | (workerId << workerIdShift) //
121                 | sequence;
122     }
123 
124     /**
125      * 阻塞到下一个毫秒,直到获得新的时间戳
126      * 
127      * @param lastTimestamp
128      *            上次生成ID的时间截
129      * @return 当前时间戳
130      */
131     protected long tilNextMillis(long lastTimestamp) {
132         long timestamp = timeGen();
133         while (timestamp <= lastTimestamp) {
134             timestamp = timeGen();
135         }
136         return timestamp;
137     }
138 
139     /**
140      * 返回以毫秒为单位的当前时间
141      * 
142      * @return 当前时间(毫秒)
143      */
144     protected long timeGen() {
145         return System.currentTimeMillis();
146     }
147 
148     // ==============================Test=============================================
149     /** 测试 */
150     public static void main(String[] args) {
151         SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
152         for (int i = 0; i < 100; i++) {
153             long id = idWorker.nextId();
154             String insertSQL = "insert into orderNumber value('" + id + "');";
155             System.out.println(insertSQL);
156         }
157     }
158 }

 

能最好

 

posted @ 2019-02-28 22:51  明天,你好啊  阅读(599)  评论(0编辑  收藏  举报