阅读大概须要3分钟
java
附源码web
[toc]算法
单体架构的服务的日子已经一去不复返了。sql
当前系统业务和数据存储的复杂度都在提高,分布式系统是目前使用很是广泛的解决方案。docker
全局惟一 ID 几乎是全部设计系统时都会遇到的,全局惟一 ID 在存储和检索中有相当重要的做用。数据库
在应用程序中,常常须要全局惟一的ID做为数据库主键。如何生成全局惟一ID?apache
首先,须要肯定全局惟一ID是整型仍是字符串?若是是字符串,那么现有的UUID就彻底知足需求,不须要额外的工做。缺点是字符串做为ID占用空间大,索引效率比整型低。安全
若是采用整型做为ID,那么首先排除掉32位int类型,由于范围过小,必须使用64位long型。服务器
采用整型做为ID时,如何生成自增、全局惟一且不重复的ID?
网络
数据库自增 ID 是咱们在数据量较小的系统中常用的,利用数据库的自增ID,从1开始,基本能够作到连续递增。Oracle能够用 SEQUENCE
,MySQL能够用主键的 AUTO_INCREMENT
,虽然不能保证全局惟一,但每一个表惟一,也基本知足需求。
数据库自增ID的缺点是数据在插入前,没法得到ID。数据在插入后,获取的ID虽然是惟一的,但必定要等到事务提交后,ID才算是有效的。有些双向引用的数据,不得不插入后再作一次更新,比较麻烦。
在咱们开发过程当中,遇到一种 主主数据库同步(简单能够理解为,一样的sql再另外一台数据库再执行一次)的场景,若是使用数据库自增 ID,就会出现主键不一致、或主键冲突问题。
分布式环境不推荐使用
uuid 是咱们比较先想到的方法,在 java.util;包中就有对应方法。这是一个具备rfc标准的uuid:https://www.ietf.org/rfc/rfc4122.txt
uuid 有很好的性能(本地调用),没有网络消耗。
可是,uuid 不易存储(生成了字符串、存储过长、不少场景不适用);信息不安全(基于 MAC 地址生成、可能会形成泄露,这个漏洞曾被用于寻找梅丽莎病毒的制做者位置。
);没法保证递增(或趋势递增);其余博主反馈,截取前20位作惟一 ID ,在大数量(大概只有220w)状况下会有重复问题。
UUID.randomUUID().toString()
这是目前使用较多分布式ID解决方案,推荐使用
背景 Twitter 云云就不介绍了,就是前段时间封了懂王帐号的 Twitter。
SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构以下图:
1位,不用。二进制中最高位为1的都是负数,可是咱们生成的id通常都使用整数,因此这个最高位固定是0
41位,用来记录时间戳(毫秒)。
10位,用来记录工做机器id。
因为在 Java 中 64bit 的整数是 long 类型,因此在 Java 中 SnowFlake 算法生成的 id 就是 long 来存储的。
SnowFlake能够保证:
存在的问题:
针对上面问题,这里提供一种解决思路,workId 使用服务器 hostName 生成,dataCenterId 使用 IP 生成,这样能够最大限度防止 10 位机器码重复,可是因为两个 ID 都不能超过 32,只能取余数,仍是不免产生重复,可是实际使用中,hostName 和 IP 的配置通常连续或相近,只要不是恰好相隔 32 位,就不会有问题,何况,hostName 和 IP 同时相隔 32 的状况更加是几乎不可能的事,平时作的分布式部署,通常也不会超过 10 台容器。
生产上使用docker配置通常是一次编译,而后分布式部署到不一样容器,不会有不一样的配置。这种状况就对上面提到的出现了不肯定状况,这个在评论中会再出一篇参考文章。
Java 版雪花ID生成算法
package com.my.blog.website.utils; import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; import java.net.Inet4Address; import java.net.UnknownHostException; /** * 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 = 1489111610226L; /** 机器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; private static SnowflakeIdWorker idWorker; static { idWorker = new SnowflakeIdWorker(getWorkId(),getDataCenterId()); } //==============================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("workerId can't be greater than %d or less than 0", maxWorkerId)); } if (dataCenterId > maxDataCenterId || dataCenterId < 0) { throw new IllegalArgumentException(String.format("dataCenterId 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(); } private static Long getWorkId(){ try { String hostAddress = Inet4Address.getLocalHost().getHostAddress(); int[] ints = StringUtils.toCodePoints(hostAddress); int sums = 0; for(int b : ints){ sums += b; } return (long)(sums % 32); } catch (UnknownHostException e) { // 若是获取失败,则使用随机数备用 return RandomUtils.nextLong(0,31); } } private static Long getDataCenterId(){ int[] ints = StringUtils.toCodePoints(SystemUtils.getHostName()); int sums = 0; for (int i: ints) { sums += i; } return (long)(sums % 32); } /** * 静态工具类 * * @return */ public static synchronized Long generateId(){ long id = idWorker.nextId(); return id; } //==============================Test============================================= /** 测试 */ public static void main(String[] args) { System.out.println(System.currentTimeMillis()); long startTime = System.nanoTime(); for (int i = 0; i < 50000; i++) { long id = SnowflakeIdWorker.generateId(); System.out.println(id); } System.out.println((System.nanoTime()-startTime)/1000000+"ms"); } }
参考原文:https://blog.csdn.net/xiaopeng9275/article/details/72123709
分享和在看是对我最大的鼓励。我是pub哥,咱们下期见!