分布式生成多个id

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

当使用rabbitmq发送一个消息时(比如钉钉通知消息),在表中需要用到一个消息id,而且我们项目部署的是集群(分布式),这个使用就需要用到雪花算法,并且去生成一个或者多个id。

一、了解雪花算法

我这边建议看一下别人的文章(https://www.cnblogs.com/h–d/p/11342741.html)
但是我们前端使用的Number类型最多只能接受53位的长度,这个时候就需要去实现特定的雪花算法。比如 把工作机器id(10位)变成一个4位的数字0,这样就是一个53位的数字了。

二、建立SnowflakeIdWorker类

 @Slf4j
public class SnowflakeldWorker(
/** 开始时间截 (2015-01-01) */
private final long twepoch = 1420041600000L;
/**序列在id中占的位数*/
private final long sequenceBits = 8L;

//机器码4bits(或者自己设定0-15)
private long machineCode = OL;
/”时间截向左移12(12)*/
private final long timestampleftShift -12;
/*生成序列的掩码,这里为1<<12 (0b111111111111)*,我们使用的是8位,下面是8位*/
private final long sequenceMask =0b11111111L;
//上次生成ID的时间截
private long lastTimestamp = -11
private static final Pattern PATTERN HOSTNAME = Pattern.compile("A.MD+(10-9)+)$);*秒内序列(0-65535) / I
pivate static long offset = 0;
private static long next = 0;
private boolean generateByLocal = false;
//获取下一个ID(线程安全的)
public synchronized long nextld(){
	long timestamp- timeGen0;
//如果当前时间小于上一次ID生成的时间截,说明系统时钟回退过这个时候应当抛出异常
	if (timestamp < lastTimestamp){
		lastTimestamp= timestamp;
		throw new Runtimefxception(
        String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp))
        }
	if(lastTimestamp != timestamp){
		offset = 0;
		lastlimestamp = timestamp;
		}
/如果是同一时间生成的,则进行豪秒内序列
	if (lastTimestamp=-timestamp){
		offset++·
		next = offset & sequenceMask;
		if (next==0) {
			loq.warm("maximurmid reached in I second in epoch:"4 LocalDateTime.now)+", timeStamp:"+ timestamp);
			timestamp- tilNextMillis(lastTimestamp);
			}
		}
//移位并通过或运算拼到一起组成53位的ID
return (timestarmp-twepoch)<< tirnestampleftShift) | machineCode << sequenceBits | next;
}

protected long tilNextMillis(long lastTimestamp) {
		long timestamp timeGen0;
		while (timestamp <= lastTimestamp){
			timestamp timeGen);
			}
		return timestamp;
}
//返回以豪秒为单位的当前时间
//@return 当前时间(秒)
protected long timeGen(){
	return System.currentTimeMillis0;
}

public void setMachineCode(long machineCode){
//取余,保证输入不会大于15,这样的话最大就是64位,之前是53位,减去数字0(4位)加上15位就是64
	this.machineCode = machineCode;
}

工具类IdUtil

	private static volatile SnowflakeldWorker instance;
	public static SnowflakeldWorker getInstance(){
		if(instance ==null){
			synchoronized(SnowflakeldWorker.class){
				if(instance ==null){
				instance = new SnowflakeldWorker();
				}
			}
		}
		return instance;
	}
//接口描述:获取单个id,优先从实例的缓存队列中获取,实例队列默认缓存50w个id,队列为空时就会通过http请求从我们的id微服务去生成,这样就可以节省http请求节省时间和提高性能,
//适用场景:业务并发量不高(少于80并发)。且每次获取id数量较少(如少于10w个),当超过80并发并且每次获取id大于10w个,就用batchGenerateId方法
	public static Long generateId(){
		return IdConsumer.getId();
	}
	
	public static List<Long> batchGenerateId(int idNum){
		List<Long> idList = new ArrayList();
		if(idNum<= IdQueue.BATCH_GET_ID_NUM){
			idList.addAll(batchGetId(idNum))
		}else{
			//分批获取
			int c = idNum / IdQueue.BATCH_GET_ID_NUM;
			int mod = idNum % IdQueue.BATCH_GET_ID_NUM;
			int total = mod==0 ? c : c+1;
			for(int i = 1;i<=total;i++){
				int getIdNum = IdQueue.BATCH_GET_ID_NUM;
				if(i==total){
					getIdNum = mod;
				}
				idList.addAll(batchGetId(getIdNum ));
			}
		}
	}
	private static List<Long> batchGetId(int idNum){
	 	RestTemplate restTemplate = SpringContextUtils.getBean( name:"baseRestTemplate");
	 	String idStr = "";
		try{
		idStr = restTemplate.getForObject(getIdServerUrl()+"/batchGetId?idNum="+idNum,String.class);
		}catch(){
		//出错了再尝试一次
		idStr = restTemplate.getForObject(getIdServerUrl()+"/batchGetId?idNum="+idNum,String.class);
		} 
		return StringUtils.isNotBlank(idStr) ? JSONArray.parseArray(idStr,Long.class) : new ArrayList<>();
	}
 public static String getIdServerUrl(){
	IdEnvConfig idEnvConfig = SpringContextUtils.getBean("idEnvConfig"); 
	return idEnvConfig.getUrl();
}

idEnvConfig类

@Data
@Configuration(value = "idEnvConfig")
public class idEnvConfig{
	@Value("${id.generator.url:http://localhost/16830}")
	private String url;
}

IdConsumer类

	private static ExecutorService executorService= Executors,newSingleThreadExetutor(new NamedThreadFactory("ld-Consumer",false));
	public static Long getId(){
		String idStr = Stringltils.EMPTY;
		RestTemplate restTemplate = SpringContextUtils.gelBean("baseRestTemplate");
		idstr = restTemplate.getForObject(ldUtil.getldServerUrl() + "/getld",String.class);
		if(StringUtil.isNotBlank(idStr)){
		id=Long.valueOf(idStr);
		}else{
		id = IdUtil.getInstance.nextId();
		}
		return id;
	}
	public static Long getld2(){
		while(true){
		Long id = IdQueue.CURRENT_QUEUE.poll();
		if(id != null){
			return id;
			}
		}else{
		IdQueue.isChangeCurrent = false;
		loadCurrentQueen();
		}
	}
	//加载当前队列,把nextQueen估财给currentQueen,然后异步获取id填充nextQueen
private static synchronized void loadCurrentQueen(){
	//如果没有加载中并且nextQueen不为空,则进行当前队列赋值
	if (ldQueue isc hanget urrent && IdQueue.NEXT QUEUE!= null){
	//将值置为true,防止在同一时刻在等待锁的线程获得锁后进来再执行ldQueue.CURRENT_QUEUE =IdQueue.NEXT _QUEUE; 
	IdQueue.isChangeCurrent = ture;
	ldQueue,CURRENT QUEUE = IdQueue.NEXT_QUEUE
	IdQueue.NEXI QUUE= null
	}
	//异步填充nextQueen
	loadNextQueen();
	}
	private static void loadNextQueen(){
	if( ldQueue NEXI QUEUE-= null && IdQueue.isLoadingNext){
	synchronized (idQueue.LOCK){
		if (ldQueue.NEXT_QUEUE== null && IdQueue.isLoadingNext){
		IdQueue.isLoadingNext = true;
		executorService.submit(()->{
			try{
			ldQueue.NEXT_QUEUE = batchGetId(IdQueue.MAX_NUM);
			}finally{
			//填充完,就要置为false,防止再次获取
			IdQueue.isLoadingNext = false;
			}
			})
		}
	}	
	}
	}
public static Queue batchGetId(int idNum){
Queue queue = ConcurrentLinkedQueuen();
queuc.addAll(IdUtil.batchGenerated(idNum));
return queue;
}

Queue类

public static final int MAX NUM = 500000;
//队列最小数量,当小于这个数量时触发生产

ingTypeEnum public static final int BATCH GET ID NUM = 300000;
public static volatile Queue<Long> ID_QUEUE = new ConcurrentLinkedQueue();
public static volatile Queue<Long>CURRENT_QUEUE = new ConcurrentLinkedQueue();
public static volatile Queue<Long> NEXT_QUEUE = new ConcurrentLinkedQueue();
 public static volatile boolean isChangeCurrent;
 public static volatile boolean isloadingNext;
public static Object LOCK = new Object();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值