雪花算法自定义解决时钟回拨问题

雪花算法默认算法生成一个 64bit的长整型(Long)数据。主要由 4部分组成,1bit符号位、41bit时间戳位、10bit工作进程位以及 12bit 序列号位。

正常情况下,该算法可以保证系统中的序号唯一,只是在时钟回拨的时候,有可能会造成id重复的问题。虽然概率不大,单生产系统中,还是尽量避免为好。

将算法做一下调整,仍然由 4部分组成,

1bit符号位保持不变

41bit时间戳位,将时间戳变更为31位,2,147,483,647秒,大约68年,时间单位变更为秒,而不是毫秒,起始时间采用2020-01-10 00:00:00为起始时间。

10bit工作进程位,保持不变

12bit 序列号位,变更为22位,最大值为4,194,300,也就是说,在400万以内,都不会重复

在算法上,时间戳保持与系统时间同步增加,序列号在最大值以前保持持续增加,最大值后重新开始计数。

package com.demo.server.config;

import cn.hutool.core.net.NetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CustomSnowFlake {

	private static Logger log = LoggerFactory.getLogger(CustomSnowFlake.class);

	// 2020-01-01 00:00:00 对应的秒
	private final static long beginTs = 1577808000L;

	// 顺序号最大值
	private final static long maxSequence = 4194300L;

	// 最大时钟回拨(秒)
	private final static long maxTimeback = 4194L;

	private long lastTs = 0L;

	private long processId;
	private int processIdBits = 10;

	private long sequence = 1L;
	private int sequenceBits = 22;

	public CustomSnowFlake() {

		String ipAddr = NetUtil.getLocalhostStr();
		log.info("当前机器的ipAddr:" + ipAddr);

		Long workerId = NetUtil.ipv4ToLong(ipAddr);
		workerId = workerId % 1024;

		log.info("当前机器的workId:" + workerId);

		this.processId = workerId;
	}

	public CustomSnowFlake(long processId) {
		if (processId > ((1 << processIdBits) - 1)) {
			throw new RuntimeException("进程ID超出范围,设置位数" + processIdBits + ",最大" + ((1 << processIdBits) - 1));
		}
		this.processId = processId;
	}

	// 获取当前时间(秒为单位)
	protected long timeGen() {
		return System.currentTimeMillis() / 1000;
	}

	// 生成一个Id
	public synchronized long nextId() {

		// 获取当前时间(秒为单位)
		long ts = timeGen();

		// 刚刚生成的时间戳比上次的时间戳还小,出错
		long tempDiff = lastTs - ts;
		if (tempDiff >= maxTimeback) {
			log.warn("时钟回拨超过4194秒,存在Id重复风险");
		}

		sequence = sequence + 1;
		if(sequence >= maxSequence)
		{
			sequence = 1;
		}

		// 更新lastTs时间戳
		lastTs = ts;
		long timeDiff = ts - beginTs;

		return (timeDiff << (processIdBits + sequenceBits)) | (processId << sequenceBits) | sequence;
	}

    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub

        CustomSnowFlake ig = new CustomSnowFlake();

        for (int i = 0; i < 10; i++) {
        	System.out.println(ig.nextId());
            Thread.sleep(1000);
        }
    }
}

理论上,只要单节点并发量不超过1000次每秒,回拨时间在1小时以内,都不会出现重复记录。为了进一步规避id重复,可以在业务逻辑层面增加重试机制,当出现主键冲突时,重新生成id进行写入尝试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值