分布式全局ID生成策略

使用场景:

//生成出库单号    
String snowFlakeID= String.valueOf(snowFlakeIDGenService.getSnowFlakeID());
return DELIVERY_ORDER_NO_PRE + snowFlakeID;

//生成商户订单号
TransfersDto model = new TransfersDto();
String snowFlakeID= String.valueOf(snowFlakeIDGenService.getSnowFlakeID());
model.setPartner_trade_no(snowFlakeID);   

//生成活动订单号或者是优惠订单号
CouponClctRec couponClctRec = CouponClctRec.builder()
                .withActOrderNo(String.valueOf(snowFlakeIDGenService.getSnowFlakeID()))

//生成防重字符串 
String snowFlakeID= String.valueOf(snowFlakeIDGenService.getSnowFlakeID());
cacheService.set(CHECK_FLAG + userId,snowFlakeID, ExpireTime.FIVE_MINUTES)   
                ...

方案一(UUID):

import java.util.UUID;
public class UuidUtil {
    public static String getUUid() {
        UUID uuid = UUID.randomUUID();
        return String.valueOf(uuid).replace("-","");
    }
}

方案二(雪花算法):

/**
 * 雪花算法ID生成
 */
public class SnowIdWorkerUtil {
    // 开始时间截 (2020-01-02)
    private final long timeToCut = 1577894400000L;
    // 机器ID所占的位数
    private final long workerIdBits = 2L;
    // 数据标识ID所占的位数
    private final long dataCenterIdBits = 8L;
    // 支持的最大机器ID,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    // 支持的最大数据标识ID,结果是31
    private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);
    // 序列在ID中占的位数
    private final long sequenceBits = 12L;
    // 机器ID向左移12位
    private final long workerIdShift = sequenceBits;
    // 数据标识ID向左移17位(12+5)
    private final long dataCenterIdShift = sequenceBits + workerIdBits;
    // 时间截向左移22位(5+5+12)
    private final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
    // 生成序列的掩码
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
    // 工作机器ID(0~31)
    private long workerId;
    // 数据中心ID(0~31)
    private long dataCenterId;
    // 毫秒内序列(0~4095)
    private long sequence = 0L;
    // 上次生成ID的时间截
    private long lastTimestamp = -1L;
    /**
     * 构造函数
     * @param workerId 工作ID (0~31)
     * @param dataCenterId 数据中心ID (0~31)
     */
    public SnowIdWorkerUtil (long workerId, long dataCenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("workerId 不符合条件");
        }
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException("dataCenterId 不符合条件");
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }

    public synchronized String nextIdVar(){
        return String.valueOf(nextId());
    }
    /**
     * 线程安全,获得下一个ID
     */
    private synchronized long nextId() {
        long timestamp = timeGen();
        // 如果当前时间小于上一次ID生成的时间戳,抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format(
                                        "时间顺序异常,时间差(上次时间-现在)=%d",
                                        lastTimestamp - timestamp));
        }
        // 如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 时间戳改变,毫秒内序列重置
            sequence = 0L;
        }
        // 上次生成ID的时间截
        lastTimestamp = timestamp;
        // 移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - timeToCut) << timestampLeftShift)
                | (dataCenterId << dataCenterIdShift)
                | (workerId << workerIdShift) | sequence;
    }

    /**
     * 阻塞,获得新的时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
    /**
     * 返回当前时间节点
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        // 参数在实际业务下需要配置管理
        SnowIdWorkerUtil idWorker = new SnowIdWorkerUtil(1, 1);
        for (int i = 0; i < 100; i++) {
            String id = idWorker.nextIdVar();
            System.out.println(id+"  "+id.length()+"位");
        }
    }
}
/**
 * 用来生成平台设备出库单号
 * 当机器ID占10位,数据中心占0位,初始时间为2012-02-02,可以保证从2020年7月30日一直用到2080年7月30日,且生成的id均为19位正整数
 */
@DubboService(interfaceName = "snowFlakeIDGenService", group = "sys-erp", version = "0.0.1", timeout = 6000)
public class SnowFlakeIDGenServiceImpl implements SnowFlakeIDGenService {
 
	// 下面两个每个5位,加起来就是10位的工作机器id
	private long workId; // 工作id
	private long dataCenterId; // 数据id
	// 序列号id长度
	private long sequenceBits = 6L;
	// 12位的序列号
	private long sequence = (1 << sequenceBits) - 1;
	// 初始时间戳
	private long twepoch = LocalDateTime.of(2012, 2, 2, 0, 0, 0, 0).toInstant(ZoneOffset.of("+8")).toEpochMilli();
	// 长度为5位
	private long workerIdBits = 8L;
	private long datacenterIdBits = 8L;
	// 最大值
//	private long maxWorkerId = -1L ^ (-1L << workerIdBits);
//	private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
	// 序列号最大值
	private long sequenceMask = -1L ^ (-1L << sequenceBits);
	// 工作id需要左移的位数,12位
	private long workerIdShift = sequenceBits;
	// 数据id需要左移位数 12+5=17位
	private long datacenterIdShift = sequenceBits + workerIdBits;
	// 时间戳需要左移位数 12+5+5=22位
	private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
	// 上次时间戳,初始值为负数
	private long lastTimestamp = -1L;
 
	private SnowFlakeIDGenServiceImpl() {
		String ipAddress = getIpAddress();
		if(Optional.ofNullable(ipAddress).isPresent()){
			String[] ipAddressArr = ipAddress.split("\\.");
			this.dataCenterId = Long.parseLong(ipAddressArr[2]);
			this.workId = Long.parseLong(ipAddressArr[3]);
		}else{
			this.dataCenterId = 0L;
			this.workId = 0L;
		}
	}
 
	@Override
	public long getSnowFlakeID() {
		return nextId();
	}
 
	@Override
	public Long[] getOriginalData(long id) {
 
		SnowFlakeIDGenServiceImpl impl = new SnowFlakeIDGenServiceImpl();
 
		String binaryString = Long.toBinaryString(id);
 
		String str1 = binaryString.substring(binaryString.length()-(int)impl.workerIdShift,binaryString.length());
		String str2 = binaryString.substring(binaryString.length()-(int)impl.datacenterIdShift,binaryString.length()-(int)impl.workerIdShift);
		String str3 = binaryString.substring(binaryString.length()-(int)impl.timestampLeftShift,binaryString.length()-(int)impl.datacenterIdShift);
		String str4 = binaryString.substring(0,binaryString.length()-(int)impl.timestampLeftShift);
 
		return new Long[]{
				StringUtils.isEmpty(str4)? 0L: Long.parseLong(str4,2)+impl.twepoch,
				StringUtils.isEmpty(str3)? 0L: Long.parseLong(str3,2),
				StringUtils.isEmpty(str2)? 0L: Long.parseLong(str2,2),
				StringUtils.isEmpty(str1)? 0L: Long.parseLong(str1,2)
		};
	}
 
	private synchronized long nextId() {
		long timestamp = timeGen();
		// 获取当前时间戳如果小于上次时间戳,则表示时间戳获取出现异常
		if (timestamp < lastTimestamp) {
			System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
			throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
		}
 
		// 获取当前时间戳如果等于上次时间戳(同一毫秒内),则在序列号加一;否则序列号赋值为0,从0开始。
		if (lastTimestamp == timestamp) {
			sequence = (sequence + 1) & sequenceMask;
			if (sequence == 0) {
				timestamp = tilNextMillis(lastTimestamp);
			}
		} else {
			sequence = 0;
		}
 
		// 将上次时间戳值刷新
		lastTimestamp = timestamp;
 
		/*
		 * 返回结果: (timestamp - twepoch) << timestampLeftShift) 表示将时间戳减去初始时间戳,再左移相应位数 (datacenterId << datacenterIdShift)
		 * 表示将数据id左移相应位数 (workerId << workerIdShift) 表示将工作id左移相应位数 | 是按位或运算符,例如:x | y,只有当x,y都为0的时候结果才为0,其它情况结果都为1。
		 * 因为个部分只有相应位上的值有意义,其它位上都是0,所以将各部分的值进行 | 运算就能得到最终拼接好的id
		 */
		return ((timestamp - twepoch) << timestampLeftShift) | (dataCenterId << datacenterIdShift) | (workId << workerIdShift) | sequence;
	}
 
	// 获取时间戳,并与上次时间戳比较
	private long tilNextMillis(long lastTimestamp) {
		long timestamp = timeGen();
		while (timestamp <= lastTimestamp) {
			timestamp = timeGen();
		}
		return timestamp;
	}
 
	// 获取系统时间戳
	private long timeGen() {
		return System.currentTimeMillis();
	}
 
	private String getIpAddress() {
		try {
			Enumeration<InetAddress> inetAddresses = NetworkInterface.getByName("eth0").getInetAddresses();
			List<InetAddress> list = Collections.list(inetAddresses).stream().filter(item -> item.getHostAddress().contains(".")).collect(Collectors.toList());
			return list.size() == 1 ? list.get(0).getHostAddress() : null;
		} catch (SocketException e) {
			e.printStackTrace();
		}
		return null;
	}
 
	public static void main(String[] args) {
		long id = new SnowFlakeIDGenServiceImpl().getSnowFlakeID();
		Long[] originalData = new SnowFlakeIDGenServiceImpl().getOriginalData(id);
		System.out.println(convertDateToStr(convertTsToDate(String.valueOf(originalData[0])),"yyyy-MM-dd HH:mm:ss")
				+ "|" + originalData[1]
				+ "|" +originalData[2]
				+ "|" + originalData[3]
		);
	}
 
	//时间戳转Date
	private static Date convertTsToDate(String ts){
		try {
			Instant timestamp = Instant.ofEpochMilli(Long.valueOf(ts));
			ZonedDateTime zonedDateTime = timestamp.atZone(ZoneId.of("Asia/Shanghai"));
			return Date.from(zonedDateTime.toInstant());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
 
	//Date转字符串
	private static String convertDateToStr(Date date,String pattrn){
		if(date == null){
			return StringUtils.EMPTY;
		}
		Instant instant = date.toInstant();
		ZoneId zone = ZoneId.of("Asia/Shanghai");
		LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
		DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattrn);
		return localDateTime.format(fmt);
	}
 
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值