Twitter的snowflake算法解决了分布式系统生成全局ID的需求,生成64位(二进制位)的Long型数字(8字节),组成部分:
- 第一位未使用
- 接下来41位是毫秒级时间,41位的长度可以表示69年的时间
- 5位datacenterId,5位workerId。10位的长度最多支持部署1024个节点
- 最后12位是毫秒内的计数,12位的计数顺序号支持每个节点每毫秒产生4096个ID序列。2^12=4096
这样的好处是:毫秒数在高位,生成的ID整体上按时间趋势递增;不依赖第三方系统,稳定性和效率较高,理论上QPS约为409.6w/s(1000*2^12),并且整个分布式系统内不会产生ID碰撞;可根据自身业务灵活分配bit位。
不足就在于:强依赖机器时钟,如果时钟回拨,则可能导致生成ID重复。
结合数据库和snowflake的唯一ID方案,可以参考业界较为成熟的解法:Leaf——美团点评分布式ID生成系统,并考虑到了高可用、容灾、分布式下时钟等问题。
Golang 举例:
1 | package main |
代码解析:
1.设置起始时间和机器 ID:起始时间用于确定雪花算法的时间起点,机器 ID 用于标识不同的机器节点。
可以通过修改 startTime 变量来自定义雪花算法的起始时间。
在分布式系统中,不同的机器节点需要有不同的机器 ID,以确保生成的 ID 全局唯一。
2.创建节点实例:通过 snowflake.NewNode 函数创建一个新的节点实例,该实例会根据当前时间和机器 ID 生成唯一的 ID。
3.生成唯一 ID:使用 node.Generate() 方法生成唯一的 ID,并可以通过不同的方法(如 Int64()、String() 等)获取 ID 的不同表示形式。
4.time.Parse 函数中,布局字符串(例如 “2006-01-02”)并不表示具体的时间值,而是表示时间的格式。布局字符串是一个固定的格式模板,用于定义如何解析时间字符串。
5.处理时间回拨:如果系统时钟发生回拨,可能会导致生成重复的 ID。可以通过关闭生成 ID 机器的时间同步或使用时间服务器来解决此问题
雪花算法的问题
雪花算法生成的 ID强依赖于系统时钟,其ID 包含时间戳信息,它利用时间的连续性来保证生成的 ID 的唯一性和顺序性。如果系统时钟发生回拨,可能会导致雪花算法试图生成比之前更早时间戳的 ID,从而可能生成已经使用过的 ID,破坏了唯一性。
系统时钟发生回拨的解决办法:
1.在生成 ID 的机器上关闭自动时间同步功能,避免因时间同步导致的时钟回拨。这适用于那些对时间精度要求不是特别高,或者可以通过手动调整来保证时间准确性的环境。(在实际操作中,关闭时间同步可能会导致系统时间与实际时间不符,需要定期手动校准)
关闭时间同步后,能正常生成ID吗?
关闭时间同步后,仍然可以正常生成雪花算法的 ID,但需要确保系统时间不会被外部时间服务器或手动调整所改变。 关闭时间同步后,系统时钟将依赖本地的硬件时钟,如果硬件时钟正常工作且没有其他因素干扰,系统时钟会保持稳定,雪花算法可以根据这个稳定的时钟生成唯一的 ID。
如何关闭时间同步功能?
关闭自动时间同步功能并不是直接通过 Beego 等框架去设置的,而是在操作系统或时间同步服务层面进行配置。
在操作系统层面关闭自动时间同步:
1 | >sudo systemctl stop ntpd //临时关闭时间同步服务 |
2.部署一个高精度的时间服务器,所有生成 ID 的机器都从这个时间服务器获取时间。时间服务器可以提供稳定、准确的时间服务,避免因本地时钟漂移或回拨导致的问题。这适用于分布式系统环境,可以保证所有节点的时间一致性。(使用时间服务器需要保证网络的稳定性和时间服务器的可靠性。)
什么情况下会发生系统时钟回拨?
1)手动调整系统时间
维护或配置错误:系统管理员或用户在维护系统时,可能会手动调整系统时间,如果不小心将时间设置为过去的某个时刻,就会导致系统时钟回拨。
跨时区部署:在部署系统到不同地理位置或时区时,如果未正确配置时区或时间同步,可能会手动调整时间到目标时区的当前时间,若操作不当,就可能造成时钟回拨。
2)硬件故障或不稳定
硬件故障:主板上的RTC(实时时钟)芯片出现故障,可能会导致系统时间不准确甚至回拨。例如,电池老化或损坏,会使RTC无法保持准确的时间。
虚拟化环境中的时间同步问题:在虚拟化平台(如VMware、KVM等)中,虚拟机的时间通常与宿主机的时间进行同步。如果宿主机或虚拟机的硬件时间出现问题,可能会导致虚拟机的系统时钟回拨。
3)网络时间协议(NTP)同步问题
NTP服务器故障或配置错误:如果系统配置了错误的NTP服务器地址,或者NTP服务器本身出现故障,系统在尝试同步时间时可能会收到错误的时间信息,从而导致时钟回拨。
网络连接不稳定:在使用NTP进行时间同步时,如果网络连接不稳定或延迟过高,可能会导致时间同步过程出现异常,使系统时间回拨。
4)虚拟机迁移或快照恢复
虚拟机迁移:在虚拟化环境中,当虚拟机从一台宿主机迁移到另一台宿主机时,如果目标宿主机的时间与虚拟机之前的时间不一致,可能会导致虚拟机的系统时钟回拨。
快照恢复:恢复虚拟机的旧快照时,系统时间会恢复到快照创建时的状态,这可能导致系统时钟回拨到过去。
5)系统崩溃或电源故障
系统崩溃:系统在运行过程中突然崩溃,可能会导致系统时间未能及时保存或更新,再次启动时可能会出现时间回拨的情况。
电源故障:突然的电源故障可能导致系统非正常关机,系统重新启动后的时间可能会回拨到上次正常关机之前的状态。
6)软件更新或配置更改
软件更新:更新系统软件或某些时间相关的服务时,可能会出现时间配置文件被意外修改或重置,从而导致系统时钟回拨。
配置文件修改:手动修改系统的时间配置文件(如/etc/systemd/timesyncd.conf等),如果不小心输入了错误的时间值或配置,可能会导致时钟回拨。
7)安全攻击
恶意攻击:恶意用户可能会通过某些漏洞或攻击手段篡改系统时间,故意导致系统时钟回拨,以破坏系统的正常运行或安全机制。
在使用雪花算法生成唯一 ID 的系统中,为了避免因系统时钟回拨导致的 ID 重复问题,可以采取以下措施:
关闭时间同步:在生成 ID 的机器上暂时关闭自动时间同步功能,避免因时间同步导致的时钟回拨。
使用时间服务器:部署一个高精度的时间服务器,所有生成 ID 的机器都从这个时间服务器获取时间,确保时间的一致性和准确性
3.在应用层面处理时间回拨问题
1)使用雪花算法库的内置处理机制:一些雪花算法的实现库(如 bwmarrin/snowflake)已经内置了对时间回拨的处理机制,例如在生成 ID 时会检测时间回拨并采取相应的等待策略或使用备用时间源(如从时间服务器获取时间),确保生成的 ID 唯一性。
短暂回拨:短暂的时间回拨(如几毫秒)通常不会导致重复 ID,因为序列号可以继续递增。
长期回拨:长期的时间回拨(如几分钟或更长时间)可能导致重复 ID,尤其是如果回拨到之前已经生成过大量 ID 的时间段。
尽管雪花算法通过结合时间戳、机器 ID 和序列号来生成唯一 ID,时间回拨仍可能增加重复 ID 的风险,尤其是在时间回拨较大或频繁发生的情况下。为了避免这种风险,建议采取以下措施:
使用高精度的时间服务器确保时间的一致性和准确性。
在应用层面实现时间回拨检测和处理机制。
避免手动调整系统时间,确保时间同步服务的稳定性。
增加时间回拨的检测是否冗余?
在一些对数据一致性和系统稳定性要求极高的场景,即使库有内置处理机制,也可以在应用层面增加时间回拨的检测作为冗余保障。比如金融、医疗等对数据准确性要求极高的行业。
Golang时间回拨检测机制:
1 | package main |
2)自定义时间源:在应用中自定义一个时间源,该时间源在检测到时间回拨时,返回之前的时间戳,避免时间回拨对雪花算法的影响。