雪花算法生成全局id
算法原理
SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图:
SnowFlake可以保证:
- 所有生成的id按时间趋势递增
- 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)
算法实现
package com.kaffatech.mocha.dsuite.id.manager.impl;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;
public class SnowflakeIdManager {
private String serverId = "192.168.100.100:9007";
private long start = 1533686888000L;
private long lastTime = -1L;
private long sequence = 0L;
//-1 的反码为 100000001 补码为 1111111111 -1L 左移 11111100 异或为 0000000011
private long sequenceShift = 12L;
private long serverShift = 10L;
private long mask = ~(-1L << sequenceShift);
private long timeShift = serverShift + sequenceShift;
public synchronized long createID() {
//获取当前时间
long currentTime = System.currentTimeMillis();
//相同的时间,序列号自增
if(currentTime == lastTime) {
//seq 自增
sequence = (sequence + 1) & mask;
//等待下一个时间
if(sequence == 0) {
while (currentTime <= this.lastTime) {
currentTime = System.currentTimeMillis();
}
}
}else {
//不同时间,序列号重新计算
sequence = 0L;
}
//时间差值
long time = currentTime - start;
//服务器编号
int hash = this.serverId.hashCode();
//服务器
long server = hash % 1024L;
lastTime = currentTime;
//生成id
long id = (time << timeShift) | (server << serverShift) | sequence;
return id;
}
public static void main(String[] args) throws InterruptedException {
//校验逻辑是否正确
SnowflakeIdManager testIdExample = new SnowflakeIdManager();
//去重集合
Set<Long> set = new CopyOnWriteArraySet<>();
//不去重集合
List<Long> list = new CopyOnWriteArrayList();
int count = 100;
CyclicBarrier cyclicBarrier = new CyclicBarrier(count);
CountDownLatch countDownLatch = new CountDownLatch(count);
for(int i = 0; i < count; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
for (int i =0; i < 1000; i ++) {
long id = testIdExample.createID();
list.add(id);
set.add(id);
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
System.out.println("去重集合大小:" + set.size() + "不去重集合大小:" + list.size());
}
}
业务码生成器
算法原理
就是通过字符串解析将将必要的值填充进去,原理比较简单,重点是我们需要有一个随机码库,而且这个有业务限制,如果增长值超过我们数据库中的随机值,会有问题,但是足以支撑比较不错的用户数量。
算法实现
package com.kaffatech.mocha.dsuite.id.manager.impl;
import com.kaffatech.mocha.common.util.InstantUtils;
import com.kaffatech.mocha.common.util.RandomUtils;
import com.kaffatech.mocha.common.util.StringUtils;
import com.kaffatech.mocha.common.util.SystemUtils;
import com.kaffatech.mocha.dsuite.id.dao.RandomIdMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author lingzhen on 2020/12/7.
*/
public class SmartBizNoManager extends BaseBizNoManager {
private static final String INCR_KEY_PREFIX = "incrid:";
private static final String BIZ_TAG_PREFIX = "bizNo.";
private static final String VAR_PREFIX = "$";
private static final String PART_SEP = "\\|";
private static final String VAR_PARAM_SEP = "-";
private static final String DAY_VAR = "$yyMMdd";
private static final int MAX_SECOND_OFFSET_LENGTH = 5;
private static final String ENV_VAR = "$env";
private static final String RANDOMID_VAR = "$randomid";
private static final String SNOWFLAKE_VAR = "$snowflake";
private static final String INCRID_VAR = "$incrid";
private static final String SHARDING_VAR = "$sharding";
private String bizTag;
@Autowired(required = false)
private RedisTemplate redisTemplate;
@Autowired(required = false)
private RandomIdMapper randomIdMapper;
public void setBizTag(String bizTag) {
this.bizTag = bizTag;
}
public void setRedisTemplate(RedisTemplate redisTemplate){
this.redisTemplate = redisTemplate;
}
public void setRandomIdMapper(RandomIdMapper randomIdMapper){
this.randomIdMapper = randomIdMapper;
}
@Override
public String generate(Map<String, Object> params) {
// 拼接全部
List<String> partList = fillPartList(params);
StringBuilder sb = new StringBuilder();
for (String part : partList) {
sb.append(part);
}
return sb.toString();
}
private List<String> fillPartList(Map<String, Object> params) {
// 获取时间
Instant now = Instant.now();
String day = InstantUtils.format(now, InstantUtils.DAY);
// long secondOffset = (now.toEpochMilli() - InstantUtils.parse(day, InstantUtils.DAY).toEpochMilli()) / 1000L;
String expression = SystemUtils.getString(BIZ_TAG_PREFIX + bizTag);
if (StringUtils.isEmpty(expression)) {
throw new IllegalArgumentException("bizNo expression cannot be null!");
}
List<String> partList = new ArrayList<>();
String[] expArray = expression.split(PART_SEP);
for (String part : expArray) {
if (DAY_VAR.equals(part)) {
partList.add(StringUtils.substring(day, 2));
continue;
}
if (ENV_VAR.equals(part)) {
partList.add(SystemUtils.isProd() ? "0" : "9");
continue;
}
if (SHARDING_VAR.equals(part)) {
Object value = params.get(part.substring(1));
// 截取或填充两位长度的字符串
String valueString = StringUtils.leftPad((value == null) ? "" : value.toString(), 2, '0');
valueString = StringUtils.substring(valueString, valueString.length() - 2, valueString.length());
// 替换非数字的字符
char[] ary = valueString.toCharArray();
StringBuilder sharding = new StringBuilder();
for (char ch : ary) {
if (StringUtils.isNumeric(String.valueOf(ch))) {
sharding.append(ch);
} else {
sharding.append('0');
}
}
partList.add(sharding.toString());
continue;
}
if (part.startsWith(RANDOMID_VAR)) {
// 随机唯一号
String[] uqidArray = part.split(VAR_PARAM_SEP);
int maxLength = Integer.parseInt(uqidArray[1]);
long incrId = generateIncr(maxLength);
long randomId = getRandomId(incrId);
partList.add(StringUtils.leftPad(String.valueOf(randomId), maxLength, '0'));
continue;
}
if (part.startsWith(INCRID_VAR)) {
// 自增唯一号
String[] uqidArray = part.split(VAR_PARAM_SEP);
int maxLength = Integer.parseInt(uqidArray[1]);
partList.add(generateUqid(maxLength));
continue;
}
if (part.startsWith(SNOWFLAKE_VAR)) {
// 雪花ID
long snowflakeId = (new SnowflakeIdManagerImpl()).generate();
partList.add(StringUtils.leftPad(String.valueOf(snowflakeId), 19, '0'));
}
if (part.startsWith(VAR_PREFIX)) {
// 变量需替换
String[] varArray = part.split(VAR_PARAM_SEP);
String key = varArray.length < 2 ? part.substring(1) : varArray[0].substring(1);
Object valObj = params.get(key);
if (valObj == null) {
partList.add("");
} else {
String value = valObj.toString();
int varLength = varArray.length < 2 ? value.length() : Integer.parseInt(varArray[1]);
partList.add(value == null ? "" : StringUtils.leftPad(value, varLength, '0'));
}
continue;
}
// 常量
partList.add(part);
}
return partList;
}
private long getRandomId(long incrId) {
Object randomIdObj = redisTemplate.opsForValue().get("random_id:" + incrId);
if (randomIdObj == null) {
// 缓存没命中
Long randomId = randomIdMapper.selectRandomId(incrId);
redisTemplate.opsForValue().set("random_id:" + incrId, randomId);
return randomId;
}
return Long.parseLong(randomIdObj.toString());
}
private String generateSencondRandomId(int maxLength, long secondOffset) {
if (maxLength <= 0) {
return "";
}
// 临时采用秒+自增号的方式
int incrMaxLength = maxLength - MAX_SECOND_OFFSET_LENGTH;
long incrId = generateIncr(incrMaxLength);
return mix(incrId, secondOffset);
}
private String mix(long incrId, long secondOffset) {
String incrStringId = StringUtils.leftPad(String.valueOf(incrId), Math.max(String.valueOf(incrId).length(), 3), '0');
String secondOffsetString = StringUtils.leftPad(String.valueOf(secondOffset), MAX_SECOND_OFFSET_LENGTH, '0');
int mixOffset = incrStringId.length() - 3;
StringBuilder sb = new StringBuilder();
sb.append(StringUtils.substring(incrStringId, 0, mixOffset));
sb.append(StringUtils.substring(secondOffsetString, 0, 1));
sb.append(StringUtils.substring(incrStringId, mixOffset, mixOffset + 1));
sb.append(StringUtils.substring(secondOffsetString, 1, 2));
sb.append(StringUtils.substring(incrStringId, mixOffset + 1, mixOffset + 2));
sb.append(StringUtils.substring(secondOffsetString, 2, 3));
sb.append(StringUtils.substring(incrStringId, mixOffset + 2, mixOffset + 3));
sb.append(StringUtils.substring(secondOffsetString, 3, 5));
return sb.toString();
}
private String generateUqid(int maxLength) {
if (maxLength <= 0) {
return "";
}
long incrId = generateIncr(maxLength);
return StringUtils.leftPad(String.valueOf(incrId), maxLength, '0');
}
private long generateIncr(int maxIncrLength) {
// if (maxIncrLength > 0) {
// // TODO:测试用临时代码
// return RandomUtils.nextInt(0, 999);
// }
if (maxIncrLength <= 0) {
return 0L;
}
String key = INCR_KEY_PREFIX + bizTag;
if (redisTemplate.opsForValue().get(key) == null) {
// 此处可能存在并发问题,真正业务上线前必须初始化对应key的起始编号
redisTemplate.opsForValue().set(key, RandomUtils.nextInt(1,999));
}
long id = redisTemplate.opsForValue().increment(key, 1L);
return maxIncrLength <= 0 ? id : (id % (long) Math.pow(10, maxIncrLength));
}
public static void main(String[] args) {
// properties文件中的配置内容
// bizNo.trade_no=$yyMMdd|$tradeType|$uqid-8|$sharding
// System.setProperty(BIZ_TAG_PREFIX + "trade_no", "$yyMMdd|$tradeType|$uqid-8|$sharding");
System.setProperty(BIZ_TAG_PREFIX + "user_no", "$sharding");
// boot中设置,应用的时候注入
SmartBizNoManager smartBizNoManager = new SmartBizNoManager();
// smartBizNoManager.setBizTag("tradeNo");
smartBizNoManager.setBizTag("user_no");
Map<String, Object> params = new HashMap<>();
params.put("userType", 2);
params.put("tradeType", 99);
params.put("sharding", 888866);
// 运用
System.out.println(smartBizNoManager.generate(params));
}
}
学习资料推广
我已经将springamqp 源码解析录制为视频上传到bibi,分为六个章节详细介绍了各个模块的具体内容
https://www.bilibili.com/video/BV1hN411Z7fn?share_source=copy_web
感兴趣的小伙伴可以看看
学习一下
录制不易,记得三联哦!