概述
1:实现原理及介绍:
全局唯一ID生成的是Long类型的ID,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特。
Snowflake ID组成结构:正数位
(占1比特)+ 时间戳
(占41比特)+ 机器ID
(占10比特)+ 自增值
(占12比特),总共64比特组成的一个Long类型。
-
第一个bit位(1bit):Java中long的最高位是符号位代表正负,正数是0,负数是1,一般生成ID都为正数,所以默认为0。
-
时间戳部分(41bit):毫秒级的时间,不建议存当前时间戳,而是用(当前时间戳 - 固定开始时间戳)的差值,可以使产生的ID从更小的值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
-
工作机器id(10bit):也被叫做
workId
,这个可以灵活配置,机房或者机器号组合都可以。 -
序列号部分(12bit),自增值支持同一毫秒内同一个节点可以生成4096个ID
- 其中工作机器ID共有2的10次方1024个 每个实例程序启动的时候进行分配一个机器编号,每台机器生成的唯一ID可以支持每秒409万并发,理论上最大可以支持41.948亿并发。
- 每台实例启动时会从redis里分配一个机器编号(redis宕机会随机分配),存储在redis中设置过期时间<machineKey,machineValue,expireTime>,结构为,在过期之前或进行续命保活操作,同事在服务列表中去注册上,服务列表存储在redis中 结构为 hsah value为map结构,具体位置 listkey-→<machineKey,machineValue> 同事也会定期去redis中维护服务列表,剔除列表中失活的机器编号,这样可以进行再分配,保证列表中都为活着的机器。
2:源码实现
package com.example.demo;
import com.zkh360.zaf.lock.GlobalLock;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.Serializable;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author wei.gu
* @project_name sp
* @create 2020-04-13 13:47
* * 利用SnowFlake算法,生成有个全局唯一的编号信息
* Snowflake生成的是Long类型的ID,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特。
* Snowflake ID组成结构:正数位(占1比特)+ 时间戳(占41比特)+ 机器ID(占5比特)+ 数据中心(占5比特)+ 自增值(占12比特),总共64比特组成的一个Long类型。
* 第一个bit位(1bit):Java中long的最高位是符号位代表正负,正数是0,负数是1,一般生成ID都为正数,所以默认为0。
* 时间戳部分(41bit):毫秒级的时间,不建议存当前时间戳,而是用(当前时间戳 - 固定开始时间戳)的差值,可以使产生的ID从更小的值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
* 工作机器id(10bit):也被叫做workId,这个可以灵活配置,机房或者机器号组合都可以。
* 序列号部分(12bit),自增值支持同一毫秒内同一个节点可以生成4096个ID
**/
@Slf4j
@Component
public class GlobalNumberUtil implements CommandLineRunner {
/**
* 起始的时间戳
*/
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 static long dataCenterId = 1L; //数据中心
private static long machineId; //机器标识
private static long sequence = 0L; //序列号
private static long lastTimeStamp = -1L; //上一次时间戳
private static String machinekey = "com:qishucai:machineId";
private static String dataCenterKey = "com:qishucai:dataCenterID";
private static StringBuilder sb = new StringBuilder("ip:pid:dataCenterKey:machinekey:");
private static String aliveListKey = "com:qishucai:aliveList";
private static String machineAndDataCenterKey;
private final static int MAXCOMBINATION = 1024; //数据中心编号和机器编号的最大组合
private final static int END = 1;//重试拿不重复的数据中心编号和机器编号的结束标志
@Resource
RedisTemplate<Object, Object> redisTemplate;
private static long getNextMill() {
long mill = getNewTimeStamp();
while (mill <= lastTimeStamp) {
mill = getNewTimeStamp();
}
return mill;
}
private static long getNewTimeStamp() {
return System.currentTimeMillis();
}
/**
* 根据指定的数据中心ID和机器标志ID生成指定的序列号
*
* @param dataCenterId 数据中心ID
* @param machineId 机器标志ID
*/
private GlobalNumberUtil(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和机器标志ID生成指定的序列号
*/
public GlobalNumberUtil() {
}
/**
* 产生下一个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;
}
/**
* 根据传过来的位数生成对应位数的唯一编号
*
* @return
*/
public synchronized long nextId(int numberCount) {
if (numberCount > 31 || numberCount < 0) {
throw new IllegalArgumentException("NumberCount can't be greater than 31 or less than 0");
}
long id = nextId();
String strID = new String(id + "");
return Long.valueOf(strID.substring(strID.length() - numberCount, strID.length()));
}
/**
* Callback used to run the bean.
*
* @param args incoming main method arguments
* @throws Exception on error
*/
@Override
public void run(String... args) throws Exception {
String ip = null;
String pid = null;
try {
ip = InetAddress.getLocalHost().getHostAddress();
String processName = ManagementFactory.getRuntimeMXBean().getName();
pid = processName.substring(0, processName.indexOf('@'));
} catch (UnknownHostException e) {
log.error("获取IP地址出错");
e.printStackTrace();
}
//获取存活列表 添加新的额 机器编号 数据中心编号对
Map<Object, Object> map = hmget(aliveListKey);
if (map == null) {
map = new HashMap<>();
}
boolean flag = true;
int endFlag = map.size() + 1;
NumberEntry numberEntry = null;
if (map.size() < MAXCOMBINATION) {//数据中心编号 和机器编号 只能有1024种组合
while (flag) {
numberEntry = getNumberEntry();
for (Map.Entry entry : map.entrySet()) {
NumberEntry value = (NumberEntry) entry.getValue();
if (value.getMachineId() == numberEntry.getMachineId() && value.getMachineId() == numberEntry.getMachineId()) {
endFlag = 0;
log.info("生成的数据中心编号为= {} 生成的机器ID为= {} 已经存在重新获取", dataCenterId, machineId);
break;
}
endFlag--;
}
if (endFlag == END) {
flag = false;
}
}
}
log.info("生成的数据中心编号为= {} 生成的机器ID为= {} 来源为= {}", dataCenterId, machineId, numberEntry.source == true ? "redis生成" : "随机生成");
machineAndDataCenterKey = sb.append(ip).append("-").append(pid).append("-").append(dataCenterId).append("-").append(machineId).toString();
map.put(machineAndDataCenterKey, numberEntry);
hmset(aliveListKey, map);
log.info("成功更新了机器列表集体信息为:key= {} value= {}", machineAndDataCenterKey, map);
//本机维护自己的 编号对一直存活下去,需要在过期之前续命
set(machineAndDataCenterKey, numberEntry, 16);
log.info("本机维护自己的 初始续命成功具体信息为 key = {} value = {}", machineAndDataCenterKey, numberEntry);
}
private NumberEntry getNumberEntry() {
Boolean flag = true;
try {
machineId = incrForMachineId(machinekey);
dataCenterId = incrForDataCenterId(dataCenterKey, machineId);
} catch (Exception e) {
flag = false;
Random random = new Random();
machineId = (long) random.nextInt(32);
dataCenterId = (long) random.nextInt(32);
e.printStackTrace();
}
return new NumberEntry(dataCenterId, machineId, flag);
}
/**
* 内部类用来存储 机器编号 和数据中心编号
*
* @return
*/
private static class NumberEntry implements Serializable {
private long dataCenterId; //数据中心
private long machineId; //机器标识
private boolean source = true; //来源标识 true 代表redis生成 false代表random生成
public NumberEntry() {
}
@Override
public String toString() {
return "{" +
"dataCenterId=" + dataCenterId +
", machineId=" + machineId +
", source=" + source +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NumberEntry that = (NumberEntry) o;
if (dataCenterId != that.dataCenterId) return false;
if (machineId != that.machineId) return false;
return source == that.source;
}
@Override
public int hashCode() {
int result = (int) (dataCenterId ^ (dataCenterId >>> 32));
result = 31 * result + (int) (machineId ^ (machineId >>> 32));
result = 31 * result + (source ? 1 : 0);
return result;
}
public long getDataCenterId() {
return dataCenterId;
}
public void setDataCenterId(long dataCenterId) {
this.dataCenterId = dataCenterId;
}
public long getMachineId() {
return machineId;
}
public void setMachineId(long machineId) {
this.machineId = machineId;
}
public boolean isSource() {
return source;
}
public void setSource(boolean source) {
this.source = source;
}
public NumberEntry(long dataCenterId, long machineId, boolean source) {
this.dataCenterId = dataCenterId;
this.machineId = machineId;
this.source = source;
}
}
/**
* 在过期之前进行重新放 保证在程序运行时一直存在
*
* @return
*/
@Scheduled(cron = "0/9 * * * * ?")
public void keepAliveBeforExpire() {
//本机维护自己的 编号对一直存活下去,需要在过期之前续命
NumberEntry numberEntry = (NumberEntry) get(machineAndDataCenterKey);
if (Objects.nonNull(numberEntry)) {
set(machineAndDataCenterKey, numberEntry, 16);
log.info("本机维护自己的 续命成功具体信息为 key= {} value= {}", machineAndDataCenterKey, numberEntry);
} else {
log.info("本机维护自己的 续命结束 对应的key= {}", machineAndDataCenterKey);
}
}
/**
* 保存的服务器存活列表 会定期进行检查是否存活 存活的话就保存在名单里
* 如果已经失活就从列表中剔除
*
* @return
*/
@Scheduled(cron = "0 */1 * * * ?")
@GlobalLock
public void aliveList() {
Map<Object, Object> map = hmget(aliveListKey);
for (Map.Entry entry : map.entrySet()) {
Object value = get(entry.getKey().toString());
if (value == null) {
hdel(aliveListKey, entry.getKey().toString());
log.info("定期从存活名单列表里剔除已死亡的服务:key= {} value= {}", entry.getKey().toString(), entry.getValue().toString());
} else {
log.info("最近机器列表中存活的机器信息为:key= {} value= {}", entry.getKey().toString(), entry.getValue().toString());
}
}
}
/**
* 获得机器编号 范围0-31
*
* @param key 键
* @return
*/
private long incrForMachineId(String key) {
Long increment = redisTemplate.opsForValue().increment(key, 1);
if (increment == null || increment > 31 || increment < 0) {
redisTemplate.opsForValue().set(key, -1);
}
return redisTemplate.opsForValue().increment(key, 1);
}
/**
* 获得数据中心编号 范围0-31
*
* @param key 键
* @return
*/
private long incrForDataCenterId(String key, long machineId) {
Long increment = new Long(redisTemplate.opsForValue().get(key).toString());
if (increment == null || increment > 31 || increment < 0) {
redisTemplate.opsForValue().set(key, 0);
}
if (machineId > 31) {
return new Long(redisTemplate.opsForValue().increment(key, 1).toString());
} else {
return new Long(redisTemplate.opsForValue().get(key).toString());
}
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
private Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
private void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
private boolean hmset(String key, Map<Object, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
private boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
private boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
private Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
}
最后
以上就是呆萌香烟为你收集整理的分布式全局唯一ID的原理及实现1:实现原理及介绍: 2:源码实现的全部内容,希望文章能够帮你解决分布式全局唯一ID的原理及实现1:实现原理及介绍: 2:源码实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复