分布式Id/框架/发号器一文介绍

一、分布式id介绍

1、什么是分布式id

业务需求中需要对各种数据使用id进行唯一标识。
分布式ID是在分布式系统下使用的唯一标识,它用于保证数据的一致性和唯一性。
在传统的单机环境下,可以使用自增主键或UUID来生成唯一的ID,但在分布式环境下,由于多个节点同时生成ID,可能会出现重复的情况。
因此,需要一种能够生成全局唯一ID的解决方案,这就是分布式ID。

2、分布式id的特点

  • 全局唯一:在分布式环境下无论是在哪个节点生成的ID,都必须是全局唯一的,不能出现重复的情况
  • 趋势递增:生成的分布式ID通常要求能够近似有序递增,有助于后续业务需求使用id记性排序。ID 的有序性可以提升数据库写入速度,如在数据库中使用B+Tree数据结构时,自增顺序写入的数据能够保持B+Tree的结构稳定,从而提高存取效率
  • 高可用性:分布式ID生成系统需要保证在任何时候都能正确生成ID,即使在某些节点故障的情况下,整个系统仍能保持正常运行
  • 高性能性:在分布式环境下,特别是在高并发场景下,分布式ID生成系统需要能够迅速、高效地生成ID,且对服务本身的本地资源消耗小,以满足业务需求。
  • 信息安全:分布式ID的生成过程需要保证安全,不能被恶意用户推测出下一个ID,以防止可能的攻击,比如获取订单号,获取下载链接等

二、UUid生成算法

1、JDK UUID

JDK提供了现成的UUID生成方式,如下代码所示,包含了32个16进制的数字,以-分为五段,8-4-4-4-12

UUID uuid = UUID.randomUUID();
//输出示例:5c89a67d-4541-42d1-85e9-807bd93f065b
System.out.println(uuid);

JDK的版本不同,生成UUID的规则也不同,实现方式参考文档:
从源码角度分析UUID的实现原理

  • 版本 1 : UUID 是根据时间和节点 ID(通常是 MAC 地址)生成;
  • 版本 2 : UUID 是根据标识符(通常是组或用户 ID)、时间和节点 ID 生成;
  • 版本 3、版本 5 : 通过散列(hashing)名字空间(namespace)标识符和名称生成;
  • 版本 4 : UUID 使用随机性open in new window或伪随机性open in new window生成

优点:生成速度快,简单易用,一行代码搞定,本地生成,没有网络损耗
缺点

  1. 存储空间消耗大: UUID 是一个 128 位的字符串,相比于传统的 32 位或 64 位整数 ID,它占用了更多的存储空间。这可能会增加数据库和存储系统的负担
  2. 可读性差: UUID 的格式通常是一长串的字符,可读性差
  3. 无序: 基于时间的 UUID 虽然具有一定的有序性,但由于其包含了时间戳、机器 MAC 地址和随机数等多个部分,因此并不完全按照时间顺序排列。这可能对于某些需要按时间顺序处理数据的场景造成不便

2、Snowflake 雪花算法

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/534cd2ef36584970a500025aa8f46281.png
Snowflake算法描述:指定机器 & 同一时刻 & 某一并发序列,是唯一的。据此可生成一个64 bits的唯一ID(long)。默认采用上图字节分配方式,除了sign:1bit序号位不可调整外,其他的标识都可以根据实际情况做些调整。

  • sign(1bit):固定1bit符号标识,表示正式,业务中常用于标识生成的UID为正数
  • timestamp(41bit):用于表示时间戳,相对于某个时间基点的增量值,单位是毫秒,可以支持 (1L<<41)/1000L360024*365 ≈ 69年
  • datacenter id + worker id (10bit):在分布式IDC环境下,一般用前5位标识机房id/大区id,后五位表示机器id,用来区分不同的集群/机房的节点,这样可以表示32个IDC,每个IDC下面可以有32个机器
  • sequence(12bit):用来表示每毫秒下的并发序列,序列号为自增值,12个bit代表单台机器每毫秒能够生成2^12=4096个ID,理论上snowflake方案的QPS约为409.6w/s
  • 可支持单业务单大区重启 2^17-1=131071次

优点:生成速度比较快,毫秒数在高位,自增序列号在低位,整体ID是趋势递增的,业务需要时可以加上业务ID
缺点:强依赖机器时钟,如果机器上时钟回拨(即服务器上的时间突然倒退回之前的时间),进而导致发号重复或者服务会处于不可用状态

3、PearFlower 梨花算法

雪花算法的改进方法,目标:解决雪花算法的时间回拨问题,秒级时间戳回避毫秒级时间回拨问题;随机段号降低相同机器ID造成的影响
原理

  • 符号位:1位
  • 时间:31位,精确到秒级,可以用68年
  • 段号(批次号):3位 每秒可分为8个段
  • 机器号:10位 最多支持1024台机器
  • sequence:19位 可表示:0–524287

优化点

  1. 经过调整,时间只对秒灵敏,成功回避了服务器间几百毫秒的时间误差引起的时间回拨问题;
  2. 若第59秒的8个段号没有用完,则当润秒来临时,还可继续使用
  3. 可设置一定的秒数(如3秒)内提前消费。比如第10秒的号码,在800毫秒用完了,可以继续使用第11秒的号码。这样,下1秒用的号码不是很多时,就可以借给上1秒使用。

参考:分布式全局唯一ID生成算法(改进的雪花算法——解决时钟回拨问题) https://blog.csdn.net/abckingaa/article/details/106460583

4、Mist 薄雾算法

在这里插入图片描述
原理

  • 第一段为最高位,占 1 位,保持为 0,使得值永远为正数;
  • 第二段放置自增数,占 47 位,自增数在高位能保证结果值呈递增态势,遂低位可以为所欲为;
  • 第三段放置随机因子一,占 8 位,上限数值 255,使结果值不可预测;
  • 第四段放置随机因子二,占 8 位,上限数值 255,使结果值不可预测;

优点:递增态势、不依赖数据库、高性能;不受时间回拨的影响

缺点:程序重启会造成按序递增数值回到初始值,要借助存储在程序重启时恢复自增序列。

三、常见发号器服务

1、数据库

1)自增

原理:利用给字段设置auto_increment_increment和auto_increment_offset来保证ID自增

  1. 建表:
CREATE TABLE `sequence_id` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `stub` char(10) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `stub` (`stub`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

创建一张序号表,stub字段无实际意义,只是为了占位,便于我们插入或者修改数据。并且,给 stub 字段创建了唯一索引,保证其唯一性。

  1. SQL
BEGIN;
REPLACE INTO sequence_id (stub) VALUES ('stub');
SELECT LAST_INSERT_ID();
COMMIT;


DELETE FROM sequence_id WHERE id = LAST_INSERT_ID();

第一步:尝试把数据插入表中
第二步:如果主键或唯一索引的字段因出现重复数据而插入失败时,先从表中删除含有重复关键字的冲突行,然后再次尝试把数据插入表中。

优点

  1. 非常简单,利用现有数据库系统的功能实现,成本小,存储空间消耗小,有DBA专业维护
  2. ID号单调自增,可以实现一些对ID有特殊要求的业务

缺点

  1. 支持的并发量不大, 强依赖DB,当DB异常时整个系统不可用,属于致命问题,配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证,主从切换时的不一致可能会导致重复发号。
  2. 存在数据库单点问题,ID发号性能瓶颈限制在单台MySQL的读写性能 ,可以用数据库集群解决,不过增加了系统复杂度。如在分布式系统中每台机器设置不同的id初始值,且步长和机器数相等,不过系统维护困难,系统水平扩展比较困难,比如定义好了步长和机器台数之后,不容易扩展
  3. 数据库压力大,每次获取ID都得读写一次数据库,只能靠堆机器来提高性能
  4. ID没有具体的业务含义,而且存在安全问题,自增的订单ID存在规律,可以推测出业务数据,比如每天订单量等

2)号段模式

原理
从数据库批量的获取自增ID,每次从数据库取出一个号段范围,例如 (1,1000] 代表1000个ID,具体的业务服务将使用本号段,生成1~1000的自增ID并加载到内存,从内存获取速度很快

  1. 建表
CREATE TABLE `sequence_id_generator` (
  `id` int(10) NOT NULL,
  `current_max_id` bigint(20) NOT NULL COMMENT '当前最大id',
  `step` int(10) NOT NULL COMMENT '号段的长度',
  `version` int(20) NOT NULL COMMENT '版本号',
  `biz_type`    int(20) NOT NULL COMMENT '业务类型',
   PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

biz_type :代表不同业务类型
current_max_id:当前最大的可用id
step :代表号段的长度
version :是一个乐观锁,每次都更新version,保证并发时数据的正确性

current_max_id 字段和step字段主要用于获取批量 ID,获取的批量 id 为:current_max_id ~ current_max_id+step。

  1. 先新增一行数据
INSERT INTO `sequence_id_generator` (`id`, `current_max_id`, `step`, `version`, `biz_type`)
VALUES
 (1, 0, 100, 0, 101);
  1. 通过 SELECT 获取指定业务下的批量唯一 ID
UPDATE sequence_id_generator SET current_max_id = current_max_id +step, version=version+1 WHERE version = 0  AND `biz_type` = 101
SELECT `current_max_id`, `step`,`version` FROM `sequence_id_generator` where `biz_type` = 101

结果:id current_max_id step version biz_type
1 100 100 1 101

  • update current_max_id = current_max_id + step,update成功则说明新号段获取成功,新的号段范围是(current_max_id ,max_id +step]
  • update id_generator set max_id = #{max_id+step}, version = version + 1 where version = # {version} and biz_type = XXX
  • 由于多业务端可能同时操作,所以采用版本号version乐观锁方式更新,这种分布式ID生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多
  1. 不够用的话,更新之后重新 SELECT 即可。
    等这批号段ID用完,再次向数据库申请新号段,对current_max_id 字段做一次update操作

优点:比于数据库主键自增的方式,数据库的号段模式对于数据库的访问次数更少,数据库压力更小,存储消耗空间更小

缺点:跟自增模式类型,同样存在数据库单点问题、ID 没有具体业务含义、安全问题、

2、NoSQL

以使用Redis为主,原理就是利用redis的 incr命令实现ID的原子性自增。

127.0.0.1:6379> set seq_id 1     // 初始化自增ID为1
OK
127.0.0.1:6379> incr seq_id      // 增加1,并返回递增后的数值
(integer) 2

要考虑到redis持久化

### Web会话管理在Web应用中的重要性 Web会话管理是构建动态交互式Web应用的核心组件之一。它允许服务器跟踪用户的活动状态,从而提供个性化的用户体验[^1]。对于单用户桌面应用程序而言,由于只有一个用户存在,因此连接用户与其会话数据相对简单。然而,在多客户端的Web环境中,服务器如何区分不同用户的会话成为了一个挑战。这一问题通过引入会话ID得以解决[^2]。 #### 会话管理的工作原理 当用户首次访问网站时,服务器创建一个新的会话并将唯一的标识符分配给该用户——即所谓的会话ID。此ID通常存储在一个HTTP Cookie中,并随每次请求发送回服务器,以便识别特定用户的会话数据。如果浏览器禁用了Cookie,则可以采用URL重写或其他机制来传递会话信息。 以下是基于Java Server Pages (JSP) 的典型会话管理方式: ```java // 获取当前HttpSession对象 HttpSession session = request.getSession(); // 设置属性到session中 session.setAttribute("username", "JohnDoe"); // 读取session中的属性 String username = (String) session.getAttribute("username"); ``` 上述代码片段展示了如何利用`request.getSession()`获取现有的或者新建一个HttpSession实例,以及怎样向其中存入和取出键值对形式的数据。 #### 安全考量 尽管会话管理极大地增强了Web应用的功能性,但它也带来了潜在的安全风险。攻击者可能尝试窃取合法用户的会话令牌以冒充他们登录系统。为此,《Best Practices for Creating Secure Web Applications》一文中提到过一些增强安全性措施的重要性[^4]。例如,应该始终启用HTTPS协议加密传输层上的通信;定期更新或销毁不再活跃的会话以防泄露敏感资料等。 另外值得注意的是,在跨多个子域部署的应用场景下实施统一身份验证方案(Single Sign-On, SSO),这不仅简化了用户体验还能提高整体系统的防护水平[^3]。 #### 集群环境下的会话共享 随着分布式计算的发展趋势日益明显,越来越多的企业开始将其服务迁移到集群架构上来提升性能与可靠性。在这种情况下保持各节点间的一致性变得尤为重要。像Shiro这样的框架支持借助Hazelcast之类的第三方库来进行集中式的会话复制操作,即使是在非传统意义上的Web上下文中也能正常运作[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值