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

1.使用UUID生成全局id

UUID是一个字符串并且没有顺序,因此不适合作主键,能够 作 token 使用。java

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

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

 

优势: 简单,代码方便 生成ID性能很是好,基本不会有性能问题 全球惟一,在碰见数据迁移,系统数据合并,或者数据库变动等状况下,能够从容应对redis

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

通常UUID在生成Token领域使用比较多spring

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

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

因此若是咱们使用数据库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 }

 

能最好

相关文章
相关标签/搜索