在复杂分布式系统中,每每须要对大量的数据和消息进行惟一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中,数据日渐增加,对数据库的分库分表后须要有一个惟一ID来标识一条数据或消息,数据库的自增ID显然不能知足需求;特别一点的如订单、骑手、优惠券也都须要有惟一ID作标识。此时一个可以生成全局惟一ID的系统是很是必要的。css
归纳下来,业务系统对ID号的要求有哪些呢?java
1.全局惟一性:不能出现重复的ID,最基本的要求。
2.趋势递增:MySQL InnoDB引擎使用的是汇集索引,因为多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面咱们应尽可能使用有序的主键保证写入性能。
3.单调递增:保证下一个ID必定大于上一个ID。
4.信息安全:若是ID是连续递增的,恶意用户就能够很容易的窥见订单号的规则,从而猜出下一个订单号,若是是竞争对手,就能够直接知道咱们一天的订单量。因此在某些场景下,须要ID无规则。算法
第三、4两个需求是互斥的,没法同时知足。sql
同时,在大型分布式网站架构中,除了须要知足ID生成自身的需求外,还须要ID生成系统可用性极高。想象如下,若是ID生成系统瘫痪,那么整个业务没法进行下去,那将是一次灾难。
所以,总结ID生成系统还须要知足以下的需求:
1.高可用,可用性达到5个9或4个9。
2.高QPS,性能不能太差,不然容易形成线程堵塞。
3.平均延迟和TP999(保证99.9%的请求都能成功的最低延迟)延迟都要尽量低。数据库
UUID是指在一台机器在同一时间中生成的数字在全部机器中都是惟一的。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字
UUID由如下几部分的组合:
(1)当前日期和时间。
(2)时钟序列。
(3)全局惟一的IEEE机器识别号,若是有网卡,从网卡MAC地址得到,没有网卡以其余方式得到。
标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12),以连字号分为五段形式的36个字符,示例:550e8400-e29b-41d4-a716-446655440000
Java标准类库中已经提供了UUID的API。安全
UUID.randomUUID()
优势服务器
缺点网络
雪花ID生成的是一个64位的二进制正整数,而后转换成10进制的数。64位二进制数由以下部分组成:数据结构
优势架构
缺点
snowflake Java实现
/** * 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左右。 */ public class SnowflakeIdWorker { // ==============================Fields=========================================== /** 开始时间截 (2015-01-01) */ private final long twepoch = 1420041600000L; /** 机器id所占的位数 */ private final long workerIdBits = 5L; /** 数据标识id所占的位数 */ private final long datacenterIdBits = 5L; /** 支持的最大机器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; /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */ 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; //==============================Constructors===================================== /** * 构造函数 * @param workerId 工做ID (0~31) * @param datacenterId 数据中心ID (0~31) */ public SnowflakeIdWorker(long workerId, long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } // ==============================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位的ID return ((timestamp - twepoch) << timestampLeftShift) // | (datacenterId << datacenterIdShift) // | (workerId << workerIdShift) // | 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(); } //==============================Test============================================= /** 测试 */ public static void main(String[] args) { SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0); for (int i = 0; i < 1000; i++) { long id = idWorker.nextId(); System.out.println(Long.toBinaryString(id)); System.out.println(id); } } }
主要思路是采用数据库自增ID + replace_into实现惟一ID的获取。
create table t_global_id( id bigint(20) unsigned not null auto_increment, stub char(1) not null default '', primary key (id), unique key stub (stub) ) engine=MyISAM;
# 每次业务可使用如下SQL读写MySQL获得ID号
replace into t_golbal_id(stub) values('a'); select last_insert_id();
replace into跟insert功能相似,不一样点在于:replace into首先尝试插入数据列表中,若是发现表中已经有此行数据(根据主键或惟一索引判断)则先删除,再插入。不然直接插入新数据。
固然为了不数据库的单点故障,最少须要两个数据库实例,经过区分auto_increment的起始值和步长来生成奇偶数的ID。以下:
Server1: auto-increment-increment = 2 auto-increment-offset = 1 Server2: auto-increment-increment = 2 auto-increment-offset = 2
优势
缺点
对于MySQL的性能问题,能够用以下方案解决
在分布式环境中,咱们能够部署N台数据库实例,每台设置成不一样的初始值,自增步长为机器的台数。每台的初始值分别为1,2,3...N,步长为N。
以上方案虽然解决了性能问题,可是也存在很大的局限性:
Redis实现了一个原子操做INCR和INCRBY实现递增的操做。当使用数据库性能不够时,能够采用Redis来代替,同时使用Redis集群来提升吞吐量。能够初始化每台Redis的初始值为1,2,3,4,5,而后步长为5。各个Redis生成的ID为:
A:1,6,11,16,21 B:2,7,12,17,22 C:3,8,13,18,23 D:4,9,14,19,24 E:5,10,15,20,25
优势
缺点:
关于分布式全局惟一ID的生成,各个互联网公司有不少实现方案,好比美团点评的Leaf-snowflake,用zookeeper解决了各个服务器时钟回拨的问题,弱依赖zookeeper。以及Leaf-segment相似上面数据库批量ID获取的方案。
做者:Misout连接:https://www.jianshu.com/p/9d7ebe37215e来源:简书