This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,133 @@
<audio id="audio" title="21 | 分布式系统架构的冰与火" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2a/5e/2ab838e044dc4bb252dbecb9c0292f5e.mp3"></audio>
最近几年我们一直在谈论各式各样的架构如高并发架构、异地多活架构、容器化架构、微服务架构、高可用架构、弹性化架构等。还有和这些架构相关的管理型的技术方法如DevOps、应用监控、自动化运维、SOA服务治理、去IOE等。面对这么多纷乱的技术我看到很多团队或是公司都是一个一个地去做这些技术非常辛苦也非常累。这样的做法就像我们在撑开一张网里面一个一个的网眼。
其实,只要我们能够找到这张网的“纲”,我们就能比较方便和自如地打开整张网了。那么,这张“分布式大网”的总线——“纲”在哪里呢?我希望通过这一系列文章可以让你找到这个“纲”,从而能让你更好更有效率地做好架构和工程。
# 分布式系统架构的冰与火
首先,我们需要阐述一下为什么需要分布式系统,而不是传统的单体架构。也许这对你来说已经不是什么问题了,但是请允许我在这里重新说明一下。使用分布式系统主要有两方面原因。
<li>
**增大系统容量**。我们的业务量越来越大,而要能应对越来越大的业务量,一台机器的性能已经无法满足了,我们需要多台机器才能应对大规模的应用场景。所以,我们需要垂直或是水平拆分业务系统,让其变成一个分布式的架构。
</li>
<li>
**加强系统可用**。我们的业务越来越关键,需要提高整个系统架构的可用性,这就意味着架构中不能存在单点故障。这样,整个系统不会因为一台机器出故障而导致整体不可用。所以,需要通过分布式架构来冗余系统以消除单点故障,从而提高系统的可用性。
</li>
当然,分布式系统还有一些优势,比如:
<li>
因为模块化,所以系统模块重用度更高;
</li>
<li>
因为软件服务模块被拆分,开发和发布速度可以并行而变得更快;
</li>
<li>
系统扩展性更高;
</li>
<li>
团队协作流程也会得到改善;
</li>
<li>
……
</li>
不过这个世界上不存在完美的技术方案采用任何技术方案都是“按下葫芦浮起瓢”都是有得有失都是一种trade-off。也就是说分布式系统在解决上述问题的同时也给我们带来了其他的问题。因此我们需要清楚地知道分布式系统所带来的问题。
下面这个表格比较了单体应用和分布式架构的优缺点。
<img src="https://static001.geekbang.org/resource/image/8f/91/8fecccec610626a3e348318b1fd17791.png" alt="" />
从上面的表格我们可以看到,分布式系统虽然有一些优势,但也存在一些问题。
<li>
架构设计变得复杂(尤其是其中的分布式事务)。
</li>
<li>
部署单个服务会比较快,但是如果一次部署需要多个服务,流程会变得复杂。
</li>
<li>
系统的吞吐量会变大,但是响应时间会变长。
</li>
<li>
运维复杂度会因为服务变多而变得很复杂。
</li>
<li>
架构复杂导致学习曲线变大。
</li>
<li>
测试和查错的复杂度增大。
</li>
<li>
技术多元化,这会带来维护和运维的复杂度。
</li>
<li>
管理分布式系统中的服务和调度变得困难和复杂。
</li>
也就是说,分布式系统架构的难点在于系统设计,以及管理和运维。所以,分布式架构解决了“单点”和“性能容量”的问题,但却新增了一堆问题。而对于这些新增的问题,还会衍生出更多的子问题,这就需要我们不断地用各式各样的技术和手段来解决这些问题。
这就出现了我前面所说的那些架构方式,以及各种相关的管理型的技术方法。这个世界就是这样变得复杂起来的。
# 分布式系统的发展
从20世纪70年代的模块化编程80年代的面向事件设计90年代的基于接口/构件设计这个世界很自然地演化出了SOA——基于服务的架构。SOA架构是构造分布式计算应用程序的方法。它将应用程序功能作为服务发送给最终用户或者其他服务。它采用开放标准与软件资源进行交互并采用标准的表示方式。
开发、维护和使用SOA要遵循以下几条基本原则。
<li>
可重用,粒度合适,模块化,可组合,构件化以及有互操作性。
</li>
<li>
符合开放标准(通用的或行业的)。
</li>
<li>
服务的识别和分类,提供和发布,监控和跟踪。
</li>
但IBM搞出来的SOA非常重所以对SOA的裁剪和优化从来没有停止过。比如之前的SOAP、WSDL和XML这样的东西基本上已经被抛弃了而改成了RESTful和JSON这样的方式。而ESBEnterprise Service Bus企业服务总线这样非常重要的东西也被简化成了Pub/Sub的消息服务……
不过SOA的思想一直延续着。所以我们现在也不说SOA了而是说分布式服务架构了。
下面是一个SOA架构的演化图。
<img src="https://static001.geekbang.org/resource/image/54/42/542f449c5aeffd20a6d66b32c1736f42.png" alt="" />
我们可以看到,面向服务的架构有以下三个阶段。
<li>
20世纪90年代前是单体架构软件模块高度耦合。当然这张图同样也说明了有的SOA架构其实和单体架构没什么两样因为都是高度耦合在一起的。就像图中的齿轮一样当你调用一个服务时这个服务会调用另一个服务然后又调用另外的服务……于是整个系统就转起来了。但是这本质是比较耦合的做法。
</li>
<li>
而2000年左右出现了比较松耦合的SOA架构这个架构需要一个标准的协议或是中间件来联动其它相关联的服务如ESB。这样一来服务间并不直接依赖而是通过中间件的标准协议或是通讯框架相互依赖。这其实就是IoC控制反转和DIP依赖倒置原则设计思想在架构中的实践。它们都依赖于一个标准的协议或是一个标准统一的交互方式而不是直接调用。
</li>
<li>
而2010年后出现了微服务架构这个架构更为松耦合。每一个微服务都能独立完整地运行所谓的自包含后端单体的数据库也被微服务这样的架构分散到不同的服务中。而它和传统SOA的差别在于服务间的整合需要一个服务编排或是服务整合的引擎。就好像交响乐中需要有一个指挥来把所有乐器编排和组织在一起。
</li>
一般来说这个编排和组织引擎可以是工作流引擎也可以是网关。当然还需要辅助于像容器化调度这样的技术方式如Kubernetes。在Martin Fowler 的 [Microservices 这篇文章](https://martinfowler.com/articles/microservices.html)中有详细描述。
微服务的出现使得开发速度变得更快部署快隔离性高系统的扩展度也很好但是在集成测试、运维和服务管理等方面就比较麻烦了。所以需要一套比较好的微服务PaaS平台。就像Spring Cloud一样需要提供各种配置服务、服务发现、智能路由、控制总线……还有像Kubernetes提供的各式各样的部署和调度方式。
没有这些PaaS层的支撑微服务也是很难被管理和运维的。好在今天的世界已经有具备了这些方面的基础设施所以采用微服务架构我认为只是一个时间问题了。
# 小结
好了,今天的内容就到这里。相信通过今天的学习,你应该已经对为什么需要分布式系统,而不是传统的单体架构,有了清晰的认识。并且对分布式系统的发展历程了然于心。下一篇文章,我将结合亚马逊的分布式架构实践,来谈谈分布式系统架构的技术难点及应对方案。
下面我列出了《分布式系统架构的本质》系列文章的目录,希望你能在这个列表里找到自己感兴趣的内容。
- [分布式系统架构的冰与火](https://time.geekbang.org/column/article/1411)
- [从亚马逊的实践,谈分布式系统的难点](https://time.geekbang.org/column/article/1505)
- [分布式系统的技术栈](https://time.geekbang.org/column/article/1512)
- [分布式系统关键技术:全栈监控](https://time.geekbang.org/column/article/1513)
- [分布式系统关键技术:服务调度](https://time.geekbang.org/column/article/1604)
- [分布式系统关键技术:流量与数据调度](https://time.geekbang.org/column/article/1609)
- [洞悉PaaS平台的本质](https://time.geekbang.org/column/article/1610)
- [推荐阅读:分布式系统架构经典资料](https://time.geekbang.org/column/article/2080)
- [推荐阅读:分布式数据调度相关论文](https://time.geekbang.org/column/article/2421)
在文章的最后,很想听听大家在进行分布式系统开发,把一个单体应用拆解成服务化或是微服务中遇到的问题和难点是什么?踩过什么样的坑?你是如何应对的?
<img src="https://static001.geekbang.org/resource/image/9e/cd/9e00f2d01bc172e791e6e39b9de2a2cd.jpg" alt="" />

View File

@@ -0,0 +1,162 @@
<audio id="audio" title="22 | 从亚马逊的实践,谈分布式系统的难点" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/65/9a/656b9f21cb949430f00efde22518469a.mp3"></audio>
从目前已经公开的资料来看分布式服务化架构思想实践最早的公司应该是亚马逊。因为早在2002年的时候亚马逊CEO杰夫·贝索斯Jeff Bezos就向全公司颁布了下面的这几条架构规定来自《[Steve Yegge对Google平台吐槽](https://coolshell.cn/articles/5701.html)》一文)。
<li>
所有团队的程序模块都要通过Service Interface方式将其数据与功能开放出来。
</li>
<li>
团队间程序模块的信息通信,都要通过这些接口。
</li>
<li>
除此之外没有其它的通信方式。其他形式一概不允许不能直接链结别的程序把其他团队的程序当做动态链接库来链接不能直接读取其他团队的数据库不能使用共享内存模式不能使用别人模块的后门等等。唯一允许的通信方式是调用Service Interface。
</li>
<li>
任何技术都可以使用。比如HTTP、CORBA、Pub/Sub、自定义的网络协议等。
</li>
<li>
所有的Service Interface毫无例外都必须从骨子里到表面上设计成能对外界开放的。也就是说团队必须做好规划与设计以便未来把接口开放给全世界的程序员没有任何例外。
</li>
<li>
不这样做的人会被炒鱿鱼。
</li>
这应该就是AWSAmazon Web Service出现的基因吧。当然前面说过采用分布式系统架构后会出现很多的问题。比如
<li>
一个线上故障的工单会在不同的服务和不同的团队中转过来转过去。
</li>
<li>
每个团队都可能成为一个潜在的DDoS攻击者除非每个服务都要做好配额和限流。
</li>
<li>
监控和查错变得更为复杂。除非有非常强大的监控手段。
</li>
<li>
服务发现和服务治理也变得非常复杂。
</li>
为了克服这些问题,亚马逊这么多年的实践让其可以运维和管理极其复杂的分布式服务架构。我觉得主要有以下几点。
<li>
**分布式服务的架构需要分布式的团队架构**。在亚马逊一个服务由一个小团队Two Pizza Team不超过16个人两张Pizza可以喂饱的团队负责从前端到数据从需求分析到上线运维。这是良性的分工策略——按职责分工而不是按技能分工。
</li>
<li>
**分布式服务查错不容易**。一旦出现比较严重的故障需要整体查错。出现一个S2的故障就可以看到每个团队的人都会上线。在工单系统里能看到在故障发生的一开始大家都在签到并自查自己的系统。如果没问题也要在线待命standby等问题解决。我在《故障处理最佳实践应对故障》一文中详细地讲过这个事
</li>
<li>
**没有专职的测试人员,也没有专职的运维人员,开发人员做所有的事情**。开发人员做所有事情的好处是——吃自己的狗粮Eat Your Own Dog Food。自己写的代码自己维护自己养会让开发人员明白写代码容易维护代码复杂。这样开发人员在接需求、做设计、写代码、做工具时都会考虑到软件的长期维护性。
</li>
<li>
**运维优先,崇尚简化和自动化**。为了能够运维如此复杂的系统亚马逊内部在运维上下了非常大的功夫。现在人们所说的DevOps这个事亚马逊在10多年前就做到了。亚马逊最为强大的就是运维拼命地对系统进行简化和自动化让亚马逊做到了可以轻松运维拥有上千万台虚机的AWS云平台。
</li>
<li>
**内部服务和外部服务一致**。无论是从安全方面,还是接口设计方面,无论是从运维方面,还是故障处理的流程方面,亚马逊的内部系统都和外部系统一样对待。这样做的好处是,内部系统的服务随时都可以开放出来。而且,从第一天开始,服务提供方就有对外服务的能力。可以想象,以这样的标准运作的团队其能力会是什么样的。
</li>
在进化的过程中,亚马逊遇到的问题很多,甚至还有很多几乎没有人会想到的非常生僻的东西,它都一一学习和总结了,而且都解决得很好。
构建分布式系统非常难,这其中充满了各种各样的挑战,但亚马逊还是毫不犹豫地走了下去。这是因为亚马逊想做平台,不是“像淘宝这样的中介式流量平台”,而是那种“可以对外输出能力的平台”。
亚马逊觉得自己没有像史蒂夫·乔布斯Steve Jobs这样的牛人不可能做出像iPhone这样的爆款产品而且用户天生就是众口难调与其做一个大家都不满意的软件还不如把一些基础能力对外输出引入外部的力量来一起完成一个用户满意的产品。
这其实就是在建立自己的生态圈。虽然在今天看来这个事已经不稀奇了,但是贝索斯早在十五年前就悟到了,实在是个天才。
所以,分布式服务架构是需要从组织,到软件工程,再到技术上的一个大的改造,需要比较长的时间来磨合和改进,并不断地总结教训和成功经验。
# 分布式系统中需要注意的问题
我们再来看一下分布式系统在技术上需要注意的问题。
# 问题一:异构系统的不标准问题
这主要表现在:
- 软件和应用不标准。
- 通讯协议不标准。
- 数据格式不标准。
- 开发和运维的过程和方法不标准。
不同的软件,不同的语言会出现不同的兼容性和不同的开发、测试、运维标准。不同的标准会让我们用不同的方式来开发和运维,引起架构复杂度的提升。比如:有的软件修改配置要改它的.conf文件而有的则是调用管理API接口。
在通讯方面,不同的软件用不同的协议,就算是相同的网络协议里也会出现不同的数据格式。还有,不同的团队因为使用不同的技术,也会有不同的开发和运维方式。这些不同的东西,会让我们的整个分布式系统架构变得异常复杂。所以,分布式系统架构需要有相应的规范。
比如我看到很多服务的API出错不返回HTTP的错误状态码而是返回个正常的状态码200然后在HTTP Body里的JSON字符串中写着个errorbla bla error message。这简直就是一种反人类的做法。我实在不明白为什么会有众多这样的设计。这让监控怎么做啊现在你应该使用Swagger的规范了。
再比如我看到很多公司的软件配置管理里就是一个key-value的东西这样的东西灵活到可以很容易地被滥用。不规范的配置命名不规范的值甚至在配置中直接嵌入前端展示内容……
一个好的配置管理应该分成三层底层和操作系统相关中间层和中间件相关最上面和业务应用相关。于是底层和中间层是不能让用户灵活修改的而是只让用户选择。比如操作系统的相关配置应该形成模板来让人选择而不是让人乱配置的。只有配置系统形成了规范我们才hold得住众多的系统。
再比如:数据通讯协议。通常来说,作为一个协议,一定要有协议头和协议体。协议头定义了最基本的协议数据,而协议体才是真正的业务数据。对于协议头,我们需要非常规范地让每一个使用这个协议的团队都使用一套标准的方式来定义,这样我们才容易对请求进行监控、调度和管理。
这样的规范还有很多,我在这就不一一列举了。
# 问题二:系统架构中的服务依赖性问题
对于传统的单体应用,一台机器挂了,整个软件就挂掉了。但是你千万不要以为在分布式的架构下不会发生这样的事。分布式架构下,服务是会有依赖的,一个服务依赖链上的某个服务挂掉了,可能会导致出现“多米诺骨牌”效应。
所以,在分布式系统中,服务的依赖也会带来一些问题。
- 如果非关键业务被关键业务所依赖,会导致非关键业务变成一个关键业务。
- 服务依赖链中出现“木桶短板效应”——整个SLA由最差的那个服务所决定。
这是服务治理的内容了。服务治理不但需要我们定义出服务的关键程度,还需要我们定义或是描述出关键业务或服务调用的主要路径。没有这个事情,我们将无法运维或是管理整个系统。
这里需要注意的是,很多分布式架构在应用层上做到了业务隔离,然而,在数据库结点上并没有。如果一个非关键业务把数据库拖死,那么会导致全站不可用。所以,数据库方面也需要做相应的隔离。也就是说,最好一个业务线用一套自己的数据库。这就是亚马逊服务器的实践——系统间不能读取对方的数据库,只通过服务接口耦合。这也是微服务的要求。我们不但要拆分服务,还要为每个服务拆分相应的数据库。
# 问题三:故障发生的概率更大
在分布式系统中,因为使用的机器和服务会非常多,所以,故障发生的频率会比传统的单体应用更大。只不过,单体应用的故障影响面很大,而分布式系统中,虽然故障的影响面可以被隔离,但是因为机器和服务多,出故障的频率也会多。另一方面,因为管理复杂,而且没人知道整个架构中有什么,所以非常容易犯错误。
你会发现,对分布式系统架构的运维,简直就是一场噩梦。我们会慢慢地明白下面这些道理。
- 出现故障不可怕,故障恢复时间过长才可怕。
- 出现故障不可怕,故障影响面过大才可怕。
运维团队在分布式系统下会非常忙忙到每时每刻都要处理大大小小的故障。我看到很多大公司都在自己的系统里拼命地添加各种监控指标有的能够添加出几万个监控指标。我觉得这完全是在“使蛮力”。一方面信息太多等于没有信息另一方面SLA要求我们定义出“Key Metrics”也就是所谓的关键指标。然而他们却没有。这其实是一种思维上的懒惰。
但是,上述的都是在“救火阶段”而不是“防火阶段”。所谓“防火胜于救火”,我们还要考虑如何防火,这需要我们在设计或运维系统时都要为这些故障考虑,即所谓 Design for Failure。在设计时就要考虑如何减轻故障。如果无法避免也要使用自动化的方式恢复故障减少故障影响面。
因为当机器和服务数量越来越多时,你会发现,人类的缺陷就成为了瓶颈。这个缺陷就是人类无法对复杂的事情做到事无巨细的管理,只有机器自动化才能帮助人类。 也就是,人管代码,代码管机器,人不管机器!
# 问题四:多层架构的运维复杂度更大
通常来说,我们可以把系统分成四层:基础层、平台层、应用层和接入层。
- 基础层就是我们的机器、网络和存储设备等。
- 平台层就是我们的中间件层Tomcat、MySQL、Redis、Kafka之类的软件。
- 应用层就是我们的业务软件,比如,各种功能的服务。
- 接入层就是接入用户请求的网关、负载均衡或是CDN、DNS这样的东西。
对于这四层,我们需要知道:
- 任何一层的问题都会导致整体的问题;
- 没有统一的视图和管理,导致运维被割裂开来,造成更大的复杂度。
很多公司都是按技能分工的,他们按照技能把技术团队分为产品开发、中间件开发、业务运维、系统运维等子团队。这样的分工导致的结果就是大家各管一摊,很多事情完全连不在一起。整个系统会像 “多米诺骨牌”一样,一个环节出现问题,就会倒下去一大片。因为没有一个统一的运维视图,不知道一个服务调用是如何经过每一个服务和资源,也就导致在出现故障时要花大量的时间在沟通和定位问题上。
之前我在某云平台的一次经历就是这样的。从接入层到负载均衡再到服务层再到操作系统底层设置的KeepAlive的参数完全不一致导致用户发现软件运行的行为和文档中定义的完全不一样。工程师查错的过程简直就是一场恶梦以为找到了一个结果还有一个来来回回花了大量的时间才把所有KeepAlive的参数设置成一致的浪费了太多的时间。
**分工不是问题,问题是分工后的协作是否统一和规范**。这点,你一定要重视。
# 小结
好了,我们来总结一下今天分享的主要内容。首先,我以亚马逊为例,讲述了它是如何做分布式服务架构的,遇到了哪些问题,以及是如何解决的。
我认为亚马逊在分布式服务系统方面的这些实践和经验积累是AWS出现的基因。随后分享了在分布式系统中需要注意的几个问题同时给出了应对方案。我认为构建分布式服务需要从组织到软件工程再到技术上的一次大的改造需要比较长的时间来磨合和改进并不断地总结教训和成功经验。下篇文章中我们讲述分布式系统的技术栈。希望对你有帮助。
也欢迎大家分享一下你在分布式架构中遇到的各种问题。
下面我列出了**《分布式系统架构的本质》系列文章**的目录,希望你能在这个列表里找到自己感兴趣的内容。如果你在分布式系统架构方面,有其他想了解的话题和内容,欢迎留言给我。
- [分布式系统架构的冰与火](https://time.geekbang.org/column/article/1411)
- [从亚马逊的实践,谈分布式系统的难点](https://time.geekbang.org/column/article/1505)
- [分布式系统的技术栈](https://time.geekbang.org/column/article/1512)
- [分布式系统关键技术:全栈监控](https://time.geekbang.org/column/article/1513)
- [分布式系统关键技术:服务调度](https://time.geekbang.org/column/article/1604)
- [分布式系统关键技术:流量与数据调度](https://time.geekbang.org/column/article/1609)
- [洞悉PaaS平台的本质](https://time.geekbang.org/column/article/1610)
- [推荐阅读:分布式系统架构经典资料](https://time.geekbang.org/column/article/2080)
- [推荐阅读:分布式数据调度相关论文](https://time.geekbang.org/column/article/2421)

View File

@@ -0,0 +1,123 @@
<audio id="audio" title="23 | 分布式系统的技术栈" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ea/07/eaee072dd364e1afe8448e052e05e107.mp3"></audio>
正如我们前面所说的,构建分布式系统的目的是增加系统容量,提高系统的可用性,转换成技术方面,也就是完成下面两件事。
- **大流量处理**。通过集群技术把大规模并发请求的负载分散到不同的机器上。
- **关键业务保护**。提高后台服务的可用性,把故障隔离起来阻止多米诺骨牌效应(雪崩效应)。如果流量过大,需要对业务降级,以保护关键业务流转。
说白了就是干两件事。一是提高整体架构的吞吐量,服务更多的并发和流量,二是为了提高系统的稳定性,让系统的可用性更高。
# 提高架构的性能
咱们先来看看,提高系统性能的常用技术。
<img src="https://static001.geekbang.org/resource/image/a9/17/a9edeae125a80f381003d8d9d0056317.png" alt="" />
<li>
**缓存系统**。加入缓存系统可以有效地提高系统的访问能力。从前端的浏览器到网络再到后端的服务底层的数据库、文件系统、硬盘和CPU全都有缓存这是提高快速访问能力最有效的手段。对于分布式系统下的缓存系统需要的是一个缓存集群。这其中需要一个Proxy来做缓存的分片和路由。
</li>
<li>
**负载均衡系统**。负载均衡系统是水平扩展的关键技术,它可以使用多台机器来共同分担一部分流量请求。
</li>
<li>
**异步调用**。异步系统主要通过消息队列来对请求做排队处理,这样可以把前端的请求的峰值给“削平”了,而后端通过自己能够处理的速度来处理请求。这样可以增加系统的吞吐量,但是实时性就差很多了。同时,还会引入消息丢失的问题,所以要对消息做持久化,这会造成“有状态”的结点,从而增加了服务调度的难度。
</li>
<li>
**数据分区和数据镜像**。**数据分区**是把数据按一定的方式分成多个区比如通过地理位置不同的数据区来分担不同区的流量。这需要一个数据路由的中间件会导致跨库的Join和跨库的事务非常复杂。而**数据镜像**是把一个数据库镜像成多份一样的数据,这样就不需要数据路由的中间件了。你可以在任意结点上进行读写,内部会自行同步数据。然而,数据镜像中最大的问题就是数据的一致性问题。
</li>
对于一般公司来说,在初期,会使用读写分离的数据镜像方式,而后期会采用分库分表的方式。
# 提高架构的稳定性
接下来,咱们再来看看提高系统系统稳定性的一些常用技术。
<img src="https://static001.geekbang.org/resource/image/be/79/befd21e1b41a257c5028f8c1bc7fa279.png" alt="" />
<li>
**服务拆分**。服务拆分主要有两个目的:一是为了隔离故障,二是为了重用服务模块。但服务拆分完之后,会引入服务调用间的依赖问题。
</li>
<li>
**服务冗余**。服务冗余是为了去除单点故障,并可以支持服务的弹性伸缩,以及故障迁移。然而,对于一些有状态的服务来说,冗余这些有状态的服务带来了更高的复杂性。其中一个是弹性伸缩时,需要考虑数据的复制或是重新分片,迁移的时候还要迁移数据到其它机器上。
</li>
<li>
**限流降级**。当系统实在扛不住压力时,只能通过限流或者功能降级的方式来停掉一部分服务,或是拒绝一部分用户,以确保整个架构不会挂掉。这些技术属于保护措施。
</li>
<li>
**高可用架构**。通常来说高可用架构是从冗余架构的角度来保障可用性。比如,多租户隔离,灾备多活,或是数据可以在其中复制保持一致性的集群。总之,就是为了不出单点故障。
</li>
<li>
**高可用运维**。高可用运维指的是DevOps中的CI/CD持续集成/持续部署)。一个良好的运维应该是一条很流畅的软件发布管线,其中做了足够的自动化测试,还可以做相应的灰度发布,以及对线上系统的自动化控制。这样,可以做到“计划内”或是“非计划内”的宕机事件的时长最短。
</li>
上述这些技术非常有技术含量,而且需要投入大量的时间和精力。
# 分布式系统的关键技术
而通过上面的分析,我们可以看到,引入分布式系统,会引入一堆技术问题,需要从以下几个方面来解决。
<li>
**服务治理**。服务拆分、服务调用、服务发现、服务依赖、服务的关键度定义……服务治理的最大意义是需要把服务间的依赖关系、服务调用链,以及关键的服务给梳理出来,并对这些服务进行性能和可用性方面的管理。
</li>
<li>
**架构软件管理**。服务之间有依赖,而且有兼容性问题,所以,整体服务所形成的架构需要有架构版本管理、整体架构的生命周期管理,以及对服务的编排、聚合、事务处理等服务调度功能。
</li>
<li>
**DevOps**。分布式系统可以更为快速地更新服务但是对于服务的测试和部署都会是挑战。所以还需要DevOps的全流程其中包括环境构建、持续集成、持续部署等。
</li>
<li>
**自动化运维**。有了DevOps后我们就可以对服务进行自动伸缩、故障迁移、配置管理、状态管理等一系列的自动化运维技术了。
</li>
<li>
**资源调度管理**。应用层的自动化运维需要基础层的调度支持也就是云计算IaaS层的计算、存储、网络等资源调度、隔离和管理。
</li>
<li>
**整体架构监控**。如果没有一个好的监控系统,那么自动化运维和资源调度管理只可能成为一个泡影,因为监控系统是你的眼睛。没有眼睛,没有数据,就无法进行高效的运维。所以说,监控是非常重要的部分。这里的监控需要对三层系统(应用层、中间件层、基础层)进行监控。
</li>
<li>
**流量控制**。最后是我们的流量控制,负载均衡、服务路由、熔断、降级、限流等和流量相关的调度都会在这里,包括灰度发布之类的功能也在这里。
</li>
此时,你会发现,要做好这么多的技术,或是要具备这么多的能力,简直就是一个门槛,是一个成本巨高无比的技术栈,看着就都头晕。要实现出来得投入多少人力、物力和时间啊。是的,这就是分布式系统中最大的坑。
不过我们应该庆幸自己生活在了一个非常不错的年代。今天有一个技术叫——Docker通过Docker以及其衍生出来的Kubernetes 之类的软件或解决方案大大地降低了做上面很多事情的门槛。Docker把软件和其运行的环境打成一个包然后比较轻量级地启动和运行。在运行过程中因为软件变成了服务可能会改变现有的环境。但是没关系当你重新启动一个Docker的时候环境又会变成初始化状态。
这样一来我们就可以利用Docker的这个特性来把软件在不同的机器上进行部署、调度和管理。如果没有Docker或是Kubernetes那么你可以认为我们还活在“原始时代”。
现在你知道为什么Docker这样的容器化虚拟化技术是未来了吧。因为分布式系统已经是完全不可逆转的技术趋势了。
但是上面还有很多的技术是Docker及其周边技术没有解决的所以依然还有很多事情要做。那么如果是一个一个地去做这些技术的话就像是我们在撑开一张网里面一个一个的网眼本质上这是使蛮力的做法。我们希望可以找到系统的“纲”一把就能张开整张网。那么这个纲在哪里呢
# 分布式系统的“纲”
总结一下上面讲述的内容,你不难发现,分布式系统有五个关键技术,它们是:
- 全栈系统监控;
- 服务/资源调度;
- 流量调度;
- 状态/数据调度;
- 开发和运维的自动化。
而最后一项——开发和运维的自动化,是需要把前四项都做到了,才有可能实现的。所以,最为关键是下面这四项技术,即应用整体监控、资源和服务调度、状态和数据调度及流量调度,它们是构建分布式系统最最核心的东西。
<img src="https://static001.geekbang.org/resource/image/89/f2/8958a432f32dd742b6503b60f97cc3f2.png" alt="" />
后面的文章中,我会一项一项地解析这些关键技术。
# 小结
回顾一下今天的要点内容。首先,我总结了分布式系统需要干的两件事:一是提高整体架构的吞吐量,服务更多的并发和流量,二是为了提高系统的稳定性,让系统的可用性更高。然后分别从这两个方面阐释,需要通过哪些技术来实现,并梳理出其中的技术难点及可能会带来的问题。最后,欢迎你分享一下你在解决系统的性能和可用性方面使用到的方法和技巧。
虽然Docker及其衍生出来的Kubernetes等软件或解决方案能极大地降低很多事儿的门槛。但它们没有解决的问题还有很多需要掌握分布式系统的五大关键技术从根本上解决问题。后面我将陆续撰写几篇文章一一阐述这几大关键技术详见文末给出的《分布式系统架构的本质》系列文章的目录。
- [分布式系统架构的冰与火](https://time.geekbang.org/column/article/1411)
- [从亚马逊的实践,谈分布式系统的难点](https://time.geekbang.org/column/article/1505)
- [分布式系统的技术栈](https://time.geekbang.org/column/article/1512)
- [分布式系统关键技术:全栈监控](https://time.geekbang.org/column/article/1513)
- [分布式系统关键技术:服务调度](https://time.geekbang.org/column/article/1604)
- [分布式系统关键技术:流量与数据调度](https://time.geekbang.org/column/article/1609)
- [洞悉PaaS平台的本质](https://time.geekbang.org/column/article/1610)
- [推荐阅读:分布式系统架构经典资料](https://time.geekbang.org/column/article/2080)
- [推荐阅读:分布式数据调度相关论文](https://time.geekbang.org/column/article/2421)

View File

@@ -0,0 +1,157 @@
<audio id="audio" title="24 | 分布式系统关键技术:全栈监控" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ae/fa/ae62ff2eb3070b4f336e60021b5d56fa.mp3"></audio>
首先,我们需要全栈系统监控,它就像是我们的眼睛,没有它,我们就不知道系统到底发生了什么,我们将无法管理或是运维整个分布式系统。所以,这个系统是非常非常关键的。
而在分布式或Cloud Native的情况下系统分成多层服务各种关联需要监控的东西特别多。没有一个好的监控系统我们将无法进行自动化运维和资源调度。
这个监控系统需要完成的功能为:
- 全栈监控;
- 关联分析;
- 跨系统调用的串联;
- 实时报警和自动处置;
- 系统性能分析。
# 多层体系的监控
所谓全栈监控,其实就是三层监控。
<li>
**基础层**监控主机和底层资源。比如CPU、内存、网络吞吐、硬盘I/O、硬盘使用等。
</li>
<li>
**中间层**就是中间件层的监控。比如Nginx、Redis、ActiveMQ、Kafka、MySQL、Tomcat等。
</li>
<li>
**应用层**监控应用层的使用。比如HTTP访问的吞吐量、响应时间、返回码、调用链路分析、性能瓶颈还包括用户端的监控。
</li>
<img src="https://static001.geekbang.org/resource/image/cf/66/cf6fe8ee30a3ac3b693d1188b46e4e66.png" alt="" />
这还需要一些监控的标准化。
- 日志数据结构化;
- 监控数据格式标准化;
- 统一的监控平台;
- 统一的日志分析。
# 什么才是好的监控系统
这里还要多说一句,现在我们的很多监控系统都做得很不好,它们主要有两个很大的问题。
<li>
**监控数据是隔离开来的**。因为公司分工的问题,开发、应用运维、系统运维,各管各的,所以很多公司的监控系统之间都有一道墙,完全串不起来。
</li>
<li>
**监控的数据项太多**。有些公司的运维团队把监控的数据项多做为一个亮点到处讲比如监控指标达到5万多个。老实说这太丢人了。因为信息太多等于没有信息抓不住重点的监控才会做成这个样子完全就是使蛮力的做法。
</li>
一个好的监控系统应该有以下几个特征。
<li>
**关注于整体应用的SLA**。主要从为用户服务的API来监控整个系统。
</li>
<li>
**关联指标聚合**。 把有关联的系统及其指标聚合展示。主要是三层系统数据基础层、平台中间件层和应用层。其中最重要的是把服务和相关的中间件以及主机关联在一起服务有可能运行在Docker中也有可能运行在微服务平台上的多个JVM中也有可能运行在Tomcat中。总之无论运行在哪里我们都需要把服务的具体实例和主机关联在一起否则对于一个分布式系统来说定位问题犹如大海捞针。
</li>
<li>
**快速故障定位**。 对于现有的系统来说故障总是会发生的而且还会频繁发生。故障发生不可怕可怕的是故障的恢复时间过长。所以快速地定位故障就相当关键。快速定位问题需要对整个分布式系统做一个用户请求跟踪的trace监控我们需要监控到所有的请求在分布式系统中的调用链这个事最好是做成没有侵入性的。
</li>
换句话说,一个好的监控系统主要是为以下两个场景所设计的。
## “体检”
<li>
**容量管理**。 提供一个全局的系统运行时数据的展示,可以让工程师团队知道是否需要增加机器或者其它资源。
</li>
<li>
**性能管理**。可以通过查看大盘,找到系统瓶颈,并有针对性地优化系统和相应代码。
</li>
## “急诊”
<li>
**定位问题**。可以快速地暴露并找到问题的发生点,帮助技术人员诊断问题。
</li>
<li>
**性能分析**。当出现非预期的流量提升时,可以快速地找到系统的瓶颈,并帮助开发人员深入代码。
</li>
只有做到了上述的这些关键点才能是一个好的监控系统。
# 如何做出一个好的监控系统
下面是我认为一个好的监控系统应该实现的功能。
- **服务调用链跟踪**。这个监控系统应该从对外的API开始然后将后台的实际服务给关联起来然后再进一步将这个服务的依赖服务关联起来直到最后一个服务如MySQL或Redis这样就可以把整个系统的服务全部都串连起来了。这个事情的最佳实践是Google Dapper系统其对应于开源的实现是Zipkin。对于Java类的服务我们可以使用字节码技术进行字节码注入做到代码无侵入式。
如下图所示截图来自我做的一个APM的监控系统
<img src="https://static001.geekbang.org/resource/image/ab/81/ab79054e0a3cf2d8f1d696e3c367ab81.png" alt="" />
- **服务调用时长分布**。使用Zipkin可以看到一个服务调用链上的时间分布这样有助于我们知道最耗时的服务是什么。下图是Zipkin的服务调用时间分布。
<img src="https://static001.geekbang.org/resource/image/5f/4c/5fd70b4194854fc8d55c48987cf3644c.png" alt="" />
- **服务的TOP N视图**。所谓TOP N视图就是一个系统请求的排名情况。一般来说这个排名会有三种排名的方法a按调用量排名b) 按请求最耗时排名c按热点排名一个时间段内的请求次数的响应时间和
<img src="https://static001.geekbang.org/resource/image/f4/f1/f4f91d5a3ee95b478c47f62499b0dcf1.png" alt="" />
- **数据库操作关联**。对于Java应用我们可以很方便地通过JavaAgent字节码注入技术拿到JDBC执行数据库操作的执行时间。对此我们可以和相关的请求对应起来。
<img src="https://static001.geekbang.org/resource/image/29/f4/29587fed0823f6e8ae7a2d38eaf35af4.png" alt="" />
- **服务资源跟踪**。我们的服务可能运行在物理机上也可能运行在虚拟机里还可能运行在一个Docker的容器里Docker容器又运行在物理机或是虚拟机上。我们需要把服务运行的机器节点上的数据如CPU、MEM、I/O、DISK、NETWORK关联起来。
这样一来我们就可以知道服务和基础层资源的关系。如果是Java应用我们还要和JVM里的东西进行关联这样我们才能知道服务所运行的JVM中的情况比如GC的情况
有了这些数据上的关联,我们就可以达到如下的目标。
<li>
当一台机器挂掉是因为CPU或I/O过高的时候我们马上可以知道其会影响到哪些对外服务的API。
</li>
<li>
当一个服务响应过慢的时候我们马上能关联出来是否在做Java GC或是其所在的计算结点上是否有资源不足的情况或是依赖的服务是否出现了问题。
</li>
<li>
当发现一个SQL操作过慢的时候我们能马上知道其会影响哪个对外服务的API。
</li>
<li>
当发现一个消息队列拥塞的时候我们能马上知道其会影响哪些对外服务的API。
</li>
总之,我们就是想知道用户访问哪些请求会出现问题,这对于我们了解故障的影响面非常有帮助。
一旦了解了这些信息,我们就可以做出调度。比如:
<li>
一旦发现某个服务过慢是因为CPU使用过多我们就可以做弹性伸缩。
</li>
<li>
一旦发现某个服务过慢是因为MySQL出现了一个慢查询我们就无法在应用层上做弹性伸缩只能做流量限制或是降级操作了。
</li>
所以一个分布式系统或是一个自动化运维系统或是一个Cloud Native的云化系统最重要的事就是把监控系统做好。在把数据收集好的同时更重要的是把数据关联好。这样我们才可能很快地定位故障进而才能进行自动化调度。
<img src="https://static001.geekbang.org/resource/image/6b/33/6b17dd779cfecd62e02924dc8618e833.png" alt="" />
上图只是简单地展示了一个分布式系统的服务调用链接上都在报错其根本原因是数据库链接过多服务不过来。另外一个原因是Java在做Full GC导致处理过慢。于是消息队列出现消息堆积堵塞。这个图只是一个示例其形象地体现了在分布式系统中监控数据关联的重要性。
# 小结
回顾一下今天的要点内容。首先,我强调了全栈系统监控的重要性,它就像是我们的眼睛,没有它,我们根本就不知道系统到底发生了什么。随后,从基础层、中间层和应用层三个层面,讲述了全栈监控系统要监控哪些内容。然后,阐释了什么才是好的监控系统,以及如何做出好的监控。最后,欢迎你分享一下你在监控系统中的比较好的实践和方法。
下一篇文章中,我将讲述分布式系统的另一关键技术:服务调度。
下面我列出了《分布式系统架构的本质》系列文章的目录,方便你快速找到自己感兴趣的内容。
- [分布式系统架构的冰与火](https://time.geekbang.org/column/article/1411)
- [从亚马逊的实践,谈分布式系统的难点](https://time.geekbang.org/column/article/1505)
- [分布式系统的技术栈](https://time.geekbang.org/column/article/1512)
- [分布式系统关键技术:全栈监控](https://time.geekbang.org/column/article/1513)
- [分布式系统关键技术:服务调度](https://time.geekbang.org/column/article/1604)
- [分布式系统关键技术:流量与数据调度](https://time.geekbang.org/column/article/1609)
- [洞悉PaaS平台的本质](https://time.geekbang.org/column/article/1610)
- [推荐阅读:分布式系统架构经典资料](https://time.geekbang.org/column/article/2080)
- [推荐阅读:分布式数据调度相关论文](https://time.geekbang.org/column/article/2421)

View File

@@ -0,0 +1,190 @@
<audio id="audio" title="25 | 分布式系统关键技术:服务调度" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7e/ed/7ed8fecbbb32ac2a0781e68f2e3ad6ed.mp3"></audio>
服务治理,你应该听得很多了。但是我想说,你所听到的服务治理可能混合了流量调度等其它内容。我们这里会把服务治理和流量调度分开来讲。所以,今天这篇文章只涉及服务治理上的一些关键技术,主要有以下几点。
- 服务关键程度
- 服务依赖关系
- 服务发现
- 整个架构的版本管理
- 服务应用生命周期全管理
# 服务关键程度和服务的依赖关系
下面,我们先看看服务关键程度和服务的依赖关系。关于服务关键程度,主要是要我们梳理和定义服务的重要程度。这不是使用技术可以完成的,它需要细致地管理对业务的理解,才能定义出架构中各个服务的重要程度。
然后,我们还要梳理出服务间的依赖关系,这点也非常重要。我们常说,“没有依赖,就没有伤害”。这句话的意思就是说,服务间的依赖是一件很易碎的事。依赖越多,依赖越复杂,我们的系统就越易碎。
因为依赖关系就像“铁锁连环”一样一个服务的问题很容易出现一条链上的问题。因此传统的SOA希望通过ESB来解决服务间的依赖关系这也是为什么微服务中希望服务间是没有依赖的而让上层或是前端业务来整合这些个后台服务。
但是要真正做到服务无依赖我认为还是比较有困难的总是会有一些公有服务会被依赖。我们只能是降低服务依赖的深度和广度从而让管理更为简单和简洁。在这一点上以Spring Boot为首的微服务开发框架就开了一个好头。
**微服务是服务依赖最优解的上限,而服务依赖的下限是千万不要有依赖环**。如果系统架构中有服务依赖环,那么表明你的架构设计是错误的。循环依赖有很多的副作用,最大的问题是这是一种极强的耦合,会导致服务部署相当复杂和难解,而且会导致无穷尽的递归故障和一些你意想不到的问题。
解决服务依赖环的方案一般是,依赖倒置的设计模式。在分布式架构上,你可以使用一个第三方的服务来解决这个事。比如,通过订阅或发布消息到一个消息中间件,或是把其中的依赖关系抽到一个第三方的服务中,然后由这个第三方的服务来调用这些原本循环依赖的服务。
服务的依赖关系是可以通过技术的手段来发现的,这其中,[Zipkin](https://zipkin.io/)是一个很不错的服务调用跟踪系统,它是通过 [Google Dapper](https://research.google.com/pubs/pub36356.html)这篇论文来实现的。这个工具可以帮你梳理服务的依赖关系,以及了解各个服务的性能。
在梳理完服务的重要程度和服务依赖关系之后,我们就相当于知道了整个架构的全局。就好像我们得到了一张城市地图,在这张地图上可以看到城市的关键设施,以及城市的主干道。再加上相关的监控,我们就可以看到城市各条道路上的工作和拥堵情况。这对于我们整个分布式架构是非常非常关键的。
我给很多公司做过相关的咨询。当他们需要我帮忙解决一些高并发或是架构问题的时候,我一般都会向他们要一张这样的“地图”,但是几乎所有的公司都没有这样的地图。
# 服务状态和生命周期的管理
有了上面这张地图后,我们还需要有一个服务发现的中间件,这个中间件是非常非常关键的。因为这个“架构城市”是非常动态的,有的服务会新加进来,有的会离开,有的会增加更多的实例,有的会减少,有的服务在维护过程中(发布、伸缩等),所以我们需要有一个服务注册中心,来知道这么几个事。
- 整个架构中有多少种服务?
- 这些服务的版本是什么样的?
- 每个服务的实例数有多少个,它们的状态是什么样的?
- 每个服务的状态是什么样的?是在部署中,运行中,故障中,升级中,还是在回滚中,伸缩中,或者是在下线中……
这个服务注册中心有点像我们系统运维同学说的CMDB这样的东西它也是非常之关键的因为没有它我们将无法知道这些服务运作的状态和情况。
有了这些服务的状态和运行情况之后,你就需要对这些服务的生命周期进行管理了。服务的生命周期通常会有以下几个状态:
- Provision代表在供应一个新的服务
- Ready表示启动成功了
- Run表示通过了服务健康检查
- Update表示在升级中
- Rollback表示在回滚中
- Scale表示正在伸缩中可以有Scale-in和Scale-out两种
- Destroy表示在销毁中
- Failed表示失败状态。
这几个状态需要管理好,不然的话,你将不知道这些服务在什么样的状态下。不知道在什么样的状态下,你对整个分布式架构也就无法控制了。
有了这些服务的状态和生命周期的管理,以及服务的重要程度和服务的依赖关系,再加上一个服务运行状态的拟合控制(后面会提到),你一下子就有了管理整个分布式服务的手段了。
一个纷乱无比的世界从此就可以干干净净地管理起来了。
# 整个架构的版本管理
对于整个架构的版本管理这个事我只见到亚马逊有这个东西叫VersionSet也就是由一堆服务的版本集所形成的整个架构的版本控制。
除了各个项目的版本管理之外还需要在上面再盖一层版本管理。如果Build过Linux分发包那么你就会知道Linux分发包中各个软件的版本上会再盖一层版本控制。毕竟这些分发包也是有版本依赖的这样可以解决各个包的版本兼容性问题。
所以在分布式架构中我们也需要一个架构的版本用来控制其中各个服务的版本兼容。比如A服务的1.2版本只能和B服务的2.2版本一起工作A服务的上个版本1.1只能和B服务的2.0一起工作。这就是版本兼容性。
如果架构中有这样的问题,那么我们就需要一个上层架构的版本管理。这样,如果我们要回滚一个服务的版本,就可以把与之有版本依赖的服务也一起回滚掉。
当然,一般来说,在设计过程中,我们希望没有版本的依赖性问题。但可能有些时候,我们会有这样的问题,那么就需要在架构版本中记录下这个事,以便可以回滚到上一次相互兼容的版本。
要做到这个事你需要一个架构的manifest一个服务清单这个服务清单定义了所有服务的版本运行环境其中包括但不限于
- 服务的软件版本;
- 服务的运行环境——环境变量、CPU、内存、可以运行的结点、文件系统等
- 服务运行的最大最小实例数。
每一次对这个清单的变更都需要被记录下来,算是一个架构的版本管理。而我们上面所说的那个集群控制系统需要能够解读并执行这个清单中的变更,以操作和管理整个集群中的相关变更。
# 资源/服务调度
服务和资源的调度有点像操作系统。操作系统一方面把用户进程在硬件资源上进行调度,另一方面提供进程间的通信方式,可以让不同的进程在一起协同工作。服务和资源调度的过程,与操作系统调度进程的方式很相似,主要有以下一些关键技术。
- 服务状态的维持和拟合。
- 服务的弹性伸缩和故障迁移。
- 作业和应用调度。
- 作业工作流编排。
- 服务编排。
## 服务状态的维持和拟合
所谓服务状态不是服务中的数据状态而是服务的运行状态换句话说就是服务的Status而不是State。也就是上述服务运行时生命周期中的状态——ProvisionReadyRunScaleRollbackUpdateDestroyFailed……
服务运行时的状态是非常关键的。服务运行过程中,状态也是会有变化的,这样的变化有两种。
<li>
一种是不预期的变化。比如,服务运行因为故障导致一些服务挂掉,或是别的什么原因出现了服务不健康的状态。而一个好的集群管理控制器应该能够强行维护服务的状态。在健康的实例数变少时,控制器会把不健康的服务给摘除,而又启动几个新的,强行维护健康的服务实例数。
</li>
<li>
另外一种是预期的变化。比如,我们需要发布新版本,需要伸缩,需要回滚。这时,集群管理控制器就应该把集群从现有状态迁移到另一个新的状态。这个过程并不是一蹴而就的,集群控制器需要一步一步地向集群发送若干控制命令。这个过程叫“拟合”——从一个状态拟合到另一个状态,而且要穷尽所有的可能,玩命地不断地拟合,直到达到目的。
</li>
详细说明一下对于分布式系统的服务管理来说当需要把一个状态变成另一个状态时我们需要对集群进行一系列的操作。比如当需要对集群进行Scale的时候我们需要
- 先扩展出几个结点;
- 再往上部署服务;
- 然后启动服务;
- 再检查服务的健康情况;
- 最后把新扩展出来的服务实例加入服务发现中提供服务。
可以看到这是一个比较稳健和严谨的Scale过程这需要集群控制器往生产集群中进行若干次操作。
这个操作的过程一定是比较“慢”的。一方面,需要对其它操作排它;另一方面,在整个过程中,我们的控制系统需要努力地逼近最终状态,直到完全达到。此外,正在运行的服务可能也会出现问题,离开了我们想要的状态,而控制系统检测到后,会强行地维持服务的状态。
**我们把这个过程就叫做“拟合”**。基本上来说集群控制系统都是要干这个事的。没有这种设计的控制系统都不能算做设计精良的控制系统而且在运行时一定会有很多的坑和bug。
如果研究过Kubernetes这个调度控制系统你就会看到它的思路就是这个样子的。
## 服务的弹性伸缩和故障迁移
有了上述的服务状态拟合的基础工作之后,我们就能很容易地管理服务的生命周期了,甚至可以通过底层的支持进行便利的服务弹性伸缩和故障迁移。
对于弹性伸缩,在上面我已经给出了一个服务伸缩所需要的操作步骤。还是比较复杂的,其中涉及到了:
- 底层资源的伸缩;
- 服务的自动化部署;
- 服务的健康检查;
- 服务发现的注册;
- 服务流量的调度。
而对于故障迁移,也就是服务的某个实例出现问题时,我们需要自动地恢复它。对于服务来说,有两种模式,一种是宠物模式,一种是奶牛模式。
- 所谓宠物模式就是一定要救活主要是对于stateful 的服务。
- 而奶牛模式,就是不救活了,重新生成一个实例。
对于这两种模式,在运行中也是比较复杂的,其中涉及到了:
- 服务的健康监控这可能需要一个APM的监控
- 如果是宠物模式,需要:服务的重新启动和服务的监控报警(如果重试恢复不成功,需要人工介入)。
- 如果是奶牛模式,需要:服务的资源申请,服务的自动化部署,服务发现的注册,以及服务的流量调度。
我们可以看到,弹性伸缩和故障恢复需要很相似的技术步骤。但是,要完成这些事情并不容易,你需要做很多工作,而且有很多细节上的问题会让你感到焦头烂额。
当然好消息是我们非常幸运地生活在了一个比较不错的时代因为有Docker和Kubernetes这样的技术可以非常容易地让我们做这个工作。
但是需要把传统的服务迁移到Docker和Kubernetes上来再加上更上层的对服务生命周期的控制系统的调度我们就可以做到一个完全自动化的运维架构了。
## 服务工作流和编排
正如上面和操作系统做的类比一样一个好的操作系统需要能够通过一定的机制把一堆独立工作的进程给协同起来。在分布式的服务调度中这个工作叫做Orchestration国内把这个词翻译成“编排”。
从《分布式系统架构的冰与火》一文中的SOA架构演化图来看要完成这个编排工作传统的SOA是通过ESBEnterprise Service Bus——企业服务总线来完成的。ESB的主要功能是服务通信路由、协议转换、服务编制和业务规则应用等。
注意ESB的服务编制叫Choreography与我们说的Orchestration是不一样的。
<li>
Orchestration的意思是一个服务像大脑一样来告诉大家应该怎么交互就跟乐队的指挥一样。查看[Service-oriented DesignA Multi-viewpoint Approach](https://eprints.qut.edu.au/622/1/SOD_%28revised%29.pdf),了解更多信息)。
</li>
<li>
Choreography的意思是在各自完成专属自己的工作的基础上怎样互相协作就跟芭蕾舞团的舞者一样。
</li>
而在微服务中我们希望使用更为轻量的中间件来取代ESB的服务编排功能。
简单来说这需要一个API Gateway或一个简单的消息队列来做相应的编排工作。在Spring Cloud中所有的请求都统一通过API GatewayZuul来访问内部的服务。这个和Kubernetes中的Ingress相似。
我觉得关于服务的编排会直接导致一个服务编排的工作流引擎中间件的产生这可能是因为我受到了亚马逊的软件工程文化的影响所致——亚马逊是一家超级喜欢工作流引擎的公司。通过工作流引擎可以非常快速地将若干个服务编排起来形成一个业务流程。你可以看一下AWS上的Simple Workflow服务。
这就是所谓的Orchestration中的conductor 指挥了。
# 小结
好了,今天的分享就这些。总结一下今天的主要内容:我们从服务关键程度、服务依赖关系、整个架构的版本管理等多个方面,全面阐述了分布式系统架构五大关键技术之一——服务资源调度。希望这些内容能对你有所启发。
你现在的公司中是怎样管理和运维线上的服务的呢?欢迎分享一下你的经验和方法。
下一篇文章中,我们将从流量调度和状态数据调度两个方面,来接着聊分布式系统关键技术。
文末有系列文章《分布式系统架构的本质》的目录,供你查看,方便你找到自己感兴趣的内容。
- [分布式系统架构的冰与火](https://time.geekbang.org/column/article/1411)
- [从亚马逊的实践,谈分布式系统的难点](https://time.geekbang.org/column/article/1505)
- [分布式系统的技术栈](https://time.geekbang.org/column/article/1512)
- [分布式系统关键技术:全栈监控](https://time.geekbang.org/column/article/1513)
- [分布式系统关键技术:服务调度](https://time.geekbang.org/column/article/1604)
- [分布式系统关键技术:流量与数据调度](https://time.geekbang.org/column/article/1609)
- [洞悉PaaS平台的本质](https://time.geekbang.org/column/article/1610)
- [推荐阅读:分布式系统架构经典资料](https://time.geekbang.org/column/article/2080)
- [推荐阅读:分布式数据调度相关论文](https://time.geekbang.org/column/article/2421)

View File

@@ -0,0 +1,146 @@
<audio id="audio" title="26 | 分布式系统关键技术:流量与数据调度" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fd/e7/fd4a81af29ceabe9e6a12bdffd496ee7.mp3"></audio>
关于流量调度现在很多架构师都把这个事和服务治理混为一谈了。我觉得还是应该分开的。一方面服务治理是内部系统的事而流量调度可以是内部的更是外部接入层的事。另一方面服务治理是数据中心的事而流量调度要做得好应该是数据中心之外的事也就是我们常说的边缘计算是应该在类似于CDN上完成的事。
所以,流量调度和服务治理是在不同层面上的,不应该混在一起,所以在系统架构上应该把它们分开。
# 流量调度的主要功能
对于一个流量调度系统来说,其应该具有的主要功能是:
<li>
依据系统运行的情况,自动地进行流量调度,在无需人工干预的情况下,提升整个系统的稳定性;
</li>
<li>
让系统应对爆品等突发事件时,在弹性计算扩缩容的较长时间窗口内或底层资源消耗殆尽的情况下,保护系统平稳运行。
</li>
这还是为了提高系统架构的稳定性和高可用性。
此外,这个流量调度系统还可以完成以下几方面的事情。
- **服务流控**。服务发现、服务路由、服务降级、服务熔断、服务保护等。
- **流量控制**。负载均衡、流量分配、流量控制、异地灾备(多活)等。
- **流量管理**。协议转换、请求校验、数据缓存、数据计算等。
所有的这些都应该是一个API Gateway应该做的事。
# 流量调度的关键技术
但是作为一个API Gateway来说因为要调度流量首先需要扛住流量而且还需要有一些比较轻量的业务逻辑所以一个好的API Gateway需要具备以下的关键技术。
<li>
**高性能**。API Gateway必须使用高性能的技术所以也就需要使用高性能的语言。
</li>
<li>
**扛流量**。要能扛流量就需要使用集群技术。集群技术的关键点是在集群内的各个结点中共享数据。这就需要使用像Paxos、Raft、Gossip这样的通讯协议。因为Gateway需要部署在广域网上所以还需要集群的分组技术。
</li>
<li>
**业务逻辑**。API Gateway需要有简单的业务逻辑所以最好是像AWS的Lambda 服务一样,可以让人注入不同语言的简单业务逻辑。
</li>
<li>
**服务化**。一个好的API Gateway需要能够通过Admin API来不停机地管理配置变更而不是通过一个.conf文件来人肉地修改配置。
</li>
基于上述的这几个技术要求就其本质来说目前可以做成这样的API Gateway几乎没有。这也是为什么我现在自己自主开发的原因你可以到我的官网MegaEase.com上查看相关的产品和技术信息
# 状态数据调度
对于服务调度来说最难办的就是有状态的服务了。这里的状态是State也就是说有些服务会保存一些数据而这些数据是不能丢失的所以这些数据是需要随服务一起调度的。
一般来说我们会通过“转移问题”的方法来让服务变成“无状态的服务”。也就是说会把这些有状态的东西存储到第三方服务上比如Redis、MySQL、ZooKeeper或是NFS、Ceph的文件系统中。
这些“转移问题”的方式把问题转移到了第三方服务上于是自己的Java或PHP服务中没有状态但是Redis和MySQL上则有了状态。所以我们可以看到现在的分布式系统架构中出问题的基本都是这些存储状态的服务。
因为数据存储结点在Scale上比较困难所以成了一个单点的瓶颈。
# 分布式事务一致性的问题
要解决数据结点的Scale问题也就是让数据服务可以像无状态的服务一样在不同的机器上进行调度这就会涉及数据的replication问题。而数据replication则会带来数据一致性的问题进而对性能带来严重的影响。
要解决数据不丢失的问题,只能通过数据冗余的方法,就算是数据分区,每个区也需要进行数据冗余处理。这就是数据副本。当出现某个节点的数据丢失时,可以从副本读到。数据副本是分布式系统解决数据丢失异常的唯一手段。简单来说:
1. 要想让数据有高可用性,就得写多份数据。
1. 写多份会引起数据一致性的问题。
1. 数据一致性的问题又会引发性能问题。
在解决数据副本间的一致性问题时,我们有一些技术方案。
- Master-Slave方案。
- Master-Master方案。
- 两阶段和三阶段提交方案。
- Paxos方案。
你可以仔细地读一下我在3年前写的[《分布式系统的事务处理》这篇文章](https://coolshell.cn/articles/10910.html)。其中我引用了Google App Engine联合创始人赖安·巴里特Ryan Barrett在2009年Google I/O上的演讲[Transaction Across DataCenter视频](http://www.youtube.com/watch?v=srOgpXECblk) 中的一张图。
<img src="https://static001.geekbang.org/resource/image/e5/ec/e566933d9967f2f5e0f4dcddc66247ec.png" alt="" />
从上面这张经典的图中,我们可以看到各种不同方案的对比。
现在很多公司的分布式系统事务基本上都是两阶段提交的变种。比如阿里推出的TCCTryConfirmCancel或是我在亚马逊见到的PlanReserveConfirm的方式等等。凡是通过业务补偿或是在业务应用层上做的分布式事务的玩法基本上都是两阶段提交或是两阶段提交的变种。
换句话说迄今为止在应用层上解决事务问题只有“两阶段提交”这样的方式而在数据层解决事务问题Paxos算法则是不二之选。
# 数据结点的分布式方案
真正完整解决数据Scale问题的应该还是数据结点自身。只有数据结点自身解决了这个问题才能做到对上层业务层的透明业务层可以像操作单机数据库一样来操作分布式数据库这样才能做到整个分布式服务架构的调度。
也就是说这个问题应该解决在数据存储方。但是因为数据存储结果有太多不同的Scheme所以现在的数据存储也是多种多样的有文件系统有对象型的有Key-Value式有时序的有搜索型的有关系型的……
这就是为什么分布式数据存储系统比较难做,因为很难做出来一个放之四海皆准的方案。类比一下编程中的各种不同的数据结构你就会明白为什么会有这么多的数据存储方案了。
但是我们可以看到,这个“数据存储的动物园”中,基本上都在解决数据副本、数据一致性和分布式事务的问题。
比如AWS的Aurora就是改写了MySQL的InnoDB引擎。为了承诺高可用的SLA所以需要写6个副本但实现方式上它不像MySQL通过bin log的数据复制方式而是更为“惊艳”地复制SQL语句然后拼命地使用各种tricky的方式来降低latency。比如使用多线程并行、使用SQL操作的merge等。
MySQL官方也有MySQL Cluster的技术方案。此外MongoDB、国内的PingCAP的TiDB、国外的CockroachDB还有阿里的OceanBase都是为了解决大规模数据的写入和读取的问题而出现的数据库软件。所以我觉得成熟的可以用到生产线上的分布式数据库这个事估计也不远了。
而对于一些需要文件存储的则需要分布式文件系统的支持。试想一个Kafka或ZooKeeper需要把它们的数据存储到文件系统上。当这个结点有问题时我们需要再启动一个Kafka或ZooKeeper的实例那么也需要把它们持久化的数据搬迁到另一台机器上。
注意虽然Kafka和ZooKeeper是HA的数据会在不同的结点中进行复制但是我们也应该搬迁数据这样有利用于新结点的快速启动。否则新的结点需要等待数据同步这个时间会比较长可能会导致数据层的其它问题。
于是我们就需要一个底层是分布式的文件系统这样新的结点只需要做一个简单的远程文件系统的mount就可以把数据调度到另外一台机器上了。
所以真正解决数据结点调度的方案应该是底层的数据结点。在它们上面做这个事才是真正有效和优雅的。而像阿里的用于分库分表的数据库中间件TDDL或是别的公司叫什么DAL之类的这样的中间件都会成为过渡技术。
## 状态数据调度小结
接下来,我们对状态数据调度做个小小的总结。
<li>
对于应用层上的分布式事务一致性,只有两阶段提交这样的方式。
</li>
<li>
而底层存储可以解决这个问题的方式是通过一些像Paxos、Raft或是NWR这样的算法和模型来解决。
</li>
<li>
状态数据调度应该是由分布式存储系统来解决的这样会更为完美。但是因为数据存储的Scheme太多所以导致我们有各式各样的分布式存储系统有文件对象的有关系型数据库的有NoSQL的有时序数据的有搜索数据的有队列的……
</li>
总之我相信状态数据调度应该是在IaaS层的数据存储解决的问题而不是在PaaS层或者SaaS层来解决的。
在IaaS层上解决这个问题一般来说有三种方案一种是使用比较廉价的开源产品NFS、Ceph、TiDB、CockroachDB、ElasticSearch、InfluxDB、MySQL Cluster和Redis Cluster之类的另一种是用云计算厂商的方案。当然如果不差钱的话可以使用更为昂贵的商业网络存储方案。
# 小结
回顾一下今天分享的主要内容。首先,我先明确表态,不要将流量调度和服务治理混为一谈(当然,服务治理是流量调度的前提),并比较了两者有何不同。
然后讲述了流量调度的主要功能和关键技术。接着进入本文的第二个话题——状态数据调度讲述了真正完整解决数据Scale问题的应该还是数据结点自身并给出了相应的技术方案随后对状态数据调度进行了小结。
欢迎你也谈一谈自己经历过的技术场景中是采用了哪些流量和数据调度的技术和产品,遇到过什么样的问题,是怎样解决的?
下篇文章中我们将开启一个全新的话题——洞悉PaaS平台的本质。
下面我列出了系列文章《分布式系统架构的本质》的目录,以方便你快速找到自己感兴趣的内容。如果你在分布式系统架构方面,有其他想了解的话题和内容,欢迎留言给我。
- [分布式系统架构的冰与火](https://time.geekbang.org/column/article/1411)
- [从亚马逊的实践,谈分布式系统的难点](https://time.geekbang.org/column/article/1505)
- [分布式系统的技术栈](https://time.geekbang.org/column/article/1512)
- [分布式系统关键技术:全栈监控](https://time.geekbang.org/column/article/1513)
- [分布式系统关键技术:服务调度](https://time.geekbang.org/column/article/1604)
- [分布式系统关键技术:流量与数据调度](https://time.geekbang.org/column/article/1609)
- [洞悉PaaS平台的本质](https://time.geekbang.org/column/article/1610)
- [推荐阅读:分布式系统架构经典资料](https://time.geekbang.org/column/article/2080)
- [推荐阅读:分布式数据调度相关论文](https://time.geekbang.org/column/article/2421)

View File

@@ -0,0 +1,134 @@
<audio id="audio" title="27 | 洞悉PaaS平台的本质" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/12/51/12f765e1e962ad2479c633f2ba64d451.mp3"></audio>
在了解了前面几篇文章中提的这些问题以后,我们需要思考一下该怎样解决这些问题。为了解决这些问题,请先允许我来谈谈软件工程的本质。
我认为,一家商业公司的软件工程能力主要体现在三个地方。
**第一提高服务的SLA。**
所谓服务的SLA也就是我们能提供多少个9的系统可用性而每提高一个9的可用性都是对整个系统架构的重新洗礼。在我看来提高系统的SLA主要表现在两个方面
- 高可用的系统;
- 自动化的运维。
你可以看一下我在CoolShell上写的《[关于高可用系统](https://coolshell.cn/articles/17459.html)》这篇文章它主要讲了构建高可用的系统需要使用的分布式系统设计思路。然而这还不够我们还需要一个高度自动化的运维和管理系统因为故障是常态如果没有自动化的故障恢复就很难提高服务的SLA。
**第二,能力和资源重用或复用。**
软件工程还有一个重要的能力就是让能力和资源可以重用。其主要表现在如下两个方面:
- 软件模块的重用;
- 软件运行环境和资源的重用。
为此,需要我们有两个重要的能力:一个是“软件抽象的能力”,另一个是“软件标准化的能力”。你可以认为软件抽象就是找出通用的软件模块或服务,软件标准化就是使用统一的软件通讯协议、统一的开发和运维管理方法……这样能让整体软件开发运维的能力和资源得到最大程度的复用,从而增加效率。
**第三,过程的自动化。**
编程本来就是把一个重复工作自动化的过程,所以,**软件工程的第三个本质就是把软件生产和运维的过程自动化起来**。也就是下面这两个方面:
- 软件生产流水线;
- 软件运维自动化。
为此我们除了需要CI/CD的DevOps式的自动化之外也需要能够对正在运行的生产环境中的软件进行自动化运维。
通过了解软件工程的这三个本质,你会发现,我们上面所说的那些分布式的技术点是高度一致的,也就是下面这三个方面的能力。(是的,世界就是这样的。当参透了本质之后,你会发现世界是大同的。)
- 分布式多层的系统架构。
- 服务化的能力供应。
- 自动化的运维能力。
只有做到了这些我们才能够真正拥有云计算的威力。这就是所谓的Cloud Native。而这些目标都完美地体现在PaaS平台上。
前面讲述的分布式系统关键技术和软件工程的本质都可以在PaaS平台上得到完全体现。所以需要一个PaaS平台把那么多的东西给串联起来。这里我结合自己的认知给你讲一下PaaS相关的东西并把前面讲过的所有东西做一个总结。
# PaaS平台的本质
一个好的PaaS平台应该具有分布式、服务化、自动化部署、高可用、敏捷以及分层开放的特征并可与IaaS实现良好的联动。
<img src="https://static001.geekbang.org/resource/image/96/0b/966f319745518156545e34d85eee010b.png" alt="" />
下面这三件事是PaaS跟传统中间件最大的差别。
- **服务化是PaaS的本质**。软件模块重用服务治理对外提供能力是PaaS的本质。
- **分布式是PaaS的根本特性**。多租户隔离、高可用、服务编排是PaaS的基本特性。
- **自动化是PaaS的灵魂**。自动化部署安装运维自动化伸缩调度是PaaS的关键。
## PaaS平台的总体架构
从下面的图中可以看到我用了Docker+Kubernetes层来做了一个“技术缓冲层”。也就是说如果没有Docker和Kubernetes构建PaaS将会复杂很多。当然如果你正在开发一个类似PaaS的平台那么你会发现自己开发出来的东西会跟Docker和Kubernetes非常像。相信我最终你还是会放弃自己的轮子而采用Docker+Kubernetes的。
<img src="https://static001.geekbang.org/resource/image/f6/68/f65ccf66daf8d01d59fa8948c8136c68.png" alt="" />
在Docker+Kubernetes层之上我们看到了两个相关的PaaS层。一个是PaaS调度层很多人将其称为iPaaS另一个是PaaS能力层通常被称为aPaaS。没有PaaS调度层PaaS能力层很难被管理和运维而没有PaaS能力层PaaS就失去了提供实际能力的业务价值。而本文更多的是在讲PaaS调度层上的东西。
在两个相关的PaaS层之上有一个流量调度的接入模块这也是PaaS中非常关键的东西。流控、路由、降级、灰度、聚合、串联等等都在这里包括最新的AWS Lambda Service的小函数等也可以放在这里。这个模块应该是像CDN那样来部署的。
然后在这个图的两边分别是与运营和运维相关的。运营这边主要是管理一些软件资源方面的东西类似Docker Hub和CMDB以及外部接入和开放平台上的东西这主要是对外提供能力的相关组件而运维这边主要是对内的相关东西主要就是DevOps。
总结一下一个完整的PaaS平台会包括以下几部分。
- PaaS调度层 主要是PaaS的自动化和分布式对于高可用高性能的管理。
- PaaS能力服务层 主要是PaaS真正提供给用户的服务和能力。
- PaaS的流量调度 主要是与流量调度相关的东西,包括对高并发的管理。
- PaaS的运营管理 软件资源库、软件接入、认证和开放平台门户。
- PaaS的运维管理 主要是DevOps相关的东西。
因为我画的是一个大而全的东西所以看上去似乎很重很复杂。实际上其中的很多组件是可以根据自己的需求被简化和裁剪的而且很多开源软件能帮你简化好多工作。虽然构建PaaS平台看上去很麻烦但是其实并不是很复杂不要被我吓到了。哈哈。
## PaaS平台的生产和运维
下面的图我给出了一个大概的软件生产、运维和服务接入的流程,它把之前的东西都串起来了。
<img src="https://static001.geekbang.org/resource/image/61/dd/61b89202b59959df224ae8ff29bdf0dd.png" alt="" />
从左上开始软件构建进入软件资产库Docker Registry+一些软件的定义然后走DevOps的流程通过整体架构控制器进入生产环境生产环境通过控制器操作Docker+Kubernetes集群进行软件部署和生产变更。
其中,同步服务的运行状态,并通过生命周期管理来拟合状态,如图右侧部分所示。服务运行时的数据会进入到相关应用监控,应用监控中的一些监控事件会同步到生命周期管理中,再由生命周期管理器来做出决定,通过控制器来调度服务运行。当应用监控中心发现流量变化,要进行强制性伸缩时,它通过生命周期管理来通知控制系统进行伸缩。
左下是服务接入的相关组件主要是网关服务以及API聚合编排和流程处理。这对应于之前说过的流量调度和API Gateway的相关功能。
# 总结
恭喜你已经听完了《分布式系统架构的本质》系列文章的7篇文章。下面我们对这些内容做一下总结。
传统的单体架构系统容量显然是有上限的。同时,为了应对有计划和无计划的下线时间,系统的可用性也是有其极限的。分布式系统为以上两个问题提供了解决方案,并且还附带有其他优势。但是,要同时解决这两个问题决非易事。为了构建分布式系统,我们面临的主要问题如下。
- 分布式系统的硬件故障发生率更高,故障发生是常态,需要尽可能地将运维流程自动化。
- 需要良好地设计服务,避免某服务的单点故障对依赖它的其他服务造成大面积影响。
- 为了容量的可伸缩性,服务的拆分、自治和无状态变得更加重要,可能需要对老的软件逻辑做大的修改。
- 老的服务可能是异构的,此时需要让它们使用标准的协议,以便可以被调度、编排,且互相之间可以通信。
- 服务软件故障的处理也变得复杂,需要优化的流程,以加快故障的恢复。
- 为了管理各个服务的容量,让分布式系统发挥出最佳性能,需要有流量调度技术。
- 分布式存储会让事务处理变得复杂;在事务遇到故障无法被自动恢复的情况下,手动恢复流程也会变得复杂。
- 测试和查错的复杂度增大。
- 系统的吞吐量会变大,但响应时间会变长。
为了解决这些问题,我们深入了解了以下这些解决方案。
- 需要有完善的监控系统,以便对服务运行状态有全面的了解。
- 设计服务时要分析其依赖链;当非关键服务故障时,其他服务要自动降级功能,避免调用该服务。
- 重构老的软件使其能被服务化可以参考SOA和微服务的设计方式目标是微服务化使用Docker和Kubernetes来调度服务。
- 为老的服务编写接口逻辑来使用标准协议,或在必要时重构老的服务以使得它们有这些功能。
- 自动构建服务的依赖地图,并引入好的处理流程,让团队能以最快速度定位和恢复故障,详见《故障处理最佳实践:应对故障》一文。
- 使用一个API Gateway它具备服务流向控制、流量控制和管理的功能。
- 事务处理建议在存储层实现根据业务需求或者降级使用更简单、吞吐量更大的最终一致性方案或者通过二阶段提交、Paxos、Raft、NWR等方案之一使用吞吐量小的强一致性方案。
- 通过更真实地模拟生产环境,乃至在生产环境中做灰度发布,从而增加测试强度;同时做充分的单元测试和集成测试以发现和消除缺陷;最后,在服务故障发生时,相关的多个团队同时上线自查服务状态,以最快地定位故障原因。
- 通过异步调用来减少对短响应时间的依赖;对关键服务提供专属硬件资源,并优化软件逻辑以缩短响应时间。
你已经看到,解决分布式服务的吞吐量和可用性问题不是件容易的事,以及目前的主流技术是怎么办到的。衍生出来的许多子问题,每一个都值得去细化、去研究其解决方案。这已经超出本文的篇幅所能及的了,但的确都是值得我们做技术的人去深入思考的。
在这里,我想邀请你来讨论一下,你在分布式系统的哪个领域研究得比较深?有什么独特的心得能与我们分享?期待你的留言。
本文末尾给出了《分布式系统架构的本质》系列文章的目录,方便你查找自己关注的内容。
- [分布式系统架构的冰与火](https://time.geekbang.org/column/article/1411)
- [从亚马逊的实践,谈分布式系统的难点](https://time.geekbang.org/column/article/1505)
- [分布式系统的技术栈](https://time.geekbang.org/column/article/1512)
- [分布式系统关键技术:全栈监控](https://time.geekbang.org/column/article/1513)
- [分布式系统关键技术:服务调度](https://time.geekbang.org/column/article/1604)
- [分布式系统关键技术:流量与数据调度](https://time.geekbang.org/column/article/1609)
- [洞悉PaaS平台的本质](https://time.geekbang.org/column/article/1610)
- [推荐阅读:分布式系统架构经典资料](https://time.geekbang.org/column/article/2080)
- [推荐阅读:分布式数据调度相关论文](https://time.geekbang.org/column/article/2421)

View File

@@ -0,0 +1,285 @@
<audio id="audio" title="28 | 推荐阅读:分布式系统架构经典资料" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/87/92/876ed01eba4a214498001e2d4c051692.mp3"></audio>
前段时间,我写了一系列分布式系统架构方面的文章,有很多读者纷纷留言讨论相关的话题,还有读者留言表示对分布式系统架构这个主题感兴趣,希望我能推荐一些学习资料。
就像我在前面的文章中多次提到的,分布式系统的技术栈巨大无比,所以我要推荐的学习资料也比较多,后面在文章中我会结合主题逐步推荐给你。在今天这篇文章中,我将推荐一些分布式系统的基础理论和一些不错的图书和资料。
这篇文章比较长,所以我特意整理了目录,帮你快速找到自己感兴趣的内容。
# 基础理论
- CAP 定理
- Fallacies of Distributed Computing
# 经典资料
- Distributed systems theory for the distributed systems engineer
- FLP Impossibility Result
- An introduction to distributed systems
- Distributed Systems for fun and profit
- Distributed Systems: Principles and Paradigms
- Scalable Web Architecture and Distributed Systems
- Principles of Distributed Systems
- Making reliable distributed systems in the presence of software errors
- Designing Data Intensive Applications
# 基础理论
下面这些基础知识有可能你已经知道了,不过还是容我把它分享在这里。我希望用比较通俗易懂的文字将这些枯燥的理论知识讲请楚。
## [CAP定理](https://en.wikipedia.org/wiki/CAP_theorem)
CAP定理是分布式系统设计中最基础也是最为关键的理论。它指出分布式数据存储不可能同时满足以下三个条件。
<li>
**一致性Consistency**:每次读取要么获得最近写入的数据,要么获得一个错误。
</li>
<li>
**可用性Availability**:每次请求都能获得一个(非错误)响应,但不保证返回的是最新写入的数据。
</li>
<li>
**分区容忍Partition tolerance**:尽管任意数量的消息被节点间的网络丢失(或延迟),系统仍继续运行。
</li>
也就是说CAP定理表明在存在网络分区的情况下一致性和可用性必须二选一。而在没有发生网络故障时即分布式系统正常运行时一致性和可用性是可以同时被满足的。这里需要注意的是CAP定理中的一致性与ACID数据库事务中的一致性截然不同。
掌握CAP定理尤其是能够正确理解C、A、P的含义对于系统架构来说非常重要。因为对于分布式系统来说网络故障在所难免如何在出现网络故障的时候维持系统按照正常的行为逻辑运行就显得尤为重要。你可以结合实际的业务场景和具体需求来进行权衡。
例如对于大多数互联网应用来说如门户网站因为机器数量庞大部署节点分散网络故障是常态可用性是必须要保证的所以只有舍弃一致性来保证服务的AP。而对于银行等需要确保一致性的场景通常会权衡CA和CP模型CA模型网络故障时完全不可用CP模型具备部分可用性。
<img src="https://static001.geekbang.org/resource/image/d9/a6/d98d65bef3719e175f16bdb5901f37a6.png" alt="" />
<li>
CA (consistency + availability)这样的系统关注一致性和可用性它需要非常严格的全体一致的协议比如“两阶段提交”2PC。CA系统不能容忍网络错误或节点错误一旦出现这样的问题整个系统就会拒绝写请求因为它并不知道对面的那个结点是否挂掉了还是只是网络问题。唯一安全的做法就是把自己变成只读的。
</li>
<li>
CP (consistency + partition tolerance)这样的系统关注一致性和分区容忍性。它关注的是系统里大多数人的一致性协议比如Paxos算法Quorum类的算法。这样的系统只需要保证大多数结点数据一致而少数的结点会在没有同步到最新版本的数据时变成不可用的状态。这样能够提供一部分的可用性。
</li>
<li>
AP (availability + partition tolerance)这样的系统关心可用性和分区容忍性。因此这样的系统不能达成一致性需要给出数据冲突给出数据冲突就需要维护数据版本。Dynamo就是这样的系统。
</li>
然而还是有一些人会错误地理解CAP定理甚至误用。Cloudera工程博客中[CAP Confusion: Problems with partition tolerance](http://blog.cloudera.com/blog/2010/04/cap-confusion-problems-with-partition-tolerance/)一文中对此有详细的阐述。
在谷歌的[Transaction Across DataCenter视频](http://www.youtube.com/watch?v=srOgpXECblk)中我们可以看到下面这样的图。这个是CAP理论在具体工程中的体现。
<img src="https://static001.geekbang.org/resource/image/f6/b8/f62e32b1a3d81db6ae24b174f1b727b8.png" alt="" />
## [Fallacies of Distributed Computing](http://en.wikipedia.org/wiki/Fallacies_of_distributed_computing)
本文是英文维基百科上的一篇文章。它是Sun公司的[劳伦斯·彼得·多伊奇Laurence Peter Deutsch](https://en.wikipedia.org/wiki/L_Peter_Deutsch)等人于1994~1997年提出的讲的是刚刚进入分布式计算领域的程序员常会有的一系列错误假设。
多伊奇于1946年出生在美国波士顿。他创办了阿拉丁企业Aladdin Enterprises并在该公司编写出了著名的Ghostscript开源软件于1988年首次发布。
他在学生时代就和艾伦·凯Alan Kay等比他年长的人一起开发了Smalltalk并且他的开发成果激发了后来Java语言JIT编译技术的创造灵感。他后来在Sun公司工作并成为Sun的公司院士。在1994年他成为了ACM院士。
基本上每个人刚开始建立一个分布式系统时都做了以下8条假定。随着时间的推移每一条都会被证明是错误的也都会导致严重的问题以及痛苦的学习体验。
1. 网络是稳定的。
1. 网络传输的延迟是零。
1. 网络的带宽是无穷大。
1. 网络是安全的。
1. 网络的拓扑不会改变。
1. 只有一个系统管理员。
1. 传输数据的成本为零。
1. 整个网络是同构的。
阿尔农·罗特姆-盖尔-奥兹Arnon Rotem-Gal-Oz写了一篇长文[Fallacies of Distributed Computing Explained](http://www.rgoarchitects.com/Files/fallacies.pdf)来解释这些点。
由于他写这篇文章的时候已经是2006年了所以从中能看到这8条常见错误被提出十多年后还有什么样的影响一是为什么当今的分布式软件系统也需要避免这些设计错误二是在当今的软硬件环境里这些错误意味着什么。比如文中在谈“延迟为零”假设时还谈到了AJAX而这是2005年开始流行的技术。
而[加勒思·威尔逊Gareth Wilson的文章](http://blog.fogcreek.com/eight-fallacies-of-distributed-computing-tech-talk/)则用日常生活中的例子,对这些点做了更为通俗的解释。
这8个需要避免的错误不仅对于中间件和底层系统开发者及架构师是重要的知识而且对于网络应用程序开发者也同样重要。分布式系统的其他部分如容错、备份、分片、微服务等也许可以对应用程序开发者部分透明但这8点则是应用程序开发者也必须知道的。
**为什么我们要深刻地认识这8个错误是因为这要我们清楚地认识到——在分布式系统中错误是不可能避免的我们能做的不是避免错误而是要把错误的处理当成功能写在代码中。**
后面,我会写一个系列的文章来谈一谈,分布式系统容错设计中的一些常见设计模式。敬请关注!
# 经典资料
## [Distributed systems theory for the distributed systems engineer](http://the-paper-trail.org/blog/distributed-systems-theory-for-the-distributed-systems-engineer/)
本文作者认为,推荐大量的理论论文是学习分布式系统理论的错误方法,除非这是你的博士课程。因为论文通常难度大又很复杂,需要认真学习,而且需要理解这些研究成果产生的时代背景,才能真正的领悟到其中的精妙之处。
在本文中作者给出了他整理的分布式工程师必须要掌握的知识列表并直言掌握这些足够设计出新的分布式系统。首先作者推荐了4份阅读材料它们共同概括了构建分布式系统的难点以及所有工程师必须克服的技术难题。
<li>
[Distributed Systems for Fun and Profit](http://book.mixu.net/distsys/),这是一本小书,涵盖了分布式系统中的关键问题,包括时间的作用和不同的复制策略。后文中对这本书有较详细的介绍。
</li>
<li>
[Notes on distributed systems for young bloods](https://www.somethingsimilar.com/2013/01/14/notes-on-distributed-systems-for-young-bloods/),这篇文章中没有理论,是一份适合新手阅读的分布式系统实践笔记。
</li>
<li>
[A Note on Distributed Systems](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.7628),这是一篇经典的论文,讲述了为什么在分布式系统中,远程交互不能像本地对象那样进行。
</li>
<li>
[The fallacies of distributed computing](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing)每个分布式系统新手都会做的8个错误假设并探讨了其会带来的影响。上文中专门对这篇文章做了介绍。
</li>
随后,分享了几个关键点。
- **失败和时间Failure and Time**。分布式系统工程师面临的很多困难都可以归咎于两个根本原因1. 进程可能会失败2. 没有好方法表明进程失败。这就涉及到如何设置系统时钟,以及进程间的通讯机制,在没有任何共享时钟的情况下,如何确定一个事件发生在另一个事件之前。
可以参考Lamport时钟和Vector时钟还可以看看[Dynamo论文](http://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf)。
- **容错的压力The basic tension of fault tolerance**。能在不降级的情况下容错的系统一定要像没有错误发生的那样运行。这就意味着,系统的某些部分必须冗余地工作,从而在性能和资源消耗两方面带来成本。
最终一致性以及其他技术方案在以系统行为弱保证为代价,来试图避免这种系统压力。阅读[Dynamo论文](http://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf)和帕特·赫尔兰Pat Helland的经典论文[Life Beyond Transactions](http://www.cloudtran.com/pdfs/LifeBeyondDistTRX.pdf)能获很得大启发。
<li>
**基本原语Basic primitives**。在分布式系统中几乎没有一致认同的基本构建模块但目前在越来越多地在出现。比如Leader选举可以参考[Bully算法](https://en.wikipedia.org/wiki/Bully_algorithm);分布式状态机复制,可以参考[维基百科](https://en.wikipedia.org/wiki/State_machine_replication)和[Lampson的论文](https://www.microsoft.com/en-us/research/publication/how-to-build-a-highly-available-system-using-consensus/?from=http%3A%2F%2Fresearch.microsoft.com%2Fen-us%2Fum%2Fpeople%2Fblampson%2F58-consensus%2Facrobat.pdf),后者更权威,只是有些枯燥。
</li>
<li>
**基本结论Fundamental Results**。某些事实是需要吸收理解的有几点如果进程之间可能丢失某些消息那么不可能在实现一致性存储的同时响应所有的请求这就是CAP定理一致性不可能同时满足以下条件a. 总是正确b. 在异步系统中只要有一台机器发生故障系统总是能终止运行——停止失败FLP不可能性一般而言消息交互少于两轮都不可能达成共识Consensus
</li>
<li>
**真实系统Real systems**。学习分布式系统架构最重要的是结合一些真实系统的描述反复思考和点评其背后的设计决策。如谷歌的GFS、Spanner、Chubby、BigTable、Dapper等以及Dryad、Cassandra和Ceph等非谷歌系统。
</li>
## [FLP Impossibility Result](https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf)
FLP不可能性的名称起源于它的三位作者Fischer、Lynch和Paterson。它是关于理论上能做出的功能最强的共识算法会受到怎样的限制的讨论。
所谓共识问题,就是让网络上的分布式处理者最后都对同一个结果值达成共识。该解决方案对错误有恢复能力,处理者一旦崩溃以后,就不再参与计算。在同步环境下,每个操作步骤的时间和网络通信的延迟都是有限的,要解决共识问题是可能的,方式是:等待一个完整的步长来检测某个处理者是否已失败。如果没有收到回复,那就假定它已经崩溃。
共识问题有几个变种,它们在“强度”方面有所不同——通常,一个更“强”问题的解决方案同时也能解决比该问题更“弱”的问题。共识问题的一个较强的形式如下。
给出一个处理者的集合,其中每一个处理者都有一个初始值:
- 所有无错误的进程(处理过程)最终都将决定一个值;
- 所有会做决定的无错误进程决定的都将是同一个值;
- 最终被决定的值必须被至少一个进程提出过。
这三个特性分别被称为“终止”、“一致同意”和“有效性”。任何一个具备这三点特性的算法都被认为是解决了共识问题。
FLP不可能性则讨论了异步模型下的情况主要结论有两条。
<li>
在异步模型下不存在一个完全正确的共识算法。不仅上述较“强”形式的共识算法不可能实现FLP还证明了比它弱一些的、只需要有一些无错误的进程做决定就足够的共识算法也是不可能实现的。
</li>
<li>
在异步模型下存在一个部分正确的共识算法,前提是所有无错误的进程都总能做出一个决定,此外没有进程会在它的执行过程中死亡,并且初始情况下超过半数进程都是存活状态。
</li>
FLP的结论是在异步模型中仅一个处理者可能崩溃的情况下就已经没有分布式算法能解决共识问题。这是该问题的理论上界。其背后的原因在于异步模型下对于一个处理者完成工作然后再回复消息所需的时间并没有上界。因此无法判断出一个处理者到底是崩溃了还是在用较长的时间来回复或者是网络有很大的延迟。
FLP不可能性对我们还有别的启发。一是网络延迟很重要网络不能长时间处于拥塞状态否则共识算法将可能因为网络延迟过长而导致超时失败。二是计算时间也很重要。对于需要计算共识的处理过程进程如分布式数据库提交需要在短时间里就计算出能否提交的结果那就要保证计算结点资源充分特别是内存容量、磁盘空闲时间和CPU时间方面要足够并在软件层面确保计算不超时。
另一个问题是像Paxos这样的共识算法为什么可行实际上它并不属于FLP不可能性证明中所说的“完全正确”的算法。它的正确性会受超时值的影响。但这并不妨碍它在实践中有效因为我们可以通过避免网络拥塞等手段来保证超时值是合适的。
## [An introduction to distributed systems](https://github.com/aphyr/distsys-class)
它是[分布式系统基础课](https://github.com/aphyr/distsys-class#review-1)的课程提纲,也是一份很棒的分布式系统介绍,几乎涵盖了所有知识点,并辅以简洁并切中要害的说明文字,非常适合初学者提纲挈领地了解知识全貌,快速与现有知识结合,形成知识体系。此外,还可以把它作为分布式系统的知识图谱,根据其中列出的知识点一一搜索,你能学会所有的东西。
## [Distributed Systems for fun and profit](http://book.mixu.net/distsys/single-page.html)
这是一本免费的电子书。作者撰写此书的目的是希望以一种更易于理解的方式讲述以亚马逊的Dynamo、谷歌的BigTable和MapReduce等为代表的分布式系统背后的核心思想。
因而,书中着力撰写分布式系统中的关键概念,以便让读者能够快速了解最为核心的知识,并且进行了足够详实的讲述,方便读者体会和理解,又不至于陷入细节。
全书分为五章讲述了扩展性、可用性、性能和容错等基础知识FLP不可能性和CAP定理探讨了大量的一致性模型讨论了时间和顺序及时钟的各种用法。随后探讨了复制问题如何防止差异以及如何接受差异。此外每章末尾都给出了针对本章内容的扩展阅读资源列表这些资料是对本书内容的很好补充。
## [Distributed Systems: Principles and Paradigms](http://barbie.uta.edu/~jli/Resources/MapReduce&amp;Hadoop/Distributed%20Systems%20Principles%20and%20Paradigms.pdf)
本书是由计算机科学家安德鲁·斯图尔特·塔能鲍姆Andrew S. Tanenbaum和其同事马丁·范·斯蒂恩Martin van Steen合力撰写的是分布式系统方面的经典教材。
语言简洁,内容通俗易懂,介绍了分布式系统的七大核心原理,并给出了大量的例子;系统讲述了分布式系统的概念和技术,包括通信、进程、命名、同步化、一致性和复制、容错以及安全等;讨论了分布式应用的开发方法(即范型)。
但本书不是一本指导“如何做”的手册,仅适合系统性地学习基础知识,了解编写分布式系统的基本原则和逻辑。中文翻译版为[《分布式系统原理与范型》(第二版)](https://item.jd.com/10079452.html)。
## [Scalable Web Architecture and Distributed Systems](http://www.aosabook.org/en/distsys.html)
这是一本免费的在线小册子,其中文翻译版为[可扩展的Web架构和分布式系统](http://nettee.github.io/posts/2016/Scalable-Web-Architecture-and-Distributed-Systems/)。
本书主要针对面向的互联网公网的分布式系统但其中的原理或许也可以应用于其他分布式系统的设计中。作者的观点是通过了解大型网站的分布式架构原理小型网站的构建也能从中受益。本书从大型互联网系统的常见特性如高可用、高性能、高可靠、易管理等出发引出了一个类似于Flickr的典型的大型图片网站的例子。
首先从程序模块化易组合的角度出发引出了面向服务架构SOA的概念。同时引申出写入和读取两者的性能问题及对此二者如何调度的考量——在当今的软硬件架构上写入几乎总是比读取更慢包括软件层面引起的写入慢如数据库的一致性要求和B树的修改和硬件层面引起的写入慢如SSD
网络提供商提供的下载带宽也通常比上传带宽更大。读取往往可以异步操作还可以做gzip压缩。写入则往往需要保持连接直到数据上传完成。因此往往我们会想把服务做成读写分离的形式。然后通过一个Flickr的例子介绍了他们的服务器分片式集群做法。
接下来讲了冗余。数据的冗余异地备份如master-slave、服务的多版本冗余、避免单点故障等。
随后,在冗余的基础上,讲了多分区扩容,亦即横向扩容。横向扩容是在单机容量无法满足需求的情况下不得不做的设计。但横向扩容会带来一个问题,即数据的局域性会变差。本来数据可以存在于同一台服务器上,但现在数据不得不存在于不同服务器上,潜在地降低了系统的性能(主要是可能延长响应时间)。另一个问题是多份数据的不一致性。
之后本书开始深入讲解数据访问层面的设计。首先抛出一个大型数据TB级以上的存储问题。如果内存都无法缓存该数据量性能将大幅下降那么就需要缓存数据。数据可以缓存在每个节点上。
但如果为所有节点使用负载均衡那么分配到每个节点的请求将十分随机大大降低缓存命中率从而导致低效的缓存。接下来考虑全局缓存的设计。再接下来考虑分布式缓存的设计。进一步介绍了Memcached以及Facebook的缓存设计方案。
代理服务器则可以用于把多个重复请求合并成一个对于公网上的公共服务来说这样做可以大大减少对数据层访问的次数。Squid和Varnish是两个可用于生产的代理服务软件。
当知道所需要读取的数据的元信息时比如知道一张图片的URL或者知道一个要全文搜索的单词时索引就可以帮助找到那几台存有该信息的服务器并从它们那里获取数据。文中扩展性地讨论了本话题。
接下来谈负载均衡器,以及一些典型的负载均衡拓扑。然后讨论了对于用户会话数据如何处理。比如,对于电子商务网站,用户的购物车在没有下单之前都必须保持有效。
一种办法是让用户会话与服务器产生关联但这样做会较难实现自动故障转移如何做好是个问题。另外何时该使用负载均衡是个问题。有时节点数量少的情况下只要使用轮换式DNS即可。负载均衡也会让在线性能问题的检测变得更麻烦。
对于写入的负载可以用队列的方式来减少对服务器的压力保证服务器的效率。消息队列的开源实现有很多如RabbitMQ、ActiveMQ、BeanstalkD但有些队列方案也使用了如Zookeeper甚至是像Redis这样的存储服务。
本书主要讲述了高性能互联网分布式服务的架构方案,并介绍了许多实用的工具。作者指出这是一个令人兴奋的设计领域,虽然只讲了一些皮毛,但这一领域不仅现在有很多创新,将来也会越来越多。
## [Principles of Distributed Systems](http://dcg.ethz.ch/lectures/podc_allstars/lecture/podc.pdf)
本书是苏黎世联邦理工学院的教材。它讲述了多种分布式系统中会用到的算法。虽然分布式系统的不同场景会用到不同算法,但并不表示这些算法都会被用到。不过,对于学生来说,掌握了算法设计的精髓也就能举一反三地设计出解决其他问题的算法,从而得到分布式系统架构设计中所需的算法。
本书覆盖的算法有:
- 顶点涂色算法(可用于解决互相冲突的任务分配问题)
- 分布式的树算法(广播算法、会聚算法、广度优先搜索树算法、最小生成树算法)
- 容错以及PaxosPaxos是最经典的共识算法之一
- 拜占庭协议(节点可能没有完全宕机,而是输出错误的信息)
- 全互联网络(服务器两两互联的情况下算法的复杂度)
- 多核计算的工程实践(事务性存储、资源争用管理)
- 主导集(又一个用随机化算法打破对称性的例子;这些算法可以用于路由器建立路由)
- ……
这些算法对你迈向更高级更广阔的技术领域真的相当有帮助的。
## [Making reliable distributed systems in the presence of software errors](https://github.com/theanalyst/awesome-distributed-systems/blob/master/README.md)
这本书的书名直译过来是在有软件错误的情况下构建可靠的分布式系统Erlang之父乔·阿姆斯特朗Joe Armstrong的力作。书中撰写的内容是从1981年开始的一个研究项目的成果这个项目是寻找更好的电信应用编程方式。
当时的电信应用都是大型程序虽然经过了仔细的测试但投入使用时程序中仍会存在大量的错误。作者及其同事假设这些程序中确实有错误然后想法设法在这些错误存在的情况下构建可靠的系统。他们测试了所有的编程语言没有一门语言拥有电信行业所需要的所有特性所以促使一门全新的编程语言Erlang的开发以及随之出现的构建健壮系统OTP的设计方法论和库集。
书中抽象了电信应用的所有需求定义了问题域讲述了系统构建思路——模拟现实简单通用并给出了指导规范。阿姆斯特朗认为在存在软件错误的情况下构建可靠系统的核心问题可以通过编程语言或者编程语言的标准库来解决。所以本书有很大的篇幅来介绍Erlang以及如何运用其构建具有容错能力的电信应用。
虽然书中的内容是以构建20世纪80年代的电信系统为背景但是这种大规模分布式的系统开发思路以及对系统容错能力的核心需求与互联网时代的分布式系统架构思路出奇一致。书中对问题的抽象、总结以及解决问题的思路和方案有深刻的洞察和清晰的阐释所以此书对现在的项目开发和架构有极强的指导和借鉴意义。
## [Designing Data Intensive Applications](https://www.amazon.com/Designing-Data-Intensive-Applications-Reliable-Maintainable/dp/1449373321)
这是一本非常好的书。我们知道,在分布式的世界里,数据结点的扩展是一件非常麻烦的事。而这本书则深入浅出地用很多工程案例讲解了如何让数据结点做扩展。
作者马丁·科勒普曼Martin Kleppmann在分布式数据系统领域有着很深的功底并在这本书中完整地梳理各类纷繁复杂设计背后的技术逻辑不同架构之间的妥协与超越很值得开发人员与架构设计者阅读。
这本书深入到B-Tree、SSTables、LSM这类数据存储结构中并且从外部的视角来审视这些数据结构对NoSQL和关系型数据库所产生的影响。它可以让你很清楚地了解到真正世界的大数据架构中的数据分区、数据复制的一些坑并提供了很好的解决方案。
**最赞的是,作者将各种各样的技术的本质非常好地关联在一起,帮你触类旁通**。而且抽丝剥茧,循循善诱,从“提出问题”,到“解决问题”,到“解决方案”,再到“优化方案”和“对比不同的方案”,一点一点地把非常晦涩的技术和知识展开。
本书的引用相当多每章后面都有几百个Reference。通过这些Reference你可以看到更为广阔更为精彩的世界。
这本书是2017年3月份出版的目前还没有中译版不过英文也不难读。非常推荐。这里有[这本书的PPT](http://www.antonfagerberg.com/files/intensive.pdf)你可从这个PPT中管中窥豹一下。
# 小结
在今天的文章中,我给出了一些分布式系统的基础理论知识和几本很不错的图书和资料,需要慢慢消化吸收。也许你看到这么庞大的书单和资料列表有点望而却步,但是我真的希望你能够花点时间来看看这些资料。相信你看完这些资料后,一定能上一个新的台阶。再加上一些在工程项目中的实践,我保证你,一定能达到大多数人难以企及的技术境界。
自从2002年开始接触分布式计算系统至今我学习分布式系统已经有15年了发现还有很多东西还要继续学习。是的学无止境啊。如果你想成为一名很不错的架构师你一定要好好学习这些知识。
**2018年新年来临祝你新年快乐**
<img src="https://static001.geekbang.org/resource/image/a3/bb/a321a64415498cbbf5222b3971accabb.png" alt="" /><br />
**插图来自电影《摔跤吧!爸爸》**
《分布式系统架构的本质》系列文章的目录如下,方便你查找自己关注的内容。
- [分布式系统架构的冰与火](https://time.geekbang.org/column/article/1411)
- [从亚马逊的实践,谈分布式系统的难点](https://time.geekbang.org/column/article/1505)
- [分布式系统的技术栈](https://time.geekbang.org/column/article/1512)
- [分布式系统关键技术:全栈监控](https://time.geekbang.org/column/article/1513)
- [分布式系统关键技术:服务调度](https://time.geekbang.org/column/article/1604)
- [分布式系统关键技术:流量与数据调度](https://time.geekbang.org/column/article/1609)
- [洞悉PaaS平台的本质](https://time.geekbang.org/column/article/1610)
- [推荐阅读:分布式系统架构经典资料](https://time.geekbang.org/column/article/2080)
- [推荐阅读:分布式数据调度相关论文](https://time.geekbang.org/column/article/2421)

View File

@@ -0,0 +1,197 @@
我们在之前的系列文章《分布式系统架构的本质》中说过,分布式系统的一个关键技术是“数据调度”。因为我们需要扩充节点,提高系统的高可用性,所以必需冗余数据结点。
建立数据结点的副本看上去容易,但其中最大的难点就是分布式一致性的问题。下面,我会带你看看数据调度世界中的一些技术点以及相关的技术论文。
对于分布式的一致性问题相信你在前面看过好几次下面这张图。从中我们可以看出Paxos算法的重要程度。还有人说分布式下真正的一致性算法只有Paxos。
<img src="https://static001.geekbang.org/resource/image/95/50/95e0fd0862be0e3489713687bf363f50.png" alt="" />
# Paxos算法
Paxos算法是莱斯利·兰伯特Lesile Lamport于1990年提出来的一种基于消息传递且具有高度容错特性的一致性算法。但是这个算法太过于晦涩所以一直以来都属于理论上的论文性质的东西。
其进入工程圈的源头在于Google的Chubby lock——一个分布式的锁服务用在了Bigtable中。直到Google发布了下面的这两篇论文Paxos才进入到工程界的视野中来。
<li>
[Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/en//archive/bigtable-osdi06.pdf)
</li>
<li>
[The Chubby lock service for loosely-coupled distributed systems](https://static.googleusercontent.com/media/research.google.com/en//archive/chubby-osdi06.pdf)
</li>
Google与Big Table相齐名的还有另外两篇论文。
<li>
[The Google File System](https://static.googleusercontent.com/media/research.google.com/en//archive/gfs-sosp2003.pdf)
</li>
<li>
[MapReduce: Simplifed Data Processing on Large Clusters](https://static.googleusercontent.com/media/research.google.com/en//archive/mapreduce-osdi04.pdf)
</li>
不过这几篇论文中并没有讲太多的Paxos算法细节上的内容反而在论文[Paxos Made Live An Engineering Perspective](https://static.googleusercontent.com/media/research.google.com/en//archive/paxos_made_live.pdf) 中提到了很多工程实现的细节。比如Google实现Paxos时遇到的各种问题和解决方案讲述了从理论到实际应用二者之间巨大的鸿沟。
尤其在满地都是坑的分布式系统领域这篇论文没有过多讨论Paxos算法本身而是在讨论如何将理论应用到实践如何弥补理论在实践中的不足如何取舍如何测试这些在实践中的各种问题才是工程的魅力。所以建议你读一读。
Paxos算法的原版论文我在这里就不贴了因为一来比较晦涩二来也不易懂。推荐一篇比较容易读的——[Neat Algorithms - Paxos](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/) ,这篇文章中还有一些小动画帮助你读懂。还有一篇可以帮你理解的文章是[Paxos by Examples](https://angus.nyc/2012/paxos-by-example/)。
如果你要自己实现Paxos算法这里有几篇文章供你参考。
<li>
[Paxos Made Code](http://www.inf.usi.ch/faculty/pedone/MScThesis/marco.pdf) ,作者是马克罗·普里米(Macro Primi)他实现了一个Paxos开源库[libpaxos](http://libpaxos.sourceforge.net/)。
</li>
<li>
[Paxos for System Builders](http://www.cnds.jhu.edu/pub/papers/cnds-2008-2.pdf) 从一个系统实现者的角度讨论了实现Paxos的诸多具体问题比如Leader选举、数据及消息类型、流控等。
</li>
<li>
[Paxos Made Moderately Complex](http://www.cs.cornell.edu/courses/cs7412/2011sp/paxos.pdf)这篇文章比较新是2011年才发表的。文中介绍了很多实现细节并提供了很多伪代码一方面可以帮助理解Paxos另一方面也可以据此实现一个Paxos。
</li>
<li>
[Paxos Made Practical](https://web.stanford.edu/class/cs340v/papers/paxos.pdf)主要介绍如何采用Paxos实现replication。
</li>
除了马克罗·普里米的那个开源实现外到GitHub上找一下你就会看到这些项目[Plain Paxos Implementations Python &amp; Java](https://github.com/cocagne/paxos)、[A go implementation of the Paxos algorithm](https://github.com/xiang90/paxos) 。
ZooKeeper 有和Paxos非常相似的一些特征比如领导选举、提案号等但是它本质上不是Paxos协议而是自己发明的Zab协议有兴趣的话可以读一下这篇论文<br />
[Zab: High-Performance broadcast for primary-backup systems](https://pdfs.semanticscholar.org/fc11/031895c302dc52404d34de58af1a72f3b817.pdf)。
上述的Google File System、MapReduce、Bigtable并称为“谷三篇”。基本上来说整个世界工程系统因为这三篇文章开始向分布式系统演化而云计算中的很多关键技术也是因为这三篇文章才得以成熟。 后来雅虎公司也基于这三篇论文开发了一个开源的软件——Hadoop。
# Raft算法
因为Paxos算法太过于晦涩而且在实际的实现上有太多的坑并不太容易写对。所以有人搞出了另外一个一致性的算法叫Raft。其原始论文是[ In search of an Understandable Consensus Algorithm (Extended Version) ](https://raft.github.io/raft.pdf)寻找一种易于理解的Raft算法。这篇论文的译文在InfoQ上《[Raft一致性算法论文译文](http://www.infoq.com/cn/articles/raft-paper)》,推荐你读一读。
Raft算法和Paxos的性能和功能是一样的但是它和Paxos算法的结构不一样这使Raft算法更容易理解并且更容易实现。那么Raft是怎样做到的呢
Raft把这个一致性的算法分解成了几个部分一个是领导选举Leader Selection一个是日志复制Log Replication一个是安全性Safety还有一个是成员变化Membership Changes。对于一般人来说Raft协议比Paxos的学习曲线更低也更平滑。
Raft协议中有一个状态机每个结点会有三个状态分别是 Leader、Candidate和Follower。Follower只响应其他服务器的请求如果没有收到任何信息它就会成为一个Candidate并开始进行选举。收到大多数人同意选票的人会成为新的Leader。
<img src="https://static001.geekbang.org/resource/image/40/33/408fe585546319dbe0e6c8422dc0e733.png" alt="" />
一旦选举出了一个Leader它就开始负责服务客户端的请求。每个客户端的请求都包含一个要被复制状态机执行的指令。Leader首先要把这个指令追加到log中形成一个新的entry然后通过AppendEntries RPC并行地把该entry发给其他服务器server。如果其他服务器没发现问题复制成功后会给Leader一个表示成功的ACK。
Leader收到大多数ACK后应用该日志返回客户端执行结果。如果Follower崩溃 crash或者丢包Leader会不断重试AppendEntries RPC。
<img src="https://static001.geekbang.org/resource/image/04/9f/0428dd28b89eba37de4e13ff9093ba9f.png" alt="" />
这里推荐几个不错的Raft算法的动画演示。
<li>
[Raft The Secret Lives of Data](http://thesecretlivesofdata.com/raft/)
</li>
<li>
[Raft Consensus Algorithm](https://raft.github.io/)
</li>
<li>
[Raft Distributed Consensus Algorithm Visualization](http://kanaka.github.io/raft.js/)
</li>
# 逻辑钟和向量钟
后面业内又搞出来一些工程上的东西比如Amazon的DynamoDB其论文[Dynamo: Amazons Highly Available Key Value Store](http://bnrg.eecs.berkeley.edu/~randy/Courses/CS294.F07/Dynamo.pdf) 的影响力也很大。这篇论文中讲述了Amazon 的DynamoDB是如何满足系统的高可用、高扩展和高可靠要求的其中还展示了系统架构是如何做到数据分布以及数据一致性的。
GFS采用的是查表式的数据分布而DynamoDB采用的是计算式的也是一个改进版的通过虚拟结点减少增加结点带来数据迁移的一致性哈希。另外这篇论文中还讲述了一个NRW模式用于让用户可以灵活地在CAP系统中选取其中两项这使用到了Vector Clock——向量时钟来检测相应的数据冲突。最后还介绍了使用Handoff的机制对可用性的提升。
这篇文章中有几个关键的概念一个是Vector Clock另一个是Gossip协议。
提到向量时钟就需要提一下逻辑时钟。所谓逻辑时间,也就是在分布系统中为了解决消息有序的问题,由于在不同的机器上有不同的本地时间,这些本地时间的同步很难搞,会导致消息乱序。
于是Paxos算法的发明人兰伯特Lamport搞了个向量时钟每个系统维护一个本地的计数器这就是所谓的逻辑时钟。每执行一个事件例如向网络发送消息或是交付到应用层都对这个计数器做加1操作。当跨系统的时候在消息体上附着本地计算器当接收端收到消息时更新自己的计数器取对端传来的计数器和自己当成计数器的最大值也就是调整自己的时钟。
逻辑时钟可以保证如果事件A先于事件B那么事件A的时钟一定小于事件B的时钟但是返过来则无法保证因为返过来没有因果关系。所以向量时钟解释了因果关系。向量时钟维护了数据更新的一组版本号版本号其实就是使用逻辑时钟
假如一个数据需要存在三个结点上A、B、C。那么向量维度就是3在初始化的时候所有结点对于这个数据的向量版本是[A:0, B:0, C:0]。当有数据更新时比如从A结点更新那么数据的向量版本变成[A:1, B:0, C:0]然后向其他结点复制这个版本其在语义上表示为我当前的数据是由A结果更新的而在逻辑上则可以让分布式系统中的数据更新的顺序找到相关的因果关系。
这其中的逻辑关系,你可以看一下[ 马萨诸塞大学课程 Distributed Operating System ](http://lass.cs.umass.edu/~shenoy/courses/spring05/lectures.html)中第10节[ Clock Synchronization ](http://lass.cs.umass.edu/~shenoy/courses/spring05/lectures/Lec10.pdf)这篇讲议。关于Vector Clock你可以看一下[ Why Vector Clocks are Easy](http://basho.com/posts/technical/why-vector-clocks-are-easy/)和[Why Vector Clocks are Hard](http://basho.com/posts/technical/why-vector-clocks-are-hard/) 这两篇文章。
# Gossip协议
另外DynamoDB中使用到了Gossip协议来做数据同步这个协议的原始论文是 [Efficient Reconciliation and Flow Control for Anti-Entropy Protocols](https://www.cs.cornell.edu/home/rvr/papers/flowgossip.pdf)。Gossip算法也是Cassandra使用的数据复制协议。这个协议就像八卦和谣言传播一样可以 “一传十、十传百”传播开来。但是这个协议看似简单,细节上却非常麻烦。
根据这篇论文,节点之间存在三种通信方式。
<li>
push方式。A节点将数据(key,value,version)及对应的版本号推送给B节点B节点更新A中比自己新的数据。
</li>
<li>
pull 方式。A仅将数据key,version推送给BB将本地比A新的数据(key,value,version)推送给AA更新本地。
</li>
<li>
push/pull方式。与pull类似只是多了一步A再将本地比B新的数据推送给BB更新本地。
</li>
如果把两个节点数据同步一次定义为一个周期那么在一个周期内push需通信1次pull需2次push/pull则需3次。从效果上来讲push/pull最好理论上一个周期内可以使两个节点完全一致。直观感觉上也是push/pull的收敛速度最快。
另外每个节点上的又需要一个协调机制也就是如何交换数据能达到最快的一致性——消除节点的不一致性。上面所讲的push、pull等是通信方式协调是在通信方式下的数据交换机制。
协调所面临的最大问题是,一方面需要找到一个经济的方式,因为不可能每次都把一个节点上的数据发送给另一个节点;另一方面,还需要考虑到相关的容错方式,也就是当因为网络问题不可达的时候,怎么办?
一般来说有两种机制一种是以固定概率传播的Anti-Entropy机制另一种是仅传播新到达数据的Rumor-Mongering机制。前者有完备的容错性但是需要更多的网络和CPU资源后者则反过来不耗资源但在容错性上难以保证。
Anti-Entropy的机制又分为Precise Reconciliation精确协调和Scuttlebutt Reconciliation整体协调这两种。前者希望在每次通信周期内都非常精确地消除双方的不一致性具体表现就是互发对方需要更新的数据。因为每个结点都可以读写所以这需要每个数据都要独立维护自己的版本号。
而整体协调与精确协调不同的是,整体协调不是为每个数据都维护单独的版本号,而是每个节点上的数据统一维护一个版本号,也就是一个一致的全局版本。这样与其他结果交换数据的时候,就只需要比较节点版本,而不是数据个体的版本,这样会比较经济一些。如果版本不一样,则需要做精确协调。
因为篇幅问题这里就不多说了你可以看看原始的论文还可以去看看Cassandra中的源码以及到GitHub搜一下其他人的实现。多说一句Cassandra的实现是基于整体协调的push/pull模式。
关于Gossip的一些图示化的东西你可以看一下动画[gossip visualization](https://rrmoelker.github.io/gossip-visualization/)。
# 分布式数据库方面
上面讲的都是一些基本概念相关的东西,下面我们来谈谈数据库方面的一些论文。
一篇是AWS Aurora的论文 [Amazon Aurora: Design Considerations for High Throughput Cloud Native Relation Databases](http://www.allthingsdistributed.com/files/p1041-verbitski.pdf)。
Aurora是AWS将MySQL的计算和存储分离后计算节点scale up存储节点scale out。并把其redo log独立设计成一个存储服务把分布式的数据方面的东西全部甩给了底层存储系统。从而提高了整体的吞吐量和水平的扩展能力。
Aurora要写6份拷贝但是其只需要把一个Quorum中的日志写成功就可以了。如下所示。可以看到将存储服务做成一个跨数据中心的服务提高数据库容灾降低性能影响。
<img src="https://static001.geekbang.org/resource/image/70/eb/70eac246964e3ef8ad5100944bf5bdeb.png" alt="" />
对于存储服务的设计核心的原理就是latency一定要低毕竟写6个copy是一件开销很大的事。所以基本上来说Aurora用的是异步模型然后拼命地做并行处理其中用到的也是Gossip协议。如下所示。
<img src="https://static001.geekbang.org/resource/image/7f/81/7f89ba6764ede9fc4df223e541179381.png" alt="" />
在上面这个图中我们可以看到完成前两步就可以ACK回调用方。也就是说只要数据在本地落地了就可以返回成功了。然后对于六个副本这个log会同时发送到6个存储结点只需要有大于4个成功ACK就算写成功了。第4步我们可以看到用的是Gossip协议。然后第5步产生cache 页便于查询。第6步在S3做Snapshot类似于Checkpoint。
第二篇比较有代表的论文是Google的 [Spanner: Googles Globally-Distributed Database](http://static.googleusercontent.com/media/research.google.com/zh-CN//archive/spanner-osdi2012.pdf)。<br />
Spanner 是Google的全球分布式数据库Globally-Distributed Database) 。Spanner的扩展性达到了令人咋舌的全球级可以扩展到数百万台机器数以百计的数据中心上万亿的行。更给力的是除了夸张的扩展性之外它还能同时通过同步复制和多版本来满足外部一致性可用性也是很好的。
下面是Spanserver的一个架构。
<img src="https://static001.geekbang.org/resource/image/11/8e/116a697f8753877308661a69a9af0a8e.png" alt="" />
我们可以看到每个数据中心都会有一套Colossus这是第二代的GFS。每个机器有100-1000个tablet也就是相当数据库表中的行集物理存储就是数据文件。比如一张表有2000行然后有20个tablet那么每个tablet分别有100行数据。
在tablet上层通过Paxos协议进行分布式跨数据中心的一致性数据同步。Paxos会选出一个replica做Leader这个Leader的寿命默认是10s10s后重选。Leader就相当于复制数据的master其他replica的数据都是从它那里复制的。读请求可以走任意的replica但是写请求只有去Leader。这些replica统称为一个Paxos Group。
Group之间也有数据交互传输Google定义了最小传输复制单元directory是一些有共同前缀的key记录这些key也有相同的replica配置属性。
<img src="https://static001.geekbang.org/resource/image/e8/68/e85a1bf5efac06601fd6c5e9b75aa068.png" alt="" />
目前基于Spanner论文的开源实现有两个一个是Google公司自己的人出来做的[CockroachDB](https://github.com/cockroachdb/cockroach),另一个是国人做的[TiDB](https://github.com/pingcap/tidb)。
# 小结
正如我在之前的分布式系统的本质文章里所说到的,分布式的服务的调度需要一个分布式的存储系统来支持服务的数据调度。而我们可以看到,各大公司都在分布式的数据库上做各种各样的创新,他们都在使用底层的分布式文件系统来做存储引擎,把存储和计算分离开来,然后使用分布式一致性的数据同步协议的算法来在上层提供高可用、高扩展的支持。
从这点来看可以预见到过去的分库分表并通过一个数据访问的代理服务的玩法应该在不久就会过时就会成为历史。真正的现代化的分布式数据存储就是Aurora和Spanner这样的方式。
通过上面的这些论文和相关的工程实践以及开源项目,相信可以让你在细节方面对分布式中最难的一块——数据调度方面有更多的认识。
(**这篇文章中提到了大量的英文文章和论文,担心读者听音频时很难理解和对应,所以没有录制音频,敬望谅解。**)
《分布式系统架构的本质》系列文章的目录如下,方便你查找自己关注的内容。
- [分布式系统架构的冰与火](https://time.geekbang.org/column/article/1411)
- [从亚马逊的实践,谈分布式系统的难点](https://time.geekbang.org/column/article/1505)
- [分布式系统的技术栈](https://time.geekbang.org/column/article/1512)
- [分布式系统关键技术:全栈监控](https://time.geekbang.org/column/article/1513)
- [分布式系统关键技术:服务调度](https://time.geekbang.org/column/article/1604)
- [分布式系统关键技术:流量与数据调度](https://time.geekbang.org/column/article/1609)
- [洞悉PaaS平台的本质](https://time.geekbang.org/column/article/1610)
- [推荐阅读:分布式系统架构经典资料](https://time.geekbang.org/column/article/2080)
- [推荐阅读:分布式数据调度相关论文](https://time.geekbang.org/column/article/2421)