雪花算法是分布式系统中常用的唯一 id 生成算法,
一般创建雪花算法的时候,依赖dataCenterId
与workerId
这两个值的取值都为 0~31 之间的整型。
对于一个雪花算法的 id 生成器,需要设置这两个参数值。
如果在单实例中,这两个值随便怎么设置都是没问题的,
但是对于应用集群部署的时候,为了保证高可用,肯定需要多实例部署。
这个时候,就需要保证一个应用多个实例的这两个参数值是不同的。
一种办法是在启动应用实例的时候,人为设置不同值。
另一种思路,就是在程序启动的时候,自动进行计算,选举出一组合适的参数。
网上已经有很多方案了,甚至已经封装成了 jar 可以很方便的进行使用。
但是基本上都是基于 ZooKeeper 的。
我这里实现了一个基于 Redis 的实现方式
public static SnowIdDto calculateDataIdAndWorkId(RedisTemplate redisTemplate, String appName) {
String key = RedisPrefix.SNOW + appName;
Object o = redisTemplate.opsForValue().get(key);
if (o == null) {
// 初始化
SnowIdDto snowIdDto = new SnowIdDto(System.currentTimeMillis(), 0, 0);
List<SnowIdDto> list = new ArrayList<>(1);
list.add(snowIdDto);
redisTemplate.opsForValue().set(key, list);
return snowIdDto;
} else {
List<SnowIdDto> list = (List<SnowIdDto>) o;
// 需要自己先排序,保证list中的数据,根据时间戳从小到大排布
Collections.sort(list);
// 节点数据还没用完
if (list.size() < DATA_SIZE * DATA_SIZE) {
//计算下一个节点
// 当前最后一个节点
SnowIdDto snowIdDto = list.get(list.size() - 1);
// 优先变更工作节点
SnowIdDto nextNode = null;
if (snowIdDto.getWorkerId() < DATA_SIZE - 1) {
nextNode = new SnowIdDto(System.currentTimeMillis(), snowIdDto.getDataCenterId(), snowIdDto.getWorkerId() + 1);
} else {
nextNode = new SnowIdDto(System.currentTimeMillis(), snowIdDto.getDataCenterId() + 1, 0);
}
list.add(nextNode);
redisTemplate.opsForValue().set(key, list);
return nextNode;
} else {
//计算出目前时间戳最小的那个,返回,更新时间戳
SnowIdDto snowIdDto = list.get(0);
snowIdDto.setTimestamp(System.currentTimeMillis());
Collections.sort(list);
redisTemplate.opsForValue().set(key, list);
return snowIdDto;
}
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SnowIdDto implements Serializable, Comparable<SnowIdDto> {
/**
* 注册时的时间戳
*/
private Long timestamp;
/**
* 数据中心节点 0~31
*/
private Integer dataCenterId;
/**
* 工作节点 0~31
*/
private Integer workerId;
@Override
public int compareTo(SnowIdDto o) {
long ex = this.timestamp - o.getTimestamp();
return ex > 0 ? 1 : -1;
}
}
private static final Integer DATA_SIZE = 32;
private static final String[] RADIX_STR = new String[]{
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v"};
private static Map<String, Integer> RADIX_MAP = new LinkedHashMap<>();
static {
for (int i = 0; i < DATA_SIZE; i++) {
RADIX_MAP.put(RADIX_STR[i], i);
}
}
// 测试时用3
// private static final Integer DATA_SIZE = 3;
/**
* 计算雪花算法参数的新算法
*
* @param redisTemplate
* @param appName
* @return
*/
public static SnowIdDto calculateDataIdAndWorkId2(RedisTemplate redisTemplate, String appName) {
String key = RedisPrefix.SNOW + appName;
RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
long andIncrement = redisAtomicLong.getAndIncrement();
// result在0~1023之间
long result = andIncrement % (DATA_SIZE * DATA_SIZE);
//将result转化为32进制数,个位为worlId,十位为dataId
String strResult = Integer.toString(Math.toIntExact(result), DATA_SIZE);
String strResult2 = StringUtils.leftPad(strResult, 2, "0");
String substring1 = strResult2.substring(0, 1);
String substring2 = strResult2.substring(1, 2);
Integer dataId = RADIX_MAP.get(substring1);
Integer workId = RADIX_MAP.get(substring2);
SnowIdDto snowIdDto = new SnowIdDto(System.currentTimeMillis(), dataId, workId);
return snowIdDto;
}
算法 2 就简单多了