1.分布式id实现方案
我们先看看常见的分布id解决方案以及各自特点的对比
1.UUID 这种方案复杂度最低,但是会影响存储空间和性能
2.利用单机数据库的自增主键,作为分布式ID的生成器,复杂度适中,ID长度较UUID更短,但是受到单机数据库性能的限制,并发量大的时候,该方案也不是最优方案。
3.利用redis,zookeeper的特性来生成id,如:redis的自增命令,zookeeper的顺序节点,这种方案和单机数据库(mysql)性能相比,性能会有所提高,可以适当选用。
4.雪花算法:一切问题如果能直接用算法解决,那是最合适的,利用雪花算法可以生成分布式Id,其底层原理就是通过某台机器在某一毫秒内对某一个数字自增,这种方案也能保证分布式架构中的系统id唯一,但是只能保证趋势递增。
2、基于redis的分布式高性能的redis分布式id生成器
基本步骤:根据初始化workerid,并保存应用和ip地址信息,存入redis, 缓存2个小时。并定时更新redis的workerId的时间戳. 然后根据workid和雪花算法生成分布式id, 主要步骤如下:
1)初始化workerId
/**
* 生成workid
*/
private long initAndGetWorkerId(@NonNull SnowflakeWorkerAutoService snowflakeWorkerAutoService) {
long time = System.currentTimeMillis();
log.info("[getWorkerIdAuto] 开始生成雪花算法workId");
long workId = snowflakeWorkerAutoService.getWorkerId(moduleName, SnowflakeIdGenerator.MAX_WORKERID);
log.info("[getWorkerIdAuto] 结束雪花算法workId:{},[生成雪花workId耗时]:{}", workId, System.currentTimeMillis() - time);
// 定时更新workId
snowflakeWorkerAutoService.timerUpdateModifiedTime(moduleName, workId);
log.info("[getWorkerIdAuto] 创建定时任务workId:{}, moduleName:{}, hostAddress:{}", workId,
moduleName, IpAddressHelper.getLocalIpAddress());
log.info("[getWorkerIdAuto] 定时更新雪花算法workId:{}", workId);
return workId;
}
protected long getWorkerId(String moduleName, long maxWorkId) {
SnowflakeWorkerAutoEntity snowflakeWorkerAuto = new SnowflakeWorkerAutoEntity();
snowflakeWorkerAuto.setModuleName(moduleName);
snowflakeWorkerAuto.setHostAddress(IpAddressHelper.getLocalIpAddress());
snowflakeWorkerAuto.setCreatedTime(LocalDateTime.now());
snowflakeWorkerAuto.setModifiedTime(LocalDateTime.now());
Long workerId = null;
for (long tryWorkId = 1; tryWorkId <= maxWorkId; tryWorkId++) {
snowflakeWorkerAuto.setId(tryWorkId);
Boolean setSuccess = redisTemplate.opsForValue().setIfAbsent(
workIdKeyPre + tryWorkId, snowflakeWorkerAuto, 2, TimeUnit.HOURS);
if (setSuccess != null && setSuccess) {
workerId = tryWorkId;
break;
}
}
// workId若是null,表示workId被用完
if (workerId == null) {
throw new RuntimeException("ELE的雪花算法的workId已经用完,建议配置您业务特有的[ele.snowflake.redis-key-prefix]");
}
return workerId;
}
2)定时更新workerid
/**
* 定时更新修改时间
*
* @param moduleName 服务名称
* @param workId workId
*/
public void timerUpdateModifiedTime(String moduleName, long workId) {
if (this.executorService == null) {
this.executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("snowflake-workid-thread-%d").daemon(true).build());
SnowflakeWorkerAutoService snowflakeWorkerAutoService = this;
Runnable timerTask = () -> {
long startTime = System.currentTimeMillis();
try {
snowflakeWorkerAutoService.updateModifiedTime(moduleName, workId);
log.info("[snowflake.updateModifiedTime] workId {} 进行续约, 耗时: {} ms", workId, System.currentTimeMillis() - startTime);
} catch (Exception e) {
log.error(String.format("[snowflake.updateModifiedTime] workId %s 续约失败, 耗时: %s ms",
workId, System.currentTimeMillis() - startTime), e);
}
};
this.executorService.scheduleAtFixedRate(timerTask, 5, 5, TimeUnit.MINUTES);
}
}
protected void updateModifiedTime(String moduleName, long workId) {
SnowflakeWorkerAutoEntity snowflakeWorkerAuto = new SnowflakeWorkerAutoEntity();
snowflakeWorkerAuto.setModuleName(moduleName);
snowflakeWorkerAuto.setHostAddress(IpAddressHelper.getLocalIpAddress());
snowflakeWorkerAuto.setCreatedTime(LocalDateTime.now());
snowflakeWorkerAuto.setModifiedTime(LocalDateTime.now());
SnowflakeWorkerAutoEntity entity = redisTemplate.opsForValue().get(workIdKeyPre + workId);
if (entity == null) {
log.error("redis雪花缓存的workId丢失: {}", workId);
} else {
if (!StringUtils.equals(snowflakeWorkerAuto.getHostAddress(), entity.getHostAddress())) {
log.error("redis雪花缓存IP信息发生不可预知变动: {} -> {}", snowflakeWorkerAuto.getHostAddress(), entity.getHostAddress());
}
if (!StringUtils.equals(snowflakeWorkerAuto.getModuleName(), entity.getModuleName())) {
log.error("redis雪花缓存module信息发生不可预知变动: {} -> {}", snowflakeWorkerAuto.getModuleName(), entity.getModuleName());
}
}
redisTemplate.opsForValue().set(workIdKeyPre + workId, snowflakeWorkerAuto, 2, TimeUnit.HOURS);
}
3)根据workerId和雪花算法生成分布式id
/**
* 下一个标识
*
* @return 下一个标识
*/
@Override
public synchronized long nextId() {
// 获取当前时间戳
long timestamp = getCurrentTimestamp();
// 判断时间戳大小
// 判断时间戳大小: 相等则递增, 归零则等待下一毫秒
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & SEQUENCE_MASK;
if (sequence == 0) {
timestamp = getNextTimestamp();
}
}
// 判断时间戳大小: 大于则设置顺序值
else {
// sequence = 0L;
// 给同一毫秒时留出递增空间
if (sequence < (SEQUENCE_MASK_QUARTER)) {
sequence = (sequence + 1) & SEQUENCE_MASK;
} else {
sequence = 0L;
}
}
// 设置上次时间戳
lastTimestamp = timestamp;
// 返回雪花标识
return ((timestamp - EPOCH_TIMESTAMP) << TIMESTAMP_SHIFT) | (workerId << WORKERID_SHIFT) | sequence;
}
本文只介绍了核心代码,完整代码已免费开放, 地址:https://download.csdn.net/download/wangyantao111/89829468