从零到日志采集索引可视化、监控报警、rpc trace跟踪-分布式唯一ID生成
来源:互联网 发布:淘宝贷款额度可以作假 编辑:程序博客网 时间:2024/05/20 12:52
在平时的工作中如果将数据库进行了分库分表,那么肯定需要分布式唯一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;
}
}
2 0
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-分布式唯一ID生成
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-日志索引
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-系统上下线监控
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-自定义日志框架
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-架构介绍
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-日志事件、埋点设计及对接
- <从PAXOS到ZOOKEEPER分布式一致性原理与实践>读书笔记-zookeeper全局唯一id生成
- php 生成分布式唯一ID
- 分布式唯一ID生成服务
- 分布式系统唯一ID生成
- 分布式系统唯一ID生成方法
- 分布式系统唯一ID生成方案汇总
- 分布式系统唯一ID生成方案汇总
- HIVE下分布式生成整型唯一ID
- 分布式系统唯一ID生成方案汇总
- 分布式系统唯一ID生成方案
- 分布式系统唯一ID生成方案汇总
- 分布式系统中唯一ID的生成
- Oracle的存储过程介绍
- WIN7下编译NIOS2出现如下错误提示:make:***[**.mk] Error 1 或者 make: *** [**.elf] Error 1
- 算法——霍夫曼编码压缩算法
- GAN人脸修复--Generative Face Completion
- ubuntu server 打开中文
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-分布式唯一ID生成
- java内连接和用pl/sql developer登录oracle时出现ORA-12154: TNS: 无法解析指定的连接标识符
- 肢体语言改变内心世界tedtalk总结
- temp todo reading
- 第四届省赛题 Substring
- java-int的封装类的比较
- 三天打鱼两天晒网问题
- Java中遍历数组的三种方式复习
- 数据库查询优化