在平时的工作中如果将数据库进行了分库分表,那么肯定需要分布式唯一ID的生成策略。rpc trace跟踪模块也需要一个分布式唯一ID的生成器,本文介绍基于snowflake改造的分布式唯一ID生成策略。
根据snowflake的算法,做了些许改变,唯一ID是一个int64的值,第一位占位,接下来40位为毫秒级的时间,接下来13位(支持8192个)为app和host组合的分配的值(即在N个服务器上每个服务器部署M个项目,总共部署N*M=8192),接下去是该毫秒产生的序列号10位(每毫秒生成1024个,每秒生成1024*1000,并发很高)
该算法中最重要的就是如何为app和host进行编号,我们采用zk写数据version自增的方式来为app和host编号,这块代码只会在初始化的时候执行一次,所以不会影响效率,而且在生成ID的时候是作为jar包在项目中生成的,并不会请求任何一个ID生成中心,减少了网络消耗。具体代码如下(见:
https://github.com/JThink/SkyEye/tree/master/skyeye-trace和
https://github.com/JThink/SkyEye/tree/master/skyeye-client):
public class IncrementIdGen
implements IdGen {
// 为某台机器上的某个项目分配的 serviceId (注意区分 Span 中的 serviceId )
private static String serviceId = null;
// register info
private RegisterDto registerDto ;
/**
* 利用 zookeeper
* @return
*/
@Override
public String nextId() {
String app = this. registerDto.getApp() ;
String host = this. registerDto.getHost() ;
ZkClient zkClient = this. registerDto.getZkClient() ;
String path = Constants. ZK_REGISTRY_ID_ROOT_PATH + Constants. SLASH + app + Constants. SLASH + host ;
if (zkClient.exists(path)) {
// 如果已经有该节点,表示已经为当前的 host 上部署的该 app 分配的编号(应对某个服务重启之后编号不变的问题),直接获取该 id ,而无需生成
return zkClient.readData(Constants. ZK_REGISTRY_ID_ROOT_PATH + Constants. SLASH + app + Constants. SLASH + host) ;
} else {
// 节点不存在,那么需要生成 id ,利用 zk 节点的版本号每写一次就自增的机制来实现
Stat stat = zkClient.writeDataReturnStat(Constants. ZK_REGISTRY_SEQ , new byte[ 0] , - 1) ;
// 生成 id
String id = String. valueOf(stat.getVersion()) ;
// 将数据写入节点
zkClient.createPersistent(path , true) ;
zkClient.writeData(path , id) ;
return id ;
}
}
/**
* 获取 ID
* @return
*/
public static String getId() {
return serviceId ;
}
/**
* 对 ID 赋值
* @param id
* @return
*/
public static void setId(String id) {
serviceId = id ;
}
public IncrementIdGen() {
}
public IncrementIdGen(RegisterDto registerDto) {
this. registerDto = registerDto ;
}
public RegisterDto getRegisterDto() {
return registerDto ;
}
public IncrementIdGen setRegisterDto(RegisterDto registerDto) {
this. registerDto = registerDto ;
return this;
}
}
// 为某台机器上的某个项目分配的 serviceId (注意区分 Span 中的 serviceId )
private static String serviceId = null;
// register info
private RegisterDto registerDto ;
/**
* 利用 zookeeper
* @return
*/
@Override
public String nextId() {
String app = this. registerDto.getApp() ;
String host = this. registerDto.getHost() ;
ZkClient zkClient = this. registerDto.getZkClient() ;
String path = Constants. ZK_REGISTRY_ID_ROOT_PATH + Constants. SLASH + app + Constants. SLASH + host ;
if (zkClient.exists(path)) {
// 如果已经有该节点,表示已经为当前的 host 上部署的该 app 分配的编号(应对某个服务重启之后编号不变的问题),直接获取该 id ,而无需生成
return zkClient.readData(Constants. ZK_REGISTRY_ID_ROOT_PATH + Constants. SLASH + app + Constants. SLASH + host) ;
} else {
// 节点不存在,那么需要生成 id ,利用 zk 节点的版本号每写一次就自增的机制来实现
Stat stat = zkClient.writeDataReturnStat(Constants. ZK_REGISTRY_SEQ , new byte[ 0] , - 1) ;
// 生成 id
String id = String. valueOf(stat.getVersion()) ;
// 将数据写入节点
zkClient.createPersistent(path , true) ;
zkClient.writeData(path , id) ;
return id ;
}
}
/**
* 获取 ID
* @return
*/
public static String getId() {
return serviceId ;
}
/**
* 对 ID 赋值
* @param id
* @return
*/
public static void setId(String id) {
serviceId = id ;
}
public IncrementIdGen() {
}
public IncrementIdGen(RegisterDto registerDto) {
this. registerDto = registerDto ;
}
public RegisterDto getRegisterDto() {
return registerDto ;
}
public IncrementIdGen setRegisterDto(RegisterDto registerDto) {
this. registerDto = registerDto ;
return this;
}
}
public class UniqueIdGen
implements IdGen {
// 开始使用该算法的时间为 : 2017-01-01 00:00:00
private static final long START_TIME = 1483200000000L ;
// 时间戳 bit 数,最多能支持到 2050 年,首位为标记位( java 的 long 首位是 0 表示为正数)
private static final int TIME_BITS = 40 ;
// worker id 的 bit 数,最多支持 8192 个 app 和 host 的组合(即在 N 个服务器上每个服务器部署 M 个项目,总共部署 N*M=8192 )
private static final int APP_HOST_ID_BITS = 13 ;
// 序列号,支持单节点最高 1000*1024 的并发
private final static int SEQUENCE_BITS = 10 ;
// 最大的 app host id , 65535
private final static long MAX_APP_HOST_ID = ~(- 1L << APP_HOST_ID_BITS) ;
// 最大的序列号, 127
private final static long MAX_SEQUENCE = ~(- 1L << SEQUENCE_BITS) ;
// app host 编号的移位
private final static long APP_HOST_ID_SHIFT = SEQUENCE_BITS ;
// 时间戳的移位
private final static long TIMESTAMP_LEFT_SHIFT = APP_HOST_ID_BITS + APP_HOST_ID_SHIFT ;
// 该项目的 app host id ,对应着为某台机器上的某个项目分配的 serviceId (注意区分 Span 中的 serviceId )
private long appHostId ;
// 上次生成 ID 的时间戳
private long lastTimestamp = - 1L ;
// 当前毫秒生成的序列
private long sequence = 0L ;
// 单例
private static volatile UniqueIdGen idGen = null;
/**
* 实例化
* @param appHostId
* @return
*/
public static UniqueIdGen getInstance( long appHostId) {
if ( idGen == null) {
synchronized(UniqueIdGen. class) {
if ( idGen == null) {
idGen = new UniqueIdGen(appHostId) ;
}
}
}
return idGen ;
}
private UniqueIdGen( long appHostId) {
if (appHostId > MAX_APP_HOST_ID) {
// zk 分配的 serviceId 过大 ( 基本小规模的公司不会出现这样的问题 )
throw new IllegalArgumentException(String. format( "app host Id wrong: %d " , appHostId)) ;
}
this. appHostId = appHostId ;
}
/**
* 利用 twitter 的 snowflake (做了些微修改)算法来实现
* @return
*/
@Override
public String nextId() {
return Long. toHexString( this.genUniqueId()) ;
}
/**
* 生成唯一 id 的具体实现
* @return
*/
private synchronized long genUniqueId() {
long current = System. currentTimeMillis() ;
if (current < lastTimestamp) {
// 如果当前时间小于上一次 ID 生成的时间戳,说明系统时钟回退过,出现问题返回 -1
return - 1 ;
}
if (current == lastTimestamp) {
// 如果当前生成 id 的时间还是上次的时间,那么对 sequence 序列号进行 +1
sequence = ( sequence + 1) & MAX_SEQUENCE ;
if ( sequence == MAX_SEQUENCE) {
// 当前毫秒生成的序列数已经大于最大值,那么阻塞到下一个毫秒再获取新的时间戳
current = this.nextMs( lastTimestamp) ;
}
} else {
// 当前的时间戳已经是下一个毫秒
sequence = 0L ;
}
// 更新上次生成 id 的时间戳
lastTimestamp = current ;
// 进行移位操作生成 int64 的唯一 ID
return ((current - START_TIME) << TIMESTAMP_LEFT_SHIFT)
| ( this. appHostId << APP_HOST_ID_SHIFT)
| sequence ;
}
/**
* 阻塞到下一个毫秒
* @param timeStamp
* @return
*/
private long nextMs( long timeStamp) {
long current = System. currentTimeMillis() ;
while (current <= timeStamp) {
current = System. currentTimeMillis() ;
}
return current ;
}
}
// 开始使用该算法的时间为 : 2017-01-01 00:00:00
private static final long START_TIME = 1483200000000L ;
// 时间戳 bit 数,最多能支持到 2050 年,首位为标记位( java 的 long 首位是 0 表示为正数)
private static final int TIME_BITS = 40 ;
// worker id 的 bit 数,最多支持 8192 个 app 和 host 的组合(即在 N 个服务器上每个服务器部署 M 个项目,总共部署 N*M=8192 )
private static final int APP_HOST_ID_BITS = 13 ;
// 序列号,支持单节点最高 1000*1024 的并发
private final static int SEQUENCE_BITS = 10 ;
// 最大的 app host id , 65535
private final static long MAX_APP_HOST_ID = ~(- 1L << APP_HOST_ID_BITS) ;
// 最大的序列号, 127
private final static long MAX_SEQUENCE = ~(- 1L << SEQUENCE_BITS) ;
// app host 编号的移位
private final static long APP_HOST_ID_SHIFT = SEQUENCE_BITS ;
// 时间戳的移位
private final static long TIMESTAMP_LEFT_SHIFT = APP_HOST_ID_BITS + APP_HOST_ID_SHIFT ;
// 该项目的 app host id ,对应着为某台机器上的某个项目分配的 serviceId (注意区分 Span 中的 serviceId )
private long appHostId ;
// 上次生成 ID 的时间戳
private long lastTimestamp = - 1L ;
// 当前毫秒生成的序列
private long sequence = 0L ;
// 单例
private static volatile UniqueIdGen idGen = null;
/**
* 实例化
* @param appHostId
* @return
*/
public static UniqueIdGen getInstance( long appHostId) {
if ( idGen == null) {
synchronized(UniqueIdGen. class) {
if ( idGen == null) {
idGen = new UniqueIdGen(appHostId) ;
}
}
}
return idGen ;
}
private UniqueIdGen( long appHostId) {
if (appHostId > MAX_APP_HOST_ID) {
// zk 分配的 serviceId 过大 ( 基本小规模的公司不会出现这样的问题 )
throw new IllegalArgumentException(String. format( "app host Id wrong: %d " , appHostId)) ;
}
this. appHostId = appHostId ;
}
/**
* 利用 twitter 的 snowflake (做了些微修改)算法来实现
* @return
*/
@Override
public String nextId() {
return Long. toHexString( this.genUniqueId()) ;
}
/**
* 生成唯一 id 的具体实现
* @return
*/
private synchronized long genUniqueId() {
long current = System. currentTimeMillis() ;
if (current < lastTimestamp) {
// 如果当前时间小于上一次 ID 生成的时间戳,说明系统时钟回退过,出现问题返回 -1
return - 1 ;
}
if (current == lastTimestamp) {
// 如果当前生成 id 的时间还是上次的时间,那么对 sequence 序列号进行 +1
sequence = ( sequence + 1) & MAX_SEQUENCE ;
if ( sequence == MAX_SEQUENCE) {
// 当前毫秒生成的序列数已经大于最大值,那么阻塞到下一个毫秒再获取新的时间戳
current = this.nextMs( lastTimestamp) ;
}
} else {
// 当前的时间戳已经是下一个毫秒
sequence = 0L ;
}
// 更新上次生成 id 的时间戳
lastTimestamp = current ;
// 进行移位操作生成 int64 的唯一 ID
return ((current - START_TIME) << TIMESTAMP_LEFT_SHIFT)
| ( this. appHostId << APP_HOST_ID_SHIFT)
| sequence ;
}
/**
* 阻塞到下一个毫秒
* @param timeStamp
* @return
*/
private long nextMs( long timeStamp) {
long current = System. currentTimeMillis() ;
while (current <= timeStamp) {
current = System. currentTimeMillis() ;
}
return current ;
}
}