前言
分布式的情况下不是和使用主键自增,可能会出现自增的主键相同的情况,下面有几种解决办法
一、UUID
Java自带的生成一串唯一随机36位字符串(32个字符串+4个“-”)的算法。它可以保证唯一性,且据说够用N亿年,但是其业务可读性差,无法有序递增。
- UUID是通用唯一识别码(Universally Unique Identifier)的缩写,开放软件基金会(OSF)规范定义了包括网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素。利用这些元素来生成UUID。
- UUID是由128位二进制组成,一般转换成十六进制,然后用String表示。
- 在java中有个UUID类,有四种不同的UUID的生成策略。
1.1 UUID 四种不同的UUID的生成策略
- randomly: 基于随机数生成UUID,由于Java中的随机数是伪随机数,其重复的概率是可以被计算出来的。
- time-based:基于时间的UUID,这个一般是通过当前时间,随机数,和本地Mac地址来计算出来,自带的JDK包并没有这个算法的我们在一些UUIDUtil中,比如我们的log4j.core.util,会重新定义UUID的高位和低位。
- DCE security:DCE安全的UUID。
name-based:基于名字的UUID,通过计算名字和名字空间的MD5来计算UUID。
1.2 UUID的优缺点
优点
通过本地生成,没有经过网络I/O,性能较快。
-
缺点
128位二进制一般转换成36位的16进制,太长了只能用String存储,空间占用较多。
- 不能生成递增有序的数字。
使用场景
UUID的适用场景可以为不需要担心过多的空间占用,以及不需要生成有递增趋势的数字。在Log4j里面他在UuidPatternConverter中加入了UUID来标识每一条日志。
1.3 代码实现
import java.util.UUID;public class Uuid {public static void main(String[] args) {for (int i = 0; i < 5; i++) {//注意replaceAll前面的是正则表达式String uuid = UUID.randomUUID().toString().replaceAll("-","");System.out.println(uuid);// System.out.println(uuid.length());}}}
二、SnowFlake
雪花ID,它是Twitter开源的由64位整数组成分布式ID,性能较高,并且在单机上递增。
2.1 构造雪花ID

SnowFlake算法产生的ID是一个64位的整型,结构如下(每一部分用“-”符号分隔): 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
1.第一位 占用1bit,其值始终是0,没有实际作用。
2.时间戳 占用41bit,精确到毫秒,总共可以容纳约69年的时间。
3.工作机器id 占用10bit,其中高位5bit是数据中心ID,低位5bit是工作节点ID,做多可以容纳1024个节点。
4.序列号 占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID。
2.2 使用雪花算法注意事项
SnowFlake算法生成的ID大致上是按照时间递增的,用在分布式系统中时,需要注意数据中心标识和机器标识必须唯一,这样就能保证每个节点生成的ID都是唯一的。
2.3 代码实现
JAVA 实现
import java.util.Random;/*** SequenceGenerateUtil class* from snowflake*/public class SequenceGenerateUtil {/*** Twitter_Snowflake<br>* SnowFlake的结构如下(每部分用-分开):<br>* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的* (如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>* 加起来刚好64位,为一个Long型。<br>* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),* 并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。*/// ==============================Fields===========================================/*** 开始时间截 (2020-10-27 15:27:28)*/private final long startTimeStamp = 1603783648;/*** 机器id所占的位数*/private final long machineIdBits = 5L;/*** 数据标识id所占的位数*/private final long dataIdBits = 5L;/*** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)*/private final long maxMachineId = -1L ^ (-1L << machineIdBits);/*** 支持的最大数据标识id,结果是31*/private final long maxDataId = -1L ^ (-1L << dataIdBits);/*** 序列在id中占的位数*/private final long sequenceBits = 12L;/*** 机器ID向左移12位*/private final long machineIdShift = sequenceBits;/*** 数据标识id向左移17位(12+5)*/private final long dataIdShift = sequenceBits + machineIdBits;/*** 时间截向左移22位(5+5+12)*/private final long timestampLeftShift = sequenceBits + machineIdBits + dataIdBits;/*** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)*/private final long sequenceMask = -1L ^ (-1L << sequenceBits);/*** 工作机器ID(0~31)*/private long machineId;/*** 数据中心ID(0~31)*/private long dataId;/*** 毫秒内序列(0~4095)*/private long sequence = 0L;/*** 上次生成ID的时间截*/private long lastTimestamp = -1L;private static Long datacenterId = Long.valueOf(new Random().nextInt(5));//==============================Constructors=====================================/*** 构造函数** @param machineId 工作ID (0~31)* @param dataId 数据中心ID (0~31)*/public SequenceGenerateUtil(long machineId, long dataId) {if (machineId > maxMachineId || machineId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", machineId));}if (dataId > maxDataId || dataId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", dataId));}this.machineId = machineId;this.dataId = dataId;}// ==============================Methods==========================================/*** 获得下一个ID (该方法是线程安全的)* 添加锁,是线程安全的。* @return SnowflakeId*/public synchronized long nextId() {long timestamp = timeGen();//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}//如果是同一时间生成的,则进行毫秒内序列if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;//毫秒内序列溢出if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}//上次生成ID的时间截lastTimestamp = timestamp;//移位并通过或运算拼到一起组成64位的IDreturn ((timestamp - startTimeStamp) << timestampLeftShift) //| (dataId << dataIdShift) //| (machineId << machineIdShift) //| sequence;}/*** 阻塞到下一个毫秒,直到获得新的时间戳** @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒为单位的当前时间** @return 当前时间(毫秒)*/protected long timeGen() {return System.currentTimeMillis();}/*** 获取序列号** @param workerId* @param datacenterId* @return*/public static Long getSequence(Long workerId, Long datacenterId) {return new SequenceGenerateUtil(workerId, datacenterId).nextId();}}
# coding: utf-8import timeclass InvalidSystemClock(Exception):"""时钟回拨异常"""pass# 64位ID的划分WORKER_ID_BITS = 5DATACENTER_ID_BITS = 5SEQUENCE_BITS = 12# 最大取值计算MAX_WORKER_ID = -1 ^ (-1 << WORKER_ID_BITS) # 2**5-1 0b11111MAX_DATACENTER_ID = -1 ^ (-1 << DATACENTER_ID_BITS)# 移位偏移计算WOKER_ID_SHIFT = SEQUENCE_BITSDATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITSTIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS# 序号循环掩码SEQUENCE_MASK = -1 ^ (-1 << SEQUENCE_BITS)# 开始时间截 (2015-01-01)TWEPOCH = 1420041600000class IdWorker(object):"""用于生成IDs"""def __init__(self, datacenter_id, worker_id, sequence=0):"""初始化:param datacenter_id: 数据中心(机器区域)ID:param worker_id: 机器ID:param sequence: 其实序号"""# sanity checkif worker_id > MAX_WORKER_ID or worker_id < 0:raise ValueError('worker_id值越界')if datacenter_id > MAX_DATACENTER_ID or datacenter_id < 0:raise ValueError('datacenter_id值越界')self.worker_id = worker_idself.datacenter_id = datacenter_idself.sequence = sequenceself.last_timestamp = -1 # 上次计算的时间戳def _gen_timestamp(self):"""生成整数时间戳:return:int timestamp"""return int(time.time() * 1000)def get_id(self):"""获取新ID:return:"""timestamp = self._gen_timestamp()# 时钟回拨if timestamp < self.last_timestamp:raise InvalidSystemClockif timestamp == self.last_timestamp:self.sequence = (self.sequence + 1) & SEQUENCE_MASKif self.sequence == 0:timestamp = self._til_next_millis(self.last_timestamp)else:self.sequence = 0self.last_timestamp = timestampnew_id = ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) | (self.datacenter_id << DATACENTER_ID_SHIFT) | \(self.worker_id << WOKER_ID_SHIFT) | self.sequencereturn new_iddef _til_next_millis(self, last_timestamp):"""等到下一毫秒"""timestamp = self._gen_timestamp()while timestamp <= last_timestamp:timestamp = self._gen_timestamp()return timestampif __name__ == '__main__':worker = IdWorker(0, 0)print(worker.get_id())
