在高并发、高性能、高可用 三高项目中如何设计适合实际业务场景的分布式id(一)(2)

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

有序id能提升写入性能的原因

在深入了解有序ID如何影响InnoDB存储引擎的写入性能之前,我们首先需要理解InnoDB的聚簇索引结构以及数据页分裂的概念。

InnoDB的聚簇索引

InnoDB存储引擎使用聚簇索引来组织表中的数据。聚簇索引定义了数据在磁盘上的物理存储顺序。通常,InnoDB表会根据主键(如果存在)自动创建一个聚簇索引。如果没有明确定义主键,InnoDB会选择一个唯一的非空索引代替。如果这样的索引也不存在,InnoDB会生成一个隐藏的、包含6字节的ROWID来作为聚簇索引。

在聚簇索引中,数据实际上是存储在索引的叶子节点上的。这意味着,当你通过主键查询数据时,InnoDB可以直接在索引中找到相应的数据,而无需进行额外的磁盘I/O操作。这就是所谓的“覆盖索引”查询,它可以大大提高查询性能。

数据页分裂

然而,聚簇索引的一个潜在缺点是它可能导致数据页分裂。当向表中插入新的数据时,如果新数据的主键值位于某个已有数据页的中间位置,InnoDB就需要为该新数据腾出空间。这通常意味着它需要将该数据页的一部分数据移动到其他数据页上,以便为新数据腾出空间。这个过程就是所谓的“数据页分裂”。

那什么是数据分页呢? lnnoDB 不是按行来操作数据的,它可操作的最小单位是页,页加载进内存后才会通过扫描页来获取行记录比如查询 id=1,是获取 1所在的数据页,加载进内存后取出1这一行。

页的默认大小为16KB,64个连续的数据页称为一个extent(区),64个页组成一个区,所以区的大小为1MB(16*64=1024),连续的256个数据区称为一组数据区。

数据页分裂是一个相对昂贵的操作,因为它涉及到数据的移动和可能的磁盘I/O操作。在高并发的写入密集型场景中,频繁的数据页分裂可能会导致性能下降。两个数据页之间会有指针指向上一个和下一个数据页,形成一个双向链表,数据页中的每个数据行之间会有单向指针连接,组成个单向链表。

在这里插入图片描述

上述就是数据页的结构, 首先两个数据页之间会有指针指向上一个和下一个数据页,形成一个双向链表, 在数据页中存的就是一行行的数据,每个数据之间会单向指针连接, 组成一个单向链表。

当一个数据页中的数据行太多放不下的时候就会生成一个新的数据页来存储, 同时使用双向链表来相连; 使用索引时,一个最基本的条件是后面数据中的数据行的主键值要大于前一个数据页中数据行的主键值。

当我们使用索引的时候,其中最基础的条件就是后面数据页中的数据行的主键值需要大于前一个数据页中数据行的主键值。索引呢,就是一遍一遍过筛子, 通过二分法的逻辑不断减少要筛选的数据,而真实数据是按主键顺序存储的, 所以主键值就成了筛选标准,以便尽快定位我们需要的数据,其时间复杂度O(logn)。

如果我们设置的主键是乱序的, 就有可能会导致数据页中的主键值大小不能满足索引使用条件。所以就会要求主键必须有序。

如果值有序,但是插入的数据不是递增的,此时就会产生页分裂, 如下图的数据页:

在这里插入图片描述

可以发现后面数据页里的主键值比前一个数据页的主键值小, 里面的数据就会进行数据挪动,那这就是我们所说的页分裂。

在这里插入图片描述

通过页分裂,我们只要将主键为2的数据行与主键值为4的数据行互相挪动一下位置,就可以保证后面一个数据页的主键值比前一个数据页中的主键值大了,

为了更清晰地理解页分裂,我们可以将其步骤概括为:

  1. 检查空间:当InnoDB尝试插入新的数据时,它首先会检查当前数据页是否有足够的空间来容纳新数据。
  2. 分裂决策:如果当前页没有足够的空间,InnoDB就会决定进行页分裂。它会创建一个新的数据页,并将原数据页中的一部分数据(通常是中位数附近的数据)移动到新页中,以确保新插入的数据可以放在合适的位置。
  3. 数据移动:实际的数据移动过程涉及将原数据页中的一部分行复制到新页中,并更新相关的索引和指针以反映这种变化。这可能涉及到多个数据页的调整,以确保数据的连续性和索引的正确性。
  4. 更新链接:InnoDB会更新数据页之间的双向链表指针,以确保分裂后的数据页仍然按照正确的顺序链接在一起。同时,它也会更新索引结构以反映新数据页的存在和位置。
  5. 插入新数据:一旦页分裂完成,InnoDB就可以在新的位置插入新数据了。这通常是在分裂后留下的空间中进行的。

需要注意的是,页分裂不仅发生在插入操作中。当更新操作导致行的大小增加,使得当前页无法容纳时,也可能发生页分裂。同样地,删除操作可能导致页的合并,以释放空间并提高存储效率。

为了减少页分裂的频率和提高写入性能,可以采取以下策略:

  • 有序插入:如您所述,通过保持插入数据的顺序性(如使用自增主键),可以减少页分裂的次数。这是因为有序插入可以使得新数据总是被添加到索引的末尾,从而避免了在中间位置插入数据所需的复杂操作。
  • 批量插入:将多个插入操作组合成一个批量插入操作可以减少单个插入操作的开销,并提高整体的写入性能。这可以通过使用InnoDB的批量插入优化来实现。
  • 调整页大小:虽然InnoDB的默认页大小是16KB,但在某些情况下,调整页大小可能有助于优化性能。然而,这需要谨慎操作,因为页大小的更改会影响到整个数据库的存储和性能特性。
  • 优化索引设计:通过合理设计索引和使用覆盖索引等技术,可以减少不必要的数据页访问和I/O操作,从而提高写入性能并减少页分裂的可能性。

所以,其结论就是主键值最好是有序的, 不仅可以不用页分裂,还能充分使用到索引。否则必须进行页分裂来保证索引的使用。

有序ID如何帮助减少数据页分裂

所以有序ID能提升写入性能的根本原因在于它们可以减少数据页分裂的次数。

当主键值是递增的(或至少是有序的)时,新插入的数据总是被添加到索引的末尾。这意味着InnoDB可以简单地分配一个新的数据页来存储新数据,而无需对现有数据页进行分裂。这大大减少了写入操作的复杂性,提高了性能。

然而,需要注意的是,完全有序的ID插入并不总是可能的或理想的。在某些场景中(如多主复制或合并多个数据源时),你可能无法控制ID的生成顺序。此外,即使你可以控制ID的生成顺序,也可能出于安全或业务原因而选择使用无序的ID。在这些情况下,你可能需要采取其他策略来优化写入性能,如使用批量插入、调整InnoDB的配置参数或考虑使用其他存储引擎。

超高并发、超高性能分布式ID生成系统三个超高

设计一个超高性能、超高并发且超低延迟的分布式ID生成系统是许多大型系统和微服务架构中的关键组件。这样的系统不仅需要生成全局唯一的ID,还要保证在极高的请求压力下仍能保持稳定的性能。

关键点:

以下是一些设计这样的系统时需要考虑的关键点:

超低延迟

要求:1 秒可处理 10W 并发请求,接口响应时间 5 ms 。

  • 算法选择:选择计算简单、性能高效的ID生成算法。例如,Snowflake算法就是一种常见的选择,它能够在不牺牲全局唯一性的情况下快速生成ID。
  • 缓存和预分配:通过缓存或预分配ID来减少生成ID时的计算延迟。例如,可以预先为每个服务实例分配一批ID,当实例需要生成ID时,直接从这批ID中取一个即可。
  • 减少网络开销:如果ID生成服务是一个独立的服务,那么网络延迟也是一个需要考虑的因素。可以通过将ID生成服务部署在靠近用户的位置或使用更高效的网络协议来减少网络延迟。
超高可用
  • 冗余部署:通过部署多个ID生成服务实例来提供冗余,确保即使部分实例发生故障,系统仍能继续生成ID。
  • 故障切换:实现故障检测和自动切换机制,当检测到某个实例故障时,自动将其从服务池中移除,并将请求路由到其他健康的实例。
  • 数据持久化:如果ID生成算法依赖于某些状态(如Snowflake中的时间戳和序列号),那么需要确保这些状态在故障转移时能够持久化并正确恢复。
超高并发
  • 水平扩展:通过增加更多的ID生成服务实例来分散负载,提高系统的并发处理能力。这通常需要一个无状态的ID生成算法或一种有效的状态同步机制。
  • 负载均衡:使用负载均衡器将请求均匀分配到各个ID生成服务实例上,避免单点压力过大。
  • 优化锁和同步:如果ID生成算法中涉及到锁或同步操作,需要对其进行优化以减少争用和等待时间。例如,可以使用分段锁或乐观锁等技术来减少锁的范围和持有时间。
  • 异步处理:将ID生成过程与其他业务逻辑解耦,采用异步方式生成ID,避免阻塞主线程或关键路径。

最后,达到如滴滴的tinyid那样的千万QPS级别的性能,通常需要结合具体的业务场景和系统架构进行深度的定制和优化。这可能包括使用专门的硬件、优化网络拓扑、调整操作系统和数据库配置等多个层面的工作。同时,还需要通过严格的性能测试和监控来确保系统在实际运行中能够达到预期的性能目标。

发展阶段

确实,随着企业业务的发展和系统复杂性的增加,ID生成服务经历了从各自封装到集成框架,再到独立服务的演进过程。下面我将详细解释ID生成服务在企业级使用场景中的各个阶段及其特点。

第一阶段:各自封装

在企业早期,各个系统或模块通常根据自己的需要实现ID生成逻辑。这些实现可能包括基于数据库自增ID、UUID、雪花算法(Snowflake)等。这种方式的优点是简单直接,但缺点是实现分散,难以统一管理和保证质量。此外,不同的ID生成策略可能导致ID冲突或不一致性,增加了系统间集成的复杂性。

第二阶段:集成框架

为了解决分散实现的问题,企业可能会开发一个统一的ID生成基础库,将各种ID生成逻辑集成到一个框架中。这样,业务方可以通过调用这个基础库来生成ID,而无需关心底层的实现细节。然而,对于像Snowflake这样需要分配worker ID的算法,业务系统仍然需要关注worker ID的分配逻辑。因此,有些企业会将Snowflake的逻辑封装到服务治理框架中,由框架负责worker ID的分配和服务内的唯一性。这种方式提高了ID生成的统一性和可管理性,但仍然存在一定的状态管理复杂性。

第三阶段:ID生成服务(idgen服务)

随着业务量的增长和系统稳定性的要求提高,企业需要一个更加稳定、高效且无状态的ID生成服务。因此,独立的ID生成服务应运而生。这种服务通常具有以下特点:

  1. 支持多种模式:如DB号段模式和Snowflake模式,以满足不同业务场景的需求。
  2. 高可用性和稳定性:通过冗余部署、故障切换和数据持久化等技术手段确保服务的高可用性和稳定性。同时,具备时钟校准能力以防止时钟回拨等问题导致的ID生成异常。
  3. 高吞吐量和低延迟:通过优化算法、减少网络开销和使用高性能的硬件等手段实现高吞吐量和低延迟的ID生成性能。TP99等关键指标必须非常低,以确保在极端情况下的性能稳定性。
  4. 兼容现有逻辑:为了方便业务迁移,ID生成服务需要兼容现有的ID生成逻辑。这可以通过配置化、插件化或版本控制等方式实现。
  5. 无状态部署:为了支持快速滚动升级和弹性伸缩,ID生成服务应该使用无状态部署方式(如Kubernetes中的Deployment)。这意味着服务实例之间不共享状态信息,可以独立地扩展和缩减实例数量而不影响服务的整体可用性。

通过提供独立的ID生成服务,企业可以更加灵活地满足各种业务场景的ID生成需求,同时提高系统的稳定性、可用性和性能。

DB 号段模式

DB 号段模式是一种用于生成唯一 ID 的策略,它优化了传统的数据库自增 ID 方案。在这种模式下,系统不是每次需要 ID 时都去数据库中查询和获取,而是采用批量获取的方式,定期从数据库中获取一个 ID 号段,然后将这个号段缓存在本地。当外部服务需要 ID 时,直接从本地缓存的号段中分配即可。这种方式大大减轻了数据库的压力,并提升了对外服务的性能。

本地ID生成器

本地ID生成器是指本地环境中生成唯一标识符ID的工具或算法, 本地ID生成器通常在单个进程或机器内部生成ID,不需要网络I/O,因此性能较高。常见的本地ID生成策略包括:

  • 自增ID:例如,使用数据库的自增主键。但这种方法在分布式环境中不可行,因为不同的机器可能生成相同的ID。
  • UUID(通用唯一标识符):UUID是基于时间和机器节点(通常是MAC地址)等信息生成的,具有很高的唯一性。但UUID较长且不易读,也不支持排序。
  • 雪花算法(Snowflake):这是一种分布式ID生成算法,但通过一些技巧(如时间戳、机器ID和序列号)在本地生成ID,同时保证了全局唯一性和有序性。

UUID

UUID是一种本地生成ID的方式,UUID(Universally Unique Identifier,通用唯一标识符)是一种标准的128位数字,用于在计算机系统中唯一地标识信息。它由一组特定的算法生成,可以确保在全球范围内生成的每个UUID都是独一无二的。

UUID的标准形式通常包含32个16进制数字,分为五段,形式为8-4-4-4-12的36个字符,其中包含了四个连字符“-”。这种格式的设计使得UUID既易于人类阅读和记录,又能够包含足够的信息以确保其唯一性。

UUID版本区别

  1. Version 1:基于时间戳和MAC地址生成。由于包含了时间信息,因此Version 1的UUID是有序的,并且可以在一定程度上反映生成时间。但是,由于依赖于MAC地址,如果MAC地址被篡改或不可用,可能会导致UUID的唯一性受到影响。
  2. Version 2:与Version 1类似,但还包含了POSIX UID/GID信息。这使得Version 2的UUID在某些特定的分布式环境中更加有用。然而,由于同样依赖于MAC地址和时间戳,因此也存在与Version 1相同的问题。
  3. Version 3:基于MD5哈希算法生成。通过对指定的命名空间(namespace)和名称(name)进行MD5哈希运算来生成UUID。这使得Version 3的UUID具有更好的唯一性和安全性。然而,由于MD5算法已知存在弱点,因此不推荐在安全性要求较高的场景中使用。
  4. Version 4:完全随机生成。Version 4的UUID不依赖于任何特定的信息或算法,而是通过随机数生成器来生成。这使得Version 4的UUID具有极高的唯一性和安全性。然而,由于是随机生成的,因此Version 4的UUID是无序的。
  5. Version 5:基于SHA-1哈希算法生成。与Version 3类似,但使用了更安全的SHA-1哈希算法来代替MD5。这使得Version 5的UUID在安全性方面更加可靠。同样地,由于是基于哈希算法生成的,因此Version 5的UUID也是无序的。

UUID的主要优点包括:

  1. 全局唯一性:UUID的生成算法基于多种信息,如时间戳、计算机的唯一标识符(如MAC地址)以及随机数等,以确保生成的标识符在实践中具有高度的唯一性。虽然UUID的概率冲突非常低,但并不能保证绝对的唯一性。然而,在实际应用中,UUID的冲突几乎可以忽略不计。
  2. 无需中央协调机构:UUID的生成是分布式的,不需要中央协调机构来管理或分配ID。这使得UUID非常适合在分布式系统中使用,其中每个节点都可以独立地生成ID,而无需与其他节点进行通信或协调。
  3. 灵活性:UUID提供了多种版本来满足不同的需求。例如,Version 1和Version 2基于时间和MAC地址生成有序的UUID;Version 3和Version 5基于哈希算法生成与特定命名空间相关的UUID;而Version 4则是完全随机的,适用于安全性要求较高的场景。

然而,UUID也存在一些缺点

  1. 存储效率:UUID的字符串表示形式相对较长,占用的存储空间较大。虽然可以使用二进制格式来减少存储需求,但这会增加处理的复杂性。
  2. 可读性:UUID是一长串字符,对于人类来说不易于阅读和记忆。这可能会影响调试和日志分析等方面的便利性。为了解决这个问题,可以将UUID与更具可读性的标识符(如数据库中的主键或业务逻辑中的实体名称)进行关联。
  3. 无序性:由于UUID是基于多种信息生成的,因此它们是无序的。在数据库中按照UUID排序可能会导致性能下降。为了解决这个问题,可以在需要排序的场景中使用其他类型的ID(如自增ID或时间戳)。

总的来说,UUID是一种非常有用的工具,可以在分布式系统中生成全局唯一的标识符。它的优点在于全局唯一性、无需中央协调机构和灵活性;而缺点则在于存储效率、可读性和无序性。在使用UUID时,需要根据具体的应用场景和需求来权衡这些优缺点。

UUID在实际应用中确实可能面临一些问题和挑战。以下是一些主要的考虑点:

  1. 存储和性能: UUID是128位的标识符,通常以36个字符(包括4个连字符)的字符串形式表示。相比于较小的整数型主键,UUID占用更多的存储空间,并可能导致索引效率降低,特别是在数据库环境中。例如,在InnoDB存储引擎中,主键索引(聚集索引)与数据紧密关联,无序的UUID主键可能导致频繁的页分裂和随机I/O,从而影响性能。
  2. 可读性和可调试性: UUID的随机性和长度使得它们对人类来说难以阅读和记忆。这在调试、日志记录和错误跟踪时可能会增加复杂性。
  3. 生成策略: 不同的UUID版本有不同的生成策略。Version 1和2基于时间和节点(如MAC地址)生成,可能在某种程度上泄露系统信息。Version 4是随机生成的,但完全随机的UUID在数据库插入时可能导致性能问题。选择合适的UUID版本以满足特定需求是一个挑战。
  4. 唯一性冲突: 尽管UUID的冲突概率非常低,但在极端情况下仍有可能发生。特别是在大量生成UUID的系统中,需要采取措施来检测和处理潜在的冲突。
  5. 业务逻辑整合: 在某些业务场景中,可能需要将UUID与其他业务逻辑或系统整合。例如,将UUID用作数据库主键时,可能需要考虑如何与其他表或系统进行有效的关联和查询。
  6. 安全性考虑: 如果UUID被用作安全令牌或访问控制的一部分,那么它们的随机性和不可预测性就变得至关重要。在这种情况下,需要确保使用的UUID生成算法符合安全标准,并且难以被攻击者猜测或预测。

为了缓解这些问题和挑战,可以采取一些策略,如使用二进制格式存储UUID以节省空间、优化数据库索引策略、选择适当的UUID版本以及实施冲突检测和处理机制等。此外,还可以考虑将UUID与其他标识符(如业务主键)结合使用,以平衡唯一性、可读性和性能的需求。

UUID(Universally Unique Identifier)适合在多种场景下使用,特别是那些需要全局唯一标识符的场合。以下是一些常见的使用场景:

  1. 数据库主键:在数据库中,UUID可以用作表的主键,确保每个记录具有唯一的标识符。这有助于避免冲突和重复,特别是在分布式数据库环境中。
  2. 分布式系统:在分布式系统中,UUID用于唯一标识各个节点、实体或资源。由于UUID的生成是分布式的,不需要中央协调机构,因此非常适合在分布式环境中进行准确的识别和跟踪。
  3. Web开发:在Web开发中,UUID可以用作会话标识符、临时文件名或URL的一部分,用于跟踪用户会话、生成唯一的资源标识符等。
  4. 软件开发:在软件开发中,UUID可用于生成唯一的文件名、标识插件或组件、识别对象实例等。这有助于确保软件组件的唯一性和可追踪性。
  5. 数据同步和复制:在数据同步和复制过程中,UUID可以用于标识不同数据源或副本中的记录,确保数据在多个系统之间的一致性和唯一性。

此外,UUID还适合在不需要明确时间上下文或排序的场景中使用。例如,在微服务架构中,UUID可以确保全局ID的唯一性,避免主键自增ID的一些缺陷。然而,需要注意的是,UUID并不适合作为需要频繁排序或具有明确时间顺序要求的场景中的主键,因为UUID是无序的。在这些情况下,可以考虑使用其他类型的标识符(如时间戳或自增ID)。

总之,UUID提供了一种可靠的方法来生成全局唯一的标识符,适用于分布式系统、数据库管理、软件开发以及其他需要唯一标识的场景。但在使用时,也需要根据具体的应用场景和需求来权衡其优缺点。


    @Test
    public void uuidExample(){

        //生成一个随机的UUID(第4版)
        UUID uuid = UUID.randomUUID();

        System.out.println("Generated UUID:"+uuid.toString());

        //也可以从字符串中解析UUID
        String uuidString = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
        UUID parsedUUID = UUID.fromString(uuidString);

        // 输出解析后的UUID
        System.out.println("Parsed UUID: " + parsedUUID);
    }

shortuuid

ShortUUID 是一种用于生成全局唯一标识符(GUID)的算法和格式,其特别之处在于生成的标识符比传统的 UUID(Universally Unique Identifier)更短,且长度固定为22个字符。ShortUUID 是基于 UUID Version 4 设计的,并使用了特定的 alphabet(字符集)来缩短表示长度。

组成与生成步骤

  1. 初始值

    • ShortUUID 的初始值基于 UUID Version 4。UUID Version 4 是一种基于随机数的 UUID,其生成过程中包含了足够的随机性以确保全局唯一性。
  2. Alphabet 变量长度

    • ShortUUID 使用了一个预定义的 alphabet,其长度固定为 57 个字符。这个 alphabet 通常由小写字母、大写字母和数字组成,有时还可能包含一些特殊字符,以提供足够的字符组合空间。
  3. ID 长度计算

    • 尽管 ShortUUID 的最终长度是固定的22个字符,但实际上,这个长度并不直接由 alphabet 的长度计算得出。相反,它是基于所需的唯一性级别和可接受的冲突概率来确定的。需要注意的是,将 128 位的 UUID 压缩到 22 个字符中,必然会导致一定的信息丢失和冲突风险。
  4. DivMod 映射

    • ShortUUID 使用 DivMod(欧几里得除法和模)算法来将 UUID 的数值映射到预定义的 alphabet 上。这个过程涉及将 UUID 转换为一个大整数,然后反复应用 DivMod 算法来生成一系列索引值,这些索引值随后被转换为 alphabet 中的对应字符。

特点

  • 全局唯一性:尽管 ShortUUID 比传统的 UUID 短得多,但它仍然旨在提供全局唯一性。然而,由于信息压缩,ShortUUID 的唯一性不如完整长度的 UUID。
  • 长度固定:ShortUUID 的长度固定为 22 个字符,这使得它在存储和传输时更加高效。
  • 基于 UUID:ShortUUID 是基于 UUID Version 4 设计的,因此它继承了 UUID 的一些优点,如跨平台兼容性和广泛的接受度。
  • 冲突风险:由于 ShortUUID 的长度较短且信息被压缩,因此存在比传统 UUID 更高的冲突风险。这种风险在高并发或大规模应用中尤为显著。
  • 不可逆性:ShortUUID 的生成过程是不可逆的,即无法从 ShortUUID 还原出原始的 UUID。

应用
ShortUUID 适用于那些需要唯一标识符但又希望减少存储和传输开销的场景。然而,由于其潜在的冲突风险,使用 ShortUUID 时需要谨慎评估其适用性,特别是在对唯一性要求极高的系统中。常见的应用场景包括短链接生成、内部标识符等。在这些场景中,ShortUUID 提供了一种在可接受的冲突概率下减少标识符长度的有效方法。

 public  static String  generateShortUuid(){
        StringBuffer  shortBuffer = new StringBuffer();
        String uuid = UUID.randomUUID().toString().replace("-","");
        for (int i=0;i<8;i++){
            String str = uuid.substring(i\*4,i\*4+4);
            int x = Integer.parseInt(str,16);
            shortBuffer.append(chars[x % 0x3E]);

        }
        return shortBuffer.toString();
 }

KSUID

KSUID是由Segment.io开发的一种分布式ID生成方案。它的设计目标是为了提供高性能、唯一性,并确保ID的可排序性。KSUID生成的ID是一个全局唯一的字符串,这使得它非常适用于各种需要唯一标识符的场合。

组成

  1. 时间戳(32位)

    • 使用32位来存储秒级的时间戳。
    • 表示自协调世界时(UTC)1970年1月1日以来的秒数。
    • 与传统的UNIX时间戳相比,KSUID使用了更长的时间戳,因此可以支持更长的时间范围。
  2. 随机字节(16位)

    • 这部分是为了增加ID的唯一性而随机生成的16位字节。
  3. 附加信息(可选)

    • KSUID的格式允许包含附加的信息,例如节点ID或其他标识符。
    • 这部分是可选的,具体是否使用取决于特定的应用场景和需求。

特点

  • 全局唯一性:由于KSUID结合了时间戳和随机字节,它生成的ID在全球范围内都是唯一的。
  • 可排序性:由于KSUID的ID是按照时间顺序生成的,因此它们可以很方便地按照生成的顺序进行排序和比较。
  • 去中心化:KSUID不依赖于任何中央化的ID生成服务,这使得它在分布式系统中特别有用。
  • 高性能:KSUID的生成算法设计得非常简单和高效,确保在高并发环境下也能快速生成ID。

应用
KSUID广泛应用于需要全局唯一标识符的各种场景,特别是那些要求ID具有可排序性的场合。例如,在分布式数据库、日志记录、消息队列等领域,KSUID都是一个非常有用的工具。

XID

XID是一个用于生成全局唯一标识符(GUID)的库。它采用基于时间的、分布式的ID生成算法,旨在确保高性能和唯一性。XID生成的ID是一个64位的整数,由时间戳、机器ID和序列号三部分组成。

XID的组成

  1. 时间戳(40位)

    • 使用40位存储纳秒级的时间戳。
    • 支持约34年的时间范围。
    • 与雪花算法相比,具有更高的时间分辨率。
  2. 机器ID(16位)

    • 用于表示分布式系统中机器的唯一标识符。
    • 每个机器应具有唯一的机器ID,可以通过手动配置或自动分配获得。
  3. 序列号(8位)

    • 在同一纳秒内生成的序列号。
    • 如果在同一纳秒内生成的ID数量超过了8位能够表示的范围,会等待下一纳秒再生成ID。

XID的特点

  1. 长度短:生成的ID是一个64位的整数,相对较短,便于存储和传输。
  2. 有序:由于包含时间戳成分,生成的ID是趋势递增的,具有良好的有序性。
  3. 不重复:通过合理的分配机器ID和序列号,确保在分布式环境下生成的ID不重复。
  4. 时钟回拨处理:通过时间戳的随机数原子+1操作(但这里可能存在误解,因为通常时间戳不是随机数),可以在一定程度上避免时钟回拨问题。然而,这部分描述可能不够准确或完整,需要更多上下文来理解具体实现。

与其他算法的比较

与雪花算法相比,XID具有以下优势:

  • 更高的时间分辨率:使用纳秒级时间戳。
  • 适用于分布式环境下的ID生成需求。

然而,在唯一性方面,XID可能稍弱一些,因为它使用了较短的机器ID和序列号。这意味着在极端情况下(如大量机器在短时间内生成大量ID),可能存在ID冲突的风险。

XID库通常提供以下功能:

  1. 生成ID:根据当前时间戳、机器ID和序列号生成新的ID。
  2. 解析ID:将生成的ID解析回其组成成分,以便分析和调试。
  3. 验证ID:验证给定ID是否有效,即是否符合XID的格式和规范。

这些功能使得XID成为一个灵活且易于使用的ID生成解决方案,适用于各种分布式系统场景。

snowflake

Snowflake是Twitter开源的一种分布式ID生成算法,它的主要目标是在分布式系统中生成全局唯一的ID。Snowflake算法结合了时间戳、机器标识和序列号等元素,确保生成的ID既唯一又具有趋势递增的特性。这种设计使得Snowflake算法非常适用于需要高性能、低延迟和有序ID的场景,如数据库索引、分布式存储系统等。

Snowflake生成的ID是一个64位的整数,通常由以下几部分组成:

在这里插入图片描述

  1. 时间戳(Timestamp):占据ID的高位部分,用于记录ID生成的时间。时间戳的精度通常到毫秒级或纳秒级,这取决于具体实现。由于时间戳是递增的,因此可以保证生成的ID具有趋势递增的特性。
  2. 机器标识(Machine ID):用于标识生成ID的机器或节点。在分布式系统中,每台机器或节点都应该有一个唯一的标识,以确保不同机器生成的ID不会冲突。
  3. 数据中心标识(Data Center ID):可选的部分,用于标识生成ID的数据中心。这对于跨数据中心的分布式系统非常有用,可以确保不同数据中心生成的ID也是唯一的。
  4. 序列号(Sequence Number):在同一时间戳内,用于区分不同ID的序列号。当在同一时间戳内需要生成多个ID时,序列号可以确保这些ID的唯一性。

Snowflake算法的特点

  1. 全局唯一性:通过合理设计时间戳、机器标识和序列号的组合方式,确保在分布式系统中生成的ID是全局唯一的。
  2. 趋势递增:由于时间戳占据ID的高位部分,因此生成的ID具有趋势递增的特性。这对于数据库索引等场景非常有利,可以提高插入性能和减少索引的分裂与碎片化。
  3. 高性能与低延迟:Snowflake算法的设计目标之一就是高性能和低延迟。通过合理的位分配和算法优化,可以实现快速生成ID并降低对系统性能的影响。
  4. 安全性:与UUID相比,Snowflake算法不会暴露MAC地址等敏感信息,因此更安全。同时,生成的ID也不会过于冗余,可以节省存储空间和网络带宽。

Snowflake算法适用于需要在分布式环境下生成唯一ID的场景,如:

  1. 数据库主键生成:在分布式数据库中,可以使用Snowflake算法生成主键ID,确保不同节点生成的主键不会冲突。
  2. 分布式存储系统:在分布式存储系统中,可以使用Snowflake算法为文件或对象生成唯一的标识符。
  3. 消息队列:在分布式消息队列中,可以使用Snowflake算法为消息生成唯一的ID,以便进行追踪和排序。
  4. 日志系统:在分布式日志系统中,可以使用Snowflake算法为日志条目生成唯一的ID,方便进行日志聚合和查询。

Snowflake是一种高性能、低延迟和趋势递增的分布式ID生成算法。它结合了时间戳、机器标识和序列号等元素,确保生成的ID既唯一又具有有序性。Snowflake算法适用于需要在分布式环境下生成唯一ID的场景,如数据库索引、分布式存储系统等。与UUID相比,Snowflake算法更安全且生成的ID更简洁。

由于雪花算法的一部分id序列是基于时间戳的, 那么就会存在时钟回拨的问题。

什么是时钟回拨问题呢。 首先我们来看下服务器上的时间突然退回之前的时间:

  • 可能是人为调整时间,
  • 也可能是服务器之间的时间校对。

具体来说,时钟回拨(Clock Drift) 指的是系统时钟在某个时刻向回调整, 即时间向过去移动。 时钟回拨可能发生在分布式系统中的某个节点上, 这可能是由于时钟同步问题、时钟漂移或其他原因导致的。

时钟回拨可能对系统造成一些问题, 特别是对于依赖与时间顺序的应用程序或算法。

在分布式系统中, 时钟回拨可能导致一下问题

  • ID 冲突: 如果系统使用基于时间的算法生成唯一ID(如雪花算法),时钟回拨可能导致生成的ID与之前生成的ID冲突,破坏了唯一性。
  • 数据不一致:时钟回拨可能导致不同节点之间的时间戳不一致,这可能影响到分布式系统中的时间相关操作,如事件排序、超时判断等。数据的一致性可能会受到影响。
  • 缓存失效:时钟回拨可能导致缓存中的过期时间计算错误,使得缓存项在实际过期之前被错误地认为是过期的,从而导致缓存失效。

为了应对时钟回拨问题,可以采取以下措施

  • 使用时钟同步服务:通过使用网络时间协议(NTP) 等时钟同步服务,可以将节点的时钟与参考时钟进行同步,减少时钟回拨的可能性。
  • 引入时钟漂移校正:在分布式系统中,可以通过周期性地校正节点的时钟漂移,使其保持与其他节点的时间同步。
  • 容忍时钟回拨:某些应用场景下,可以容忍一定范围的时钟回拨。在设计应用程序时,可以考虑引入一些容错机制,以适应时钟回拨带来的影响。

总之, 时钟回拨是分布式系统中需要关注的一个问题, 可能对系统的时间相关操作、数据一致性和唯一ID生成等方面产生影响。

通过使用时钟同步服务、时钟漂移校正和容忍机制等方法, 可以减少时钟回拨带来的问题。

参考leaf, snowflake本身的容错有两点,一是防止自身节点时钟回拨, 另一点是防止节点自身时钟的不正确。

  • 防止节点自身时钟回拨

Snowflake通过定时上报当前时间并在etcd或zookeeper等分布式协调服务中记录节点上次的时间来解决时钟回拨问题。当节点启动时,它会根据节点ID从etcd或zookeeper中取回之前的时间。如果检测到时钟回拨,Snowflake会采取相应的措施。如果回拨时间很少,Snowflake可以选择等待回拨时间过后,再正常启动。如果回拨过大,节点将直接启动失败并报错,此时需要人为介入处理。

此外,Snowflake还采用了一种策略来避免新节点和旧节点之间的时间冲突风险。当节点定时上报时间时,它可以选择上报当前时间加上一个时间间隔(now+interval)的方式。这样,新节点需要超过这个时间戳才能启动,从而避免了时间冲突的问题。

  • 防止节点时钟不正确

为了降低时钟错误的风险,Snowflake要求每个节点都会定期上报自己的节点信息(IP/Port)到etcd或zookeeper,并提供一个RPC方法以供外界获取本节点的时间戳。当一个新节点启动时,它会通过etcd或zookeeper注册的其他节点信息,并发调用RPC方法获取其他节点的时间戳,并进行一一对比。如果时间戳差异过大,则代表本节点时间戳可能有问题,直接报错并需要人为介入处理。

这种解决方案的准确性相对较高,因为它不是简单地取各个节点上报的时间戳进行判断,而是通过实时获取其他节点的时间戳进行对比。这可以减少由于各节点定期上报时间戳导致的时间差异,并提高判断时间偏差的准确性。

至于第一个节点时间戳错误的情况,虽然发生的几率较低,但Snowflake的解决方案会在启动正常节点时报错并需要人为介入。在这种情况下,可以停掉异常节点,然后逐个启动正常的新节点。第一个新节点启动时,由于etcd或zookeeper内没有其他节点信息,无需进行校验。

总的来说,Snowflake的时钟回拨解决方案通过结合定时上报时间、分布式协调服务和实时时间戳对比等方法,有效地减少了时钟回拨和时钟错误对分布式系统的影响。

Q: 为什么不采用把各个节点上报时间戳到etcd,新启动节点直接取 etcd 内的时间戳进行逐个判断呢?

主要考虑时间校准的准确性, 如果各节点定期上报时间戳, 各节点时间戳差异会比较大, 这会导致我们判断时间偏差的幅度不较大,准确性会下降。

Q: 如果第一个节点时间戳是错误的, 后续正确节点启动怎么办?

首先,这种情况发生的几率非常低并且此时我们启动正常节点时肯定会报错,人为介入。

报错时,直接停掉异常节点,然后逐个启动正常的新节点,第一个新节点启动时, etcd 内也没有其他节点信息,无需校验。

利用zookeeper 解决时钟回拨问题:

在使用ZooKeeper解决Snowflake时钟回拨问题时,我们主要利用ZooKeeper的分布式协调功能来同步和校验各个Snowflake节点的时间戳。以下是一个详细的解决方案:

  1. 节点时间上报与同步

步骤一: 每个Snowflake节点在启动时或定期(如每分钟)向ZooKeeper上报其当前的时间戳。这个时间戳可以包含节点的IP地址、端口号和时间戳值。

步骤二: ZooKeeper将这些时间戳存储在其数据结构中,例如使用ZNode来存储每个节点的时间戳信息。

  1. 节点时间校验

当一个新的Snowflake节点启动时,或者在运行过程中检测到可能的时钟回拨时,该节点会执行以下操作来进行时间校验:

步骤一: 节点从ZooKeeper中获取其他所有节点的时间戳信息。

步骤二: 节点比较自己的时间戳与其他节点的时间戳。如果发现自己的时间戳明显落后于其他节点(超过一个预设的阈值,如5分钟),则可能存在时钟回拨问题。

步骤三: 如果检测到时钟回拨,节点可以采取以下策略之一:

  • 等待策略:节点可以等待一段时间(超过回拨的时间差),然后再次尝试启动或继续操作。
  • 报错并停止:节点可以立即报错并停止运行,通知管理员进行手动干预。

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

下操作来进行时间校验:

步骤一: 节点从ZooKeeper中获取其他所有节点的时间戳信息。

步骤二: 节点比较自己的时间戳与其他节点的时间戳。如果发现自己的时间戳明显落后于其他节点(超过一个预设的阈值,如5分钟),则可能存在时钟回拨问题。

步骤三: 如果检测到时钟回拨,节点可以采取以下策略之一:

  • 等待策略:节点可以等待一段时间(超过回拨的时间差),然后再次尝试启动或继续操作。
  • 报错并停止:节点可以立即报错并停止运行,通知管理员进行手动干预。

[外链图片转存中…(img-oaYDHoIB-1715735390628)]
[外链图片转存中…(img-QgCoALUv-1715735390629)]
[外链图片转存中…(img-1AE9yccc-1715735390629)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

  • 16
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值