背景
目前平台采用雪花算法作为主键生成策略。雪花算法有以下优点:
- 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
- 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
- 可以根据自身业务特性分配bit位,非常灵活
需求及目标
- 原有的截取IP方式,很大可能性出现重复;
- 需要将项目代码(AA~99)加入主键生成策略,而项目编码可能组合为1296个,需要占用至少11位的长度方能满足需求;
- 基于以上需求,共计需要1(首位) + 41(时间戳差值) + 11(项目编码) + 5(机器Id) + 12(序列) = 70位;已经超出long类型的范围。
项目编码问题实现思路
项目编码目前为AA-99两个字符组合。如需加入主键生成运算,需要将AA-99转换成数字,本方案采用Ascall码的方式,分字符标识,列出所有可能,并返回一个数字,来将字符串转换成编码。
示例代码如下:
private static int getProjectId() {
//项目编码
String originalProjectId = BeanUtils.getBean(Environment.class).getProperty(
"liems.projectId", "AA");
char[] projectCode = originalProjectId.toCharArray();
//将项目编码的英文字母,转换为ascll码
StringBuilder code = new StringBuilder();
for (char c : projectCode) {
code.append((int)c);
}
List<String> list = new ArrayList<>();
for (int i = '0'; i <= 'Z'; i++) {
for (int j = '0'; j <= 'Z'; j++) {
list.add(i + "" + j);
}
}
return list.indexOf(code.toString());
}
长度不够解决思路
在Java中,BigDecimal可以标识最大128位的数字。
BigDecimal使用示例
//起始时间
long START_STMP = 1288834974657L;
//以当前时间为例,计算时间戳差值
long time = System.currentTimeMillis() - START_STMP;
decimal = new BigDecimal(time);
//时间戳直接左移28位会导致直接溢出,所以此处采用乘法的方式
decimal = decimal.multiply(new BigDecimal(Math.pow(2, 28)));
//加入项目编码“AA”,左移17位(5位机器码 + 12位序列)
decimal = decimal.add(new BigDecimal(748 << 17));
//机器码左移12位
decimal = decimal.add(new BigDecimal(9 << 12));
decimal = decimal.add(new BigDecimal(0));
当前时间戳输出结果:87281447913632862208,转换成字符串后占用长度20
50年后的时间戳输出:856797335480725704704,转换成字符串后占用长度21,满足需求。
机器ID可能重复解决思路
在集群情况下,动态生成当前节点的编码。服务启动的时候,往Redis中放入IP+Port为Key的数据,以此来标记不同节点的机器编码,最大支持32个节点。