Apache Seata基于改良版雪花算法的分布式UUID生成器分析1

2024-04-30 09:04

本文主要是介绍Apache Seata基于改良版雪花算法的分布式UUID生成器分析1,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


title: Seata基于改良版雪花算法的分布式UUID生成器分析
author: selfishlover
keywords: [Seata, snowflake, UUID]
date: 2021/05/08

本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。

Seata基于改良版雪花算法的分布式UUID生成器分析

Seata内置了一个分布式UUID生成器,用于辅助生成全局事务ID和分支事务ID。我们希望该生成器具有如下特点:

  • 高性能
  • 全局唯一
  • 趋势递增

高性能不必多言。全局唯一很重要,否则不同的全局事务/分支事务会混淆在一起。
此外,趋势递增对于使用数据库作为TC集群的存储工具的用户而言,能降低数据页分裂的频率,从而减少数据库的IO压力
(branch_table表以分支事务ID作为主键)。

在老版Seata(1.4以前),该生成器的实现基于标准版的雪花算法。标准版雪花算法网上已经有很多解读文章了,此处就不再赘述了。
尚未了解的同学可以先看看网上的相关资料,再来看此文章。
此处我们谈谈标准版雪花算法的几个缺点:

  1. 时钟敏感。因为ID生成总是和当前操作系统的时间戳绑定的(利用了时间的单调递增性),因此若操作系统的时钟出现回拨,
    生成的ID就会重复(一般而言不会人为地去回拨时钟,但服务器会有偶发的"时钟漂移"现象)。
    对于此问题,Seata的解决策略是记录上一次的时间戳,若发现当前时间戳小于记录值(意味着出现了时钟回拨),则拒绝服务,
    等待时间戳追上记录值。 但这也意味着这段时间内该TC将处于不可用状态。
  2. 突发性能有上限。标准版雪花算法宣称的QPS很大,约400w/s,但严格来说这算耍了个文字游戏~
    因为算法的时间戳单位是毫秒,而分配给序列号的位长度为12,即每毫秒4096个序列空间。
    所以更准确的描述应该是4096/ms。400w/s与4096/ms的区别在于前者不要求每一毫秒的并发都必须低于4096
    (也许有些毫秒会高于4096,有些则低于)。Seata亦遵循此限制,若当前时间戳的序列空间已耗尽,会自旋等待下一个时间戳。

在较新的版本上(1.4之后),该生成器针对原算法进行了一定的优化改良,很好地解决了上述的2个问题。
改进的核心思想是解除与操作系统时间戳的时刻绑定,生成器只在初始化时获取了系统当前的时间戳,作为初始时间戳,
但之后就不再与系统时间戳保持同步了。它之后的递增,只由序列号的递增来驱动。比如序列号当前值是4095,下一个请求进来,
序列号+1溢出12位空间,序列号重新归零,而溢出的进位则加到时间戳上,从而让时间戳+1。
至此,时间戳和序列号实际可视为一个整体了。实际上我们也是这样做的,为了方便这种溢出进位,我们调整了64位ID的位分配策略,
由原版的:
在这里插入图片描述

改成(即时间戳和节点ID换个位置):
在这里插入图片描述

这样时间戳和序列号在内存上是连在一块的,在实现上就很容易用一个AtomicLong来同时保存它俩:

/*** timestamp and sequence mix in one Long* highest 11 bit: not used* middle  41 bit: timestamp* lowest  12 bit: sequence*/
private AtomicLong timestampAndSequence;

最高11位可以在初始化时就确定好,之后不再变化:

/*** business meaning: machine ID (0 ~ 1023)* actual layout in memory:* highest 1 bit: 0* middle 10 bit: workerId* lowest 53 bit: all 0*/
private long workerId;

那么在生产ID时就很简单了:

public long nextId() {// 获得递增后的时间戳和序列号long next = timestampAndSequence.incrementAndGet();// 截取低53位long timestampWithSequence = next & timestampAndSequenceMask;// 跟先前保存好的高11位进行一个或的位运算return workerId | timestampWithSequence;
}

至此,我们可以发现:

  1. 生成器不再有4096/ms的突发性能限制了。倘若某个时间戳的序列号空间耗尽,它会直接推进到下一个时间戳,
    "借用"下一个时间戳的序列号空间(不必担心这种"超前消费"会造成严重后果,下面会阐述理由)。
  2. 生成器弱依赖于操作系统时钟。在运行期间,生成器不受时钟回拨的影响(无论是人为回拨还是机器的时钟漂移),
    因为生成器仅在启动时获取了一遍系统时钟,之后两者不再保持同步。
    唯一可能产生重复ID的只有在重启时的大幅度时钟回拨(人为刻意回拨或者修改操作系统时区,如北京时间改为伦敦时间~
    机器时钟漂移基本是毫秒级的,不会有这么大的幅度)。
  3. 持续不断的"超前消费"会不会使得生成器内的时间戳大大超前于系统的时间戳, 从而在重启时造成ID重复?
    理论上如此,但实际几乎不可能。要达到这种效果,意味该生成器接收的QPS得持续稳定在400w/s之上~
    说实话,TC也扛不住这么高的流量,所以说呢,天塌下来有个子高的先扛着,瓶颈一定不在生成器这里。

此外,我们还调整了下节点ID的生成策略。原版在用户未手动指定节点ID时,会截取本地IPv4地址的低10位作为节点ID。
在实践生产中,发现有零散的节点ID重复的现象(多为采用k8s部署的用户)。例如这样的IP就会重复:

  • 192.168.4.10
  • 192.168.8.10

即只要IP的第4个字节和第3个字节的低2位一样就会重复。
新版的策略改为优先从本机网卡的MAC地址截取低10位,若本机未配置有效的网卡,则在[0, 1023]中随机挑一个作为节点ID。
这样调整后似乎没有新版的用户再报同样的问题了(当然,有待时间的检验,不管怎样,不会比IP截取策略更糟糕)。

以上就是对Seata的分布式UUID生成器的简析,如果您喜欢这个生成器,也可以直接在您的项目里使用它,
它的类声明是public的,完整类名为:
io.seata.common.util.IdWorker

当然,如果您有更好的点子,也欢迎跟Seata社区讨论。

这篇关于Apache Seata基于改良版雪花算法的分布式UUID生成器分析1的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/948442

相关文章

Python中的Walrus运算符分析示例详解

《Python中的Walrus运算符分析示例详解》Python中的Walrus运算符(:=)是Python3.8引入的一个新特性,允许在表达式中同时赋值和返回值,它的核心作用是减少重复计算,提升代码简... 目录1. 在循环中避免重复计算2. 在条件判断中同时赋值变量3. 在列表推导式或字典推导式中简化逻辑

C#使用StackExchange.Redis实现分布式锁的两种方式介绍

《C#使用StackExchange.Redis实现分布式锁的两种方式介绍》分布式锁在集群的架构中发挥着重要的作用,:本文主要介绍C#使用StackExchange.Redis实现分布式锁的... 目录自定义分布式锁获取锁释放锁自动续期StackExchange.Redis分布式锁获取锁释放锁自动续期分布式

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

深入理解Apache Kafka(分布式流处理平台)

《深入理解ApacheKafka(分布式流处理平台)》ApacheKafka作为现代分布式系统中的核心中间件,为构建高吞吐量、低延迟的数据管道提供了强大支持,本文将深入探讨Kafka的核心概念、架构... 目录引言一、Apache Kafka概述1.1 什么是Kafka?1.2 Kafka的核心概念二、Ka

openCV中KNN算法的实现

《openCV中KNN算法的实现》KNN算法是一种简单且常用的分类算法,本文主要介绍了openCV中KNN算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录KNN算法流程使用OpenCV实现KNNOpenCV 是一个开源的跨平台计算机视觉库,它提供了各

Python 迭代器和生成器概念及场景分析

《Python迭代器和生成器概念及场景分析》yield是Python中实现惰性计算和协程的核心工具,结合send()、throw()、close()等方法,能够构建高效、灵活的数据流和控制流模型,这... 目录迭代器的介绍自定义迭代器省略的迭代器生产器的介绍yield的普通用法yield的高级用法yidle

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java