框架篇:分布式全局唯一ID
前言
每一次HTTP请求,数据库的事务的执行,我们追踪代码执行的过程中,需要一个唯一值和这些业务操作相关联,对于单机的系统,可以用数据库的自增ID或者时间戳加一个在本机递增值,即可实现唯一值。但在分布式,又该如何实现唯一性的ID
- 分布式ID的特性
- 数据库自增的ID
- Redis分布式ID
- Zookeeper分布式ID
- 全局唯一UUID的优缺点
- Twitter的雪花算法生成分布式ID
关注公众号,一起交流,微信搜一搜: 潜行前行
github地址,感谢star
分布式ID的特性
- 全局唯一性,必须性
- 幂等性,如果是根据某些信息生成,则需要保障幂等性
- 注意安全性,ID里隐藏一些信息,不能被猜出来,也不能被猜出来 ID 如何生成
- 趋势递增性,在查询比较时,可以判断业务操作的时间顺序
数据库自增的ID
- 实现简单,ID单调自增,数值类型查询速度快,但是单点DB存在宕机风险,无法扛住高并发场景
CREATE TABLE FLIGHT_ORDER (
id int(11) unsigned NOT NULL auto_increment, #自增ID
PRIMARY KEY (id),
) ENGINE=innodb;
集群下如何保证数据库ID的唯一性
- 当随着业务发展,服务拓展到多台的大集群时,为了解决单点数据库的压力,数据库也会相应的变成一个集群,那如何保证集群下数据库ID的唯一性
- 每一台数据库实例都设置一个起始值和增长步长
- 缺点:不利于后续扩容,如果后续需要扩容还需要人工介入修改 起始值和增长步长
Redis 分布式ID
- 假如系统有亿万的数据,依靠数据库的自增ID在分表分库之后,需要人工修改每台数据库实例,扩容性差,维护性不好
基于Redis INCR 命令生成分布式全局唯一ID
- 服务向redis获取Id,ID则和数据库解耦,可以解决ID和分表分库的问题,而且redis比数据库性能更快,可以支撑集群服务并发获取ID的需求
- redis的INCR命令具备了 INCR AND GET 的原子操作;redis是单进程单线程架构,INCR 命令不会出现 ID 重复
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final String ID_KEY = "id_good_order";
public Long incrementId() {
return stringRedisTemplate.opsForValue().increment(ID_KEY);
}
HINCRBY 命令
- 实际上,为了存储序列号的更多相关信息,可以使用了 Redis 的 Hash 数据结构,Redis 同样为 Hash 提供 HINCRBY 命令来实现 “INCR AND GET” 原子操作
//KEY_NAME 是 hash结构对应的Key,FIELD_NAME 是hash结构的字段,INCR_BY_NUMBER是增量值
redis 127.0.0.1:6379> HINCRBY KEY_NAME FIELD_NAME INCR_BY_NUMBER
宕机序列号恢复问题
- redis是内存数据库,在没有开启RDB或AOF持久化的情况下,一旦宕机ID数据将会有丢失。即便开启了RDB持久化,由于最近一次快照时间和最新一条 HINCRBY 命令的时间有可能存在时间差,宕机后通过RDB快照恢复数据集会发生ID取值重复的情况
- redis宕机序列号恢复方案
- 利用关系型数据库来记录一个短时内 最大可取序列号 MAX_ID,从redis获取ID时只能取小于 MAX_ID 的序列号
- 为了计算最大值,需要一个定时任务定期计算ID消费速度RATE,存于redis。当客户端取得 CUR_ID、RATE 和 MAX_ID,则根据 ID 消费速度 RATE 计算 CUR_ID 是否逼近MAX_ID,如果是则更新数据库的MAX_ID
Zookeeper 分布式ID
- 利用zookeeper的持久性有序节点,可以实现自增的分布式ID,而且zookeeper是个高可用的集群服务,提交成功的消息具有持久性,因此不怕机器宕机问题,或者单机问题
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
- 示例
RetryPolicy retryPolicy = new ExponentialBackoffRetry(500, 3);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("localhost:2181")
.connectionTimeoutMs(5000)
.sessionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.build();
client.start();
String sequenceName = "root/sequence/distributedId";
DistributedAtomicLong distAtomicLong = new DistributedAtomicLong(client, sequenceName, retryPolicy);
//使用DistributedAtomicLong生成自增序列
public Long sequence() throws Exception {
AtomicValue<Long> sequence = this.distAtomicLong.increment();
if (sequence.succeeded()) {
return sequence.postValue();
} else {
return null;
}
}
UUID的优缺点
- 基于数据库,redis,zookeeper的分布式ID都高度依赖一个外部服务,对于某些场景,假如不存在这些外部服务又该怎么生成分布式的ID
- JDK里自带一个唯一性的ID的生成器,具有全球唯一性,这就是UUID,不过它是串无意义的字符串,存储性能差,查询也很耗时,对于订单系统,不适合作为唯一ID,常见优化方案为转化为两个uint64整数存储或者 折半存储(折半后不能保证唯一性)
- 但对于日志系统,或只是为了作为数据里可以唯一识别序列号的关联属性时,可以用UUID
String uuid = UUID.randomUUID().toString().replaceAll("-","");
Twitter 的雪花算法生成分布式ID
- 和UUID一样,雪花算法并不依赖外部服务
- 雪花算法时 Twitter 公司内部分布式项目采用的ID生成算法,广受国内公司好评。不依赖第三方服务,效率高
- Snowflake ID组成结构:正数位(占1比特)+ 时间戳(占41比特)+ 机器ID(占5比特)+ 数据中心(占5比特)+ 自增值(占12比特),总共64比特组成的一个Long类型。
1:第一个bit位(1bit):Java中long的最高位是符号位代表正负,正数是0,负数是1,一般生成ID都为正数,所以默认为0。
2:时间戳部分(41bit):毫秒级的时间,不建议存当前时间戳,而是用(当前时间戳 - 固定开始时间戳)的差值,可以使产生的ID从更小的值开始
3:工作机器id(10bit):也被叫做workId,这个可以灵活配置,机房或者机器号组合都可以。
4:序列号部分(12bit),自增值支持同一毫秒内同一个节点可以生成4096个ID
//Twitter的SnowFlake算法,使用SnowFlake算法生成一个整数
public class SnowFlakeShortUrl {
//起始的时间戳
static long START_TIMESTAMP = 1624698370256L;
//每一部分占用的位数
static long SEQUENCE_BIT = 12; //序列号占用的位数
static long MACHINE_BIT = 5; //机器标识占用的位数
static long DATA_CENTER_BIT = 5; //数据中心占用的位数
//每一部分的最大值
static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
static long MAX_DATA_CENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT);
//每一部分向左的位移
static long MACHINE_LEFT = SEQUENCE_BIT;
static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;
//dataCenterId + machineId 等于10bit工作机器ID
private long dataCenterId; //数据中心
private long machineId; //机器标识
private volatile long sequence = 0L; //序列号
private volatile long lastTimeStamp = -1L; //上一次时间戳
private volatile long l currTimeStamp = -1L; //当前时间戳
private long getNextMill() {
long mill = System.currentTimeMillis();
while (mill <= lastTimeStamp) mill = System.currentTimeMillis();
return mill;
}
//根据指定的数据中心ID和机器标志ID生成指定的序列号
public SnowFlakeShortUrl(long dataCenterId, long machineId) {
Assert.isTrue(dataCenterId >=0 && dataCenterId <= MAX_DATA_CENTER_NUM,"dataCenterId is illegal!");
Assert.isTrue(machineId >= 0 || machineId <= MAX_MACHINE_NUM,"machineId is illegal!");
this.dataCenterId = dataCenterId;
this.machineId = machineId;
}
//生成下一个ID
public synchronized long nextId() {
currTimeStamp = System.currentTimeMillis();
Assert.isTrue(currTimeStamp >= lastTimeStamp,"Clock moved backwards");
if (currTimeStamp == lastTimeStamp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
if (sequence == 0L) { //同一毫秒的序列数已经达到最大,获取下一个毫秒
currTimeStamp = getNextMill();
}
} else {
sequence = 0L; //不同毫秒内,序列号置为0
}
lastTimeStamp = currTimeStamp;
return (currTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT //时间戳部分
| dataCenterId << DATA_CENTER_LEFT //数据中心部分
| machineId << MACHINE_LEFT //机器标识部分
| sequence; //序列号部分
}
public static void main(String[] args) {
SnowFlakeShortUrl snowFlake = new SnowFlakeShortUrl(10, 4);
for (int i = 0; i < (1 << 12); i++) {
//10进制
System.out.println(snowFlake.nextId());
}
}
}
欢迎指正文中错误
参考文章
框架篇:分布式全局唯一ID的更多相关文章
- Mysql系列七:分库分表技术难题之分布式全局唯一id解决方案
一.前言 在前面的文章Mysql系列四:数据库分库分表基础理论中,已经说过分库分表需要应对的技术难题有如下几个: 1. 分布式全局唯一id 2. 分片规则和策略 3. 跨分片技术问题 4. 跨分片事物 ...
- 分布式全局唯一ID的实现
分布式全局唯一ID的实现 前言 上周末考完试,这周正好把工作整理整理,然后也把之前的一些素材,整理一番,也当自己再学习一番. 一方面正好最近看到几篇这方面的文章,另一方面也是正好工作上有所涉及,所以决 ...
- (4.24)【mysql、sql server】分布式全局唯一ID生成方案
参考:分布式全局唯一ID生成方案:https://blog.csdn.net/linzhiqiang0316/article/details/80425437 分表生成唯一ID方案 sql serve ...
- 分布式全局唯一ID生成策略
为什么分布式系统需要用到ID生成系统 在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识.如在美团点评的金融.支付.餐饮.酒店.猫眼电影等产品的系统中,数据日渐增长,对数据库的分库分表后需要有 ...
- 分布式全局唯一ID与自增序列
包含时间顺序的ID 此场景最简单的实现方案,就是采用 twitter 的 Snowflake 算法.ID总长64位,第1位不可用,41位表示时间戳,10位表示生成机器的id,后12位表示序列号. 为什 ...
- 分布式全局唯一ID
方案一.UUID UUID的方式能生成一串唯一随机32位长度数据,它是无序的一串数据,按照开放软件基金会(OSF)制定的标准计算,UUID的生成用到了以太网卡地址.纳秒级时间.芯片ID码和许多可能的数 ...
- 常见分布式全局唯一ID生成策略
全局唯一的 ID 几乎是所有系统都会遇到的刚需.这个 id 在搜索, 存储数据, 加快检索速度 等等很多方面都有着重要的意义.工业上有多种策略来获取这个全局唯一的id,针对常见的几种场景,我在这里进行 ...
- 分布式全局唯一ID生成策略
一.背景 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表.因为数据量巨大一张表无法承接,就会对其进行分库分表. 但一旦涉及到分库分表,就会引申出分布式系统中唯一主键ID的生成问题. ...
- 面试官:如何在分布式场景下生成全局唯一 ID?
在分布式系统中,有一些场景需要使用全局唯一 ID ,可以和业务场景有关,比如支付流水号,也可以和业务场景无关,比如分库分表后需要有一个全局唯一 ID,或者用作事务版本号.分布式链路追踪等等,好的全局唯 ...
随机推荐
- TLB和CPU缓存
TLB 如果每次应用程序访问一个线性地址都需要先解析(查PDT,PTT)那么效率十分低,为了提高执行效率CPU在CPU内部建立了一个TLB表,此表和寄存器一样访问速度极高.其会记录线性地址和物理地址之 ...
- 【Matlab】BFSK的调制与解调仿真
写在前面 本篇是[Matlab]BASK的调制与解调仿真的下篇,考虑到阅读体验,故另开一篇分享将BFSK的调制与解调仿真. 索引 写在前面 一.BFSK的调制 1.1 异频载波生成 1.2 信号合并 ...
- CRM的未来发展前景有哪些?
随着时代的发展,近年来越来越多的国内中小企业开始采用CRM客户关系管理系统,CRM从此不再是大企业的专利,也开始让中小企业得以不断成长.国内CRM行业的发展越来越快, 它的前景是什么?今天小Z就来给大 ...
- 在微信框架模块中,基于Vue&Element前端的后台管理功能介绍
微信开发包括公众号.企业微信.微信小程序等方面的开发内容,需要对腾信的微信API接口进行封装:包括事件.菜单.订阅用户.多媒体文件.图文消息.消息群发.微信支付和企业红包.摇一摇设备.语义理解.微信小 ...
- [Web] 网络安全(SSH SSL HTTPS)
概念 SSH(Secure Shell) 一种安全通信协议 为shell提供加密通信服务 使用了非对称加密和对称加密 对称加密(Symmetric-Key Encryption):只用一个密钥来进行加 ...
- 通用PE u盘装Ghost Win7系统教程
通用PE u盘装Ghost Win7系统教程 导读 通用pe工具箱是现在最老牌的的U盘装系统和维护电脑的专用工具之一,一键式制作.操作简单便捷,几乎100%支持所有U盘,不再为装机烦恼们,抓紧时间下载 ...
- 系统区域设置 本地语言的支持依赖于 /etc/locale.conf,/etc/locale.conf 包含不少于此相关的环境变量
https://linux.cn/lfs/LFS-BOOK-7.7-systemd/chapter07/locale.html 7.7. 系统区域设置 本地语言的支持依赖于 /etc/locale.c ...
- 003.kubernets对于namespace的管理
一 Kuberbetes的架构简单介绍 1.1 云计算的传统分类 1.2 kubernetes基础架构 工作机制 用户通过kubectl向api-server提交需要运行的pod描述 api-serv ...
- 7.2-5 usermod
7.2 usermod:修改用户信息 usermod 命令用于修改系统已经存在的用户的账号信息. -c comment 修改用户password文件中用户的说明栏,同useradd ...
- 8.2-3 partprobe、tune2fs
8.2 partprobe:更新内核的硬盘分区表信息 partprobe命令用于在硬盘分区发生改变时,更新Linux内核中的硬盘分区表数据.有时在使用fdisk.part命令对硬盘进行分区 ...