分布式ID生成器-订单号的生成(全局唯一id生成策略)
分库分表产生的4大问题:跨库问题、分布式事务问题、查询数据结果集合并、全局唯一性id保证。本文掰扯下全局唯一性id。
全局唯一id的4种生成策略:UUID、数据库递增、snowflake、Redis
全局唯一性id保证的4个要求:全局唯一、数据递增、信息安全、高并发高可用
---a.全局唯一:不能出现重复的id号;
---b.数据递增:保证下一个id一定大于上一个id;
---c.信息安全:防止恶意用户根据id规则来获取数据;
---d.高并发高可用:短时间内快速生成可用,解决线程安全问题。。
分布式唯一id生成策略 | 优点 | 缺点 |
UUID(通用唯一识别码) | 代码实现简单、不占用带宽、数据迁移不受影响 | 无序无法建索引、无法保证数据趋势递增、字符存储,传输和查询慢、不可读 |
snowflake雪花算法 | 代码实现简单、不占用带宽、数据迁移不受影响、低位趋势递增 | 无序无法建索引、无法保证数据趋势递增、强依赖时钟(多台服务器时间一定要一样) |
数据库递增 | 代码实现简单、性能好、数字排序、可读性强 | 位数不确定,有溢出风险、高并发情况下有性能瓶颈、受限数据库、扩展麻烦、插入数据库才能拿到id、单点故障问题 |
redis | 不依赖数据库、灵活方便、性能优于数据库、没有单点故障(高可用) | 需要占用网络资源、性能要比本地生成慢、需要增加插件 |
一、UUID
UUID是通用唯一识别码。16B=128bit的长数字。组成=当前日期和时间序列+全局唯一性网卡mac地址。java可使用java.util.UUID来做。
优点:代码实现简单、不占用带宽、数据迁移不受影响
缺点:无序、无法保证数据趋势递增、字符存储,传输和查询慢、不可读
uuid有很多版本,比如:MD5,SHA1,随机
二、snowflake雪花算法(国外twitter)
1bit高位随机 + 41bit毫秒数 + 10bit机器码(数据中心+机器id) + 12bit序列号 = 64bit
1bit:二进制中最高位为1的都是负数,但是我们生成的id一般都使用整数,所以这个最高位固定是0;
41bit:可表示2^41−1个毫秒的值。转化成单位年则是(2^41−1)/(1000*60*60*24*365)=69年;
10bit:用来记录工作机器id。可以部署在2^10=1024个节点,包括5位datacenterId和5位workerId,5bit可以表示的最大正整数是2^5−1=31,即可以用0、1、2、3....31这32个数字,来表示不同的datecenterId或workerId;
12bit:序列号,用来记录同毫秒内产生的不同id。12bit可表示的最大正整数是2^12−1=4095,表示同一机器同一时间截(毫秒)内产生的4095个ID序号;
SnowFlake可以:每毫秒最多生成4096个ID,每秒可达4096000个(每秒400万+个ID)。理论上,只要CPU计算能力足够,单机每秒实测10w+:snowflake每秒能够产生26万个ID; 所有生成的id按时间趋势递增; 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)详见:https://github.com/souyunku/SnowFlake
国内保证数据唯一性就行了---IDC机房
优点:代码实现简单、不占用带宽、数据迁移不受影响、低位趋势递增
缺点:无序、无法保证数据趋势递增、强依赖时钟(多台服务器时间一定要一样)
关键代码如下,详细代码见附录
snowflake算法生成id的关键代码: ---------------------------------------------------------------- 需要解析生成的id,可以使用如下方式: 其中,arr[0-3]分别代表id生成的时间戳,机器标识,业务标识,序列号 |
//Snowflake生成订单号需要注意的是其中两个参数的含义 //dataCenterId可用于区分机器,workerId可用于区分业务 //datacenterId=2; workerId=5 Snowflake snowflake = new Snowflake(2, 5); long id1 = snowflake.nextId(); long id2 = snowflake.nextId(); |
三、数据库递增(mysql自增id)
可以设置步长,比如:奇偶,递增步长=2
适合小型互联网公司,比如:5w订单量,一年1800w,mysql一张表500w,如果公司每天订单量5w的数据,用mysql设置步长=100(100张表),可以用27年,如果公司拿到风投了,每天的订单量100w,撑不过3年!
CREATE TABLE `tl_num`(`id` bigint(11) NOT NULL AUTO INCREMENT,KEY (`id`) USING BTREE
) ENGINE=InnoDB auto increment=1 DEFAULT CHARSET=utf8;
优点:代码实现简单、性能好、数字排序、可读性强
缺点:受限数据库、扩展麻烦、插入数据库才能拿到id、单点故障问题
主从同步的时候:电商下单--->支付insert master db select数据,因为数据同步延迟导致查不到这个数据。解决:加cache(不是最好的方式),数据要求比较严的话查master主库。
四、redis
缩减版本、有关业务代码没有包含到里头、Redis方案
优点:不依赖数据库、灵活方便、性能优于数据库、没有单点故障(高可用)
缺点:需要占用网络资源、性能要比本地生成慢、需要增加插件
=================附录============================================
snowflake第一种解法:https://github.com/souyunku/SnowFlake
实测:100万个ID 耗时5秒/** * 描述: Twitter的分布式自增ID雪花算法snowflake (Java版)**/
public class SnowFlake {/*** 起始的时间戳*/private final static long START_STMP = 1480166465631L;/*** 每一部分占用的位数*/private final static long SEQUENCE_BIT = 12; //序列号占用的位数private final static long MACHINE_BIT = 5; //机器标识占用的位数private final static long DATACENTER_BIT = 5;//数据中心占用的位数/*** 每一部分的最大值*/private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_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 DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;private long datacenterId; //数据中心private long machineId; //机器标识private long sequence = 0L; //序列号private long lastStmp = -1L;//上一次时间戳public SnowFlake(long datacenterId, long machineId) {if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_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() {long currStmp = getNewstmp();if (currStmp < lastStmp) {throw new RuntimeException("Clock moved backwards. Refusing to generate id");}if (currStmp == lastStmp) {//相同毫秒内,序列号自增sequence = (sequence + 1) & MAX_SEQUENCE;//同一毫秒的序列数已经达到最大if (sequence == 0L) {currStmp = getNextMill();}} else {//不同毫秒内,序列号置为0sequence = 0L;}lastStmp = currStmp;return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分| datacenterId << DATACENTER_LEFT //数据中心部分| machineId << MACHINE_LEFT //机器标识部分| sequence; //序列号部分}private long getNextMill() {long mill = getNewstmp();while (mill <= lastStmp) {mill = getNewstmp();}return mill;}private long getNewstmp() {return System.currentTimeMillis();}public static void main(String[] args) {SnowFlake snowFlake = new SnowFlake(2, 3);long start = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {System.out.println(snowFlake.nextId());}System.out.println(System.currentTimeMillis() - start);}
}
snowflake第二种解法:
/** * 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 Snowflake { /** 开始时间截 (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(1,1); } /** * 构造函数 * @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; } /** 获得下一个ID (该方法是线程安全的) * @return SnowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); if (timestamp < lastTimestamp) {//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 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; } lastTimestamp = timestamp;//上次生成ID的时间截 //移位并通过或运算拼到一起组成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(); } /** * 静态工具类 * @return */ public static Long generateId(){ long id = idWorker.nextId(); return id; } /** 测试 */ 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"); }}
分布式ID生成器-订单号的生成(全局唯一id生成策略)相关推荐
- 如何在分布式场景下生成全局唯一 ID ?
作者 l 会点代码的大叔(CodeDaShu) 在分布式系统中,有一些场景需要使用全局唯一 ID ,可以和业务场景有关,比如支付流水号,也可以和业务场景无关,比如分库分表后需要有一个全局唯一 ID,或 ...
- 如何在高并发分布式系统中生成全局唯一Id
又一个多月没冒泡了,其实最近学了些东西,但是没有安排时间整理成博文,后续再奉上.最近还写了一个发邮件的组件以及性能测试请看 <NET开发邮件发送功能的全面教程(含邮件组件源码)> ,还弄了 ...
- 高并发分布式系统中生成全局唯一Id汇总
全局唯一ID <高并发分布式系统中生成全局唯一Id汇总> Twitter 方案(Snowflake 算法):41位时间戳+10位机器标识(比如IP,服务器名称等)+12位序列号(本地计数器 ...
- 全局唯一递增的id_生成全局唯一id的几种方式
生成全局唯一id的几种方式: 1.uuid生成全球唯一id,生成方式简单粗暴,本地生成,没有网络开销,效率高:缺点长度较长,没有递增趋势性,不易维护,常用于生成token令牌. 2.mysql自带自增 ...
- Redis生成全局唯一ID
简介: 全局唯一ID生成器是一种在分布式系统下用来生成全局唯一ID的工具 特性: 唯一性 高性能 安全性 高可用 递增性 生成规则: 有时为了增加ID的安全性,我们可以不直接使用Redis自增的数值, ...
- 雪花算法及运用PHP,雪花算法生成全局唯一ID,参考了下网上雪花算法生成规则,机器ID和序列号自动获取 理论上毫秒可生成 1024*4096个唯一ID
任务要求毫秒生成10000个唯一ID 研究了下twitter/snowflake的算法思想: 参考了下网上雪花算法生成规则,把数据中心和机器编号整合一起,变成10位机器ID, 机器ID和序列号自动获取 ...
- 游戏服务器生成全局唯一ID的几种方法
在服务器系统开发时,为了适应数据大并发的请求,我们往往需要对数据进行异步存储,特别是在做分布式系统时,这个时候就不能等待插入数据库返回了取自动id了,而是需要在插入数据库之前生成一个全局的唯一id,使 ...
- snowflake mysql_SnowFlake 生成全局唯一id
public classSnowFlakeUtil {private longworkerId;private longdatacenterId;private long sequence = 0L; ...
- 【架构】生成全局唯一ID的3个思路,来自一个资深架构师的总结
标识(ID / Identifier)是无处不在的,生成标识的主体是人,那么它就是一个命名过程,如果是计算机,那么它就是一个生成过程.如何保证分布式系统下,并行生成标识的唯一与标识的命名空间有着密不可 ...
- 生成全局唯一ID的3个思路,来自一个资深架构师的总结
标识(ID / Identifier)是无处不在的,生成标识的主体是人,那么它就是一个命名过程,如果是计算机,那么它就是一个生成过程.如何保证分布式系统下,并行生成标识的唯一与标识的命名空间有着密不可 ...
最新文章
- Spring基础专题——第十章(基础注解编程——下)
- python UnicodeEncodeError 编码错误总结
- Java虚拟机详解02----JVM内存结构
- react编译报错:Import in body of module; reorder to top import/first
- spark从入门到精通spark内存管理详解- 堆内堆外内存管理
- 关于EF查询表里的部分字段
- contains不区分大小写_趣读丨2020祝福语怎么发才不像是群发?全网独一份的模板安排上了!...
- ArcGIS Runtime SDK for .NET (Quartz Beta)之连接ArcGIS Portal
- 微机笔记——1.微型计算机概述
- python车牌字符分割_OpenCV+Python识别车牌和字符分割的实现
- 视频教程-思科CCNP专题系列②:EIGRP路由协议-思科认证
- Word如何插入图片
- 制作简易的幸运转盘抽奖
- pr导出html,premiere视频导出怎么设置? pr导出高质量视频的教程
- 用了这个办法解决Discuz! Database Error报错
- Wordpress 网站数据库恢复记录
- 【问题】loadrunner运行场景时,用户卡在run状态,且退出时卡在gradual exiting状态
- Mac运行Android模拟器报The emulator process for AVD XXX has terminated错误
- DataGrip使用教程
- php添加水印,水印平铺整个图片
热门文章
- 5类6类7类网线对比_5类 6类 7类网线有没有什么区别
- C# 实现俄罗斯方块
- 【AS】Android stdio运行APP闪退或keeps stopping
- 大数据论文_01_GFS(个人总结)
- 淘宝订单同步及解决方法
- 动点四边形周长最短_中考数学之四边形周长最小值
- 打外星生物的塔防java_《异形防御者》测评:外星人也玩塔防游戏
- 附加支付和统筹支付_医保附加支付是什么意思?
- 【项目管理/PMP/PMBOK第六版/新考纲】纯干货!敏捷型/Stacey矩阵/vuca/敏捷宣言/冲刺/产品负责人/敏捷团队/敏捷教练/待办事项列表/迭代任务列表/可交付产品增量
- PMI-ACP练习题(22)