You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

121 lines
11 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 第39讲 | 谈谈常用的分布式ID的设计方案Snowflake是否受冬令时切换影响
专栏的绝大部分主题都侧重于Java语言和虚拟机基本都是单机模式下的问题今天我会补充一个分布式相关的问题。严格来说分布式并不算是Java领域而是一个单独的大主题但确实也会在Java技术岗位面试中被涉及。在准备面试时如果有丰富的分布式系统经验当然好如果没有你可以选择典型问题和基础技术进行适当准备。关于分布式我自身的实战经验也非常有限专栏里就谈谈从理论出发的一些思考。
今天我要问你的问题是谈谈常用的分布式ID的设计方案Snowflake是否受冬令时切换影响
## 典型回答
首先我们需要明确通常的分布式ID定义基本的要求包括
* 全局唯一,区别于单点系统的唯一,全局是要求分布式系统内唯一。
* 有序性通常都需要保证生成的ID是有序递增的。例如在数据库存储等场景中有序ID便于确定数据位置往往更加高效。
目前业界的方案很多,典型方案包括:
* 基于数据库自增序列的实现。这种方式优缺点都非常明显,好处是简单易用,但是在扩展性和可靠性等方面存在局限性。
* 基于Twitter早期开源的[Snowflake](https://github.com/twitter/snowflake)的实现,以及相关改动方案。这是目前应用相对比较广泛的一种方式,其结构定义你可以参考下面的示意图。
![](https://static001.geekbang.org/resource/image/ff/ad/ffd41494a39ef737b3c1151929c3c4ad.png)
整体长度通常是64 1 + 41 + 10+ 12 = 64适合使用Java语言中的long类型来存储。
头部是1位的正负标识位。
紧跟着的高位部分包含41位时间戳通常使用System.currentTimeMillis()。
后面是10位的WorkerID标准定义是5位数据中心 + 5位机器ID组成了机器编号以区分不同的集群节点。
最后的12位就是单位毫秒内可生成的序列号数目的理论极限。
Snowflake的[官方版本](https://github.com/twitter/snowflake)是基于Scala语言Java等其他语言的[参考实现](https://github.com/relops/snowflake)有很多,是一种非常简单实用的方式,具体位数的定义是可以根据分布式系统的真实场景进行修改的,并不一定要严格按照示意图中的设计。
* Redis、ZooKeeper、MongoDB等中间件也都有各种唯一ID解决方案。其中一些设计也可以算作是Snowflake方案的变种。例如MongoDB的[ObjectId](http://mongodb.github.io/node-mongodb-native/2.0/tutorials/objectid/)提供了一个12 byte96位的ID定义其中32位用于记录以秒为单位的时间机器ID则为24位16位用作进程ID24位随机起始的计数序列。
* 国内的一些大厂开源了其自身的部分分布式ID实现InfoQ就曾经介绍过微信的[seqsvr](http://www.infoq.com/cn/articles/wechat-serial-number-generator-architecture),它采取了相对复杂的两层架构,并根据社交应用的数据特点进行了针对性设计,具体请参考相关[代码实现](https://github.com/nebula-im/seqsvr)。另外,[百度](https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md)、美团等也都有开源或者分享了不同的分布式ID实现都可以进行参考。
关于第二个问题,**Snowflake是否受冬令时切换影响**
我认为没有影响你可以从Snowflake的具体算法实现寻找答案。我们知道Snowflake算法的Java实现大都是依赖于System.currentTimeMillis()这个数值代表什么呢从Javadoc可以看出它是返回当前时间和1970年1月1号UTC时间相差的毫秒数这个数值与夏/冬令时并没有关系,所以并不受其影响。
## 考点分析
今天的问题不仅源自面试的热门考点并且也存在着广泛的应用场景我前面给出的回答只是一个比较精简的典型方案介绍。我建议你针对特定的方案进行深入分析以保证在面试官可能会深入追问时能有充分准备如果恰好在现有系统使用分布式ID理解其设计细节是很有必要的。
涉及分布式,很多单机模式下的简单问题突然就变得复杂了,这是分布式天然的复杂性,需要从不同角度去理解适用场景、架构和细节算法,我会从下面的角度进行适当解读:
* 我们的业务到底需要什么样的分布式ID除了唯一和有序还有哪些必须要考虑的要素
* 在实际场景中,针对典型的方案,有哪些可能的局限性或者问题,可以采取什么办法解决呢?
## 知识扩展
如果试图深入回答这个问题首先需要明确业务场景的需求要点我们到底需要一个什么样的分布式ID
除了唯一和有序考虑到分布式系统的功能需要通常还会额外希望分布式ID保证
* 有意义或者说包含更多信息例如时间、业务等信息。这一点和有序性要求存在一定关联如果ID中包含时间本身就能保证一定程度的有序虽然并不能绝对保证。ID中包含额外信息在分布式数据存储等场合中有助于进一步优化数据访问的效率。
* 高可用性,这是分布式系统的必然要求。前面谈到的方案中,有的是真正意义上的分布式,有得还是传统主从的思路,这一点没有绝对的对错,取决于我们业务对扩展性、性能等方面的要求。
* 紧凑性ID的大小可能受到实际应用的制约例如数据库存储往往对长ID不友好太长的ID会降低MySQL等数据库索引的性能编程语言在处理时也可能受数据类型长度限制。
在具体的生产环境中还有可能提出对QPS等方面的具体要求尤其是在国内一线互联网公司的业务规模下更是需要考虑峰值业务场景的数量级层次需求。
第二,**主流方案的优缺点分析**。
对于数据库自增方案除了实现简单它生成的ID还能够保证固定步长的递增使用很方便。
但是因为每获取一个ID就会触发数据库的写请求是一个代价高昂的操作构建高扩展性、高性能解决方案比较复杂性能上限明显更不要谈扩容等场景的难度了。与此同时保证数据库方案的高可用性也存在挑战数据库可能发生宕机即使采取主从热备等各种措施也可能出现ID重复等问题。
实际大厂商往往是构建了多层的复合架构,例如美团公开的数据库方案[Leaf-Segment](https://tech.meituan.com/MT_Leaf.html)引入了起到缓存等作用的Leaf层对数据库操作则是通过数据库中间件提供的批量操作这样既能保证性能、扩展性也能保证高可用。但是这种方案对基础架构层面的要求很多未必适合普通业务规模的需求。
与其相比Snowflake方案的好处是算法简单依赖也非常少生成的序列可预测性能也非常好比如Twitter的峰值超过10万/s。
但是,它也存在一定的不足,例如:
* 时钟偏斜问题Clock Skew。我们知道普通的计算机系统时钟并不能保证长久的一致性可能发生时钟回拨等问题这就会导致时间戳不准确进而产生重复ID。
针对这一点Twitter曾经在文档中建议开启[NTP](http://doc.ntp.org/4.1.0/ntpd.htm)毕竟Snowflake对时间存在依赖但是也有人提议关闭NTP。我个人认为还是应该开启NTP只是可以考虑将stepback设置为0以禁止回调。
从设计和具体编码的角度,还有一个很有效的措施就是缓存历史时间戳,然后在序列生成之前进行检验,如果出现当前时间落后于历史时间的不合理情况,可以采取相应的动作,要么重试、等待时钟重新一致,或者就直接提示服务不可用。
* 另外序列号的可预测性是把双刃剑虽然简化了一些工程问题但很多业务场景并不适合可预测的ID。如果你用它作为安全令牌之类则是非常危险的很容易被黑客猜测并利用。
* ID设计阶段需要谨慎考虑暴露出的信息。例如[Erlang版本](https://github.com/boundary/flake)的flake实现基于MAC地址计算WorkerID在安全敏感的领域往往是不可以这样使用的。
* 从理论上来说类似Snowflake的方案由于时间数据位数的限制存在与[2038年问题](https://en.wikipedia.org/wiki/Year_2038_problem)相似的理论极限。虽然目前的系统设计考虑数十年后的问题还太早,但是理解这些可能的极限是有必要的,也许会成为面试的过程中的考察点。
如果更加深入到时钟和分布式系统时序的问题还有与分布式ID相关但又有所区别的问题比如在分布式系统中不同机器的时间很可能是不一致的如何保证事件的有序性Lamport在1978年的论文[Time, Clocks, and the Ording of Events in a Distributed System](https://amturing.acm.org/p558-lamport.pdf))中就有很深入的阐述,有兴趣的同学可以去查找相应的翻译和解读。
最后,我再补充一些当前分布式领域的面试热点,例如:
* 分布式事务,包括其产生原因、业务背景、主流的解决方案等。
* 理解[CAP](https://en.wikipedia.org/wiki/CAP_theorem)、[BASE](https://en.wikipedia.org/wiki/Eventual_consistency)等理论,懂得从最终一致性等角度来思考问题,理解[Paxos](https://en.wikipedia.org/wiki/Paxos_(computer_science))、[Raft](https://raft.github.io/)等一致性算法。
* 理解典型的分布式锁实现,例如最常见的[Redis分布式锁](https://redis.io/topics/distlock)。
* 负载均衡等分布式领域的典型算法,至少要了解主要方案的原理。
这些方面目前都已经有相对比较深入的分析,尤其是来自于一线大厂的实践经验。另外,在[左耳听风专栏的“程序员练级攻略”](http://time.geekbang.org/column/48)里,提供了非常全面的分布式学习资料,感兴趣的同学可以参考。
今天我简要梳理了当前典型的分布式ID生成方案并探讨了ID设计的一些考量尤其是应用相对广泛的Snowflake的不足之处希望对你有所帮助。
## 一课一练
关于今天我们讨论的题目你做到心中有数了吗今天的思考题是从理论上来看Snowflake这种基于时间的算法从形式上天然地限制了ID的并发生成数量如果在极端情况下短时间需要更多ID有什么办法解决呢
请你在留言区写写你对这个问题的思考,我会选出经过认真思考的留言,送给你一份学习奖励礼券,欢迎你与我一起讨论。
你的朋友是不是也在准备面试呢?你可以“请朋友读”,把今天的题目分享给好友,或许你能帮到他。