九种分布式ID
一、了解
1、概念
全局唯一ID即分布式ID。例如在使用分库分表时,数据量日渐增长,需要一个唯一ID标识一条数据,数据库自增ID不能满足需求,此时就需要全局唯一ID做标识。
2、满足条件
- 全局唯一:ID是全局唯一的
- 高性能:高可用低延时,生成响应要快
- 高可用:创建ID请求时,服务器要几近于100%创建一个唯一的Id
- 趋势递增:MySQL中InnoDB引擎使用的是聚集索引。多数RDBMS使用Btree的数据结构来存储索引数据,在主键的选择上尽量选择有序的主键保证写入性能。
二、生成
以下是九种生成方式:

1、UUID
public static String generateUUID() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
缺点:
无序字符串,不符合趋势自增
字符串没有含义
36位字符串,对存储和查询性能消耗较大,MYSQL官方建议主键越短越好
优点:
生成简单
唯一性
2、数据库自增ID

优点:
数字型,占用空间小,顺序自增,查询速度快
缺点:
不适合于分布式存储,数据量大时,会导致查询数据库操作变慢。
分库分表的操作,水平分表时,划分到不同的库/表,主键冲突。
3、多主模式
set @@auto_increment_offset = 1; -- 起始值
set @@auto_increment_increment = 2; -- 步长
set @@auto_increment_offset = 2; -- 起始值
set @@auto_increment_increment = 2; -- 步长
水平分表/库,有利于解决数据库单点压力的问题,同时根据 起始值+自增步长解决ID冲突的问题。一旦继续拓展则又需要更改起始值和步长,否则还会导致重复的问题。
4、号段模式
从数据库批量的获取自增ID,每次取出一个号段范围,比如【1-100】,具体的服务生成自增ID加载到内存。

屁号段用完,再次申请新号段,对max_id更新,version版本+1,
version :乐观锁,每次更新version,保证并发时数据的正确性
5、redis
使用redis的原子性自增
set order_id 1
incr order_id
两种持久化方式:
RDB:定时一个快照。id连续自增但是没及时持久化,此时redis宕机,重启后会出现ID重复
AOF:对每条命令持久化,即使宕机也不会出现ID重复,但redis重启恢复需要一定时间
实现:
@Autowired
private StringRedisTemplate redisTemplate;
@GetMapping("/rdb")
public void rdb(String key) {
if (redisTemplate.opsForValue().get(key) == null) {
//是否存在100
Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, "100");
if (!ifAbsent) {
Long pm1 = redisTemplate.opsForValue().increment(key);
}
} else {
Long pm1 = redisTemplate.opsForValue().increment(key);
}
log.info("自增ID:" + redisTemplate.opsForValue().get(key));
}
@Autowired
private StringRedisTemplate redisTemplate;
public Long incrementAndGetId(String key) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
return counter.incrementAndGet();
}
6、雪花算法

实现:
//简单写法
public class IdUtil {
private static SnowflakeShardingKeyGenerator generator = new SnowflakeShardingKeyGenerator();
public static Long generateSnowFlakeId(){
return Long.parseLong(generator.generateKey().toString());
}
}
/**
* Twitter的SnowFlake算法,使用SnowFlake算法生成一个整数,然后转化为62进制变成一个短地址URL
*
* https://github.com/beyondfengyu/SnowFlake
*/
public class SnowFlakeShortUrl {
/**
* 起始的时间戳
*/
private final static long START_TIMESTAMP = 1480166465631L;
/**
* 每一部分占用的位数
*/
private final static long SEQUENCE_BIT = 12; //序列号占用的位数
private final static long MACHINE_BIT = 5; //机器标识占用的位数
private final static long DATA_CENTER_BIT = 5; //数据中心占用的位数
/**
* 每一部分的最大值
*/
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
private final static long MAX_DATA_CENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT);
/**
* 每一部分向左的位移
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;
private long dataCenterId; //数据中心
private long machineId; //机器标识
private long sequence = 0L; //序列号
private long lastTimeStamp = -1L; //上一次时间戳
private long getNextMill() {
long mill = getNewTimeStamp();
while (mill <= lastTimeStamp) {
mill = getNewTimeStamp();
}
return mill;
}
private long getNewTimeStamp() {
return System.currentTimeMillis();
}
/**
* 根据指定的数据中心ID和机器标志ID生成指定的序列号
*
* @param dataCenterId 数据中心ID
* @param machineId 机器标志ID
*/
public SnowFlakeShortUrl(long dataCenterId, long machineId) {
if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
throw new IllegalArgumentException("DtaCenterId can't be greater than MAX_DATA_CENTER_NUM or less than 0!");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("MachineId can't be greater than MAX_MACHINE_NUM or less than 0!");
}
this.dataCenterId = dataCenterId;
this.machineId = machineId;
}
/**
* 产生下一个ID
*
* @return
*/
public synchronized long nextId() {
long currTimeStamp = getNewTimeStamp();
if (currTimeStamp < lastTimeStamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
if (currTimeStamp == lastTimeStamp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大
if (sequence == 0L) {
currTimeStamp = getNextMill();
}
} else {
//不同毫秒内,序列号置为0
sequence = 0L;
}
lastTimeStamp = currTimeStamp;
return (currTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT //时间戳部分
| dataCenterId << DATA_CENTER_LEFT //数据中心部分
| machineId << MACHINE_LEFT //机器标识部分
| sequence; //序列号部分
}
public static void main(String[] args) {
SnowFlakeShortUrl snowFlake = new SnowFlakeShortUrl(2, 3);
for (int i = 0; i < (1 << 4); i++) {
//10进制
System.out.println(snowFlake.nextId());
}
}
}
7、百度
官方:https://github.com/baidu/uid-generator
https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md
8、美团
官方:https://github.com/Meituan-Dianping/Leaf
https://github.com/Meituan-Dianping/Leaf/blob/master/README_CN.md
9、滴滴
https://github.com/didi/tinyid.git