目录
分布式ID在构建大规模分布式系统时扮演着至关重要的角色,主要用于确保在分布式环境中数据的唯一性和一致性。
雪花算法
SnowFlake算法是Twitter开源的分布式ID生成算法。核心思想就是:使用一个64 bit的 long 型的数字作为全局唯一ID。算法中还引入了时间戳,基本上保证了自增特性。
其特点是将64位的long型ID分为四个部分,分别为:时间戳、机器ID和序列号:


由于在 Java 中 64bit 的整数是 long 类型,所以在 Java 中 SnowFlake 算法生成的 id 就是 long 来存储的。而对于每一个雪花算法服务,需要先指定 10 位的机器码,这个根据自身业务进行设定即可。例如机房号+机器号,机器号+服务号,或者是其他可区别标识的 10 位比特位的整数值都行。
最终效果是,Snowflake算法给出的唯一 ID生成器是一个支持多机房共1024个服务 实例规模、单个服务实例每秒可生成410万个long类型唯一 ID的分布式系统,且此系统可以正常工作69年。
优缺点:
- 高并发分布式环境下生成不重复 id,每秒可生成百万个不重复 id。基于时间戳,以及同一时间戳下序列号自增,基本保证 id 有序递增。不依赖第三方库或者中间件。算法简单,在内存中进行,效率高。
- 依赖服务器时间,服务器时钟回拨时可能会生成重复 id。算法中可通过记录最后一个生成 id 时的时间戳来解决,每次生成 id 之前比较当前服务器时钟是否被回拨,避免生成重复 id。
实现:
java
public class SnowFlake {
/**
* 起始的时间戳(可设置当前时间之前的邻近时间)
*/
private final static long START_STAMP = 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_DATA_CENTER_NUM = ~(-1L << DATA_CENTER_BIT);
private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_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;
/**
* 数据中心ID(0~31)
*/
private final long dataCenterId;
/**
* 工作机器ID(0~31)
*/
private final long machineId;
/**
* 毫秒内序列(0~4095)
*/
private long sequence = 0L;
/**
* 上次生成ID的时间截
*/
private long lastStamp = -1L;
public SnowFlake(long dataCenterId, long machineId) {
if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
throw new IllegalArgumentException("dataCenterId 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
*/
public synchronized long nextId() {
long currStamp = getNewStamp();
if (currStamp < lastStamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
if (currStamp == lastStamp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大
if (sequence == 0L) {
//阻塞到下一个毫秒,获得新的时间戳
currStamp = getNextMill();
}
} else {
//不同毫秒内,序列号置为0
sequence = 0L;
}
lastStamp = currStamp;
// 移位并通过或运算拼到一起组成64位的ID
return (currStamp - START_STAMP) << TIMESTAMP_LEFT //时间戳部分
| dataCenterId << DATA_CENTER_LEFT //数据中心部分
| machineId << MACHINE_LEFT //机器标识部分
| sequence; //序列号部分
}
private long getNextMill() {
long mill = getNewStamp();
while (mill <= lastStamp) {
mill = getNewStamp();
}
return mill;
}
private long getNewStamp() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
SnowFlake snowFlake = new SnowFlake(11, 11);
long start = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
System.out.println(snowFlake.nextId());
}
System.out.println(System.currentTimeMillis() - start);
}
}
博客地址:分布式系列之ID生成器_分布式id生成器-CSDN博客
https://blog.csdn.net/lonelymanontheway/article/details/104532828?ops_request_misc=%257B%2522request%255Fid%2522%253A%25221e5d88ad71cd69252001b52b0da29ef4%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=1e5d88ad71cd69252001b52b0da29ef4&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-104532828-null-null.142^v102^pc_search_result_base8&utm_term=%E5%88%86%E5%B8%83%E5%BC%8FId%E7%94%9F%E6%88%90%E5%99%A8&spm=1018.2226.3001.4187分布式唯一ID生成器 最详解-CSDN博客
https://blog.csdn.net/2402_82958989/article/details/148616323?ops_request_misc=&request_id=&biz_id=102&utm_term=%E5%88%86%E5%B8%83%E5%BC%8FId%E7%94%9F%E6%88%90%E5%99%A8&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-148616323.142^v102^pc_search_result_base8&spm=1018.2226.3001.4187
百度UidGenerator
UidGenerator 是Java实现的, 基于 Snowflake 算法的唯一ID生成器。
UidGenerator 以组件形式工作在应用项目中, 支持自定义 workerId 位数和初始化策略, 从而适用于docker 等虚拟化环境下实例自动重启、漂移等场景。在实现上,UidGenerator 通过借用未来时间来解决 sequence 天然存在的并发限制;采用 RingBuffer 来缓存已生成的 UID, 并行化 UID 的生产和消费, 同时对 CacheLine 补齐,避免了由 RingBuffer 带来的硬件级「伪共享」问题. 最终单机 QPS 可达600万。
Snowflake 算法描述:指定机器 & 同一时刻 & 某一并发序列,是唯一的。据此可生成一个 64 bits的唯一ID(long)。默认采用上图字节分配方式:
- sign(1bit):固定1bit符号标识,即生成的UID为正数。
- delta seconds (28 bits):当前时间,相对于时间基点"2016-05-20"的增量值,单位:秒,最多可支持约8.7年
- worker id (22 bits):机器id,最多可支持约420w次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略。
- sequence (13 bits):每秒下的并发序列,13 bits可支持每秒8192个并发。
雪花实现创建ID
下面代码方案通过 Spring 自动装配 + Redis Lua ,在应用启动时 全局唯一地分配 workId / dataCenterId ,并将其 注入到 SnowflakeIdGenerator 中作为单例使用。
java
@Data
public class WorkDataCenterId {
/**
* Snowflake 中的 workerId
* 用来区分不同机器 / 实例
*/
private Long workId;
/**
* Snowflake 中的 dataCenterId
* 用来区分不同机房 / 集群
*/
private Long dataCenterId;
}
java
public class IdGeneratorAutoConfig {
/**
* 将 WorkAndDataCenterIdHandler 注册为 Spring Bean
*
* 作用:
* - 管理 Redis + Lua 分配逻辑
* - 整个应用生命周期只需要一个实例
*/
@Bean
public WorkAndDataCenterIdHandler workAndDataCenterIdHandler(
StringRedisTemplate stringRedisTemplate){
return new WorkAndDataCenterIdHandler(stringRedisTemplate);
}
/**
* 在 Spring 启动阶段就获取 workId / dataCenterId
*
* - 只在启动时执行一次
* - 不是每次生成 ID 都访问 Redis
*/
@Bean
public WorkDataCenterId workDataCenterId(
WorkAndDataCenterIdHandler workAndDataCenterIdHandler){
return workAndDataCenterIdHandler.getWorkAndDataCenterId();
}
/**
* 创建 SnowflakeIdGenerator
*
* 依赖:
* - WorkDataCenterId
*
* 最终效果:
* - SnowflakeIdGenerator 是一个全局单例
* - workId / dataCenterId 固定
*/
@Bean
public SnowflakeIdGenerator snowflakeIdGenerator(
WorkDataCenterId workDataCenterId){
return new SnowflakeIdGenerator(workDataCenterId);
}
}
java
public class IdGeneratorConstant {
/**
* 机器标识位数
*/
public static final long WORKER_ID_BITS = 5L;
public static final long DATA_CENTER_ID_BITS = 5L;
public static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);
public static final long MAX_DATA_CENTER_ID = -1L ^ (-1L << DATA_CENTER_ID_BITS);
}
java
@Slf4j // Lombok 注解:自动生成 private static final Logger log
public class WorkAndDataCenterIdHandler {
/**
* Redis 中用于记录 snowflake workerId 的 key
* 本质是一个全局自增 / 分配标识
*/
private final String SNOWFLAKE_WORK_ID_KEY = "snowflake_work_id";
/**
* Redis 中用于记录 snowflake dataCenterId 的 key
*/
private final String SNOWFLAKE_DATA_CENTER_ID_key = "snowflake_data_center_id";
/**
* Lua 脚本执行时使用的 KEYS 列表
* 对应 Lua 中的:
* KEYS[1] -> snowflake_work_id
* KEYS[2] -> snowflake_data_center_id
*/
public final List<String> keys =
Stream.of(SNOWFLAKE_WORK_ID_KEY, SNOWFLAKE_DATA_CENTER_ID_key)
.collect(Collectors.toList());
/**
* Spring 提供的 Redis 操作模板
* 用来执行 Lua 脚本
*/
private StringRedisTemplate stringRedisTemplate;
/**
* Redis Lua 脚本封装对象
* Spring 用它来执行 Lua 并解析返回值
*/
private DefaultRedisScript<String> redisScript;
/**
* 构造方法:由 Spring 注入 StringRedisTemplate
*/
public WorkAndDataCenterIdHandler(StringRedisTemplate stringRedisTemplate){
this.stringRedisTemplate = stringRedisTemplate;
try {
// 创建 Lua 脚本执行对象
redisScript = new DefaultRedisScript<>();
// 加载 classpath 下的 lua/workAndDataCenterId.lua
redisScript.setScriptSource(
new ResourceScriptSource(
new ClassPathResource("lua/workAndDataCenterId.lua")
)
);
// 指定 Lua 返回值类型(这里返回 JSON 字符串)
redisScript.setResultType(String.class);
} catch (Exception e) {
// Lua 脚本初始化失败直接记录错误
log.error("redisScript init lua error", e);
}
}
/**
* 从 Redis 中获取(或分配)workId 和 dataCenterId
*
* @return WorkDataCenterId(包含 workId 和 dataCenterId)
*/
public WorkDataCenterId getWorkAndDataCenterId(){
// 返回对象
WorkDataCenterId workDataCenterId = new WorkDataCenterId();
try {
/**
* Lua 脚本的 ARGV 参数
*/
String[] data = new String[2];
data[0] = String.valueOf(IdGeneratorConstant.MAX_WORKER_ID);
data[1] = String.valueOf(IdGeneratorConstant.MAX_DATA_CENTER_ID);
/**
* 执行 Lua 脚本
*/
String result = stringRedisTemplate.execute(redisScript, keys, data);
/**
* Lua 返回的是 JSON 字符串
* 例如:
* {"workId":3,"dataCenterId":1}
*/
workDataCenterId =
JSON.parseObject(result, WorkDataCenterId.class);
} catch (Exception e) {
log.error("getWorkAndDataCenterId error", e);
}
return workDataCenterId;
}
}
WorkAndDataCenterIdHandler是执行lua脚本的执行器,执行完脚本后获得了 WorkDataCenterId的实体,包好了 workId和 dataCenterId
WorkDataCenterId在注入到spring上下文的过程中,就调用了 WorkAndDataCenterIdHandler#getWorkAndDataCenterId方法在redis中加载 workId和 dataCenterId
下面就来分析下加载获取 workId和 dataCenterId的详细过程:
① 保证
workId / dataCenterId在 Redis 中存在(初始化),如果 Redis 里还没有,那么第一次启动的第一个实例会把它们都初始化为0② 判断是不是"第一次初始化的实例",如果两个 key 都是本次脚本创建的,说明这是整个集群里的第一个服务实例,直接返回{ "workId": 0, "dataCenterId": 0 }
③ 非第一次启动 → 开始"编号分配",后续实例进来,读取当前
workId 和 dataCenterId,然后按一个双层计数器逻辑分配:(优先增长 workId,workId 用完 → 推进 dataCenterId,两者都用完 → 全部归零)
情况 行为 workId < maxWorkerId workId 自增 workId == maxWorkerId 且 dataCenterId < maxDataCenterId dataCenterId 自增 workId == max && dataCenterId == max 两个都重置为 0 ④ 返回一个 JSON 给 Java:
Lua{ "workId": 3, "dataCenterId": 1 }
Lua
-- redis中work_id的key
local snowflake_work_id_key = KEYS[1]
-- redis中data_center_id的key
local snowflake_data_center_id_key = KEYS[2]
-- worker_id的最大阈值
local max_worker_id = tonumber(ARGV[1])
-- data_center_id的最大阈值
local max_data_center_id = tonumber(ARGV[2])
-- 返回的work_id
local return_worker_id = 0
-- 返回的data_center_id
local return_data_center_id = 0
-- work_id初始化flag
local snowflake_work_id_flag = false
-- data_center_id初始化flag
local snowflake_data_center_id_flag = false
-- 构建并返回JSON字符串
local json_result = string.format('{"%s": %d, "%s": %d}',
'workId', return_worker_id,
'dataCenterId', return_data_center_id)
-- 如果work_id不存在,则将值初始化为0
if (redis.call('exists', snowflake_work_id_key) == 0) then
redis.call('set',snowflake_work_id_key,0)
snowflake_work_id_flag = true
end
-- 如果data_center_id不存在,则将值初始化为0
if (redis.call('exists', snowflake_data_center_id_key) == 0) then
redis.call('set',snowflake_data_center_id_key,0)
snowflake_data_center_id_flag = true
end
-- 如果work_id和data_center_id都是初始化了,那么执行返回初始化的值
if (snowflake_work_id_flag and snowflake_data_center_id_flag) then
return json_result
end
-- 获得work_id的值
local snowflake_work_id = tonumber(redis.call('get',snowflake_work_id_key))
-- 获得data_center_id的值
local snowflake_data_center_id = tonumber(redis.call('get',snowflake_data_center_id_key))
-- 如果work_id的值达到了最大阈值
if (snowflake_work_id == max_worker_id) then
-- 如果data_center_id的值也达到了最大阈值
if (snowflake_data_center_id == max_data_center_id) then
-- 将work_id的值初始化为0
redis.call('set',snowflake_work_id_key,0)
-- 将data_center_id的值初始化为0
redis.call('set',snowflake_data_center_id_key,0)
else
-- 如果data_center_id的值没有达到最大值,将进行自增,并将自增的结果返回
return_data_center_id = redis.call('incr',snowflake_data_center_id_key)
end
else
-- 如果work_id的值没有达到最大值,将进行自增,并将自增的结果返回
return_worker_id = redis.call('incr',snowflake_work_id_key)
end
return string.format('{"%s": %d, "%s": %d}',
'workId', return_worker_id,
'dataCenterId', return_data_center_id)

这样将得到了加载后的包含datacenterId 和 workerId 的WorkDataCenterId 对象,当创建SnowflakeIdGenerator 时,将WorkDataCenterId注入进去:
注入 datacenterId 和 workerId 代码:
javapublic SnowflakeIdGenerator(WorkDataCenterId workDataCenterId) { if (Objects.nonNull(workDataCenterId.getDataCenterId())) { this.workerId = workDataCenterId.getWorkId(); this.datacenterId = workDataCenterId.getDataCenterId(); }else { this.datacenterId = getDatacenterId(maxDatacenterId); workerId = getMaxWorkerId(datacenterId, maxWorkerId); } }
java
@Slf4j
public class SnowflakeIdGenerator {
/**
* 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
*/
private static final long BASIS_TIME = 1288834974657L;
/**
* 机器标识位数
*/
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/**
* 毫秒内自增位
*/
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
/**
* 时间戳左移动位
*/
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
private final long workerId;
/**
* 数据标识 ID 部分
*/
private final long datacenterId;
/**
* 并发控制
*/
private long sequence = 0L;
/**
* 上次生产 ID 时间戳
*/
private long lastTimestamp = -1L;
/**
* IP 地址
*/
private InetAddress inetAddress;
public SnowflakeIdGenerator(WorkDataCenterId workDataCenterId) {
if (Objects.nonNull(workDataCenterId.getDataCenterId())) {
this.workerId = workDataCenterId.getWorkId();
this.datacenterId = workDataCenterId.getDataCenterId();
}else {
this.datacenterId = getDatacenterId(maxDatacenterId);
workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
}
public SnowflakeIdGenerator(InetAddress inetAddress) {
this.inetAddress = inetAddress;
this.datacenterId = getDatacenterId(maxDatacenterId);
this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
initLog();
}
private void initLog() {
if (log.isDebugEnabled()) {
log.debug("Initialization SnowflakeIdGenerator datacenterId:" + this.datacenterId + " workerId:" + this.workerId);
}
}
/**
* 有参构造器
*
* @param workerId 工作机器 ID
* @param datacenterId 序列号
*/
public SnowflakeIdGenerator(long workerId, long datacenterId) {
Assert.isFalse(workerId > maxWorkerId || workerId < 0,
String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
Assert.isFalse(datacenterId > maxDatacenterId || datacenterId < 0,
String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
this.workerId = workerId;
this.datacenterId = datacenterId;
initLog();
}
/**
* 获取 maxWorkerId
*/
protected long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuilder mpid = new StringBuilder();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (StringUtils.isNotBlank(name)) {
/*
* GET jvmPid
*/
mpid.append(name.split("@")[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}
/**
* 数据标识id部分
*/
protected long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
if (null == this.inetAddress) {
this.inetAddress = InetAddress.getLocalHost();
}
NetworkInterface network = NetworkInterface.getByInetAddress(this.inetAddress);
if (null == network) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
if (null != mac) {
id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
}
} catch (Exception e) {
log.warn(" getDatacenterId: " + e.getMessage());
}
return id;
}
public long getBase(){
int five = 5;
long timestamp = timeGen();
//闰秒
if (timestamp < lastTimestamp) {
long offset = lastTimestamp - timestamp;
if (offset <= five) {
try {
wait(offset << 1);
timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
}
if (lastTimestamp == timestamp) {
// 相同毫秒内,序列号自增
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 同一毫秒的序列数已经达到最大
timestamp = tilNextMillis(lastTimestamp);
}
} else {
// 不同毫秒内,序列号置为 1 - 2 随机数
sequence = ThreadLocalRandom.current().nextLong(1, 3);
}
lastTimestamp = timestamp;
return timestamp;
}
/**
* 获取分布式id
*
* @return id
*/
public synchronized long nextId() {
long timestamp = getBase();
// 时间戳部分 | 数据中心部分 | 机器标识部分 | 序列号部分
return ((timestamp - BASIS_TIME) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence;
}
/**
* 获取订单编号
*
* @return orderNumber
*/
public synchronized long getOrderNumber(long userId,long tableCount) {
long timestamp = getBase();
long sequenceShift = log2N(tableCount);
// 时间戳部分 | 数据中心部分 | 机器标识部分 | 序列号部分 | 用户id基因
return ((timestamp - BASIS_TIME) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| (sequence << sequenceShift)
| (userId % tableCount);
}
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
protected long timeGen() {
return SystemClock.now();
}
/**
* 反解id的时间戳部分
*/
public static long parseIdTimestamp(long id) {
return (id>>22)+ BASIS_TIME;
}
/**
* 求log2(N)
* */
public long log2N(long count) {
return (long)(Math.log(count)/ Math.log(2));
}
public long getMaxWorkerId() {
return maxWorkerId;
}
public long getMaxDatacenterId() {
return maxDatacenterId;
}
}
总结:
- 在构建
SnowflakeIdGenerator时,如果通过lua执行加载获取workDataCenterId失败,则还采取Mybiats-plus的生成策略nextId方法就是获取分布式id的方法,其内部getBase()是更新时间戳的部分,由 时间戳部分 | 数据中心部分 | 机器标识部分 | 序列号部分 这四个部分组成getOrderNumber方法是生成订单编号,使用了基因替换法,来解决在分库分表情况下,使用订单id和用户id查询订单时的全路由问题,关于基因法的详细介绍,可跳转到相关文档
