zookeeper和snowflake结合生成分布式唯一ID
·
背景
为便于问题定位,最近想通过日志记录每个HTTP请求的链路,即通过记录在日志内的traceId快速查出该HTTP请求对应的所有业务日志。对于争分夺秒的问题定位,依据traceId来定位问题与通过time + threadId相比,可大大解约定位问题时间。
将traceId写入日志
- 通过拦截器拦截HTTP请求;
- 在拦截器中通过MDC将traceId放入到ThreadLocal中,并在logBack.xml中配置
MDC.put("traceId", String.valueOf(snowflakeService.getId()));
<encoder>
<pattern>[%-5p] [%d{yyyy-MM-dd HH:mm:ss}] [%t] %X{traceId} [%C{1}:%M:%L] %m%n</pattern>
</encoder>
如何获取traceId
获取traceId的方法有很多,主要有:
Java 自带UUID
- 优点
简单方便,直接使用;无额外依赖,可保证分布式系统生成唯一traceId
String traceId = UUID.randomUUID().toString();
- 缺点
通过UUID生成的traceId有36位无规律字符串,在将其中四位’-'剔除后为32位字符串;该traceId一方面无规律,另一方面太长
开源解决方案
这些开源的ID生成方案多数借鉴了twitter算法来进行了具体的实现,美团和百度的开源方案比较完备,另一方面也增加了复杂度,对接入不太有好。
目前网络上已有的方案多数集中在traceId生成,对于如何生成工作ID描述不多。
如何生成工作机器ID
本文通过zookeeper临时顺序节点的方式生成机器ID,生成机器ID的代码逻辑如下:
// 每次机器启动时生成机器ID
private void init() {
//ZooKeeper客户端
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", retryPolicy);
client.start();
String nodeName = "/IDMaker/ID-";
// 每次重新启动服务时才需要重新生成机器ID
workerId = Long.valueOf(makeId(nodeName));
}
## 生成临时顺序节点,从上图看最多客容纳1024台机器,因此需要对获取的节点序号进行模1024取余
public Long makeId(String nodeName) {
String str = createSeqNode(nodeName);
if (null == str) {
return null;
}
int index = str.lastIndexOf(nodeName);
if (index >= 0) {
index += nodeName.length();
str = index <= str.length() ? str.substring(index) : "";
}
return Long.valueOf(str) % 1024;
}
// 使用zookeeper临时顺序节点特定生成递增机器ID
private String createSeqNode(String pathPefix) {
try {
// 创建一个 ZNode 顺序节点
String destPath = client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL)//避免zookeeper的顺序节点暴增,可以删除创建的顺序节点
.forPath(pathPefix);
return destPath;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
由于本程序对zookeeper有依赖,运行上述程序的前提是需要部署zookeeper服务,只有这样才可以正常生成分布式traceId,部署zookeeper可参考Zookeeper入门
生成traceId整体逻辑
@Service
public class IDMaker implements InitializingBean {
private final static Logger logger = LoggerFactory.getLogger(IDMaker.class);
private long workerId;
private final long epoch = 1575817859683L; // 时间起始标记点,作为基准,一般取系统的最近时间
private final long workerIdBits = 10L; // 机器标识位数
private long sequence = 0L; // 0,并发控制
private final long sequenceBits = 12L; //毫秒内自增位
private final long workerIdShift = this.sequenceBits; // 12
private final long timestampLeftShift = this.sequenceBits + this.workerIdBits;// 22
private final long sequenceMask = -1L ^ -1L << this.sequenceBits; // 4095,111111111111,12位
private long lastTimestamp = -1L;
CuratorFramework client = null;
@Override
public void afterPropertiesSet() {
init();
}
private void init() {
//ZooKeeper客户端
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", retryPolicy);
client.start();
String nodeName = "/IDMaker/ID-";
workerId = Long.valueOf(makeId(nodeName));
}
private String createSeqNode(String pathPefix) {
try {
// 创建一个 ZNode 顺序节点
String destPath = client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL)//避免zookeeper的顺序节点暴增,可以删除创建的顺序节点
.forPath(pathPefix);
return destPath;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public Long makeId(String nodeName) {
String str = createSeqNode(nodeName);
if (null == str) {
return null;
}
int index = str.lastIndexOf(nodeName);
if (index >= 0) {
index += nodeName.length();
str = index <= str.length() ? str.substring(index) : "";
}
return Long.valueOf(str) % 1024;
}
public synchronized long nextId() throws Exception {
long timestamp = this.timeGen();
if (this.lastTimestamp == timestamp) { // 如果上一个timestamp与新产生的相等,则sequence加一(0-4095循环); 对新的timestamp,sequence从0开始
this.sequence = this.sequence + 1 & this.sequenceMask;
if (this.sequence == 0) {
timestamp = this.tilNextMillis(this.lastTimestamp);// 重新生成timestamp
}
} else {
this.sequence = 0;
}
if (timestamp < this.lastTimestamp) {
logger.error(String.format("clock moved backwards.Refusing to generate id for %d milliseconds", (this.lastTimestamp - timestamp)));
throw new Exception(String.format("clock moved backwards.Refusing to generate id for %d milliseconds", (this.lastTimestamp - timestamp)));
}
this.lastTimestamp = timestamp;
return timestamp - this.epoch << this.timestampLeftShift | this.workerId << this.workerIdShift | this.sequence;
}
/**
* 等待下一个毫秒的到来, 保证返回的毫秒数在参数lastTimestamp之后
*/
private long tilNextMillis(long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
/**
* 获得系统当前毫秒数
*/
private static long timeGen() {
return System.currentTimeMillis();
}
}
生成的traceId如下图所示:
参考文档
更多推荐



所有评论(0)