IT博文
MySQL 事务隔离级别详解
使用 docker compose 安装 tidb
架构师日记-如何写的一手好代码
生产事故-记一次特殊的OOM排查
Docker安装RabbitMQ——基于docker-compose工具
使用 docker-compose 部署单机 RabbitMQ
只需3步,即刻体验Oracle Database 23c
长达 1.7 万字的 explain 关键字指南!
Redis为什么能抗住10万并发?揭秘性能优越的背后原因
深度剖析Redis九种数据结构实现原理
【绩效季】遇到一个好领导有多重要,从被打差绩效到收获成长
为什么Redis不直接使用C语言的字符串?
Java阻塞队列中的异类,SynchronousQueue底层实现原理剖析
如何调整和优化 Go 程序的内存管理方式?
应用部署引起上游服务抖动问题分析及优化实践方案
Java 并发工具合集 JUC 大爆发!!!
卷起来!!这才是 MySQL 事务 & MVCC 的真相。
JDK8 到 JDK17 有哪些吸引人的新特性?
告别StringUtil:使用Java 11的全新String API优化你的代码
从JDK8飞升到JDK17,再到未来的JDK21
Java JMH Benchmark Tutorial
linux和macOS下top命令区别
Windows10关闭Hyper-V的三种方法
为什么应该选择 POSTGRES?
阿里云对象存储 OSS 限流超过阈值自动关闭【防破产,保平安】
Java高并发革命!JDK19新特性——虚拟线程(Virtual Threads)
“请不要在虚拟机中运行此程序”的解决方案
Spring中的循环依赖及解决
浅谈复杂业务系统的架构设计 | 京东云技术团队
面试题:聊聊TCP的粘包、拆包以及解决方案
操作日志记录实现方式
字节跳动技术团队-慢 SQL 分析与优化
Spring Boot 使用 AOP 防止重复提交
Controller层代码就该这么写,简洁又优雅!
SpringBoot 项目 + JWT 完成用户登录、注册、鉴权
重复提交不再是问题!SpringBoot自定义注解+AOP巧妙解决
SpringBoot 整合 ES 实现 CRUD 操作
SpringBoot 整合 ES 进行各种高级查询搜索
SpringBoot操作ES进行各种高级查询
SpringBoot整合ES查询
如何做架构设计? | 京东云技术团队
最值得推荐的五个VPN软件(便宜+好用+稳定),靠谱的V2ray梯子工具
我说MySQL每张表最好不超过2000万数据,面试官让我回去等通知?
vivo 自研鲁班分布式 ID 服务实践
使用自带zookeeper超简单安装kafka
推荐 6 个很牛的 IDEA 插件
喜马拉雅 Redis 与 Pika 缓存使用军规
「程序员转型技术管理」必修的 10 个能力提升方向
jdk17 下 netty 导致堆内存疯涨原因排查 | 京东云技术团队
如何优雅做好项目管理?
MySQL 到 TiDB:Hive Metastore 横向扩展之路
聊聊即将到来的 MySQL5.7 停服事件
Linux终端环境配置
微软 Edge 浏览器隐藏功能一览:多线程下载、IE 模式、阻止视频自动播放等
Hutool 中那些常用的工具类和实用方法
clash 内核删库?汇总目前常用的内核仓库和客户端
JDK11 升级 JDK17 最全实践干货来了 | 京东云技术团队
我是如何写一篇技术文的?
虚拟线程原理及性能分析
Java线程池实现原理及其在美团业务中的实践
Editplus和EmEditor配置一键编译java运行环境
用Spring Boot 3.2虚拟线程搭建静态文件服务器有多快?
SpringBoot中使用LocalDateTime踩坑记录 - 程序员偏安 - 博客园
程序员必备!10款实用便捷的Git可视化管理工具 - 追逐时光者 - 博客园
基于Netty开发轻量级RPC框架
开发Java应用时如何用好Log
复杂SQL治理实践 | 京东物流技术团队
火山引擎ByteHouse:分析型数据库如何设计并发控制?
多次崩了之后,阿里云终于改了
推荐程序员必知的四大神级学习网站
初探分布式链路追踪
新项目为什么决定用 JDK 17了
Java上进了,JDK21 要来了,并发编程再也不是噩梦了
mapstruct这么用,同事也开始模仿
再见RestTemplate,Spring 6.1新特性:RestClient 了解一下!
【MySQL】MySQL表设计的经验(建议收藏)
如何正确地理解应用架构并开发
解读工行专利CN112905176B
工商银行取得「基于 Spring Boot 的 web 系统后端实现方法及装置」专利
IDEA 2024.1:Spring支持增强、GitHub Action支持增强、更新HTTP Client等
TIOBE 2 月:Go 首次进入前十、“上古语言” COBOL 和 Fortran 排名飙升
Java 21 虚拟线程如何限流控制吞吐量
🎉 通用、灵活、高性能分布式 ID 生成器 | CosId 2.6.6 发布
20年编程,AI编程6个月,关于Copliot辅助编码工具,你想知道的都在这里
Java 8 内存管理原理解析及内存故障排查实践
消息队列选型之 Kafka vs RabbitMQ
从 MongoDB 到 PostgreSQL 的大迁移
腾讯云4月8日故障复盘及情况说明
PHP 在 2024 年还值得学习吗?
AMD集显安装显卡驱动之后出现黑屏,建议这样解决
使用 Docker 部署 moments 微信朋友圈 - 谱次· - 博客园
Java 17 是最常用的 Java LTS 版本
盘点Lombok的几个骚操作
Llama 3 + Ollama + Open WebUI打造本机强大GPT
如何优雅地编写缓存代码
Gmeek快速上手
笔记软件思源远程和本地接入大语言模型服务Ollama实现AI辅助写作(Windows篇)
Git Subtree:简单粗暴的多项目管理神器
这款轻量级规则引擎,真香!!
Ollama教程:本地LLM管理、WebUI对话、Python/Java客户端API应用
GLM-4-9B支持 Ollama 部署
智谱AI开源代码生成大模型第四代版本:CodeGeeX4-ALL-9B
美团二面:如何保证Redis与Mysql双写一致性?连续两个面试问到了!
免费开源好用,Obsidian和Omnivore真正实现一键联动剪藏文章,手把手教程!
得物 Redis 设计与实践
架构图怎么画?手把手教您,以生鲜电商为例剖析业务/应用/数据/技术架构图
使用Hutool要注意了!升级到6.0后你调用的所有方法都将报错 - 掘金
别再用雪花算法生成ID了!试试这个吧
无敌的Arthas!
Navicat Premium v16、v17 破解激活
🎉 分布式接口文档聚合,Solon 是怎么做的?
深入体验全新 Cursor AI IDE 后,说杀疯了真不为过!
Nacos 3.0 架构全景解读,AI 时代服务注册中心的演进
本文档使用 MrDoc 发布
-
+
🎉 通用、灵活、高性能分布式 ID 生成器 | CosId 2.6.6 发布
[2023 大语言模型技术报告.pdf ](https://talk.gitee.com/report/china-open-source-2023-llm-report.pdf?fr=newsdetail)  [](https://gitee.com/link?target=https%3A%2F%2Fwww.apache.org%2Flicenses%2FLICENSE-2.0.html) [](https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2FAhoo-Wang%2FCosId%2Freleases) [](https://gitee.com/link?target=https%3A%2F%2Fapp.codacy.com%2Fgh%2FAhoo-Wang%2FCosId%3Futm_source%3Dgithub.com%26utm_medium%3Dreferral%26utm_content%3DAhoo-Wang%2FCosId%26utm_campaign%3DBadge_Grade_Settings) [](https://gitee.com/link?target=https%3A%2F%2Fcodecov.io%2Fgh%2FAhoo-Wang%2FCosId)  > _官方文档_   [https://ahoowang.gitee.io/cosid/](https://ahoowang.gitee.io/cosid/)  ## 更新内容 - 依赖: 升级 `org.springframework.boot:spring-boot-dependencies` 版本 `v3.2.3` - 依赖: 升级 `com.github.spotbugs` 版本 `v6.0.7` - 依赖: 升级 `org.axonframework:axon-bom` 版本 `v4.9.3` - 依赖: 升级 `org.testcontainers:testcontainers-bom` 版本 `v1.19.6` - 依赖: 升级 `flowable` 版本 `v7` - 依赖: 升级 `gradle` 版本 `v8.6` - 依赖: 升级 `junit5` 版本 `v5.10.2` ## 简介 _[CosId](https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2FAhoo-Wang%2FCosId)_ 旨在提供通用、灵活、高性能的分布式 ID 生成器。 - `CosIdGenerator` : _单机 TPS 性能:1557W/s_,三倍于 `UUID.randomUUID()`,基于时钟的全局趋势递增 ID。 - `SnowflakeId` : _单机 TPS 性能:409W/s_ [JMH 基准测试](https://gitee.com/link?target=https%3A%2F%2Fcosid.ahoo.me%2Fguide%2Fperf-test.html) , 主要解决 _时钟回拨问题_ 、_机器号分配问题_、_取模分片不均匀问题_ 并且提供更加友好、灵活的使用体验。 - `SegmentId`: 每次获取一段 (`Step`) ID,来降低号段分发器的网络 IO 请求频次提升性能。 - `IdSegmentDistributor`: 号段分发器(号段存储器) - `RedisIdSegmentDistributor`: 基于 _Redis_ 的号段分发器。 - `JdbcIdSegmentDistributor`: 基于 _Jdbc_ 的号段分发器,支持各种关系型数据库。 - `ZookeeperIdSegmentDistributor`: 基于 _Zookeeper_ 的号段分发器。 - `MongoIdSegmentDistributor`: 基于 _MongoDB_ 的号段分发器。 - `SegmentChainId`(**推荐**):`SegmentChainId` (_lock-free_) 是对 `SegmentId` 的增强。性能可达到近似 `AtomicLong` 的 _TPS 性能:12743W+/s_ [JMH 基准测试](https://gitee.com/link?target=https%3A%2F%2Fcosid.ahoo.me%2Fguide%2Fperf-test.html) 。 - `PrefetchWorker` 维护安全距离 (`safeDistance`), 并且支持基于饥饿状态的动态 `safeDistance` 扩容 / 收缩。 ## [快速开始](https://ahoowang.gitee.io/cosid/guide/getting-started.html) ## 背景(为什么需要_分布式 ID_) 在软件系统演进过程中,随着业务规模的增长 (TPS / 存储容量),我们需要通过集群化部署来分摊计算、存储压力。 应用服务的无状态设计使其具备了伸缩性。在使用 **Kubernetes** 部署时我们只需要一行命令即可完成服务伸缩 (`kubectl scale --replicas=5 deployment/order-service`)。 但对于有状态的数据库就不那么容易了,此时数据库变成系统的性能瓶颈是显而易见的。 ### 分库分表 > 从微服务的角度来理解垂直拆分其实就是微服务拆分。以限界上下文来定义服务边界将大服务 / 单体应用拆分成多个自治的粒度更小的服务,因为自治性规范要求,数据库也需要进行业务拆分。 但垂直拆分后的单个微服务依然会面临 TPS / 存储容量 的挑战,所以这里我们重点讨论水平拆分的方式。  数据库分库分表方案是逻辑统一,物理分区自治的方案。其核心设计在于中间层映射方案的设计 (上图 **Mapping**),即分片算法的设计。 几乎所有编程语言都内置实现了散列表 (java:`HashMap`/csharp:`Dictionary`/python:`dict`/go:`map` ...)。分片算法跟散列表高度相似 (`hashCode`),都得通过 `key`/`shardingValue` 映射到对应的槽位 (`slot`)。 那么 `shardingValue` 从哪里来呢?**CosId**!! > 当然还有很多分布式场景需要_分布式 ID_,这里不再一一列举。 ## 分布式 ID 方案的核心指标 - **全局(相同业务)唯一性**:唯一性保证是 **ID** 的必要条件,假设 ID 不唯一就会产生主键冲突,这点很容易可以理解。 - 通常所说的全局唯一性并不是指所有业务服务都要唯一,而是相同业务服务不同部署副本唯一。 比如 Order 服务的多个部署副本在生成 `t_order` 这张表的 `Id` 时是要求全局唯一的。至于 `t_order_item` 生成的 `ID` 与 `t_order` 是否唯一,并不影响唯一性约束,也不会产生什么副作用。 不同业务模块间也是同理。即唯一性主要解决的是 ID 冲突问题。 - **有序性**:有序性保证是面向查询的数据结构算法(除了 Hash 算法)所必须的,是**二分查找法** (分而治之) 的前提。 - MySq-InnoDB B + 树是使用最为广泛的,假设 Id 是无序的,B+ 树 为了维护 ID 的有序性,就会频繁的在索引的中间位置插入而挪动后面节点的位置,甚至导致频繁的页分裂,这对于性能的影响是极大的。那么如果我们能够保证 ID 的有序性这种情况就完全不同了,只需要进行追加写操作。所以 ID 的有序性是非常重要的,也是 ID 设计不可避免的特性。 - **吞吐量 / 性能 (ops/time)**:即单位时间(每秒)能产生的 ID 数量。生成 ID 是非常高频的操作,也是最为基本的。假设 ID 生成的性能缓慢,那么不管怎么进行系统优化也无法获得更好的性能。 - 一般我们会首先生成 ID,然后再执行写入操作,假设 ID 生成缓慢,那么整体性能上限就会受到限制,这一点应该不难理解。 - **稳定性 (time/op)**:稳定性指标一般可以采用**每个操作的时间进行百分位采样**来分析,比如 _[CosId](https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2FAhoo-Wang%2FCosId)_ 百分位采样 **P9999=0.208 us/op**,即 **0% ~ 99.99%** 的单位操作时间小于等于 **0.208 us/op**。 - [百分位数 WIKI](https://gitee.com/link?target=https%3A%2F%2Fzh.wikipedia.org%2Fwiki%2F%25E7%2599%25BE%25E5%2588%2586%25E4%25BD%258D%25E6%2595%25B0) :统计学术语,若将一组数据从小到大排序,并计算相应的累计百分点,则某百分点所对应数据的值,就称为这百分点的百分位数,以 Pk 表示第 k 百分位数。百分位数是用来比较个体在群体中的相对地位量数。 - 为什么不用平均_每个操作的时间_:马老师的身价跟你的身价能平均么?平均后的值有意义不? - 可以使用最小_每个操作的时间_、最大_每个操作的时间_作为参考吗?因为最小、最大值只说明了零界点的情况,虽说可以作为稳定性的参考,但依然不够全面。而且_百分位数_已经覆盖了这俩个指标。 - **自治性(依赖)**:主要是指对外部环境有无依赖,比如**号段模式**会强依赖第三方存储中间件来获取 `NexMaxId`。自治性还会对可用性造成影响。 - **可用性**:分布式 ID 的可用性主要会受到自治性影响,比如 **SnowflakeId** 会受到时钟回拨影响,导致处于短暂时间的不可用状态。而**号段模式**会受到第三方发号器(`NexMaxId`)的可用性影响。 - [可用性 WIKI](https://gitee.com/link?target=https%3A%2F%2Fzh.wikipedia.org%2Fwiki%2F%25E5%258F%25AF%25E7%2594%25A8%25E6%2580%25A7) :在一个给定的时间间隔内,对于一个功能个体来讲,总的可用时间所占的比例。 - MTBF:平均故障间隔 - MDT:平均修复 / 恢复时间 - Availability=MTBF/(MTBF+MDT) - 假设 MTBF 为 1 年,MDT 为 1 小时,即 `Availability=(365*24)/(365*24+1)=0.999885857778792≈99.99%`,也就是我们通常所说对可用性 4 个 9。 - **适应性**:是指在面对外部环境变化的自适应能力,这里我们主要说的是面对流量突发时动态伸缩分布式 ID 的性能, - **SegmentChainId** 可以基于**饥饿状态**进行**安全距离**的动态伸缩。 - **SnowflakeId** 常规位分配方案性能恒定 409.6W,虽然可以通过调整位分配方案来获得不同的 TPS 性能,但是位分配方法的变更是破坏性的,一般根据业务场景确定位分配方案后不再变更。 - **存储空间**:还是用 MySq-InnoDB B + 树来举例,普通索引(二级索引)会存储主键值,主键越大占用的内存缓存、磁盘空间也会越大。Page 页存储的数据越少,磁盘 IO 访问的次数会增加。总之在满足业务需求的情况下,尽可能小的存储空间占用在绝大多数场景下都是好的设计原则。 ### 不同分布式 ID 方案核心指标对比 | 分布式 ID | 全局唯一性 | 有序性 | 吞吐量 | 稳定性(1s=1000,000us) | 自治性 | 可用性 | 适应性 | 存储空间 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | UUID/GUID | 是 | 完全无序 | 3078638(ops/s) | P9999=0.325(us/op) | 完全自治 | 100% | 否 | 128-bit | | SnowflakeId | 是 | 本地单调递增,全局趋势递增 (受全局时钟影响) | 4096000(ops/s) | P9999=0.244(us/op) | 依赖时钟 | 时钟回拨会导致短暂不可用 | 否 | 64-bit | | SegmentId | 是 | 本地单调递增,全局趋势递增 (受 Step 影响) | 29506073(ops/s) | P9999=46.624(us/op) | 依赖第三方号段分发器 | 受号段分发器可用性影响 | 否 | 64-bit | | SegmentChainId | 是 | 本地单调递增,全局趋势递增 (受 Step、安全距离影响) | 127439148(ops/s) | P9999=0.208(us/op) | 依赖第三方号段分发器 | 受号段分发器可用性影响,但因安全距离存在,预留 ID 段,所以高于 SegmentId | 是 | 64-bit | ### 有序性 (要想分而治之・二分查找法,必须要维护我) 刚刚我们已经讨论了 ID 有序性的重要性,所以我们设计 ID 算法时应该尽可能地让 ID 是单调递增的,比如像表的自增主键那样。但是很遗憾,因全局时钟、性能等分布式系统问题,我们通常只能选择局部单调递增、全局趋势递增的组合(就像我们在分布式系统中不得不的选择最终一致性那样)以获得多方面的权衡。下面我们来看一下什么是单调递增与趋势递增。 #### 有序性之单调递增  单调递增:T 表示全局绝对时点,假设有 Tn+1>Tn(绝对时间总是往前进的,这里不考虑相对论、时间机器等),那么必然有 F (Tn+1)>F (Tn),数据库自增主键就属于这一类。 另外需要特别说明的是单调递增跟连续性递增是不同的概念。 连续性递增:`F(n+1)=(F(n)+step)` 即下一次获取的 ID 一定等于当前 `ID+Step`,当 `Step=1` 时类似于这样一个序列:`1->2->3->4->5`。 > 扩展小知识:数据库的自增主键也不是连续性递增的,相信你一定遇到过这种情况,请思考一下数据库为什么这样设计? #### 有序性之趋势递增  趋势递增:Tn>Tn-s,那么大概率有 F (Tn)>F (Tn-s)。虽然在一段时间间隔内有乱序,但是整体趋势是递增。从上图上看,是有上升趋势的(趋势线)。 - 在 **SnowflakeId** 中 n-s 受到全局时钟同步影响。 - 在号段模式 (**SegmentId**) 中 n-s 受到号段可用区间 (`Step`) 影响。 ## 分布式 ID 分配方案 ### UUID/GUID - 不依赖任何第三方中间件 - 性能高 - 完全无序 - 空间占用大,需要占用 128 位存储空间。 UUID 最大的缺陷是随机的、无序的,当用于主键时会导致数据库的主键索引效率低下(为了维护索引树,频繁的索引中间位置插入数据,而不是追加写)。这也是 UUID 不适用于数据库主键的最为重要的原因。 ### SnowflakeId  > _SnowflakeId_ 使用 `Long`(64-bit)位分区来生成 ID 的一种分布式 ID 算法。 通用的位分配方案为:`timestamp`(41-bit)+`machineId`(10-bit)+`sequence`(12-bit)=63-bit。 - 41-bit`timestamp`\=(1L<<41)/(1000/3600/365),约可以存储 69 年的时间戳,即可以使用的绝对时间为 `EPOCH`+69 年,一般我们需要自定义 `EPOCH` 为产品开发时间,另外还可以通过压缩其他区域的分配位数,来增加时间戳位数来延长可用时间。 - 10-bit`machineId`\=(1L<<10)=1024,即相同业务可以部署 1024 个副本 (在 Kubernetes 概念里没有主从副本之分,这里直接沿用 Kubernetes 的定义)。一般情况下没有必要使用这么多位,所以会根据部署规模需要重新定义。 - 12-bit`sequence`\=(1L<<12)\*1000=4096000,即单机每秒可生成约 409W 的 ID,全局同业务集群可产生 `4096000*1024=419430W=41.9亿(TPS)`。 从 _SnowflakeId_ 设计上可以看出: - `timestamp` 在高位,单实例 _SnowflakeId_ 是会保证时钟总是向前的(校验本机时钟回拨),所以是本机单调递增的。受全局时钟同步 / 时钟回拨影响 _SnowflakeId_ 是全局趋势递增的。 - _SnowflakeId_ 不对任何第三方中间件有强依赖关系,并且性能也非常高。 - 位分配方案可以按照业务系统需要灵活配置,来达到最优使用效果。 - 强依赖本机时钟,潜在的时钟回拨问题会导致 ID 重复、处于短暂的不可用状态。 - `machineId` 需要手动设置,实际部署时如果采用手动分配 `machineId`,会非常低效。 #### SnowflakeId 之机器号分配问题 在 **SnowflakeId** 中根据业务设计的位分配方案确定了基本上就不再有变更了,也很少需要维护。但是 `machineId` 总是需要配置的,而且集群中是不能重复的,否则分区原则就会被破坏而导致 ID 唯一性原则破坏,当集群规模较大时 `machineId` 的维护工作是非常繁琐,低效的。 > 有一点需要特别说明的,**SnowflakeId** 的 **MachineId** 是逻辑上的概念,而不是物理概念。 想象一下假设 **MachineId** 是物理上的,那么意味着一台机器拥有只能拥有一个 **MachineId**,那会产生什么问题呢? > 目前 _[CosId](https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2FAhoo-Wang%2FCosId)_ 提供了以下五种 `MachineId` 分配器。 - ManualMachineIdDistributor: 手动配置 `machineId`,一般只有在集群规模非常小的时候才有可能使用,不推荐。 - StatefulSetMachineIdDistributor: 使用 `Kubernetes` 的 `StatefulSet` 提供的稳定的标识 ID(HOSTNAME=service-01)作为机器号。 - RedisMachineIdDistributor: 使用 **Redis** 作为机器号的分发存储,同时还会存储 `MachineId` 的上一次时间戳,用于**启动时时钟回拨**的检查。 - JdbcMachineIdDistributor: 使用**关系型数据库**作为机器号的分发存储,同时还会存储 `MachineId` 的上一次时间戳,用于**启动时时钟回拨**的检查。 - ZookeeperMachineIdDistributor: 使用 **ZooKeeper** 作为机器号的分发存储,同时还会存储 `MachineId` 的上一次时间戳,用于**启动时时钟回拨**的检查。   #### SnowflakeId 之时钟回拨问题 时钟回拨的致命问题是会导致 ID 重复、冲突(这一点不难理解),ID 重复显然是不能被容忍的。 在 **SnowflakeId** 算法中,按照 **MachineId** 分区 ID,我们不难理解的是不同 **MachineId** 是不可能产生相同 ID 的。所以我们解决的时钟回拨问题是指当前 **MachineId** 的时钟回拨问题,而不是所有集群节点的时钟回拨问题。 **MachineId** 时钟回拨问题大体可以分为俩种情况: - 运行时时钟回拨:即在运行时获取的当前时间戳比上一次获取的时间戳小。这个场景的时钟回拨是很容易处理的,一般 **SnowflakeId** 代码实现时都会存储 `lastTimestamp` 用于运行时时钟回拨的检查,并抛出时钟回拨异常。 - 时钟回拨时直接抛出异常是不太好地实践,因为下游使用方几乎没有其他处理方案(噢,我还能怎么办呢,等吧),时钟同步是唯一的选择,当只有一种选择时就不要再让用户选择了。 - `ClockSyncSnowflakeId` 是 `SnowflakeId` 的包装器,当发生时钟回拨时会使用 `ClockBackwardsSynchronizer` 主动等待时钟同步来重新生成 ID,提供更加友好的使用体验。 - 启动时时钟回拨:即在启动服务实例时获取的当前时钟比上次关闭服务时小。此时的 `lastTimestamp` 是无法存储在进程内存中的。当获取的外部存储的**机器状态**大于当前时钟时钟时,会使用 `ClockBackwardsSynchronizer` 主动同步时钟。 - LocalMachineStateStorage:使用本地文件存储 `MachineState`(机器号、最近一次时间戳)。因为使用的是本地文件所以只有当实例的部署环境是稳定的,`LocalMachineStateStorage` 才适用。 - RedisMachineIdDistributor:将 `MachineState` 存储在 **Redis** 分布式缓存中,这样可以保证总是可以获取到上次服务实例停机时**机器状态**。 #### SnowflakeId 之 JavaScript 数值溢出问题 `JavaScript` 的 `Number.MAX_SAFE_INTEGER` 只有 53-bit,如果直接将 63 位的 `SnowflakeId` 返回给前端,那么会产生值溢出的情况(所以这里我们应该知道后端传给前端的 `long` 值溢出问题,**迟早**会出现,只不过 SnowflakeId 出现得更快而已)。 很显然溢出是不能被接受的,一般可以使用以下俩种处理方案: - 将生成的 63-bit`SnowflakeId` 转换为 `String` 类型。 - 直接将 `long` 转换成 `String`。 - 使用 `SnowflakeFriendlyId` 将 `SnowflakeId` 转换成比较友好的字符串表示:`{timestamp}-{machineId}-{sequence} -> 20210623131730192-1-0` - 自定义 `SnowflakeId` 位分配来缩短 `SnowflakeId` 的位数(53-bit)使 `ID` 提供给前端时不溢出 - 使用 `SafeJavaScriptSnowflakeId`(`JavaScript` 安全的 `SnowflakeId`) ## 号段模式(SegmentId)  从上面的设计图中,不难看出**号段模式**基本设计思路是通过每次获取一定长度(Step)的可用 ID(Id 段 / 号段),来降低网络 IO 请求次数,提升性能。 - 强依赖第三方号段分发器,可用性受到第三方分发器影响。 - 每次号段用完时获取 `NextMaxId` 需要进行网络 IO 请求,此时的性能会比较低。 - 单实例 ID 单调递增,全局趋势递增。 - 从设计图中不难看出 **Instance 1** 每次获取的 `NextMaxId`,一定比上一次大,意味着下一次的号段一定比上一次大,所以从单实例上来看是单调递增的。 - 多实例各自持有的不同的号段,意味着同一时刻不同实例生成的 ID 是乱序的,但是整体趋势的递增的,所以全局趋势递增。 - ID 乱序程度受到 Step 长度以及集群规模影响(从趋势递增图中不难看出)。 - 假设集群中只有一个实例时**号段模式**就是单调递增的。 - `Step` 越小,乱序程度越小。当 `Step=1` 时,将无限接近单调递增。需要注意的是这里是无限接近而非等于单调递增,具体原因你可以思考一下这样一个场景: - 号段分发器 T1 时刻给 **Instance 1** 分发了 `ID=1`,T2 时刻给 **Instance 2** 分发了 `ID=2`。因为机器性能、网络等原因,`Instance 2` 网络 IO 写请求先于 `Instance 1` 到达。那么这个时候对于数据库来说,ID 依然是乱序的。 ## 号段链模式(SegmentChainId) [分布式 ID (CosId) 之号段链模式性能 (1.2 亿 /s) 解析](https://gitee.com/link?target=https%3A%2F%2Fcosid.ahoo.me%2Fguide%2Fsegment-chain.html)  **SegmentChainId** 是 **SegmentId** 增强版,相比于 **SegmentId** 有以下优势: - 稳定性:**SegmentId** 的稳定性问题(P9999=46.624 (us/op))主要是因为号段用完之后同步进行 `NextMaxId` 的获取导致的(会产生网络 IO)。 - **SegmentChainId** (P9999=0.208 (us/op))引入了新的角色 **PrefetchWorker** 用以维护和保证**安全距离**,理想情况下使得获取 ID 的线程几乎完全不需要进行同步的等待 `NextMaxId` 获取,性能可达到近似 `AtomicLong` 的 _TPS 性能:12743W+/s_ [JMH 基准测试](https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2FAhoo-Wang%2FCosId%2Fblob%2Fmain%2FREADME.zh-CN.md%23jmh-benchmark) 。 - 适应性:从 **SegmentId** 介绍中我们知道了影响 **ID 乱序**的因素有俩个:集群规模、`Step` 大小。集群规模是我们不能控制的,但是 `Step` 是可以调节的。 - `Step` 应该近可能小才能使得 **ID 单调递增**的可能性增大。 - `Step` 太小会影响吞吐量,那么我们如何合理设置 `Step` 呢?答案是我们无法准确预估所有时点的吞吐量需求,那么最好的办法是吞吐量需求高时,Step 自动增大,吞吐量低时 Step 自动收缩。 - **SegmentChainId** 引入了**饥饿状态**的概念,**PrefetchWorker** 会根据**饥饿状态**检测当前**安全距离**是否需要膨胀或者收缩,以便获得吞吐量与有序性之间的权衡,这便是 **SegmentChainId** 的自适应性。 ## 集成 ### CosIdPlugin(MyBatis 插件) > Kotlin DSL **implementation**("me.ahoo.cosid:cosid-mybatis:${cosidVersion}") **public** **class** **Order** { @CosId(value \= "order") **private** **Long** orderId; **private** **Long** userId; **public** **Long** **getOrderId**() { **return** orderId; } **public** **void** **setOrderId**(**Long** orderId) { **this**.orderId \= orderId; } **public** **Long** **getUserId**() { **return** userId; } **public** **void** **setUserId**(**Long** userId) { **this**.userId \= userId; } } ### ShardingSphere 插件 > `CosIdKeyGenerateAlgorithm`、`CosIdModShardingAlgorithm`、`CosIdIntervalShardingAlgorithm` 已合并至 [ShardingSphere](https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2Fapache%2Fshardingsphere%2Fpull%2F14132) 官方,未来 _[cosid-shardingsphere](https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2FAhoo-Wang%2FCosId%2Ftree%2Fmain%2Fcosid-shardingsphere)_ 模块的维护可能会以官方为主。 > Kotlin DSL **implementation**("me.ahoo.cosid:cosid-shardingsphere:${cosidVersion}") #### CosIdKeyGenerateAlgorithm (分布式主键) spring: shardingsphere: rules: sharding: key-generators: cosid: type: COSID props: id-name: \_\_share\_\_ #### 基于间隔的时间范围分片算法  - 易用性:支持多种数据类型 (`Long`/`LocalDateTime`/`DATE`/ `String` / `SnowflakeId`),而官方实现是先转换成字符串再转换成 `LocalDateTime`,转换成功率受时间格式化字符影响。 - 性能:相比于 `org.apache.shardingsphere.sharding.algorithm.sharding.datetime.IntervalShardingAlgorithm` 性能高出 _1200~4000_ 倍。 | **PreciseShardingValue** | **RangeShardingValue** | | --- | --- | |  |  | - CosIdIntervalShardingAlgorithm - SnowflakeIntervalShardingAlgorithm - type: COSID\_INTERVAL\_SNOWFLAKE spring: shardingsphere: rules: sharding: sharding-algorithms: alg-name: type: COSID\_INTERVAL\_{type\_suffix} props: logic-name-prefix: logic-name-prefix id-name: cosid-name datetime-lower: 2021\-12\-08 22:00:00 datetime-upper: 2022\-12\-01 00:00:00 sharding-suffix-pattern: yyyyMM datetime-interval-unit: MONTHS datetime-interval-amount: **1** #### 取模分片算法  - 性能:相比于 `org.apache.shardingsphere.sharding.algorithm.sharding.mod.ModShardingAlgorithm` 性能高出 _1200~4000_ 倍。并且稳定性更高,不会出现严重的性能退化。 | **PreciseShardingValue** | **RangeShardingValue** | | --- | --- | |  |  | spring: shardingsphere: rules: sharding: sharding-algorithms: alg-name: type: COSID\_MOD props: mod: **4** logic-name-prefix: t\_table\_ ## 性能测试报告 ### SegmentChainId - 吞吐量 (ops/s)  ### SegmentChainId - 每次操作耗时的百分位数 (us/op) > [百分位数](https://gitee.com/link?target=https%3A%2F%2Fzh.wikipedia.org%2Fwiki%2F%25E7%2599%25BE%25E5%2588%2586%25E4%25BD%258D%25E6%2595%25B0) ,统计学术语,若将一组数据从小到大排序,并计算相应的累计百分点,则某百分点所对应数据的值,就称为这百分点的百分位数,以 Pk 表示第 k 百分位数。百分位数是用来比较个体在群体中的相对地位量数。  ### CosId VS 美团 Leaf > CosId (`SegmentChainId`) 性能是 Leaf (`segment`) 的 5 倍。 
admin
2024年2月24日 22:29
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码