分布式环境生成一个唯一id从来不是一个容易的事,不同的节点都独立的各自生成id,高并发性场景下容易生成相同的订单id。
方案1:数据库自增主键
优点:全局唯一、不会重复
缺点:订单id有序、容易被外界爬虫知道业务的订单量数据
方案2:UUID
UUID(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例:550e8400-e29b-41d4-a716-446655440000
优点:性能非常高,本地生成,没有网络消耗
缺点:不易于存储,UUID太长,16字节128位,通常以36长度的字符串表示
由于是无序,长字符串,存储数据库后索引效率比较差
String uuid = UUID.randomUUID().toString()
方案3:时间戳 + ip或者Mac地址
优点:无序、高并发情况下几乎不可能产生相同id,大部分业务可以满足使用
缺点:不适用超高并发,例如1ms可以生成多个订单id的场景,对系统健壮性要求极高的场景
方案4:snowflake(推荐)
Snowflake,Twitter开源的一种分布式ID生成算法。基于64位数实现,下图为Snowflake算法的ID构成图。
整个64-bit由以下组成部分组成
1.第一位
占用1bit,其值始终是0,没有实际作用。
2.时间戳
占用41bit,精确到毫秒,总共可以容纳约69 年的时间。
3.工作机器id
占用10bit,其中高位5bit是数据中心ID(datacenterId),低位5bit是工作节点ID(workerId),最多可以容纳1024个节点。
4.序列号
占用12bit,这个值在同一毫秒同一节点上从0开始不断累加,最多可以累加到4095。
SnowFlake算法在同一毫秒的ID数量 = 1024 X 4096 = 4194304 ,一毫秒可以生成419万的订单id !!!
用Java实现SnowFlake算法 , 其实Snowflake算法不难,设计的也很巧妙
/** * Created by Zhon.Thao on 2019/10/24. * * @author Zhon.Thao */ public class SnowFlake { /** * 起始的时间戳:这个时间戳自己随意获取 */ private final static long START_MILLS = 1543903501000L; /** * 每一部分占用的位数 */ private final static long SEQUENCE_BIT = 12; //序列号占用的位数 /** * 机器标识占用的位数 */ private final static long MACHINE_BIT = 5; /** * 数据中心占用的位数 */ private final static long DATACENTER_BIT = 5; /** * 用位运算计算出最大支持的数据中心数量:31 */ private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); /** * 用位运算计算出最大支持的机器数量:31 */ private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); /** * 用位运算计算出12位能存储的最大正整数:4095 */ private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); /** * 机器标志较序列号的偏移量 */ private final static long MACHINE_LEFT = SEQUENCE_BIT; /** * 数据中心较机器标志的偏移量 */ private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; /** * 时间戳较数据中心的偏移量 */ private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; /** * 数据中心id */ private long dataCenterId; /** * 机器标识id */ private long machineId; /** * 序列号 1ms内 */ private static long sequence = 0L; /** * 上一次的时间戳 */ private static long lastMills = -1L; /** * 如果业务方有datacenterId、machineId使用以下构造方法 */ public SnowFlake(long dataCenterId, long machineId) { this.dataCenterId = dataCenterId; this.machineId = machineId; } /** * 如果业务方觉得配置datacenterId、machineId比较麻烦,可以各自机器随机从最大值里去一个数 * * 在极高并发下可能会生成相同id */ public SnowFlake() { this.dataCenterId = RandomUtils.nextLong(0, MAX_DATACENTER_NUM); this.machineId = RandomUtils.nextLong(0, MAX_MACHINE_NUM); } /** * 生成订单ID * * @return */ public synchronized long getId() { /** 获取当前时间戳 */ long currMills = System.currentTimeMillis(); /** 如果当前时间戳小于上次时间戳则抛出异常 */ if (currMills < lastMills) { throw new RuntimeException("getId error,currMills < lastMills"); } /** 相同毫秒内 */ if (currMills == lastMills) { //相同毫秒内,序列号自增 sequence = (sequence + 1) & MAX_SEQUENCE; //同一毫秒的序列数已经达到最大 if (sequence == 0L) { /** 获取下一时间的时间戳并赋值给当前时间戳 */ currMills = getNextMill(); } } else { //不同毫秒内,序列号置为0 sequence = 0L; } /** 当前时间戳存档记录,用于下次产生id时对比是否为相同时间戳 */ lastMills = currMills; return (currMills - START_MILLS) << TIMESTMP_LEFT //时间戳部分 | dataCenterId << DATACENTER_LEFT //数据中心部分 | machineId << MACHINE_LEFT //机器标识部分 | sequence; //序列号部分 } private static long getNextMill() { long mill = System.currentTimeMillis(); while (mill <= lastMills) { mill = System.currentTimeMillis(); } return mill; } }
方案五 : 美团点评分布式唯一id生成系统,目前已开源
文档 : https://tech.meituan.com/2017/04/21/mt-leaf.html
Github: https://github.com/Meituan-Dianping/Leaf