前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >结合业务探讨分布式ID技术与实现

结合业务探讨分布式ID技术与实现

作者头像
千羽
发布2024-04-17 15:33:33
1600
发布2024-04-17 15:33:33
举报
文章被收录于专栏:程序员千羽程序员千羽

hello,大家好,我是千羽。

最近有同学私信到数据库分布式id设计的时候,咨询这一块是怎么设计的,所以趁着周末,总结了根据现有业务来探讨分布式ID技术与实现。

先从传统的主键自增ID开始聊起,探讨其存在的局限性以及业务系统对分布式ID的需求。

随后,我们将调研业界常见的分布式ID生成方案,包括雪花算法、号段模式、UUID等。在选择方案时,我们将采取雪花算法与段模式相结合的方式。最后,我们将深入探讨分布式ID的落地与实现,包括使用Golang实现雪花算法和段模式,并结合实际业务场景进行讨论。

引言:

在当今大数据时代,随着业务规模的不断扩大和数据量的不断增长,业务系统对于唯一标识符(ID)的需求越来越迫切。特别是在分布式系统中,生成唯一ID成为了一项挑战。

本文将深入探讨为什么需要分布式ID,业务系统对分布式ID的要求,以及业界几种常见的分布式ID生成方案。结合部门的实际的业务案例,将详细介绍如何根据业务需求选择合适的分布式ID技术,并通过段模式和雪花模式重构部门数据库,实现更高效的数据管理。

一、聊聊传统的主键自增ID

传统的MySQL主键ID模式通常采用自增主键的方式来生成唯一标识符。

在这种模式下,数据库表通常会定义一个名为"id"的列,将其设置为主键,并启用自动递增功能。每当向表中插入一条新记录时,MySQL都会自动为该记录分配一个唯一的ID值,并且这个ID值会自动递增,确保每个记录都具有不同的ID。

比如这张表而言

具体的表设计

代码语言:javascript
复制
CREATE TABLE `book` (
  `bookid` int NOT NULL AUTO_INCREMENT COMMENT '图书ID',
  `bookname` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '图书名称',
  `price` decimal(6,2) NOT NULL COMMENT '价格',
  `author` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '作者',
  `publisher` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '出版社',
  `tid` int NOT NULL COMMENT '类别ID',
  `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态:0-未上架,1-已上架',
  `del` tinyint(1) NOT NULL DEFAULT 0 COMMENT '删除标志:0-未删除,1-已删除',
  `comment` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '评论',
  PRIMARY KEY (`bookid`),
  FOREIGN KEY (`tid`) REFERENCES `category`(`categoryid`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;

我们可以来分析一下,最后一行 ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;

  • ENGINE=InnoDB:指定了使用的存储引擎为InnoDB。InnoDB是MySQL的一种常用存储引擎,提供了事务支持和行级锁等特性。
  • AUTO_INCREMENT=9:指定了表的自增主键从值9开始递增。这意味着当向表中插入新记录时,自增主键的初始值为9,并且每次插入新记录时,该主键值会自动递增1。
  • DEFAULT CHARSET=utf8mb3:指定了表的默认字符集为utf8mb3。utf8mb3是UTF-8的一种实现方式,支持最多3个字节表示一个字符,适用于大部分的中文和英文字符。
  • ROW_FORMAT=DYNAMIC:指定了行的格式为动态行格式。动态行格式是InnoDB存储引擎的一种行存储格式。在动态行格式中,每行的列不固定,根据实际数据大小进行灵活存储,可以节省存储空间并提高性能。

AUTO_INCREMENT=9,表示该表自增到9的位置。

1.1 主键ID自增存在的局限

如果是单体系统来说,主键ID可能会常用主键自动的方式进行设置,这种ID生成方法在单体项目是可行的。

但是对于在分布式系统中,可能存在多个数据库实例,每个数据库实例都有自己的自增ID生成器,这样就会造成跨库的ID不唯一问题,需要额外的处理来解决,所以这是不符合业务的。

1.2 业务系统对分布式ID的要求

  1. 全局唯一性:生成的ID必须在全局范围内是唯一的,不同的节点和不同的系统都不能生成相同的ID。
  2. 趋势递增:趋势递增,这对于MySQL等使用聚集索引的数据库来说尤为重要,可提高写入效率。
  3. 单调递增:保证下一个ID大于上一个ID,这种情况可以保证事务版本号,排序等特殊需求实现
  4. 可扩展性:ID生成方案需要具备良好的扩展性,能够适应系统规模的持续增长。无论是增加节点数量还是增加系统负载,ID生成器都能够轻松应对,不会成为系统的瓶颈。
  5. 可预测性:生成的ID应具有一定的可预测性,即在一定范围内,可以预测下一个生成的ID值是多少。(段模式)

二、调研业界常见的分布式ID生成方案

2.1 雪花算法(SnowFlake):

雪花算法是Twitter开源的一种分布式ID生成算法,采用64位的整数表示,其中包含时间戳、机器ID、数据中心ID和序列号等信息,保证了ID的全局唯一性和趋势递增。

  • 优点:
  • 高效性能:雪花算法通过位运算和时间戳生成ID,性能高效,适用于高并发场景。
  • 全局唯一性:雪花算法生成的ID具有全局唯一性,不会产生重复。
  • 缺点:
    • 时钟回拨问题:如果系统时钟发生回拨,可能会导致生成的ID不唯一或不连续。
    • 依赖时间戳:雪花算法的ID生成依赖于时间戳,如果时间戳不稳定,可能会影响ID的唯一性。

2.2 号段模式

号段模式将ID的生成分成两个步骤,首先申请一个区间(号段),然后在该区间内自增生成ID。号段模式适用于高并发场景,可以减少对数据库的访问压力,但需要额外的管理和调度机制。

  • 优点:
    • 分段管理:号段模式可以将ID生成过程分成两个阶段,提高了并发能力和性能。
    • 适用性广泛:号段模式适用于各种分布式系统,并且可以灵活调整号段的大小和生成频率。
  • 缺点:
    • 管理复杂:需要额外的管理和调度机制来管理号段的分配和使用。
    • 可能存在重复:如果号段生成不当,可能会导致ID的重复或碰撞。

2.3 UUID:

全球唯一标识符(UUID)是一种由128位数字表示的标准,通常以32位的十六进制数表示。UUID生成算法基于时间戳和设备唯一标识等信息,保证了全局唯一性。但由于其长度较长,不适合作为数据库的主键。

  • 优点:
    • 全局唯一性:UUID是全球唯一标识符,保证了生成的ID在全球范围内的唯一性。
    • 无序性:UUID是随机生成的,不受顺序限制,适合于分布式系统。
  • 缺点:
    • 长度较长:UUID通常为128位,较长的长度可能会占用较大的存储空间。
    • 不易读性:由于UUID是一串数字和字母的组合,不易于人类识别和记忆。

2.4 数据库自增

在数据库中使用自增主键生成ID,每次插入新记录时,数据库会自动分配一个唯一的ID值。这种方式简单易用,但不适用于分布式环境,可能存在单点故障和性能瓶颈。

  • 优点:
    • 简单易用:使用数据库自增主键生成ID非常简单,不需要额外的代码实现。
    • 递增性:自增主键生成的ID是递增的,有助于提高查询效率。
  • 缺点:
    • 单点故障:在分布式系统中,数据库自增主键可能存在单点故障和性能瓶颈。
    • 不适合分布式:数据库自增主键无法满足分布式系统的需求,不适合于跨数据库实例的应用。

2.5 Redis实现

利用Redis的原子操作和分布式锁机制,可以实现分布式ID的生成。通过维护一个递增的计数器或使用Redis的自增功能,可以生成全局唯一的ID。

  • 优点:
    • 高性能:Redis具有高效的原子操作和分布式锁机制,可以实现高性能的分布式ID生成。
    • 可扩展性:Redis支持集群模式,可以轻松扩展到多个节点,适用于大规模分布式系统。
  • 缺点:
    • 单点故障:Redis作为单点服务可能存在单点故障的风险。
    • 数据丢失:由于Redis是内存数据库,数据可能会丢失或不稳定。

此外,还有其他大厂之间的百度Uidgenerator,美团Leaf,滴滴TinyID等等。

三、方案选择:采取雪花算法+段模式

结合当前的系统业务场景,既要进行分布式id也要进行自增和保持历史数据的现状。采取雪花算法+段模式两种模式去实现分布式id的实现。

3.1 雪花算法(SnowFlake)

保证了生成的ID具有全局唯一性和趋势递增性,每个ID都是递增的,并且不会出现重复的情况。

3.2 段模式

段模式在分段管理的过程中也能够保证ID的唯一性和递增性,通过对号段进行动态管理和分配,可以充分利用号段的使用效率,提高了ID的生成性能和效率。

此外,段模式还可以一眼开出这个id是谁谁谁,清晰明了。

四、分布式ID落地与实现

4.1 golang实现雪花算法

通过一个简单的 SnowFlake 结构体,其中包含了生成唯一ID所需的参数和方法。通过调用 NextID() 方法,可以生成基于雪花算法的唯一ID

代码语言:javascript
复制
package main

import (
 "fmt"
 "sync"
 "time"
)

// SnowFlake 结构体定义
type SnowFlake struct {
 mu           sync.Mutex
 startTime    int64 // 起始时间戳,单位为毫秒
 datacenterID int64 // 数据中心ID
 workerID     int64 // 工作节点ID
 sequence     int64 // 序列号
 lastStamp    int64 // 上次生成ID的时间戳
}

// NewSnowFlake 函数用于创建一个新的 SnowFlake 对象
func NewSnowFlake(datacenterID, workerID int64) *SnowFlake {
 return &SnowFlake{
  startTime:    1609459200000, // 2021-01-01 00:00:00 的毫秒级时间戳
  datacenterID: datacenterID,
  workerID:     workerID,
  sequence:     0,
  lastStamp:    -1,
 }
}

// NextID 方法用于生成下一个唯一ID
func (sf *SnowFlake) NextID() int64 {
 sf.mu.Lock()
 defer sf.mu.Unlock()

 // 获取当前时间戳,单位为毫秒
 now := time.Now().UnixNano() / 1e6

 // 如果当前时间小于上次生成ID的时间戳,则等待
 if now < sf.lastStamp {
  for now <= sf.lastStamp {
   now = time.Now().UnixNano() / 1e6
  }
 }

 // 如果当前时间与上次生成ID的时间戳相同,则递增序列号
 if now == sf.lastStamp {
  sf.sequence = (sf.sequence + 1) & 4095 // 序列号取值范围为 0-4095
  if sf.sequence == 0 {
   now = sf.waitNextMillis(now)
  }
 } else {
  sf.sequence = 0
 }

 // 更新上次生成ID的时间戳
 sf.lastStamp = now

 // 生成ID
 id := ((now - sf.startTime) << 22) | (sf.datacenterID << 17) | (sf.workerID << 12) | sf.sequence
 return id
}

// waitNextMillis 方法用于等待下一个毫秒
func (sf *SnowFlake) waitNextMillis(lastStamp int64) int64 {
 now := time.Now().UnixNano() / 1e6
 for now <= lastStamp {
  now = time.Now().UnixNano() / 1e6
 }
 return now
}

func main() {
 // 创建一个 SnowFlake 对象
 sf := NewSnowFlake(1, 1) // 设置数据中心ID和工作节点ID

 // 生成并打印 10 个唯一ID
 for i := 0; i < 10; i++ {
  fmt.Println("ID:", sf.NextID())
 }
}

4.2 golang实现段模式

结合Segment 结构体,其中包含了生成唯一ID所需的参数和方法。通过调用 NextID() 方法,可以生成基于段模式的唯一ID

代码语言:javascript
复制
package main

import (
 "fmt"
 "sync"
)

// Segment 结构体定义
type Segment struct {
 mu      sync.Mutex
 start   int64 // 起始值
 step    int64 // 步长
 current int64 // 当前值
}

// NewSegment 函数用于创建一个新的 Segment 对象
func NewSegment(start, step int64) *Segment {
 return &Segment{
  start:   start,
  step:    step,
  current: start,
 }
}

// NextID 方法用于生成下一个唯一ID
func (s *Segment) NextID() int64 {
 s.mu.Lock()
 defer s.mu.Unlock()

 id := s.current
 s.current += s.step
 return id
}

func main() {
 // 创建一个 Segment 对象
 segment := NewSegment(1000, 1) // 设置起始值和步长

 // 生成并打印 10 个唯一ID
 for i := 0; i < 10; i++ {
  fmt.Println("ID:", segment.NextID())
 }
}

4.3 实际的业务

在实际的业务上,通过设置一个分布式id的生成服务,每次涉及新增的逻辑,会先调研这个分布式服务生成id,在进行数据库插入等等。

当然在数据库层面也会设置:是否为雪花算法和段模式。

代码语言:javascript
复制
//分布式id改造
protected $distributed = true;
protected $distributedType = 1;
protected $distributedTag = "test:test:book";

protected $table = 'book';
public $timestamps = false;
  1. $distributed:这个变量表示是否启用分布式ID。如果设置为true,则表示启用分布式ID,否则表示不启用。在这段代码中,设置为true,即启用分布式ID。
  2. $distributedType:这个变量表示分布式ID的类型。在这里,设置为1,指定了雪花算法分布式ID生成算法或方案的类型。2是段模式。
  3. $distributedTag:这个变量表示分布式ID的标签或命名空间。在分布式系统中,通常会使用命名空间来区分不同的业务模块或数据表。
  4. $table:这个变量表示数据库表的名称。在这段代码中,设置为'book',表示该模型对应的数据库表名称是'wx_label_v2'。
  5. $timestamps:这个变量表示是否启用模型的自动维护时间戳。在这段代码中,设置为false,表示不启用模型的自动维护时间戳,即不会自动生成created_at和updated_at字段。

五、总结

当我考虑雪花算法(SnowFlake)和段模式时,我发现它们都是用于生成分布式系统中唯一ID的重要方案。但两种方案各有优劣:

雪花算法(SnowFlake)是一种简单且高效的算法。它通过利用时间戳和节点ID生成全局唯一的ID,这确保了ID的唯一性和趋势递增。这使得它在许多场景下都是一种理想的选择,特别是在需要高性能和简单实现的情况下。

另一方面,段模式则更加灵活。它允许每个节点预分配一段ID范围,并自行管理这些ID。这种方式避免了单点故障,并且可以根据需求动态调整ID范围

总的来说,我认为雪花算法(SnowFlake)适用于简单的分布式系统场景,而段模式则更适用于复杂的分布式系统场景。在选择适合自己系统的ID生成方案时,需要权衡它们的优缺点,并根据实际情况做出合适的选择。

如果你对分布式ID生成方案还有其他疑问或需要进一步讨论的地方,请随时在评论区留言哦~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-04-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 千羽的编程时光 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言:
  • 一、聊聊传统的主键自增ID
    • 1.1 主键ID自增存在的局限
      • 1.2 业务系统对分布式ID的要求
      • 二、调研业界常见的分布式ID生成方案
        • 2.1 雪花算法(SnowFlake):
          • 2.2 号段模式
            • 2.3 UUID:
              • 2.4 数据库自增
                • 2.5 Redis实现
                • 三、方案选择:采取雪花算法+段模式
                  • 3.1 雪花算法(SnowFlake)
                    • 3.2 段模式
                    • 四、分布式ID落地与实现
                      • 4.1 golang实现雪花算法
                        • 4.2 golang实现段模式
                          • 4.3 实际的业务
                          • 五、总结
                          相关产品与服务
                          数据库
                          云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档