赞
踩
唯一ID可以标识数据的唯一性,在分布式系统中生成唯一ID的方案有很多,常见的方式大概有以下几种:
本文主要讲解Twitter的SnowFlake(雪化算法)
采用数据库自增序列:
UUID随机数:
雪花算法生成的ID是纯数字且具有时间顺序的。其原始版本是scala版,后面出现了许多其他语言的版本如Java、C++等。
雪花算法在单机系统上ID是递增的,但是在分布式系统多节点的情况下,所有节点的时钟并不能保证不完全同步,所以有可能会出现不是全局递增的情况。
-
- /**
- * 分布式唯一ID 雪花算法实现
- */
- public class SnowFlake {
- /**
- * 起始的时间戳 2020-06-30 00:00:00
- */
- private final static long START_TIMESTAMP = 1593446400L;
-
- /**
- * 每一部分占用的位数
- */
- private final static long SEQUENCE_BIT = 12; //序列号占用的位数
- private final static long MACHINE_BIT = 5; //机器标识占用的位数
- private final static long DATA_CENTER_BIT = 5;//数据中心占用的位数
-
- /**
- * 每一部分的最大值
- */
- private final static long MAX_DATA_CENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT);
- private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
- private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
-
- /**
- * 每一部分向左的位移
- */
- private final static long MACHINE_LEFT = SEQUENCE_BIT;
- private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
- private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;
-
- /**
- * 数据中心编码,初始化后不可修改
- * 最大值: 2^5-1 取值范围: [0,31]
- */
- private long dataCenterId;
-
- /**
- * 机器或进程编码,初始化后不可修改
- * 最大值: 2^5-1 取值范围: [0,31]
- */
- private long machineId;
-
- /**
- * 序列号
- * 最大值: 2^12-1 取值范围: [0,4095]
- */
- private long sequence = 0L;
-
- private long lastStamp = -1L;//上一次时间戳
-
- public SnowFlake(long dataCenterId, long machineId) {
- if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
- throw new IllegalArgumentException("dataCenterId can't be greater than MAX_DATA_CENTER_NUM or less than 0");
- }
- if (machineId > MAX_MACHINE_NUM || machineId < 0) {
- throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
- }
- this.dataCenterId = dataCenterId;
- this.machineId = machineId;
- }
-
- /**
- * 产生下一个ID
- *
- * @return
- */
- public synchronized long nextId() {
- //获取当前时间戳 可用System.currentTimeMillis(); 方法替换
- long currStamp = getNewStamp();
- //保证当前时间戳不小于最后一次的时间戳
- if (currStamp < lastStamp) {
- throw new RuntimeException("Clock moved backwards. Refusing to generate id");
- }
- //如果当前时间戳等于最后一次时间戳 就进行序号递增
- if (currStamp == lastStamp) {
- //相同毫秒内,序列号自增
- sequence = (sequence + 1) & MAX_SEQUENCE;
- //同一毫秒的序列数已经达到最大
- if (sequence == 0L) {
- currStamp = getNextMill();
- }
- } else {
- //不同毫秒内,序列号置为0
- sequence = 0L;
- }
- //保存最后一次时间戳
- lastStamp = currStamp;
- //通过位运算,将相应的二进制数值放到对应的位置 41位时间戳 + 5位数据中心 + 5位机器码 + 12位序列号 ,首位没有使用
- return (currStamp - START_TIMESTAMP) << TIMESTAMP_LEFT //时间戳部分
- | dataCenterId << DATA_CENTER_LEFT //数据中心部分
- | machineId << MACHINE_LEFT //机器标识部分
- | sequence; //序列号部分
- }
-
- private long getNextMill() {
- //获取当前时间戳
- long mill = getNewStamp();
- //获取大于上一次时间戳的时间戳
- while (mill <= lastStamp) {
- mill = getNewStamp();
- }
- return mill;
- }
-
- /**
- * 获取当前时间戳
- */
- private long getNewStamp() {
- return System.currentTimeMillis();
- }
-
- //测试
- public static void main(String[] args) {
- SnowFlake snowFlake = new SnowFlake(2, 3);
-
- long start = System.currentTimeMillis();
- for (int i = 0; i < 100000; i++) {
- System.out.println(snowFlake.nextId());
- }
-
- }
- }
分布式唯一ID的方案有很多,本文主要讨论了雪花算法,组成结构大致分为了无效位、时间位、机器位和序列号位。其特点是自增、有序、纯数字组成查询效率高且不依赖于数据库。适合在分布式的场景中应用,可根据需求调整具体实现细节。对于瞬时的高峰唯一ID请求,可以考虑结合Redis 队列进行预生产大量ID,业务消费方优先从Redis获取,ID生产方 持续写入到Redis进行瞬时问题处理。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。