first commit

This commit is contained in:
张乾
2024-10-16 06:37:41 +08:00
parent 633f45ea20
commit 206fad82a2
3590 changed files with 680090 additions and 0 deletions

View File

@ -0,0 +1,68 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
00 开篇词 四纵四横,带你透彻理解分布式技术
你好我是聂鹏程智载云帆的CTO。在接下来的三个月时间里我将带你打卡分布式技术。
在众多计算机技术当中分布式技术无疑是最璀璨的明珠之一。毫不夸张地说没有分布式技术就没有互联网也就没有现在的阿里巴巴、腾讯、亚马逊、Facebook、谷歌等科技巨头更不会有以信息技术为核心的、对人类历史产生巨大变革的第三次工业革命。万维网、Email、DNS等都是分布式系统的典型代表。
随着分布式技术的不断发展它也早已不再局限于传统的互联网等应用场景。在今年的两会期间全国政协委员、360董事长周鸿祎更是大唱IM ABCDE字母歌。IMABCDE这7个字母所代表的IoT物联网、Mobile移动计算、AI人工智能、Blockchain区块链、Cloud云计算、Data大数据、Edge边缘计算也无不都是以分布式技术为基石。
无疑谁更好地掌握了分布式技术谁就更容易在新一轮技术浪潮中获得主动。在全球经济增速趋缓的大背景下与许多应用业务大量裁人形成鲜明对比的是各大巨头的中间件团队、实验室等基础部门依然在大规模地招兵买马。随着业务扩展以及IMABCDE等新兴技术领域的布局分布式技术人才已然成为巨头们争夺的焦点。
一方面是各大厂商的求贤若渴,一方面是分布式专业技术人才的一将难求。在多年的面试中,我经常能体会到,有些面试者确实非常积极主动,但他们表现出来的水平却无法通过面试。分布式技术人才市场的供应与需求,俨然一首冰与火之歌。
2007年我在西安电子科技大学攻读博士期间就开始研究并行与分布式计算毕业后在IBM做过HPC大规模负载管理系统LSF相关的设计和研发工作在华为负责过分布式IoT相关项目的架构设计以及电信级业务微服务框架、函数服务框架的设计工作也从事过区块链相关的研究工作。现在我在智载云帆负责技术体系的构建专注于无服务器Serverless的架构实践。
从我深入研究分布式技术这十多年的经验来看,分布式技术概念繁多、知识庞杂、新兴技术层出不穷,令许多新手望而却步,而许多有一定年限工作经验的老手,虽然也能对一些概念滔滔不绝,但追问到实质性问题就变得磕磕巴巴,顾左右而言它。比如:
各种分布式概念、名词学了一大堆,但经常张冠李戴,傻傻分不清楚;
做了多年技术,也参与了很多分布式技术实践,却无法回答工作中各种分布式技术、组件、框架选型背后的根源;
在一个分布式技术配套的典型场景往往能驾轻就熟,但一旦稍微变更考察业务场景、业务目标后,就变得毫无头绪。
究其原因,主要是知识碎片化、不成体系、见树不见林。如果再追究更深层次的原因,那无外乎就是两点:
在计算机学科课程设置中,分布式技术尴尬如同中小学中的性教育,重要但不受重视。鲜有高校在计算机本科专业中设置分布式课程,即便是有些高校在研究生教育中设置了相关课程,也是如同高手过招点到为止。
信息碎片化与信息孤立。在信息泛滥的信息时代,各种经典教材、最新文章自然是唾手可得。但,教材固然经典但严谨有余浅出不够,最重要的是没有与时下最新的场景相结合,一方面是因为教材“年久失修”,另一方面确实是因为分布式领域新技术推出的速度令人叹为观止;而网上的各种技术文章虽然多,却鲜有体系化的说明,一个个概念如同一座座信息孤岛。如果不能体系化地理解这些概念,何谈掌握,更谈不上真正地去综合运用这些知识了。
因为工作太忙,这些年我整体而系统的输出比较少。偶然一次听到任正非的讲话,他提到了基础教育、孩子是一个社会的未来,这让我感触良多。我想,如果说一个社会的未来,离不开朝气蓬勃的孩子们,那么一个行业的兴盛,同样也离不开一个广泛的从业者基础。而我如果能做好分布式通识课这样的一个专栏,既可以对自己这些年的经验做一次系统梳理,温故而知新,又能帮助更多的人系统理解并掌握分布式核心技术,为整个行业的兴盛略尽绵薄之力,何乐而不为呢?
其实,在工作、面试、演讲等多种场合,也经常会有人问我:“聂博士,分布式领域的新概念繁多、各种框架五花八门、各种组件层出不穷,应该如何应对啊?”我回答说:“其实你已经有答案了。”
看着他们满脸疑惑我笑着问“RISC芯片程序设计中的封装、继承还有现在提倡的中台战略它们都在做一件什么事情呢”他们答道“莫非是重用
我说:“是的,既然指令可以重用,代码可以重用,技术、业务、数据等都可以重用,为什么知识体系不可以呢?学好分布式通识课,掌握了分布式的核心技术、体系,你就会发现很多新技术、新框架、新组件只不过是‘新瓶装旧酒’,将分布式核心技术进行了再包装、再组合,至多也就是做了一点延伸而已。”
那么分布式通识课究竟该如何学呢在接下来的三个月时间里我会遵循以下4个思路带着你一起学习。
第一,分布式技术错综复杂,各种技术相互耦合,确实无法简单地像网络等技术一样划分层次,所以我会结合自己多年的积累和思考,首先为你梳理出一个脉络清晰、四纵四横的分布式核心技术知识体系,然后从这个纵横的技术体系中抽取最核心、最普适的技术思想以及概念,结合各种适用场景一一解析。这样的设计,旨在帮助你找到核心知识点,并将这些知识点联系起来,快速形成分布式核心技术的知识网络,从而形成自己的技术判断力,进而规划出自己的技术路线。
第二,从一个熟知的事物出发,进行浅出的讲解,帮助你从已有知识体系扩展到新的知识体系,从而迅速、牢固地掌握分布式技术的核心知识点。
第三透过表象深入讲解技术本质而不是case by case地讲解帮助你知其然并知其所以然真正做到理解与运用时的举一反三。
第四,针对同一分布式问题的不同方法,从多维度、多角度进行对比、分析,方便你在工作中灵活选型,避免重复“造轮子”。你甚至可以综合权衡各种方法的优缺点,提炼发明出新的方法,最终做到活学活用。
讲到这里,你是不是也有点摩拳擦掌、跃跃欲试了呢?“分布式世界这么大,我要去看看!”别慌,请先看完这份技术地图。
首先按照业务的架构层次栈我自底向上按照资源、通信、数据与计算的维度梳理出了4个技术层次分布式资源池化、分布式通信、分布式数据存储与管理、分布式计算。这样的划分符合业务架构设计的一般规律即“在一定资源上进行一定通信通过一定计算完成一定数据的加工和处理从而对外提供特定的服务”。另一方面这样的划分也整合了零散的知识点具有完备性。
既然横向的4个层次都已经完备了那为什么又多出了4个纵向的技术呢如果我们把横向的4个层次比作派生类的话那么纵向的4条技术线应该是它们的基类。因为在分布式环境下无论是资源、通信、数据还是计算都需要去解决协同、调度、追踪高可用还有部署的问题。因此我从横向的技术层次中提炼出分布式协同、分布式调度、分布式追踪与高可用、分布式部署4个纵向技术线。由于分布式追踪、分布式部署虽属于支撑技术但并不会影响业务的构成因此我不会在本专栏中进行讲解。
最后,如果说现在分布式领域里各种包装出来的、五花八门的新技术,像是令人高不可攀的女神、男神的话,那么这个分布式通识课程中所提炼出来的体系和核心知识点无疑就是女神、男神素颜的样子。我想说,等你看尽素颜,无论是女神、男神也好,还是各种高大上的技术也好,也就不会觉得那么高不可攀了。
既然你已经看到了这里,相信你也看到了学习分布式技术知识的迫切需求,那么不妨请你在留言区做个自我介绍,给我说说你的困惑,也说说你想通过这个专栏收获些什么,这样我后续也可以根据你的情况进行有针对性的讲解。
我是聂鹏程,今天的内容就到这里了,我们下一讲再会。

View File

@ -0,0 +1,117 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
01 分布式缘何而起:从单兵,到游击队,到集团军
你好,我是聂鹏程。这是专栏的第一篇文章,我们先来聊聊什么是分布式。
与其直接用些抽象、晦涩的技术名词去给分布式下一个定义,还不如从理解分布式的发展驱动因素开始,我们一起去探寻它的本质,自然而然地也就清楚它的定义了。
在今天这篇文章中,我将带你了解分布式的起源,是如何从单台计算机发展到分布式的,进而帮助你深入理解什么是分布式。为了方便你更好地理解这个演进过程,我将不考虑多核、多处理器的情况,假定每台计算机都是单核、单处理器的。
分布式起源
单兵模式:单机模式
1946年情人节发布的ENIAC是世界上的第一台通用计算机它占地170平米重达30吨每秒可进行5000次加法或者400次乘法运算标志着单机模式的开始。
所谓单机模式是指,所有应用程序和数据均部署在一台电脑或服务器上,由一台计算机完成所有的处理。
以铁路售票系统为例,铁路售票系统包括用户管理、火车票管理和订单管理等模块,数据包括用户数据、火车票数据和订单数据等,如果使用单机模式,那么所有的模块和数据均会部署在同一台计算机上,也就是说数据存储、请求处理均由该计算机完成。这种模式的好处是功能、代码和数据集中,便于维护、管理和执行。
单机模式的示意图,如下所示:
这里需要注意的是,本文的所有示意图中,紫色虚线表示在一台计算机内。
事物均有两面性我们再来看看单机模式的缺点。单个计算机的处理能力取决于CPU和内存等但硬件的发展速度和性能是有限的而且升级硬件的性价比也是我们要考虑的由此决定了CPU和内存等硬件的性能将成为单机模式的瓶颈。
你有没有发现,单机模式和单兵作战模式非常相似,单台计算机能力再强,就好比特种兵以一敌百,但终归能力有限。此外,将所有任务都交给一台计算机,也会存在将所有鸡蛋放到一个篮子里的风险,也就是单点失效问题。
归纳一下,单机模式的主要问题是:性能受限、存在单点失效问题。
游击队模式:数据并行或数据分布式
既然单机模式存在性能和可用性的问题。那么,有没有什么更好的计算模式呢?答案是肯定的。
为解决单机模式的问题,并行计算得到了发展,进而出现了数据并行(也叫作数据分布式)模式。并行计算采用消息共享模式使用多台计算机并行运行或执行多项任务,核心原理是每台计算机上执行相同的程序,将数据进行拆分放到不同的计算机上进行计算。
请注意,并行计算强调的是对数据进行拆分,任务程序在每台机器上运行。要达到这个目的,我们必须首先把单机模式中的应用和数据分离,才可能实现对数据的拆分。这里的应用就是执行任务的程序,任务就是提交的请求。以铁路售票系统为例,运行在服务器上的用户管理、火车票管理和订单管理等程序就是应用,用户提交的查询火车票、购买火车票的请求就是任务。
在单机模式中,应用和数据均在一台计算机或服务器上,要实现数据的并行,首先必须将应用和数据分离以便将应用部署到不同的计算机或服务器上;然后,对同类型的数据进行拆分,比方说,不同计算机或服务器上的应用可以到不同的数据库上获取数据执行任务。
以铁路售票系统的数据并行为例,主要包括两个步骤,如下所示:
第一步,将应用与数据分离,分别部署到不同的服务器上:
第二步,对数据进行拆分,比如把同一类型的数据拆分到两个甚至更多的数据库中,这样应用服务器上的任务就可以针对不同数据并行执行了。
对于铁路售票系统来说根据线路将用户、火车票和订单数据拆分到不同的数据库中部署到不同的服务器上比如京藏线的数据放在数据库服务器1上的数据库中沪深线的数据放在数据库服务器2上的数据库中。
需要注意的是,为了更好地帮助你理解这个数据拆分的过程,我在这里选择拆分数据库的方式进行讲解。由于数据库服务器本身的并发特性,因此你也可以根据你的业务情况进行选择,比方说所有业务服务器共用一个数据库服务器,而不一定真的需要去进行数据库拆分。
可以看出,在数据并行或数据分布式模式中,每台计算机都是全量地从头到尾一条龙地执行一个程序,就像一个全能的铁道游击队战士。所以,你也可以将这种模式形象地理解成游击队模式,就和铁道游击队插曲的歌词有点类似:“我们扒飞车那个搞机枪,撞火车那个炸桥梁……”
这种模式的好处是,可以利用多台计算机并行处理多个请求,使得我们可以在相同的时间内完成更多的请求处理,解决了单机模式的计算效率瓶颈问题。但这种模式仍然存在如下几个问题,在实际应用中,我们需要对其进行相应的优化:
相同的应用部署到不同的服务器上,当大量用户请求过来时,如何能比较均衡地转发到不同的应用服务器上呢?解决这个问题的方法是设计一个负载均衡器,我会在“分布式高可靠”模块与你讲述负载均衡的相关原理。
当请求量较大时对数据库的频繁读写操作使得数据库的IO访问成为瓶颈。解决这个问题的方式是读写分离读数据库只接收读请求写数据库只接收写请求当然读写数据库之间要进行数据同步以保证数据一致性。
当有些数据成为热点数据时,会导致数据库访问频繁,压力增大。解决这个问题的方法是引入缓存机制,将热点数据加载到缓存中,一方面可以减轻数据库的压力,另一方面也可以提升查询效率。
从上面介绍可以看出,数据并行模式实现了多请求并行处理,但如果单个请求特别复杂,比方说需要几天甚至一周时间的时候,数据并行模式的整体计算效率还是不够高。
由此可见,数据并行模式的主要问题是:对提升单个任务的执行性能及降低时延无效。
集团军模式:任务并行或任务分布式
那么,有没有办法可以提高单个任务的执行性能,或者缩短单个任务的执行时间呢?答案是肯定的。任务并行(也叫作任务分布式)就是为解决这个问题而生的。那什么是任务并行呢?
任务并行指的是,将单个复杂的任务拆分为多个子任务,从而使得多个子任务可以在不同的计算机上并行执行。
我们仍以铁路售票系统为例,任务并行首先是对应用进行拆分,比如按照领域模型将用户管理、火车票管理、订单管理拆分成多个子系统分别运行在不同的计算机或服务器上。换句话说,原本包括用户管理、火车票管理和订单管理的一个复杂任务,被拆分成了多个子任务在不同计算机或服务器上执行,如下图所示:
可以看出,任务并行模式完成一项复杂任务主要有两个核心步骤:首先将单任务拆分成多个子任务,然后让多个子任务并行执行。这种模式和集团军模式很像,任务拆分者对应领导者,不同子系统对应不同兵种,不同子程序执行不同任务就像不同的兵种执行不同的命令一样,并且运行相同子系统或子任务的计算机又可以组成一个兵团。
在集团军模式中,由于多个子任务可以在多台计算机上运行,因此通过将同一任务待处理的数据分散到多个计算机上,在这些计算机上同时进行处理,就可以加快任务执行的速度。因为,只要一个复杂任务拆分出的任意子任务执行时间变短了,那么这个任务的整体执行时间就变短了。
当然nothing is perfect。集团军模式在提供了更好的性能、扩展性、可维护性的同时也带来了设计上的复杂性问题毕竟对一个大型业务的拆分也是一个难题。不过对于大型业务来讲从长远收益来看这个短期的设计阵痛是值得的。这也是许多大型互联网公司、高性能计算机构等竞相对业务进行拆分以及重构的一个重要原因。
分布式是什么?
讲了半天,那到底什么是分布式呢?
总结一下,分布式其实就是将相同或相关的程序运行在多台计算机上,从而实现特定目标的一种计算方式。
从这个定义来看,数据并行、任务并行其实都可以算作是分布式的一种形态。从这些计算方式的演变中不难看出,产生分布式的最主要驱动力量,是我们对于性能、可用性及可扩展性的不懈追求。
总结
在今天这篇文章中,我和你分享了分布式的起源,即从单机模式到数据并行(也叫作数据分布式)模式,再到任务并行(也叫作任务分布式)模式。
单机模式指的是,所有业务和数据均部署到同一台机器上。这种模式的好处是功能、代码和数据集中,便于维护、管理和执行,但计算效率是瓶颈。也就是说单机模式性能受限,也存在单点失效的问题。
数据并行(也叫作数据分布式)模式指的是,对数据进行拆分,利用多台计算机并行执行多个相同任务,通过在相同的时间内完成多个相同任务,从而缩短所有任务的总体执行时间,但对提升单个任务的执行性能及降低时延无效。
任务并行(也叫作任务分布式)模式指的是,单任务按照执行流程,拆分成多个子任务,多个子任务分别并行执行,只要一个复杂任务中的任意子任务的执行时间变短了,那么这个业务的整体执行时间也就变短了。该模式在提高性能、扩展性、可维护性等的同时,也带来了设计上的复杂性问题,比如复杂任务的拆分。
在数据并行和任务并行这两个模式的使用上,用户通常会比较疑惑,到底是采用数据并行还是任务并行呢?一个简单的原则就是:任务执行时间短,数据规模大、类型相同且无依赖,则可采用数据并行;如果任务复杂、执行时间长,且任务可拆分为多个子任务,则考虑任务并行。在实际业务中,通常是这两种模式并用。赶紧行动起来,去分析一下你的业务到底应该采用哪种分布式模式吧,加油!
课后思考
你觉得分布式与传统的并行计算的区别是什么?你可以结合多核、多处理器的情况进行思考。
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!

View File

@ -0,0 +1,106 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
02 分布式系统的指标:啥是分布式的三围
你好,我是聂鹏程。
在上一篇文章中,通过对分布式发展历程的学习,我们对分布式技术有了一个整体印象。接下来,我们就再来看看可以用哪些指标去具体地衡量一个分布式系统。如果你已经对分布式系统的指标了解得很清楚了,可以直接跳过这篇文章,学习下一讲的内容。
分布式系统的指标
从分布式技术的起源可以看出,分布式系统的出现就是为了用廉价的、普通的机器解决单个计算机处理复杂、大规模数据和任务时存在的性能问题、资源瓶颈问题,以及可用性和可扩展性问题。换句话说,分布式的目的是用更多的机器,处理更多的数据和更复杂的任务。
由此可以看出,性能、资源、可用性和可扩展性是分布式系统的重要指标。没错,它们就是分布式系统的“三围”。接下来,我们一起来看看这几个指标吧。
性能Performance
性能指标,主要用于衡量一个系统处理各种任务的能力。无论是分布式系统还是单机系统,都会对性能有所要求。
不同的系统、服务要达成的目的不同关注的性能自然也不尽相同甚至是相互矛盾。常见的性能指标包括吞吐量Throughput、响应时间Response Time和完成时间Turnaround Time
吞吐量指的是系统在一定时间内可以处理的任务数。这个指标可以非常直接地体现一个系统的性能就好比在客户非常多的情况下要评判一个银行柜台职员的办事效率你可以统计一下他在1个小时内接待了多少客户。常见的吞吐量指标有QPSQueries Per Second、TPSTransactions Per Second和BPSBits Per Second
QPS即查询数每秒用于衡量一个系统每秒处理的查询数。这个指标通常用于读操作越高说明对读操作的支持越好。所以我们在设计一个分布式系统的时候如果应用主要是读操作那么需要重点考虑如何提高QPS来支持高频的读操作。
TPS即事务数每秒用于衡量一个系统每秒处理的事务数。这个指标通常对应于写操作越高说明对写操作的支持越好。我们在设计一个分布式系统的时候如果应用主要是写操作那么需要重点考虑如何提高TPS来支持高频写操作。
BPS即比特数每秒用于衡量一个系统每秒处理的数据量。对于一些网络系统、数据管理系统我们不能简单地按照请求数或事务数来衡量其性能。因为请求与请求、事务与事务之间也存在着很大的差异比方说有的事务大需要写入更多的数据。那么在这种情况下BPS更能客观地反映系统的吞吐量。
响应时间指的是,系统响应一个请求或输入需要花费的时间。响应时间直接影响到用户体验,对于时延敏感的业务非常重要。比如用户搜索导航,特别是用户边开车边搜索的时候,如果响应时间很长,就会直接导致用户走错路。
完成时间指的是,系统真正完成一个请求或处理需要花费的时间。任务并行(也叫作任务分布式)模式出现的其中一个目的,就是缩短整个任务的完成时间。特别是需要计算海量数据或处理大规模任务时,用户对完成时间的感受非常明显。
资源占用Resource Usage
资源占用指的是一个系统提供正常能力需要占用的硬件资源比如CPU、内存、硬盘等。
一个系统在没有任何负载时的资源占用叫做空载资源占用体现了这个系统自身的资源占用情况。比如你在手机上安装一个App安装的时候通常会提示你有多少KB这就是该App的空载硬盘资源占用。对于同样的功能空载资源占用越少说明系统设计越优秀越容易被用户接受。
一个系统满额负载时的资源占用,叫做满载资源占用,体现了这个系统全力运行时占用资源的情况,也体现了系统的处理能力。同样的硬件配置上,运行的业务越多,资源占用越少,说明这个系统设计得越好。
可用性Availability
可用性,通常指的是系统在面对各种异常时可以正确提供服务的能力。可用性是分布式系统的一项重要指标,衡量了系统的鲁棒性,是系统容错能力的体现。
系统的可用性可以用系统停止服务的时间与总的时间之比衡量。假设一个网站总的运行时间是24小时在24小时内如果网站故障导致不可用的时间是4个小时那么系统的可用性就是4/24=0.167也就是0.167的比例不可用或者说0.833的比例可用。
除此之外系统的可用性还可以用某功能的失败次数与总的请求次数之比来衡量比如对网站请求1000次其中有10次请求失败那么可用性就是99%。
你可能经常在一个系统的宣传语中见到或听到3个9或3N3 Nines、5个9或9N9 Nines。这些宣传语中所说的3个9、5个9实际上就是系统厂商对可用性的一种标榜表明该系统可以在99.9%或99.999%的时间里能对外无故障地提供服务。
讲到了可用性你可能还会想到一个非常近似的术语可靠性Reliability。那可靠性和可用性有什么区别呢
可靠性通常用来表示一个系统完全不出故障的概率,更多地用在硬件领域。而可用性则更多的是指在允许部分组件失效的情况下,一个系统对外仍能正常提供服务的概率。
杰夫 · 迪恩Jeff Dean曾在Google I/O大会上透露谷歌一个基于1000台通用计算机的集群一年之内就有1000+硬盘会出现故障。由于现在比较常见的分布式系统基本上都是基于通用计算机的,这就意味着在这些系统中无法实现真正的可靠,所以我们也会在一些场合见到可靠性和可用性交换使用的情况。
可扩展性Scalability
可扩展性,指的是分布式系统通过扩展集群机器规模提高系统性能(吞吐量、响应时间、 完成时间)、存储容量、计算能力的特性,是分布式系统的特有性质。
分布式系统的设计初衷,就是利用集群多机的能力处理单机无法解决的问题。然而,完成某一具体任务所需要的机器数目,即集群规模,取决于单个机器的性能和任务的要求。
当任务的需求随着具体业务不断提高时,除了升级系统的性能做垂直/纵向扩展外,另一个做法就是通过增加机器的方式去水平/横向扩展系统规模。
这里垂直/纵向扩展指的是增加单机的硬件能力比如CPU增强、内存增大等水平/横向扩展指的就是,增加计算机数量。好的分布式系统总是在追求“线性扩展性”,也就是说系统的某一指标可以随着集群中的机器数量呈线性增长。
衡量系统可扩展性的常见指标是加速比Speedup也就是一个系统进行扩展后相对扩展前的性能提升。
如果你的扩展目标是为了提高系统吞吐量,则可以用扩展后和扩展前的系统吞吐量之比进行衡量。
如果你的目标是为了缩短完成时间,则可以用扩展前和扩展后的完成时间之比进行衡量。
不同场景下分布式系统的指标
我们都希望自己的分布式系统是高性能、高可用、高扩展和低资源占用的。但出于硬件成本、开发效率等因素的约束,我们无法在性能、可用性、可靠性和资源占用做到面面俱到。因此,在不同的业务场景中,设计者们需要有所取舍。
接下来我带你一起看一下典型的电商、IoT、电信、HPC高性能计算、大数据、云计算、区块链等业务或系统对不同指标的诉求。
电商系统。对于一个电商系统而言,系统设计者最看重的是吞吐量,为了处理更多的用户访问或订单业务,甚至不惜牺牲一些硬件成本。
IoT。对于一个IoT系统而言设计者最看重的是资源占用指标因为在一些功能极简的IoT设备上RAM、ROM的可用资源通常都是KB级的。
电信业务。对于电信业务而言,最重要的无疑是响应时间、完成时间,以及可用性。因为,你在打电话时不希望你的声音半天才被对方听到,也不希望半天才听到对方的回应,更不希望你的电话无法拨出。
HPC。HPC系统最显著的特点是任务执行时间极长一个天体物理任务的分析和计算通常耗时数周甚至数月。因此通过水平扩展来提高系统的加速比是HPC系统设计者需要关注的。
大数据。大数据任务的处理时间可能相对HPC系统来讲比较短但常见的完成时间也达到了小时级所以扩展性也是大数据系统首先要考虑的。
云计算。对于一个云计算系统而言,常见任务是虚拟主机或容器的创建、资源调整、销毁等操作,如何减少这些操作的完成时间,从而提升用户体验是设计者们要重点关注的。另外,云计算系统本质上卖的是资源,那么降低系统本身的资源开销,也是系统设计的重中之重。
区块链。区块链的吞吐量比较低比特币的TPS只有7次每秒单平均一次交易的确认就需要10分钟左右因此吞吐量和完成时间通常是区块链系统设计者的首要目标。
总结与思考
按照不同维度,分布式系统的指标可以分为性能、资源占用、可用性、可扩展性这四大类。我们自然希望自己的系统,是高性能、高可用、高扩展和低资源占用的,但考虑到硬件成本、开发效率等因素,必须要在设计不同的系统、业务时有所取舍。
所以我又和你分析了典型的电商、IoT、电信、HPC高性能计算、大数据、云计算、区块链等业务或系统的不同诉求进而得出了系统设计者需要关注哪些指标。你在设计其他类型的系统时可以按照这个思路进行取舍。
我在文中提到了,分布式系统的指标之间会存在一些冲突或约束。那你不妨思考一下:我们今天讲解的指标中,哪些指标之间是相互制约、相互冲突的,它们又是如何制约的呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,179 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
03 分布式互斥:有你没我,有我没你
你好!我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
通过前面的两篇文章,相信你已经对此次的分布式世界之行有了一个初步了解,想必对此次旅行也是充满期待。今天,我就带你正式踏上第一站:分布式协调与同步。在这一站,我将带你学习如何让分布在不同计算机上的程序具有“团队精神”,换句话说就是如何让程序通过协作共同去达成一个业务目标。
在本站,我将带你打卡的第一个景点是分布式互斥。那,什么是分布式互斥呢?
什么是分布式互斥?
想象一下,你正在一家餐厅使用自助咖啡机泡制咖啡,突然有个人过来挪走了你的杯子,开始泡制他自己的咖啡。你耐着性子等他操作完,继续泡制自己的咖啡。结果你开始没多久,他又回来中断了你泡制咖啡的过程。相信要不了几个回合,你和他就会上演一场“有你没我,有我没你”的格斗了。
这样现实的问题也同样存在于分布式世界。就像我们使用自助咖啡机时不希望被打扰一样,对于同一共享资源,一个程序正在使用的时候也不希望被其他程序打扰。这,就要求同一时刻只能有一个程序能够访问这种资源。
在分布式系统里这种排他性的资源访问方式叫作分布式互斥Distributed Mutual Exclusion而这种被互斥访问的共享资源就叫作临界资源Critical Resource
接下来,我带你一起看看如何才能让分布式系统里的程序互斥地访问临界资源。
霸道总裁:集中式算法
对于前面提到的咖啡机问题,我们首先想到的就是,增加一个“协调者”来约束大家使用自助咖啡机,解决强行插入打断别人的问题。
类似的,我们引入一个协调者程序,得到一个分布式互斥算法。每个程序在需要访问临界资源时,先给协调者发送一个请求。如果当前没有程序使用这个资源,协调者直接授权请求程序访问;否则,按照先来后到的顺序为请求程序“排一个号”。如果有程序使用完资源,则通知协调者,协调者从“排号”的队列里取出排在最前面的请求,并给它发送授权消息。拿到授权消息的程序,可以直接去访问临界资源。
这个互斥算法,就是我们所说的集中式算法,也可以叫做中央服务器算法。之所以这么称呼,是因为协调者代表着集中程序或中央服务器。
集中式算法的示意图如下所示:
如图所示程序1、2、3、4为普通运行程序另一个程序为协调者。当程序2和程序4需要使用临界资源时它们会向协调者发起申请请求协调者授权。
不巧的是程序3正在使用临界资源。这时协调者根据程序2和4的申请时间顺序依次将它们放入等待队列。在这个案例里程序4的申请时间早于程序2因此排在程序2的前面。
程序3使用完临界资源后通知协调者释放授权。此时协调者从等待队列中取出程序4并给它发放授权。这时程序4就可以使用临界资源了。
从上述流程可以看出,一个程序完成一次临界资源访问,需要如下几个流程和消息交互:
向协调者发送请求授权信息1次消息交互
协调者向程序发放授权信息1次消息交互
程序使用完临界资源后向协调者发送释放授权1次消息交互。
因此每个程序完成一次临界资源访问需要进行3次消息交互。
不难看出,集中式算法的优点在于直观、简单、信息交互量少、易于实现,并且所有程序只需和协调者通信,程序之间无需通信。但是,这个算法的问题也出在了协调者身上。
一方面协调者会成为系统的性能瓶颈。想象一下如果有100个程序要访问临界资源那么协调者要处理100*3=300条消息。也就是说协调者处理的消息数量会随着需要访问临界资源的程序数量线性增加。
另一方面,容易引发单点故障问题。协调者故障,会导致所有的程序均无法访问临界资源,导致整个系统不可用。
因此,在使用集中式算法的时候,一定要选择性能好、可靠性高的服务器来运行协调者。
小结一下:集中式算法具有简单、易于实现的特点,但可用性、性能易受协调者影响。在可靠性和性能有一定保障的情况下,比如中央服务器计算能力强、性能高、故障率低,或者中央服务器进行了主备,主故障后备可以立马升为主,且数据可恢复的情况下,集中式算法可以适用于比较广泛的应用场景。
民主协商:分布式算法
既然引入协调者会带来一些问题,这时你可能就会想,不用协调者是否可以实现对临界资源的互斥访问呢?想象一下,当你需要使用自助咖啡机的时候,是不是可以先去征求其他人的意见,在确认其他人都没在使用也暂时不会使用咖啡机时,你就可以放心大胆地去泡制自己的咖啡了呢?
同理我们可以把这套算法用于分布式系统。当一个程序要访问临界资源时先向系统中的其他程序发送一条请求消息在接收到所有程序返回的同意消息后才可以访问临界资源。其中请求消息需要包含所请求的资源、请求者的ID以及发起请求的时间。
这,就是民主协商法。在分布式领域中,我们称之为分布式算法,或者使用组播和逻辑时钟的算法。
如图所示程序1、2、3需要访问共享资源A。在时间戳为8的时刻程序1想要使用资源A于是向程序2和3发起使用资源A的申请希望得到它们的同意。在时间戳为12的时刻程序3想要使用资源A于是向程序1和2发起访问资源A的请求。
如图所示此时程序2暂时不访问资源A因此同意了程序1和3的资源访问请求。对于程序3来说由于程序1提出请求的时间更早因此同意程序1先使用资源并等待程序1返回同意消息。
如图所示程序1接收到其他所有程序的同意消息之后开始使用资源A。当程序1使用完资源A后释放使用权限向请求队列中需要使用资源A的程序3发送同意使用资源的消息并将程序3从请求队列中删除。此时程序3收到了其他所有程序的同意消息获得了使用资源A的权限开始使用临界资源A的旅程。
从上述流程可以看出,一个程序完成一次临界资源的访问,需要进行如下的信息交互:
向其他n-1个程序发送访问临界资源的请求总共需要n-1次消息交互
需要接收到其他n-1个程序回复的同意消息方可访问资源总共需要n-1次消息交互。
可以看出一个程序要成功访问临界资源至少需要2*(n-1)次消息交互。假设现在系统中的n个程序都要访问临界资源则会同时产生2n(n-1)条消息。总结来说,在大型系统中使用分布式算法,消息数量会随着需要访问临界资源的程序数量呈指数级增加,容易导致高昂的“沟通成本”。
从上述分析不难看出,分布式算法根据“先到先得”以及“投票全票通过”的机制,让每个程序按时间顺序公平地访问资源,简单粗暴、易于实现。但,这个算法可用性很低,主要包括两个方面的原因:
当系统内需要访问临界资源的程序增多时,容易产生“信令风暴”,也就是程序收到的请求完全超过了自己的处理能力,而导致自己正常的业务无法开展。
一旦某一程序发生故障,无法发送同意消息,那么其他程序均处在等待回复的状态中,使得整个系统处于停滞状态,导致整个系统不可用。所以,相对于集中式算法的协调者故障,分布式算法的可用性更低。
针对可用性低的一种改进办法是,如果检测到一个程序故障,则直接忽略这个程序,无需再等待它的同意消息。这就好比在自助餐厅,一个人离开餐厅了,那你在使用咖啡机前,也无需征得他的同意。但这样的话,每个程序都需要对其他程序进行故障检测,这无疑带来了更大的复杂性。
因此分布式算法适合节点数目少且变动不频繁的系统且由于每个程序均需通信交互因此适合P2P结构的系统。比如运行在局域网中的分布式文件系统具有P2P结构的系统等。
那么,在我们工作中,什么样的场景适合采用分布式算法呢?
Hadoop是我们非常熟悉的分布式系统其中的分布式文件系统HDFS的文件修改就是一个典型的应用分布式算法的场景。
如下图所示处于同一个局域网内的计算机1、2、3中都有同一份文件的备份信息且它们可以相互通信。这个共享文件就是临界资源。当计算机1想要修改共享的文件时需要进行如下操作
计算机1向计算机2、3发送文件修改请求
计算机2、3发现自己不需要使用资源因此同意计算机1的请求
计算机1收到其他所有计算机的同意消息后开始修改该文件
计算机1修改完成后向计算机2、3发送文件修改完成的消息并发送修改后的文件数据
计算机2和3收到计算机1的新文件数据后更新本地的备份文件。
归纳一下:分布式算法是一个“先到先得”和“投票全票通过”的公平访问机制,但通信成本较高,可用性也比集中式算法低,适用于临界资源使用频度较低,且系统规模较小的场景。
轮值CEO令牌环算法
那么除了集中式算法、分布式算法以外还有什么方法可以实现分布式互斥吗答案是肯定的。毕竟方法总比问题多。华为独创的轮值CEO其实就给了我们一个很好的启示。在华为的轮值CEO体系里CEO就是临界资源同时只能有一个人担任由多名高管轮流出任CEO。
类似的程序访问临界资源问题也可按照轮值CEO的思路实现。 如下图所示,所有程序构成一个环结构,令牌按照顺时针(或逆时针)方向在程序之间传递,收到令牌的程序有权访问临界资源,访问完成后将令牌传送到下一个程序;若该程序不需要访问临界资源,则直接把令牌传送给下一个程序。
在分布式领域这个算法叫作令牌环算法也可以叫作基于环的算法。为了便于理解与记忆你完全可以把这个方法形象地理解为轮值CEO法。
因为在使用临界资源前,不需要像分布式算法那样挨个征求其他程序的意见了,所以相对而言,在令牌环算法里单个程序具有更高的通信效率。同时,在一个周期内,每个程序都能访问到临界资源,因此令牌环算法的公平性很好。
但是不管环中的程序是否想要访问资源都需要接收并传递令牌所以也会带来一些无效通信。假设系统中有100个程序那么程序1访问完资源后即使其它99个程序不需要访问也必须要等令牌在其他99个程序传递完后才能重新访问资源这就降低了系统的实时性。
综上,令牌环算法非常适合通信模式为令牌环方式的分布式系统,例如移动自组织网络系统。一个典型的应用场景就是无人机通信。
无人机在通信时,工作原理类似于对讲机,同一时刻只能发送信息或接收信息。因此,通信中的上行链路(即向外发送信息的通信渠道)是临界资源。
如下图所示,所有的无人机组成一个环,按照顺时针方向通信。每个无人机只知道其前一个发送信息的无人机,和后一个将要接收信息的无人机。拥有令牌的无人机可以向外发送信息,其他无人机只能接收数据。拥有令牌的无人机通信完成后,会将令牌传送给后一个无人机。
所有的无人机轮流通信并传输数据,从而消除了多个无人机对通信资源的争夺,使得每个无人机都能接收到其他无人机的信息,降低了通信碰撞导致的丢包率,保证了网络通信的稳定性,提高了多个无人机之间的协作效率。
令牌环算法是一种更加公平的算法,通常会与通信令牌结合,从而取得很好的效果。特别是当系统支持广播或组播通信模式时,该算法更加高效、可行。
对于集中式和分布式算法都存在的单点故障问题在令牌环中若某一个程序例如上图的无人机2出现故障则直接将令牌传递给故障程序的下一个程序例如上图中无人机1直接将令牌传送给无人机3从而很好地解决单点故障问题提高系统的健壮性带来更好的可用性。但这就要求每个程序都要记住环中的参与者信息这样才能知道在跳过一个参与者后令牌应该传递给谁。
小结一下:令牌环算法的公平性高,在改进单点故障后,稳定性也很高,适用于系统规模较小,并且系统中每个程序使用临界资源的频率高且使用时间比较短的场景。
知识扩展:有适合大规模系统中的分布式互斥算法吗?
可以看到上面提到的集中式、分布式和令牌环3个互斥算法都不适用于规模过大、节点数量过多的系统。那么什么样的互斥算法适用于大规模系统呢
由于大规模系统的复杂性,我们很自然地想到要用一个相对复杂的互斥算法。时下有一个很流行的互斥算法,两层结构的分布式令牌环算法,把整个广域网系统中的节点组织成两层结构,可以用于节点数量较多的系统,或者是广域网系统。
我们知道广域网由多个局域网组成因此在该算法中局域网是较低的层次广域网是较高的层次。每个局域网中包含若干个局部进程和一个协调进程。局部进程在逻辑上组成一个环形结构在每个环形结构上有一个局部令牌T在局部进程间传递。局域网与局域网之间通过各自的协调进程进行通信这些协调进程同样组成一个环结构这个环就是广域网中的全局环。在这个全局环上有一个全局令牌在多个协调进程间传递。
总结
我首先结合生活中的案例带你剖析了什么是分布式互斥以及为什么需要分布式互斥。然后我和你介绍了3类典型的分布式互斥方法集中式算法、分布式算法以及令牌环算法并列举了对应的适用场景相信你通过今天的学习一定可以为你的场景选择一个合适的分布式互斥算法了加油
接下来,我把今天的内容通过下面的一张思维导图再全面总结下。
课后问题
最后,我想请你思考以下两个问题:
你认为集中式算法、分布式算法和令牌环算法,还有什么可以改进的地方吗?
传统单机上的互斥方法,为什么不能用于分布式环境呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,170 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
04 分布式选举:国不可一日无君
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
相信你对集群的概念并不陌生。简单说,集群一般是由两个或两个以上的服务器组建而成,每个服务器都是一个节点。我们经常会听到数据库集群、管理集群等概念,也知道数据库集群提供了读写功能,管理集群提供了管理、故障恢复等功能。
接下来,你开始好奇了,对于一个集群来说,多个节点到底是怎么协同,怎么管理的呢。比如,数据库集群,如何保证写入的数据在每个节点上都一致呢?
也许你会说,这还不简单,选一个“领导”来负责调度和管理其他节点就可以了啊。
这个想法一点儿也没错。这个“领导”,在分布式中叫做主节点,而选“领导”的过程在分布式领域中叫作分布式选举。
然后,你可能还会问,怎么选主呢。那接下来,我们就一起去揭开这个谜底吧。
为什么要有分布式选举?
主节点,在一个分布式集群中负责对其他节点的协调和管理,也就是说,其他节点都必须听从主节点的安排。
主节点的存在,就可以保证其他节点的有序运行,以及数据库集群中的写入数据在每个节点上的一致性。这里的一致性是指,数据在每个集群节点中都是一样的,不存在不同的情况。
当然,如果主故障了,集群就会天下大乱,就好比一个国家的皇帝驾崩了,国家大乱一样。比如,数据库集群中主节点故障后,可能导致每个节点上的数据会不一致。
这,就应了那句话“国不可一日无君”,对应到分布式系统中就是“集群不可一刻无主”。总结来说,选举的作用就是选出一个主节点,由它来协调和管理其他节点,以保证集群有序运行和节点间数据的一致性。
分布式选举的算法
那么,如何在集群中选出一个合适的主呢?这是一个技术活儿,目前常见的选主方法有基于序号选举的算法( 比如Bully算法、多数派算法比如Raft算法、ZAB算法等。接下来就和我一起来看看这几种算法吧。
长者为大Bully算法
Bully算法是一种霸道的集群选主算法为什么说是霸道呢因为它的选举原则是“长者”为大即在所有活着的节点中选取ID最大的节点作为主节点。
在Bully算法中节点的角色有两种普通节点和主节点。初始化时所有节点都是平等的都是普通节点并且都有成为主的权利。但是当选主成功后有且仅有一个节点成为主节点其他所有节点都是普通节点。当且仅当主节点故障或与其他节点失去联系后才会重新选主。
Bully算法在选举过程中需要用到以下3种消息
Election消息用于发起选举
Alive消息对Election消息的应答
Victory消息竞选成功的主节点向其他节点发送的宣誓主权的消息。
Bully算法选举的原则是“长者为大”意味着它的假设条件是集群中每个节点均知道其他节点的ID。在此前提下其具体的选举过程是
集群中每个节点判断自己的ID是否为当前活着的节点中ID最大的如果是则直接向其他节点发送Victory消息宣誓自己的主权
如果自己不是当前活着的节点中ID最大的则向比自己ID大的所有节点发送Election消息并等待其他节点的回复
若在给定的时间范围内本节点没有收到其他节点回复的Alive消息则认为自己成为主节点并向其他节点发送Victory消息宣誓自己成为主节点若接收到来自比自己ID大的节点的Alive消息则等待其他节点发送Victory消息
若本节点收到比自己ID小的节点发送的Election消息则回复一个Alive消息告知其他节点我比你大重新选举。
目前已经有很多开源软件采用了Bully算法进行选主比如MongoDB的副本集故障转移功能。MongoDB的分布式选举中采用节点的最后操作时间戳来表示ID时间戳最新的节点其ID最大也就是说时间戳最新的、活着的节点是主节点。
小结一下。Bully算法的选择特别霸道和简单谁活着且谁的ID最大谁就是主节点其他节点必须无条件服从。这种算法的优点是选举速度快、算法复杂度低、简单易实现。
但这种算法的缺点在于需要每个节点有全局的节点信息因此额外信息存储较多其次任意一个比当前主节点ID大的新节点或节点故障后恢复加入集群的时候都可能会触发重新选举成为新的主节点如果该节点频繁退出、加入集群就会导致频繁切主。
民主投票Raft算法
Raft算法是典型的多数派投票选举算法其选举机制与我们日常生活中的民主投票机制类似核心思想是“少数服从多数”。也就是说Raft算法中获得投票最多的节点成为主。
采用Raft算法选举集群节点的角色有3种
Leader即主节点同一时刻只有一个Leader负责协调和管理其他节点
Candidate即候选者每一个节点都可以成为Candidate节点在该角色下才可以被选为新的Leader
FollowerLeader的跟随者不可以发起选举。
Raft选举的流程可以分为以下几步
初始化时所有节点均为Follower状态。
开始选主时所有节点的状态由Follower转化为Candidate并向其他节点发送选举请求。
其他节点根据接收到的选举请求的先后顺序,回复是否同意成为主。这里需要注意的是,在每一轮选举中,一个节点只能投出一张票。
若发起选举请求的节点获得超过一半的投票则成为主节点其状态转化为Leader其他节点的状态则由Candidate降为Follower。Leader节点与Follower节点之间会定期发送心跳包以检测主节点是否活着。
当Leader节点的任期到了即发现其他服务器开始下一轮选主周期时Leader节点的状态由Leader降级为Follower进入新一轮选主。
节点的状态迁移如下所示图中的term指的是选举周期
请注意每一轮选举每个节点只能投一次票。这种选举就类似人大代表选举正常情况下每个人大代表都有一定的任期任期到后会触发重新选举且投票者只能将自己手里唯一的票投给其中一个候选者。对应到Raft算法中选主是周期进行的包括选主和任值两个时间段选主阶段对应投票阶段任值阶段对应节点成为主之后的任期。但也有例外的时候如果主节点故障会立马发起选举重新选出一个主节点。
Google开源的Kubernetes擅长容器管理与调度为了保证可靠性通常会部署3个节点用于数据备份。这3个节点中有一个会被选为主其他节点作为备。Kubernetes的选主采用的是开源的etcd组件。而etcd的集群管理器etcds是一个高可用、强一致性的服务发现存储仓库就是采用了Raft算法来实现选主和一致性的。
小结一下。Raft算法具有选举速度快、算法复杂度低、易于实现的优点缺点是它要求系统内每个节点都可以相互通信且需要获得过半的投票数才能选主成功因此通信量大。该算法选举稳定性比Bully算法好这是因为当有新节点加入或节点故障恢复后会触发选主但不一定会真正切主除非新节点或故障后恢复的节点获得投票数过半才会导致切主。
具有优先级的民主投票ZAB算法
ZABZooKeeper Atomic Broadcast选举算法是为ZooKeeper实现分布式协调功能而设计的。相较于Raft算法的投票机制ZAB算法增加了通过节点ID和数据ID作为参考进行选主节点ID和数据ID越大表示数据越新优先成为主。相比较于Raft算法ZAB算法尽可能保证数据的最新性。所以ZAB算法可以说是对Raft算法的改进。
使用ZAB算法选举时集群中每个节点拥有3种角色
Leader主节点
Follower跟随者节点
Observer观察者无投票权。
选举过程中集群中的节点拥有4个状态
Looking状态即选举状态。当节点处于该状态时它会认为当前集群中没有Leader因此自己进入选举状态。
Leading状态即领导者状态表示已经选出主且当前节点为Leader。
Following状态即跟随者状态集群中已经选出主后其他非主节点状态更新为Following表示对Leader的追随。
Observing状态即观察者状态表示当前节点为Observer持观望态度没有投票权和选举权。
投票过程中,每个节点都有一个唯一的三元组(server_id, server_zxID, epoch)其中server_id表示本节点的唯一IDserver_zxID表示本节点存放的数据ID数据ID越大表示数据越新选举权重越大epoch表示当前选取轮数一般用逻辑时钟表示。
ZAB选举算法的核心是“少数服从多数ID大的节点优先成为主”因此选举过程中通过(vote_id, vote_zxID)来表明投票给哪个节点其中vote_id表示被投票节点的IDvote_zxID表示被投票节点的服务器zxID。ZAB算法选主的原则是server_zxID最大者成为Leader若server_zxID相同则server_id最大者成为Leader。
接下来我以3个Server的集群为例此处每个Server代表一个节点与你介绍ZAB选主的过程。
第一步当系统刚启动时3个服务器当前投票均为第一轮投票即epoch=1且zxID均为0。此时每个服务器都推选自己并将选票信息广播出去。
第二步根据判断规则由于3个Server的epoch、zxID都相同因此比较server_id较大者即为推选对象因此Server 1和Server 2将vote_id改为3更新自己的投票箱并重新广播自己的投票。
第三步此时系统内所有服务器都推选了Server 3因此Server 3当选Leader处于Leading状态向其他服务器发送心跳包并维护连接Server1和Server2处于Following状态。
小结一下。ZAB算法性能高对系统无特殊要求采用广播方式发送信息若节点中有n个节点每个节点同时广播则集群中信息量为n*(n-1)个消息容易出现广播风暴且除了投票还增加了对比节点ID和数据ID这就意味着还需要知道所有节点的ID和数据ID所以选举时间相对较长。但该算法选举稳定性比较好当有新节点加入或节点故障恢复后会触发选主但不一定会真正切主除非新节点或故障后恢复的节点数据ID和节点ID最大且获得投票数过半才会导致切主。
三种选举算法的对比分析
好了我已经带你理解了分布式选举的3种经典算法即Bully算法、Raft算法和ZAB算法。那么接下来我就从消息传递内容、选举机制和选举过程的维度对这3种算法进行一个对比分析以帮助你理解记忆。
知识扩展:为什么“多数派”选主算法通常采用奇数节点,而不是偶数节点呢?
多数派选主算法的核心是少数服从多数,获得投票多的节点胜出。想象一下,如果现在采用偶数节点集群,当两个节点均获得一半投票时,到底应该选谁为主呢?
答案是,在这种情况下,无法选出主,必须重新投票选举。但即使重新投票选举,两个节点拥有相同投票数的概率也会很大。因此,多数派选主算法通常采用奇数节点。
也是大家通常看到ZooKeeper、 etcd、Kubernetes等开源软件选主均采用奇数节点的一个关键原因。
总结
今天我首先与你讲述了什么是分布式选举以及为什么需要分布式选举。然后我和你介绍了实现分布式选举的3种方法Bully算法、Raft算法以及ZooKeeper中的ZAB算法并通过实例与你展示了各类方法的选举流程。
我将今天的主要内容总结为了如下所示的思维导图,来帮助你加深理解与记忆。
思考题
分布式选举和一致性的关系是什么?
你是否见到过一个集群中存在双主的场景呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,171 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
05 分布式共识:存异求同
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
首先,我们来回忆下上篇文章的分布式选举。分布式选举问题,是从多个节点中选出一个主节点,相关的选举方法几乎都有一个共同特点:每个节点都有选举权和被选举权。大部分选举方法采用多数策略,也就是说一个节点只有得到了大部分节点的同意或认可才能成为主节点,然后主节点向其他节点宣告主权。
其实,这个选主过程就是一个分布式共识问题,因为每个节点在选出主节点之前都可以认为自己会成为主节点,也就是说集群节点“存异”;而通过选举的过程选出主节点,让所有的节点都认可该主节点,这叫“求同”。由此可见,分布式共识的本质就是“存异求同”。
所以,从本质上看,分布式选举问题,其实就是传统的分布式共识方法,主要是基于多数投票策略实现的。基于多数投票策略的分布式选举方法,如果用于分布式在线记账一致性问题中,那么记账权通常会完全掌握到主节点的手里,这使得主节点非常容易造假,且存在性能瓶颈。因此,分布式选举不适用于分布式在线记账的一致性问题。在今天这篇文章中,我就带你了解另外一种用于解决分布式在线记账一致性问题的分布式共识技术。
这里所说的分布式在线记账,是指在没有集中的发行方,也就是没有银行参与的情况下,任意一台接入互联网的电脑都能参与买卖,所有看到该交易的服务器都可以记录这笔交易,并且记录信息最终都是一致的,以保证交易的准确性。而如何保证交易的一致性,就是该场景下的分布式共识问题。
接下来,我们就一起学习下分布式共识技术吧。
什么是分布式共识?
假设现在有5台服务器分散在美国华盛顿、英国伦敦、法国巴黎、中国北京、中国上海分别对应着用户{A,B,C,D,E}。现在用户A给用户B转了100元。
在传统方法中我们通过银行进行转账并记录该笔交易。但分布式在线记账方法中没有银行这样的一个集中方而是由上述5台服务器来记录该笔交易。但是这5台服务器均是有各自想法的个体都可以自主操作或记录那么如何保证记录的交易是一致的呢就是分布式共识技术要解决的问题。
可以看出,分布式共识就是在多个节点均可独自操作或记录的情况下,使得所有节点针对某个状态达成一致的过程。通过共识机制,我们可以使得分布式系统中的多个节点的数据达成一致。
看到这里,相信你已经看出来了,我在这里说的分布式在线记账,就是近几年比较火的区块链技术解决的问题。而分布式共识技术,就是区块链技术共识机制的核心。
接下来,请和我一起看看分布式共识是如何实现的,有哪些方法吧。
分布式共识方法
为了不影响你理解分布式共识的核心技术,我会先和你分享区块链中的一个核心概念:挖矿。
在传统的交易方式中用户A给用户B转账需要银行来实行具体的转账操作并记录交易银行会从中收取相应的手续费。而采用分布式在线记账的话参与记录这笔交易的服务器也可以从中获得一些奖励这些奖励在区块链技术中可以换成钱。所有服务器帮助记录交易并达成一致的过程就是区块链中的“挖矿”。
区块链是一种链式数据结构,由包含交易信息的区块通过哈希指针、根据时间顺序连接而成,也是一种分布式数据库。区块是区块链的主要组成部分,每个区块由区块头和区块内容数据构成。区块头记录了时间戳,并用于保证区块链的连接性;区块内容数据中包含了多条交易信息。如果你对区块链技术的其他概念感兴趣的话,可以自行查阅更多资料。
接下来我将与你介绍3种主流的解决分布式在线记账一致性问题的共识技术PoWProof-of-Work工作量证明、PoSProof-of-Stake权益证明和DPoSDelegated Proof of Stake委托权益证明
PoW
从分布式选举问题可以看出,同一轮选举中有且仅有一个节点成为主节点。同理,在分布式在线记账问题中,针对同一笔交易,有且仅有一个节点或服务器可以获得记账权,然后其他节点或服务器同意该节点或服务器的记账结果,达成一致。
也就是说,分布式共识包括两个关键点,获得记账权和所有节点或服务器达成一致。
PoW算法是以每个节点或服务器的计算能力即“算力”来竞争记账权的机制因此是一种使用工作量证明机制的共识算法。也就是说谁的计算力强、工作能力强谁获得记账权的可能性就越大。
那么,如何体现节点的“算力”呢?答案就是,每个节点都去解一道题,谁能先解决谁的能力就强。
假设每个节点会划分多个区块用于记录用户交易PoW算法获取记账权的原理是利用区块的index、前一个区块的哈希值、交易的时间戳、区块数据和nonce值通过SHA256哈希算法计算出一个哈希值并判断前k个值是否都为0。如果不是则递增nonce值重新按照上述方法计算如果是则本次计算的哈希值为要解决的题目的正确答案。谁最先计算出正确答案谁就获得这个区块的记账权。
请注意nonce值是用来找到一个满足哈希值的数字k为哈希值前导零的个数标记了计算的难度0越多计算难度越大。
达成共识的过程,就是获得记账权的节点将该区块信息广播给其他节点,其他节点判断该节点找到的区块中的所有交易都是有效且之前未存在过的,则认为该区块有效,并接受该区块,达成一致。
接下来我以上文提到的分散在世界各地的5台服务器为例和你说明基于PoW的共识记账过程。
假设客户端A产生一个新的交易基于PoW的共识记账过程为
客户端A产生新的交易向全网进行广播要求对交易进行记账。
每个记账节点接收到这个请求后,将收到的交易信息放入一个区块中。
每个节点通过PoW算法计算本节点的区块的哈希值尝试找到一个具有足够工作量难度的工作量证明。
若节点D找到了一个工作量证明向全网广播。当然当且仅当包含在该区块中的交易都是有效且之前未存在过的其他节点才会认同该区块的有效性。
其他节点接收到广播信息后,若该区块有效,接受该区块,并跟随在该区块的末尾,制造新区块延长该链条,将被接受的区块的随机哈希值视为新区块的随机哈希值。
可以看出PoW算法中谁的计算能力强获得记账权的可能性就越大。但必须保证其记账的区块是有效的并在之前未存在过才能获得其他节点的认可。
目前比特币平台采用了PoW算法属于区块链1.0阶段其重心在于货币比特币大约10min 才会产生一个区块,区块的大小也只有 1MB仅能够包含 30004000 笔交易,平均每秒只能够处理 5~7个位数笔交易。
PoW通过“挖矿”的方式发行新币把比特币分散给个人实现了相对的公平。PoW的容错机制允许全网50%的节点出错因此如果要破坏系统则需要投入极大成本若你有全球51%的算力,则可尝试攻击比特币)。
PoW机制每次达成共识需要全网共同参与运算增加了每个节点的计算量并且如果题目过难会导致计算时间长、资源消耗多而如果题目过于简单会导致大量节点同时获得记账权冲突多。这些问题都会增加达成共识的时间。
所以PoW机制的缺点也很明显共识达成的周期长、效率低资源消耗大。
PoS
为了解决PoW算法的问题引入了PoS算法。它的核心原理是由系统权益代替算力来决定区块记账权拥有的权益越大获得记账权的概率就越大。
这里所谓的权益就是每个节点占有货币的数量和时间而货币就是节点所获得的奖励。PoS算法充分利用了分布式在线记账中的奖励鼓励“利滚利”。
在股权证明PoS模式下根据你持有货币的数量和时间给你发利息。每个币每天产生1币龄比如你持有100个币总共持有了50天那么你的币龄就为5000。这个时候如果你发现了一个PoS区块你的币龄就会被减少365。每被减少365币龄你就可以从区块中获得0.05个币的利息(可理解为年利率5%)。
在这个案例中,利息 = 5000*5% /365 = 0.68个币。这下就有意思了,持币有利息。
基于PoS算法获得区块记账权的方法与基于PoW的方法类似不同之处在于节点计算获取记账权的方法不一样PoW是利用区块的index、前一个区块的哈希值、交易的时间戳、区块数据和nonce值通过SHA256哈希算法计算出一个哈希值并判断前k个值是否都为0而PoS是根据节点拥有的股权或权益进行计算的。
接下来我们看一个具体的案例。假设一个公链网络中共有3个节点A 、B和C。其中 A 节点拥有10000 个币总共持有30天而 B 和 C 节点分别有 1000 和 2000 个币分别持有15和20天。
通过PoS算法决定区块记账权的流程和PoW算法类似唯一不同的就是每个节点在计算自己记账权的时候通过计算自己的股权或权益来评估如果发现自己权益最大则将自己的区块广播给其他节点当然必须保证该区块的有效性。
以太坊平台属于区块链2.0阶段在区块链1.0的基础上进一步强调了合约采用了PoS算法。12年发布的点点币PPC综合了PoW工作量证明及PoS权益证明方式从而在安全和节能方面实现了创新。
可以看出PoS将算力竞争转变成权益竞争。与PoW相比PoS不需要消耗大量的电力就能够保证区块链网络的安全性同时也不需要在每个区块中创建新的货币来激励记账者参与当前网络的运行这也就在一定程度上缩短了达成共识所需要的时间。所以基于PoS算法的以太坊每秒大概能处理 30 笔左右的交易。
PoS算法中持币越多或持币越久币龄就会越高持币人就越容易挖到区块并得到激励而持币少的人基本没有机会这样整个系统的安全性实际上会被持币数量较大的一部分人掌握容易出现垄断现象。
DPoS
为了解决PoS算法的垄断问题2014年比特股BitShares的首席开发者丹尼尔 · 拉里默Dan Larimer提出了委托权益证明法也就是DPoS算法。
DPoS算法的原理类似股份制公司的董事会制度普通股民虽然拥有股权但进不了董事会他们可以投票选举代表受托人代他们做决策。DPoS是由被社区选举的可信帐户受托人比如得票数排行前101位来拥有记账权。
为了成为正式受托人用户要去社区拉票获得足够多的信任。用户根据自己持有的货币数量占总量的百分比来投票好比公司股票机制假设总的发行股票为1000现在股东A持股10那么股东A投票权为10/1000=1/100。如下图所示根据自己拥有的权益投票选出可代表自己的受托节点受托节点之间竞争记账权。
在DPos算法中通常会选出k(比如101)个受托节点,它们的权利是完全相等的。受托节点之间争取记账权也是根据算力进行竞争的。只要受托节点提供的算力不稳定,计算机宕机或者利用手中的权力作恶,随时可以被握着货币的普通节点投票踢出整个系统,而后备的受托节点可以随时顶上去。
DPoS在比特股和Steem上已运行多年整个网络中选举出的多个节点能够在 1s 之内对 99.9% 的交易进行确认。此外DPoS在EOSEnterprise Operation System为商用分布式应用设计的一款区块链操作系统中也有广泛应用被称为区块链3.0阶段。
DPoS是在PoW和PoS的基础上进行改进的相比于PoS算法DPoS引入了受托人优点主要表现在
由投票选举出的若干信誉度更高的受托人记账解决了所有节点均参与竞争导致消息量大、达成一致的周期长的问题。也就是说DPoS能耗更低具有更快的交易速度。
每隔一定周期会调整受托人,避免受托人造假和独权。
但是在DPoS中由于大多数持币人通过受托人参与投票投票的积极性并不高且一旦出现故障节点DPoS无法及时做出应对导致安全隐患。
三种分布式共识算法对比分析
好了现在我们已经理解了PoW、PoS和DPoS这3种分布式共识算法。接下来为了方便你理解与记忆我把这三种算法放在一起做下对比如下图所示。
知识扩展:一致性与共识的区别是什么?
在平常使用中,我们通常会混淆一致性和共识这两个概念,接下来我就为你分析下这两个概念吧。
一致性是指在分布式系统中,针对同一数据或状态以多个副本形式保存在不同节点上;当对某个数据或状态副本做出修改后,能保证多副本达到对外表现的数据一致性。
共识是指一个或多个进程提议某些修改后,采用一种大家认可的方法,使得系统中所有进程对该修改达成一致意见,该方法称为共识机制。
也就是说,共识重点在于达成一致的过程或方法,一致性问题在于最终对外表现的结果。
总结
今天我和你介绍了分布式在线记账问题中的3种常见共识算法PoW、PoS和DPoS。
PoW算法 以每个节点或服务器的计算能力即“算力”来竞争记账权的机制。类似于按劳分配谁工作量大谁拿的多。其实竞争的就是挖矿设备看谁的挖矿设备的CPU、GPU等更厉害缺点就是费电、污染环境。
PoS算法由系统权益代替算力来决定区块记账权拥有的权益越大获得记账权的概率就越大。这种方法的优点是节能不需要挖矿了但缺点是容易形成垄断。
DPoS算法是一种委托权益证明算法。持有币的人可以通过投票选举出一些节点来作为代表去记账类似于全国人民代表大会制度。
讲到这里我还希望你明确区块链中的共识技术并没那么难和神秘常用的算法就是PoW、PoS和DPoS。希望通过这篇文章你能对共识技术有一定的了解能勇敢、自信地去探索分布式共识技术和区块链技术。
最后,我再用思维导图概括一下今天的内容。
思考题
你能描述出拜占庭将军问题是什么吗?你认为可以如何解决拜占庭将军的容错问题呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,275 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
06 分布式事务All or nothing
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
对于网上购物的每一笔订单来说,电商平台一般都会有两个核心步骤:一是订单业务采取下订单操作,二是库存业务采取减库存操作。
通常这两个业务会运行在不同的机器上甚至是运行在不同区域的机器上。针对同一笔订单当且仅当订单操作和减库存操作一致时才能保证交易的正确性。也就是说一笔订单只有这两个操作都完成才能算做处理成功否则处理失败充分体现了“All or nothing”的思想。
在分布式领域中,这个问题就是分布式事务问题。那么今天,我们就一起打卡分布式事务吧。
什么是分布式事务?
在介绍分布式事务之前,我们首先来看一下什么是事务。
事务Transaction提供一种机制将包含一系列操作的工作序列纳入到一个不可分割的执行单元。只有所有操作均被正确执行才能提交事务任意一个操作失败都会导致整个事务回滚Rollback到之前状态即所有操作均被取消。简单来说事务提供了一种机制使得工作要么全部都不做要么完全被执行即all or nothing。
通常情况下我们所说的事务指的都是本地事务也就是在单机上的事务。而事务具备四大基本特征ACID具体含义如下。
A原子性Atomicity即事务最终的状态只有两种全部执行成功和全部不执行不会停留在中间某个环节。若处理事务的任何一项操作不成功就会导致整个事务失败。一旦操作失败所有操作都会被取消即回滚使得事务仿佛没有被执行过一样。就好比买一件商品购买成功时则给商家付了钱商品到手购买失败时则商品在商家手中消费者的钱也没花出去。
C一致性Consistency是指事务操作前和操作后数据满足完整性约束数据库保持一致性状态。比如用户A和用户B在银行分别有800元和600元总共1400元用户A给用户B转账200元分为两个步骤从A的账户扣除200元和对B的账户增加200元。一致性就是要求上述步骤操作后最后的结果是用户A还有600元用户B有800元总共1400元而不会出现用户A扣除了200元但用户B未增加的情况该情况用户A和B均为600元总共1200元
I隔离性Isolation是指当系统内有多个事务并发执行时多个事务同时使用相同的数据时不会相互干扰每个事务都有一个完整的数据空间对其他并发事务是隔离的。也就是说消费者购买商品这个事务是不影响其他消费者购买的。
D持久性Durability也被称为永久性是指一个事务被执行后那么它对数据库所做的更新就永久地保存下来了。即使发生系统崩溃或宕机等故障重新启动数据库系统后只要数据库能够重新被访问那么一定能够将其恢复到事务完成时的状态。就像消费者在网站上的购买记录即使换了手机也依然可以查到。
只有在数据操作请求满足上述四个特性的条件下,存储系统才能保证处于正确的工作状态。因此,无论是在传统的集中式存储系统还是在分布式存储系统中,任何数据操作请求都必须满足 ACID 特性。
分布式事务,就是在分布式系统中运行的事务,由多个本地事务组合而成。在分布式场景下,对事务的处理操作可能来自不同的机器,甚至是来自不同的操作系统。文章开头提到的电商处理订单问题,就是典型的分布式事务。
分布式事务由多个事务组成因此基本满足ACID其中的C是强一致性也就是所有操作均执行成功才提交最终结果以保证数据一致性或完整性。但随着分布式系统规模不断扩大复杂度急剧上升达成强一致性所需时间周期较长限定了复杂业务的处理。为了适应复杂业务出现了BASE理论该理论的一个关键点就是采用最终一致性代替强一致性。我会在“知识扩展”模块与你详细展开BASE理论这部分内容。
介绍完什么是事务和分布式事务,以及它们的基本特征后,就进入“怎么做”的阶段啦。所以接下来,我们就看看如何实现分布式事务吧。
如何实现分布式事务?
实际上分布式事务主要是解决在分布式环境下组合事务的一致性问题。实现分布式事务有以下3种基本方法
基于XA协议的二阶段提交协议方法
三阶段提交协议方法;
基于消息的最终一致性方法。
其中基于XA协议的二阶段提交协议方法和三阶段提交协议方法采用了强一致性遵从ACID。基于消息的最终一致性方法采用了最终一致性遵从BASE理论。下面我将带你一起学习这三种方法。
基于XA协议的二阶段提交方法
XA是一个分布式事务协议规定了事务管理器和资源管理器接口。因此XA协议包括事务管理器和本地资源管理器两个部分。
XA实现分布式事务的原理就类似于我在第3讲中与你介绍的集中式算法事务管理器相当于协调者负责各个本地资源的提交和回滚而资源管理器就是分布式事务的参与者通常由数据库实现比如Oracle、DB2等商业数据库都实现了XA接口。
基于 XA协议的二阶段提交方法中二阶段提交协议Two-phase Commit Protocol2PC用于保证分布式系统中事务提交时的数据一致性是XA在全局事务中用于协调多个资源的机制。
那么,两阶段提交协议如何保证分布在不同节点上的分布式事务的一致性呢?为了保证它们的一致性,我们需要引入一个协调者来管理所有的节点,并确保这些节点正确提交操作结果,若提交失败则放弃事务。接下来,我们看看两阶段提交协议的具体过程。
两阶段提交协议的执行过程分为投票Voting和提交Commit两个阶段。
首先我们看一下第一阶段投票在这一阶段协调者Coordinator即事务管理器会向事务的参与者Cohort即本地资源管理器发起执行操作的CanCommit请求并等待参与者的响应。参与者接收到请求后会执行请求中的事务操作将操作信息记录到事务日志中但不提交即不会修改数据库中的数据待参与者执行成功则向协调者发送“Yes”消息表示同意操作若不成功则发送“No”消息表示终止操作。
当所有的参与者都返回了操作结果Yes或No消息系统进入了第二阶段提交阶段也可以称为执行阶段。在提交阶段协调者会根据所有参与者返回的信息向参与者发送DoCommit提交或DoAbort取消指令。具体规则如下
若协调者从参与者那里收到的都是“Yes”消息则向参与者发送“DoCommit”消息。参与者收到“DoCommit”消息后完成剩余的操作比如修改数据库中的数据并释放资源整个事务过程中占用的资源然后向协调者返回“HaveCommitted”消息
若协调者从参与者收到的消息中包含“No”消息则向所有参与者发送“DoAbort”消息。此时投票阶段发送“Yes”消息的参与者则会根据之前执行操作时的事务日志对操作进行回滚就好像没有执行过请求操作一样然后所有参与者会向协调者发送“HaveCommitted”消息
协调者接收到来自所有参与者的“HaveCommitted”消息后就意味着整个事务结束了。
接下来我以用户A要在网上下单购买100件T恤为例重点与你介绍下单操作和减库存操作这两个操作帮助你加深对二阶段提交协议的理解。
第一阶段订单系统中将与用户A有关的订单数据库锁住准备好增加一条关于用户A购买100件T恤的信息并将同意消息“Yes”回复给协调者。而库存系统由于T恤库存不足出货失败因此向协调者回复了一个终止消息“No”。
第二阶段由于库存系统操作不成功因此协调者就会向订单系统和库存系统发送“DoAbort”消息。订单系统接收到“DoAbort”消息后将系统内的数据退回到没有用户A购买100件T恤的版本并释放锁住的数据库资源。订单系统和库存系统完成操作后向协调者发送“HaveCommitted”消息表示完成了事务的撤销操作。
至此用户A购买100件T恤这一事务已经结束用户A购买失败。
由上述流程可以看出,二阶段提交的算法思路可以概括为:协调者向参与者下发请求事务操作,参与者接收到请求后,进行相关操作并将操作结果通知协调者,协调者根据所有参与者的反馈结果决定各参与者是要提交操作还是撤销操作。
虽然基于XA的二阶段提交算法尽量保证了数据的强一致性而且实现成本低但依然有些不足。主要有以下三个问题
同步阻塞问题二阶段提交算法在执行过程中所有参与节点都是事务阻塞型的。也就是说当本地资源管理器占有临界资源时其他资源管理器如果要访问同一临界资源会处于阻塞状态。因此基于XA的二阶段提交协议不支持高并发场景。
单点故障问题:该算法类似于集中式算法,一旦事务管理器发生故障,整个系统都处于停滞状态。尤其是在提交阶段,一旦事务管理器发生故障,资源管理器会由于等待管理器的消息,而一直锁定事务资源,导致整个系统被阻塞。
数据不一致问题在提交阶段当协调者向所有参与者发送“DoCommit”请求时如果发生了局部网络异常或者在发送提交请求的过程中协调者发生了故障就会导致只有一部分参与者接收到了提交请求并执行提交操作但其他未接到提交请求的那部分参与者则无法执行事务提交。于是整个分布式系统便出现了数据不一致的问题。
三阶段提交方法
三阶段提交协议Three-phase Commit Protocol3PC是对二阶段提交2PC的改进。为了更好地处理两阶段提交的同步阻塞和数据不一致问题三阶段提交引入了超时机制和准备阶段。
与2PC只是在协调者引入超时机制不同3PC同时在协调者和参与者中引入了超时机制。如果协调者或参与者在规定的时间内没有接收到来自其他节点的响应就会根据当前的状态选择提交或者终止整个事务从而减少了整个集群的阻塞时间在一定程度上减少或减弱了2PC中出现的同步阻塞问题。
在第一阶段和第二阶段中间引入了一个准备阶段或者说把2PC的投票阶段一分为二也就是在提交阶段之前加入了一个预提交阶段。在预提交阶段尽可能排除一些不一致的情况保证在最后提交之前各参与节点的状态是一致的。
三阶段提交协议就有CanCommit、PreCommit、DoCommit三个阶段下面我们来看一下这个三个阶段。
第一CanCommit阶段。
协调者向参与者发送请求操作CanCommit请求询问参与者是否可以执行事务提交操作然后等待参与者的响应参与者收到CanCommit请求之后回复Yes表示可以顺利执行事务否则回复No。
3PC的CanCommit阶段与2PC的Voting阶段相比
类似之处在于协调者均需要向参与者发送请求操作CanCommit请求询问参与者是否可以执行事务提交操作然后等待参与者的响应。参与者收到CanCommit请求之后回复Yes表示可以顺利执行事务否则回复No。
不同之处在于在2PC中在投票阶段若参与者可以执行事务会将操作信息记录到事务日志中但不提交并返回结果给协调者。但在3PC中在CanCommit阶段参与者仅会判断是否可以顺利执行事务并返回结果。而操作信息记录到事务日志但不提交的操作由第二阶段预提交阶段执行。
CanCommit阶段不同节点之间的事务请求成功和失败的流程如下所示。
当协调者接收到所有参与者回复的消息后进入预提交阶段PreCommit阶段
第二PreCommit阶段。
协调者根据参与者的回复情况来决定是否可以进行PreCommit操作预提交阶段
如果所有参与者回复的都是“Yes”那么协调者就会执行事务的预执行
协调者向参与者发送PreCommit请求进入预提交阶段。
参与者接收到PreCommit请求后执行事务操作并将Undo和Redo信息记录到事务日志中。
如果参与者成功执行了事务操作则返回ACK响应同时开始等待最终指令。
假如任何一个参与者向协调者发送了“No”消息或者等待超时之后协调者都没有收到参与者的响应就执行中断事务的操作
协调者向所有参与者发送“Abort”消息。
参与者收到“Abort”消息之后或超时后仍未收到协调者的消息执行事务的中断操作。
预提交阶段,不同节点上事务执行成功和失败的流程,如下所示。
预提交阶段保证了在最后提交阶段DoCmmit阶段之前所有参与者的状态是一致的。
第三DoCommit阶段。
DoCmmit阶段进行真正的事务提交根据PreCommit阶段协调者发送的消息进入执行提交阶段或事务中断阶段。
执行提交阶段:
若协调者接收到所有参与者发送的Ack响应则向所有参与者发送DoCommit消息开始执行阶段。
参与者接收到DoCommit消息之后正式提交事务。完成事务提交之后释放所有锁住的资源并向协调者发送Ack响应。
协调者接收到所有参与者的Ack响应之后完成事务。
事务中断阶段:
协调者向所有参与者发送Abort请求。
参与者接收到Abort消息之后利用其在PreCommit阶段记录的Undo信息执行事务的回滚操作释放所有锁住的资源并向协调者发送Ack消息。
协调者接收到参与者反馈的Ack消息之后执行事务的中断并结束事务。
执行阶段不同节点上事务执行成功和失败(事务中断)的流程,如下所示。
3PC协议在协调者和参与者均引入了超时机制。即当参与者在预提交阶段向协调者发送 Ack消息后如果长时间没有得到协调者的响应在默认情况下参与者会自动将超时的事务进行提交从而减少整个集群的阻塞时间在一定程度上减少或减弱了2PC中出现的同步阻塞问题。
但三阶段提交仍然存在数据不一致的情况比如在PreCommit阶段部分参与者已经接受到ACK消息进入执行阶段但部分参与者与协调者网络不通导致接收不到ACK消息此时接收到ACK消息的参与者会执行任务未接收到ACK消息且网络不通的参与者无法执行任务最终导致数据不一致。
基于分布式消息的最终一致性方案
2PC和3PC核心思想均是以集中式的方式实现分布式事务这两种方法都存在两个共同的缺点一是同步执行性能差二是数据不一致问题。为了解决这两个问题通过分布式消息来确保事务最终一致性的方案便出现了。
在eBay的分布式系统架构中架构师解决一致性问题的核心思想就是将需要分布式处理的事务通过消息或者日志的方式异步执行消息或日志可以存到本地文件、数据库或消息队列中再通过业务规则进行失败重试。这个案例就是使用基于分布式消息的最终一致性方案解决了分布式事务的问题。
基于分布式消息的最终一致性方案的事务处理引入了一个消息中间件在本案例中我们采用Message QueueMQ消息队列用于在多个应用之间进行消息传递。实际使用中阿里就是采用RocketMQ 机制来支持消息事务。
基于消息中间件协商多个节点分布式事务执行操作的示意图,如下所示。
仍然以网上购物为例。假设用户A在某电商平台下了一个订单需要支付50元发现自己的账户余额共150元就使用余额支付支付成功之后订单状态修改为支付成功然后通知仓库发货。
在该事件中,涉及到了订单系统、支付系统、仓库系统,这三个系统是相互独立的应用,通过远程服务进行调用。
根据基于分布式消息的最终一致性方案用户A通过终端手机首先在订单系统上操作通过消息队列完成整个购物流程。然后整个购物的流程如下所示。
订单系统把订单消息发给消息中间件,消息状态标记为“待确认”。
消息中间件收到消息后,进行消息持久化操作,即在消息存储系统中新增一条状态为“待发送”的消息。
消息中间件返回消息持久化结果(成功/失败),订单系统根据返回结果判断如何进行业务操作。失败,放弃订单,结束(必要时向上层返回失败结果);成功,则创建订单。
订单操作完成后,把操作结果(成功/失败)发送给消息中间件。
消息中间件收到业务操作结果后,根据结果进行处理:失败,删除消息存储中的消息,结束;成功,则更新消息存储中的消息状态为“待发送(可发送)”,并执行消息投递。
如果消息状态为“可发送”则MQ会将消息发送给支付系统表示已经创建好订单需要对订单进行支付。支付系统也按照上述方式进行订单支付操作。
订单系统支付完成后会将支付消息返回给消息中间件中间件将消息传送给订单系统。若支付失败则订单操作失败订单系统回滚到上一个状态MQ中相关消息将被删除若支付成功则订单系统再调用库存系统进行出货操作操作流程与支付系统类似。
在上述过程中,可能会产生如下异常情况,其对应的解决方案为:
订单消息未成功存储到MQ中则订单系统不执行任何操作数据保持一致
MQ成功将消息发送给支付系统或仓库系统但是支付系统或仓库系统操作成功的ACK消息回传失败由于通信方面的原因导致订单系统与支付系统或仓库系统数据不一致此时MQ会确认各系统的操作结果删除相关消息支付系统或仓库系统操作回滚使得各系统数据保持一致
MQ成功将消息发送给支付系统或仓库系统但是支付系统或仓库系统操作成功的ACK消息回传成功订单系统操作后的最终结果成功或失败未能成功发送给MQ此时各系统数据可能不一致MQ也需确认各系统的操作结果若数据一致则更新消息若不一致则回滚操作、删除消息。
基于分布式消息的最终一致性方案采用消息传递机制,并使用异步通信的方式,避免了通信阻塞,从而增加系统的吞吐量。同时,这种方案还可以屏蔽不同系统的协议规范,使其可以直接交互。
在不需要请求立即返回结果的场景下, 这些特性就带来了明显的通信优势,并且通过引入消息中间件,实现了消息生成方(如上述的订单系统)本地事务和消息发送的原子性,采用最终一致性的方式,只需保证数据最终一致即可,一定程度上解决了二阶段和三阶段方法要保证强一致性而在某些情况导致的数据不一致问题。
可以看出,分布式事务中,当且仅当所有的事务均成功时整个流程才成功。所以,分布式事务的一致性是实现分布式事务的关键问题,目前来看还没有一种很简单、完美的方案可以应对所有场景。
三种实现方式对比
现在,为了方便你理解并记忆这三种方法,我总结了一张表格,从算法一致性、执行方式、性能等角度进行了对比:
知识扩展:刚性事务与柔性事务
在讨论事务的时候,我们经常会提到刚性事务与柔性事务,但却很难区分这两种事务。所以,今天的知识扩展内容,我就来和你说说什么是刚性事务、柔性事务,以及两者之间有何区别?
刚性事务遵循ACID原则具有强一致性。比如数据库事务。
柔性事务,其实就是根据不同的业务场景使用不同的方法实现最终一致性,也就是说我们可以根据业务的特性做部分取舍,容忍一定时间内的数据不一致。
总结来讲与刚性事务不同柔性事务允许一定时间内数据不一致但要求最终一致。而柔性事务的最终一致性遵循的是BASE理论。
什么是BASE理论呢
eBay 公司的工程师 Dan Pritchett曾提出了一种分布式存储系统的设计模式——BASE理论。 BASE理论包括基本可用Basically Available、柔性状态Soft State和最终一致性Eventual Consistency
基本可用分布式系统出现故障的时候允许损失一部分功能的可用性保证核心功能可用。比如某些电商618大促的时候会对一些非核心链路的功能进行降级处理。
柔性状态:在柔性事务中,允许系统存在中间状态,且这个中间状态不会影响系统整体可用性。比如,数据库读写分离,写库同步到读库(主库同步到从库)会有一个延时,其实就是一种柔性状态。
最终一致性:事务在操作过程中可能会由于同步延迟等问题导致不一致,但最终状态下,所有数据都是一致的。
BASE理论为了支持大型分布式系统通过牺牲强一致性保证最终一致性来获得高可用性是对ACID原则的弱化。ACID 与 BASE 是对一致性和可用性的权衡所产生的不同结果但二者都保证了数据的持久性。ACID 选择了强一致性而放弃了系统的可用性。与ACID原则不同的是BASE理论保证了系统的可用性允许数据在一段时间内可以不一致最终达到一致状态即可也即牺牲了部分的数据一致性选择了最终一致性。
具体到今天的三种分布式事务实现方式二阶段提交、三阶段提交方法遵循的是ACID原则而消息最终一致性方案遵循的就是BASE理论。
总结
我从事务的ACID特性出发介绍了分布式事务的概念、特征以及如何实现分布式事务。在关于如何实现分布式的部分我以网购为例与你介绍了常见的三种实现方式即基于XA协议的二阶段提交方法三阶段方法以及基于分布式消息的最终一致性方法。
二阶段和三阶段方法是维护强一致性的算法它们针对刚性事务实现的是事务的ACID特性。而基于分布式消息的最终一致性方案更适用于大规模分布式系统它维护的是事务的最终一致性遵循的是BASE理论因此适用于柔性事务。
在分布式系统的设计与实现中,分布式事务是不可或缺的一部分。可以说,没有实现分布式事务的分布式系统,不是一个完整的分布式系统。分布式事务的实现过程看似复杂,但将方法分解剖析后,你就会发现分布式事务的实现是有章可循的。
我将实现分布式事务常用的三个算法整理为了一张思维导图,以帮助你加深理解与记忆。
思考题
你觉得分布式互斥与分布式事务之间的关系是什么呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,210 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
07 分布式锁:关键重地,非请勿入
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
我在第3篇文章中与你一起学习了分布式互斥领悟了其“有你没我有我没你”的精髓为你解释了同一临界资源同一时刻只能被一个程序访问的问题并介绍了解决分布式互斥的算法。
不知道你有没有发现一个细节,在之前介绍的算法中,我主要讲了如何协调多个进程获取权限和根据权限有序访问共享资源,“获得访问权限的进程可以访问共享资源,其他进程必须等待拥有该权限的进程释放权限”。但是,我并没有介绍在访问共享资源时,这个权限是如何设置或产生的,以及设置或产生这个权限的工作原理是什么。
那么,在本讲,我就将带你一起打卡分布式锁,去学习分布式锁是如何解决这个问题的。
为什么要使用分布锁?
首先,我先带你认识一下什么是锁。
在单机系统中,经常会有多个线程访问同一种资源的情况,我们把这样的资源叫做共享资源,或者叫做临界资源。为了维护线程操作的有效性和正确性,我们需要某种机制来减少低效率的操作,避免同时对相同数据进行不一样的操作,维护数据的一致性,防止数据丢失。也就是说,我们需要一种互斥机制,按照某种规则对多个线程进行排队,依次、互不干扰地访问共享资源。
这个机制指的是,为了实现分布式互斥,在某个地方做个标记,这个标记每个线程都能看到,到标记不存在时可以设置该标记,当标记被设置后,其他线程只能等待拥有该标记的线程执行完成,并释放该标记后,才能去设置该标记和访问共享资源。这里的标记,就是我们常说的锁。
也就是说,锁是多线程同时访问同一资源的场景下,为了让线程互不干扰地访问共享资源,从而保证操作的有效性和正确性的一种标记。
与普通锁不同的是分布式锁是指分布式环境下系统部署在多个机器中实现多进程分布式互斥的一种锁。为了保证多个进程能看到锁锁被存在公共存储比如Redis、Memcached、数据库等三方存储中以实现多个进程并发访问同一个临界资源同一时刻只有一个进程可访问共享资源确保数据的一致性。
那什么场景下需要使用分布式锁呢?
比如现在某电商要售卖某大牌吹风机以下简称“吹风机”库存只有2个但有5个来自不同地区的用户{A,B,C,D,E}几乎同时下单那么这2个吹风机到底会花落谁家呢
你可能会想,这还不简单,谁先提交订单请求,谁就购买成功呗。但实际业务中,为了高并发地接收大量用户订单请求,很少有电商网站真正实施这么简单的措施。
此外,对于订单的优先级,不同电商往往采取不同的策略,比如有些电商根据下单时间判断谁可以购买成功,而有些电商则是根据付款时间来判断。但,无论采用什么样的规则去判断谁能购买成功,都必须要保证吹风机售出时,数据库中更新的库存是正确的。为了便于理解,我在下面的讲述中,以下单时间作为购买成功的判断依据。
我们能想到的最简单方案就是,给吹风机的库存数加一个锁。当有一个用户提交订单后,后台服务器给库存数加一个锁,根据该用户的订单修改库存。而其他用户必须等到锁释放以后,才能重新获取库存数,继续购买。
在这里,吹风机的库存就是共享资源,不同的购买者对应着多个进程,后台服务器对共享资源加的锁就是告诉其他进程“关键重地,非请勿入”。
但问题就这样解决了吗?当然没这么简单。
想象一下用户A想买1个吹风机用户B想买2个吹风机。在理想状态下用户A网速好先买走了1个库存还剩下1个此时应该提示用户B库存不足用户B购买失败。但实际情况是用户A和用户B同时获取到商品库存还剩2个用户A买走1个在用户A更新库存之前用户B又买走了2个此时用户B更新库存商品还剩0个。这时电商就头大了总共2个吹风机却卖出去了3个。
不难看出,如果只使用单机锁将会出现不可预知的后果。因此,在高并发场景下,为了保证临界资源同一时间只能被一个进程使用,从而确保数据的一致性,我们就需要引入分布式锁了。
此外,在大规模分布式系统中,单个机器的线程锁无法管控多个机器对同一资源的访问,这时使用分布式锁,就可以把整个集群当作一个应用一样去处理,实用性和扩展性更好。
分布式锁的三种实现方法及对比
接下来我带你看看实现分布式锁的3种主流方法
基于数据库实现分布式锁,这里的数据库指的是关系型数据库;
基于缓存实现分布式锁;
基于ZooKeeper实现分布式锁。
基于数据库实现分布式锁
实现分布式锁最直接的方式通过数据库进行实现,首先创建一张表用于记录共享资源信息,然后通过操作该表的数据来实现共享资源信息的修改。
当我们要锁住某个资源时,就在该表中增加一条记录,想要释放锁的时候就删除这条记录。数据库对共享资源做了唯一性约束,如果有多个请求被同时提交到数据库的话,数据库会保证只有一个操作可以成功,操作成功的那个线程就获得了访问共享资源的锁,可以进行操作。
基于数据库实现的分布式锁是最容易理解的。但是因为数据库需要落到硬盘上频繁读取数据库会导致IO开销大因此这种分布式锁适用于并发量低对性能要求低的场景。对于双11、双12等需求量激增的场景数据库锁是无法满足其性能要求的。而在平日的购物中我们可以在局部场景中使用数据库锁实现对资源的互斥访问。
下面我们还是以电商售卖吹风机的场景为例。吹风机库存是2个有3个来自不同地区的用户{A,B,C}想要购买其中用户A想买1个用户B想买2个用户C想买1个。
用户A和用户B几乎同时下单但用户A的下单请求最先到达服务器。因此该商家的产品数据库中增加了一条关于用户A的记录用户A获得了锁他的订单请求被处理服务器修改吹风机库存数减去1后还剩下1个。
当用户A的订单请求处理完成后有关用户A的记录被删除服务器开始处理用户B的订单请求。这时库存只有1个了无法满足用户B的订单需求因此用户B购买失败。
从数据库中删除用户B的记录服务器开始处理用户C的订单请求库存中1个吹风机满足用户C的订单需求。所以数据库中增加了一条关于用户C的记录用户C获得了锁他的订单请求被处理服务器修改吹风机数量减去1后还剩下0个。
可以看出,基于数据库实现分布式锁比较简单,绝招在于创建一张锁表,为申请者在锁表里建立一条记录,记录建立成功则获得锁,消除记录则释放锁。该方法依赖于数据库,主要有两个缺点:
单点故障问题。一旦数据库不可用,会导致整个系统崩溃。
死锁问题。数据库锁没有失效时间,未获得锁的进程只能一直等待已获得锁的进程主动释放锁。倘若已获得共享资源访问权限的进程突然挂掉、或者解锁操作失败,使得锁记录一直存在数据库中,无法被删除,而其他进程也无法获得锁,从而产生死锁现象。
基于缓存实现分布式锁
数据库的性能限制了业务的并发量那么对于双11、双12等需求量激增的场景是否有解决方法呢
基于缓存实现分布式锁的方式非常适合解决这种场景下的问题。所谓基于缓存也就是说把数据存放在计算机内存中不需要写入磁盘减少了IO读写。接下来我以Redis为例与你展开这部分内容。
Redis通常可以使用setnx(key, value)函数来实现分布式锁。key和value就是基于缓存的分布式锁的两个属性其中key表示锁idvalue = currentTime + timeOut表示当前时间+超时时间。也就是说某个进程获得key这把锁后如果在value的时间内未释放锁系统就会主动释放锁。
setnx函数的返回值有0和1
返回1说明该服务器获得锁setnx将key对应的value设置为当前时间 + 锁的有效时间。
返回0说明其他服务器已经获得了锁进程不能进入临界区。该服务器可以不断尝试setnx操作以获得锁。
我还是以电商售卖吹风机的场景为例,和你说明基于缓存实现的分布式锁,假设现在库存数量是足够的。
用户A的请求因为网速快最先到达Server2setnx操作返回1并获取到购买吹风机的锁用户B和用户C的请求几乎同时到达了Server1和Server3但因为这时Server2获取到了吹风机数据的锁所以只能加入等待队列。
Server2获取到锁后负责管理吹风机的服务器执行业务逻辑只用了1s就完成了订单。订单请求完成后删除锁的key从而释放锁。此时排在第二顺位的Server1获得了锁可以访问吹风机的数据资源。但不巧的是Server1在完成订单后发生了故障无法主动释放锁。
于是排在第三顺位的Server3只能等设定的有效时间比如30分钟到期锁自动释放后才能访问吹风机的数据资源也就是说用户C只能到00:30:01以后才能继续抢购。
总结来说Redis通过队列来维持进程访问共享资源的先后顺序。Redis锁主要基于setnx函数实现分布式锁当进程通过setnx函数返回1时表示已经获得锁。排在后面的进程只能等待前面的进程主动释放锁或者等到时间超时才能获得锁。
相对于基于数据库实现分布式锁的方案来说,基于缓存实现的分布式锁的优势表现在以下几个方面:
性能更好。数据被存放在内存而不是磁盘避免了频繁的IO操作。
很多缓存可以跨集群部署,避免了单点故障问题。
使用方便。很多缓存服务都提供了可以用来实现分布式锁的方法比如Redis的setnx和delete方法等。
可以直接设置超时时间例如expire key timeout来控制锁的释放因为这些缓存服务器一般支持自动删除过期数据。
这个方案的不足是,通过超时时间来控制锁的失效时间,并不是十分靠谱,因为一个进程执行时间可能比较长,或受系统进程做内存回收等影响,导致时间超时,从而不正确地释放了锁。
为了解决基于缓存实现的分布式锁的这些问题我们再来看看基于ZooKeeper实现的分布式锁吧。
基于ZooKeeper实现分布式锁
ZooKeeper基于树形数据存储结构实现分布式锁来解决多个进程同时访问同一临界资源时数据的一致性问题。ZooKeeper的树形数据存储结构主要由4种节点构成
持久节点PERSISTENT。这是默认的节点类型一直存在于ZooKeeper中。
持久顺序节点PERSISTENT_SEQUENTIAL。在创建节点时ZooKeeper根据节点创建的时间顺序对节点进行编号命名。
临时节点EPHEMERAL。当客户端与Zookeeper连接时临时创建的节点。与持久节点不同当客户端与ZooKeeper断开连接后该进程创建的临时节点就会被删除。
临时顺序节点EPHEMERAL_SEQUENTIAL。就是按时间顺序编号的临时节点。
根据它们的特征ZooKeeper基于临时顺序节点实现了分布锁。
还是以电商售卖吹风机的场景为例。假设用户A、B、C同时在11月11日的零点整提交了购买吹风机的请求ZooKeeper会采用如下方法来实现分布式锁
在与该方法对应的持久节点shared_lock的目录下为每个进程创建一个临时顺序节点。如下图所示吹风机就是一个拥有shared_lock的目录当有人买吹风机时会为他创建一个临时顺序节点。
每个进程获取shared_lock目录下的所有临时节点列表注册Watcher用于监听子节点变更的信息。当监听到自己的临时节点是顺序最小的则可以使用共享资源。
每个节点确定自己的编号是否是shared_lock下所有子节点中最小的若最小则获得锁。例如用户A的订单最先到服务器因此创建了编号为1的临时顺序节点LockNode1。该节点的编号是持久节点目录下最小的因此获取到分布式锁可以访问临界资源从而可以购买吹风机。
若本进程对应的临时节点编号不是最小的,则分为两种情况:
本进程为读请求,如果比自己序号小的节点中有写请求,则等待;
本进程为写请求,如果比自己序号小的节点中有请求,则等待。
例如用户B也想要买吹风机但在他之前用户C想看看吹风机的库存量。因此用户B只能等用户A买完吹风机、用户C查询完库存量后才能购买吹风机。
可以看到使用ZooKeeper实现的分布式锁可以解决前两种方法提到的各种问题比如单点故障、不可重入、死锁等问题。但该方法实现较复杂且需要频繁地添加和删除节点所以性能不如基于缓存实现的分布式锁。
三种实现方式对比
我通过一张表格来对比一下这三种方式的特点,以方便你理解、记忆。
值得注意的是,这里的实现复杂性,是针对同样的分布式锁的实现复杂性,与之前提到的基于数据库的实现非常简易不一样。
基于数据库实现的分布式锁存在单点故障和死锁问题仅仅利用数据库技术去解决单点故障和死锁问题是非常复杂的。而ZooKeeper已定义相关的功能组件因此可以很轻易地解决设计分布式锁时遇到的各种问题。所以说要实现一个完整的、无任何缺陷的分布式锁ZooKeeper是一个最简单的选择。
总结来说ZooKeeper分布式锁的可靠性最高有封装好的框架很容易实现分布式锁的功能并且几乎解决了数据库锁和缓存式锁的不足因此是实现分布式锁的首选方法。
从上述分析可以看出,为了确保分布式锁的可用性,我们在设计时应考虑到以下几点:
互斥性,即在分布式系统环境下,对于某一共享资源,需要保证在同一时间只能一个线程或进程对该资源进行操作。
具备锁失效机制,防止死锁。即使出现进程在持有锁的期间崩溃或者解锁失败的情况,也能被动解锁,保证后续其他进程可以获得锁。
可重入性,即进程未释放锁时,可以多次访问临界资源。
有高可用的获取锁和释放锁的功能,且性能要好。
知识扩展:如何解决分布式锁的羊群效应问题?
在分布式锁问题中,会经常遇到羊群效应。
所谓羊群效应就是在整个ZooKeeper分布式锁的竞争过程中大量的进程都想要获得锁去使用共享资源。每个进程都有自己的“Watcher”来通知节点消息都会获取整个子节点列表使得信息冗余资源浪费。
当共享资源被解锁后Zookeeper会通知所有监听的进程这些进程都会尝试争取锁但最终只有一个进程获得锁使得其他进程产生了大量的不必要的请求造成了巨大的通信开销很有可能导致网络阻塞、系统性能下降。
那如何解决这个问题呢?具体方法可以分为以下三步。
在与该方法对应的持久节点的目录下,为每个进程创建一个临时顺序节点。
每个进程获取所有临时节点列表,对比自己的编号是否最小,若最小,则获得锁。
若本进程对应的临时节点编号不是最小的则注册Watcher监听自己的上一个临时顺序节点当监听到该节点释放锁后获取锁。
总结
我以电商购物为例首先带你剖析了什么是分布式锁以及为什么需要分布式锁然后与你介绍了三种实现分布式锁的方法包括基于数据库实现、基于缓存实现以Redis为例以及基于ZooKeeper实现。
分布式锁是解决多个进程同时访问临界资源的常用方法在分布式系统中非常常见比如开源的ZooKeeper、Redis中就有所涉及。通过今天这篇文章对分布式锁原理及方法的讲解我相信你会发现分布式锁不再那么神秘、难懂然后以此为基础对分布式锁进行更深入的学习和应用。
接下来,我把今天的内容通过下面的一张思维导图再全面总结下。
思考题
分布式锁与分布式互斥的关系是什么呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,165 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
08 分布式技术是如何引爆人工智能的?
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
通过前面课程,相信你已经对分布式的起源以及什么是分布式有了一定的了解,从用户留言来看很多同学对分布式技术、分布式技术的应用,以及分布式技术的重要性非常感兴趣。所以,我将以人工智能技术为例,带你了解分布式技术的应用及其重要性。
什么是人工智能?
2016年3月Google AlphaGo以4:1的比分赢得了世界围棋冠军李世石。这场围棋人机大战将人工智能技术推向了高潮。现在人工智能已经广泛渗透到了我们的生活中比如手机拍照美化、人脸识别、平安城市、自然语言处理、语音识别等。
那么,到底什么是人工智能呢?
所谓人工智能,其实就是希望机器能够模拟人的思维,像人一样智能。目前,对人工智能的定义大多可划分为四类,即机器“像人一样思考”“像人一样行动”“理性地思考”和“理性地行动”。这里的行动,指的是采取行动或制定行动的决策。
那人工智能是如何让机器像人那样智能呢?人并不是天生就会解决问题的,我们经常会听到一句经典的话“见多识广”,人遇到新的问题,是通过学习新知识,然后结合自己的经验去解决的。比如,人并不是生来就认识香蕉,而是通过后天的学习(包括学习香蕉的形状、颜色、口味等)来获取识别香蕉的经验,当下次再看到香蕉时,就知道这是香蕉了。
人工智能要模拟人的智能也类似,需要通过大量的数据进行学习和分析获得规律(即建立一个模型),然后利用该规律或模型对未知数据进行预测,以判断是否与建模数据具有相同特征。
从人工智能的定义可以看出,数据、模型(也叫作算法)、算力是人工智能的三大核心。在一定程度上可以说,数据决定了机器学习能达到的上限,模型提供了方法。因此数据处理和模型训练是人工智能的关键技术,算力决定了数据处理和模型训练的实用性能,而分布式技术就是解决算力的不二妙招。
接下来,我就对数据处理和模型训练进行具体分析,来帮助你了解人工智能中需要用到哪些分布式技术来解决算力问题。
数据处理
数据处理又称数据预处理,是指通过数据统计、数据集成、数据清理、数据规约、数据变换等方法,对数据缺失、数据噪声、数据冗余、多数据源等问题进行处理以得到高质量数据,为模型训练提供高质量输入,是人工智能不可缺少的环节。
其实,数据处理类似于我们的知识整理过程。一个精心打造的、体系化梳理过的专栏文章,可以帮助我们在学习一门课程时,少走弯路、避免踩雷、达到事半功倍的效果。同样地,一个精心处理过的数据集,对于人工智能的模型训练也能起到事半功倍的效果,一方面可以缩短机器学习的周期,另一方面也可以提高机器学习的质量。
所以接下来,我们就一起看看数据预处理的方法吧。
数据统计Data Statistics。数据统计是数据预处理的第一步其范围、规模、方式等会直接影响数据分析的结果。常见的统计特征有最大值、最小值、均值、中位数、方差、标准差等。
数据集成Data Integration。数据的收集有多种途径比如文件数据、数据库数据、问卷数据等而不同的数据源其数据的存储方式、命名规则、单位等不尽相同所以我们需要数据集成来将多个数据源的数据整合到一起以保证数据结构、属性的一致性并去除冗余数据方便后续分析。
数据清理Data Cleaning。由于用户忘记或设备损坏经常会造成部分数据缺失由于仪器故障或用户填写错误经常会出现数据错误噪声数据等。如果不对这些数据做任何处理后面的模型训练过程将产生严重偏差。数据清理过程就是用来解决这个问题的它可以通过平均值或众数等来填充丢失值或修改这些噪声值。
数据规约Data Reduction。由于机器学习中的数据量很大因此会导致很多重复的特征或者很多不重要的特征比如ID号等。数据规约通常指通过主成分分析法 (Principal Component AnalysisPCA)、小波变换(Wavelet TransformWT)等方法去除重复特征及不重要的特征,从而减少数据的维度或者数据量,降低问题复杂度,同时不影响后面训练的结果。
数据变换Data Conversion。数据变化是指通过标准化、离散化和分层化等方法对数据进行集成、清理、规约等操作使得数据更加一致、更加容易被模型处理。数据变换方法主要有数据标准化、数据离散化和数据泛化三类。
可以看出,数据预处理虽然很复杂,但可以拆分成多个步骤进行。对于小样本数据处理时,单台机器的处理能力就足够了,所以采用单台机器进行处理即可。但是对于大规模数据来说,单台机器的处理能力已成为瓶颈,此时,不得不需要分布式数据处理了。
目前业界已经有很多大数据处理软件比如分布式计算框架MapReduce、Spark分布式存储框架HDFS、HBase等来进行分布式数据处理。
备注:我会在专栏的“第三站:分布式计算技术”和“第五站:分布式数据存储”与你详细讲述这些框架。
接下来,我们再一起看看分布式如何助力模型训练。
分布式模型训练
在了解什么是分布式模型训练之前,我们先看一下什么是模型训练。
什么是分布式模型训练?
模型训练就是从已知数据中找到规律。具体来说就是,不断通过已有数据进行学习寻找规律,并进行验证增强,最终给出最适合的模型参数,并根据该模型参数对给定的未知数据进行预测。
比如有一堆橘子和西瓜,可以通过模型训练得到:大的、绿色的判定为西瓜,小的、黄色的判定为橘子。那么当给出一个未知数据时,我们通过它的大小及颜色信息就可以判断该水果是橘子还是西瓜。这就是模型训练。
其中大小和颜色属于预测的两个特征而它们的具体数值比如大于10厘米等颜色RGB的数值范围就是模型参数。
随着大数据时代的到来人工智能技术逐渐向大规模训练数据、大模型训练等方向发展。比如百度的Deep Speech 2系统使用了11940小时的语音数据以及超过200万句表述来训练英语的语音识别模型2011年谷歌训练出拥有十亿个参数的超大神经网络模型。很明显单台计算机的存储能力、计算能力已经不能满足了因此分布式模型训练诞生了。
研究表明,在具有 GPU加速卡的单机上采用ImageNet 数据集,完成一次训练大概需要多达一周的时间。这还仅仅只是一次训练迭代的时间,如果是比较严格的生产级业务,至少需要数十次迭代,训练累计时间将会达到数十周。试想一下,如果一个业务仅仅是模型训练就花费数十周,那么等到真正上线,恐怕最佳时间窗口也已经过去了。
在多台机器上的分布式训练无疑能极大减少训练时间近期研究中基于ImageNet数据集采用包含2048个GPU 的集群将训练时间降低到了4分钟。TensorFlow是由Google开源且在业内非常流行的机器学习计算框架它的分布式版本利用了 GPU 加速服务器的虚拟化集群,将深度学习的训练时间从数周缩短到数小时。
总结来讲分布式训练可以大大提升训练效率大幅缩短训练时间从而缩短业务面市周期所以各大公司都在研究分布式训练比如华为、IBM、阿里巴巴等。
那,什么是分布式模型训练呢?
分布式模型训练是利用分布式集群,将多个计算机的存储能力、计算能力等进行统一管理和调度,从而实现模型训练。
可以看到,分布式模型训练的前提是有一个分布式集群,因此一个高效、可靠的分布式集群是基础。而这个分布式集群的架构、选主、调度、可靠性等关键技术,奠定了分布式模型训练的基础。
备注:关于分布式集群的架构和调度,我会在本专栏的“第二站:分布式资源管理与负载调度”进行详细讲解;关于集群的选主,我已经在本专栏的“第一站:分布式协调与同步”中进行了讲解;而关于可靠性,我会在在本专栏的“第六站:分布式高可靠”进行讲解。
好了,有了分布式集群作为基础,接下来,我们要考虑的就是如何进行分布式模型训练了。不同的场景,采用的分布式模型训练的方法也不一样,主要包括数据分布式训练、模型分布式训练和混合模型训练三类。
接下来,我将带你了解这三种分布式模型训练模式,并带你了解其中涉及的分布式技术。
数据分布式训练
数据分布式训练主要是针对大规模训练数据的场景。如下图所示,数据分布式训练是在每个节点(假设,一台服务器代表一个节点)上都存储或运行一个完整的模型训练程序的基础上,将大规模数据进行划分,然后将划分后的数据子集分配到多个节点上,每个节点根据自己接收到的数据进行训练。
每个节点会根据自己拥有的数据子集训练出一个子模型,并按照一定的规则与其他节点通信,比如各节点向其他节点传递本节点的子模型参数或参数更新等信息,以有效整合来自各个节点的训练结果,来得到全局的机器学习模型。比如,每个节点训练一个子模型得到自己的参数,最终的模型为多个节点的参数取平均值。
可以看出,数据分布式有如下两个重要信息:
数据需拆分存储到不同的节点进行训练,因此涉及了数据的拆分方法、数据的分布式存储和管理,其中数据拆分方法主要有两类,对训练样本进行划分和对每个样本的维度进行划分,这是非常基础的方法。目前,市面上大部分的书籍均有介绍,如果你感兴趣的话可以自行学习。
节点之间需要通信交互信息。分布式通信是实现任何分布式技术的底座,没有分布式通信技术,分布式模型训练犹如纸上谈兵。
备注:数据的分布式存储和管理,是数据分布式的基础,我会在“第五站:分布式数据存储”中与你详细讲述;而关于分布式通信的相关技术,我会在“第四站:分布式通信技术”与你介绍。
模型分布式训练
了解了数据分布式训练,我们再来看一下模型分布式训练。它针对的主要是大模型训练场景,在分布式领域中也被称为任务并行或任务分布式。
如下图所示模型分布式训练是指将大模型进行拆分然后将拆分后的子模型分配到不同的节点上进行训练。与数据分布式训练不同的是首先每个节点上只存储和运行部分模型训练程序而不是完整的模型训练程序其次各个子模型之间存在较强的依赖关系比如节点1的输出是节点2和节点3子模型的输入因此节点之间需要进行中间计算结果的通信。
可以看出,模型分布式训练包含如下两个关键信息:
大模型拆分为多个小模型其本质是将大任务拆分为多个子任务这其实就是分而治之策略。而子任务之间的拆分需要运用包括流水线、MapReduce等在内的多种分布式计算模式。
不同节点上的子任务之间,需要通过通信交互中间计算结果,涉及分布式通信技术。
备注:任务拆分和流水线等分布式计算模式是模型分布式训练不可缺少的技术,我会在“第三站:分布式计算技术”与你详细介绍;而关于分布式通信的相关技术,你可以移步“第四站:分布式通信技术”中进行了解。
混合模型训练
混合模型训练,主要是针对大规模训练数据和大模型训练共存的场景。
所谓混合模型训练就是将数据分布式训练和模型分布式训练结合起来。如下图所示假设有一个多GPU集群系统首先对模型进行拆分将子模型分配到单节点上不同的GPU然后对数据进行划分每个节点负责训练一部分数据最后进行模型参数同步得到全局参数和全局模型。
从混合模型训练的流程可以看出:
单节点或多节点实现模型并行或模型分布式训练,涉及模型拆分、并行与分布式计算模式等;
多节点之间实现了数据分布式训练,涉及数据的拆分方法和数据的分布式存储和管理等技术;
单节点之间的模型分布式训练,需要单节点上多进程之间通信;多节点之间的分布式训练需要跨节点跨进程通信。
备注:我会在“第三站:分布式计算技术”“第四站:分布式通信技术”和“第五站:分布式数据存储”模块中,与你讲述这其中涉及的分布式技术。
总结
讲完分布式模型训练,这节课就告一段落了。分布式训练可以将深度学习的训练时间从数周缩短到数小时,极大提升了训练效率,这充分说明了分布式技术的重要性。接下来,我们一起总结下今天的主要内容吧。
首先,我与你介绍了什么是人工智能,让你先对其有了一个整体的理解。
然后,我与你介绍了人工智能中的数据预处理方法,包括数据清理、数据统计、数据集成、数据规约、数据变换等,在这其中分布式数据预处理是处理大规模数据的一个很好的方式。
最后我与你介绍了分布式模型训练包括数据分布式训练、模型分布式训练和混合模型训练3种方法并介绍了其中涉及的关键分布式技术比如数据的分布式存储和管理、分布式通信等。没有这些关键的分布式技术分布式模型训练其实就是空谈了。
现在,我将人工智能中涉及的关键分布式技术整理为了一张表格,以方便你学习。
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,192 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
09 分布式体系结构之集中式结构:一人在上,万人在下
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
云这个话题对我们来说已经非常熟悉了。可以说,云在我们的生活中无处不在,比如我们平时看的视频通常就是放在云上的。当我们要播放一段视频时,请求会先转发到云上,从云上下载数据到本地,然后播放。在这里,你肯定会疑惑,云上资源那么丰富吗,可以存放这么多东西吗?
云上的资源确实丰富,因为它可以尽可能地把更多的服务器组织起来,作为一个统一的资源,为多个用户提供服务。这里的重点是,把多个服务器管理起来,作为一个统一的资源提供服务。而如何组织,就是分布式体系结构的范畴了。
你会发现很多场景下我们的请求都会汇总到一台服务器上由这台服务器统一协调我们的请求和其他服务器之间的关系。这种由一台服务器统一管理其他服务器的方式就是分布式体系结构中的集中式结构也称为Master/Slave架构其中统一管理其他服务器的服务器是主其他服务器是从可以形象地比喻为“一人在上万人在下”。
接下来,我就带你一起打卡分布式体系结构中的集中式结构吧。
什么是集中式结构?
集中式结构就是,由一台或多台服务器组成中央服务器,系统内的所有数据都存储在中央服务器中,系统内所有的业务也均先由中央服务器处理。多个节点服务器与中央服务器连接,并将自己的信息汇报给中央服务器,由中央服务器统一进行资源和任务调度:中央服务器根据这些信息,将任务下达给节点服务器;节点服务器执行任务,并将结果反馈给中央服务器。
集中式结构最大的特点,就是部署结构简单。这是因为,集中式系统的中央服务器往往是多个具有较强计算能力和存储能力的计算机,为此中央服务器进行统一管理和调度任务时,无需考虑对任务的多节点部署,而节点服务器之间无需通信和协作,只要与中央服务器通信协作即可,具体示意图如下所示:
经典集中式结构
现在我们理解了什么是集中式结构为了加深理解接下来我以Google Borg、Kubernetes和Apache Mesos三个经典的集群管理系统为例带你深入学习集中式结构的原理。
Google Borg
Borg是Google内部使用的集群管理系统采用了典型的集中式结构负责提交、调度、开始、重启和管理Google运行在其上的所有应用。
在Borg中一个集群称为一个Cell每个Cell里面有一个Leader称为BorgMaster即为中央服务器其他服务器为节点服务器或从服务器被称为Borglet。
首先我们一起看看BorgMaster。它由两个进程组成一个是Borgmaster主进程一个是独立的scheduler进程
主进程处理客户端的RPC请求比如任务的执行状态更新或者查询等同时管理系统中所有实体的状态比如服务器、任务等并负责和Borglet通信。
scheduler进程负责任务调度通过任务对资源的需求以及当前Borglet所在服务器的资源情况进行匹配为任务寻找一个合适的节点服务器执行。我会在第11篇文章“分布式调度之单体调度物质文明、精神文明一手抓”中与你详细讲述具体的调度流程。
接下来我们一起看看Borglet。它是运行在每个节点机器的一个agent负责任务的拉起、停止、重启等并管理和收集本服务器资源将任务的状态、服务器状态等信息上报给BorgMaster。而BorgMaster会周期性地轮询每个Borglet以获取节点服务器的状态和资源信息等。
Borg的整体架构示意图如下所示
备注此图引自Borg论文。
Borg的主要用户是Google的开发者以及运行Google应用和服务的系统管理员网站可靠性工程师简称SRE。用户以Job的形式向Borg提交工作每个Job由运行一个或多个运行相同程序的Task组成。每个Job运行在一个Borg Cell中并将一组机器当作一个单元进行管理。
Borg可以运行各种各样的任务这些任务主要分为两类
第一类是长服务即长时间运行不停止的服务并且要求能够处理短暂的、延迟敏感的请求延迟要求在几微秒到几百毫秒之间。这些服务主要用于面向终端用户的服务比如Gmail、Google Docs、Web搜索以及内部的一些基础设施服务比如BigTable
第二类是批处理任务。通常需要几秒到几天的时间来完成的批处理Job这些Job对短时间的性能波动并不是非常敏感。
这些负载通常在Cell之间混合分布每个Cell随着主要租户以及时间的不同会运行各种不同的应用批处理类型的Job来了又走而许多面向终端用户的Job又期望一个能长时间使用的模式。
对于这些不同的服务要求Borg能很好地处理所有的情况。Borg主要有三大优点
开发者只需关注应用,不需要关注底层资源管理。它隐藏了资源管理以及错误处理,因此用户能集中精力开发应用。
高可靠性和可用性,支持多种应用。
支持上千级服务器的管理和运行。
Borg并不是第一个解决这些问题的系统但却是少数能在这么大规模处理这些问题的同时还能实现这样的弹性和完整性的系统之一。
Kubernetes
Kubernetes是Google开源的容器集群管理系统是Borg的一个开源版本。Kubernetes 是用于自动部署、扩展和管理容器化应用程序的开源系统。其核心是,在集群的节点上运行容器化应用,可以进行自动化容器操作,包括部署、调度和在节点间弹性伸缩等。
Kubernetes也是典型的集中式结构一个Kubernetes集群主要由Master节点和Worker节点组成以及客户端命令行工具kubectl和其他附加项。
我们先来看看Master节点。它运行在中心服务器上Master节点由API Server、Scheduler、Cluster State Store和Control Manger Server组成负责对集群进行调度管理。
API Server是所有REST命令的入口负责处理REST的操作确保它们生效并执行相关业务逻辑。
Scheduler根据容器需要的资源以及当前Worker节点所在节点服务器的资源信息自动为容器选择合适的节点服务器。
Cluster State Store集群状态存储默认采用etcdetcd是一个分布式key-value存储主要用来做共享配置和服务发现。
Control Manager负责整个集群的编排管理。它监视集群中节点的离开和加入将集群的当前状态与etcd中存储的所需状态进行核对。比方说当某个节点发生故障它会在其它节点上增加新的Pod以匹配所需的副本数。
接下来我们看看Worker节点吧。它作为真正的工作节点运行在从节点服务器包括kubelet和kube-proxy核心组件负责运行业务应用的容器。
kubelet用于通过命令行与API Server进行交互根据接收到的请求对Worker节点进行操作。也就是说通过与API Server进行通信接收Master节点根据调度策略发出的请求或命令在Worker节点上管控容器Pod并管控容器的运行状态比如重新启动出现故障的Pod等。Pod是Kubernetes的最小工作单元每个Pod包含一个或多个容器。
kube-proxy负责为容器Pod创建网络代理/负载平衡服务从API Server获取所有Server信息并根据Server信息创建代理服务这种代理服务称之为Service。Kube-proxy主要负责管理Service的访问入口即实现集群内的Pod客户端访问Service或者是集群外访问Service具有相同服务的一组Pod可抽象为一个Service。每个Service都有一个虚拟IP地址VIP和端口号供客户端访问。
Kubernetes架构示意图如下所示
备注此图引自https://www.padok.fr/en/blog/kubernetes-architecture-clusters
图中, Kube DNS负责为整个集群提供DNS服务CNI是Container Network Interface的一个标准的通用接口用于连接容器管理系统和网络插件。
与Borg不同的是Kubernetes主要是一个容器编排引擎不仅支持Docker还支持Rocket(另一种容器技术)。
Kubernetes也已经被很多公司采用比如网易云、华为在需要使用容器进行资源隔离以运行相关业务的场景下采用了大规模 Kubernetes 集群。
在容器管理方面Kubernetes有很多优势。
自动化容器的部署和复制。Kubernetes执行容器编排因此不必人工编写这些任务的脚本。
将容器组织为组弹性伸缩。Kubernetes引入Pod机制Pod代表着能够作为单一应用程序加以控制的一组容器集合。通过Pod机制Kubernetes实现了多个容器的协作能够有效避免将太多功能集中到单一容器镜像这样的错误实践中。此外软件可以向外扩展跨越多个Pods实现初步部署且相关部署可随时进行规模伸缩。
容器间负载均衡。Services用于将具备类似功能的多个Pod整合为一组可轻松进行配置以实现其可发现性、可观察性、横向扩展以及负载均衡。
易于版本控制与滚动更新。Kubernetes采取“滚动方式”实现编排且可跨越部署范围内的全部Pod。这些滚动更新可进行编排并以预定义方式配合当前可能尚不可用的Pods数量以及暂时存在的闲置Pods数量。Kubernetes利用新的应用程序镜像版本对已部署Pods进行更新并在发现当前版本存在不稳定问题时回滚至早期部署版本。
Mesos
理解了Google Borg和Kubernetes的集中式结构接下来我们再看看Apache旗下的开源分布式资源管理框架Mesos吧。它被称为是分布式系统的内核最初由加州大学伯克利分校的AMPLab开发后在Twitter得到广泛使用。
Mesos的开发受到了Borg系统的启发也是采用的典型的集中式架构。Mesos与Borg不同之处在于Borg的Master直接对接用户应用也就是说用户可以向Borg的Master直接请求任务。但Mesos不可以Mesos只负责底层资源的管理和分配并不涉及存储、 任务调度等功能因此Mesos Master对接的是Spark、Hadoop、Marathon等框架用户的任务需要提交到这些框架上。也正因为此Mesos的任务调度框架是双层结构。
在Mesos中一个集群包括Mesos Master和多个Mesos Agent。其中Mesos Master运行在中央服务器Mesos Agent运行在节点服务器上。
Mesos Master负责收集和管理所有Agent所在服务器的资源和状态并且对接Spark、Hadoop等框架将集群中服务器的资源信息告知给这些框架以便这些框架进行任务资源匹配和调度。Mesos Agent负责任务的拉起、停止、重启等并负责收集所在服务器的资源(比如CPU、内存等)信息和状态上报给Mesos Master。
Mesos Master通常采用一主两备的方式以方便故障处理和恢复。而Mesos Master的选主策略采用的就是我们在第4篇文章“分布式选举国不可一日无君”中介绍的ZAB算法。
Mesos架构示意图如下所示
备注此图引自《Mesos架构 · Mesos中文手册》
如上所述Mesos对接的是框架并且可以同时对接多个框架目前已经被很多公司使用。比如国外的Twitter、Apple、Airbnb、Uber等国内的爱奇艺、去哪儿、携程、当当等。
这些公司选择Mesos主要是因为它具有如下优势
效率。Mesos对物理资源进行了逻辑抽象在应用层而不是物理层分配资源通过容器而不是虚拟机VM分配任务。因为应用程序的调度器知道如何最有效地利用资源所以在应用层分配资源能够为每个应用程序的特殊需求做考量; 而通过容器分配任务则能更好地进行“装箱”。
可扩展性。Mesos可扩展设计的关键是两级调度架构其中Framework进行任务调度Mesos Master进行资源分配。由于Master不必知道每种类型的应用程序背后复杂的调度逻辑不必为每个任务做调度因此可以用非常轻量级的代码实现更易于扩展集群规模。
模块化。每接入一种新的框架Master无需增加新的代码并且Agent模块可以复用为此开发者可以专注于应用和框架的选择。这就使得Mesos可以支持多种框架适应不同的应用场景。
随着分布式应用程序和微服务的流行越来越多的用户正在寻找一种技术来帮助他们管理这些复杂的应用程序。而Mesos为数据中心带来的这些好处就使得越来越多的人关注Mesos及其相关项目。
分析对比
Borg、Kubernetes和Mesos采用的都是集中式结构要理解它们的实现原理就要清楚其架构。所以虽然这部分内容理解起来有难度但希望你可以深入进去探其本质这样在实际操作中就可以从用途出发选择合适的集群管理架构。
接下来我将这3种集群管理系统的特点梳理为了一张表格以方便你理解与记忆。
知识扩展Mesos是如何支持容器部署的
目前容器技术十分热门解决了服务打包发布、资源隔离的问题。我们知道Kubernetes的设计主要针对的就是容器那么Mesos又是如何支持容器部署呢
Mesos本身只负责资源管理不负责任务调度。但Mesos可以对接不同的框架Mesos+Marathon可以支持容器调度和部署。Marathon支持容器的调度将容器部署请求发给Mesos MasterMesos Master再将请求转发给Mesos AgentMesos Agent的执行器会将容器拉起。
目前Mesos+Marathon支持的容器主要包括Docker和cgroups。
总结
今天我主要与你分享了分布式系统中的集中式架构并以Borg、Kubernetes、Mesos这三款知名的集群管理系统为例与你描述了集中式架构的设计目的、框架结构以及各组件模块的功能等。
Borg是Google公司内部使用的集群管理系统既可以执行长服务也可以执行批处理任务是一个具有强大功能的、复杂的集群管理系统。
Kubernetes是Borg的简化开源版是一个正在兴起的集群管理系统。Mesos和Kubernetes都是为帮助应用程序在集群环境中运行而创建的Kubernetes更加专注于运行容器集群具有更多功能。
Mesos是非常典型的开源集群管理系统。在Mesos之上可以搭载诸如Spark、Hadoop等框架甚至可以在Mesos上集成Kubernetes扩展性强。
可以发现,这三种集群管理系统虽然具有不同的功能组件,但整体框架采用的都是集中式架构。因此,你只要理解了一个集群管理系统的架构,再去理解其他集中式的集群管理架构就会很容易了。
Kubernetes由于其成熟的社区、丰富的文档所以如果你是一个新手的话Kubernetes就是一个很棒的开始。加油赶紧开启你的集群管理之旅吧。
好了,到最后,我再以一个思维导图为你总结一下本讲的内容,以方便你理解记忆。
思考题
在集中式架构中Master如何判断Slave是否存活呢
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,192 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
10 分布式体系结构之非集中式结构:众生平等
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在上一篇文章中,我带你了解了分布式体系结构中的集中式结构。虽然很多云上的管理都采用了集中式结构,但是这种结构对中心服务器性能要求很高,而且存在单点瓶颈和单点故障问题。
为了解决这个问题,分布式领域中又出现了另一个经典的系统结构,即非集中式结构,也叫作分布式结构。那什么是非集中式结构呢,它的原理是什么样的,又有哪些集群采用了这种结构呢?
今天,我们就一起打卡非集中式结构,揭开它的神秘面纱吧。
什么是非集中式结构?
在非集中式结构中,服务的执行和数据的存储被分散到不同的服务器集群,服务器集群间通过消息传递进行通信和协调。
也就是说,在非集中式结构中,没有中央服务器和节点服务器之分,所有的服务器地位都是平等(对等)的,也就是我们常说的“众生平等”。这样一来,相比于集中式结构,非集中式结构就降低了某一个或者某一簇计算机集群的压力,在解决了单点瓶颈和单点故障问题的同时,还提升了系统的并发度,比较适合大规模集群的管理。
所以近几年来Google、 Amazon、Facebook、阿里巴巴、腾讯等互联网公司在一些业务中也相继采用了非集中式结构。
接下来我将为你介绍3种典型的非集中式架构系统包括Akka集群、Redis集群和Cassandra集群来帮助你深入理解非集中式架构。
Akka集群
在介绍Akka集群的结构之前我带你了解一下什么是Akka框架吧。
Akka是一个开发库和运行环境用于构建可扩展的、弹性的、快速响应的应用程序。Akka框架是基于Actor模型实现的Actor模型是一个封装了状态和行为的对象它接收消息并基于该消息执行计算。Actor之间通信的唯一机制就是消息传递每个Actor都有自己的MailBox。
比如在分布式系统中一个服务器或一个节点可以视为一个ActorActor与Actor之间采用mail进行通信如下图所示
可以看到Actor发送的Mail消息会存储在接收方的MailBox中。默认情况下接收方按照mail到达的先后顺序从MailBox中提取mail消息并进行相应的计算处理。
备注关于Actor模型更详细的内容我会在第17篇文章中与你讲述。
显然Actor模型采用异步消息调用机制具有非阻塞、高性能等特点可以用于处理并发问题。Akka集群充分利用了Actor模型的优势提供了一个非集中式架构的集群管理模块用来构建可扩展的、弹性的分布式应用程序。
Akka集群负责Actor模型底层的节点管理包括故障检测、节点加入/退出集群等。也就是说Akka集群为Actor模型提供了一个可容错、去中心化的节点集群管理系统来保证Actor的运行和Actor之间的通信。
如下图所示Akka集群是一个完全去中心化的分布式集群管理系统。一个集群由多个节点组成每个节点都可以进行数据处理和任务执行节点之间均可进行通信。节点有Leader节点和非Leader节点之分。与非Leader节点相比Leader节点只是增加了负责节点的加入和移除集群的功能所以并不会影响非集中式结构中节点的平等关系。
可以看到Akka集群的两个重点是数据传输和集群组建及管理所以接下来我将从这两个方面与你介绍Akka集群。
首先我们看一下数据传输。在Akka集群中节点是对等的也就是说每个节点是可以并发处理的因此必然存在数据传输和一致性的问题。
比如我们要针对数据进行操作将X=1修改为X=2。现在集群中节点1进行了修改使得X=2但其他节点上还是X=1因此节点1需要将X=2的消息告知其他节点以保证最终集群中所有节点上均为X=2。
其实这个问题就是分布式共识问题。我已经在第5篇文章“分布式共识存异求同”中与你介绍了PoW、PoS和DPoS三种达成共识的方法你可以再复习下相关内容。
Akka集群主要采用的是谁的时间戳最新也就是数据最新就以谁为准的原则。在这里我要重点与你讲述的是如何将X=2这个消息传输给集群中的每个节点。
Akka集群采用了Gossip协议该协议是最终一致性协议。它的原理是每个节点周期性地从自己维护的集群节点列表中随机选择k个节点将自己存储的数据信息发给这k个节点接收到该信息的节点采用前面讲的共识原则对收到的数据和本地数据进行合并这样迭代几个周期后集群中所有节点上的数据信息就一致了。
这就好比我们生活中的“谣言传播”一样用户A告诉用户B“商场新开了一家火锅店”用户B收到信息后再告诉用户C然后用户C再告诉用户D。这样用户A、B、C、D最终都知道了这个消息。
接下来我们看一下集群组建及管理。下图展示了Akka集群的创建过程。在创建集群时节点被分为三种类型
种子节点。使用静态配置文件方式或者系统运行时指定方式,可以生成种子节点;种子节点是普通节点加入集群的联系点,可以自动接收新加入集群的节点的信息。
首种子节点。首种子节点是配置文件中的第一个种子节点其功能是集群第一次启动时首种子节点启动起来集群才能组建成功保证集群第一次创建时只有一个集群。如下图A节点就是Akka集群的首种子节点。
普通节点。可以向种子节点或集群中的任意节点发送Join消息请求加入集群。如下图的B和C节点通过向A节点发送Join消息从而加入到Akka集群。
图片来源https://getakka.net/articles/clustering/cluster-overview.html
Akka集群的每个节点启动后读取配置文件获取种子节点列表然后开始组建集群
如果本节点为首种子节点,则把自己加入到集群列表中,即以自己为中心构建集群;
如果本节点为种子节点,则向首种子节点请求加入集群,当首种子节点回复同意消息后,可以加入集群,否则不可加入集群;
如果本节点为普通节点,则可以向任一种子节点(包括首种子节点)请求加入集群,收到同意后,则加入集群,否则不可加入集群。
加入首种子节点或种子节点的节点信息会通过Gossip协议的传播方式传播给当前已加入的所有节点以完成集群组建。当集群组建完成后就不存在种子节点与普通节点之分了每个节点均可执行Actor应用程序。
Akka集群可以构建可扩展的、弹性的分布式应用程序因此在JVM中应用了Akka框架从而实现并发编程。目前豌豆荚、蘑菇街等公司采用了Akka集群。
到这里我们小结一下吧。Akka集群是一个完全去中心化的集群管理系统当集群组建完成后每个节点均可执行Actor应用程序因此支持并发操作。但这个并发操作引入了数据同步和一致性问题所以Akka集群采用了Gossip协议进行数据同步通过谁的时间戳最新就以谁为准来解决一致性问题。
在实际业务场景中除了面向应用程序平台的分布式集群管理之外分布式数据存储也是一个非常重要的话题。在这其中分布式数据存储中的集群管理便是一个关键因素。那么接下来我就以开源数据库Redis的集群管理系统为例与你展开介绍吧。
Redis集群
Redis是一个开源的、包含多种数据结构的高性能Key-value数据库主要有以下特征
支持多种数据结构包括字符串String、散列Hash、列表List、集合Set、有序集合Sorted Set
支持数据的持久化和备份。数据可以保存在磁盘中下次直接加载使用且可以采用主从模式Master/Slave进行数据备份。
基于内存运行,具有极高的性能。
Redis的这些特征均是为数据存储进行服务的数据可分片存储在不同的Redis节点上多个Redis节点间可共享数据而提供这项能力的就是Redis 集群。
Redis 集群中不存在中央节点是典型的去中心化结构每个节点均可与其他节点通信。所有节点均可负责存储数据、记录集群的状态包括键值到正确节点的映射客户端可以访问或连接到任一节点上。Redis 集群的架构图,如下所示。
当然节点之间的数据传输仍采用了Gossip协议来保证集群中数据的最终一致性。
Redis集群中的节点用于数据存储所以在设计时需要考虑数据的可靠性和分片存储问题。
对于可靠性的问题集群中每个节点均存在主备也就是说每台服务器上都运行两个Redis服务分别为主备主故障后备升主。
而对于数据的分片存储问题Redis集群引入了“哈希槽”的这一概念。Redis 集群内置了16384个哈希槽每个节点负责一部分哈希槽。当客户端要存储一个数据或对象时Redis先对key进行CRC16校验然后进行16384取模也即HASH_SLOT = CRC16(key) mod 16384来决定哈希槽的编号从而确定存储在哪个节点上。
比如当前集群有3个节点那么:
节点 A 包含 0 到 5500号哈希槽
节点 B 包含5501 到 11000 号哈希槽;
节点 C 包含11001 到 16383号哈希槽。
Redis集群利用哈希槽实现了数据的分片存储从而将Redis的写操作分摊到了多个节点上提高了写并发能力。
到这里我们小结一下。Redis集群是一个非集中式集群管理系统没有中心节点不会因为某个节点造成性能瓶颈每个节点均支持数据存储且采用分片存储方式提高了写的并发能力。同时每个节点的设计采用主备设计提高了数据的可靠性。
鉴于这些优点Redis已被Twitter、Uber、GitHub、Instagaram等公司采用。
除了Redis外还有一个开源分布式key-value数据库系统Cassandra。接下来我就再与你分享下Cassandra集群的设计以加深你对非集中式架构的理解。
Cassandra集群
与Redis类似Cassandra也支持数据的分布式存储和操作。因此Cassandra的集群架构与数据分片存储方案与Redis集群类似。
如下图所示Cassandra集群的系统架构是基于一致性哈希的完全P2P结构没有Master的概念所有节点都是同样的角色彻底避免了因为单点问题导致的系统不稳定。Cassandra集群节点间的状态同步也是通过Gossip协议来进行P2P通信的。
集群中的每个节点都可以存储数据并接收来自客户端的请求。Cassandra集群数据存储与Redis的不同之处是Redis集群每个节点代表一部分哈希槽一个哈希槽代表一个哈希值区间而Cassandra集群中每个节点代表一个哈希值。
在Cassandra集群中每次客户端可以向集群中的任意一个节点请求数据接收到请求的节点将key值进行哈希操作找出在一致性哈希环上是哪些节点应该存储这个数据然后将请求转发到相应节点上并将查询结果反馈返回给客户端。
目前Cassandra集群因为完全去中心化的结构模式已经被Hulu、Apple、Comcast、Instagram、Spotify、eBay、Netflix等公司使用。
到这里我们小结一下吧。Cassandra采用去中心化的架构解决了集中式结构的单点故障问题同时因为数据基于哈希值分区存储提高了读写数据的并发能力。在Cassandra集群中没有Master的概念每个节点代表一个哈希值通过哈希映射的方式决定数据存储的位置。集群间的状态同步通过Gossip协议来进行P2P的通信。
对比分析
好了以上就是Akka集群、Redis集群和Cassandra集群的主要内容了。为了便于理解与记忆我将这3个集群的主要特征梳理为了一张表格如下所示-
知识扩展如何优化Gossip协议中的重复消息问题
非集中式结构的通信协议采用了Gossip协议。而Gossip是一种谣言传播协议每个节点周期性地从节点列表中选择k个节点将本节点存储的信息传播出去直到所有节点信息一致即算法收敛了。
这里有个问题如果每次都是随机选择k个节点的话势必会存在重复选择同样节点的可能增加消息量。你觉得这个问题是否可以优化又应该如何优化呢
首先这个问题肯定是可以优化的。解决方案是每个节点记录当前传输的消息且还未达到收敛的时候已经发送给了哪些节点然后每次选择时从没有发送过的节点列表中随机选择k个节点直到所有节点均被传输或集群收敛为止。这样一方面减少了重复消息量另一方面加快了收敛速度。
总结
集中式结构虽然易于理解但容易出现单点瓶颈和单点故障等问题而非集中结构才是超大规模分布式系统的首选结构。所以今天我以Akka集群、Redis集群和Cassandra集群的结构为例与你详细介绍了非集中式架构。
Akka集群是一个完全去中心化的集群管理系统节点之间都是P2P的连接模式通过Gossip协议来进行通信节点之间有角色划分负责数据存储的节点会进行存储数据。
Redis集群也是P2P的网状连接模式但是基于key-value的数据库模型每个节点都可以执行数据的计算和存储。此外Redis集群引入了哈希槽的概念来解决数据的分片存储问题。
Cassandra集群的结构是一致性哈希的P2P节点会构成一个环结构通过哈希映射来选择对应的节点。
好了,到最后,我再以一个思维导图为你总结一下这三个集群核心知识点,以方便你理解与记忆。
虽然这三种集群的节点组织结构各有不同但节点之间都是通过Gossip协议来传递信息的。因此在实现过程中集群的消息传输、节点的功能等在不同的分布式系统中都是类似的而难点主要在于集群结构的设计。
由于Akka集群、Redis集群和Cassandra集群都是典型的非集中式集群组织结构目前应用已经非常广泛了所以有很多的实现案例可供你借鉴了。对于具体集群使用配置可参考相应的官网手册讲得比较全和细。
相信你通过对今天的学习,很快就可以上手非集中式架构的集群管理应用和实践了。加油,挑战一下自己吧!
思考题
边缘计算中边缘设备的管理,你认为适合非集中式结构还是集中式结构呢,原因又是什么呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,160 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
11 分布式调度架构之单体调度:物质文明、精神文明一手抓
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在前两篇文章中,我和你分析了云资源管理的集中式架构和非集中式架构。可以看出,分布式系统架构的目的是,将多个服务器资源管理起来,寻找合适的服务器去执行用户任务。
那,什么是合适的服务器呢?衡量一个服务器是否合适会涉及很多条件或约束,比如在一些场景下,任务存在优先级,那当我们需要执行多个任务的时候,通常需要满足优先级高的任务优先执行的条件。但在这些条件中,服务器资源能够满足用户任务对资源的诉求是必须的。
而为用户任务寻找合适的服务器这个过程,在分布式领域中叫作调度。在分布式系统架构中,调度器就是一个非常重要的组件。它通常会提供多种调度策略,负责完成具体的调度工作。
当然,不同的分布式架构的调度器原理也不一样,最常见或最直观的是单体调度,就是任务和分布式系统中的空闲资源直接进行匹配调度。也就是说,调度器同时管理任务和资源,如果把资源比作“物质文明”,把任务比作“精神文明”,那么单体调度就是“物质文明和精神文明一手抓”。
接下来,我带你一起打卡分布式调度架构之单体调度。
首先,让我们先了解一下什么是单体调度。
什么是单体调度?
分布式系统中的单体调度是指,一个集群中只有一个节点运行调度进程,该节点对集群中的其他节点具有访问权限,可以对其他节点的资源信息、节点状态等进行统一管理,同时根据用户下发的任务对资源的需求,在调度器中进行任务与资源匹配,然后根据匹配结果将任务指派给合适的节点。
单体调度器拥有全局资源视图和全局任务可以很容易地实现对任务的约束并实施全局性的调度策略。目前很多集群管理系统采用了单体调度设计比如我们第9讲中提到的Google Borg、Kubernetes等。
如下图所示图中展示了一个典型的单体调度框架。Master节点上运行了调度进程负责资源管理、Tasks和资源匹配Node 1Node 2….. Node N对应着我们在第9篇文章中讲的Master/Slave架构中的Slave节点。
在单体调度框架中多个Node节点会将本节点的State例如资源信息等上报给Master节点。Master节点将Nodes State信息记录在Cluster State模块Cluster State模块用于管理集群中节点的资源等状态。Master节点中的Scheduling Logic模块用于进行Tasks与节点资源的匹配。当Master需要下发任务时Cluster State模块会将节点的资源状态传送给Scheduling Logic模块以便Scheduling Logic模块进行Tasks与资源匹配并根据匹配结果将Task发送给匹配到的节点。
单体调度设计
下图展示了单体调度的基本模型具有全局系统视角的单体调度器是“Scheduler”模块“Scheduler”模块包含多个子模块包括记录全局资源信息的“Resource State”模块、负责任务调度的“Task Scheduling”模块等。“Scheduler”主要的工作就是基于每个节点的资源信息根据制定的资源-任务匹配规则,从而将任务下发给对应的节点。
每个节点都有本地的资源管理模块“Resource Manager”上报节点资源信息并接收来自中央调度器下发的任务。
在Borg和Kubernetes这两个典型的集中式集群管理系统中Scheduler是它们的核心。而Kubernetes又是Borg的开源版本。所以接下来我就以Borg为例与你讲述它的调度器该如何设计才能保证在大规模集群上运行来自不同应用的成千上万的作业。
Borg调度设计
调度的初衷是为作业或任务寻找合适的计算资源,也就是说作业或任务是调度的对象。那么“作业”和“任务”到底是什么呢?下面,我带你先了解一下作业和任务的概念以及关系。
我们先来看看作业和任务的定义分别是什么吧。
一个Borg 作业job的属性通常包括作业名称、作业产生者和作业包含的任务数量。作业可以有一些约束来限制作业中的任务task运行在指定的机器上比如机器ID、任务所需数据所在机器等属性。这些约束可以是刚性的也可以是柔性的其中柔性约束表示偏好而非必须。需要注意的是一个作业只能在一个集群中运行。
而一个任务对应的是一组Linux进程运行在一台机器上的一个容器内或直接运行在节点上。任务也有一些属性比如资源需求量、在作业中的序号等。
那么,作业和任务是什么关系呢?
概括来说,一个作业可以包含多个任务。作业类似于用户在一次事务处理或计算过程中要求计算机所做工作的总和,而任务就是一项项具体的工作。
一个作业中的任务大多有相同的属性比如CPU核、内存、硬盘空间、硬盘访问速度、TCP端口等。在任务运行时 这些相同的属性,可以被覆盖 ,比如特定任务的命令行参数、各维度的资源等。
多个任务可以在多台机器上同时执行,从而加快作业的完成速度,提高系统的并行程度。而具体将哪个任务分配给哪个机器去完成,就是调度器要做的事儿了。
接下来我就与你讲述下Borg的调度器——Scheduler组件来帮助你理解Borg内部的任务调度流程以加深你对单体调度的理解。其实很多框架比如Hadoop、Spark等都是采用了单体调度设计它们整体的思想类似所以我希望通过对Borg调度的讲解能够帮助你理解你所在业务中的调度逻辑。
我们先来回忆下Borg的系统架构图吧。
-
Scheduler负责任务的调度当用户提交一个作业给BorgMaster后BorgMaster会把该作业保存到Paxos仓库中并将这个作业的所有任务加入等待队列中。调度器扫描任务等待队列根据预定义的调度算法将队列中的任务分配到满足作业需求且有足够资源的计算节点上也即上上图所示的Borglet节点
这里我要再强调一下,调度是以任务为单位的,而不是以作业为单位。调度器在扫描队列时,按照任务的优先级顺序,从高到低进行选择;且高优先级未被分配的任务可以抢占低优先级已被分配的任务。同优先级的任务则以轮询的方式处理。这样的任务分配次序,可以保证任务的公平,并避免队首的大型任务阻塞队列。
接下来,我们再看看调度器的核心部分,也就是调度算法吧。
Borg调度算法
Borg调度算法的核心思想是“筛选可行评分取优”具体包括两个阶段
可行性检查找到一组可以运行任务的机器即上图中的Borglet
评分从可行的机器中选择一个合适的机器即上图中的Borglet
首先,我们看一下可行性检查阶段的具体规则。
在可行性检查阶段,调度器会找到一组满足任务需求,且有足够可用资源(包括空闲资源,和已经分配给低优先级任务但可以抢占的资源)的机器。
假设系统中有6个可以执行任务的机器依次标记为节点1~6。现在有一个任务A只能部署在节点1、节点3或节点5中并且任务A的资源需求为0.5个CPU200MB内存。根据任务A的约束条件可以先从所有节点中筛选出节点1、节点3和节点5然后根据任务A的资源需求再从这3个节点中寻找满足任务资源需求的节点。
然后,我们看看评分阶段。
在评分阶段调度器确定每台可行机器的适宜性。Borg的评分机制有很多种但是不同的评分机制都是针对可行性检查阶段中筛选出的机器从这些可行的机器中根据评分机制进行打分从而选出最适合调度的一台机器。
在评分过程中,我们可以根据不同的场景制定不同的评价指标,比如最小化被抢占的任务数、尽量选择已经执行了相同任务的机器、目标任务是否需要跨域部署、是否把不同优先级任务进行混合部署等。 根据不同的考虑因素,可以定制不同的评分算法。
其中,常见的评分算法,包括“最差匹配”和“最佳匹配”两种。
Borg早期使用修改过的E-PVM算法来评分该算法的核心是将任务尽量分散到不同的机器上以并行的方式提高任务执行的速度。该算法的问题在于它会导致每个机器都有少量的无法使用的剩余资源称为“碎片资源”因此有时称其为“最差匹配”worst fit
比如现在有两个机器机器A的空闲资源为1个CPU和1G内存、机器B的空闲资源为0.8个CPU和1.2G内存同时有两个任务Task1的资源需求为0.4个CPU和0.3G内存、Task2的资源需求为0.3CPU和0.5G内存。
按照最差匹配算法思想Task1和Task2会分别分配到机器A和机器B上导致机器A和机器B都存在一些资源碎片如果此时来了一个Task3其任务需求为0.8个CPU和0.8G内存则Task3就可能无法立即分配到机器上需要等到Task1或Task2执行完才能被分配执行。
与“最差匹配”相反的是“最佳匹配”best fit即把同一个机器上的任务塞得越满越好。这样就可以“空”出一些“空闲”的机器它们仍运行存储服务用于部署计算资源需求大的任务。
比如在上面的例子中按照最佳匹配算法的思想Task1和Task2会被一起部署到机器A或机器B上这样未被部署的机器就可以用于执行Task3这样的大型任务了。
但是如果用户或Borg错误估计了资源需求紧凑的“最佳匹配”操作会对性能造成巨大的影响。比如用户估计它的任务A需要0.5个CPU和1G内存运行该任务的服务器上由于部署了其他任务现在还剩0.2个CPU和1.5G内存但用户的任务A突发峰值时比如电商抢购需要1个CPU和3G内存很明显初始资源估计错误此时服务器资源不满足峰值需求导致任务A不能正常运行。
所以说,“最佳匹配”策略不利于有突发负载的应用,而且对申请少量计算资源的批处理作业也不友好。因为这些作业申请少量计算资源批处理作业,可以分配到多个机器上,从而使用机器上的碎片化资源,使得任务可以更快速地被调度执行。
但是,“最佳匹配”策略中,批处理作业往往会集中分配在一起,因此占用的并非是碎片化资源。此外,“最佳匹配”这种策略有点类似“把所有鸡蛋放到一个篮子里面”,当这台服务器故障后,运行在这台服务器上的任务都会故障,对业务会造成很大的影响。
“最差匹配”和“最佳匹配”,这两个评分算法各有利弊。在实践过程中,我们往往会根据实际情况来选择更合适的评分算法。比如,对于资源比较紧缺,且业务流量比较规律,基本不会出现突发情况的场景,可以选择最佳匹配算法;如果资源比较丰富,且业务流量会经常出现突发情况的场景,可以选择最差匹配算法。
Borg的任务部署机制是支持优先级高的任务抢占低优先级任务的如果评分算法选中的机器上没有足够的资源来运行新任务Borg会抢占该机器上已经部署的低优先级任务的资源从最低优先级的任务开始逐级向上抢占任务资源直到可用资源足够运行新任务。其中被抢占的任务放回到调度器的等待队列里。
当然有很多调度框架是支持用户根据自己的场景自定义调度策略的,比如优先级策略、亲和性策略、反亲和性策略等。
知识扩展:多个集群/数据中心如何实现单体调度呢?
今天这篇文章中,我与你讲述的单体调度,其实是针对一个集群或一个数据中心的,那么多个集群或多个数据中心,能不能基于单体调度实现呢?
答案是肯定的,这就是集群联邦的概念了。
所谓集群联邦就是将多个集群联合起来工作核心思想是增加一个控制中心由它提供统一对外接口多个集群的Master向这个控制中心进行注册控制中心会管理所有注册集群的状态和资源信息控制中心接收到任务后会根据任务和集群信息进行调度匹配选择到合适的集群后将任务发送给相应的集群去执行。
集群联邦的概念其实就是单体调度的分层实现。如果你对集群联邦感兴趣的话推荐你看一下Kubernetes的集群联邦设计和工作原理。
总结
今天我以Borg为例与你讲述了单体调度架构的设计及调度算法。
单体调度是指一个集群中只有一个节点运行调度进程,该调度进程负责集群资源管理和任务调度,也就是说单体调度器拥有全局资源视图和全局任务。
单体调度的特征,可以总结为以下四点:
单体调度器可以很容易实现对作业的约束并实施全局性的调度策略,因此适合作为批处理任务和吞吐量较大、运行时间较长的任务。
单体调度系统的状态同步比较容易且稳定,这是因为资源使用和任务执行的状态被统一管理,降低了状态同步和并发控制的难度。
调度算法只能全部内置在核心调度器当中,因此调度框架的灵活性和策略的可扩展性不高。
单体调度存在单点故障的可能性。
现在,我再用一个思维导图为你总结一下今天的主要内容,以方便你理解记忆。
单体调度器虽然具有单点瓶颈或单点故障问题但因为其具有全局资源视图和全局任务简单易维护被很多公司广泛采用比如Google、阿里、腾讯等公司。另外我们今天介绍的Borg集群管理系统以及其开源版Kubernetes集群管理系统使用的都是单体调度结构。
单体调度结构虽然结构单一,但是其调度算法可以扩展甚至自定义,也就是说你可以根据业务特征,自定义调度策略,比如优先级策略、亲和性策略等。
学完了关于单体调度的知识后,赶紧上手试试,定制一个独特的调度算法或设计一个特定的单体调度器吧。如果你在这个过程中遇到了什么问题,就留言给我吧。
思考题
你能和我分享下Google Borg是采用什么技术实现的资源隔离吗
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,179 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
12 分布式调度架构之两层调度:物质文明、精神文明两手抓
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
我在上一篇文章中,与你分享了单体调度。单体调度的核心是,所有节点的资源以及用户的任务均由中央服务器统一管理和调度。因此,中央服务器很容易成为单点瓶颈,会直接导致其支持的调度规模和服务类型受限。
于是两层调度就出现了。那么,到底什么是两层调度呢,它是如何设计的,又有哪些调度算法呢?接下来,就和我一起打卡分布式调度架构的两层调度,去探寻这些问题的答案吧。
什么是两层调度?
在单体调度架构中,中央服务器的单点瓶颈问题,会限制调度的效率和支持的任务类型。中央服务器的性能会限制调度的效率,很好理解,但为什么会限制支持的任务类型呢?
简单地说,这是因为不同的服务具有不同的特征,对调度框架和计算的要求都不一样。比如说,你的业务最开始时只有批处理任务,后来发展到同时还包括流数据任务,但批处理任务是处理静态数据,流数据任务却是处理实时数据。显然,单体调度框架会随着任务类型增加而变得越来越复杂,最终出现扩展瓶颈。
那么,为了提升调度效率并支持多种类型的任务,最直接的一个想法就是,能不能把资源和任务分开调度,也就是说一层调度器只负责资源管理和分配,另外一层调度器负责任务与资源的匹配呢。
很显然,这个解决方案是可以的。这种调度架构,就是我们通常所说的两层调度。如果我们还是把资源比作物质文明、把任务比作精神文明的话,两层调度就可以理解为“物质文明与精神文明两手抓”。
两层调度结构对应的就是两层调度器,资源的使用状态同时由中央调度器和第二层调度器管理,中央调度器从整体上进行资源的管理与分配,将资源分配到第二层调度器;再由第二层调度器负责将资源与具体的任务配对,因此第二层调度可以有多个调度器,以支持不同的任务类型。
如下图所示Scheduler-1表示第一层调度负责收集和管理集群中的资源信息Scheduler-2 表示第二层调度Scheduler-1会将集群资源发送给Scheduler-2然后Scheduler-2根据任务的资源需求和Scheduler-1发送的资源信息进行任务匹配和调度。
两层调度器中的第一层调度器仍是一个经简化的中央调度器,通常放在分布式集群管理系统中,而第二层调度则是由各个应用程序框架完成。两层调度器的职责分别是:第一层调度器负责管理资源并向框架分配资源,第二层调度器接收分布式集群管理系统中第一层调度器分配的资源,然后根据任务和接收到的资源进行匹配。
采用两层调度结构的集群管理系统有很多,典型代表是 Apache Mesos 和 Hadoop YARN。我在第9篇文章中讲述Mesos的体系结构时和你分析了它采用的是典型的两层调度。那么今天我就继续以Mesos为例带你学习两层调度的架构设计和对应的分配算法吧。
两层调度设计
由于Mesos只负责底层资源的管理和分配并不涉及存储、 任务调度等功能因此Mesos要实现类似Borg那样的资源与任务管理还需要上层框架的配合。
具体到两层调度架构上Mesos本身实现的调度器为第一层调度负责资源管理然后将第二层任务调度交给了框架完成。接下来我们就具体看看吧。
两层调度架构
以Mesos为基础的分布式资源管理与调度框架包括两部分即Mesos资源管理集群和框架。
资源管理集群是由一个Master节点和多个Slave节点组成的集中式系统。每个集群有且仅有一个Master节点负责管理Slave节点并对接上层框架Slave节点向Master节点周期汇报资源状态信息并执行框架提交的任务。
框架Framework运行在Mesos上是负责应用管理与调度的“组件”比如Hadoop、Spark、MPI和Marathon等不同的框架用于完成不同的任务比如批处理任务、实时分析任务等。框架主要由调度器Scheduler和执行器Executor组成调度器可以从Master节点获取集群节点的信息 执行器在Slave节点上执行任务。
从上述的架构描述可以看出Mesos是一个典型的双层调度框架。Mesos Master上有一个调度器也就是Allocation Module负责管理并分配集群中的所有资源是第一层调度。框架上负责任务的管理与调度的调度器是第二层调度如下图所示。
接下来我们再看看Mesos两层调度的基本原理吧。
框架向Mesos Master注册
Mesos Slave节点定期或周期向Mesos Master上报本节点的空闲资源
Mesos Master的Scheduler进程收集所有节点的空闲资源信息并以Resource Offer的方式将空闲资源发送给注册的框架
框架的Scheduler接收到Mesos发送的资源后进行任务调度与匹配匹配成功后将匹配结果下发给Mesos Master并由Mesos Master转发给相应节点的执行器执行任务。
可以看出Mesos实现双层调度时采用Resource Offer机制衔接了第一层和第二层调度。Resource Offer机制指的是Mesos Master主动将节点空闲资源以类似发放Offer的方式发给每个框架如果框架需要则使用不需要则还回。
也就是说通过Resource Offer机制第一层调度将资源主动告知第二层调度然后第二层调度进行具体的任务匹配从而实现了任务调度与资源管理的分离Mesos Master通过资源分配算法决定给各个Framework提供多少资源而Framework则决定接受哪些资源以及哪些任务使用这些资源运行。这样一来一个两层调度架构就实现了。
在Mesos的两层调度中Framework第二层调度器中的任务与资源匹配的调度策略很常见也有很多文章做了比较深入的分析了所以如果你想要深入研究的话可以参考下Hadoop、Spark等的调度策略这里我就不多说了。
接下来我们重点看下Mesos第一层调度算法理解其如何为框架分配资源以支持多用户多框架。
资源分配算法
Mesos的资源分配算法解决的问题是决策需要将当前可用资源分配给哪些框架以及分配多少。接下来我将重点与你介绍两种主要的资源分配算法最大最小公平算法Max-min FairnessMMF和主导资源公平算法Dominant Resource FairnessDRF
首先我们看看最大最小公平算法。这是一种在兼顾公平的前提下尽可能让更多人满意的资源分配算法。为什么这么说呢因为这个算法有3个主要原则
按照用户对资源需求量递增的顺序进行空闲资源分配;
不存在用户得到的资源超过自己需求的情况;
对于分配的资源不满足需求的用户,所获得的资源是相等的。
在执行资源分配时最大最小公平算法按照上述3条原则进行多次迭代每次迭代中资源均平均分配如果还有剩余资源就进入下一次迭代一直到所有用户资源得到满足或集群资源分配完毕迭代结束。
接下来,我们通过一个具体的例子来看看最大最小公平算法的资源分配流程吧。
假设现在有总量为100的空闲资源有4个用户A、B、C、D对该资源的需求量分别为35102545分配流程如下所示
按照用户对资源的需求量升序排列则4个用户的需求量为B:10C:25A:35D:45
平均分配空闲资源。资源空闲总量100除以用户数4则平均空闲资源量为25按照第一步中需求量分配后用户资源需求量为001020且用户B由于资源需求量小于25因此会剩余资源。此时空闲资源量为15资源需求人数为2。
重复第二步平均分配资源15/2=7.5即分别为用户A和D分配7.5份资源此时用户资源需求量为002.512.5空闲资源量为0资源需求人数为2。
所有资源已分配完,算法终止。
最大最小公平算法的执行流程,如下图所示。
在这个案例中最大最小公平算法是由于所有资源全部分配完才终止的。至此对于需求量为10253545的用户们来说分配到的资源是102532.532.5)。这个算法的另外一个结束条件是,资源分配满足了所有用户的资源需求,即当没有用户有资源需求时,算法也会终止。
接下来,我们再看看主导资源公平算法。
最大最小公平算法采用了绝对公平的方式分配资源会导致大量的资源浪费比如用户需求量为35和45的用户A和用户D均分配了32.5的空闲资源,但由于资源不满足需求,这两个用户均无法使用。
而主导资源公平算法在考虑用户公平性的前提下,还考虑了用户对不同资源类型的需求,以尽可能地合理分配资源。也就是说,同样的资源量,主导资源公平算法可以尽可能地满足更多的用户。
在Mesos中框架对资源的需求往往包括对CPU、内存等多种类型资源的需求。针对多种资源的需求主导资源公平算法首先计算已经分配给用户的每一种资源的占用率Resource Share比如已经分配的CPU占总资源量的多少已经分配的内存占总资源量的多少。所有资源占用率中的最大值称作该用户的主导资源占用率而主导资源占用率对应的资源就是用户的主导资源。
我们通过一个具体的案例看看如何判断用户的主导资源吧。如下图所示假设系统中的资源共包括18个 CPU 和36 GB 内存有两个FrameworkFramework A和Framework B分别运行了两种任务假设Framework A运行内存密集型任务Framework B运行CPU密集型任务且每个任务所需要的资源量是一致的分别是 和 。
第一步:计算资源分配量。
假设x和y分别是Framework A和Framework B分配的任务数那么Framework A消耗的资源为{2x CPU8x GB}Framework B消耗的资源数为{6y CPU2y GB}分配给两个Framework的总资源量为2x+6y个CPU和8x+2yGB内存。
第二步:确定主导资源。
对于Framework A来说每个任务要消耗总CPU资源的2/18总内存资源的8/36所以Framework A的主导资源为内存对于Framework B来说每个任务要消耗总CPU资源的6/18和总内存资源的2/36因而Framework B的主导资源为CPU。
第三步DRF算法的核心是平衡所有用户的主导资源占用率尽可能试图最大化所有用户中最小的主导资源占用率。通过求解下列公式可以计算出Framework A和Framework B分配的任务数并且要在满足公式的条件下使得x和y越大越好。
2x+6y≤18
8x+2y≤36
8x/36=6y/18
通过求解可以得出x=3即Framework A可以运行3个任务y=2即Framework B可以运行2个任务。这样分配的话每个Framework获取了相同比例的主导资源A获取了2/3的内存B获取了2/3的CPU从而在主导资源上体现了调度算法的公平性。
在实际任务分配过程中主导资源率是根据已经分配给Framework的资源占集群中总资源量的多少进行计算的并且在每次分配过程中会选择主导资源最小的Framework进行分配也就是试图最大化所有用户中最小的主导资源占用率。
如果你想深入研究主导资源公平算法的话可参考“Dominant Resource Fairness: Fair Allocation of Multiple Resource Types”这篇论文。
现在,我来对比下这两种调度算法吧。
最大最小公平算法适用于单一类型的资源分配场景,而主导资源公平算法适用于多种类型资源混合的场景。并且,最大最小公平算法从公平的角度出发,为每个用户分配不多于需求量的资源;而主导资源公平算法从任务出发,目的在于尽量充分利用资源使得能够执行的任务越多越好。
知识扩展:两层调度如何保证不同的业务不会互相干扰?
类似Mesos这样的两层调度机制可以同时支持多个框架和多种类型的业务那么如何保证这些业务运行时不会互相干扰呢
首先我们思考一下什么情况下会存在业务运行时相互干扰呢。答案就是当多个业务运行在同一台机器上共同使用CPU、内存以及系统环境时会存在相互干扰。
要解决这个问题我想你肯定会问不同的业务能在独立的环境中运行吗也就是说隔离不同的业务资源和环境应该就不会存在相互干扰了吧。不错解决这个问题的办法就是资源隔离就好比我们现在接触的虚拟机一样在同样的服务器上安装多个虚拟机不同的用户在不同的虚拟机上运行这些用户互不干扰。在Mesos中实现这种资源隔离的是容器。
容器的实质是进程,该进程运行于属于自己的独立的命名空间,可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至是自己的用户 ID 空间。Mesos支持的容器包括Linux自带的cgroups和Docker。
所以说Mesos正是用容器隔离开了不同的业务使得它们运行时不会互相干扰。
总结
今天我以Mesos为例与你讲述了两层调度的架构设计和资源分配算法。我们一起总结下今天的核心内容吧。
两层调度是一种资源和任务分开调度的设计,也就是说一层调度器只负责资源的管理和分配,另外一层调度器负责任务与资源的匹配。
在Mesos中第一层资源调度由Mesos提供第二层任务调度由框架提供Mesos将资源以Resource Offer的形式发放给框架调度器框架调度器根据任务需求和得到的资源信息进行任务匹配调度为此提高了调度的并发性。
而关于第一层的调度算法,通常有最大最小公平算法和主导资源公平算法等。
两层调度的一个问题是,由于第二层调度只能获得部分资源视图,因此无法实现全局最优调度。
最后,我将今天这篇文章的核心知识点梳理为了一张思维导图,以方便你理解与记忆。
两层调度提供了多租户多框架的支持,如果你的业务类型比较多或者面向的是不同的租户的话,建议你采用两层调度框架。
相信你通过这篇文章可以看到,在分布式领域中,同时支持多种框架、支持多种类型任务调度的调度机制,并没有那么神秘,只要你静下心来弄明白这篇文章的调度机制,以后遇到类似的调度机制,就可以做到心中有数了。
不得不说Mesos的两层调度设计得非常巧妙并且Mesos支持你自己写一个调度器注册到Mesos作为第二层调度。赶快动手实践一下吧Mesos的官网提供了相应的案例方便你入门加油相信你一定可以
思考题
你觉得Mesos双层调度机制容易导致资源碎片问题吗原因又是什么呢
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,172 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
13 分布式调度架构之共享状态调度:物质文明、精神文明多手协商抓
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在上一篇文章中,我们一起学习了两层调度。在两层调度架构中,第二层调度只知道集群中的部分资源,无法进行全局最优调度。那么,是否有办法解决全局最优调度的问题呢?
答案是肯定的,解决办法就是我今天要带你打卡的共享状态调度。
接下来,我们就一起看看共享状态调度到底是什么,以及它的架构和工作原理吧。
什么是共享状态调度?
通过我们前两篇文章的讲述,不难发现,集群中需要管理的对象主要包括两种:
一是,资源的分配和使用状态;
二是,任务的调度和执行状态;
在单体调度中,这两种对象都是由单体调度器管理的,因此可以比较容易地保证全局状态的一致性,但问题是可扩展性较差(支持业务类型受限),且存在单点瓶颈问题。
而在两层调度中这两种对象分别由第一层中央调度器和第二层Framework调度器管理由于Framwork调度器只能看到部分资源因此不能保证全局状态的一致性也不容易实现全局最优的调度。
为了解决这些问题,一种新的调度器架构被设计了出来。这种架构基本上沿袭了单体调度器的模式,通过将单体调度器分解为多个调度器,每个调度器都有全局的资源状态信息,从而实现最优的任务调度,提供了更好的可扩展性。
也就是说,这种调度架构在支持多种任务类型的同时,还能拥有全局的资源状态信息。要做到这一点,这种调度架构的多个调度器需要共享集群状态,包括资源状态和任务状态等。因此,这种调度架构,我们称之为共享状态调度器。
如果我们继续把资源比作物质文明、把任务比作精神文明的话,相对于单体调度和两层调度来说,共享状态调度就是“物质文明与精神文明多手协商抓”。
共享状态调度架构的示意图,如下所示:
可以看出,共享状态调度架构为了提供高可用性和可扩展性,将集群状态之外的功能抽出来作为独立的服务。具体来说就是:
State Storage模块资源维护模块负责存储和维护资源及任务状态以便Scheduler查询资源状态和调度任务
Resource Pool即为多个节点集群接收并执行Scheduler调度的任务
而Scheduler只包含任务调度操作而不是像单体调度器那样还需要管理集群资源等。
共享状态调度也支持多种任务类型,但与两层调度架构相比,主要有两个不同之处:
存在多个调度器,每个调度器都可以拥有集群全局的资源状态信息,可以根据该信息进行任务调度;
共享状态调度是乐观并发调度在执行了任务匹配算法后调度器将其调度结果提交给State Storage由其决定是否进行本次调度从而解决竞争同一种资源而引起的冲突问题实现全局最优调度。而两层调度是悲观并发调度在执行任务之前避免冲突无法实现全局最优匹配。
看到这里,我再和你说说乐观并发调度和悲观并发调度的区别吧。
乐观并发调度,强调事后检测,在事务提交时检查是否避免了冲突:若避免,则提交;否则回滚并自动重新执行。也就是说,它是在执行任务匹配调度算法后,待计算出结果后再进行冲突检测。
悲观并发调度强调事前预防在事务执行时检查是否会存在冲突。不存在则继续执行否则等待或回滚。也就是说在执行任务匹配调度算法前通过给不同的Framework发送不同的资源以避免冲突。
现在,我们已经对共享状态调度有了一个整体印象,知道了它可以解决什么问题。那么接下来,我们再看看这种调度架构是如何设计的吧。
共享状态调度设计
共享状态调度的理念最早是Google针对两层调度器的不足提出的一种调度架构。这种调度结构的典型代表有Google的Omega、微软的Apollo以及Hashicorp的Nomad容器调度器。
作为Google公司的第二代集群管理系统Omega在设计时参考了Borg的设计吸收了Borg的优点并改进了其不足之处。所以接下来我就以Omega为例和你讲述共享状态调度的架构和工作原理吧。这样一来你可以对照着第11篇文章中Borg的调度设计一起理解。
Omega调度架构
Omega集群中有一个“Cell”的概念每个Cell管理着部分物理集群一个集群有多个Cell。实际上你可以直接将这里的“Cell”理解为一个集群的子集群或子节点的集合。
Omega集群的调度架构示意图如下所示。
我在介绍共享状态调度的架构时提到State Storage模块负责存储和维护资源及任务状态里面有一个Cell State文件记录着全局共享的集群状态。实际上State Storage组件中的集群资源状态信息就是主本而Cell State就是以主副本的形式存在的。每个调度器都包含一个私有的Cell State副本也就是拥有了一个集群资源状态信息的副本进而达到了共享集群资源状态信息的目的。
在Omega系统中没有中央资源分配器所有资源分配决策都在调度器Scheduler中进行。每个调度器都可以根据私有的Cell State副本来制定调度决策。
调度器可以查看Cell的整个状态并申请任何可用的集群资源。一旦调度器做出资源调度决策它就会在原子提交中更新本地的Cell State的资源状态副本。若同时有多个调度器申请同一份资源State Storage模块可以根据任务的优先级选择优先级最高的那个任务进行调度。
可以看出在Omega系统中的每个调度器都具有对整个集群资源的访问权限从而允许多个调度器自由地竞争空闲资源并在更新集群状态时使用乐观并发控制来调解资源冲突问题。
这样一来Omega就有效地解决了两层调度中Framework只拥有局部资源无法实现全局最优的问题。
接下来我们看一下Omega共享调度的工作原理吧。
Omega共享调度工作原理
Omega 使用事务管理状态的设计思想将集群中资源的使用和任务的调度类似于基于数据库中的一条条事务Transaction一样进行管理。显然数据库是一个共享的状态对应Omega中的Cell State而每个调度器都要根据数据库的信息即集群的资源信息去独立完成自己的任务调度策略。
接下来,我们就具体看看吧。
如下图所示在一个应用执行的过程中调度器会将一个Job中的所有Task与Resource进行匹配可以说Task与Resource之间是进行多对多匹配的。其间调度器会设置多个Checkpoint来检测Resource是否都已经被占用只有这个Job的所有Task可以匹配到可用资源时这个Job才可以被调度。
这里的Job相当于一个事务也就是说当所有Task匹配成功后这个事务就会被成功Commit如果存在Task匹配不到可用资源那么这个事务需要执行回滚操作Job调度失败。
无论事务是否执行成功调度器都会在事务执行之后重新从主本那里同步更新本地Cell State的资源状态副本以保证本地集群信息状态的有效性。若事务未成功执行则调度器会在必要时重新运行其调度算法并再次尝试申请资源。
也就是说调度器对Job的调度是具有原子性的一个Job的所有Task都是一起调度的即使部分Task调度失败了调度器再次调度时也必须再次调度整个Job。多个调度器可以并行调度无需等待其他调度器调度结果若存在冲突时进行冲突处理比如根据Job的优先级优先级高则获得资源。
由此我们可以看到Omega涉及了Job并发调度。针对这一点Omega采用了传统数据库中的乐观锁MVCCMulti-Version Concurrency Control基于多版本的并发访问控制即每一个应用都发放了所有的可用资源在更新集群状态时使用乐观并发控制来解决资源冲突问题来提高Omega的并发度。
不同的Omega调度器可以实现不同的策略但有一些调度规则是所有调度器必须达成一致的比如哪些资源是允许分配的、如何评估作业的优先级等。
因此Omega调度器将两层调度器中的集中式资源调度模块简化成了一些持久化的共享数据状态和针对这些数据的验证代码。而这里的“共享数据”实际上就是整个集群的实时资源状态信息而验证代码就是解决调度冲突的调度规则。
知识扩展:单体调度、两层调度和共享调度的区别是什么?
现在,我已经带你学习了单体调度、双层调度和共享调度,那么这三种调度的区别是什么呢?接下来,我们就一起回忆并对比下吧。
我把这三种调度的架构示意图放到一起,先帮你有一个整体认识。
单体调度,是由一个中央调度器去管理整个集群的资源信息和任务调度,也就是说所有任务只能通过中央调度器进行调度。
这种调度架构的优点是,中央调度器拥有整个集群的节点资源信息,可以实现全局最优调度。但它的缺点是,无调度并发性,且中央服务器存在单点瓶颈问题,导致支持的调度规模和服务类型受限,同时会限制集群的调度效率。因此,单体调度适用于小规模集群。
两层调度,是将资源管理和任务调度分为两层来调度。其中,第一层调度器负责集群资源管理,并将可用资源发送给第二层调度;第二层调度接收到第一层调度发送的资源,进行任务调度。
这种调度架构的优点是,避免了单体调度的单点瓶颈问题,可以支持更大的服务规模和更多的服务类型。但其缺点是,第二层调度器往往只对全局资源信息有部分可观察性,因此任务匹配算法无法实现全局最优。双层调度适用于中等规模集群。
共享状态调度,多个调度器,每个调度器都可以看到集群的全局资源信息,并根据这些信息进行任务调度。相较于其他两个调度架构来说,共享状态调度架构适用的集群规模最大。
这种调度架构的优点是,每个调度器都可以获取集群中的全局资源信息,因此任务匹配算法可以实现全局最优性。但,也因为每个调度器都可以在全局范围内进行任务匹配,所以多个调度器同时调度时,很可能会匹配到同一个节点,从而造成资源竞争和冲突。
虽然Omega的论文宣称可以通过乐观锁机制避免冲突。但在工程实践中如果没有妥善处理资源竞争的问题则很可能会产生资源冲突从而导致任务调度失败。这时用户就需要对调度失败的任务进行处理比如重新调度、任务调度状态维护等从而进一步增加了任务调度操作的复杂度。
我将单体调度、两层调度、共享状态调度总结在了一张表格中:
总结
今天,我主要与你分享了分布式调度架构设计中的共享状态调度。我们一起来总结下今天的核心知识点吧。
首先,我讲述了什么是共享状态调度。概括地说,共享状态调度是将单体调度器分解为多个服务,由多个服务共享集群状态,包括资源状态和任务状态等。
接下来我以Google的Omega集群管理系统为例和你分享了共享状态调度的架构和工作原理。共享状态调度包含多个调度器每个调度器都可以看到集群的全局资源信息并根据这些信息进行任务调度。
最后,我要和你说明的是,共享状态调度是乐观并发调度,调度器将其调度的结果以原子的方式提交给资源维护模块,由其决定是否进行本次调度。
接下来,我整理一张思维导图来帮助你巩固今天的核心知识点。
我想让你知道的是在分布式领域中共享状态调度是Google号称的下一代集群管理系统Omega的调度机制可以解决双层调度无法实现全局最优的问题同时也避免了单体调度的单点瓶颈问题。
但,说到这儿你可能会回想起曾经看到的两句话:
为了达到设计目标Omega 的实现逻辑变得越来越复杂。 在原有的 Borg 共享状态模型已经能满足绝大部分需要的情况下Omega 的前景似乎没有那么乐观。
Omega系统缺点是在小集群下没有优势。
这里我再与你解释下为什么说Omega是Google准备打造的下一代集群管理系统。
从调度架构方面来看Borg无法支持同时存在多种业务类型的场景并且存在单点瓶颈问题。而Omega解决了Borg的这两个问题但是当多个调度器并行调度时可能存在资源冲突当资源申请产生冲突时会导致大量任务或任务多次调度失败增加了任务调度失败的故障处理的复杂度比如需要进行作业回滚、任务状态维护等。
因此,设计一个好的冲突避免策略是共享状态调度的关键。对于小规模集群来说,其集群规模、任务数量等都不大,使用单体调度就可以满足其任务调度的需求,避免了考虑复杂的冲突避免策略。也就是说,共享状态调度比较适合大规模、同时存在多种业务类型的场景,不太适合小规模集群。
思考题
共享状态调度的核心是解决并发冲突,那你认为有没有什么好的方法可以解决冲突呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,138 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
14 答疑篇:分布式事务与分布式锁相关问题
你好,我是聂鹏程。
到目前为止“分布式技术原理与算法解析”专栏已经更新13篇文章了主要与你介绍了“分布式起源”“分布式协调与同步”和“分布式资源管理与负载调度”。
在这里,我首先要感谢你们在评论区留下的一条条精彩留言。这让我感觉到,你们对分布式技术的浓厚兴趣,以及对我和对这个专栏的支持。这不但活跃了整个专栏的氛围、丰富了专栏的内容,也让我备受鼓舞,干劲儿十足地去交付更高质量的文章。
比如,@xfan@Jackey等同学积极思考问题,并对文中有疑惑的地方结合其他资料给出了自己的思考和建议;再比如,@zhaozp@静水流深等同学,每次更新后都在坚持打卡学习;再比如,@约书亚等同学针对分布式事务提出了非常好的问题@每天晒白牙等同学对文中内容进行了非常好的总结
这些同学有很多,我就不再一一点名了。感谢你们的同时,我也相信,积极参与并留言也会帮助你更深入地理解每一个知识点。所以我希望,你接下来可以多多留言给我,让我们一起学习,共同进步。
留言涉及的问题有很多,但我经过进一步地分析和总结后,发现针对分布式事务和分布式锁的问题比较多,同学们的疑惑也比较多。
确实,这两大问题属于分布式技术的关键问题。因此,今天的这篇答疑文章,我就围绕这两大问题来进行一次集中的分析和回答吧。
我们先来看一下分布式事务的相关问题。
分布式事务的相关问题
在第6篇文章“分布式事务All or Nothing”中我介绍了两阶段提交协议和三阶段提交协议。有很多同学提出了疑问两阶段提交协议2PC和三阶段提交协议3PC的区别到底是什么
在回答这个问题前我建议你先回到第6篇文章去回忆一下它们的流程。然后我们看看2PC和3PC的第一步到底是不是“类似”的
2PC的第一步投票voting阶段中参与者收到事务执行询问请求时就执行事务但不提交而3PC却写着在PreCommit阶段执行事务不提交。2PC和3PC的第一步是非常不类似吧
其实,我说它们类似,是指它们均是通过协调者,来询问参与者是否可以正常执行事务操作,参与者也都会给协调者回复。
在2PC中如果所有参与者都返回结果后会进入第二阶段也就是提交阶段也可以说是执行阶段根据第一阶段的投票结果进行提交或取消。
在3PC中进入真正的提交阶段前还会有一个预提交阶段这个预提交阶段不会做真正的提交而是会将相关信息记录到事务日志中当所有参与者都返回Yes消息后才会真正进入提交阶段。
这样说明后,相信你对这个问题的疑惑应该解决了吧。
现在,我们继续延展一下这个问题吧。
追问13PC在预提交阶段才开始执行事务操作那协调者发送CanCommit给参与者的时候参与者根据什么返回Yes或者No消息呢?
3PC在投票阶段CanCommit阶段协调者发送CanCommit询问后参与者会根据自身情况比如自身空闲资源是否足以支撑事务、是否会存在故障等预估自己是否可以执行事务但不会执行事务参与者根据预估结果给协调者返回Yes或者No消息。
追问23PC出现的目的是解决2PC的同步阻塞和数据不一致性问题。那么我们不可以在2PC中直接去解决这些问题吗3PC多了预提交和超时机制就真的解决这些问题了吗
我们先来看看同步阻塞的问题。
在2PC中参与者必须等待协调者发送的事务操作指令才会执行事务比如提交事务或回滚等操作如果协调者故障也就是说参与者无法收到协调者的指令了那么参与者只能一直等待下去。这就好比在一个班级里面班主任是协调者学生是参与者班主任告诉学生今天下午6点组织一个比赛但班主任今天生病了根本到不了学校并且也无法发送信息告诉学生那么学生们就只能一直等待。
3PC在协调者和参与者中都引入了超时机制2PC只是在协调者引入了超时也就是说当参与者在一定时间内没有接收到协调者的通知时会执行默认的操作从而减少了整个集群的阻塞时间。这就好比班主任生病了学生默认等待半个小时如果班主任还没有任何通知那么默认比赛取消学生可以自由安排做自己的事情去了。
但其实阻塞在实际业务中是不可能完全避免的。在上面的例子中学生等待超时的半个小时中其实还是阻塞的只是阻塞的时间缩短了。所以相对于2PC来说3PC只是在一定程度上减少或者说减弱了阻塞问题。
接下来,我们再看看数据不一致的问题吧。
通过上面的分析可以看到同步阻塞的根本原因是协调者发生故障想象一下比如现在有10个参与者协调者在发送事务操作信息的时候假设在发送给了5个参与者之后发生了故障。在这种情况下未收到信息的5个参与者会发生阻塞收到信息的5个参与者会执行事务以至于这10个参与者的数据信息不一致。
3PC中引入了预提交阶段相对于2PC来讲是增加了一个预判断如果在预判断阶段协调者出现故障那就不会执行事务。这样可以在一定程度上减少故障导致的数据不一致问题尽可能保证在最后提交阶段之前各参与节点的状态是一致的。
所以说3PC是研究者们针对2PC中存在的问题做的一个改进虽然没能完全解决这些问题但也起到了一定的效果。
在实际使用中通常采用多数投票策略来代替第一阶段的全票策略类似采用Raft算法选主的多数投票策略即获取过半参与者的投票数即可。关于Raft算法的选主的具体原理你可以再回顾下第4篇文章“分布式选举国不可一日无君”中的相关内容。
追问33PC也是只有一个协调者为什么就不会有单点故障问题了
首先,我先明确下这里所说的单点故障问题。
因为系统中只有一个协调者那么协调者所在服务器出现故障时系统肯定是无法正常运行的。所以说2PC和3PC都会有单点故障问题。
但是3PC因为在协调者和参与者中都引入了超时机制可以减弱单点故障对整个系统造成的影响。为什么这么说呢
因为引入的超时机制参与者可以在长时间没有得到协调者响应的情况下自动将超时的事务进行提交不会像2PC那样被阻塞住。
好了以上就是关于分布式事务中的2PC和3PC的相关问题了相信你对这两个提交协议有了更深刻的认识。接下来我们再看一下分布式锁的相关问题吧。
分布式锁的相关问题
在第7篇文章“分布式锁关键重地非请勿入”后的留言中我看到很多同学都问到了分布式互斥和分布式锁的关系是什么。
我们先来回顾下,分布式互斥和分布式锁分别是什么吧。
在分布式系统中某些资源即临界资源同一时刻只有一个程序能够访问这种排他性的资源访问方式就叫作分布式互斥。这里你可以再回顾下第3篇文章中的相关内容。
分布式锁指的是在分布式环境下系统部署在多个机器中实现多进程分布式互斥的一种锁。这里你可以再回顾下第7篇文章中的相关内容。
分布式锁的目的是,保证多个进程访问临界资源时,同一时刻只有一个进程可以访问,以保证数据的正确性。因此,我们可以说分布式锁是实现分布式互斥的一种手段或方法。
除了分布式互斥和分布式锁的关系外很多同学都针对基于ZooKeeper和基于Redis实现分布式锁提出了不少好问题。我们具体看看这些问题吧。
首先我们来看一下基于ZooKeeper实现分布式锁的问题。有同学问ZooKeeper分布式锁可能存在多个节点对应的客户端在同一时间完成事务的情况吗
这里我需要先澄清一下ZooKeeper不是分布式锁而是一个分布式的、提供分布式应用协调服务的组件。基于ZooKeeper的分布式锁是基于ZooKeeper的数据结构中的临时顺序节点来实现的。
请注意这里提到了ZooKeeper是一个分布式应用协调服务的组件。比如在一个集中式集群中以Mesos为例Mesos包括master节点和slave节点slave节点启动后是主动去和master节点建立连接的但建立连接的条件是需要知道master节点的IP地址和状态等。而master节点启动后会将自己的IP地址和状态等写入ZooKeeper中这样每个slave节点启动后都可以去找ZooKeeper获取master的信息。而每个slave节点与ZooKeeper进行交互的时候均需要一个对应的客户端。
这个例子说明了存在多个节点对应的客户端与ZooKeeper进行交互。同时由于每个节点之间并未进行通信协商且它们都是独立自主的启动时间、与ZooKeeper交互的时间、事务完成时间都是独立的因此存在多个节点对应的客户端在同一时间完成事务的这种情况。
接下来我们看一下基于Redis实现分布式锁的问题。Redis为什么需要通过队列来维持进程访问共享资源的先后顺序
在我看来,这是一个很好的问题。
@开心小毛认为Redis的分布式锁根本没有队列收到setnx返回为0的进程会不断地重试直到某一次的重试成为DEL命令后第一个到达的setnx从而获得锁至于此进程在等待获得锁的众多进程中是不是第一个发出setnx的Redis并不关心。
其实,客观地说,这个理解是合情合理的,是我们第一反应所能想到的最直接、最简单的解决方法。可以说,这是一种简单、粗暴的方法,也就是获取不到锁,就不停尝试,直到获取到锁为止。
但你有没有想过当多个进程频繁去访问Redis时Redis会不会成为瓶颈性能会不会受影响。带着这个疑惑我们来具体看看基于Redis实现的分布式锁到底需不需要队列吧。
如果没有队列维护多进程请求,那我们可以想到的解决方式,就是我刚刚和你分析过的,通过多进程反复尝试以获取锁。
但,这种方式有三个问题:
一是,反复尝试会增加通信成本和性能开销;
二是,到底过多久再重新尝试;
三是,如果每次都是众多进程进行竞争的话,有可能会导致有些进程永远获取不到锁。
在实际的业务场景中,尝试时间的设置,是一个比较难的问题,与节点规模、事务类型均有关系。
比如节点规模大的情况下如果设置的时间周期较短多个节点频繁访问Redis会给Redis带来性能冲击甚至导致Redis崩溃对于节点规模小、事务执行时间短的情况若设置的重试时间周期过长会导致节点执行事务的整体时间变长。
基于队列来维持进程访问共享资源先后顺序的方法中,当一个进程释放锁之后,队列里第一个进程可以访问共享资源。也就说,这样一来就解决了上面提到的三个问题。
总结
我针对前面13篇文章留言涉及的问题进行了归纳总结从中摘取了分布式事务和分布式锁这两个知识点串成了今天这篇答疑文章。
今天没来得及和你扩展的问题,后续我会再找机会进行解答。最后,我要和你说的是,和我一起打卡分布式核心技术,一起遇见更优秀的自己吧。
篇幅所限,留言区见。
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,172 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
15 分布式计算模式之MR一门同流合污的艺术
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
我在第12篇文章中与你介绍两层调度时提到Mesos的第二层调度是由Framework完成的。这里的Framework通常就是计算框架比如Hadoop、Spark等。用户基于这些计算框架可以完成不同类型和规模的计算。
那么在接下来的4篇文章我们就要进入“第三站分布式计算技术”了。在这一站我将与你详细介绍分布式领域中的4种计算模式包括MapReduce、Stream、Actor和流水线。而今天这篇文章我们就先从MR模式开始吧。
Hadoop这个框架主要用于解决海量数据的计算问题。那么它是如何做到海量数据计算的呢你可能会想既然是海量数据规模这么大那就分成多个进程每个进程计算一部分然后汇总一下结果就可以提升运算速度了。其实整个计算流程我们可以很形象地用一个词来解释就是“同流合污“。
没错就是这种想法在分布式领域中就叫作MR模式即Map Reduce模式。接下来我们就一起揭开MR模式的神秘面纱吧。
什么是分而治之?
分而治之Divide-and-Conquer是计算机处理问题的一个很重要的思想简称为分治法。
顾名思义,分治法就是将一个复杂的、难以直接解决的大问题,分割成一些规模较小的、可以比较简单的或直接求解的子问题,这些子问题之间相互独立且与原问题形式相同,递归地求解这些子问题,然后将子问题的解合并得到原问题的解。
比如,现在要统计全中国的人口数,由于中国的人口规模很大,如果让工作人员依次统计每个省市的人口数,工作量会非常大。在实际统计中,我们通常会按照省分别统计,比如湖南省的工作人员统计湖南省的人口数,湖北省的工作人员统计湖北省的人口数等,然后汇总各个省的人口数,即可得到全国人口数。
这,就是一个非常好的分而治之的例子。
当然这种分治的思想还广泛应用于计算机科学的各个领域中分布式领域中的很多场景和问题也非常适合采用这种思想解决并为此设计出了很多计算框架。比如Hadoop中的MapReduce。
那么,在分布式领域,具体有哪些问题适合采用分治法呢?要回答这个问题,我们先看下适合分治法的问题具有哪些特征吧。
问题规模比较大或复杂,且问题可以分解为几个规模较小的、简单的同类型问题进行求解;
子问题之间相互独立,不包含公共子问题;
子问题的解可以合并得到原问题的解。
根据这些特征,我们可以想到,诸如电商统计全国商品数量时,按区域或省市进行统计,然后将统计结果合并得到最终结果等大数据处理场景,均可以采用分治法。
同时,根据这些特征,我们可以推导出,采用分治法解决问题的核心步骤是:
分解原问题。将原问题分解为若干个规模较小,相互独立,且与原问题形式相同的子问题。
求解子问题。若子问题规模较小且容易被解决则直接求解,否则递归地求解各个子问题。
合并解,就是将各个子问题的解合并为原问题的解。
接下来,我们就一起看看分布式系统中分治法的原理和应用吧。
分治法的原理
分布式原本就是为处理大规模应用而生的,所以基于分布式系统,如何分而治之地处理海量数据就是分布式领域中的一个核心问题。
Google提出的MapReduce分布式计算模型Hadoop MapReduce是Google的开源实现作为分治法的典型代表最开始用于搜索领域后来被广泛用于解决各种海量数据的计算问题。下面我将以MapReduce为例带你了解分治法的抽象模型、工作原理和实践应用。
抽象模型
如下图所示MapReduce分为Map和Reduce两个核心阶段其中Map对应“分”即把复杂的任务分解为若干个“简单的任务”执行Reduce对应着“合”即对Map阶段的结果进行汇总。
在第一阶段也就是Map阶段将大数据计算任务拆分为多个子任务拆分后的子任务通常具有如下特征
相对于原始任务来说,划分后的子任务与原任务是同质的,比如原任务是统计全国人口数,拆分为统计省的人口数子任务时,都是统计人口数;并且,子任务的数据规模和计算规模会小很多。
多个子任务之间没有依赖,可以独立运行、并行计算,比如按照省统计人口数,统计河北省的人口数和统计湖南省的人口数之间没有依赖关系,可以独立、并行地统计。
第二阶段也就是Reduce阶段第一阶段拆分的子任务计算完成后汇总所有子任务的计算结果以得到最终结果。也就是汇总各个省统计的人口数得到全国的总人口数。
MapReduce工作原理
那么在MapReduce里各个组件是如何分工完成一个复杂任务的呢为了解答这个问题我先带你了解一下MapReduce的组件结构。
如上图所示MapReduce主要包括以下三种组件
Master也就是MRAppMaster该模块像一个大总管一样独掌大权负责分配任务协调任务的运行并为Mapper分配map()函数操作、为Reducer分配reduce()函数操作。
Mapper worker负责Map函数功能即负责执行子任务。
Reducer worker负责Reduce函数功能即负责汇总各个子任务的结果。
基于这三种组件MapReduce的工作流程如下所示
程序从User Program开始进入MapReduce操作流程。其中图中的“step1step2step6”表示操作步骤。
step1User Program将任务下发到MRAppMaster中。然后MRAppMaster执行任务拆分步骤把User Program下发的任务划分成M个子任务M是用户自定义的数值。假设MapReduce函数将任务划分成了5个其中Map作业有3个Reduce作业有2个集群内的MRAppMaster以及Worker节点都有任务的副本。
step2MRAppMaster分别为Mapper和Reducer分配相应的Map和Reduce作业。Map作业的数量就是划分后的子任务数量也就是3个Reduce作业是2个。
step3被分配了Map作业的Worker开始读取子任务的输入数据并从输入数据中抽取出键值对每一个键值对都作为参数传递给map()函数。
step4map()函数的输出结果存储在环形缓冲区kvBuffer中这些Map结果会被定期写入本地磁盘中被存储在R个不同的磁盘区。这里的R表示Reduce作业的数量也是由用户定义的。在这个案例中R=2。此外每个Map结果的存储位置都会上报给MRAppMaster。
step5MRAppMaster通知Reducer它负责的作业在哪一个分区Reducer远程读取相应的Map结果即中间键值对。当Reducer把它负责的所有中间键值对都读过来后首先根据键值对的key值对中间键值对进行排序将相同key值的键值对聚集在一起从而有利于Reducer对Map结果进行统计。
step6Reducer遍历排序后的中间键值对将具有相同key值的键值对合并并将统计结果作为输出文件存入负责的分区中。
从上述流程可以看出整个MapReduce的工作流程主要可以概括为5个阶段Input输入、Splitting拆分、Mapping映射、Reducing化简以及Final Result输出
所有MapReduce操作执行完毕后MRAppMaster将R个分区的输出文件结果返回给User Program用户可以根据实际需要进行操作。比如通常并不需要合并这R个输出文件而是将其作为输入交给另一个MapReduce程序处理。
MapReduce实践应用
通过上述的流程描述你大概已经知道MapReduce的工作流程了。接下来我和你分享一个电商统计用户消费记录的例子再帮你巩固一下MapReduce的功能吧。
需要注意的是,为了方便理解,我对下面用的数据做了一定的处理,并不完全是真实场景中的数据。
每隔一段时间,电商都会统计该时期平台的订单记录,从而分析用户的消费倾向。在不考虑国外消费记录的前提下,全国范围内的订单记录已经是一个很大规模的工程了。
在前面的文章中我也提到过,电商往往会在每个省份、多个城市分布式地部署多个服务器,用于管理某一地区的平台数据。因此,针对全国范围内的消费统计,可以拆分成对多个省份的消费统计,并再一次细化到统计每一个城市的消费记录。
为方便描述假设我们现在要统计苏锡常地区第二季度手机订单数量Top3的品牌。我们来看看具体的统计步骤吧。
任务拆分Splitting阶段。根据地理位置分别统计苏州、无锡、常州第二季度手机订单Top3品牌从而将大规模任务划分为3个子任务。
通过循环调用map()函数统计每个品牌手机的订单数量。其中key为手机品牌value为手机购买数量单位万台。如下图Mapping阶段所示为简化描述图中直接列出了统计结果
与前面讲到的计算流程不同的是Mapping阶段和Reducing阶段中间多了一步Shuffling操作。Shuffling阶段主要是读取Mapping阶段的结果并将不同的结果划分到不同的区。在大多数参考文档中Mapping和Reducing阶段的任务分别定义为映射以及归约。但是在映射之后要对映射后的结果进行排序整合然后才能执行归约操作因此往往将这一排序整合的操作单独放出来称之为Shuffling阶段。
Reducing阶段归并同一个品牌的购买次数。
得到苏锡常地区第二季度Top3品牌手机的购买记录。
由上述流程可以看出Map/Reduce作业和map()/reduce()函数是有区别的:
Map 阶段由一定数量的 Map 作业组成,这些 Map 作业是并发任务可以同时运行且操作重复。Map阶段的功能主要由map()函数实现。每个Map作业处理一个子任务比如一个城市的手机消费统计需要调用多次map()函数来处理(因为城市内不同的居民倾向于不同的手机)。
Reduce阶段执行的是汇总任务结果遍历Map阶段的结果从而返回一个综合结果。与Reduce阶段相关的是reduce()函数它的输入是一个键key和与之对应的一组数据values其功能是将具有相同key值的数据进行合并。Reduce作业处理一个分区的中间键值对期间要对每个不同的key值调用一次reduce()函数。在完成Map作业后每个分区中会存在多个临时文件而执行完Reduce操作后一个分区最终只有一个输出文件。
知识扩展Fork-Join计算模式是什么意思呢
MapReduce是一种分而治之的计算模式在分布式领域中除了典型的Hadoop的MapReduce(Google MapReduce的开源实现)还有Fork-Join。你知道Fork-join是什么吗
Fork-Join是Java等语言或库提供的原生多线程并行处理框架采用线程级的分而治之计算模式。它充分利用多核CPU的优势以递归的方式把一个任务拆分成多个“小任务”把多个“小任务”放到多个处理器上并行执行即Fork操作。当多个“小任务”执行完成之后再将这些执行结果合并起来即可得到原始任务的结果即Join操作。
虽然MapReduce是进程级的分而治之计算模式但与Fork-Join的核心思想是一致的。因此Fork-Join又被称为Java版的MapReduce框架。
MapReduce和Fork-Join之间有一个本质的区别
Fork-Join不能大规模扩展只适用于在单个Java虚拟机上运行多个小任务虽然运行在不同的处理器上但可以相互通信甚至一个线程可以“窃取”其他线程上的子任务。
MapReduce可以大规模扩展适用于大型计算机集群。通过MapReduce拆分后的任务可以跨多个计算机去执行且各个小任务之间不会相互通信。
总结
所谓分而治之,就是将一个复杂的、难以直接解决的大问题,分割成一些规模较小的、可以直接求解的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将子问题的解合并以后就是原问题的解。
分布式计算模型MapReduce就运用了分而治之的思想通过Map操作将大任务分成多个较小的任务去执行得到的多个结果再通过Reduce操作整合成一个完整的结果。所以今天我就以MapReduce为例与你讲述了分布式领域中分治法的模型、原理与应用。
最后,我将今天涉及的核心知识点梳理为了一张思维导图,以方便你理解与记忆。
分而治之的思想,是简单且实用的处理复杂问题的方法。所以无论是计算机领域还是其他研究领域亦或日常生活中,我们都可以用分治法去处理很多复杂庞大的问题,将大问题划分成多个小问题,化繁为简、化整为零。
其实,很多算法并不是凭空创造出来的,都是源于生活并服务于生活的。在日常工作学习中,我们对眼前的问题一筹莫展时,就可以将其化繁为简,从最简单的小问题出发,逐渐增加问题的规模,进而解决这个复杂的问题。同样的道理,我们也可以借鉴生活中的例子去解决专业问题。
思考题
MapReduce属于批量处理任务类型吗你能说说其中的原因吗
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,151 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
16 分布式计算模式之Stream一门背锅的艺术
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在上一篇文章中我与你介绍了分布式计算模式中的MapReduce模式。这种模式的核心思想是将大任务拆分成多个小任务针对这些小任务分别计算后再合并各小任务的结果以得到大任务的计算结果。
这种模式下任务运行完成之后整个任务进程就结束了属于短任务模式。但任务进程的启动和停止是一件很耗时的事儿因此MapReduce对处理实时性的任务就不太合适了。
实时性任务主要是针对流数据的处理对处理时延要求很高通常需要有常驻服务进程等待数据的随时到来随时处理以保证低时延。处理流数据任务的计算模式在分布式领域中叫作Stream。
今天我将针对流数据的处理展开分享和你一起打卡Stream这种计算模式。
什么是Stream
近年来由于网络监控、传感监测、AR/VR等实时性应用的兴起一类需要处理流数据的业务发展了起来。比如各种直播平台中我们需要处理直播产生的音视频数据流等。这种如流水般持续涌现且需要实时处理的数据我们称之为流数据。
总结来讲流数据的特征主要包括以下4点
数据如流水般持续、快速地到达;
海量数据规模数据量可达到TB级甚至PB级
对实时性要求高,随着时间流逝,数据的价值会大幅降低;
数据顺序无法保证,也就是说系统无法控制将要处理的数据元素的顺序。
在分布式领域中处理流数据的计算模式就是流计算也叫作Stream。这个名字是不是非常形象呢
流计算的职责是实时获取来自不同数据源的海量数据,进行实时分析处理,获得有价值的信息。
它是一个对实时性要求非常高的计算形式如果数据处理不及时很容易导致过时、没用的结果这时就需要对造成的后果进行“背锅”。从这个角度来说Stream可谓“一门背锅的艺术”。
类比于水流的持续不断且变幻莫测,流数据也是以大量、快速、时变的流形式持续在应用中产生,因此流计算一般用于处理数据密集型应用。
比如,百度、淘宝等大型网站中,每天都会产生大量的流数据,这些数据包括用户的搜索内容、用户的浏览记录等。实时采集用户数据,并通过流计算进行实时数据分析,可以了解每个时刻数据流的变化情况,甚至可以分析用户的实时浏览轨迹,从而进行个性化内容实时推荐,提高用户体验。
此外,我们常用的爱奇艺、腾讯等音视频平台,对电影、电视剧等数据的处理,也是采用了流计算模式。
那么,这种实时的流计算到底是如何运行的呢?接下来,我们就一起看看流计算的工作原理吧。
Stream工作原理
我在上一篇文章中与你介绍的MapReduce是一种批量计算的形式。这种模式下会先收集数据并将其缓存起来等到缓存写满时才开始处理数据。因此批量计算的一个缺点就是从数据采集到得到计算结果之间经历的时间很长。
而流计算强调的是实时性数据一旦产生就会被立即处理当一条数据被处理完成后会序列化存储到缓存中然后立刻通过网络传输到下一个节点由下一个节点继续处理而不是像MapReduce那样等到缓存写满才开始处理、传输。为了保证数据的实时性在流计算中不会存储任何数据就像水流一样滚滚向前。
所以说,流计算属于持续性、低时延、事件驱动型的计算作业。
从这些分析中可以看出使用流计算进行数据处理一般包括3个步骤如下图所示。
第一步,提交流式计算作业。流式计算作业是一种常驻计算服务,比如实时交通监测服务、实时天气预报服务等。对于流式计算作业,首先必须预先定义计算逻辑,并提交到流计算系统中,使得流计算系统知道自己该如何处理数据。
系统在整个运行期间,由于收集的是同一类型的数据、执行的是同一种服务,因此流式计算作业的处理逻辑不可更改。如果用户停止当前作业运行后再次提交作业,由于流计算不提供数据存储服务,因此之前已经计算完成的数据无法重新再次计算。
第二步,加载流式数据进行流计算。流式计算作业一旦启动将一直处于等待事件触发的状态,一旦有小批量数据进入流式数据存储,系统会立刻执行计算逻辑并迅速得到结果。
从上图中我们可以看出,在流计算系统中,有多个流处理节点,流处理节点会对数据进行预定义的处理操作,并在处理完后按照某种规则转发给后续节点继续处理。此外,流计算系统中还存在管理节点,主要负责管理处理节点以及数据的流动规则。其中,处理节点的个数以及数据转发的规则,都在第一步作业提交时定义。
第三步,持续输出计算结果。流式计算作业在得到小批量数据的计算结果后,可以立刻将结果数据写入在线/批量系统,无需等待整体数据的计算结果,以进一步做到实时计算结果的实时展现。
到这里,我们小结一下吧。流计算不提供流式数据的存储服务,数据是持续流动的,在计算完成后就会立刻丢弃。流计算适用于需要处理持续到达的流数据、对数据处理有较高实时性要求的场景。为了及时处理流数据,流计算框架必须是低延迟、可扩展、高可靠的。
流计算的应用场景有很多比如它是网络监控、传感监测、AR/VR、音视频流等实时应用的发展的基础。所以目前流计算相关的框架和平台也有很多了主流的划分方式是将其分为如下3类
商业级的流计算平台比如IBM的InfoSphere Streams和TIBCO的StreamBase。InfoSphere Streams支持同时分析多种数据类型并实时执行复杂计算。StreamBase是一个用于实时分析的软件可以快速构建分析系统即时做出决策。StreamBase可以为投资银行、对冲基金、政府机构等提供实时数据分析服务。
开源流计算框架典型代表是Apache Storm由Twitter开源和S4由Yahoo开源。Storm是一个分布式的、容错的实时计算系统可以持续进行实时数据流处理也可以用于分布式RPC。S4是一个通用的、分区容错的、可扩展的、可插拔的分布式流式系统。这些开源的分布式流计算系统由于具备开源代码因此比较适合开发人员将其搭建在自身业务系统中。
各大公司根据自身业务特点而开发的流计算框架比如Facebook的Puma、百度的Dstream旨在处理有向无环的数据流、淘宝的银河流数据处理平台一个通用的、低延迟、高吞吐、可复用的流数据实时计算系统
除了这些框架外我们还会经常听到Spark、Flink等。Spark和Flink与Storm框架的不同之处在于Spark和Flink除了支持流计算还支持批量计算因此我没有直接将它们列入上述的流计算框架中。如果你的业务中需要用到或者需要参考某种计算框架或者平台的话可以再参考其官方文档或者相关的技术文章。
接下来我就以Storm这个开源的流计算框架为例通过介绍Storm的工作原理以加深你对流计算模式的进一步理解进而帮助你将其运用到实际业务中。
Storm的工作原理
说到Storm的工作原理我们先来对比下Storm与MapReduce的区别吧。Hadoop上运行的是“MapReduce作业”而Storm上运行的是“计算拓扑Topologies”。 “作业”和“拓扑”的一个关键区别是MapReduce 的一个作业在得到结果之后总会结束;而拓扑描述的是计算逻辑,该计算逻辑会永远在集群中运行(除非你杀死该进程)。
如下图所示Storm集群上有两种节点即主节点Master Node和工作节点Worker Nodes
Nimbus是整个Storm集群的主守护进程以唯一实例的方式运行在主节点上。它负责把任务分配和分发给集群的工作节点并监控这些任务的执行情况。当某个节点故障时它会重新分配该故障节点上的任务到其它节点。
Supervisor是Storm集群里工作守护进程每个工作节点都存在一个这样的实例。它通过Zookeeper与Nimbus守护进程进行通信。在接受到Nimbus分配的任务后它会为每个任务启动单独的工作进程。
前面我介绍了Nimbus是负责分发任务或代码的Supervisor是负责接收任务并启动和停止工作进程以执行任务的。那么Nimbus和Supervisors之间具体是怎么协同的呢下面我们一起看一下。
如果所有数据和信息均存储在Master Node上Master Node故障后会导致整个集群信息丢失因此引入了ZooKeeper集群来加强可靠性。为此Master Node与Worker Node之间的交互通过ZooKeeper完成由于Nimbus 和 Supervisors是Master Node和Worker Node之间负责交互的进程因此Nimbus和Supervisors之间的所有协调都是通过ZooKeeper集群完成的比如Nimbus会将任务的分配情况或信息发送给ZooKeeper集群然后Supervisors向ZooKeeper集群获取任务并启动工作进程以执行任务。
当Supervisor接收到分配的任务后会启动工作节点的工作进程(Worker)去执行任务。我们知道一个计算任务可以分成任务数据的读取以及任务执行两部分。Worker提供了两个组件Spout和Bolt分别进行数据读取和任务执行。
在详细介绍Worker组件之前我首先介绍一下Storm的核心抽象数据流。数据流是一个无界序列是在分布式环境中并行创建、处理的一组元组tuple。数据流可以由一种能够表述数据流中元组的域fields的模式来定义。
Storm为进行数据流转换提供了基本组件Spout和Bolt。 Spout和Bolt有用户自定义的接口用于运行特定应用程序的逻辑。如下图所示Storm上运行的计算拓扑其实是由一系列Spout 和 Bolt 组成的有向无环图,这个有向无环图代表了计算逻辑。
备注:
图中箭头,表示数据元组的传递方向。
此图引自“Twitter Analysis with Apache Storm”。
接下来我们看看Spout和Bolt的含义吧。
Spout用于接收源数据。通常情况下Spout 会从一个外部的数据源读取数据元组然后将它们发送到拓扑中。例如Spout从Twitter API读取推文并将其发布到拓扑中。
Bolt负责处理输入的数据流比如数据过滤filtering、函数处理functions、聚合aggregations、联结joins、数据库交互等。数据处理后可能输出新的流作为下一个Bolt的输入。每个Bolt往往只具备单一的计算逻辑。当我们执行简单的数据流转换时比如仅进行数据过滤则通常一个 Bolt 可以实现;而复杂的数据流转换通常需要使用多个 Bolt 并通过多个步骤完成,比如在神经网络中,对原始数据进行特征转换,需要经过数据过滤、清洗、聚类、正则化等操作。
知识扩展:流计算和批量计算的区别是什么?
MapReduce可以说是一种批量计算与我们今天介绍的用于实时数据处理的流计算是什么关系呢
虽然流计算和批量计算属于两种不同的计算模式,但并不是非此即彼的关系,只是适用于不同的计算场景。
在流计算中数据具有时效性因此在5G以及人工智能应用的驱动下专注于实时处理的流计算越来越得到广泛的关注。流计算的低延时、易扩展等性能非常适用于对时延要求高的终端应用比如直播中音视频的处理等从而极大提高用户的服务体验。而批量计算适用于对时延要求低的任务。
在实际运用中,可以根据计算要求,选择不同的计算模式。我将这两种计算模式的特点,总结为了一张表格,以帮助你理解、记忆,以及选择适合自己业务场景的计算模式。
总结
今天,我与你介绍了分布式计算模式中的流计算。流数据的价值会随时间的流逝而降低,“时间就是金钱”在流计算中体现得淋漓尽致。这就要求流计算框架必须是低延迟、可扩展、高可靠的。
在介绍流计算的工作原理时我首先通过一个流程图与你介绍了它的3个步骤即提交流式计算作业、加载流式数据进行流计算和持续输出计算结果。
然后我以流计算开源框架中的Storm为例与你讲述了Storm的核心组件以及通过Spout和Bolt构建有向无环图代表流计算逻辑以实现流计算以加深你对流计算原理的理解。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
思考题
离线计算和批量计算,实时计算和流式计算是等价的吗?你能和我说说你做出判断的原因吗?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,148 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
17 分布式计算模式之Actor一门甩锅的艺术
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
我在前两篇文章中带你一起学习了MapReduce和Stream计算模式相信你对批处理和流计算也有了一定的了解。虽然这两种计算模式对数据的处理方式不同但都是以特定数据类型分别对应静态数据和动态数据作为计算维度。
在接下来两篇文章中我将从计算过程或处理过程的维度与你介绍另外两种分布式计算模式即Actor和流水线。分布式计算的本质就是在分布式环境下多个进程协同完成一件复杂的事情但每个进程各司其职完成自己的工作后再交给其他进程去完成其他工作。当然对于没有依赖的工作进程间是可以并行执行的。
你是不是想说,分布式进程那么多,如果需要开发者自己去维护每个进程之间的数据、状态等信息,这个开发量可不是一般得大,而且特别容易出错。那么,有没有什么办法可以让开发者只关注自己的逻辑呢?
答案是肯定的Actor计算模式就能满足你的需求。也就是说你可以把数据、状态等都扔给Actor。这是不是“一门甩锅的艺术”呢
接下来我们就一起打卡分布式计算模式中的Actor模式。
什么是Actor
在第10篇文章“分布式体系结构之非集中式结构众生平等”中我曾提到Akka框架基于Actor模型提供了一个用于构建可扩展的、弹性的、快速响应的应用程序的平台。
其中Actor类似于一个“黑盒”对象封装了自己的状态和行为使得其他Actor无法直接观察到它的状态调用它的行为。多个Actor之间通过消息进行通信这种消息类似于电子邮箱中的邮件。Actor接收到消息之后才会根据消息去执行计算操作。
那么Actor模型又是什么呢Actor模型代表一种分布式并行计算模型。这种模型有自己的一套规则规定了Actor的内部计算逻辑以及多个Actor之间的通信规则。在Actor模型里每个Actor相当于系统中的一个组件都是基本的计算单元。
Actor模型的计算方式与传统面向对象编程模型Object-Oriented ProgrammingOOP类似一个对象接收到一个方法的调用请求类似于一个消息从而去执行该方法。
但是OOP因为数据封装在一个对象中不能被外部访问当多个外部对象通过方法调用方式即同步方式进行访问时会存在死锁、竞争等问题无法满足分布式系统的高并发性需求。而Actor模型通过消息通信采用的是异步方式克服了OOP的局限性适用于高并发的分布式系统。
举一个最简单的例子假如你现在定义了三个对象A、B和C对象C中有一个函数Function现在对象A和对象B同时调用对象C中的Function此时对象C中的Function就成为了我们在第3篇文章“分布式互斥有你没我有我没你”中提到的共享资源有可能会存在竞争、死锁等问题。
而对于Actor模式对象A、B和C对应着Actor A、Actor B和Actor C当Actor A和Actor B需要执行Actor C中的Function逻辑时Actor A和 Actor B会将消息发送给Actor C Actor C的消息队列存储着Actor A和 Actor B的消息然后根据消息的先后顺序执行Function即可。
也就是说Actor模式采用了异步模式并且每个Actor封装了自己的数据、方法等解决了OOP存在的死锁、竞争等问题。
Actor计算模式
接下来我们再一起看看Actor计算模式吧。如下图所示描述了具有3个Actor的Actor模型。
可以看到Actor模型的三要素是状态、行为和消息有一个很流行的等式Actor模型=(状态+行为)+ 消息。
接下来,我们一起看看这三要素的具体含义吧。
状态State。Actor的状态指的是Actor组件本身的信息相当于OOP对象中的属性。Actor的状态会受Actor自身行为的影响且只能被自己修改。
行为Behavior。Actor的行为指的是Actor的计算处理操作相当于OOP对象中的成员函数。Actor之间不能直接调用其他Actor的计算逻辑。Actor只有收到消息才会触发自身的计算行为。
消息Mail。Actor的消息以邮件形式在多个Actor之间通信传递每个Actor会有一个自己的邮箱MailBox用于接收来自其他Actor的消息因此Actor模型中的消息也称为邮件。一般情况下对于邮箱里面的消息Actor是按照消息达到的先后顺序FIFO进行读取和处理的。
了解了Actor的三要素后我们再一起看下Actor的工作原理吧。
Actor工作原理
为了方便你理解Actor的工作原理我会通过讲述3个Actor之间基于消息和消息队列的工作流程进行说明。这3个Actor的工作流程如下所示。
Actor1和Actor3先后向Actor2发送消息消息被依次放入Actor2的MailBox队列的队尾;
Actor2从MailBox队列的队首依次取出消息执行相应的操作由于Actor1先把消息发送给Actor2因此Actor2先处理Actor1的消息
Actor2处理完Actor1的消息后更新内部状态并且向其他Actor发送消息然后处理Actor3发送的消息。
了解了Actor之间的消息交互和处理流程我再以一个具体案例和你详细解读一下Actor之间的消息传递过程吧。
我们已经知道,在系统中,不同的组件/模块可以视为不同的Actor。现在有一个执行神经网络的应用其中有两个组件A和B分别表示数据处理模块和模型训练模块。假设我们可以将组件A和B看作两个Actor训练过程中的数据可以通过消息进行传递。如上图所示完整的消息传输过程为
组件A创建一个Actor System用来创建并管理多个Actor。
组件A产生QuoteRequest消息即mail消息比如数据处理后的数据并将其发送给ActorRef。ActorRef是Actor System创建的组件B对应Actor的一个代理。
ActorRef 将消息经过数据处理后的数据传输给Message Dispatcher模块。Message Dispatcher类似于快递的中转站负责接收和转发消息。
Message Dispatcher将消息数据处理后的数据加入组件B的MailBox队列的队尾。
Message Dispatcher将MailBox加入线程。需要注意的是只有当MailBox是线程时才能处理MailBox中的消息。
组件B的MailBox将队首消息数据取出并删除队首消息交给组件B处理进行模型训练。
Actor关键特征
通过上面的描述可以看出Actor的通信机制与日常的邮件通信非常类似。因此我们可以进一步总结出Actor模型的一些特点
实现了更高级的抽象。我在前面提到过Actor与OOP对象类似封装了状态和行为。但是Actor之间是异步通信的多个Actor可以独立运行且不会被干扰解决了OOP存在的竞争问题。
非阻塞性。在Actor模型中Actor之间是异步通信的所以当一个Actor发送信息给另外一个Actor之后无需等待响应发送完信息之后可以在本地继续运行其他任务。也就是说Actor模型通过引入消息传递机制从而避免了阻塞。
无需使用锁。Actor从MailBox中一次只能读取一个消息也就是说Actor内部只能同时处理一个消息是一个天然的互斥锁所以无需额外对代码加锁。
并发度高。每个Actor只需处理本地MailBox的消息因此多个Actor可以并行地工作从而提高整个分布式系统的并行处理能力。
易扩展。每个Actor都可以创建多个Actor从而减轻单个Actor的工作负载。当本地Actor处理不过来的时候可以在远程节点上启动Actor然后转发消息过去。
虽然Actor模型有上述的诸多优点但它并不适用于分布式领域中所有的应用平台或计算框架。因为Actor模型还存在如下一些不足之处
Actor提供了模块和封装但缺少继承和分层这使得即使多个Actor之间有公共逻辑或代码部分都必须在每个Actor中重写这部分代码也就是说重用性小业务逻辑的改变会导致整体代码的重写。
Actor可以动态创建多个Actor使得整个Actor模型的行为不断变化因此在工程中不易实现Actor模型。此外增加Actor的同时也会增加系统开销。
Actor模型不适用于对消息处理顺序有严格要求的系统。因为在Actor模型中消息均为异步消息无法确定每个消息的执行顺序。虽然可以通过阻塞Actor去解决顺序问题但显然会严重影响Actor模型的任务处理效率。
尽管Actor模型在需要同步处理的应用等场景具有局限性但它在异步场景中应用还是比较广泛的。接下来我们就一起看看Actor目前都应用在哪些地方吧。
Actor模型的应用
Actor模型在1973年被提出已广泛应用在多种框架和语言中。可以说很多框架或语言支持Actor编程模型是为了给开发者提供一个通用的编程框架让用户可以聚焦到自己的业务逻辑上而不用像面向对象等编程模型那样需要关心死锁、竞争等问题。
那么到底有哪些框架或语言支持Actor编程模型呢接下来我就和你列举几个典型的框架或语言吧以方便你参考。
Erlang/OTP。Erlang是一种通用的、面向并发的编程语言使用Erlang编写分布式应用比较简单而OTP就是Erlang技术栈中的标准库。Actor模型在Erlang语言中得到广泛支持和应用其他语言的Actor逻辑实现在一定程度上都是参照了Erlang的模式。实现了Actor模型逻辑的Erlang/OTP可以用于构建一个开发和运行时环境从而实现分布式、实时的、高可用性的系统。
Akka。Akka是一个为Java和Scala构建高度并发、分布式和弹性的消息驱动应用程序的工具包。Akka框架基于Actor模型提供了一个用于构建可扩展的、弹性的、快速响应的应用程序的平台。通过使用Actors和Streams技术 Akka为用户提供了多个服务器使用户更有效地使用服务器资源并构建可扩展的系统。
Quasar (Java) 。Quasar是一个开源的JVM库极大地简化了高度并发软件的创建。Quasar在线程实现时参考了Actor模型采用异步编程逻辑从而为JVM提供了高性能、轻量级的线程可以用在Java和Kotlin编程语言中。
知识扩展Akka中Actor之间的通信可靠性是通过Akka集群来保证的那么Akka集群是如何检测节点故障的呢
在第10篇文章“分布式体系结构之非集中式结构众生平等”中我与你介绍了Akka 集群是一个去中心化的架构比如现在集群中有n个节点这n个节点之间的关系是对等的。节点之间采用心跳的方式判断该节点是否故障但未采用集中式架构中的心跳检测方法。
Akka 集群中的故障检测方法是集群中每个节点被k个节点通过心跳进行监控比如k = 3节点1被节点2、节点3和节点4通过心跳监控当节点2发现节点1心跳不可达时就会标记节点1为不可达unreachable并且将节点1为不可达的信息通过Gossip传递给集群中的其他节点这样集群中所有节点均可知道节点1不可达。
其中k个节点的选择方式是将集群中每个节点计算一个哈希值然后基于哈希值将所有节点组成一个哈希环比如从小到大的顺序最后根据哈希环针对每个节点逆时针或顺时针选择k个临近节点作为监控节点。
总结
接下来我们小结一下吧。今天我与你介绍了分布式计算中一门甩锅的计算模型即Actor模型。
首先我介绍了什么是Actor模型以及Actor模型的三要素包括状态、行为和消息。
其次我介绍了Actor的工作原理并通过实例介绍了Actor之间通过消息及消息队列进行异步通信的流程以便于你进一步理解Actor的工作原理。
最后我为你介绍了几个当前支持Actor编程模型的框架和语言以便于你在需要采用Actor模型编程时做一个参考。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
著名的Erlang并发编程语言以及Akka这一分布式计算框架都实现了Actor模型的计算逻辑。因此即使你在之前未曾接触过Actor模型学习了这篇文章后你也可以根据开源的Erlang或Akka项目去更深刻地理解Actor模型了加油
思考题
Actor是否可以采用阻塞方式去运行呢原因是什么呢
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,167 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
18 分布式计算模式之流水线:你方唱罢我登场
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
通过前面几篇文章我们一起学习了分布式计算模式中的MapReduce、Stream和Actor它们各显神通解决了很多实际问题。
但是,在现实生活中,经常还会出现这样的情况,前一个任务的结果是另外一个任务的输入。比如工厂生产一瓶饮料,首先需要往瓶子里装上饮料,待饮料装满后,再封口。如果装饮料和封口分别为子任务,那么前一个任务(装饮料)结束后才可以开始第二个任务(封口)。类似这样的作业,就是我们常说的流水线作业。
在分布式领域中解决类似具有依赖关系的流水线作业的计算模式叫作流水线计算模式。其实流水线计算模式是我们在第1篇文章中提到的数据并行计算的一种形式就是将一个任务拆分为多个步骤子任务然后多个这样的任务通过对步骤子任务的重叠执行以实现数据并行处理的场景。
这种流水线模式在计算机领域中最先用于CPU指令设计后来推广到机器学习领域进行数据处理、模型训练等。在流水线计算模式中由于前一个子任务执行后会扔给下一个子任务由下一个子任务去展现自己的能力因此可以形象地比喻为“你方唱罢我登场”。
接下来,我们就一起打卡分布式计算模式中的流水线模式吧。
什么是流水线模式?
其实,分布式领域的流水线计算模式,就是参考了工业生产中的流水作业模式,将一个任务分为多个步骤执行,使得不同任务可以并行执行。此外,你肯定还会想到计算机技术中的流水线计算吧。
计算机中的流水线Pipeline技术是一种将每条指令拆分为多个步骤多条指令的不同步骤重叠操作从而实现几条指令并行处理的技术。现代CPU指令采用了流水线设计将一条CPU指令分为取指IF、译码ID、执行EX、访存MEM、回写WB五级流水线来执行。
如下图所示,在第一条指令执行译码操作时,第二条指令就可以执行取指操作了,从而实现了多条指令的并行操作。
在分布式领域中,流水线计算模式也类似,它是将一个大任务拆分为多个步骤执行,不同的步骤可以采用不同的进程执行。这,使得不同任务可以并行执行,从而提高了系统效率。
以机器学习中的数据预处理为例假设现在有5个样本数据每个样本数据进行数据预处理的流程包括数据去重、数据缺失值处理、数据归一化3个步骤且需要按照顺序执行。也就是说数据预处理这个任务可拆分为数据去重—>数据缺失值处理—>数据归一化3个子任务。
如果现在有3个节点节点1执行数据去重节点2执行数据缺失值处理节点3执行数据归一化。那么节点1处理完样本1的数据将处理后的数据发送节点2后则节点1可以继续处理样本2的数据同时节点2处理样本1的数据以此类推就实现了多任务的并行执行。
接下来,我们再具体看看分布式领域中的流水线计算模式吧。
流水线计算模式
流水线计算模式的应用非常广泛在AI技术中也非常常见。对流水线计算模式的学习将有助于你学习AI技术因此我接下来会以机器学习为例为你介绍流水线计算模式。
当然流水线计算模式的原理是通用的也可以应用到其他领域比如通信领域中使用HTTP流水线传输、计算机图形学中的图流水线等。
随着神经网络、深度学习在全世界掀起了All in AI的热潮用于加速的GPU和TPU也被越来越多的人使用。虽然诸如GPU、TPU之类的加速器可以从根本上减少执行单个训练步骤所需的时间但为了达到最佳性能我们仍然需要高效的输入流水线机制。
比如在流水线模式中数据预处理与GPU/TPU进行模型训练可以重叠进行再比如第N个样本进行模型训练时第N+1个样本可以进行数据预处理也就是说在第N+1个样本进行预处理前已经将第N个样本处理后的数据提供给了模型训练进一步减少了整体的数据处理和模型训练时间。
Tensorflow是Google开源的一个分布式机器学习框架已被各大公司采用比如网易、eBay、Intel等公司。接下来我就以TensorFlow的输入流水线模式为例与你介绍流水线技术模式的原理并带你了解如何构建机器学习的流水线。
流水线计算模式的原理
TensorFlow运用了流水线模式对输入数据进行预处理因此称为输入流水线TensorFlow Training Input Pipelines。其数据输入流水线主要包含3个步骤
提取Extract。通过多种途径读取数据比如内存、本地的HDD或SSD、远程的HDFS、GCS等。数据的种类也有很多比如图像数据、文本数据、视频数据等。
转换Transform。使用 CPU处理器对输入的数据进行解析以及预处理操作包括混合重排shuffling、批处理batching, 以及一些特定的转换。比如图像解压缩和扩充、文本矢量化、视频时序采样等。
加载Load。将转换后的数据加载到执行机器学习模型的加速器设备上比如GPU 或 TPU。
由于输入流水线包含了提取、转换、加载3个步骤因此TensorFlow的数据输入流水线也称为ETL流水线。TensorFlow提供了一个官方API也就是tf.data利用简单、可重用的数据片段构建复杂的输入流水线。
没错在加速模型训练方面输入流水线是非常重要的一个模块。由上述流程可知要执行训练步骤首先需要提取并使用CPU转换数据然后将其提供给在加速器上运行的模型。
如果不引入流水线模型的话,当 CPU 正在预处理数据时加速器处于空闲状态。同样当GPU/TPU正在训练模型时CPU 处于空闲状态。因此,训练的用时是 CPU 预处理时间和加速器训练时间的总和。
为了帮助你理解我们一起看下TensorFlow官网给出的一个示例吧。这个例子展示了一个不使用流水线技术和使用流水线技术时CPU、GPU/TPU的训练过程对比。
我们先看看不使用流水线技术的训练过程。如下图所示Prepare 1表示CPU正在对第1个样本数据进行预处理操作Train 1表示GPU/TPU正在训练第1个样本数据。
备注图片来源为www.tensorflow.org/guide。
图中的“idle”指的是空闲时间。可以看出如果不使用流水线CPU 和 GPU/TPU 运作的时间没有重叠,因此在大部分时间都可能处于空闲状态。
接下来我们再看看使用流水线技术的训练过程。流水线模型可以将训练步骤的数据预处理和数据训练过程重叠到一起。比如当GPU/TPU正在训练第 N 个样本数据时CPU 可以预处理第 N+1 个样本数据。这样做不仅可以最大限度地缩短训练的单步用时,还可以缩短提取和转换数据所需的时间,如下图所示:
图片来源www.tensorflow.org/guide
很明显采用流水线的设计可以充分利用CPU和GPU/TPU从而避免资源闲置加速训练过程。
到这里,我们来小结一下吧。
TensorFlow的输入流水线模式将对数据的操作拆分为提取、转换、加载3个不重叠的部分。当CPU对第N个样本的数据完成预处理之后会将预处理后的数据发送给GPU/TPU然后CPU继续对第N+1个样本的数据进行预处理同时GPU/TPU对第N个样本数据进行模型训练。也就是说这种计算模式实现了多样本数据处理和模型训练的并行执行。
可以看出,在模型训练中引入流水线模式,可以提高 CPU、GPU/TPU的利用率还可以加速训练过程。
实践: 构建机器学习流水线
前面提到在TensorFlow中流水线模式主要运用在数据读取阶段。那么对于一个复杂的机器学习任务是否也可以构建一套流水线作业呢
答案是肯定的。接下来,我们就一起看看,如何构建机器学习流水线。
一个典型的机器学习训练模型按照流水线计算模式拆分可以包括如下所示的5个步骤
数据输入,指的是从不同的数据源中导入数据。
数据转换,主要是要把输入的无结构数据转换成合适的格式,以便特征提取。
特征提取,指的是从数据集中提取特征数据。
模型训练,包括提供一个算法,并提供一些训练数据让模型可以学习。学习算法会从训练数据中发现模型,并生成输出模型。
模型验证,指的是通过训练得到的结果,对模型进行错误率验证。比如,图像分类中分类结果的验证,预测中的准确度验证,从而提高模型的准确性。
值得注意的是,在数据输入和数据转换之间,有时需要进行数据清洗。数据清洗主要是剔除错误数据和不重要的数据,从而降低模型训练的错误率。
接下来,我以图像分类为例,带你了解机器学习流水线的流程。关于图像分类的详细知识点,你可以自行查阅相关资料。
如下图所示假如现在有10000张小狗照片需要训练出一个关于小狗的预测模型。
假设这10000张照片中8000张作为训练集2000张作为测试集采用CNN进行模型训练。CNN包括输入层、卷积层、池化层、全连接层其中输入层为数据输入卷积层和池化层为特征提取全连接层是连接所有特征输出数据到分类器中以得到训练结果。
如上图所示生成小狗预测模型的流水线可以分为数据输入、数据转换、特征提取、模型训练、模型验证5部分。具体流程如下
输入数据也就是输入图像数据即8000张图片其中图像以像素表示。比如图片的大小是480_480那么图像输入数据格式可以是480_480*3的数组。3代表的是RGB的维度。
数据转换,也就是对输入的图像数据进行解析、正则化处理,消除一些噪声数据,得到格式化的数据。
特征提取,指的是得到格式化的数据之后,就可以对输入图像进行特征提取,通过卷积操作提取小狗的一些轮廓特征,比如耳朵、尾巴、身体等,然后通过池化层识别出主要特征,比如小狗的耳朵、眼睛、舌头等,对特征进行精简。
模型训练。在CNN中模型训练其实和特征提取是相辅相成的也就是特征提取后实现特征提取的那些参数就是模型参数而训练过程会根据梯度下降法等对参数进行调整以使得在模型验证阶段预测结果逼近真实结果。也就是说特征提取和模型训练这两步在CNN中是放到一起的这里我为了方便你理解才显式地把这两步划分了出来。
模型验证。将带有标签的测试数据集的图像2000张输入到小狗预测模型将预测结果与实际结果进行对比如果误差比较大则对模型参数进行优化并进入下一次迭代训练如果误差较小那么得到的结果就是最终的小狗预测模型。
知识扩展流水线模式和MapReduce模式中都有将大任务拆分为多个子任务两者的区别是什么
如题目所述流水线计算模式与分而治之的MapReduce计算模式你可以再回顾下第15篇文章中的相关知识点有相似之处都是将一个完整的、大的任务进行划分但它们划分的模式不一样
MapReduce以任务为粒度将大的任务划分成多个小任务每个任务都需要执行完整的、相同的步骤同一任务能被并行执行可以说是任务并行的一种计算模式
而流水线计算模式以步骤为粒度,一个任务拆分为多个步骤,每个步骤执行的是不同的逻辑,多个同类型任务通过步骤重叠以实现不同任务的并行计算,可说是数据并行的一种模式。
此外,它们的子任务(步骤)间的关系不同:
在MapReduce中各个子任务可以独立执行互不干扰多个子任务执行完后进行结果合并得到整个任务的结果因此要求子任务之间是没有依赖关系的
而在流水线模式中,多个子任务之间是具有依赖关系的,前一个子任务的输出是后一个子任务的输入。
所以综合来讲MapReduce计算模式适合任务并行的场景而流水线计算模式适合同类型任务数据并行处理的场景。
总结
首先,我与你介绍了什么是分布式计算模式中的流水线模式。它参考了工业生产中的流水作业模式,将一个任务分为多个步骤执行,不同任务之间的步骤可以重叠执行,这使得多个不同任务可以并行执行。
然后,我以典型的机器学习流程为例,介绍了机器学习流水线处理流程,以加深你对分布式流水线计算模型的理解。
最后我以CNN进行小狗分类模型训练为例通过讲述数据输入、数据处理、特征提取卷积、池化等操作、模型训练、模型验证等过程带你进一步理解了流水线计算模式在实际应用中的原理。
现在,我再通过一张思维导图来归纳一下今天的核心知识点吧。
流水线计算模式适合同类型任务,且每个任务可以拆分为多个步骤(子任务)进行执行的场景,通过重叠执行多个不同任务间的不同步骤实现数据并行。在实际应用场景中,有很多例子,最常见的就是机器学习。相信你在理解了本文的计算原理之后,一定可以将这种研究方法运用在你的工作中,加油!
思考题
流水线计算模式和流计算的区别是什么?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,229 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
19 分布式通信之远程调用:我是你的千里眼
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在前面三个模块中,我带你学习了分布式领域中的分布式协调与同步、分布式资源管理与负载调度,以及分布式计算技术,相信你对分布式技术已经有了一定的了解。
通过前面的学习,不知道你有没有发现分布式的本质就是多进程协作,共同完成任务。要协作,自然免不了通信。那么,多个进程之间是如何通信的呢?这也就是在“第四站:分布式通信技术”模块中,我将要为你讲解的问题。
话不多说,接下来我们就一起进入分布式通信的世界吧。今天,我首先带你打卡的是,分布式通信中的远程调用。
什么是远程调用?
首先,我通过一个例子,来让你对远程调用和本地调用有一个直观了解。
以电商购物平台为例每一笔交易都涉及订单系统、支付系统和库存系统假设三个系统分别部署在三台机器A、B、C中独立运行订单交易流程如下所示
用户下单时调用本地机器A的订单系统进行下单
下单完成后会远程调用机器B上的支付系统进行支付待支付完成后返回结果之后在本地更新订单状态
在本地远程调用机器C上的仓库系统出货出货完成后返回出货结果。
在整个过程中,“下单”和“订单状态更新”两个操作属于本地调用,而“支付”和“出货”这两个操作是通过本地的订单系统调用其他两个机器上的函数(方法)实现的,属于远程调用。
整个订单交易流程如下图所示。
通过这个例子,你应该对本地调用和远程调用有了一个初步的认识了。那到底什么是本地调用,什么是远程调用呢?
本地调用通常指的是,进程内函数之间的相互调用;而远程调用,是进程间函数的相互调用,是一种进程间通信模式。通过远程调用,一个进程可以看到其他进程的函数、方法等,这是不是与我们通常所说的“千里眼”有点类似呢?
在分布式领域中,一个系统由很多服务组成,不同的服务由各自的进程单独负责。因此,远程调用在分布式通信中尤为重要。
根据进程是否部署在同一台机器上,远程调用可以分为如下两类:
本地过程调用Local Procedure CallLPC是指同一台机器上运行的不同进程之间的互相通信即在多进程操作系统中运行的不同进程之间可以通过LPC进行函数调用。
远程过程调用Remote Procedure CallRPC是指不同机器上运行的进程之间的相互通信某一机器上运行的进程在不知道底层通信细节的情况下就像访问本地服务一样去调用远程机器上的服务。
在这两种远程调用中RPC中的不同进程是跨机器的适用于分布式场景。因此在今天这篇文章中我主要针对RPC进行详细讲解。接下来我再提到远程调用时主要指的就是RPC了。
远程调用的原理及应用
我们经常会听别人提起 B/S ( Browser/Server浏览器/服务器) 架构。在这种架构中被调用方服务器有一个开放的接口然后调用方用户通过Browser使用这个接口来间接调用被调用方相应的服务从而实现远程调用。
比如用户A在自己的电脑上通过浏览器查询北京今天的天气 浏览器会将用户查询请求通过远程调用方式调用远程服务器相应的服务,然后为用户返回北京今天的天气预报。
但是B/S架构是基于HTTP协议实现的每次调用接口时都需要先进行HTTP请求。这样既繁琐又浪费时间不适用于有低时延要求的大规模分布式系统所以远程调用的实现大多采用更底层的网络通信协议。
接下来我将为你介绍两种常用的远程调用机制远程过程调用RPC(Remote Procedure Call)和远程方法调用RMI(Remote Method Invocation)。
首先我们一起看一下RPC的原理和应用吧。
RPC的原理及应用
简单地说RPC就是像调用本机器上的函数或方法一样去执行远程机器上的函数或方法并返回结果。在整个过程中不感知底层具体的通信细节。
如下图所示我们以刚才电商购物平台例子中的“支付”操作为例来详细看看一次RPC调用的完整流程吧
本地服务器也就是机器A中的订单系统调用本地服务器上的支付系统中的支付操作Pay(Order)该方法会直接调用Client Stub其中Stub是用于转换RPC过程中在订单系统和支付系统所在机器之间传递的参数这是一次正常的本地调用。
Client Stub将方法Pay、参数Order等打包成一个适合网络传输的消息通过执行一次系统调用也就是调用操作系统中的函数来发送消息。
订单系统所在机器A的本地操作系统通过底层网络通信将打包好的消息根据支付系统所在机器B的地址发送出去。
机器B上的操作系统接收到消息后将消息传递给Server Stub。
机器B上的Server Stub将接收到的消息进行解包获得里面的参数然后调用本地的支付订单的操作Pay(Order)。
机器B上的支付操作Par(Order)完成后将结果发送给Server Stub其中结果可使用XDRExternal Data Representation一种可以在不同计算机系统间传输的数据格式语言表示。
机器B上的Server Stub将结果数据打包成适合网络传输的消息然后进行一次系统调用发送消息。
机器B的本地操作系统将打包好的消息通过网络发送回机器A。
机器A的操作系统接收到来自机器B的消息并将消息发送给本地的Client Stub。
本地的Client Stub对消息进行解包然后将解包得到的结果返回给本地的订单系统。
到此整个RPC过程结束。
从整个流程可以看出机器A上的Pay(Order)、 Client Stub 和网络调用之间的交互属于本地调用机器B上的Pay(Order)、Server Stub和网络调用之间的交互也属于本地调用。而机器A和机器B之间的远程调用的核心是发生在机器A上的网络调用和机器B上的网络调用。
RPC的目的其实就是要将第2到第8步的几个过程封装起来让用户看不到这些细节。从用户的角度看订单系统的进程只是做了一次普通的本地调用然后就得到了结果。
也就是说订单系统进程并不需要知道底层是如何传输的在用户眼里远程过程调用和调用一次本地服务没什么不同。这就是RPC的核心。
接下来我再带你一起看一下RPC与本地调用进程内函数调用的区别吧以加深你对RPC的理解。
你可以先想象一下,本地调用过程是怎样的。
简单来说,同一进程是共享内存空间的,用户可以通过{函数名+参数}直接进行函数调用。
而在RPC中由于不同进程内存空间无法共享且涉及网络传输所以不像本地调用那么简单。所以RPC与本地调用主要有三点不同。
第一个区别是调用ID和函数的映射。在本地调用中由于在进程内调用即使用的地址空间是同一个因此程序可直接通过函数名来调用函数。而函数名的本质就是一个函数指针可以看成函数在内存中的地址。比如调用函数f()编译器会帮我们找到函数f()相应的内存地址。但在RPC中由于不同进程的地址空间不一样因此单纯通过函数名去调用相应的服务是不行的。
所以在RPC中所有的函数必须要有一个调用ID来唯一标识。一个机器上运行的进程在做远程过程调用时必须附上这个调用ID。
另外我们还需要在通信的两台机器间分别维护一个函数与调用ID的映射表。两台机器维护的表中相同的函数对应的调用ID必须保持一致。
当一台机器A上运行的进程P需要远程调用时它就先查一下机器A维护的映射表找出对应的调用ID然后把它传到另一台机器B上机器B通过查看它维护的映射表从而确定进程P需要调用的函数然后执行对应的代码最后将执行结果返回到进程P。
第二个区别是序列化和反序列化。我们知道了调用方调用远程服务时需要向被调用方传输调用ID和对应的函数参数那调用方究竟是怎么把这些数据传给被调用方的呢
在本地调用中进程之间共享内存等因此我们只需要把参数压到栈里然后进程自己去栈里读取就行。但是在RPC中两个进程分布在不同的机器上使用的是不同机器的内存因此不可能通过内存来传递参数。
而网络协议传输的内容是二进制流,无法直接传输参数的类型,因此这就需要调用方把参数先转成一个二进制流,传到被调用方后,被调用方再把二进制流转换成自己能读取的格式。调用方将参数转换成二进制流,通常称作序列化。被调用方对二进制的转换通常叫作反序列化。
同理被调用方将结果返回给调用方也需要有序列化和反序列化的过程。也就是说RPC与本地调用相比参数的传递需要进行序列化和反序列化操作。
第三个区别是网络传输协议。序列化和反序列化解决了调用方和被调用方之间的数据传输格式问题但要想序列化后的数据能在网络中顺利传输还需要有相应的网络协议比如TCP、UDP等因此就需要有一个底层通信层。
调用方通过该通信层把调用ID和序列化后的参数传给被调用方被调用方同样需要该通信层将序列化后的调用结果返回到调用方。
也就是说只要调用方和被调用方可以互传数据就可以作为这个底层通信层。因此它所使用的网络协议可以有很多只要能完成网络传输即可。目前来看大部分RPC框架采用的是TCP协议。
说完RPC的核心原理下面我以一个具有代表性的RPC框架Apache Dubbo为例帮助你更加深入地了解RPC。
在讲解Dubbo之前你可以先想一下如果你是一个RPC框架的设计者你会如何设计呢
首先必须得有服务的提供方和调用方。如下图所示假设服务提供方14为调用方14提供服务每个调用方都可以任意访问服务提供方。
当服务提供方和服务调用方越来越多时服务调用关系会愈加复杂。假设服务提供方有n个 服务调用方有m个则调用关系可达n*m这会导致系统的通信量很大。此时你可能会想到为什么不使用一个服务注册中心来进行统一管理呢这样调用方只需要到服务注册中心去查找相应的地址即可。
这个想法很好,如下图所示,我们在服务调用方和服务提供方之间增加一个服务注册中心,这样调用方通过服务注册中心去访问提供方相应的服务,这个服务注册中心相当于服务调用方和提供方的中心枢纽。
这样是不是好多了呢?
Dubbo就是在引入服务注册中心的基础上又加入了监控中心组件用来监控服务的调用情况以方便进行服务治理实现了一个RPC框架。如下图所示Dubbo的架构主要包括4部分
服务提供方。服务提供方会向服务注册中心注册自己提供的服务。
服务注册中心。服务注册与发现中心,负责存储和管理服务提供方注册的服务信息和服务调用方订阅的服务类型等。
服务调用方。根据服务注册中心返回的服务所在的地址列表,通过远程调用访问远程服务。
监控中心。主要统计服务的调用次数和调用时间等信息,以方便进行服务管理或服务失败分析等。
可以看到Dubbo的大致工作流程如下
服务提供方将自身提供的服务注册到服务注册中心;
服务调用方需要向注册中心预订调用服务的提供方地址列表;
服务注册中心将服务对应的提供方地址列表返回给调用方;
服务调用方根据服务地址信息进行远程服务调用;
服务调用方和服务提供方定时向监控中心发送服务调用次数及调用时间等信息。
接下来我再带你学习另一个远程调用机制RMI。
RMI的原理及应用
RMI是一个用于实现RPC的Java API能够让本地Java虚拟机上运行的对象调用远程方法如同调用本地方法隐藏通信细节。
RMI可以说是RPC的一种具体形式其原理与RPC基本一致唯一不同的是RMI是基于对象的充分利用了面向对象的思想去实现整个过程其本质就是一种基于对象的RPC实现。
RMI的具体原理如下图所示
RMI的实现中客户端的订单系统中的Stub是客户端的一个辅助对象用于与服务端实现远程调用服务端的支付系统中Skeleton是服务端的一个辅助对象用于与客户端实现远程调用。
也就是说客户端订单系统的Pay(Order)调用本地Stub对象上的方法Stub对调用信息比如变量、方法名等进行打包然后通过网络发送给服务端的Skeleton对象Skeleton对象将收到的包进行解析并调用服务端Pay(Order)系统中的相应对象和方法进行计算,计算结果又会以类似的方式返回给客户端。
为此我们可以看出RMI与PRC最大的不同在于调用方式和返回结果的形式RMI通过对象作为远程接口来进行远程方法的调用返回的结果也是对象形式比如Java对象类型或者是基本数据类型等。
RMI的典型实现框架有EJBEnterprise JavaBean企业级JavaBean如果你需要深入了解这个框架的话可以参考其官方文档。
RPC与RMI对比分析
好了上面我带你学习了RPC和RMI接下来我通过一个表格来对比下它们的异同吧以方便你进一步理解与记忆。
知识扩展:远程过程调用存在同步和异步吗?
分布式领域中,我们经常会听到同步和异步这两个词,那么远程过程调用存在同步和异步吗?
答案是肯定的。
远程过程调用包括同步调用和异步调用两种,它们的含义分别是:
同步调用指的是调用方等待被调用方执行完成并返回结果。这就好比在现实生活中用户A让用户B完成一篇文章用户A就在那里等着一直等用户B将写好的文章交给用户A后才离开并对文章进行审核。
异步调用指的是调用方调用后不用等待被调用方执行结果即返回返回结果调用方可以通过回调通知等方式获取。这就好比在现实生活中用户A让用户B完成一篇文章用户A告知用户B后用户A离开去做其他事情当用户B完成文章后反馈给用户A用户A收到反馈后开始审核文章。
也就是说,同步调用和异步调用的区别是,是否等待被调用方执行完成并返回结果。
因此,同步调用通常适用于需要关注被调用方计算结果的场景,比如用户查询天气预报,调用方需要直接返回结果;异步调用通常适用于对响应效率要求高、但对结果正确性要求相对较低的场景,比如用户下发部署一个任务,但真正执行该任务需要进行资源匹配和调度、进程拉起等过程,时间比较长,如果用户进程阻塞在那里,会导致体验很差,这种情况下可以采用异步调用。
总结
今天,我主要与你分享了分布式通信中的远程调用。
我以电商购物平台为例首先让你对本地调用和远程调用有了一定的认识然后分析了两种常用的远程调用机制RPC和RMI并对两者进行了比较。除此之外我还介绍了Dubbo这个代表性的RPC框架。
接下来,我们再回顾下今天涉及的几个与远程调用相关的核心概念吧。
本地调用通常指的是同一台机器进程间函数的相互调用,而远程调用是指不同机器进程间函数的相互调用。
RPC是指调用方通过参数传递的方式调用远程服务并得到返回的结果。在整个过程中RPC会隐藏具体的通信细节使得调用方就像在调用本地函数或方法一样。
RMI可以说是一个用于实现RPC的Java API能够让本地Java虚拟机上运行的对象调用远程方法如同调用本地方法隐藏通信细节。
Dubbo是一个代表性的RPC框架服务提供方首先将自身提供的服务注册到注册中心调用方通过注册中心获取提供的相对应的服务地址列表然后选择其中一个地址去调用相应的服务。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
现在是不是觉得RPC没有之前那么神秘了呢如果你对RPC感兴趣的话Dubbo就是一个很棒的出发点。加油赶紧开启你的分布式通信之旅吧。
思考题
在Dubbo中引入了一个注册中心来存储服务提供方的地址列表若服务消费方每次调用时都去注册中心查询地址列表如果频繁查询会导致效率比较低你会如何解决这个问题呢
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,180 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
20 分布式通信之发布订阅:送货上门
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在上一篇文章中,我带你一起学习了分布式通信中的远程调用。远程调用的核心是在网络服务层封装了通信协议、序列化、传输等操作,让用户调用远程服务如同进行本地调用一样。
其实这种方式就是通过网络服务层的封装实现了不同机器上不同进程之间的直接通信因为是直接通信所以通过线程阻塞的方式实现同步调用比较容易因此通常被用于同步调用。比如机器1上的进程A调用机器2上的进程B进程A被挂起进程B开始执行当进程B将值返回给A时A继续执行。
虽然这种方式也可以用于异步通信,但因为进程之间是直接交互的,所以当进程比较多时,会导致进程维护通信的复杂度非常高,且一个进程通信接口改变,与其通信的进程都会受到影响。
随着业务和分布式计算规模的逐渐增大和复杂化,远程调用模型有点心有余力而不足了,为此出现了专门的异步通信模式,也就是消息发布订阅模式和消息队列模式。在接下来的两篇文章中,我将与你详细讲述这两种通信模式。
话不多说,今天,我就带你一起打卡分布式通信中的发布订阅模式吧。
什么是发布订阅?
其实,发布订阅的思想在我们的生活中随处可见。
比如学术届电子论文的订阅方式。通常各个会议方或出版社会将学术论文发布到论文网站或平台上比如ACM、知网等然后学生或老师向论文网站订阅自己感兴趣的论文比如分布式相关的、AI相关的等。
当会议方或出版社将论文发布到论文网站后,论文网站会根据订阅信息,将相应的论文推送给订阅者(比如通过邮件的方式)。这里的会议方或出版社就相当于生产者,负责发布论文,学生或老师就相当于消费者,而论文网站就相当于一个消息中心。
由此可以看出,发布订阅的三要素是生产者、消费者和消息中心,生产者负责产生数据放到消息中心,消费者向消息中心订阅自己感兴趣的消息,当发布者推送数据到消息中心后,消息中心根据消费者订阅情况将相关数据推送给对应的订阅者。这种将数据送到消费者手里的行为,是不是和我们现在常说的“送货上门”一样呢?
发布订阅的原理及应用
这个论文订阅的例子,充分体现了发布订阅的思想。接下来,我就与你进一步分析下发布订阅的原理吧。
发布订阅的基本工作原理
在分布式通信领域中消息系统一般有两种典型的模式。一种是点对点模式P2PPoint to Point另一种是发布订阅模式Pub/SubPublish/Subscribe。接下来我们就一起看看这两种模式以帮助你深入理解发布订阅模式的原理。
首先,我们一起看一下什么是点对点模式。
生产者将消息发送到消息中心,然后消费者从消息中心取出对应的消息进行消费。消息被消费后,消息中心不再存储该消息,因此其他消费者无法再消费该消息。也就是说,点对点模式虽然支持多个消费者,但一个消息只能被一个消费者消费,不允许重复消费。
这种模式就好比限定了每篇论文只能被一个用户消费比如现在有一篇分布式相关的论文这篇论文推送给学生A之后论文网站就必须将其删除或下架也就是说其他用户无法再获取或阅读该论文了。当然实际情况并不是这样的这里只是为了方便你理解我做了相应的假设。
接下来,我们看一下发布订阅模式。
生产者可以发送消息到消息中心而消息中心通常以主题Topic进行划分每条消息都会有相应的主题消息会被存储到自己所属的主题中订阅该主题的所有消费者均可获得该消息进行消费。
比如图中假设生产者1发布一个Topic相关数据或消息消费者13均订阅了该Topic消息则该消息会推送消费者13也就是说同一个消息被3个消费者消费了。
这种模式就好比不同的方向代表不同的主题比如分布式领域代表一个主题当会议方或出版社发布分布式相关的论文时该论文会被存储到论文网站的分布式主题下同时学生或老师也会根据自己感兴趣的主题进行订阅。如果学生A订阅了分布式主题那么当会议方或出版社发布分布式相关的论文后会议网站会将这些论文推送给学生A。
与点对点模式相比,发布订阅模式中一个消息可以被多个消费者进行消费,这也是和点对点模式的本质区别。
以上就是发布订阅中的两种典型模式了。
在分布式系统中通常会为多用户服务而多个用户通常会关注相同类型的消息因此发布订阅模式在分布式系统中非常常见。接下来我再结合经典的分布式发布订阅消息系统Kafka的发布订阅原理及工作机制来帮助你巩固对发布订阅的理解。
Kafka发布订阅原理及工作机制
Kafka是一种典型的发布订阅消息系统其系统架构也是包括生产者、消费者和消息中心三部分。
生产者Producer负责发布消息到消息中心比如电子论文的会议方或出版社
消费者Consumer向消息中心订阅自己感兴趣的消息获得数据后进行数据处理比如订阅电子论文的老师或学生
消息中心Broker负责存储生产者发布的消息和管理消费者订阅信息根据消费者订阅信息将消息推送给消费者比如论文网站。在Kafka中消息中心本质上就是一组服务器也可以说是Kafka集群。
Kafka的架构图如下所示
可以看到Kafka中除了Producer、Broker、Consumer之外还有一个ZooKeeper集群。Zookeeper集群用来协调和管理Broker和Consumer实现了Broker和Consumer的解耦并为系统提供可靠性保证。
ZooKeeper集群可以看作是一个提供了分布式服务协同能力的第三方组件Consumer和Broker启动时均会向ZooKeeper进行注册由ZooKeeper进行统一管理和协调。
ZooKeeper中会存储一些元数据信息比如对于Broker会存储主题对应哪些分区Partition每个分区的存储位置等对于Consumer会存储消费组Consumer Group中包含哪些Consumer每个Consumer会负责消费哪些分区等。
接下来,我们看看分区和消费组的原理和作用吧。
从上面的介绍可以看出Broker负责存储消息数据Consumer负责消费数据Consumer消费数据的能力会影响Broker数据存储是否溢出的问题。若Consumer消费太慢会导致Broker存储溢出Broker就会丢弃一部分消息。
因此Broker和Consumer是Kafka的核心。接下来我将带你进一步了解Kafka中Broker和Consumer的关键技术如下图所示
首先我们看一下Broker。
在Kafka中为了解决消息存储的负载均衡和系统可靠性问题所以引入了主题和分区的概念。其中主题是一个逻辑概念指的是消息类型或数据类型就好比电子论文案例所讲的分布式是一个主题。
而分区是针对主题而言的指的是一个主题的内容可以被划分成多个集合分布在不同的Broker上不同的Broker在不同的节点上。这里的集合就是分区其中同一个分区只属于一个Broker。
那么,分区有什么好处呢?
在我看来,分区的好处主要包括如下两点:
实现负载均衡避免单个Broker上的负载过高。比如Topic 0被分为Partiton-0、Partiton-1和Partiton-2三个分区分别分布在Broker 0、Broker 1和Broker 2上。这就使得Topic 0的消息可以分布在这3个分区中实现负载均衡。
实现消息的备份从而保证系统的高可靠。比如Topic 1包含两个分区Partiton-0、Partiton-1每个分区内容一致分别存储在Broker 0和Broker 1上借此实现了数据备份。
接下来我们再看看Consumer吧。
Kafka中的消费组指的是多个消费者的一个集合。一个消费组中的消费者共同消费主题消息并且主题中每个消息只可以由消费组中的某一个消费者进行消费。
引入消费组的目的是什么呢我们知道在消息过多的情况下单个消费者消费能力有限时会导致消费效率过低从而导致Broker存储溢出丢弃一部分消息。Kafka为了解决这个问题所以引入了消费组。
这样一来,我们对发布订阅的基本工作机制就比较清楚了。接下来,我们再结合电商购物平台的例子,来看看发布订阅技术的具体应用吧。
发布订阅实践应用
假设在电商购物平台(为了方便理解,我对电商购物平台做了一定的简化)中,用户首先在订单系统下单,下单后库存系统会进行出货,通知系统则负责通知用户,整个流程可以用发布订阅的模式进行,如下图所示:
订单系统对应发布订阅模式中的生产者,消息中心有个主题专门存放下单信息,每次用户下单后,订单系统会向该主题写入数据;
库存系统和通知系统对应发布订阅模式中的消费者,它们会向消息中心订阅下单信息相关的主题;
订单系统向消息中心发布订单信息后,库存系统和通知系统都会获取到相应的下单信息,然后进行各自后续的操作,即库存系统进行出货,通知系统通过短信或邮件等方式通知用户。
接下来,我们总结下发布订阅模式的关键特征吧。
实现了系统解耦,易于维护。生产者/发布者只负责消息的发布,不需要知道订阅者/消费者的数量,也不需要知道订阅者/消费者获取消息用来做什么,而订阅者/消费者也不需要知道什么时候生产者/发布者会发布消息。
所以,生产者/发布者和订阅者/消费者互相独立,进而实现了系统解耦,每个部分可以单独维护,减少了因为生产者和消费者的耦合引入的一些相互影响。比如,如果两者耦合在一起,当生产者逻辑更改需要修改代码时,消费者部分的代码也受影响,因此每个部分单独维护降低了维护的复杂度。
实现了异步执行,避免高负载。生产者/发布者发布消息到消息中心,当消息超过消息中心可以存储的容量后,消息中心会丢弃掉超出的消息,这样系统就不会因为消息数量多而导致系统故障。
知识扩展:观察者模式和发布订阅模式的区别是什么?
我们还经常会听到一个概念,叫作观察者模式,也会在分布式系统中都会经常用到。那么,观察者模式和发布订阅模式的区别到底是什么呢?
首先,我们看一下观察者模式。顾名思义,观察者模式下有观察者,那么就有被观察者,两者之间的关系是什么呢?
观察者负责监控被观察者的状态变更如果被观察者的状态发生了改变那么观察者根据状态的变更执行相关操作。举个例子现在进程A是被观察者进程B和进程C是观察者当进程B观察到进程A中变量X由3变为4时执行X+1的操作当进程C观察到进程A中变量X由3变为4时执行X-1的操作。也就是说观察者模式定义了被观察者与观察者的直接交互或通信关系。
接下来,我们看一下发布订阅模式。发布订阅模式中存在发布者、订阅者和消息中心,订阅者需要向消息中心指定自己对哪些数据感兴趣,发布者推送的数据放入消息中心后,消息中心根据订阅者订阅信息推送数据。也就是说,发布者和订阅者之间引入了消息中心,实现的是间接通信。
总结来讲,观察者模式采用了直接通信,观察者和被观察者通信时延会低一些,但它们的依赖关系比较强,不管是被观察者还是观察者逻辑或接口有更改,另外一个均会受影响。而发布者和订阅者模式采用间接通信,引入了消息中心,相对比较厚重,且通信时延相对会高一点,但实现了订阅者与发布者的解耦。
总结
我首先通过论文订阅的案例与你介绍了什么是发布订阅以及发布订阅的基本原理然后介绍了一个经典的分布式发布订阅消息系统Kafka最后以一个电商购物平台的案例描述了发布订阅模式的应用场景。
我再和你总结下今天的核心知识点吧。
发布订阅就是生产者产生消息发布到消息中心,消费者订阅自己感兴趣的消息,消息中心根据消费者的订阅情况将相关消息发给对应的消费者。
Kafka是一个经典的发布订阅消息系统采用多分区实现了消息备份、负载均衡并引入消费组提高了消费者的消费能力防止Broker因为存储资源不够丢弃消息的情况从而提高了Kafka系统的可靠性。
发布订阅模式可以使系统松耦合易于维护,也可异步执行解决高负载问题,适用于系统解耦、流量削峰等场景。
最后,我再通过一张思维导图梳理下今天的核心知识点,以帮助你理解与记忆。
发布订阅模式易于理解,与点对点模式很类似。不同的是,点对点模式中一个消息只能由一个消费者消费,而发布者订阅者模式中一个消息可以由多个消费者消费。
不同的通信模式适用于不同的分布式场景其中发布订阅模式适合具备多个生产者、多个消费者且异步处理的场景比如现在的视频App多个用户都可以通过同一款App看同一部电视剧当然这个电视剧可以是被不同的生产者发布。点对点模式由于其局限性一般适用于需要进行点对点通信的场景比如近场投屏等。
相信你通过本讲的学习后,可以针对不同的分布式场景选择合适的通信模式,加油!
思考题
发布订阅模式下,当发布者消息量很大时,单个订阅者的处理能力是有限的,那么能否实现订阅者负载均衡消费呢?又该如何实现呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,168 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
21 分布式通信之消息队列:货物自取
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在上一篇文章,我带你学习了分布式通信技术中的发布订阅。总结来说,发布订阅就是发布者产生数据到消息中心,订阅者订阅自己感兴趣的消息,消息中心根据订阅者的订阅情况,将相关消息或数据发送给对应的订阅者。所以,我将其思想,概括为“送货上门”。
在实际使用场景中,还有一种常用的通信方式,就是将消息或数据放到一个队列里,谁需要谁就去队列里面取。在分布式领域中,这种模式叫“消息队列”。与发布订阅相比,消息队列技术的核心思想可以概括为“货物自取”。
接下来,我们就一起打卡分布式通信技术中的消息队列吧。
什么是消息队列?
回想一下,在上一篇学术电子论文订阅的例子中,出版社或会议方将论文发布到论文网站(或平台)上,然后论文网站再将论文推送给订阅相关论文的老师或学生。这里的论文网站就是消息中心,负责根据订阅信息将论文送货上门,角色非常关键。
但其实,除了将论文送货上门外,我们还能想到另外一种模式,也就是出版社或会议方将论文发布到论文网站进行存储,老师或学生根据需要到论文网站按需购买文章。
这种思想,在分布式通信领域中称为消息队列模式,论文网站充当的就是消息队列的角色,也非常关键。接下来,我再通过一个具体的应用案例来帮助你更加深入地理解什么是消息队列吧。
比如,很多系统都提供了用户注册功能,注册完成后发送通知邮件。如下图所示,假设用户通过邮箱进行注册,填写完注册信息并点击提交后,系统的处理过程主要分为两步:
检查用户注册信息的合法性,如果合法则将注册信息写入数据库中,若不合法,直接返回,流程结束;
将用户注册信息写入数据库后,给用户发送通知邮件,以告知用户注册的相关信息,比如注册账号等信息。
假设系统将注册信息写入数据库需要花费400ms、给用户发送通知邮件需要花费600ms。
这时注册消息写入数据库和发送通知邮件这两个组件间是直接交互且是同步通信方式。那么从用户提交注册到收到响应需要等系统完成这两个步骤。也就是说如果不考虑通信延迟的话注册系统对用户的响应时间是1000ms即1s。
如下图所示,如果引入消息队列作为注册消息写入数据库和发送通知邮件这两个组件间的中间通信者,那么这两个组件就可以实现异步通信、异步执行。引入消息队列后,上述步骤可以分为三步:
检查用户注册信息的合法性,如果合法则将注册信息写入数据库中,若不合法则直接返回,流程结束;
注册消息写入消息数据库后,将消息写入消息队列的队尾;
发送通知邮件的组件去消息队列取出队首的消息,给用户发送通知邮件,告知用户注册的相关信息。
也就是说采用消息队列模式只需要第2步完成即可给用户返回响应。第3步发送通知邮件可以在返回响应之后执行。
用户的注册信息写入数据库之后,通过数据库的可靠性设计来保证用户注册信息不会丢失,也就是说发送通知邮件的组件一定可以获取到用户注册信息,即保证会给注册用户发送通知邮件。也就是说,消息队列的引入不会影响用户注册网站,但会提升用户响应效率。
通常情况下将消息写入消息队列的速度很快假设需要100ms。那么引入消息队列后发送通知邮件实现了异步读取系统响应时间缩短为500ms响应速度提升了一倍提升了用户体验。
讲完了用户注册这个例子,我们再来看消息队列的定义就比较容易理解了。
队列是一种具有先进先出特点的数据结构消息队列是基于队列实现的存储具有特定格式的消息数据比如定义一个包含消息类型、标志消息唯一性的ID、消息内容的一个结构体作为消息数据的特定格式。消息以特定格式放入这个队列的尾部后可以直接返回并不需要系统马上处理之后会有其他进程从队列头部开始读取消息按照消息放入的顺序逐一处理。
从上面的例子中,我们也可以看出引入消息队列的好处是,提高响应速度,以及实现组件间的解耦。
消息队列的原理
现在,我把消息队列的工作原理从用户注册这个例子中剥离出来,给你一个更加直接的解释吧。
消息队列工作原理
消息队列的核心结构如下图所示。与发布订阅模式类似消息队列模式也是包括3个核心部分
生产者。生产者会产生消息或数据,并将消息或数据插入到消息队列中。
消息队列。一种具有先进先出特点的数据结构,用于存储消息。
消费者。从消息队列中获取消息或数据,进行相关处理。
具体流程是,生产者将发送的消息插入消息队列,也就是入队,之后会有一个消费者从消息队列中逐次取出消息进行处理,完成出队。
了解了消息队列的工作原理接下来我以阿里开源的RocketMQ为例与你进一步介绍消息队列的原理、工作机制和实践应用。
RocketMQ消息队列原理及工作机制
首先我们看一下RocketMQ的架构图形成一个整体认知。
RokcetMQ共包括NameServer Cluster、Producer Cluster、Broker Cluster和Consumer Cluster共4部分。接下来我们一起看看每部分的具体功能吧。
NameServer Cluster指的是名字服务器集群。这个集群的功能与Kafka中引入的ZooKeeper类似提供分布式服务的协同和管理功能在RocketMQ中主要是管理Broker的信息包括有哪些Broker、Broker的地址和状态等以方便生产者获取Broker信息发布消息以及订阅者根据Broker信息获取消息。
Producer Cluster指的是生产者集群负责接收用户数据然后将数据发布到消息队列中心Broker Cluster。那么生产者按照集群的方式进行部署好处是什么呢在我看来好处可以概括为以下两点
一是多个Producer可以并发接收用户的输入数据提升业务处理效率
二是考虑到可靠性问题如果只有一个Producer接收用户输入数据当这个Producer故障后整个业务就无法运行了。
Consumer Cluster指的是消费者集群负责从Broker中获取消息进行消费。Consumer以集群方式进行部署的好处是提升消费者的消费能力以避免消息队列中心存储溢出消息被丢弃。
Broker Cluster指的是Broker集群负责存储Producer Cluster发布的数据以方便消费者进行消费。
Broker Cluster中的每个Broker都进行了主从设计即每个Broker分为Broker Master 和 Broker SlaveMaster 既可以写又可以读Slave 不可以写只可以读。每次Broker Master会把接收到的消息同步给Broker Slave以实现数据备份。一旦Broker Master崩溃了就可以切换到Broker Slave继续提供服务。这种设计的好处是提高了系统的可靠性。
可以看出Broker Cluster就是我们今天要讲的核心“消息队列中心”那么它到底是如何采用队列实现的呢接下来我们就一起看看Broker Cluster的实现方式吧。
如下图所示在Broker Cluster中消息的存储采用主题Topic+消息队列Queue的方式实现
与Kafka一样RocketMQ中的主题也是一个逻辑概念。一个主题可以分区分布在各个不同的Broker中每个Broker上只有该主题的部分数据。每个主题分区中队列的数量可以不同由用户在创建主题时指定。队列是资源分配的基本单元消息进行存储时会存放到相应主题的分区中。
上面我为你介绍了RocketMQ的关键组件。接下来我们再看看RocketMQ的工作流程如下图所示
首先启动NameServer然后启动Broker。Broker启动后会主动找NameServer建立连接并将自己的信息注册到NameServer上。注册完毕后Broker会周期性地给NameServer发送心跳包比如每隔1s发送一次以告知NameServer自己还活着心跳包里还可以包括Broker当前存储的数据信息也就是说Broker可以周期性地向NameServer更新自己的数据信息以保证NameServer上存储的数据是最新的。
创建主题并确定这个主题的数据放入哪些Broker中。
当Producer生产消息发送到主题时需要先到NameServer查询该主题存放在哪些Broker中获取到相关Broker信息后将消息发送给这些Broker进行存储。
Consumer要从主题消费消息也需要首先到NameServer查询一下该主题的消息存储在哪些Broker上然后去相应的Broker获取消息进行消费。
通过对RocketMQ的介绍相信你已经对消息队列有比较深刻的认识了。接下来我们再看看消息队列模式适用于什么场景吧。
消息队列模式,是根据消费者需求到消息队列获取数据消费的,消费者只需要知道消息队列地址即可,消息队列中心也无需提前知道消费者信息。也就是说,这种模式对消费者没有特别需求,因此比较适合消费者为临时用户的场景。
比如目前阿里内部将RocketMQ应用于购物交易、充值、消息推送等多个场景因为在这些场景下每个消费者不是常驻进程或服务几乎都是临时存在。此外滴滴、联想等公司也都有采用RocketMQ。
知识扩展:发布订阅和消息队列模式都支持系统解耦,两者是否一致呢?
概括地说,发布订阅和消息队列模式虽然都支持系统解耦,但它们在实现时采用的数据结构和方式并不相同。
首先,我们看一下它们实现解耦的数据结构。
发布订阅模式采用了消息中心消息队列模式采用了消息队列中心它们均用来存储生产者发布的数据并均有主题、Broker等概念
唯一不同之处是消息队列模式中采用了具有先进先出特征的队列结构进行存储而订阅发布采用了map或数组等方式存储。
然后,我们再看看它们实现解耦的方式。
消息队列模式中,生产者发布数据到消息队列中心,消息队列中心会存储数据,等待消费者按需获取数据。这样生产者就不需要和消费者进行直接通信了,实现了生产者和消费者的解耦。
而在发布订阅模式中,消费者需要提前向消息中心订阅自己感兴趣的数据,待生产者发布数据到消息中心后,消息中心根据订阅者订阅信息将数据主动推送给消费者,也实现了消费者和生产者的解耦。
对于消息队列模式,消息队列中心无需提前获取消费者信息,因此对消费者比较灵活,适合消费者为临时用户的场景;而发布订阅模式,需要消费者提前向消息中心订阅消息,也就是说消息中心需要提前获取消费者信息,比较适合消费者为长驻进程或服务的场景。
总结
今天,我主要与你分享的是分布式通信技术中的消息队列模式。
首先,我通过用户注册的案例,与你介绍了什么是消息队列模式,以及它的好处。其中,消息队列模式中的核心是以一种具有先进先出特点的队列结构来存储数据,实现组件间的解耦和异步执行。
然后我与你介绍了消息队列的基本原理并以RocketMQ为例对其架构、核心组件和工作原理做了更深入的讲解以帮助你进一步了解消息队列模型。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
加油,行动起来,试着将发布订阅和消息队列这两种通信模式用到你的业务场景中吧,相信你可以的。如果你需要进一步了解这两种通信模式对应的产品源码的话,相信你结合这两篇文章中讲述的原理,可以比较容易地开启你的源码之旅了。
思考题
消息队列模型中,消费者是主动去消息队列获取消息的,而消息队列需要保证多个消费者可以获取到消息,也就是说一个消费者获取消息后并不会删除该消息,那么如何保证同一个消息不被同一个消费者重复消费呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,127 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
22 答疑篇:分布式体系架构与分布式计算相关问题
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
到目前为止“分布式技术原理与算法解析”专栏已经更新21篇文章了我已经为你介绍了分布式技术四纵四横知识体系中的三横即“分布式资源管理”“分布式计算技术”和“分布式通信”以及四纵中的“分布分布式式协同”和“分布式调度”。
在这里,我首先要感谢你们坚持学习每一篇文章,以及对每一道思考题的积极思考与讨论,并且还在此基础上对类似问题进行了扩展。
比如,@1024@每天晒白牙@游弋云端@Jackey和@Dale等同学,对双主问题展开了激烈的讨论;再比如,@xj_zh@mt11912@小白啊@随心而至等同学对Master如何判断Slave是否存活的问题进行了讨论特别是@小白啊还专门查询了Kubernetes的方法,在留言区进行了回复。
这样的同学还有很多,我就不再一一点名了。今天,我就针对前面文章涉及的与思考题有关的留言,做一次进一步的梳理与分析,以帮助你夯实前面所学的知识点。
留言涉及的问题有很多,但我经过进一步地分析和总结后,发现大家特别感兴趣和有疑惑的思考题主要分为两类:
分布式体系架构中,如何判断节点存活的问题;
分布式计算技术中,离线计算、批量计算、实时计算和流式计算的区别。
今天,我主要就对这两类思考题进行一下分析和讲解。
分布式体系架构相关问题
在第9篇文章“分布式体系结构之集中式结构一人在上万人在下”中我给你留了一个思考题在集中式架构中Master 如何判断 Slave 是否存活呢?
首先我先和你说说Slave故障的两类情况一种是Slave进程退出另一种是Slave所在服务器宕机或重启了。你可能会说这两种情况的判断方法难道还不一致吗别着急且听我慢慢道来。
如下图所示假设Master节点与3个Slave节点相连。请注意我在图中Master与Slave之间画了两条线实线旁写的是TCP长连接虚线旁写的是心跳。因为Master与Slave之间的监控关系是固定的因此我用了两种机制协同来判断Slave是否存活。
其中TCP长连接就是针对Slave进程退出但是Slave所在服务器未故障的情况。这种方式是借助TCP长连接的工作原理进行判断的。TCP长连接中TCP会对对端的Socket进行检测当发现对端Socket不可用时比如不能发出探测包或探测包未收到响应会返回-1的状态表示连接断开。所以这种方式可以快速检测到Slave进程的退出。
对于Slave所在服务器故障的情况由于服务器宕机或重启那么系统环境等均不工作了这种情况TCP长连接也无法进行探测了也就是说TCP长连接方法在这种场景下无法判断节点是否故障。
对于这种场景现有的软件架构中基本都采用了心跳方式。其核心策略是Master按照周期性比如每隔1s的方式给Slave发送心跳包正常情况下Slave收到Master发送的心跳包后会立即回复一个心跳包告知Master自己还活着。当某个Slave比如Slave1所在服务器故障后由于Slave无法接收到Master的心跳包也就无法回复了。
因此Master也无法接收到这个Slave比如Slave1的回复信息。通常情况下系统会设置一个阈值一般设置为与心跳周期一致若超过这个阈值还未收到Slave节点的回复Master就会标记自己与该Slave心跳超时。
其中设置阈值的目的是解决Slave故障情况下Master一直收不到心跳信息而阻塞在那里等待心跳回复的问题。一般连续k次Master与Slave的心跳超时Master就会判断该Slave故障了。其中设置连续k次的目的是降低因为系统做垃圾回收或网络延迟导致误判的概率。
这里的k主要是根据业务场景进行设置的。如果k设置得太小容易导致故障误判率过高因为系统在做垃圾回收或系统进程正在占用资源时会阻塞心跳导致心跳包无法及时回复而超时从而被误判。如果k设置得太大会导致故障发现的时间过长因为故障发现时间=k*心跳发送周期。
接下来,我们继续延展下这个问题吧。
追问1非集中式架构中如何判断节点是否存活
集中式架构中采用了TCP连接和心跳协同判断节点是否存活那么非集中式架构中是否也是这样判断的呢
其实在非集中式架构与集中式架构中判断节点是否存活的原理有所不同。因为非集中式架构中节点之间是对等的没有Master与Slave之分。如果每个节点间都建立TCP长连接假设集群中有n个节点那么每个节点均需要与其他n-1个节点建长连接这将导致每个节点的资源占用都会非常多。因此非集中式架构是采用心跳的方式进行判断的。
这里你可能会问如果像集中式架构那样每个节点与其他n-1个节点都发送心跳的话整个集群中同一时间心跳消息为n*(n-1),消息量也特别大,甚至会导致网络风暴,应该怎么办。
其实与集中式架构中的心跳包不同非集中式架构中采用的心跳方式的核心思想是每个节点被b1≤b个节点监控以减少心跳信息量。
接下来我们以Akka的原理为例先来看看b的取值原则吧。
如果用户不设置b的值那么b默认取值的原则是若集群中节点总数n小于6b=n-1若n大于等于6b=5。
若用户设置b的值则b以用户设置的值为准。
接下来我们再看看Akka集群中具体是如何通过心跳方式判断节点是否存活的。
Akka中集群组建完成后每个节点拥有整个集群中的节点列表。
每个节点根据集群节点列表计算哈希值比如根据节点ID计算一个哈希值然后基于哈希值将所有节点组成一个哈希环比如从小到大的顺序如下图所示。由于每个节点上的计算方法一致因此虽然每个节点独立计算但每个节点上维护的哈希环是一致的。
根据哈希环针对每个节点逆时针或顺时针方向选择b图中设置b=2个临近节点作为监控节点比如图中Node 2和 Node3监控Node1Node 3和 Node4监控Node2以此类推。由于每个节点被b个节点监控反过来也可以说在这个环上每个节点监控b个节点因此具体的实现方式是每个节点按照逆时针或顺时针方向选择b个节点进行监控。
当某个节点发现自己监控的节点心跳超时时比如Node 2发现Node1心跳超时则标记该节点不可达Node2标记Node1不可达并将该信息通过Gossip协议传播给集群中的其他节点。
如果某个节点被标记为不可达之后比如Node1不可达若不将该节点踢出集群那么Node2和Node3仍然会给Node1发送心跳若后面Node2又发现Node1心跳可达时则重新将Node1更新为可达状态然后同步给集群中其他节点。
这里的判断心跳超时机制可采用集中式方法中的连续k次心跳超时的方法进行判断也可以通过历史心跳信息进行预测。具体的预测方法我将在第31篇文章“分布式高可用之故障恢复知错能改善莫大焉”中做进一步讲解。
追加2: 一个集群为什么会存在双主的场景呢?
上面我提到判断节点存活的方法主要是通过心跳的方式。如果是因为网络连接断开那么节点之间就会被误判为对方故障了。在主备场景下通常会出现双主的情况。这也就是第4篇文章“分布式选举国不可一日无君”的课后思考题答案了。
在主备场景下,正常情况下,主节点提供服务,备节点是对主节点的数据、状态进行备份,以确保主故障后备升主后业务可以正常运行。主备节点之间通常会通过心跳的方式进行检测,目的是监控主节点是否故障,若故障则备升主,保证业务运行。
想象一下,如果主备节点之间的网络连接断开了,那么主节点与备节点之间心跳均不可达,因此主节点会认为备节点故障,此时主节点会继续提供服务,而备节点会认为主节点故障,备升主。所以,集群中就出现了双主的场景。
好了,以上就是关于分布式体系结构中如何判断节点是否存活的相关问题了,相信你对这几个问题有了比较深刻的理解。接下来,我们再看看分布计算技术的相关问题吧。
分布计算技术相关问题
在分布式计算技术中,我们经常会听到离线计算、批量计算、实时计算和流式计算这四个概念,也常常会弄混。那么,离线计算和批量计算,实时计算和流式计算到底是什么呢?离线计算和批量计算、实时计算和流式计算分别是等价的吗?
接下来,就请你带着问题,随我一起进入下文。
首先我们来看一下离线计算。通常我们提到的离线计算主要的应用场景是对时延要求不敏感、计算量大、需要计算很长时间比如需要数天、数周甚至数月的场景比如大数据分析、复杂的AI模型训练比如神经网络等。
这种场景如果采用在线计算或实时计算的话,通常会存在数据量不够或大量计算影响正在运行的业务等问题,因此往往会采用离线计算的方式。
离线计算方式的核心思想是,先采集数据,并将这些数据存储起来,待数据达到一定量或规模时再进行计算,然后将计算结果(比如离线训练的模型)应用到实际业务场景中。
其次我们看一下批量计算。批量计算通常是指将原始数据集划分为多个数据子集然后每个任务负责处理一个数据子集多个任务并发执行以加快整个数据的处理。比如我在第15篇文章“分布式计算模式之MR: 一门同流合污的艺术”中讲MR计算模式时提到MapReduce中的Map其实就属于批量计算Map计算的结果会通过Reduce进行汇总。
接下来我们再看一下实时计算。实时计算其实是和离线计算相对应的离线计算对时延要求不敏感相反实时计算对时延的要求比较敏感。这种模式需要短时间执行完成并输出结果比如秒级、分钟级也就是说强调时效通常用于秒杀、抢购等场景。实时计算由于时延要求低因此计算量通常不大、数据量也不会太多所计算的数据往往是K、M级别的。
最后我们在看看流式计算。我在第16篇文章“分布式计算模式之Stream: 一门背锅的艺术”中,与你讲述了流式计算。流计算强调的是实时性,数据一旦产生就会被立即处理,当一条数据被处理完成后,会立刻通过网络传输到下一个节点,由下一个节点继续处理。这种模式通常用于商业场景中每天的报表统计、持续多天的促销活动效果分析等。
为了便于你理解与记忆,我将这四种计算模式的特点,汇总为了一张表格,如下所示。
通过对这四种计算模式的讲解,相信你已经发现了,离线计算和批量计算对任务执行的时延不是特别敏感,而实时计算和流式计算对任务执行的时延敏感。但,离线计算和实时计算是从计算时延的维度进行分类的,而批量计算和流式计算是从计算方式的维度进行分类的,因此我们不能将离线计算和批量计算直接等同,也不能将实时计算和流式计算直接等同。
总结
我把前面21篇文章中大家针对思考题的讨论和困惑筛选出了分布式系统架构中如何判断节点是否存活以及四种分布式计算模式的异同做了进一步展开梳理成了今天的这篇答疑文章。
如果还有哪些思考题或者留言问题,还没来得及扩展的话,你可以留言给我,后续我会再找机会进行解答。最后,我要和你说的是,和我一起打卡分布式核心技术,一起遇见更优秀的自己吧。
篇幅所限,留言区见。
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,210 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
23 CAP理论这顶帽子我不想要
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在开篇词中我将分布式计算划分为了四横四纵。而在前面的文章中我们已经一起学习了四横中的分布式计算、分布式通信和分布式资源池化三横的相关知识。比如在分布式计算中我们学习了分布计算模式包括MapReduce、Stream、Actor和流计算的原理和实际应用在分布式通信中我们学习了远程调用、订阅发布和消息队列模式的原理和应用在分布式资源池化中我们学习了分布式系统架构和分布式调度架构。
相信通过对这些内容的学习,你已经对分布式技术有比较深刻的了解了。分布式系统处理的关键对象是数据,前面这些文章也都是为数据处理服务的。那么,数据本身相关的分布式技术有哪些呢?这就是接下来的几讲,我要带你学习的四横中的最后一横“分布式数据存储与管理”的相关技术。
在正式介绍分布式数据存储技术之前我需要先带你了解一个基本理论也就是CAP理论。前面提到分布式系统处理的关键对象是数据而数据其实是与用户息息相关的。CAP理论指导分布式系统的设计以保证系统的可用性、数据一致性等特征。比如电商系统中保证用户可查询商品数据、保证不同地区访问不同服务器查询的数据是一致的等。
话不多说接下来我们就一起打卡CAP理论吧。
什么是CAP
如果你之前没有听说过CAP理论的话看到这三个字母第一反应或许是“帽子”吧。那么在分布式领域中CAP这顶“帽子”到底是什么呢我们先来看看这三个字母分别指的是什么吧。
接下来我结合电商的例子带你理解CAP的含义。
假设某电商,在北京、杭州、上海三个城市建立了仓库,同时建立了对应的服务器{A, B, C}用于存储商品信息。比如某电吹风在北京仓库有20个在杭州仓库有10个在上海仓库有30个。那么CAP这三个字母在这个例子中分别代表什么呢
首先我们来看一下C。C代表Consistency一致性是指所有节点在同一时刻的数据是相同的即更新操作执行结束并响应用户完成后所有节点存储的数据会保持相同。
在电商系统中A、B、C中存储的该电吹风的数量应该是20+10+30=60。假设现在有一个北京用户买走一个电吹风服务器A会更新数据为60-1=59与此同时要求B和C也更新为59以保证在同一时刻无论访问A、B、C中的哪个服务器得到的数据均是59。
然后看一下A。A代表Availability可用性是指系统提供的服务一直处于可用状态对于用户的请求可即时响应。
在电商系统中用户在任一时刻向A、B、C中的任一服务器发出请求时均可得到即时响应比如查询商品信息等。
最后我们看一下P。P代表Partition Tolerance分区容错性是指在分布式系统遇到网络分区的情况下仍然可以响应用户的请求。网络分区是指因为网络故障导致网络不连通不同节点分布在不同的子网络中各个子网络内网络正常。
在电商系统中假设C与A和B的网络都不通了A和B是相通的。也就是说形成了两个分区{A, B}和{C},在这种情况下,系统仍能响应用户请求。
一致性、可用性和分区容错性就是分布式系统的三个特征。那么我们平时说的CAP理论又是什么呢
CAP理论指的就是在分布式系统中C、A、P这三个特征不能同时满足只能满足其中两个如下图所示。这是不是有点像分布式系统在说这顶“帽子”我不想要呢
接下来我就通过一个例子和你进一步解释下什么是CAP以及CAP为什么不能同时满足吧。
如下图所示网络中有两台服务器Server1和Server2分别部署了数据库DB1和DB2这两台机器组成一个服务集群DB1和DB2两个数据库中的数据要保持一致共同为用户提供服务。用户User1可以向Server1发起查询数据的请求用户User2可以向服务器Server2发起查询数据的请求它们共同组成了一个分布式系统。
对这个系统来说分别满足C、A和P指的是
在满足一致性C的情况下Server1和Server2中的数据库始终保持一致即DB1和DB2内容要始终保持相同
在满足可用性A的情况下用户无论访问Server1还是Server2都会得到即时响应
在满足分区容错性P的情况下Server1和Server2之间即使出现网络故障也不会影响Server1和Server2分别处理用户的请求。
当用户发起请求时,收到请求的服务器会及时响应,并将用户更新的数据同步到另一台服务器,保证数据一致性。具体的工作流程,如下所示:
用户User1向服务器Server1发起请求将数据库DB1中的数据a由1改为2
系统会进行数据同步即图中的S操作将Server1中DB1的修改同步到服务器Server2中使得DB2中的数据a也被修改为2
当User2向Server2发起读取数据a的请求时会得到a最新的数据值2。
这其实是在网络环境稳定、系统无故障的情况下的工作流程。但在实际场景中,网络环境不可能百分之百不出故障,比如网络拥塞、网卡故障等,会导致网络故障或不通,从而导致节点之间无法通信,或者集群中节点被划分为多个分区,分区中的节点之间可通信,分区间不可通信。
这种由网络故障导致的集群分区情况,通常被称为“网络分区”。
在分布式系统中网络分区不可避免因此分区容错性P必须满足。接下来我们就来讨论一下在满足分区容错性P的情况下一致性C和可用性A是否可以同时满足。
假设Server1和Server2之间网络出现故障User1向Server1发送请求将数据库DB1中的数据a由1修改为2而Server2由于与Server1无法连接导致数据无法同步所以DB2中a依旧是1。这时User2向Server2发送读取数据a的请求时Server2无法给用户返回最新数据那么该如何处理呢
我们能想到的处理方式有如下两种。
第一种处理方式是保证一致性C牺牲可用性AServer2选择让User2的请求阻塞一直等到网络恢复正常Server1被修改的数据同步更新到Server2之后即DB2中数据a修改成最新值2后再给用户User2响应。
第二种处理方式是保证可用性A牺牲一致性CServer2选择将旧的数据a=1返回给用户等到网络恢复再进行数据同步。
除了以上这两种方案没有其他方案可以选择。可以看出在满足分区容错性P的前提下一致性C和可用性A只能选择一个无法同时满足。
CAP选择策略及应用
通过上面的分析你已经知道了分布式系统无法同时满足CAP这三个特性那该如何进行取舍呢
其实C、A和P没有谁优谁劣只是不同的分布式场景适合不同的策略。接下来我就以一些具体场景为例分别与你介绍保CA弃P、保CP弃A、保AP弃C这三种策略以帮助你面对不同的分布式场景时知道如何权衡这三个特征。
比如对于涉及钱的交易时数据的一致性至关重要因此保CP弃A应该是最佳选择。2015年发生的支付宝光纤被挖断的事件就导致支付宝就出现了不可用的情况。显然支付宝当时的处理策略就是保证了CP而牺牲了A。
而对于其他场景大多数情况下的做法是选择AP而牺牲C因为很多情况下不需要太强的一致性数据始终保持一致只要满足最终一致性即可。
最终一致性指的是不要求集群中节点数据每时每刻保持一致在可接受的时间内最终能达到一致就可以了。不知道你是否还记得在第6篇文章分布式事务中介绍的基于分布式消息的最终一致性方案没错这个方案对事务的处理就是选择AP而牺牲C的例子。
这个方案中,在应用节点之间引入了消息中间件,不同节点之间通过消息中间件进行交互,比如主应用节点要执行修改数据的事务,只需要将信息推送到消息中间件,即可执行本地的事务,而不需要备应用节点同意修改数据才能真正执行本地事务,备应用节点可以从消息中间件获取数据。
保CA弃P
首先我们看一下保CA弃P的策略。
在分布式系统中现在的网络基础设施无法做到始终保持稳定网络分区网络不连通难以避免。牺牲分区容错性P就相当于放弃使用分布式系统。因此在分布式系统中这种策略不需要过多讨论。
既然分布式系统不能采用这种策略那单点系统毫无疑问就需要满足CA特性了。比如关系型数据库 DBMS比如MySQL、Oracle部署在单台机器上因为不存在网络通信问题所以保证CA就可以了。
保CP弃A
如果一个分布式场景需要很强的数据一致性或者该场景可以容忍系统长时间无响应的情况下保CP弃A这个策略就比较适合。
一个保证CP而舍弃A的分布式系统一旦发生网络分区会导致数据无法同步情况就要牺牲系统的可用性降低用户体验直到节点数据达到一致后再响应用户。
我刚刚也提到了这种策略通常用在涉及金钱交易的分布式场景下因为它任何时候都不允许出现数据不一致的情况否则就会给用户造成损失。因此这种场景下必须保证CP。
保证CP的系统有很多典型的有Redis、HBase、ZooKeeper等。接下来我就以ZooKeeper为例带你了解它是如何保证CP的。
首先我们看一下ZooKeeper架构图。
备注此图引自ZooKeeper官网。
ZooKeeper集群包含多个节点Server这些节点会通过分布式选举算法选出一个Leader节点。在ZooKeeper中选举Leader节点采用的是ZAB算法你可以再回顾下第4篇文章中的相关内容。
在ZooKeeper集群中Leader节点之外的节点被称为Follower节点Leader节点会专门负责处理用户的写请求
当用户向节点发送写请求时如果请求的节点刚好是Leader那就直接处理该请求
如果请求的是Follower节点那该节点会将请求转给Leader然后Leader会先向所有的Follower发出一个Proposal等超过一半的节点同意后Leader才会提交这次写操作从而保证了数据的强一致性。
具体示意图如下所示:
当出现网络分区时如果其中一个分区的节点数大于集群总节点数的一半那么这个分区可以再选出一个Leader仍然对用户提供服务但在选出Leader之前不能正常为用户提供服务如果形成的分区中没有一个分区的节点数大于集群总节点数的一半那么系统不能正常为用户提供服务必须待网络恢复后才能正常提供服务。
这种设计方式保证了分区容错性,但牺牲了一定的系统可用性。
保AP弃C
如果一个分布式场景需要很高的可用性,或者说在网络状况不太好的情况下,该场景允许数据暂时不一致,那这种情况下就可以牺牲一定的一致性了。
网络分区出现后,各个节点之间数据无法马上同步,为了保证高可用,分布式系统需要即刻响应用户的请求。但,此时可能某些节点还没有拿到最新数据,只能将本地旧的数据返回给用户,从而导致数据不一致的情况。
适合保证AP放弃C的场景有很多。比如很多查询网站、电商系统中的商品查询等用户体验非常重要所以大多会保证系统的可用性而牺牲一定的数据一致性。
以电商购物系统为例如下图所示某电吹风在北京仓库有20个在杭州仓库有10个在上海仓库有30个。初始时北京、杭州、上海分别建立的服务器{A, B, C}存储该电吹风的数量均为60个。
假如上海的网络出现了问题与北京和杭州网络均不通此时北京的用户通过北京服务器A下单购买了一个电吹风电吹风数量减少到59并且同步给了杭州服务器B。也就是说现在用户的查询请求如果是提交到服务器A和B那么查询到的数量为59。但通过上海服务器C进行查询的结果却是60。
当然待网络恢复后服务器A和B的数据会同步到CC更新数据为59最终三台服务器数据保持一致用户刷新一下查询界面或重新提交一下查询就可以得到最新的数据。而对用户来说他们并不会感知到前后数据的差异到底是因为其他用户购买导致的还是因为网络故障导致数据不同步而产生的。
当然,你可能会说,为什么上海服务器不能等网络恢复后,再响应用户请求呢?可以想象一下,如果用户提交一个查询请求,需要等上几分钟、几小时才能得到反馈,那么用户早已离去了。
也就是说这种场景适合优先保证AP因为如果等到数据一致之后再给用户返回的话用户的响应太慢可能会造成严重的用户流失。
目前采用保AP弃C的系统也有很多比如CoachDB、Eureka、Cassandra、DynamoDB等。
对比分析
保CA弃P、保CP弃A和保AP弃C这三种策略以方便你记忆和理解。
知识扩展CAP和ACID的“C”“A”是一样的吗
首先我们看一下CAP中的C和ACID中的C是否一致。
CAP中的C强调的是数据的一致性也就是集群中节点之间通过复制技术保证每个节点上的数据在同一时刻是相同的。
ACID中的C强调的是事务执行前后数据的完整性保持一致或满足完整性约束。也就是不管在什么时候不管并发事务有多少事务在分布式系统中的状态始终保持一致。具体原理可参考第6篇文章“分布式事务All or Nothing”。
其次我们看一下CAP中的A和ACID中的A。
CAP中的A指的是可用性Availability也就是系统提供的服务一直处于可用状态即对于用户的请求可即时响应。
ACID中的A指的是原子性Atomicity强调的是事务要么执行成功要么执行失败。
因此CAP和ACID中的“C”和“A”是不一样的不能混为一谈。
总结
今天我主要与你分享的是CAP理论。
首先我通过电商的例子带你了解了CAP这三个字母在分布式系统中的含义以及CAP理论并与你证明了C、A和P在分布式系统中最多只能满足两个。
然后我为你介绍了分布式系统设计时如何选择CAP策略包括保CA弃P、保CP弃A、保AP弃C以及这三种策略适用的场景。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
相信通过今天的学习你不仅对CAP理论有了更深刻的认识并且可以针对不同场景采用哪种策略给出自己的建议。加油行动起来为你的业务场景选择一种合适的策略来指导分布式系统的设计吧。相信你一定可以的
思考题
CAP理论和BASE理论的区别是什么
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,165 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
24 分布式数据存储系统之三要素:顾客、导购与货架
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在上一篇文章中我们一起学习了CAP理论。该理论指出在分布式系统中不能同时满足一致性、可用性和分区容错性指导了分布式数据存储系统的设计。
随着数据量和访问量的增加,单机性能已经不能满足用户需求,分布式集群存储成为一种常用方式。把数据分布在多台存储节点上,可以为大规模应用提供大容量、高性能、高可用、高扩展的存储服务。而,分布式存储系统就是其具体实现。
在今天这篇文章,我将带你学习分布式存储系统的关键三要素,让你对分布式数据存储系统有一个直观的理解。在后面几篇文章中,我会针对这三要素中的关键技术进一步展开,以帮助你更深入地理解分布式数据存储系统。
接下来,我们就一起打卡分布式存储系统的三要素:顾客、导购与货架。
什么是分布式数据存储系统?
分布式存储系统的核心逻辑,就是将用户需要存储的数据根据某种规则存储到不同的机器上,当用户想要获取指定数据时,再按照规则到存储数据的机器里获取。
如下图所示当用户即应用程序想要访问数据D分布式操作引擎通过一些映射方式比如Hash、一致性Hash、数据范围分类等将用户引导至数据D所属的存储节点获取数据。
静下心来想一下,获取数据的整个过程与你到商店购物的过程是不是有些类似呢?
顾客到商店购物时,导购会根据顾客想要购买的商品引导顾客到相应的货架,然后顾客从这个货架上获取要购买的商品,完成购物。这里的顾客就是图中的应用程序,导购就相当于分布式操作引擎,它会按照一定的规则找到相应的货架,货架就是存储数据的不同机器节点。
其实,这个过程就是分布式存储系统中获取数据的通用流程,顾客、导购和货架组成了分布式存储系统的三要素,分别对应着分布式领域中的数据生产者/消费者、数据索引和数据存储。
接下来,我们就详细看看这三个要素吧。
分布式数据存储系统三要素
顾客就是数据的生产者和消费者,也就是说顾客代表两类角色,生产者会生产数据(比如,商店购物例子中的供货商就属于生产类顾客),将数据存储到分布式数据存储系统中,消费者是从分布式数据存储系统中获取数据进行消费(比如,商店购物例子中购买商品的用户就属于消费类顾客);导购就是数据索引,将访问数据的请求转发到数据所在的存储节点;货架就是存储设备,用于存储数据。
顾客:生产和消费数据
顾客相当于分布式存储系统中的应用程序,而数据是应用程序的原动力。根据数据的产生和使用,顾客分为生产者和消费者两种类型。生产者负责给存储系统添加数据,而消费者则可以使用系统中存储的数据。
就像是火车票存储系统,如图所示,铁路局就相当于生产者类型的顾客,而乘客就相当于消费者类型的顾客。铁路局将各个线路的火车票信息发布到订票网站的后台数据库中,乘客通过订票网站访问数据库,来进行查询余票、订票、退票等操作。
生产者和消费者生产和消费的数据通常是多种多样的,不同应用场景中数据的类型、格式等都不一样。根据数据的特征,这些不同的数据通常被划分为三类:结构化数据、半结构化数据和非结构化数据。
结构化数据通常是指关系模型数据,其特征是数据关联较大、格式固定。火车票信息比如起点站、终点站、车次、票价等,就是一种结构化数据。结构化数据具有格式固定的特征,因此一般采用分布式关系数据库进行存储和查询。
半结构化数据通常是指非关系模型的有基本固定结构模式的数据其特征是数据之间关系比较简单。比如HTML文档使用标签书写内容。半结构化数据大多可以采用键值对形式来表示比如HTML文档可以将标签设置为key标签对应的内容可以设置为value因此一般采用分布式键值系统进行存储和使用。
非结构化数据是指没有固定模式的数据其特征是数据之间关联不大。比如文本数据就是一种非结构化数据。这种数据可以存储到文档中通过ElasticSearch一个分布式全文搜索引擎等进行检索。
导购:确定数据位置
导购是分布式存储系统必不可少的要素,如果没有导购, 顾客就需要逐个货架去寻找自己想要的商品。
想象一下,如果你去订票网站订火车票,按照自己的需求点击查询车票后,系统会逐个扫描分布式存储系统中每台机器的数据,寻找你想要购买的火车票。如果系统中存储的数据不多,响应时间也不会太长,毕竟计算机的速度还是很快的;但如果数据分布在几千台甚至上万台机器中,系统逐个机器扫描后再给你响应,我相信你会对这个订票网站很失望。
这种定位数据存储位置的方式会浪费你很多时间,严重影响购票体验。因此,在分布式存储系统中,必须有相应的数据导购,否则系统响应会很慢,效率很低。为解决这个问题,数据分片技术就走入了分布式存储系统的大家庭。
数据分片技术,是指分布式存储系统按照一定的规则将数据存储到相对应的存储节点中,或者到相对应的存储节点中获取想要的数据,这是一种很常用的导购技术。这种技术,一方面可以降低单个存储节点的存储和访问压力;另一方面,可以通过规定好的规则快速找到数据所在的存储节点,从而大大降低搜索延迟,提高用户体验。
也就是说当铁路局发布各个线路的火车票信息时会按照一定规则存储到相应的机器中比如北京到上海的火车票存储到机器A中西安到重庆的火车票存储到机器B中。当乘客查询火车票时系统就可以根据查询条件迅速定位到相对应的存储机器然后将数据返回给用户响应时间就大大缩短了。如图所示当查询北京-上海的火车票相关信息时可以与机器A进行数据交互。
这个例子中按照数据起点、终点的方式划分数据,将数据分为几部分存储到不同的机器节点中,就是数据分片技术的一种。当查询数据时,系统可以根据查询条件迅速找到对应的存储节点,从而实现快速响应。
上述的例子中,按照数据特征进行了数据分片,当然,还有其他很多数据分片的方案。比如,按照数据范围,采用哈希映射、一致性哈希环等对数据划分。我会在下一篇文章中,与你详细讲述哈希和一致性哈希的内容。
接下来,我就针对数据范围这种数据分片方案做一个具体介绍吧。
针对数据范围的数据分片方案是指按照某种规则划分数据范围然后将在这个范围内的数据归属到一个集合中。这就好比数学中通常讲的整数区间比如11000的整数[1,100]的整数属于一个子集、[101,1000]的整数属于另一个子集。
对于前面讲的火车票的案例按照数据范围分片的话可以将属于某条线的所有火车票数据划分到一个子集或分区进行存储比如机器A存储京广线的火车票数据机器B存储京沪线的火车票数据。也就是说数据范围的方案是按照范围或区间进行存储或查询。
如图所示,当用户查询北京-上海的火车票相关信息时,首先判断查询条件属于哪个范围,由于北京-上海的火车线路属于京沪线因此系统按照规则将查询请求转到存取京沪线火车票数据的机器B然后由机器B进行处理并给用户返回响应结果。
为了提高分布式系统的可用性与可靠性,除了通过数据分片减少单个节点的压力外,数据复制也是一个非常重要的方法。数据复制就是将数据进行备份,以使得多个节点存储该数据。
想象一下当某个存储节点出现故障时如果只采用数据分片技术那这个节点的数据就会丢失从而给用户造成损失。因此数据复制在分布式存储系统中是不可或缺的。关于数据复制技术我会在第26篇文章中与你详细讲解。
接下来,我与你说说数据复制和数据分片技术的区别吧。关于它们之间的区别,你可以先看看下面这张图片:
数据A被拆分为两部分存储在两个节点Node1和Node2上属于数据分片而对数据B来说同一份完整的数据在两个节点中均有存储就属于数据复制。
在实际的分布式存储系统中,数据分片和数据复制通常是共存的:
数据通过分片方式存储到不同的节点上,以减少单节点的性能瓶颈问题;
而数据的存储通常用主备方式保证可靠性,也就是对每个节点上存储的分片数据,采用主备方式存储,以保证数据的可靠性。其中,主备节点上数据的一致,是通过数据复制技术实现的。
讲到这里我们再回忆下第20篇文章中涉及的Kafka集群的总体架构图吧。我从中抽取出Kafka集群消息存储架构图如下所示。
消息数据以Partition分区进行存储一个Topic主题可以由多个Partition进行存储Partition可以分布到多个Broker中同时Kafka还提供了Partition副本机制对分区存储的信息进行备份比如Broker 1中的Topic-1 Partion-0是对Broker 0上的Topic-1 Partition-0进行的备份从而保证了消息存储的可靠性。
这就是数据分片和数据复制共存的一个典型应用场景。
货架:存储数据
货架是用来存储数据的,因为数据是由顾客产生和消费的,因此货架存储的数据类型与顾客产生和消费的数据类型是一致的,即包括结构化数据、半结构化数据和非结构化数据。
针对这三种不同的数据类型,存储“货架”可以大致划分为以下三种:
分布式数据库通过表格来存储结构化数据方便查找。常用的分布式数据库有MySQL Sharding、Microsoft SQL Azure、Google Spanner、Alibaba OceanBase等。
分布式键值系统通过键值对来存储半结构化数据。常用的分布式键值系统有Redis、Memcache等可用作缓存系统。具体的缓存技术我将在第27篇文章“分布式数据之缓存技术身手钥钱随身带”中与你详细介绍。
分布式存储系统通过文件、块、对象等来存储非结构化数据。常见的分布式存储系统有Ceph、GFS、HDFS、Swift等。
而对货架材料也就是存储介质的选择,本质就是选择将数据存储在磁盘还是内存(缓存)上:
磁盘存储量大但IO开销大访问速度较低常用于存储不经常使用的数据。比如电商系统中排名比较靠后或购买量比较少、甚至无人购买的商品信息通常就存储在磁盘上。
内存容量小,访问速度快,因此常用于存储需要经常访问的数据。比如,电商系统中,购买量比较多或排名比较靠前的商品信息,通常就存储在内存中。
知识扩展:业界主流的分布式数据存储系统有哪些?
在前面介绍货架的时候,我有提到针对结构化数据、半结构化数据和非结构化数据,分别对应不同的“货架”,即分布式数据库、分布式键值系统和分布式文件系统进行存储。
对于分布式键值系统我会在第27篇文章中进行讲解并与你介绍和分析主流存储系统。
所以在这里,我就重点与你对比分析分布式数据库和分布式文件系统的几款主流的系统,以便于你理解和选型。
首先我们看一下主流的分布式数据库主要包括MySQL Sharding、SQL Azure、Spanner、OceanBase等具体对比分析如下表所示。
然后我们看一下主流的分布式存储系统主要包括Ceph、GFS、HDFS和Swift等具体对比分析如下所示。
总结
今天,我主要与你分享的是分布式数据存储系统的三要素,即顾客、导购和货架,对应到分布式领域的术语就是数据生产者/消费者、数据索引和数据存储。
其中,顾客包括产生数据的顾客和消费数据的顾客两类;导购,就是数据索引引擎,包括数据存储时确定数据位置,以及获取数据时确定数据所在位置;货架,负责数据存储,包括磁盘、缓存等存储介质等。
不同应用场景中,顾客产生的数据类型、格式等通常都不一样。根据数据的特征,这些不同的数据可以被划分为三类:结构化数据、半结构化数据和非结构化数据。与之相对应的,货架也就是数据存储系统,也包括三类:分布式数据库、分布式键值系统和分布式文件系统。
针对分布式数据库和分布式文件系统的主流框架我在“知识扩展模块”进行了对比分析以方便你理解、记忆与应用。而对于分布式键值系统我将在第27篇文章中进行详细介绍。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
相信通过今天的学习,你对分布式数据存储有了更深入的理解,对其中的核心角色和关键技术也有个更清晰的认识。加油,和我一起学习后面的章节,一起揭开分布式数据存储系统的神秘面纱吧!
思考题
传统单机关系型数据库与分布式数据库的区别是什么?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,201 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
25 数据分布方式之哈希与一致性哈希:“掐指一算”与“掐指两算”的事
你好!我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在上一篇文章中,我带你了解了分布式存储系统的三个要素:顾客、导购和货架。其中,导购实现了分布式数据存储系统中数据索引的功能,包括存储数据时确定存储位置,以及获取数据时确定数据所在位置。
那么,在分布式系统中,具体是如何实现数据索引或数据分布的呢?目前最常用的方法就是哈希和一致性哈希。
接下来,我们就一起打卡数据分布式方式中的哈希与一致性哈希吧。
首先,我们来看一下数据分布设计的原则。数据分布设计原则是分布式存储系统设计的基本原则,指导了哈希和一致性哈希方法的选择和应用。
数据分布设计原则
其实这里的数据分布主要就是数据分片。相信你还记得我在第24篇文章中与你分享分布式存储系统的导购时已经和你提到数据分片技术它解决了确定数据位置的问题并着重与你讲述了按照数据特征进行划分的分片方法。今天我主要与你讲解按照数据范围采用哈希、一致性哈希等对数据划分的方法。
假设现在有上百G数据需要进行分布式存储也就是要存储到不同的节点上。提到这个问题你可能立刻就会想到很多种方法比如随机分布、范围分布、映射分布等。那么我们应该如何选择到底要使用哪种方法呢
在分布式数据存储系统中,存储方案选型时,通常会考虑数据均匀、数据稳定和节点异构性这三个维度。
从数据均匀的维度考虑,主要包括两个方面:
不同存储节点中存储的数据要尽量均衡避免让某一个或某几个节点存储压力过大而其他节点却几乎没什么数据。比如现在有100G数据4个同类型节点通常希望数据存储时尽可能均衡比如每个节点存储25G数据。
另外用户访问也要做到均衡避免出现某一个或某几个节点的访问量很大但其他节点却无人问津的情况。比如现在有1000个请求对于上述存储数据的4个节点处理用户访问请求尽量均衡比如每个节点处理250个请求当然这是非常理想的情况实际情况下每个节点之间相差不太大即可。
从数据稳定的维度考虑,当存储节点出现故障需要移除或者扩增时,数据按照分布规则得到的结果应该尽量保持稳定,不要出现大范围的数据迁移。
比如现有100G数据刚开始有4个同类型节点节点1~4每个节点存储25G数据现在节点2故障了也就是说每个节点需要存储100G/3数据。
数据稳定就是尽可能只迁移节点2上的数据到其他节点上而不需要对大范围或所有数据进行迁移存储。当然如果有扩展同类型节点也是尽可能小范围迁移数据到扩展的节点上。具体的迁移方法可以采用下文介绍的一致性哈希方法。
从节点异构性的维度考虑,不同存储节点的硬件配置可能差别很大。比如,有的节点硬件配置很高,可以存储大量数据,也可以承受更多的请求;但,有的节点硬件配置就不怎么样,存储的数据量不能过多,用户访问也不能过多。
如果这种差别很大的节点,分到的数据量、用户访问量都差不多,本质就是一种不均衡。所以,一个好的数据分布算法应该考虑节点异构性。
当然除了上面这3个维度外我们一般还会考虑隔离故障域、性能稳定性等因素。
隔离故障域,是为了保证数据的可用和可靠性。比如,我们通常通过备份来实现数据的可靠性。但如果每个数据及它的备份,被分布到了同一块硬盘或节点上,就有点违背备份的初衷了。所以,一个好的数据分布算法,应该为每个数据映射一组存储节点,这些节点应该尽量在不同的故障域,比如不同机房、不同机架等。
性能稳定性是指,数据存储和查询的效率要有保证,不能因为节点的添加或者移除,造成存储或访问性能的严重下降。
了解了数据分布的设计原则后,接下来我们再看看主流的数据分布式方法,哈希和一致性哈希吧。其中,哈希和一致性哈希是数据分布的基础方法,在不同场景下,数据分布设计的原则需要考虑的维度也不一样。随着维度的增加,一致性哈希又可进一步演进为带有限负载的一致性哈希和带虚拟节点的一致性哈希方法。
接下来我们就一起看看这4种方法的具体原理和应用场景吧。
数据分布方法
哈希是指,将数据按照提前规定好的函数(哈希函数)映射到相应的存储节点,即进行一个哈希计算,得到的结果就是数据应该存储的节点。
一致性哈希同样是采用哈希函数,进行两步哈希:
对存储节点进行哈希计算,也就是对存储节点做哈希映射;
当对数据进行存储或访问时,首先对数据进行映射得到一个结果,然后找到比该结果大的第一个存储节点,就是该数据应该存储的地方。我会在下面的内容中,与你详细介绍其中的原理。
总结来讲,哈希是一步计算直接得到相应的存储节点,而一致性哈希需要两步才可以找到相应的存储节点。这,是不是就是“掐指一算”与“掐指两算”的事呢?
接下来,我们先一起看看哈希的具体原理吧。
哈希
哈希是一种非常常用的数据分布方法,其核心思想是,首先确定一个哈希函数,然后通过计算得到对应的存储节点。我们通过一个具体的例子来看一下吧。
假设有三个存储节点分别为Node1、Node2和Node3现有以下数据ID的范围为[0,1000]D0:{ id:100, name:a0}、D1:{ id:200, name:a1} 、D2:{ id:300, name:a2}、D3:{ id:400, name:a3}、D4:{ id:500, name:a4}、D5:{ id:600, name:a5}和D6:{ id:700, name:a6}。
假设哈希函数为“id%节点个数”通过计算可以得到每个数据应该存入的节点。在这个例子中哈希函数是“id%3”结果为0的数据存入Node1、结果为1的数据存入Node2、结果为2的数据存入Node3。
如图所示Node1将存储数据D2300%3=0和D5600%3=0Node2将存储数据D0100%3=1、D3400%3=1和D6700%3=1Node3将存储数据D1200%3=2和D4500%3=2
可以看出,哈希算法的一个优点是,只要哈希函数设置得当,可以很好地保证数据均匀性,但有一个较为严重的缺点,就是稳定性较差。
比如,随着数据量的增加,三个节点的容量无法再满足存储需求了,需要再添加一个节点。这时,哈希函数变成了 id%4原先存储在那三个节点的数据需要重新计算然后存入相应节点即需要大规模的数据迁移显然会降低稳定性。
所以哈希方法适用于同类型节点且节点数量比较固定的场景。目前Redis就使用了哈希方法你可以再回顾下第10篇文章“分布式体系结构之非集中式结构众生平等”中的相关内容。
接下来,我们再看看一致性哈希吧。
一致性哈希
一致性哈希是指将存储节点和数据都映射到一个首尾相连的哈希环上存储节点可以根据IP地址进行哈希数据通常通过顺时针方向寻找的方式来确定自己所属的存储节点即从数据映射在环上的位置开始顺时针方向找到的第一个存储节点。
我们看看如何用一致性哈希方法,来实现上述案例的数据存储吧。
如图所示假设数据D0D7按照ID进行等值映射即映射值与ID值相等比如数据D0映射到哈希环上的值为100数据D1映射到哈希环上的值为200······同时假设存储节点Node1、Node2和Node3映射到哈希环上的值分别为400、600、900。
按照规则D0D1D2和D3顺时针方向的下一个存储节点为Node1因此Node1将存储数据D0id = 100、D1id = 200、D2id = 300和D3id = 400同理Node2将存取数据D4id = 500和D5id = 600Node3将存取数据D6id = 700
可以看出,一致性哈希是对哈希方法的改进,在数据存储时采用哈希方式确定存储位置的基础上,又增加了一层哈希,也就是在数据存储前,对存储节点预先进行了哈希。
这种改进可以很好地解决哈希方法存在的稳定性问题。当节点加入或退出时仅影响该节点在哈希环上顺时针相邻的后继节点。比如当Node2发生故障需要移除时由于Node3是Node2顺时针方向的后继节点本应存储到Node2的数据就会存储到Node3中其他节点不会受到影响因此不会发生大规模的数据迁移。
所以一致性哈希方法比较适合同类型节点、节点规模会发生变化的场景。目前Cassandra就使用了一致性哈希方法你可以再回顾下第10篇文章“分布式体系结构之非集中式结构众生平等”中的相关内容。
一致性哈希方法虽然提升了稳定性,但随之而来的均匀性问题也比较明显,即对后继节点的负载会变大。有节点退出后,该节点的后继节点需要承担该节点的所有负载,如果后继节点承受不住,便会出现节点故障,导致后继节点的后继节点也面临同样的问题。
那么,有没有更好的方法来解决这个问题呢?
Google在2017年提出了带有限负载的一致性哈希算法就对这个问题做了一些优化。
带有限负载的一致性哈希
带有限负载的一致性哈希方法的核心原理是,给每个存储节点设置了一个存储上限值来控制存储节点添加或移除造成的数据不均匀。当数据按照一致性哈希算法找到相应的存储节点时,要先判断该存储节点是否达到了存储上限;如果已经达到了上限,则需要继续寻找该存储节点顺时针方向之后的节点进行存储。
我们看看如何用带有限负载的一致性哈希方法,来实现上述案例的数据存储吧。
如图所示假设每个存储节点设置的上限值为3按照一致性哈希算法当存储数据D3id = 400会发现应该存储到Node1中但Node1已经存储了三个数据D0id = 100、D1id = 200和D2id = 300达到了存储上限因此会存储到该节点顺时针方向的下一个节点Node2中。当然在存储前也会先检查Node2是否达到了存储上限如果达到了会继续寻找其他节点。
如果你想要了解该算法的详细内容可以阅读“Consistent Hashing with Bounded Loads”这篇论文。
带有限负载的一致性哈希方法比较适合同类型节点、节点规模会发生变化的场景。目前在Google Cloud Pub/Sub、HAProxy中已经实现该方法应用于Google、Vimeo等公司的负载均衡项目中。
其实,哈希、一致性哈希、带有限负载的一致性哈希,都没有考虑节点异构性的问题。如果存储节点的性能好坏不一,数据分布方案还按照这些方法的话,其实还是没做到数据的均匀分布。
接下来,我们再看一种主要针对存储节点为异构节点场景的方法,即带虚拟节点的一致性哈希吧。
带虚拟节点的一致性哈希
带虚拟节点的一致性哈希方法,核心思想是根据每个节点的性能,为每个节点划分不同数量的虚拟节点,并将这些虚拟节点映射到哈希环中,然后再按照一致性哈希算法进行数据映射和存储。
假设Node1性能最差Node2性能一般Node3性能最好。以Node1的性能作为参考基准Node2是Node1的2倍Node3是Node1的3倍。
因此Node1对应一个虚拟节点Node1_1Node2对应2个虚拟节点Node2_1和Node2_2Node3对应3个虚拟节点Node3_1、Node3_2和Node3_3。
假设虚拟节点Node1_1、Node2_1、Node2_2、Node3_1、Node3_2、Node3_3的哈希值分别为100、200、300、400、500、600。
那么,按照带虚拟节点的哈希一致性方法, 数据D0和D6按顺时针方向的下一个虚拟存储节点为Node 1-1因此节点Node1将会存储数据D0id = 100和D6id = 700同理Node2将会存储数据D1id = 200和D2id = 300Node3将会存储数据D3id = 400、D4id = 500和D5id = 600
可以看出带虚拟节点的一致性哈希方法比较适合异构节点、节点规模会发生变化的场景。目前Memcached缓存系统实现了该方法我会在第27篇文章中与你详细分析。
这种方法不仅解决了节点异构性问题,还提高了系统的稳定性。当节点变化时,会有多个节点共同分担系统的变化,因此稳定性更高。
比如,当某个节点被移除时,对应该节点的多个虚拟节点均会移除,而这些虚拟节点按顺时针方向的下一个虚拟节点,可能会对应不同的物理节点,即这些不同的物理节点共同分担了节点变化导致的压力。
当然,这种方法引入了虚拟节点,增加了节点规模,从而增加了节点的维护和管理的复杂度,比如新增一个节点或一个节点故障时,对应到虚拟节点构建的哈希环上需要新增和删除多个节点,数据的迁移等操作相应地也会很复杂。
四种数据分布方法对比
为方便理解与记忆,我再通过一个表格和你对比分析下这四种方法吧。请注意,以下方法之间的对比都是相对的比较,实际性能优劣与哈希函数的设定以及具体的数据场景密切相关。
知识扩展:数据分片和数据分区,有何区别?
首先我们一起回忆一下第20篇文章中提到的数据分区。
数据分区是从数据存储块的维度进行划分不同的分区物理上归属于不同的节点。比如现在有2个节点Node1和Node22个数据分区Partition1和Partition2Partition1属于Node1、Partition2属于Node2。
对于数据分区可用于存储不同的数据也可以用来存储相同的数据实现数据备份。数据分区可以归结为是“货架”相关的关键技术也就是为数据存储提供合适的位置。具体实例参见第20篇文章中介绍的Kafka分区的内容。
接下来,我们再看一下数据分片。
数据分片是从数据的维度进行划分,是指将一个数据集合按照一定的方式划分为多个数据子集,不同的数据子集存在不同的存储块上,而这些存储块可以在不同的节点上,也可以在同一节点上。
具体的数据分片策略可以采用我今天和你分享的哈希、一致性哈希等方法。数据分片是实现“导购”的关键技术,目的是构建索引,为数据确定位置,包括存储数据和查询数据时确定数据位置。具体例子,可参考上文的相关内容。
由此可见,数据分片和数据分区是两个不同的概念,且属于分布式存储系统中不同角色的技术,前者是实现“导购”的关键技术,后者是“货架”相关的技术,不可直接等同。
但正因为一个是导购相关的关键技术一个是货架相关的技术一个提供确定数据索引的位置一个提供合适的数据存储位置因此正如我在第24篇文章中所说这两个技术是可以共存的比如下面这个例子。
有3个节点{Node1, Node2, Node3}有3个分区{Partition1, Partition2, Partition3}用于存储用户信息每个节点上1个分区。现在有1000个用户信息需要存储用户id编号为[1,1000]为防止将所有信息存储到一个节点上所有用户发起请求时该节点成为瓶颈为此需要将这1000个用户信息存储到3个节点上。
假设我们采用最简单的哈希方法用户id%节点总数(3)进行哈希映射id%3 = 0的所有用户信息存储到节点1的Partition1 id%3=1的所有用户信息存储到节点2的Partition2id%3=2的所有用户信息存储到节点3的Partition3。
总结
今天,我主要带你学习了数据分布式方法中的哈希与一致性哈希。
首先,我带你了解了分布式数据存储系统中,设计数据分布方法需要考虑的原则,主要包括数据均匀性、稳定性和节点异构性。
其次,基于数据分布设计原则,我为你介绍了哈希、一致性哈希、带有限负载的一致性哈希和带虚拟节点的一致性哈希方法,并以例子进行辅助讲解,以便于你理解和学习。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
加油,相信你通过对本讲的学习,对分布式数据存储系统中“导购”的角色有了更深入的理解,对分布式数据分布方法的原理,以及如何针对不同场景进行选型有了一定的认知。加油,赶紧行动起来,为你的应用场景选择一种合适的数据分布方法,并实践起来吧!
思考题
今天,我主要与你分享的是哈希方法相关的数据分布方法,你还知道哪些常用的数据分布方法吗?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,158 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
26 分布式数据复制技术:分身有术
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在上一篇文章中,我为你讲解了数据分布(也称数据分片)技术,主要用于构建数据索引,是实现“导购”功能的关键技术。数据分布的本质是,将原数据集划分为多个数据子集,以存储到不同的地方,在一定程度上体现了数据的可用性和可靠性(一个存储节点故障,只影响该存储节点的数据)。
我在第24讲中介绍“导购”时提到数据分片和数据复制技术均是实现“导购”的关键技术。其中数据分片是确定数据位置数据复制是实现数据可靠性的关键方法。
在实际情况下,仅考虑数据分片,其实是无法真正应用到生产环境的。因为,故障导致数据丢失和不可用是很常见的情况。因此,在进行分布式数据存储设计时,通常会考虑对数据进行备份,以提高数据的可用性和可靠性,而实现数据备份的关键技术就是“数据复制技术”。
接下来,我们就一起打卡分布式数据复制技术吧。
什么是数据复制技术?
概括来讲数据复制是一种实现数据备份的技术。比如现在有节点1和节点2节点1上存储了10M用户数据直观地说数据复制技术就是将节点1上的这10M数据拷贝到节点2上以使得节点1和节点2上存储了相同的数据也就是节点2对节点1的数据进行了备份。当节点1出现故障后可以通过获取节点2上的数据实现分布式存储系统的自动容错。
也就是说,数据复制技术,可以保证存储在不同节点上的同一份数据是一致的。这样当一个节点故障后,可以从其他存储该数据的节点获取数据,避免数据丢失,进而提高了系统的可靠性。
这是不是就像数据有了自己的“分身”呢?那么,分布式系统是如何实现数据“分身有术”的呢?
接下来,我们通过一个例子来具体看下吧。
在分布式数据库系统中,通常会设置主备数据库,当主数据库出现故障时,备数据库可以替代主数据库进行后续的工作,从而保证业务的正常运行。这里,备数据库继续提供服务就是提高了分布式存储系统的可用性及可靠性。
那么,在这个过程中,又是如何实现备数据库替代主数据库的呢?这,就涉及到数据一致性的问题了,即只有主备数据库中的数据保持一致时,才可实现主备的替换。因此,在这个例子中,数据复制技术实际就是指,如何让主备数据库保持数据一致的技术。
理解了数据复制技术的基本含义,我们再一起看看数据复制技术的具体原理和应用吧。
数据复制技术原理及应用
不知你是否还记得我在第23篇文章与你分享的CAP理论的C、A和P三个特性呢我曾提到在分布式存储系统中分区容错性是肯定要满足的为此需要在一致性和可用性之间做出权衡。
所以,对于数据的一致性,通常是指不同节点上数据要保持一致。要实现不同节点上的数据一致,数据复制技术必不可少。为此,对于分布式存储系统中的数据复制技术来讲,也需要在一致性和可用性之间做出一些权衡。因此,这就导致出现了多种数据复制技术方法,大体上有三类:
第一类方法,比较注重一致性,比如同步复制技术;
第二类方法,则更注重可用性,比如异步复制技术;
第三类方法,是介于前两者之间的,比如半同步复制技术。
接下来,我就针对同步数据复制技术、异步数据复制技术,以及半同步数据复制技术分别进行详细讲解。
同步复制技术原理及应用
同步复制技术是指,当用户请求更新数据时,主数据库必须要同步到备数据库之后才可给用户返回,即如果主数据库没有同步到备数据库,用户的更新操作会一直阻塞。这种方式保证了数据的强一致性,但牺牲了系统的可用性。
接下来,我们看一个具体的案例吧。
在一个分布式数据库系统中有两个节点分别作为主节点和备节点。通常情况下两个节点均可接收用户读请求然后将本节点的数据及时返回给用户也就是说读请求响应比较快。而如果用户发送的是写请求写操作必须由主节点进行即使用户将写请求发送到备节点备节点也会将该请求转发给主节点因此写请求通常比读请求响应慢。MySQL集群的读写分离就是一个典型实例。
如此设计的原因是,读请求不需要改变数据,只需要在更改数据时保证数据一致,就可以随时读;而写请求,因为要修改数据,如果每个节点均修改同一数据,则可能导致数据不一致。因此只有主节点可以进行写操作,但又要保证主节点和备节点的数据一致,这就是数据复制技术要发挥的作用了。
对于上述场景如果采用同步复制技术的话对于写请求主数据库会执行写操作并将数据同步到所有备数据库之后才可以响应用户。如图所示客户端向主数据库发起更新操作V将X设置为2主数据库会将写请求同步到备数据库备数据库操作完后会通知主数据库同步成功然后主数据库才会告诉客户端更新操作成功。MySQL集群支持的全复制模式就采用了同步复制技术。
在同步复制技术中,主数据库需要等待所有备数据库均操作成功才可以响应用户,性能不是很好,会影响用户体验,因此,同步复制技术经常用于分布式数据库主备场景(对于一主多备场景,由于多个备节点均要更新成功后,主节点才响应用于,所需时延比较长)或对数据一致性有严格要求的场合,比如金融、交易之类的场景。
异步复制技术原理及应用
异步复制技术是指,当用户请求更新数据时,主数据库处理完请求后可直接给用户响应,而不必等待备数据库完成同步,即备数据库会异步进行数据的同步,用户的更新操作不会因为备数据库未完成数据同步而导致阻塞。显然,这种方式保证了系统的可用性,但牺牲了数据的一致性。
如图所示,客户端 1向主数据库发起更新操作V主数据库执行该操作将X=1修改为X=2执行后直接返回给客户端 1更新操作成功而未将数据同步到备数据库。因此当客户端 2请求主数据库的数据X时可以得到X=2但客户端 3请求备数据库中的数据X时却只能得到X=1从而导致请求结果不一致。
当然分布式数据库主备模式场景下若对数据一致性要求不高也可以采用异步复制方法。MySQL集群默认的数据复制模式采用的是异步复制技术我就以MySQL集群默认的复制模式为例与你简单描述下主备数据库同步的流程吧。
主数据库完成写操作后可直接给用户回复执行成功将写操作写入binary log中binary log中记录着主数据库执行的所有更新操作以便备数据库获取更新信息。
备数据库启动一个IO线程专门读取binary log中的内容然后写入relay log中。
备数据库启动一个SQL线程会定时检查relay log里的内容如发现有新内容则会立即在备数据库中执行从而实现数据的一致。
异步复制技术大多应用在对用户请求响应时延要求很高的场景比如很多网站或App等需要面向实际用户这时后台的数据库或缓存如果采用同步复制技术可能会流失用户因此这种场景采用异步复制技术就比较合适。
除了MySQL集群在缓存数据库Redis集群中采用的也是异步复制技术因此性能较高。但在Redis中还会有其他机制来保证数据的一致性我会在第27篇文章中与你详细介绍。
半同步复制技术原理及应用
同步复制技术会满足数据的强一致性,但会牺牲一定的可用性;异步复制技术会满足高可用,但一定程度上牺牲了数据的一致性。介于两者中间的是,半同步复制技术。
半同步复制技术的核心是,用户发出写请求后,主数据库会执行写操作,并给备数据库发送同步请求,但主数据库不用等待所有备数据库回复数据同步成功便可响应用户,也就是说主数据库可以等待一部分备数据库同步完成后响应用户写操作执行成功。
半同步复制技术通常有两种方式:
一种是,当主数据库收到多个备数据库中的某一个回复数据同步成功后,便可给用户响应写操作完成;
另一种是,主数据库等超过一半节点(包括主数据库)回复数据更新成功后,再给用户响应写操作成功。
显然,第二种半同步复制方案要求的一致性比第一种要高一些,但相对可用性会低一些。
前面所讲的MySQL集群在一主多备场景下也支持半同步复制模式一般采用的是第一种半同步复制技术这种技术既不会影响过多的性能还可以更好地实现对数据的保护。
还记得我在第23篇文章中提到的具有CP特性的ZooKeeper集群吗它采用的数据复制技术就是第二种半同步复制方案。在ZooKeeper集群中写请求必须由Leader节点进行处理每次写请求Leader会征求其他Follower的同意只有当多数节点同意后写操作才可成功因此保证了较高的一致性。
除此之外还有很多系统采用了第二种半同步复制方案比如微软云关系型数据库Microsoft SQL Azure的后端存储系统Cloud SQL Server、Kubernetes中保存集群所有网络配置和对象状态信息的Etcd组件该组件采用的是Raft一致性协议你可以再回顾下第4篇文章中的相关内容等。
实际上多数的分布式存储系统可以通过配置来选择不同的数据复制技术。比如上面讲过的MySQL数据库集群就支持全同步复制、异步复制和半同步复制三种模式再比如Oracle数据库也提供了三种模式
最大保护模式,对于写请求,要求主数据库必须完成至少一个备数据库的数据同步才可成功返回给客户端,采用的是半同步复制技术中的第一种方式。
最大性能模式,对于写请求,只要主数据库执行成功即可返回给客户端,采用的是异步复制技术。这种方式极大地提高了系统的可用性,但一致性难以保证。
最大可用性模式,介于最大保护模式和最大性能模式两者之间。这种模式是指,系统在通常情况下采用最大保护模式,但当主备之间出现网络故障时,切换为最大性能模式,等到网络恢复后,备数据库再进行数据同步。这种方式在系统的一致性和可用性之间做了一个权衡。
三种数据复制技术对比
以上,就是同步复制技术、异步复制技术和半同步复制技术的核心知识点了。接下来,我通过一张表格对比下这三种方法,以便于你记忆和理解。
知识扩展:在半同步复制技术中,对于未回复数据更新结果的节点,如何解决数据不一致或冲突呢?
对于半同步复制技术,因为只有部分备节点更新数据后,主节点即可返回响应用户。那么,对于未回复数据更新结果的节点,如何解决可能存在的数据不一致或冲突呢?
对于这个问题不同的场景有不同的处理方式需要根据用户的需求进行选择比如以最新数据为准、以最大数据为准等没有统一的评判规则和用户的需求紧密相关。由于在分布式系统中很多系统采用了Raft算法你可以再回顾下第4篇文章中的相关内容因此这里我以Raft算法的处理策略为例与你展开介绍以便你理解大部分常用的分布式系统的处理策略。
我刚刚提到Raft算法采用的是第二种半同步复制技术也就是主数据库等超过一半节点包括主数据库回复数据更新成功后再给用户响应写操作成功。当有Follower节点的数据与Leader节点数据不一致时采用强制复制策略来解决不一致情况。
由于所有的数据更新操作最先在Leader节点执行因此当产生冲突时以Leader节点为准。Leader节点上会对比与自己数据不一致的Follower节点所存储的信息找到两者最后达成一致的地方然后强制将这个地方之后的数据复制到该Follower节点上。
具体方法是Leader节点将每一次数据操作看作一条记录并对这条记录标记一个index用于索引。Leader节点会为每个Follower节点维护一个记录状态变量nextIndex即下一个记录的索引位置nextIndex的值为Leader节点当前存储数据记录的下一个Index值。Leader节点会将nextIndex发送给Follower节点若Follower节点发现与本节点的nextIndex不一致则告知Leader节点不一致Leader节点将nextIndex减1重复上述过程直到与Follower节点的nextIndex相等位置即找到了两者最后达成一致的地方。
比如对于变量XLeader节点记录的操作是{(Index 1, X = 1, Version:0), (Index 2, X=2, Version:1), (Index3 , X=3, Version:2)}其中Follower节点2记录的操作为{(Index 2, X=1, Version:0), (Index 6, X=4, Version:2)}。
那么Leader节点发现两者最后一致的状态是{(Index 1, X=1, Version:0)},为此将后续的{(Index 2, X=2, Version:1), (Index 3, X=3, Version:2)}复制到节点2上则节点2更新为(Index 1, X = 1, Version: 0), (Index 2, X=2, Version:1), (Index3 , X=3, Version:2)}。从而节点2与Leader节点的数据保持一致。
总结
今天,我主要和你分析的是分布式数据复制技术。
首先,我通过分布式数据库主备节点数据一致的例子,为你比较直观地讲解了什么是数据复制技术。
然后我为你介绍了数据复制技术的原理及应用以及同步复制技术、异步复制技术和半同步复制技术这三种方法。其中对于用户更新数据的请求同步复制技术要求主备节点均更新完成才返回结果告知用户更新成功异步复制技术只需要主节点更新成功就可返回结果半同步复制技术要求主节点更新成功且备节点中至少有1个或过半节点更新成功才可返回结果。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
相信通过今天的学习你对分布式数据复制技术已经有了深刻的理解并且对于不同技术适用的场景也有了自己的看法。加油赶紧行动起来相信你可以为你的业务场景选择一个合适的复制策略也能够比较容易看懂ZooKeeper等开源软件采用的复制技术的原理了。
思考题
本讲主要是从应用或分布式数据系统的角度介绍了三种比较基本的数据复制技术,你还知道一些其他的基于本讲介绍的复制技术的演进或变种的复制方法吗?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,183 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
27 分布式数据之缓存技术:“身手钥钱”随身带
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
不知不觉分布式数据存储这一站已经到了最后一讲。在前面几讲我与你分享了CAP理论、分布式存储系统的三要素顾客、导购和货架、数据分布式分片方法和数据复制技术其中数据分片方法和数据复制技术均是导购中的关键技术。
在这一讲,我将为你讲解分布式存储中“货架”的关键技术——缓存技术。
在计算机领域的各个方面,缓存都非常重要,是提升访问性能的一个重要技术。为什么这么说呢?
从单个计算机的体系结构来看,内存和处理器速度差异很大,如果不采用缓存技术,处理器的性能会受到很大的限制。
再看计算机应用,如果不采用缓存技术,对于每个请求,应用都要与后台数据库做一次交互,而数据库中的数据存储在磁盘上,因此每次请求都要和磁盘做交互,而磁盘访问的性能很低,造成访问延迟。
除此之外,还有网络访问,如果没有缓存机制,每次访问主机都要与远程机器做交互,速度又可想而知。
接下来,我们就一起打卡分布式缓存技术吧。
什么是分布式缓存?
打比方来说,缓存技术其实就像一个水缸,平时它会存储一定的水,而这些水就来自深井。如果每次都去深井打水,一方面井口比较小,导致一次能接收的用水请求有限;另一方面,井比较深,打水的工序比较复杂,导致所需时间比较长。
而有了这个水缸,我们就不需要去深井里打水,当水缸里没水时,水泵会将深井里的水抽到水缸中暂时存储起来。也就是说,“缓存技术”存储了满足人们一定时间内常用的“水量”,以提高用水效率。
在计算机领域,缓存技术一般是指,用一个更快的存储设备存储一些经常用到的数据,供用户快速访问。用户不需要每次都与慢设备去做交互,因此可以提高访问效率。
分布式缓存就是指在分布式环境或系统下,把一些热门数据存储到离用户近、离应用近的位置,并尽量存储到更快的设备,以减少远程数据传输的延迟,让用户和应用可以很快访问到想要的数据。这,是不是可以形象地理解为“身手钥钱”随身带呢?
其实,我们通常说的分布式数据缓存,属于计算机应用中的缓存的一种。而计算机应用中的缓存,一般指内存,即内存存储了用户经常访问的数据,用户或应用不再需要到磁盘中去获取相应的数据,大幅提高访问速度。
如下图所示数据A是应用经常访问的数据而数据B很少被应用访问因此当应用访问数据A时不需要到磁盘而直接访问内存即可得到对应的值速度较快相反访问数据B时由于内存中没有缓存数据B所以应用需要到磁盘中获取对应的值速度较慢。
那么今天,我要与你分享的分布式数据存储相关的缓存技术,就是以内存做为磁盘的缓存。
分布式缓存原理
接下来我以主流的分布式缓存系统Redis和Memcached为例与你讲述分布式缓存技术以加深你的理解吧。
Redis分布式缓存原理
Redis的全称是Remote Dictionary Server远程字典服务器。可以直观地看出它是以字典结构将数据存储在内存中应用可直接到内存读写Redis存储的数据。
Redis集群是一个典型的去中心化结构每个节点都负责一部分数据的存储同时每个节点还会进行主备设计来提高Redis的可靠性具体原理你可以再回顾下第10篇文章中的相关内容。
接下来我与你分享下Redis中与缓存关系最紧密的三个特性支持多数据结构、支持持久化和主备同步。
第一Redis支持多数据结构。
Redis是一个基于内存的key-value数据库为了方便支持多应用的缓存比如缓存文本类型、数据库的查询结果字段与字段对应的值等等支持的数据结构不仅有简单的kv类型还可以支持List、Set、Hash等复杂类型的存储。
以Hash这种复杂类型的存储为例Redis将Hash视作一个整体当作数据库的value可以是一个对象比如结构体对象进行存储。如果把Hash结构的整体看作对象的话Hash结构里的key-value相当于该对象的属性名和属性值。
比如插入Hash数据类型的命令HMSET test field1 “Hello” field2 “World”中如下图所示test为key值 field1 “Hello” field2 “World” 为value值如果把整个Hash结构看做对象的话则field1、field2类似于对象中的属性名“Hello”“World”类似于对象中的属性值。
第二Redis支持持久化。
持久化是指将数据从内存这种易失性存储设备中写入磁盘从而让数据永久保存。Redis中存储的数据虽然是基于内存的但它也提供了持久化的机制主要有两种方式RDB和AOF。
RDBRedis DataBase也称快照方式简单来说就是Redis会定时将内存中的数据备份到磁盘中形成一个快照比如每天保存一下过去一周的数据。这样当节点出现故障时可以根据快照恢复到不同版本的数据。这种方式有一个明显的缺点是会造成数据丢失即当节点出现故障时新数据可能还未备份到磁盘中。
AOFAppend Only File的出现主要弥补了RDB数据不一致的问题其思想与上一讲提到的数据库复制技术中binary log类似即记录下Redis中所有的更新操作。
在Redis中提供了三种实现AOF的策略
AOF_FSYNC_NO (不同步),即不会自动触发写操作的同步;
AOF_FSYNC_EVERYSEC (每秒同步),即每隔一秒都会将写操作同步到磁盘;
AOF_FSYNC_ALWAYS (每次写都同步),即每次发生写操作会立即同步到磁盘。
Redis中默认采用AOF_FSYNC_EVERYSEC每秒同步的策略因为这种策略的性能很不错而且一旦出现故障最多只会丢失一秒的数据。
第三Redis支持主备同步。
说到主备同步我相信你应该想到了上一讲提到的数据复制技术。在Redis中采用的是异步复制技术但Redis可以通过配置min-replicas-to-write和min-replicas-max-lag这两个参数来有效地保证数据一致性。
比如设置min-replicas-to-write=3、min-replicas-max-lag=10表示当至少有3个备数据库连接主数据库的延迟时间小于10s时主数据库才可以执行写操作。延迟时间是从最后一次收到备数据库的心跳开始计算通常每秒发送一次心跳。
除了上面对写操作的同步在Redis中还有两种情况是需要进行数据同步的
一种情况是初次同步,即备数据库刚启动时的数据同步;
另一种情况是,因网络故障导致主备数据库断开连接,待网络恢复后的备数据库的数据同步。
针对这两种情况Redis提供了两种同步模式即完整重同步和部分重同步。
完整重同步的流程如下所示:
当备服务器启动时会向主服务器发送SYNC命令
主服务器收到命令后会生成RDB快照文件并记录从现在起新执行的写操作
RDB生成后会发送给备服务器备服务器通过RDB文件进行数据更新
更新完成后,主服务器再将新记录的写操作发送给备服务器,备服务器执行完这些新记录的写操作,便与主服务器的数据保持一致了。
简单地说,部分重同步就是,当网络恢复后,主数据库将主备数据库断开连接之后的一系列写操作发送给备服务器,备数据库执行这些写操作,从而保证数据保持一致。
在这种方式的实现中,主备数据库会共同维护一个复制偏移量,这样主数据库就知道应该将哪些写操作发给备数据库,备数据库同步时也知道应该从哪里继续执行操作。
如图所示主数据库的复制偏移量为5000时向备数据库发送了100个字节的数据发送结束后复制偏移量为5100。
此时主备数据库连接断开备数据库没有接收到这100个字节的数据等到备数据库重新与主数据库连接上之后会给主数据库发送PSYNC命令并带上自己的复制偏移量5000主数据库接收到信息后根据接收到的复制偏移量将5000之后的数据发给备数据库从而完成数据的部分重同步。
以上就是分布式缓存系统Redis中涉及的关键技术包括支持的数据结构、数据持久化方法和数据同步方法相信通过上面的介绍你对分布式缓存技术已经有了一定的了解。
接下来我再带你学习另一个缓存数据库Memcached。
Memcached分布式缓存原理
与Redis类似Memcached也是一个基于内存的高性能key-value缓存数据库。Memcached比Redis问世更早也有很多公司在使用比如Facebook、Vox、LiveJournal等。
其实Memecached的缓存原理和Redis类似。所以接下来的内容我会着重于你讲述这两个数据库在支持的数据结构、持久化和主备同步上的不同。这样你可以对比着学习这两个数据库也会理解得更全面、深入些。
首先我要先带你了解一下Memcached的集群结构。
Redis的集群结构是每个节点负责一部分哈希槽且每个节点可以设计主备。与Redis不同Memcached集群采用一致性哈希的思路使用的是Ketama算法。该算法的主要思想就是带虚拟节点的一致性哈希算法。
在实际应用中每个物理节点对应100~200个虚拟节点才能到达一个较好的存储均衡。这里为了方便理解我对Memcached的集群结构做了简化如下图所示物理节点1对应两个虚拟节点1-1、1-2物理节点2对应三个虚拟节点2-1、2-2和2-3。
采用带虚拟节点的一致性哈希方法有一个优点是当添加或移除节点时不会出现大规模的数据迁移。你可以再回顾下第25篇文章中的相关内容。
对于数据结构的支持Memcached仅支持简单的kv数据类型如果想要存储复杂的数据类型比如List、Set和Hash等需要客户端自己处理将其转化为字符串然后进行存储。这样就导致了一个缺点操作不灵活。比如Memcached存储的数组中有一个元素需要修改则需要将整个数组的数据取出来修改后再整体写入到数据库中 。
而对于持久化Memcached是不支持的。这意味着断电时Memcached中存储的数据将会全部丢失。因为内存是一种易失性存储设备断电后将不会存储数据。
在Memcached中服务器和服务器之间没有任何通信即自身不支持主备。如果想要实现高可用需要通过第三方实现。比如Repcached实现了Memcached的复制功能支持一主一备从而使Memcached满足高可用。
对比分析
上面我以Redis和Memcached这两个主流的分布式缓存系统为例带你学习了分布式缓存技术。接下来我以一个表格对它们进行分析对比以便于你理解和查阅。
知识扩展:除了分布式存储中的缓存,还有计算机体系结构和网络中的缓存,它们又分别是什么呢?
计算机体系结构中的缓存通常是指专用的缓存设备。由于内存和CPU访问速度相差很大为了提高CPU的性能计算机内部在CPU与内存之间设置了相应的缓存。
现在大多数机器分为三级缓存L1高级缓存、L2高级缓存和L3高级缓存。就访问速度来讲L1高级缓存 > L2高级缓存 > L3高级缓存>内存。其中L1高级缓存的访问速度几乎和CPU中寄存器的访问速度一样快。
有了这三级缓存,很多数据不需要到内存中读取,而直接读取这三级缓存中的数据即可,缩短了数据访问的时间,使得计算机运行速度变得更快。
网络访问中的缓存,通常是指本地的“磁盘”。通过网络访问数据时,需要与远程服务器交互来进行传输,而网络间数据传输以及远程服务器对请求的响应,会耗费很多时间。如果本机器的磁盘可以对你经常访问的远程内容进行存储,这样就不用每次都与远程服务器交互,从而减少网络数据传输与服务器响应的延迟,极大地提高性能。
可以看出,缓存的概念是相对的,基于不同的背景或应用场景,缓存所映射的存储设备是不一样的。
总结
今天,我主要与你分享了分布式数据的缓存技术。
首先,我以水缸的例子带你直观了解了什么是缓存,并引出了什么是分布式数据缓存。分布式数据缓存是以内存作为磁盘的缓存,存储一些用户经常需要用的数据,以提高访问速度。
其次我以主流的Redis和Memcached为例与你介绍了分布式缓存技术中的关键技术包括支持的数据存储结构比如k/v、Set、List等、持久化技术包括快照方式等和数据同步技术具体技术原理可参见第26篇文章
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
相信通过本讲的学习你已不再觉得分布式缓存有多么神秘了不管是使用Redis还是看Redis等系统的源码一定会更容易理解和上手。加油行动起来吧
思考题
本讲我主要介绍了Redis和Memcached分布式数据缓存系统你还知道哪些主流的分布式数据缓存系统呢它们的缓存核心技术是什么呢
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,196 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
28 分布式高可靠之负载均衡:不患寡,而患不均
你好!我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
到目前为止,我已经为你介绍了分布式起源、分布式协调与同步、分布式资源管理与负载调度、分布式计算技术、分布式通信技术和分布式数据存储。可以说,掌握了这些内容,基本上就掌握了分布式的关键技术。
然而,只有可靠的分布式系统才能真正应用起来。那么,分布式系统的可靠性又是如何实现的呢?
不要着急,接下来几篇文章,我会和你一起学习分布式可靠性相关的知识,包括负载均衡、流量控制、故障隔离和故障恢复。
在这其中,负载均衡是分布式可靠性中非常关键的一个问题或技术,在一定程度上反映了分布式系统对业务处理的能力。比如,早期的电商抢购活动,当流量过大时,你可能就会发现有些地区可以购买,而有些地区因为服务崩溃而不能抢购。这,其实就是系统的负载均衡出现了问题。
接下来,我们就一起来打卡分布式高可靠之负载均衡。
什么是负载均衡?
先举个例子吧。以超市收银为例,假设现在只有一个窗口、一个收银员:
一般情况下收银员平均2分钟服务一位顾客10分钟可以服务5位顾客
到周末高峰期时收银员加快收银平均1分钟服务一位顾客10分钟最多服务10位顾客也就是说一个顾客最多等待10分钟
逢年过节顾客数量激增一下增加到30位顾客如果仍然只有一个窗口和一个收银员那么所有顾客就只能排队等候了一个顾客最多需要等待30分钟。这样购物体验就非常差了。
那有没有解决办法呢?
当然有。那就是新开一个收银窗口每个收银窗口服务15个顾客这样最长等待时间从30分钟缩短到15分钟。但如果这两个窗口的排队顾客数严重不均衡比如一个窗口有5个顾客排队另一个窗口却有25个顾客排队就不能最大化地提升顾客的购物体验。
所以,尽可能使得每个收银窗口排队的顾客一样多,才能最大程度地减少顾客的最长排队时间,提高用户体验。
看完这个例子,你是不是想到了一句话“不患寡,而患不均”?这,其实就是负载均衡的基本原理。
通常情况下,负载均衡可以分为两种:
一种是请求负载均衡,即将用户的请求均衡地分发到不同的服务器进行处理;
另一种是数据负载均衡,即将用户更新的数据分发到不同的存储服务器。
我在第25篇文章分享数据分布方法时提到数据分布算法很重要的一个衡量标准就是均匀分布。可见哈希和一致性哈希等其实就是数据负载均衡的常用方法。那么今天我就与你着重说说服务请求的负载均衡技术吧。
分布式系统中,服务请求的负载均衡是指,当处理大量用户请求时,请求应尽量均衡地分配到多台服务器进行处理,每台服务器处理其中一部分而不是所有的用户请求,以完成高并发的请求处理,避免因单机处理能力的上限,导致系统崩溃而无法提供服务的问题。
比如有N个请求、M个节点负载均衡就是将N个请求均衡地转发到这M个节点进行处理。
服务请求的负载均衡方法
通常情况下计算机领域中在不同层有不同的负载均衡方法。比如从网络层的角度通常有基于DNS、IP报文等的负载均衡方法在中间件层也就是我们专栏主要讲的分布式系统层常见的负载均衡策略主要包括轮询策略、随机策略、哈希和一致性哈希等策略。
今天,我着重与你分析的就是,中间件层所涉及的负载均衡策略。接下来,我们就具体看看吧。
轮询策略
轮询策略是一种实现简单,却很常用的负载均衡策略,核心思想是服务器轮流处理用户请求,以尽可能使每个服务器处理的请求数相同。生活中也有很多类似的场景,比如,学校宿舍里,学生每周轮流打扫卫生,就是一个典型的轮询策略。
在负载均衡领域中,轮询策略主要包括顺序轮询和加权轮询两种方式。
首先我们一起看看顺序轮询。假设有6个请求编号为请求1~6有3台服务器可以处理请求编号为服务器1~3如果采用顺序轮询策略则会按照服务器1、2、3的顺序轮流进行请求。
如表所示将6个请求当成6个步骤
请求1由服务器1处理
请求2由服务器2进行处理。
以此类推直到处理完这6个请求。
最终的处理结果是服务器1处理请求1和请求4服务器2处理请求2和请求5服务器3处理请求3和请求6。
接下来,我们看一下加权轮询。
加权轮询为每个服务器设置了优先级每次请求过来时会挑选优先级最高的服务器进行处理。比如服务器1~3分配了优先级{411}这6个请求到来时还当成6个步骤如表所示。
请求1由优先级最高的服务器1处理服务器1的优先级相应减1此时各服务器优先级为{311}
请求2由目前优先级最高的服务器1进行处理服务器1优先级相应减1此时各服务器优先级为{211}。
以此类推直到处理完这6个请求。每个请求处理完后相应服务器的优先级会减1。
最终的处理结果是服务器1处理请求1~4服务器2处理请求5服务器3会处理请求6。
以上就是顺序轮询和加权轮询的核心原理了。轮询策略的应用比较广泛比如Nginx默认的负载均衡策略就是一种改进的加权轮询策略。我们具体看看它的核心原理吧。
首先我来解释下Nginx轮询策略需要用到的变量吧。
weight配置文件中为每个服务节点设置的服务节点权重固定不变。
effective_weight服务节点的有效权重初始值为weight。 在Nginx的源码中有一个最大失败数的变量max_fails当服务发生异常时则减少相应服务节点的有效权重公式为effective_weight = effective_weight - weight / max_fails之后再次选取本节点若服务调用成功则增加有效权重effective_weight ++ 直至恢复到weight。
current_weight服务节点当前权重初始值均为0之后会根据系统运行情况动态变化。
假设,各服务器的优先级是{411}我还是将6个请求分为6步来进行讲解如表所示
遍历集群中所有服务节点使用current_weight = current_weight + effective_weight计算此时每个服务节点的current_weight得到current_weight为{411}total为4+1+1=6。选出current_weight值最大的服务节点即服务器1来处理请求随后服务器1对应的current_weight减去此时的total值即4 - 6变为了-2 。
按照上述步骤执行首先遍历按照current_weight = current_weight + effective_weight计算每个服务节点current_weight的值结果为{222}total为6选出current_weight值最大的服务节点。current_weight 最大值有多个服务节点时直接选择第一个节点即可在这里选择服务器1来处理请求随后服务器1对应的current_weight值减去此时的total即2 - 6结果为-4。
以此类推直到处理完这6个请求。
最终的处理结果为服务器1处理请求1、2、4、6服务器2处理请求3服务器3会处理请求5。
可以看到,与普通的加权轮询策略相比,这种轮询策略的优势在于,当部分请求到来时,不会集中落在优先级较高的那个服务节点。
还是上面的例子假设只有4个请求按照普通的加权轮询策略会全部由服务器1进行处理即{1,1,1,1}而按照这种平滑的加权轮询策略的话会由服务器1和2共同进行处理即{1,1,2,1}。
轮询策略的优点就是,实现简单,且对于请求所需开销差不多时,负载均衡效果比较明显,同时加权轮询策略还考虑了服务器节点的异构性,即可以让性能更好的服务器具有更高的优先级,从而可以处理更多的请求,使得分布更加均衡。
但轮询策略的缺点是,每次请求到达的目的节点不确定,不适用于有状态请求的场景。并且,轮询策略主要强调请求数的均衡性,所以不适用于处理请求所需开销不同的场景。
比如有两个服务器节点A和节点B性能相同CPU个数和内存均相等有4个请求需要处理其中请求1和请求3需要1个CPU请求2和请求4需要2个CPU。根据轮询策略请求1和请求3由节点A、请求2和请求4由节点B处理。由此可见节点A和节点B关于CPU的负载分别是2和4从这个角度来看两个节点的负载并不均衡。
综上所述,轮询策略适用于用户请求所需资源比较接近的场景。
随机策略
随机策略也比较容易理解,指的就是当用户请求到来时,会随机发到某个服务节点进行处理,可以采用随机函数实现。这里,随机函数的作用就是,让请求尽可能分散到不同节点,防止所有请求放到同一节点或少量几个节点上。
如图所示假设有5台服务器Server 1~5可以处理用户请求每次请求到来时都会先调用一个随机函数来计算出处理节点。这里随机函数的结果只能是{1,2,3,4,5}这五个值然后再根据计算结果分发到相应的服务器进行处理。比如图中随机函数计算结果为2因此该请求会由Server2处理。
这种方式的优点是,实现简单,但缺点也很明显,与轮询策略一样,每次请求到达的目的节点不确定,不适用于有状态的场景,而且没有考虑到处理请求所需开销。除此之外,随机策略也没有考虑服务器节点的异构性,即性能差距较大的服务器可能处理的请求差不多。
因此,随机策略适用于,集群中服务器节点处理能力相差不大,用户请求所需资源比较接近的场景。
比如我在第19篇文章中提到的RPC框架Dubbo当注册中心将服务提供方地址列表返回给调用方时调用方会通过负载均衡算法选择其中一个服务提供方进行远程调用。关于负载均衡算法Dubbo提供了随机策略、轮询策略等。
哈希和一致性哈希策略
无论是轮询还是随机策略,对于一个客户端的多次请求,每次落到的服务器很大可能是不同的,如果这是一台缓存服务器,就会对缓存同步带来很大挑战。尤其是系统繁忙时,主从延迟带来的同步缓慢,可能会造成同一客户端两次访问得到不同的结果。解决方案就是,利用哈希算法定位到对应的服务器。
哈希和一致性哈希,是数据负载均衡的常用算法。 我在第25篇文章介绍哈希与一致性哈希时提到过数据分布算法的均匀性一方面指数据的存储均匀另一方面也指数据请求的均匀。
数据请求就是用户请求的一种,哈希、一致性哈希、带有限负载的一致性哈希和带虚拟节点的一致性哈希算法,同样适用于请求负载均衡。
所以哈希与一致性策略的优点是哈希函数设置合理的话负载会比较均衡。而且相同key的请求会落在同一个服务节点上可以用于有状态请求的场景。除此之外带虚拟节点的一致性哈希策略还可以解决服务器节点异构的问题。
但其缺点是,当某个节点出现故障时,采用哈希策略会出现数据大规模迁移的情况,采用一致性哈希策略可能会造成一定的数据倾斜问题。同样的,这两种策略也没考虑请求开销不同造成的不均衡问题。
应用哈希和一致性哈希策略的框架有很多比如Redis、Memcached、Cassandra等你可以再回顾下第25篇文章中的相关内容。
除了以上这些策略还有一些负载均衡策略比较常用。比如根据服务节点中的资源信息CPU内存等进行判断服务节点资源越多就越有可能处理下一个请求再比如根据请求的特定需求如请求需要使用GPU资源那就需要由具有GPU资源的节点进行处理等。
对比分析
以上,就是轮询策略、随机策略、哈希和一致性哈希策略的主要内容了。接下来,我再通过一个表格对比下这三种方法,以便于你学习和查阅。
知识扩展:如果要考虑请求所需资源不同的话,应该如何设计负载均衡策略呢?
上面提到的轮询策略、随机策略,以及哈希和一致性哈希策略,主要考虑的是请求数的均衡,并未考虑请求所需资源不同造成的不均衡问题。那么,如何设计负载均衡策略,才能解决这个问题呢?
其实,这个问题的解决方案有很多,常见的思路主要是对请求所需资源与服务器空闲资源进行匹配,也称调度。
关于调度不知你是否还记得第11篇文章所讲的单体调度我们可以使用单体调度的思路让集群选举一个主节点每个从节点会向主节点汇报自己的空闲资源当请求到来时主节点通过资源调度算法选择一个合适的从节点来处理该请求。
在这篇文章中我提到了最差匹配和最佳匹配算法。这两种算法各有利弊最差匹配算法可以尽量将请求分配到不同机器但可能会造成资源碎片问题而最佳匹配算法虽然可以留出一些“空”机器来处理开销很大的请求但会造成负载不均的问题。因此它们适用于不同的场景你可以再回顾下第11篇文章中的相关内容。
除此之外,一致性哈希策略也可以解决这个问题:让请求所需的资源和服务器节点的空闲资源,与哈希函数挂钩,即通过将资源作为自变量,带入哈希函数进行计算,从而映射到哈希环中。
比如,我们设置的哈希函数结果与资源正相关,这样就可以让资源开销大的请求由空闲资源多的服务器进行处理,以实现负载均衡。但这种方式也有个缺点,即哈希环上的节点资源变化后,需要进行哈希环的更新。
总结
今天,我主要带你学习了分布式高可靠技术中的负载均衡。
首先我以超市收银为例与你介绍了什么是负载均衡。负载均衡包括数据负载均衡和请求负载均衡我在第25篇文章中介绍的数据分布其实就是数据的负载均衡所以我今天重点与你分享的是请求的负载均衡。
然后我与你介绍了常见的负载均衡策略包括轮询策略、随机策略、哈希和一致性哈希策略。其中轮询策略和随机策略因为每次请求到达的目的节点不确定只适用于无状态请求的场景而哈希和一致性哈希策略因为相同key的请求会落在同一个服务节点上所以可以用于有状态请求的场景。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
加油,相信通过本讲的学习,你对分布式系统中的负载均衡有了一定的理解,也可以进一步对电商系统、火车票系统等涉及的请求负载均衡的问题进行分析了。加油,行动起来吧!
思考题
在分布式系统中,负载均衡技术除了各节点共同分担请求外,还有什么好处呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,176 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
29 分布式高可靠之流量控制:大禹治水,在疏不在堵
你好!我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在上一篇文章中,我带你学习了分布式高可靠中的负载均衡。负载均衡的核心在于,将用户请求均匀分配到多个处理服务器处理,以解决单个服务器的单点瓶颈问题。但,如果用户请求数非常多的话,即便实现了负载均衡,服务器能力达到上限,还是无法处理所有的用户请求。
比如,类似双十一、双十二的秒杀场景,用户流量突增时,即使做了负载均衡,我们仍然会感受到点击抢购时,需要等待较长的时间。这背后的原理是什么呢?
你是不是想到了,这是因为系统控制了用户的请求量呢?没错,这就是今天我们要一起打卡的流量控制技术。
什么是流量控制?
说到流量控制,如果你学过计算机网络的话,第一反应肯定是网络传输中的流量控制。网络传输中的流量控制,就是让发送方发送数据的速率不要太快,让接收方来得及接收数据,具体的实现方法就是滑动窗口。
简单来讲,滑动窗口指的是,在任意时刻,发送方都维持一个连续的允许发送的数据大小,称为发送窗口;接收方也会维持一个连续的允许接收的数据大小,称为接收窗口。每次发送方给接收方发送数据后,必须收到接收方返回的确认消息,发送窗口才可向后移动,发送新的数据。
接下来我们通过一个简单的例子来看看滑动窗口在网络流量控制中是如何发挥作用的吧。如图所示发送窗口和接收窗口大小均为1发送方发送数据D1后只有接收到来自接收方的确认消息ACK发送窗口才可向后移动即发送方才可以发送后续数据D2。
这是网络传输中的流量控制,那么具体到分布式系统中,流量控制又是什么呢?
在前面提到的双十一、双十二秒杀场景中,用户流量突增,在这种高并发、大流量的情况下,服务器的处理能力成为电商系统的瓶颈,处理不好就会导致系统崩溃,服务不可用。而分布式系统中的流量控制,就是解决这类问题的一种关键技术。
通俗地说,分布式流量控制就是在分布式系统下,控制每个服务器接收的请求数,以保证服务器来得及处理这些请求,也就是说尽可能保证用户请求持续地被处理,而不是让大量的用户请求“阻塞”在服务器中,等待被执行。这就好比“大禹治水,在疏不在堵”。
接下来,我们就一起学习下分布式系统常用的流量控制策略吧。
分布式系统流量控制策略
还记得第21篇文章中讲到的消息队列吗消息队列就是实现流量控制的一种方法通过一个消息队列来存放用户的消息然后服务器到消息队列中逐个消费就可以避免消息过多时服务器处理不过来的情况。
除此之外,分布式系统的流量控制策略还有很多,常用的主要包括两种:漏桶策略和令牌桶策略。
漏桶策略
相信你看到“漏桶”两个字,头脑里应该已经有了一个漏桶的样子。确实,名字就已经很形象地说明了这种策略的含义。
如下图所示,有一个固定容量的水桶,桶底有一个小洞,水桶可以接收任意速率的水流,但无论水桶里有多少水,水从小洞流出的速率始终不变,桶里的水满了之后,水就会溢出。
漏桶策略借鉴上述原理,无论用户请求有多少,无论请求速率有多大,“漏桶”都会接收下来,但从漏桶里出来的请求是固定速率的,保证服务器可以处理得游刃有余。当“漏桶”因为容量限制放不下更多的请求时,就会选择丢弃部分请求。这种思路其实就是一种“宽进严出”的策略。
比如在某段时间内系统每秒会有10个用户发出请求但这些请求经过漏桶后每秒始终只流出2个请求也就是说服务器每秒最多处理2个请求。这样的话无论请求速率有多大都能达到限流的目的避免服务器在短暂时间内需要处理大量请求但由于处理能力受限导致系统崩溃从而保证了系统的高可靠。
这种策略的好处是,做到了流量整形,即无论流量多大,即便是突发的大流量,输出依旧是一个稳定的流量。但其缺点是,对于突发流量的情况,因为服务器处理速度与正常流量的处理速度一致,会丢弃比较多的请求。但是,当突发大流量到来时,服务器最好能够更快地处理用户请求,这也是分布式系统大多数情况下想要达到的效果。
所以说,漏桶策略适用于间隔性突发流量且流量不用即时处理的场景,即可以在流量较小时的“空闲期”,处理大流量时流入漏桶的流量;不适合流量需要即时处理的场景,即突发流量时可以放入桶中,但缺乏效率,始终以固定速率进行处理。
目前漏桶算法已经用于很多框架了比如阿里开源的流量控制框架Sentinel中的匀速排队限流策略就采用了漏桶算法分布式追踪系统Jaeger中有一种采集策略是速率限制类型内部使用的也是漏桶算法等。
令牌桶策略
令牌桶策略,也是一个很形象的名字,指的是桶里放着很多令牌,请求只有拿到令牌才能被服务器处理。
如图所示,有一个固定容量的存放令牌的桶,我们以固定速率向桶里放入令牌,桶满时会丢弃多出的令牌。每当请求到来时,必须先到桶里取一个令牌才可被服务器处理,也就是说只有拿到了令牌的请求才会被服务器处理。所以,你可以将令牌理解为门卡,只有拿到了门卡才能顺利进入房间。
同样的,我们通过一个具体的例子,来加深对令牌桶策略的理解吧。
假设令牌以每秒3个的速率放入到令牌桶中桶的容量为10。通常情况下 每秒会有2个用户请求请求到来时就会到桶里取一个令牌由于请求的速率低于放令牌的速率因此令牌桶里令牌会逐渐增多直到达到桶的容量。超过桶容量后令牌会被丢弃。
当大流量到来时比如某个时刻来了10个请求此时桶里有10个令牌因此请求都会被服务器处理但如果来的请求数不止10个令牌会被取完多余的请求取不到令牌也就没办法及时被服务器处理需要等待令牌。
通过上述的例子,就能看出这种策略的好处:当有突发大流量时,只要令牌桶里有足够多的令牌,请求就会被迅速执行。通常情况下,令牌桶容量的设置,可以接近服务器处理的极限,这样就可以有效利用服务器的资源。因此,这种策略适用于有突发特性的流量,且流量需要即时处理的场景。
在实际使用中令牌桶算法也很常见。比如Google开源工具包Guava提供的限流工具类RateLimiter就是基于令牌桶算法来完成限流的。
两种策略对比
以上就是漏桶策略和令牌桶策略的核心原理了,接下来我们通过一张表格对比下这两种策略吧。
Sentinel流量控制工作原理
我们都知道阿里的流量控制做得很好特别是双十一、抢购等情况下。接下来我以阿里开源的流量控制框架Sentinel为例与你进一步介绍流量控制的工作原理。
Sentinel的核心是监控应用的并发线程数或QPS请求数 /每秒指标当达到系统设定的阈值时Sentinel可以采取一定的策略对流量进行控制以避免应用被瞬时高流量击垮从而保证应用高可靠。
为此在Sentinel中关于流量控制有两种方式一种是通过并发线程数进行流量控制另一种是通过QPS指标进行流量控制。
首先,我们看一下通过并发线程数进行流量控制。
要理解这种限流方式,我需要先带你搞清楚什么是线程池。
我们知道,过多的线程会消耗非常多的系统资源,包括线程资源消耗、线程调度消耗等。为了解决这个问题,我们引入了线程池。线程池维护了多个启动着的线程,随时等待着去执行系统分配的任务,即系统每次需要处理任务时,可以直接从线程池中取线程,从而避免了创建和销毁线程的时间和资源等消耗。
同一时刻每个线程只能执行一个任务或请求,因此,可以通过并发线程数进行流量控制。我们看一个案例吧。
如图所示假设现在线程池中有3个线程也就是说最大并发处理数为3现在有2个请求Q1和Q2到来由于请求数少于线程数因此请求可以被并发执行。线程池中启动着的线程1和线程2会进行相应的处理而不会创建新线程除此之外线程处理完请求后也不会被销毁而是回到线程池中继续等待新的请求。
但如果现在同时有4个请求到来那么只有3个请求可以被并发处理而剩下的一个请求要么丢弃要么等待空闲线程。
在分布式系统中,每个请求都会由一个线程去进行处理。当请求太多系统处理不过来时,意味着线程池可能已经被耗尽(线程池中无空闲线程),因此当请求过多时,执行请求的并发线程数自然会随之增加,当超过一定的阈值(比如线程池中线程总数)时,需要采取一定的策略来进行流量控制。
在Sentinel中就采用了直接拒绝的方式即新来的请求会直接拒绝。
然后我们再看一下通过QPS指标进行流量控制吧。
QPS是指每秒的请求数大流量也就意味着QPS大。当QPS达到阈值时Sentinel提供了三种流量控制策略分别是直接拒绝、预热Warm Up和匀速排队。
直接拒绝是最直接也是最暴力的方式与并发线程数流量控制采取的方式一致就是当QPS达到系统设定的阈值时直接拒绝新来的请求。
这种策略乍一听起来确实不是很好,但对于系统处理能力确切已知的情况(即阈值设定为每秒能接受的最大处理请求数),却非常实用。当请求超出阈值时,可以直接拒绝,因为系统已经没有更多的能力来处理多余的请求了。因此,该策略适用于对系统处理能力确切已知的场景。
接下来我们看看预热。当系统的QPS长期处于一个较低水平时一旦发生流量骤增如果直接让系统每秒处理大量的请求可能会因为服务器处理能力不足导致系统崩溃。因此Sentinel提供了一种“预热”机制让系统的QPS缓慢增加在一定的时间内逐渐增加到上限。
下面以一个例子为例带你进一步理解预热的原理。如下图所示假设通常情况下系统每秒处理3个请求即QPS=3当用户请求增加时系统每秒处理的请求数相应增加但不会一下子提高很多。比如每秒增加1个处理请求逐步达到QPS=10的处理上限并不再继续增加从而避免大流量一下子导致系统故障。
可以看出,预热这种策略有点像是一种特殊的令牌桶:放令牌的速率通常保持在一个较低的水平,当流量突增时,放令牌的速率不会一下子提高到最高水平,而是会慢慢增加,直到增加到最大速率则不可再增加。因此,该策略与令牌桶策略的适用场景类似,即适用于具有突发特性的流量,且流量可以即时处理的场景。
匀速排队的思想,其实本质就是漏桶策略。它会严格控制系统每秒处理的请求数,请求数很多时,请求之间的间隔也会保持一致。
如图所示当QPS=5时每隔200ms才允许服务器处理下一个请求。假设请求队列中有10个请求瞬间到达服务器不会一下子全处理完而是按照请求的顺序每200ms处理一个请求直到处理完所有请求。这时处理的请求就像是在匀速排队因此得名。
该策略中系统会设定一个时间间隔T假设最大排队时长设置为6T上次请求通过的时刻为t1。当新的请求在t2时刻到来的话则进行判断首先查看是否还有其他请求在排队。如果没有请求在排队分两种情况
当t2 - t1的值大于或等于时间间隔T请求可以通过
当t2 - t1的值小于T时需要等待直到t2 - t1的值达到时间间隔T时才可以让请求通过。
而如果新请求到来时已经有请求在排队就需要计算该新请求的预期通过时间。比如有3个请求在排队则该新请求预期通过时间为t1+4T因为需要等到在该请求前面的请求都通过后该请求才可通过且两个请求通过的时间间隔必须达到T才可以。
另外若排队的请求过多新来的请求预期等待时间超出最大排队时长即等待时间超过6T时则直接拒接这个请求。
现在我想你应该理解了为什么说匀速排队策略本质就是漏桶策略了吧。因此,匀速排队的适用场景与漏桶策略类似,即适用于间隔性突发流量且流量不用即时处理的场景。
知识扩展:什么是拥塞控制?它与流量控制的区别是什么?
其实,在分布式领域拥塞控制与流量控制的区别还是蛮大的。为什么这么说呢?
今天,我们讲述的流量控制,主要是指业务上的流量,即用户请求。而拥塞控制通常针对的是网络上传输的数据,即网络上数据传输出现拥塞时应当如何控制。所以,这两个概念不是一回事儿。
但是,对于网络上数据的传输而言,流量控制与拥塞控制非常容易混淆。
网络数据传输中,流量控制是指控制发送方和接收方的传输和接收速率在双方都可以接受的范围,通常使用的方法是滑动窗口;而拥塞控制是通过检测网络状况,随时疏通网络,避免网络中过多数据堆积,导致无法传输数据,包括慢启动与拥塞避免方法。如果你想深入了解拥塞控制的相关内容,可以自行查阅计算机网络的相关书籍。
总结
今天,我主要带你学习了分布式高可靠技术中的流量控制。
首先,我以网络传输中的流量控制和电商系统的例子,和你引入了分布式系统中的流量控制,即控制每个服务器的请求数,以保证处理请求所需计算能力在服务器处理能力的上限之内,从而避免系统崩溃。
然后,我为你介绍了常见的流量控制策略,包括漏桶策略和令牌桶策略。其中,漏桶策略的核心是“宽进严出”,发送给服务器进行处理的请求速率固定,以避免超过服务器处理能力上限,导致系统崩溃,但这种方式不适合突发流量增加的场景。令牌桶策略的核心是,只要桶里有令牌,请求就可以被处理,只要在服务器处理能力内即可,所以适用于处理及时且处理速率非固定的场景。
最后我和你分享了阿里开源的Sentinel流量控制并介绍了通过并发线程数和通过QPS指标进行流量控制的两种方式。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
加油,相信通过本讲的学习,你对分布式系统中的流量控制有了一定的理解,也可以进一步对电商系统中抢购、秒杀中的流量控制问题进行分析了。加油,行动起来吧!
思考题
除了漏桶策略和令牌桶策略,你还知道哪些流量控制策略吗?它们的原理是什么呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,146 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
30 分布式高可用之故障隔离:当断不断,反受其乱
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在前面两篇文章中,我带你一起学习了分布式系统高可靠的关键技术,包括分布式负载均衡和流量控制。除了高可靠,在实际生产中,分布式系统的高可用问题也极其重要。
比如,在双十一的抢购高峰期,如果分布式系统不能满足高可用的特性,那么当大量用户同时抢购时就可能导致系统崩溃,无法提供服务,导致大量用户流失。
因此,在接下来的两篇文章,我将从故障隔离和恢复机制这两项关键技术入手,和你一起学习如何保证分布式系统的高可用。
今天,我们就先一起打卡分布式高可用中的故障隔离吧。
什么是故障隔离?
从字面意思来看,故障隔离就是,把故障通过某种方式与其他正常模块进行隔离,以保证某一模块出现故障后,不会影响其他模块。
其实,我们生活有很多故障隔离的例子,比如交通。一辆车就类似于分布式系统中的一个模块,当一辆车在高速公路上出现故障后,我们通常会将其停靠在紧急车道,或者在其前后设置故障指示牌,以防止其他车辆与其相撞,引起更大的交通事故。这种将故障车辆停靠在路边紧急车道或设置故障指标牌的方法,就是一种故障隔离。
现在我们回到分布式系统,故障隔离,就是采用一定的策略,以实现当某个模块故障时,不会影响其他模块继续提供服务,以保证整个系统的可用性。所以说,故障隔离,可以避免分布式系统出现大规模的故障,甚至是瘫痪,降低损失。
在分布式系统中,要实现故障隔离,通常需要在进行系统设计时,提前对可能出现的故障进行预防,以使得在出现故障后能实现故障隔离。此外,由于是提前设计预防的,因此故障隔离还可以帮助我们快速定位故障点。
也就是说,分布式系统中的故障隔离策略是在系统设计时就进行考虑,从预防的角度来实现故障发生时,该模块故障不会影响其他模块。因此,我今天与你介绍的故障隔离策略,是整个系统设计时,从高可用这个维度进行设计的策略。
好了,理解了故障隔离为什么可以提高分布式系统的可用性以后,我们再来看看实现故障隔离有哪些常见策略吧。
分布式故障隔离策略
分布式系统中的故障隔离策略有很多,大体上可以从两个维度来划分:
一类是以系统功能模块为粒度进行隔离。比如,通过系统功能/服务划分,将系统分为多个功能/服务模块,各个功能/服务模块之间实现松耦合,即一个功能/服务模块出现故障,不会影响其他功能/服务模块,根据功能模块或服务由线程执行还是进程执行,通常分为线程级隔离、进程级隔离。
另一类是,通过资源隔离来实现。比如,系统中各个模块拥有自己独立的资源,不会发生资源争抢,从而大大提升系统性能。根据资源所属粒度,通常包括进程级隔离(比如采用容器隔离)、虚拟机隔离、服务器隔离和机房隔离等。
基于这个分类,接下来,我将为你讲述三种比较常见的故障隔离策略,包括以功能模块为粒度进行隔离的线程级隔离和进程级隔离,以及以资源为隔离维度的资源隔离。
线程级隔离
线程级故障隔离,是指使用不同的线程池处理不同的请求任务。当某种请求任务出现故障时,负责其他请求任务的线程池不会受到影响,即会继续提供服务,从而实现故障的隔离。
如图所示,以电商购物平台为例,假设初期运行在单台机器的一个进程中,在这个进程中有三个线程池,分别负责订单任务、支付任务和配送任务。这样,当订单请求出现故障时,不会影响已下单用户的支付和仓库配送服务。
线程级的故障隔离策略,在生产环境中较为常用,尤其对于单体应用(单进程多线程的应用)。在单体应用场景下,应用被单个进程执行,但单进程中包括多个线程,因此该场景下,只需要实现线程级隔离即可,实现简单、效果好,因此是一种很常用的方式。
系统实现线程级隔离后,线程间的通信通常使用共享变量来实现。简单地说,共享变量就是一个进程中的全局变量,在进程的各个线程间可以同时使用。这种通信方式,实现简单且效果明显。
进程级隔离
随着业务逐渐扩大,业务系统也会越来越复杂,单体应用可能无法满足公司与用户的需求,这时候就需要对系统进行拆分。
一种常用的方式就是,将系统按照功能分为不同的进程,分布到相同或不同的机器中。如果系统的进程分布到不同机器上的话,从资源的角度来看,也可以说成是主机级的故障隔离。因为从另一个层面看,系统确实分布到了不同机器上,当某个机器出现故障时,不会对其他机器造成影响。
如图所示,电商购物平台可以分为订单系统、支付系统和配送系统三部分。这三个子系统可以采用三个不同的进程来服务用户。
这就是一个进程级的故障隔离方案,即不同的子系统对应不同的进程,某一个子系统出现故障,都不会导致其他系统不可用。
系统实现进程级隔离后进程间的协同必须通过进程间通信IPC来实现。进程间通信有很多方式大体可以分为以下两类
如果进程都在同一台机器上,则可以通过管道、消息队列、信号量、共享内存等方式,来实现;
如果进程分布在不同机器上则可以通过远程调用来实现你可以再回顾下第19篇文章中的相关内容。
进程级故障隔离,目前在分布式应用中应用广泛,比如常见的电商、火车票购买等业务都可以采用。
资源隔离
前面介绍的是以服务或功能模块为粒度进行隔离的,下面我们一起看下从资源角度进行隔离是怎么做的?
简单来说,资源隔离就是将分布式系统的所有资源分成几个部分,每部分资源负责一个模块,这样系统各个模块就不会争抢资源,即资源之间互不干扰。这种方式不仅可以提高硬件资源利用率,也便于系统的维护与管理,可以大幅提升系统性能。
微服务就是一个典型的例子。当前很多公司都在将自己的业务系统微服务化比如亚马逊、阿里、华为、微软等。在微服务的理念中是尽可能将服务最小化服务与服务之间进行解耦合包括运行环境的相互隔离等。比如现在通常采用容器进行隔离我在第9篇文章中分享的Mesos、Kubernetes等可实现容器管理与调度而Mesos和Kuberntes的上层应用很多都是微服务。
实际上,在微服务框架中,一个服务通常对应一个容器,而一个容器其实就是操作系统中一个进程,不同容器负责不同的服务,就类似于刚才所讲的:不同进程负责系统不同的功能模块。
如图所示,如果将电商购物平台微服务化,则可以启动三个容器,分别负责订单服务、支付服务和配送服务。一个容器对应一个进程,因此微服务框架本质上还是一种进程级故障隔离策略。
但与进程级隔离不同的是,微服务框架采用容器进行故障隔离。容器虽然本质上是操作系统的一个进程,但具备普通进程不具备的特性,比如资源隔离。
一个普通进程有很大的计算或内存需求时可能会占满物理机上所有的CPU、内存资源导致其他进程没有资源可用引发进程间的资源争夺
但容器可以实现资源限制让每个容器占用的资源都有一个上限比如CPU、内存均会设置一个上限值这个上限值限定了该容器的处理能力就好比一台服务器具有资源上限值一样。因此一个容器使用的资源不会影响其他容器的资源从而避免资源争抢提高性能。
那到底什么是容器呢? 容器是一种虚拟化技术,可以为应用提供一整套运行环境。容器通过限制自身使用的资源来实现资源隔离,从而让容器就像一个个的“集装箱”:容量固定,存放着任意的物品。
目前比较常用的容器是Docker。Docker主要使用Linux内核中的Linux Cgroups模块来设置容器的资源上限包括CPU、内存、磁盘、网络带宽等。通过Cgroups模块容器间就形成了资源隔离从而避免了容器间的资源争夺提升了系统性能。
通过容器进行资源隔离后需要容器进行网络配置来进行容器间的通信。比如Docker默认是通过建立虚拟网桥来实现容器间通信的。如果你想深入了解容器网络配置相关的内容可以自行查阅Docker官方文档中网络部分的内容。
除了容器级别的资源隔离,虚拟机级别的隔离也是资源隔离的一种常用手段,一台物理机可以安装多个虚拟机,每个虚拟机都会分配一定的资源,即进行资源隔离。除此之外,主机级别的隔离也可以说是一种资源隔离,每台机器的资源是独享的,不会与其他机器发生资源争夺,从而做到资源隔离。
除了以上所讲到的故障隔离策略,其实还有一些更粗粒度的隔离策略,比如集群隔离、机房隔离等,这些策略主要是跨集群或跨地域的隔离策略。这些粗粒度的隔离策略,不仅可以根据系统功能/服务等维度对系统进行划分,比如每个功能/服务由一个集群或一个机房单独负责,而且也是一种资源隔离策略,即集群间或机房间资源互相隔离,不会发生资源争夺,互不影响。
故障隔离策略综合对比
以上,就是分布式应用中常用的几种故障隔离策略了。接下来,我再通过一个表格进行对比分析,以便于你理解和记忆。
知识扩展:从用户角度看,有哪些常用的故障隔离方案?
无论是按照功能/服务划分模块,实现进程级、虚拟机级等故障隔离,还是按照系统资源进行故障隔离,它们都是一种针对服务方的故障隔离手段。除此之外,还有一种故障隔离策略是,针对用户的,即用户级别的故障隔离。
用户级别的故障隔离是指,将不同用户分开,当系统出现故障时,只影响部分用户,而不是全体用户。比如,发布产品前大多会有一个“灰度发布”过程, 就是先发布给一小部分用户进行测试,如果没问题再大规模发布;如果有问题也只是影响一小部分用户。这就是一种典型的用户级别的故障隔离。
常用的用户级别故障隔离策略有数据分片、负载均衡等。你可以再回顾下第25篇文章中关于数据分片以及第28篇文章中关于负载均衡技术的相关内容。
以数据分片为例,系统可以将不同用户的数据存储到不同的数据库,即一个数据库只存储部分用户的信息。这样当某个数据库出现故障时,仅影响该故障数据库存储的用户,而不会影响全部用户。
负载均衡也是这个道理。当处理请求的某个服务器出现故障时,只影响该故障服务器负责的用户请求,而不会影响其他服务器负责的用户请求。
总结
今天,我主要带你学习了分布式高可用技术中的故障隔离技术。
首先,我以汽车故障为例,带你了解了故障隔离的概念,并引出分布式系统中的故障隔离。分布式系统中的故障隔离技术是,在进行分布式系统可用性设计时,考虑故障隔离的设计,也就是提前预防或避免出现故障后对整个系统造成影响。
然后,我与你介绍了常见的故障隔离策略,包括线程级隔离、进程级隔离和资源隔离策略。其中,线程级隔离和进程级隔离是从对功能/服务模块进行隔离的维度进行划分的,借助了系统本身对线程或进程的隔离机制实现故障隔离;资源隔离是从资源的维度进行隔离,主要通过容器、服务器、集群、机房等物理资源维度进行隔离。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
加油,相信通过今天的学习,你对分布式系统中的故障隔离技术有了一定的理解,也可以进一步对容器、虚拟机等的隔离技术进行深入分析了。加油,行动起来吧!
思考题
分布式系统难免会发生故障,那么评判一个系统故障的指标有哪些呢?或者说通过哪些指标可以判断故障的健康度呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,183 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
31 分布式高可用之故障恢复:知错能改,善莫大焉
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在上一篇文章,我带你学习了故障隔离。故障隔离的目的是,对故障组件进行隔离,以避免其影响系统中的其他组件,尽可能保证分布式系统的可用性。
在分布式系统中故障在所难免发生故障后仅仅进行隔离还远远不够还需要进行故障恢复。比如现在集群中有3个节点节点1故障后对节点1进行隔离如果节点2、节点3紧接着故障了又隔离了这两个节点。那么整个集群就无法继续提供服务了何谈分布式系统的高可用呢
为了解决这种问题,分布式领域还有一个关键技术来保证系统的高可用,即故障恢复。
接下来,我们就一起打卡分布式系统的故障恢复技术吧。
分布式故障基础知识
在介绍故障恢复之前,我先与你说说分布式系统中会有哪些故障类型。
故障类型
在任何一个分布式系统中,故障都是不可避免的。这里的故障,通常包括两类:
一类是物理故障,比如硬盘损坏、断电断网、硬件升级等;
另一类是软件层故障比如系统存在Bug导致系统崩溃、系统负载过高导致系统崩溃等。
在讨论分布式系统故障时,我们通常还会从是否是网络导致的故障的角度来进行故障划分,包括节点故障和网络故障,而这两类故障可能同时包括物理故障和软件层故障。由于软件层故障和具体的程序实现等相关,因此主要由开发者根据自己的实现去解决;而物理故障通常具有很多共同特征,因此今天我主要针对物理故障导致软件不可用的情况进行讲解。
首先,我们看一下节点故障。
简单地说节点故障就是单个机器自身出现故障。比如由机器A、B……Z构成的分布式集群中机器A自身出现故障而不是非机器之间的网络连接出现故障就是节点故障。
节点故障有很多种,大体可以分为两类:
一类是硬件故障,比如机器硬盘损坏、内存接触不良等;
另一类是软件故障,比如由于请求过多,超过服务器处理能力上限,导致无法处理,又或者是机器被攻击,导致机器瘫痪等。
节点故障在软件层的表现结果是,该机器无法为用户提供服务。
其次,我们看一下网络故障。
简单地说网络故障就是分布式集群中节点之间无法完成通信。比如由机器AB……Z构成的分布式集群中机器间比如机器A和B之间无法完成通信就属于网络故障。
网络故障也有很多种比如路由器故障、DNS故障、网络线路断裂等。这些物理故障在软件层的表现结果是机器间无法通信影响分布式应用正常提供服务。
了解了故障的类型,我们还要搞明白如何检查到故障,也就是如何进行故障检测,因为这是故障恢复的前提。
故障检测
故障检测,就是指通过一定的方式识别或发现故障。就好比,我们把火灾、地震等危险事件看作是故障,采用火灾报警器、地震仪等来检测发现火灾或地震。
如果可以提前检测到事件的发生,就能将损失降到最小。在分布式系统中,检测硬件故障通常比较麻烦,因此会通过查看软件层的表现结果来进行故障检测。比如,网络故障导致服务器之间无法通信,因此就可以通过检测服务器之间是否可以通信(比如,服务器之间心跳包是否可以正常地发送和接收),来检测是否存在网络故障。
关于故障检测的具体策略,我会在后文与你展开。当检测到故障后,就需要进行故障恢复了。
故障恢复
故障恢复,就是指修复分布式系统中出现的故障,使系统恢复正常。简单来说,故障恢复就是故障发生之后的弥补方案,可以理解为对故障进行修正或修复,以保证服务正常运行,有点类似“知错能改,善莫大焉”。
接下来,我们就具体看看故障检测和故障恢复的原理或者说策略吧。
分布式故障检测原理
在分布式系统中,常见的故障检测方法是心跳机制。基于心跳进行故障检测的策略主要分为两类,固定心跳检测策略和根据历史心跳信息预测故障策略。
还记得我在第22篇文章中与你介绍的通过心跳方式判断集中式架构和非集中式架构中节点是否存活的方法吗其实这里用到的就是固定心跳检测策略。具体的检测原理你可以再回顾下这篇文章。
所以接下来,我主要与你分享基于历史心跳消息预测故障的策略,也就是我们常说的\(φ\)值故障检测。
\(φ\)值故障检测是基于心跳间隔符合正态分布的假设进行计算的。其中,\(φ\)值是用来评估心跳是否超时的概率,是对心跳间隔的概率求对数,将非整数转换为整数以便于理解。
\(φ\)值故障检测方法中,通常会设置一个阈值Ф,若当前心跳计算得到的\(φ≥Ф\),则判断心跳超时,否则心跳未超时。
那么,\(φ\)值是如何计算的呢?
从流程上来讲,\(φ\)值的计算可以分为三步,即:
采样窗口存储心跳到达的时间;
通过样本计算出心跳到达时间间隔的分布;
使用得到的正态分布计算当前的\(φ\)值。
接下来,我们就具体看看这三个阶段吧。
第一步:采样窗口存储心跳到达的时间。
采样窗口就是一个具有固定容量的容器一般存储近k次的心跳信息每次心跳到达时会将到达时间存储到采样窗口如果采样窗口已满则会删除窗口中最旧的数据。
比如采样窗口最多存储最近10次心跳到达的时间t1t2…… t10当第11次心跳到来时采样窗口会将t1删除存入t11。到达时间的间隔很容易得到比如第11次心跳到来后到达时间的间隔是t3 - t2t4 t3……t11 t10。通过这些采样数据可以计算出样本的平均值μ和方差 σ2以便后面计算\(φ\)值。当然,随着新的心跳到来,这些数据会进行相应的更新。
第二步:通过样本计算出心跳到达时间间隔的分布。
\(φ\)值故障检测是假设心跳到达时间间隔的分布遵循正态分布假设Plater(t)表示接收到上一次心跳之后t个时间片能收到下一次心跳的概率则通过第一步中得到的样本平均值µ和方差 σ2得到Plater(t)的计算结果如下:
\[P\_{later}(t)=\\frac{1}{\\sigma\\sqrt{2\\pi}}\\int\_{t}^{+\\infty}e^{-\\frac{(x-u)^{2}}{2\\sigma^{2}}}dx=1-F(t)\]其中F(t)是具有均值µ和方差 σ2的正态分布的累积分布函数。
第三步:使用得到的正态分布计算当前的\(φ\)值。
假设Tlast表示最近一次接收到心跳的时间tnow表示当前时间。将Tlast、tnow和第二步求得的Plater(t),带入以下公式即可求得\(φ\)值:
\[\\varphi(t\_{now})\\xlongequal{def}-log\_{10}(P\_{later}(t\_{now}-T\_{last}))\]求得\(φ\)值后,与阈值Ф进行比较,即可判断节点是否发生故障。
以上就是\(φ\)值故障检测策略的介绍它的基本思想是利用了历史心跳信息来降低误判的可能性。如果你想了解这个策略更详细的内容可以参考“The \(φ\) Accrual Failure Detector”这篇论文。
这篇论文中提到,当阈值Ф=1时误判的可能性大约为10%;Ф=2时误判的可能性大约为1%;Ф=3时误判的可能性大约为0.1% ······
通过以上讲解,可以看出,\(φ\)值故障检测策略可以根据历史心跳信息动态预测下一次心跳是否超时并可以通过设置阈值来自由调控误判的可能性。目前该策略已被应用到一些框架中比如我们熟悉的Akka集群的故障检测便是采用了\(φ\)值故障检测策略。
当采用故障检测策略检测到故障后,故障如何恢复呢?接下来,我们就一起看看故障恢复策略吧。
故障恢复策略
关于故障恢复策略,我从单节点故障和网络故障两个维度展开。
对于单节点故障问题,往往采取主备策略,即当主节点故障后,从备节点中选出一个作为新的主节点,以继续提供服务。这种备升主的方式比较好理解。
如下图所示用户A访问分布式集群时一直是与Master交互的但当Master故障后其他Slave会通过分布式选举算法选出一个新的主节点。
假设从Slave 1、Slave 2和Slave 3中选举出Slave 2作为新的Master则Slave 2需要承担原来Master的职责继续为用户提供服务因此当用户A再次访问集群时提供服务的是新选出的Master也就是Slave 2。这就是备升主的过程。
关于分布式选举算法的相关内容你可以再回顾下第4篇文章。
从用户A的角度来看并不会感受到服务有什么异常因为依旧可以正常访问集群。因此主备策略可以大大提高分布式系统的可用性在分布式系统中随处可见。比如第10篇文章涉及的Redis集群、第23篇文章中讲到的ZooKeeper集群等都是采用了这种主备策略来做故障恢复。
而对于网络故障问题的解决方案简单来说就是C、A、P选择的问题即在分布式系统的可用性和数据一致性之间做权衡。根据不同的应用场景选择不同的解决方案。
当分布式系统中出现网络故障时对于高可用性要求严格的系统比如要求必须及时响应用户的场景就需要采用保AP弃C的策略对于数据一致性有严格要求的系统比如银行、金融系统等场景就需要采用保CP弃A的策略。具体内容你可以再回顾下第23篇文章。
其实网络故障恢复问题也可以看作数据复制的问题即网络故障恢复后节点间数据同步的问题。还记得第26篇文章中的同步复制、异步复制和半同步复制技术吗其中半同步复制技术因为既能有效保证数据安全又能满足系统高性能的要求所以最受欢迎被大多数分布式系统采用。
关于如何通过半同步复制技术来进行网络故障恢复你可以再回顾下第26篇文章的相关内容。
其实,节点故障和网络故障也有交叉的地方,比如网络故障产生的原因可能是节点故障,即因为节点故障导致节点间无法通信,而不是纯粹的网络链路问题。这种情况有两种可能性,一种是节点临时性故障,即一段时间后就会恢复;一种是节点永久性故障,即节点不会恢复。针对第一种情况,只需等到故障恢复后,数据进行同步即可;第二种情况则需要备升主策略来解决。
知识扩展:固定心跳检测和基于历史心跳信息预测故障的策略,各有什么特点呢?
首先,我们看一下固定心跳检测。
固定心跳检测的核心是固定周期T秒发送心跳若连续k次未收到心跳回复时间T内则判断心跳超时的时间为k*T秒。可以看出k和T的设置非常重要。
比如对于要求秒级故障检测的场景时延敏感性场景则k*T≤1s因此需要将T设置为ms级比如200msk设置为1000/200=5次。但这样一来容易导致误判。因为判断超时的时间设置得太短很可能是系统做内存回收或系统本身有高任务在运行导致心跳回复延后。
而对于时延不太敏感的场景k或T可以设置得大一些降低误判率但却会增加发现故障的时间。
接下来,我们看一下\(φ\)值故障检测。\(φ\)值故障检测是基于心跳间隔符合正态分布的假设,通过对历史心跳数据采样来预测当前心跳是否超时的。也就是说,心跳间隔符合比较平稳或符合规律的情况下,比较适合,但对于具有突发情况或心跳间隔无规律的场景误判率比较高。
在网络状况确定且比较稳定的场景下大多数系统会采用固定心跳检测策略因为其可以根据网络状况与业务场景自主设定合适的k和T值简单有效而当网络状况有所变化且变化有规律的场景则可以使用\(φ\)值故障检测策略。
总结
今天,我主要带你学习了分布式高可用技术中的故障恢复技术。
首先我为你介绍了分布式系统中的故障类型主要包括物理故障和软件故障软件故障主要是由于程序或软件Bug等导致通常由开发者在开发或测试过程中解决而物理故障导致软件不可用的故障类型主要分为两类节点故障和网络故障。
其次我为你介绍了故障检测方法。故障检测方法主要是心跳检测方法包括固定心跳策略和基于历史信息的心跳策略。其中固定心跳策略的具体原理见第22篇文章中的相关内容基于历史心跳策略的核心是通过统计历史数据规律以预测当前心跳是否超时以进行故障检测
紧接着我为你介绍了故障恢复策略主要涉及备升主、数据复制等关键技术相关关键技术原理你可以再回顾第4篇文章中的分布式选举算法以及第26篇文章中的数据复制技术。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
加油,相信通过今天的学习,分布式系统中的故障恢复技术对你来说不再陌生了,你可以根据业务场景设计出对应的故障检测和故障恢复策略了。行动起来吧,加油!
思考题
在分布式系统中,网络分区是一个非常重要的故障问题。那么如何判断网络分区呢?以及出现网络分区后,如何处理呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,156 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
32 答疑篇:如何判断并解决网络分区问题?
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
到目前为止,“分布式技术原理与算法解析”专栏已经接近尾声了。在这里,我首先要感谢你坚持学习每一篇文章,以及对每一道思考题的积极思考与讨论,并在此基础上扩展了类似问题。
比如@Jackey@Eternal@leslie@mt11912@小白啊@随心而至等同学,一直在跟着专栏的更新节奏学习,并非常积极地在留言区留言讨论、总结自己的理解,并查询相关资料补充文中未讲解到或没有深入展开的问题。
今天,我梳理了文后的留言,发现大家对最近几篇文章介绍的分布式高可靠问题特别感兴趣,特别是我没有详细展开的网络分区问题。
比如在第4篇文章“分布式选举国不可一日无君”中我给你留下的思考题是集群中是否会存在双主的场景很多同学提到双主是网络分区导致的。
再比如在第31篇文章“分布式高可用之故障恢复知错能改善莫大焉”中我给你留下的思考题是如何判断以及处理网络分区。
因此,在今天这篇文章中,我将会与你深入探讨网络分区问题,以帮助你进一步理解并解决业务中的故障恢复问题。
什么是网络分区?
我们先来看看网络分区到底是什么吧。在第31篇文章分享故障恢复时我与你介绍了故障类型中的网络故障网络分区就是其中的一种故障类型。
通常情况下,网络分区指的是在分布式集群中,节点之间由于网络不通,导致集群中节点形成不同的子集,子集中节点间的网络相通,而子集和子集间网络不通。也可以说,网络分区是子集与子集之间在网络上相互隔离了。
那么,应该如何判断是否发生了网络分区呢?
如何判断是否发生了网络分区?
在分布式集群中,不同的集群架构网络分区的形态略有不同。所以,要判断是否发生了网络分区,我们需要弄清楚不同的分布式集群架构,即集中式架构和非集中式架构中的网络分区形态是什么样的。
首先,我们来看一下集中式架构的网络分区形态。
集中式架构中Master节点通常以一主多备的形式部署Slave节点与Master节点相连接Master节点的主和备之间会通过心跳相互通信。
以Master节点主备部署为例如下图所示集中式架构中的网络分区主要是主节点与备节点之间网络不通且一部分Slave节点只能与主Master节点连通另一部分只能与备Master节点连通。
图1 集中式架构中网络分区的形态
然后,我们再来看看非集中式架构中的网络分区形态。
如下图所示非集中式架构中节点是对称的因此网络分区的形态是形成不同子集子集内节点间可互相通信而子集之间的节点不可通信。比如子集群1中Node1、Node2和Node4可以相互通信子集群2中Node3和Node5也可以相互通信但子集群1和子集群2之间网络不通。
图2 非集中式架构中网络分区的形态
从集中式和非集中式这两种分布式集群架构的网络分区形态可以看出,要判断是否形成网络分区,最朴素的方法就是判断节点之间心跳是否超时,然后将心跳可达的节点归属到一个子集中。
由于非集中式系统中每个节点都是对等的、提供的服务相同所以当多个子集群之间不可达或部分节点出现故障后尽管提供的服务质量SLA可能会下降但并不影响这些剩余节点或子集群对外提供服务。所以接下来我将与你重点讨论集中式系统的网络分区问题。
网络分区最微妙的地方在哪里?
在工作和生活中遇到一个问题,你的本能反应估计是,有问题就解决问题好了。而网络分区最微妙的地方在于,你很难通过程序去判断问题到底出在哪里,而只能通过心跳等手段知道部分节点的网络不可达了。
但,导致节点不可达的原因有很多,有可能是网络的原因,也有可能是节点本身的问题。也就是说,我们无法通过一些症状就判断出是否真的产生了分区。另外,你也很难通过程序去判断这个问题是不是很快就会被恢复。这也是应对网络分区问题最微妙的地方。
网络分区出现概率较高的场景是什么?
在第31篇文章留言区中有同学提到
个人理解,网络分区故障一般是针对不同集群来说的,单个集群一般在同一个网络中,集群内的单点故障并不会出现网络分区。当整个集群的网络出故障时,才会有分区的说法。能想到检测办法是,单独有机器对不同集群的主节点进行心跳检测来判断。
首先,我要澄清的是,我们说的网络分区肯定是就同一个集群而言的。对于不同集群来说,正是因为集群间本就没有太多的交互,才需要从逻辑上分割成不同的集群,这些逻辑上不同的集群本就是可以独立对外提供服务的。
当集群跨多个网络时,确实正如这位同学所说,从概率上讲相对容易出现网络分区的情况,比如一个业务集群部署在多个数据中心时。所以,集群跨多网络部署时,就是网络分区出现概率较高的场景。
接下来,我们看看如何处理网络分区吧。
网络分区有哪些常见的处理方法?
为了不影响分布式系统的高可用性,检测到网络分区后,我们就需要尽快地进行处理。
假如,我们采用一种非常激进的方式去处理,即一旦发现节点不可达,则将不可达节点从现有集群中剔除,并在这个新集群中选出新的主。
以图1所示集中式集群为例当备Master、Slave3和Slave4节点检测到主Master、Slave1和Slave2节点不可达时剔除这些不可达节点后备Master升主连同Slave3和Slave4节点组成一个新的集群。
如果不可达是由于节点故障导致的那么这种策略没有任何问题。这些剩余节点组成的集群可以继续对外提供服务。但如果不可达是因为网络故障引起的那么集群中的另一个子集即主Master、Slave1和Slave2也会采用同样的策略仍然对外提供服务。这时集群就会出现第22篇文章中讲到的双主问题了。
假如,我们采用一种保守的方式去处理,即节点一旦发现某些节点不可达,则直接停止自己的服务。这样确实解决了双主的问题,但因为不同分区之间的不可达是相互的,且所有的分区都采取了这种停服策略,就会导致系统中所有的节点都停止服务,整个系统完全不可用。这显然也不是我们想看到的。
那么,当系统中出现节点不可达后,如何在不出现双主的情况下,尽可能地提升系统的可用性呢?或者说,有没有什么更均衡的策略呢?
接下来我就与你分享四种均衡的网络分区处理方法即Static Quorum、Keep Majority、设置仲裁机制和基于共享资源的方式。
方法一通过Static Quorum处理网络分区
Static Quorum是一种固定票数的策略。在系统启动之前先设置一个固定票数。当发生网络分区后如果一个分区中的节点数大于等于这个固定票数则该分区为活动分区。
为了保证发生分区后,不会出现多个活动分区,导致出现双主或多主的问题,需要对固定票数的取值进行一些约束,即:固定票数≤ 总节点数≤2*固定票数 - 1。
这个策略的优点是,简单、容易实现,但却存在两个问题:
一是对于分区数比较少的时候比方2个分区时该策略很容易选出一个唯一的活动分区。但是当活动分区非常多的时候由于各个分区的票数分散不容易找到一个满足条件的分区没有活动分区也就意味着整个集群不可用了。
二是,由于这种策略里固定票数是固定不变的,所以不适用于集群中有动态节点加入的场景。
方法二通过Keep Majority处理网络分区
顾名思义Keep Majority就是保留具备大多数节点的子集群。由于不限定每个分区的节点数超过一个固定的票数所以可以应用于动态节点加入的场景。
假设集群数为n出现网络分区后保留的子集群为节点数w≥n/2的集群。为防止出现双主或两个集群同时工作的情况通常将集群总节点数n设置为奇数。
可想而知若集群总数为偶数比如图1集中式架构的例子中子集群1和2都包含2个Slave节点就会导致两个子集群同时存活在业务场景只允许一个主的情况下会导致业务运行不正确。
那么,如果真的出现了集群总节点数为偶数,两个子集群节点数均为总数一半时,又应该如何解决分区问题呢?
这时我们可以在Keep Majority的基础上叠加一些策略比如保留集群节点ID最小的节点所在的子集群。如图1所示假设集群节点总数为6现在因为网络故障形成网络分区子集群1{主MasterSlave1, Slave2}和子集群2{备MasterSlave3, Slave4}假设Slave1是ID最小的节点那么此时要保留包含Slave1的子集群1。
虽然Keep Majority方法可以解决动态节点加入的问题但也不适用于产生多分区的场景。因为随着分区数增多、节点分散也难以在众多分区中出现一个节点数w≥n/2的分区。
前面我讲到集群跨多个网络部署时更容易产生网络分区因此我不推荐采用Static Quorum和Keep Majority方法去处理跨多网络集群的网络分区问题。
方法三:通过设置仲裁机制处理网络分区
设置仲裁机制的核心是,引入一个第三方组件或节点作为仲裁者,该仲裁者可以与集群中的所有节点相连接,集群中所有节点将自己的心跳信息上报给这个中心节点。因此,该中心节点拥有全局心跳信息,可以根据全局心跳信息判断出有多少个分区。当出现网络分区后,由仲裁者确定保留哪个子集群,舍弃哪些子集群。
如下图所示假设引入Node0作为第三个节点该节点IP为10.12.24.35当出现网络分区子集群1{Node1, Node3}和子集群2{Node2, Node4}时每个子集群中选择一个Leader节点并ping一下Node0的IP能ping通则保留否则舍弃。比如下图中子集群1可以ping通子集群2 ping不通因此保留子集群1。
图3 通过设置仲裁机制处理网络分区
方法四:基于共享资源的方式处理网络分区
说到共享资源,我们需要先回顾下分布式锁的相关知识。分布式锁是实现多个进程有序、避免冲突地访问共享资源的一种方式。
基于共享资源处理网络分区的核心其实就类似于分布式锁的机制。也就是哪个子集群获得共享资源的锁就保留该子集群。获得锁的集群提供服务只有当该集群释放锁之后其他集群才可以获取锁。关于锁的管理和获取你可以再回顾下第7篇文章中的相关内容。
这种方式的问题是,如果获取锁的节点发生故障,但未释放锁,会导致其他子集群不可用。因此,这种方式适用于获取锁的节点可靠性有一定保证的场景。
基于仲裁和共享资源的网络分区处理方法,其实都是依赖一个三方的节点或组件,然后借助这个第三方来保证系统中同时只有一个活动分区。所以,这两种处理方法适用于有一个稳定可靠的三方节点或组件的场景。
总结
今天,我与你进一步展开了分布式系统中的网络分区问题,以加深你对网络分区问题的检测、处理方式的理解,并帮助你在实践应用中处理网络分区问题。
关于网络分区的处理方法其本质就是在产生分区后选出一个分区保证同时最多有一个分区对外提供服务。基于此我为你梳理了四种常见的处理方法包括Static Quorum、Keep Majority、设置仲裁机制和基于共享资源的方式。
其中基于Static Quorum的方法因为涉及固定票数策略所以不适用于处理多个分区以及有动态节点加入的场景基于Keep Majority的方法可以解决动态节点场景下分区问题但因为要求子集群节点数≥1/2总节点数所以也不适用于处理多个分区的情况而基于仲裁和共享资源的网络分区处理方法其实都是依赖一个三方的节点或组件所以适用于有一个稳定可靠的三方节点或组件的场景。
如果还有哪些思考题或者留言问题,还没来得及扩展的话,你可以留言给我,后续我会再找机会进行解答。最后,我要和你说的是,和我一起打卡分布式核心技术,一起遇见更优秀的自己吧。
篇幅所限,留言区见。
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,143 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
33 知识串联:以购买火车票的流程串联分布式核心技术
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
还记得在专栏之初,我和你分享的“分布式四纵四横知识体系”吗?截止到目前,我已经带你学习了四横和三纵,包括分布式计算、分布式存储与管理、分布式通信、分布式资源池化、分布式协同、分布式调度和分布式追踪与高可用的关键技术(由于分布式追踪、分布式部署虽属于支撑技术,但并不会影响业务的构成,因此我没有在专栏中展开)。
但学以致用才是最终目的,所以在接下来的模块中,我将通过两篇总结性质的文章,为你串联起前面讲到的核心知识点,看看它们在业务中是如何应用的。
今天,我就先以购买火车票的流程,带你串联下整个专栏涉及的分布式核心技术吧。
首先,为方便你理解,并抓住其中涉及的核心技术,我对购买火车票的流程做了一个简化,大致划分为三大核心步骤:
首先,铁路局向购票系统发布火车票;
然后,用户通过系统查询火车票,找到需要的火车票后购买;
最后,购票系统给用户响应,完成购票。
这个流程看似简单,但涉及了我们之前讲过的很多知识。
这里,我有个小建议,在学习后面的内容前,你可以先自己思考下这个过程涉及了哪些知识点,然后再与我接下来的讲述进行对比,以验证自己对之前内容的掌握程度。这样一来,你可以加深对已掌握知识的理解深度,也可以查漏补缺进而有针对性地复习其他内容。
那么接下来,我就主要分为三部分进行讲解:铁路局发布火车票、用户查询火车票,以及用户购买火车票。
铁路局发布火车票
铁路局发布火车票的过程,主要涉及分布式数据存储这一站的知识,包括存储系统三要素、数据分布和数据复制等技术。
铁路局向购票系统发布火车票的过程,主要是将火车票信息发送到服务器集群进行存储,需要用到存储系统三要素的相关技术。其中,铁路局就是存储系统三要素中的“顾客”,火车票存储到具体哪个服务器需要构建数据索引,也就是我们说的三要素中的“导购”,而存储数据的服务器就是三要素中的“货架”。
由于涉及多个服务器存储火车票信息,不可避免地需要考虑服务器之间数据存储的均衡,以及快速确定火车票信息存储位置以方便后续查询和购买火车票。因此,这里需要用到分布式存储中的数据分布技术。
铁路局按照火车线路将火车票发布到不同的服务器上,即在数据分片技术中提到的按照数据范围进行分片。比如,京广线的车票信息存储在京广线服务器集群、京沪线的车票信息存储在京沪线服务器集群等。
除此之外为了保证可靠性也就是当一台服务器故障后该服务器存储的火车票信息可以恢复或不丢失通常会进行数据备份。也就是说同一份数据可能会有多台服务器一起存储比如京广线的火车票数据存储到服务器A1、A2和A3上京沪线的火车票数据存储在服务器B1、B2和B3上。
而数据备份用到的就是分布式存储中的数据复制技术。由于同一份数据被多台服务器存储自然就需要保证数据的一致性。关于数据一致性你可以参考CAP理论这篇文章。
接下来,我们再看看用户查询火车票过程中涉及了哪些相关技术吧。
用户查询火车票
对于用户查询火车票来说,大致过程是用户向服务器发起查询请求,服务器根据用户请求,通过数据索引,也就是导购技术,定位到火车票信息存储的位置,然后获取数据返回给用户。
从这个流程可以看出,服务器接收用户查询请求是第一步。
正常情况下,用户并发请求量比较小,很少会出现服务器能力有限导致系统崩溃的情况。但,遇到节假日或春节,用户请求量通常会非常大,这时如果不采取一定策略的话,大概率会因为服务器能力受限导致系统崩溃。
而这里的策略,通常就是保证分布式高可靠的负载均衡和流量控制。比如,每年春运,火车票发布的瞬间,就有大量的用户抢票,如果购票系统后台不使用负载均衡和流量控制的话,服务器一下子就被击垮了。
除此之外,服务器还不可避免地会出现一些小故障,比如磁盘损坏、网络故障等问题。如何检测服务器故障,以及如何进行故障恢复,就需要用到分布式高可用的故障隔离与故障恢复的相关技术了。
接收用户请求后,接下来需要将请求转发至相应的服务器集群,然后再从中选择某一台服务器处理用户请求,也就是获取数据并返回给用户。本质上,这就是在进行数据索引,设计分布式数据存储中的数据分布方式的相关技术。
以用户查询从北京到上海的火车票信息为例,查询流程如下:
首先,根据查询条件,系统将请求转发至存储京沪线火车票信息的服务器集群中;
然后,服务器集群再使用一次负载均衡,比如使用轮询算法, 将请求转发至某一台服务器;
最后,这台服务器将火车票的车次、余票等信息返回给用户。
在这个过程中,还可以使用限流算法,比如漏桶、令牌桶等策略来限制用户流量,保证系统高可用。
除此之外在存储火车票信息的服务器中各个服务器的服务单独运行也就相当于做了一定的故障隔离比如图中的服务器A1~A3、B1~B3。
当然,这里还需要注意一个问题。在上面的过程中,我一直提到的是服务器集群。既然是集群,就会涉及集群架构、分布式选主等策略。
以集中式架构Master/Slave为例服务器集群中会通过分布式选举算法选出一个MasterMaster和其他服务器节点之间会维持心跳并通过心跳来感知服务器节点的存活状态。比如京广线服务器集群选出的Master节点为A2A2与A1和A3之间一直维持心跳。
具体的集群架构原理,你可以参考“第二站:分布式资源管理与负载调度”;而分布式选主的原理,你可以再回顾“第一站:分布式协同与同步”的相关内容。
当集群中Master节点故障后会从其他节点中重新选举出一个Master节点继续为用户提供服务也就是备升主以保证服务的可用性。这里备升主就是一种故障恢复策略。
最后,我们再来分析下用户购买火车票的过程。
用户购买火车票
用户购买火车票的过程与用户查询火车票的过程非常相似,唯一不同的是会造成数据库的变化。换句话说,用户查询火车票是读请求,而购买火车票相当于一个写请求。那么,写请求又会造成什么新问题呢?
写请求与读请求的区别是写请求会造成数据的变化因此相对于查询火车票购买火车票的过程涉及的技术问题会多一些但多出的无非就是数据的一致性问题。谈到数据的一致性我们就会想到CAP理论、数据复制技术、分布式事务、分布式锁等。其实不仅仅是购买火车票任何一个简单的购买操作或写操作中都会涉及这些分布式知识。
本质上讲,每次购买火车票的操作,就是一个分布式事务,要么执行成功要么执行失败。
当用户购买了火车票时该火车票对应时间的车次余票数量必须相应减1如果减少时发现原先票数就已经为0了此时就应该提醒用户购买火车票失败余票为0同样的如果票数不为0则票数应该相应减1并提示用户购票成功。
不难看出购买火车票会改变火车票数据也不可避免地会存在多个用户同时购买相同路线、相同车次比如京沪线的T12的场景。也就是说这个购买过程存在多个进程同时访问共享资源的问题因此还要用到分布式锁的相关技术。
另外用户购买火车票的过程还会涉及用户体验、数据一致性和网络故障等问题因此还涉及C、A、P策略的选择问题。
在铁路局发布火车票的流程中,为了保证可靠性,同一数据通常会备份到多个服务器上。当用户购买火车票导致火车票数据改变时,主节点上的数据必须与备节点上的数据保持一致,以防止主节点故障后,备升主,但数据不一致导致业务出错的情况。
比如用户A购买2019年10月12日北京到上海的T12的火车票已购买成功座位号为3车厢23B。假设主节点和备节点之间数据不一致主节点上已经减去该火车票但未在备节点上减去。此时若主节点故障备节点升主用户B此时申请购买相同火车票系统将3车厢23B火车票又卖给了用户B。等到乘车时用户A和B就难免“打架”了。
当然,通常因为网络故障或节点故障等原因导致主节点不能正常工作,才会发生备升主,而备升主其实就是故障恢复策略,而一致性问题涉及的是“第五站:分布式数据存储”的相关技术。
同时购买火车票的场景需要快速响应用户以保证用户体验。因此通常优先保证系统的可用性稍微降低对数据一致性的要求但也必须保证最终一致性。这就是我们平常遇到的查询火车票时还有余票但下单后却提示余票为0无法购买。
导致这个结果的原因是下单前你访问的数据库中数据还未同步显示有余票而下单后数据实现同步了发现余票数量已经为0因此提示你无法购买该火车票。
实现上述策略的方法,通常会采用半同步复制技术,即将修改后的数据同步到多个备数据库中的某一个或几个后立即响应用户,而不用将数据同步到所有备数据库。
除此之外业务量很大的情况下为了让服务更加健壮、低耦合、便于管理会根据功能拆分为不同的服务。比如将整个购票系统拆分为订单系统、支付系统和通知系统而当购票系统拆分为3个子系统后子系统之间不可避免地存在信息的交互子系统之间的交互就会涉及分布式通信的相关知识比如远程调用RPC、消息队列等。
如图所示,用户购买火车票后,会首先在订单系统下单,下单成功后会调用支付系统的支付操作进行支付,之后将支付成功的消息存放到消息队列中,通知系统到消息队列中获取消息,最后通知用户购买成功。
总结
今天,我主要以购买火车票为例,为你串联了分布式技术在实践中的应用。
为方便理解,我将购买火车票的模型简化为三个核心步骤,即铁路局发布火车票、用户查询火车票和用户购买火车票。
其次,我分别与你分析了这三个核心步骤涉及的关键的分布式技术。
对于铁路局发布火车票这个流程来说,铁路局是数据的生产者,需要将数据发布到服务器进行存储,主要涉及的是分布式数据存储相关技术,对应专栏“第五站:分布式数据存储”的内容。
对于用户查询火车票来说,主要是读请求,涉及负载均衡、流量控制、集群管理及选主等技术,对应专栏“第一站:分布式协调与同步”“第二站:分布式资源管理与负载调度”和“第六站:分布式高可靠”的内容。
而对于用户购买火车票来说,是写请求,涉及数据的改变,因此除了用户查询火车票涉及的技术外,还额外涉及一致性、分布式事务、远程通信等技术,对应专栏“第一站:分布式协调与同步”、“第四站:分布式通信技术”、“第五站:分布式数据存储”和第六站:“分布式高可用”等内容。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
思考题
结合购买火车票这个案例,你能和我分享你身边的应用场景或系统,都涉及或采用了哪些分布式技术吗?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,456 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
34 搭建一个分布式实验环境:纸上得来终觉浅,绝知此事要躬行
你好,我是聂鹏程。
上一讲,我以购买火车票为例,为你串讲了分布式技术的应用,帮助你理解所学分布式技术可以应用到哪些业务中。其实,到目前为止,我们主要是从理论上学习相关的分布式技术。但,“纸上得来终觉浅,绝知此事要躬行”。
今天我就以Kubernetes为例和你一起搭建一个分布式实验环境。我先简单和你说下这篇文章的内容分配
不会特别详细地讲述搭建过程,而是着重说明搭建的主要步骤以及可能遇到的问题;
在讲述搭建过程时,串联一下其中涉及的分布式相关知识;
搭建完Kubernetes集群之后我会以部署Nginx服务为例帮助你更直观地体验分布式技术以巩固、加深对分布式技术的理解。
话不多说,接下来,我们就一起搭建这个分布式实验环境吧。
搭建目标
Kubernetes是Google开源的容器集群管理系统是Borg的开源版本。我在第9篇文章中讲解集中式架构时和你分析过Kubernetes集群属于主从架构的分布式集群。
Kubernetes集群主要由Master节点和Worker节点组成。Master节点就是中心服务器负责对集群进行调度管理Worker节点是真正的工作节点负责运行业务应用的容器。而容器是一种虚拟化技术通过限制自身使用的资源来实现资源隔离可以为应用提供一整套运行环境从而实现了服务运行环境的隔离进而实现了故障隔离。你可以回顾下第30篇文章中资源隔离的相关内容。
接下来,我们明确下这次搭建分布式实验室环境的目标:
搭建一个Kubernetes集群包括一个Master节点两个Worker节点;
在Kubernetes集群上创建一个Nginx服务。
搭建前的准备
今天我们要搭建的Kubernetes集群以3台服务器为例一台作为Master节点两台作为Worker节点。服务器应具备的条件如下
Ubuntu 16.04操作系统;
2GB或以上的内存
2核CPU或以上
服务器间网络连通;
每台服务器具有唯一的主机名、MAC地址和product_uuid
通过执行命令swapoff -a来关闭Swap
30GB及以上的磁盘空间
具备外网访问权限,以方便获取相关镜像。
在这次部署中,我采用的机器配置如下:
准备工作完成后,我们就开始搭建集群吧。
Kubernetes集群搭建
搭建Kubernetes集群的步骤主要包括安装Docker安装部署kubeadm、kubelet、kubectl部署Master节点部署Worker节点安装网络插件这几步。
其中安装Docker、部署Master节点和Worker节点涉及分布式的需要在多个节点上部署比如Docker节点需要在每个Worker节点部署Master节点若为集群模式需要在多个节点上配置主备Worker节点需要与Master节点建立连接等。
接下来, 我们具体看看如何一步一步搭建出Kubernetes集群吧。
1. 安装Docker
Kubernetes是一个容器集群管理系统因此每个Worker节点会运行容器以实现业务运行环境隔离。我们在每台服务器上采用如下命令安装Docker
apt-get install -y docker.io
2. 安装部署kubeadm、kubelet、kubectl
kubeadm是Kubernetes社区提供的一个部署工具该工具将kubelet组件之外的其他组件均采用容器部署实现了自动化 避免了手动部署容器的麻烦,简化了部署操作。
其中Master节点包括API Server、Scheduler、Cluster State Store(默认etcd)和Control Manager Srever核心组件Worker节点包括kubelet和kube-proxy核心组件。具体的组件功能和原理你可以再回顾下第9篇文章中的相关内容。
kubelet组件本身是一个管控容器的组件需要执行配置容器网络等操作这些操作需要在宿主机上执行不采用容器部署。因此kubelet组件需要单独部署而不能用kubeadm进行部署。
除此之外我们还需要安装一下kubectl组件。这个组件是Kubernetes的命令行工具通过kubectl可以部署和管理应用查看资源创建、删除和更新组件。
那么如何部署kubeadm、kubelet和kubectl这三个组件呢
apt是Linux下常用的安装管理工具这里我就采用apt来安装这三个组件。
首先我们需要添加Kubernetes源。
你可以通过执行以下语句获取Kubernetes源需要外网权限
sudo apt-get update && sudo apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
然后使用以下命令更新源
sudo apt-get update
这时我们就可以使用如下命令来安装kubeletkubectl和kubeadm了
sudo apt-get install -y kubelet kubeadm kubectl
如果没有外网访问权限在添加kubernetes源的时候可以执行以下命令来添加阿里云的Kubernetes源
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial main
EOF
同样的使用以下命令来更新源
apt-get update # 忽略gpg的报错信息
最后使用如下命令安装kubeletkubectl和kubeadm
apt-get install -y kubelet kubeadm kubectl --allow-unauthenticated
安装好这三个组件之后我们就可以使用kubeadm来一键部署集群节点了
首先我们来部署Master节点
3. 部署Master节点
这一步其实就是容器化启动Master节点中的各个组件直接使用kubeadm工具提供的一条命令kubeadm init即可自动安装
kubeadm init这条命令底层其实就是将Master的各个组件比如API Serveretcd等以Pod形式容器集合运行起来
当然了你可以部署多个Master节点来实现集群的高可用比如两个Master节点互为主备你可以回顾下第31篇文章中介绍的主备机制)。具体的部署方法你可以参考这篇文章
除此之外etcd组件也可以采用集群方式部署从而保证了数据不会丢失etcd采用的是Raft强一致性协议相关技术你可以再回顾下第4篇文章中的相关问题
在本次部署中我以一个Master节点为例为你讲解集群的搭建关于Master为集群的方式各节点上kubernetes配置类似你可以参考Kubernetes官网
在这里我把192.168.124.49这台机器作为Master节点在该机器上直接执行kubeadm init即可完成部署
kubeadm init
如果有外网访问权限基本就可以部署成功了那么我们可以根据如下信息判断自己是否部署成功
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.124.49:6443 --token uv17vd.q3ber8i5knxg4h0x \
--discovery-token-ca-cert-hash sha256:c55bd70d346d809e1079565cc1fc1a05f001671cc9f2d02c55bbbc4a00bcc2a3
可以看到想要使用集群需要执行以下命令执行结束后才可以使用kubectl
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
如果没有外网访问权限会报pull image xxxxxx的错误
此时不要慌从报错信息中我们可以看到哪些镜像拉取不成功我们可以手动在Docker Hub上寻找相对应的组件及版本进行拉取然后再通过Docker打tag修改为需要的镜像
比如[ERROR ImagePull]: failed to pull image k8s.gcr.io/kube-apiserver:v1.16.3为例可以通过以下代码进行拉取
# 可以拉取的相对应的组件和版本
docker pull aiotceo/kube-apiserver:v1.16.3
# 通过打tag的方式修改为所需要的镜像
docker tag aiotceo/kube-apiserver:v1.16.3 k8s.gcr.io/kube-apiserver:v1.16.3
然后重新执行 kubeadm init即可
从以上操作也可以看出kubeadm的底层其实就是将容器化组件的操作实现了自动化 省去了手动部署的麻烦
部署完Master节点后我们来继续部署Worker节点
4. 部署Worker节点
部署Worker节点与部署Master节点类似都可以通过命令一键部署这里我们使用kubeadm提供的kubeadm join命令来进行自动化部署
kubeadm join命令的底层与kubeadm init类似会自动以Pod形式运行Worker节点中需要的组件不同的是命令执行后底层还需要将Worker节点加入到Kubernetes集群中
执行kubeadm join命令后具体命令如下所示就可以看到Kubernetes集群中的节点信息了这条命令中需要配置Master节点的IP和Port信息目的是Worker节点根据IP和Port信息建立连接并在建立连接的基础上建立心跳机制
具体的心跳机制你可以参考第31篇文章中关于故障恢复的内容
到目前为止Kubernetes的集群已经完成大半了下面我们继续部署集群
根据Master节点部署成功后输出结果的最后几行可以知道想要加入集群可以执行kubeadm join命令我在另外2台机器上都执行了如下命令
kubeadm join 192.168.124.49:6443 --token uv17vd.q3ber8i5knxg4h0x \
--discovery-token-ca-cert-hash sha256:c55bd70d346d809e1079565cc1fc1a05f001671cc9f2d02c55bbbc4a00bcc2a3
这条命令执行后一个集中式架构的雏形就搭建完成了接下来我们需要安装相应的网络插件以实现Kubernetes集群中Pod之间的通信
5. 安装网络插件
网络插件有很多比如CanalFlannelWeave等不同的插件命令不一致具体命令可参考官网
这里我以安装Weave插件为例通过执行以下命令完成安装
sysctl net.bridge.bridge-nf-call-iptables=1
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"
6. 验证
到这里集群就部署完成了是不是很简单呢接下来我就通过获取节点和Pod信息来验证一下集群部署是否成功
可以通过刚刚安装的kubectl组件提供的命令查看集群的相关信息比如查看节点的运行状态可以通过kubectl get nodes来获得查看各个组件对应的Pod运行状态可以通过kubectl get pods来获得命令执行结果如下所示
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# vm1-pc Ready master 11h v1.16.3
# vm2-pc Ready <none> 11h v1.16.3
# vm3-pc Ready <none> 24m v1.16.3
kubectl get pods --all-namespaces
# NAMESPACE NAME READY STATUS RESTARTS AGE
# kube-system coredns-5644d7b6d9-9dprc 1/1 Running 0 11h
# kube-system coredns-5644d7b6d9-ljv5w 1/1 Running 0 11h
# kube-system etcd-vm1-pc 1/1 Running 0 11h
# kube-system kube-apiserver-vm1-pc 1/1 Running 0 11h
# kube-system kube-controller-manager-vm1-pc 1/1 Running 0 11h
# kube-system kube-proxy-qpvtb 1/1 Running 0 25m
# kube-system kube-proxy-v2xnb 1/1 Running 0 11h
# kube-system kube-proxy-wkxzg 1/1 Running 0 11h
# kube-system kube-scheduler-vm1-pc 1/1 Running 0 11h
# kube-system weave-net-6nj4c 2/2 Running 0 25m
# kube-system weave-net-lm6dh 2/2 Running 0 37m
# kube-system weave-net-vwnc2 2/2 Running 0 37m
可以看到节点全部是Ready状态各个组件对应的Pod也处于Running状态表明部署成功
7. 可能遇到的问题
如果整个安装失败的话可以重置重新安装即重新kubeadm init
kubeadm reset
部署Worker节点时pod部署不成功原因可能是因为没有外网访问权限镜像拉取不下来可以通过以下命令查看pod的相关信息
# 检查所有pod是否正常
kubectl get pod --all-namespaces -o wide
#如果pod处于非running状态则查看该pod
kubectl describe pod xxxxx -n kube-system
从错误信息里可以查看到是哪个镜像拉取不下来与部署Master节点时采用的方式一样到Docker Hub上手动拉取镜像并设置Tag即可
至此Kubernetes集群就配置成功了
集群环境搭建后如何验证集群是可用的呢或者说如何在集群上运行服务呢接下来我就以Nginx服务为例带你了解如何在Kubernetes集群上进行服务部署当然你可以参考这个例子在Kubernetes集群上部署其他服务
Nginx服务部署
Kubernetes推荐使用YAML配置文件的方式来创建服务所以我接下来会使用这种方式部署完成Nginx服务的部署
部署Nginx服务这个Demo时我会创建两个Kubernetes对象Kubernetes对象是Kubernetes系统中的持久实体用于表示集群的状态一个是Deployment一个是Service
Deployment对象规定Pod创建的相关信息比如期望创建几个Pod每个Pod应该部署什么应用等
Service对象用来给用户访问提供接口它可以通过Label Selector标签选择器来指定可以访问的Pod有哪些关于Kubernetes对象的相关内容你可以参考这篇文章
因为Pod是Kubernetes中最小的工作单元所以Nginx服务都部署在Pod中下面我就来创建一个Deployment对象来创建我们期望的Pod状态
首先创建一个YAML配置文件我将其命名为nginx-deployment.yaml为将用户请求负载均衡到不同的Pod减轻单个Pod的访问压力这里我会创建三个Pod共同运行Nginx服务
文件内容如下
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
文件中replicas字段就是副本数量也就是Pod数量设置为3即创建三个Pod来运行Nginx服务template字段规定了单个Pod中运行哪些容器这里运行的是名称为nginx的容器
创建完配置文件后通过以下命令就可以将Deployment对象创建成功
kubectl apply -f nginx-deployment.yaml
执行后就等待对象的创建可以通过以下命令来查看创建是否成功
kubectl get deployment
以下是我创建成功后的输出
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 3m17s
同时你也可以通过以下命令来查看创建的Pod的信息
kubectl get pod
以下是我的输出结果
NAME READY STATUS RESTARTS AGE
nginx-deployment-59c9f8dff-dtg4w 1/1 Running 0 3m15s
nginx-deployment-59c9f8dff-f2hmv 1/1 Running 0 3m15s
nginx-deployment-59c9f8dff-lsvdh 1/1 Running 0 3m15s
创建完deployment之后我们来创建Service服务同样是通过配置文件来创建文件名是nginx-service.yaml内容如下
apiVersion: v1
kind: Service
metadata:
name: nginx-service
labels:
app: nginx
spec:
ports:
- port: 88
targetPort: 80
selector:
app: nginx
type: NodePort
文件中port属性就是service对外提供的端口
同样的采用kubectl apply命令创建Nginx服务
kubectl apply -f nginx-service.yaml
执行完成后可以通过以下命令来查看创建是否成功
kubectl get service
以下是我的输出结果
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12h
nginx-service NodePort 10.101.29.9 <none> 88:30755/TCP 5m12s
现在我们就可以通过访问Nginx服务来查看它是否部署成功了。访问该服务可以通过以下命令
curl 10.101.29.9:88
结果如下表明Nginx服务部署成功。
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
在这个过程中,有两个步骤涉及负载均衡的相关知识:
一个是创建Deployment时该Deployment会创建三个Pod而Pod需要部署到某个Worker节点中因此会将Pod均衡部署到各个Worker节点中
另一个是用户访问Nginx服务后台三个运行的Pod都可以提供服务用户访问到来时可以均衡分布到各个Pod中进行处理。
到这里,我们搭建的目标就完成了,下面为你留几个实验题,你可以尝试去搭建一下或运行一下,以进一步加深对分布式技术的理解。
实验一搭建高可用Kubernetes集群也就是通过etcd实现Master节点以集群模式部署。具体搭建方法可参考https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/
实验二在Kubernetes上部署Cassandra其中Cassandra作为服务部署到容器中以学习Cassandra集群的节点发现、集群组件等原理具体搭建方法可参考https://kubernetes.io/docs/tutorials/stateful-application/cassandra/
实验三在Kubernetes集群上通过部署一个MySQL服务体验在Kubernetes集群上如何运行一个单实例有状态应用。具体搭建方法可参考https://kubernetes.io/docs/tasks/run-application/run-single-instance-stateful-application/。
好了,整个搭建环境,我就讲到这里。
其实,到这里,对分布式世界的探索可以说才刚开始,只有动手去实践,你学到的知识才能真正转化为你自己的。加油,赶紧行动起来吧。
总结
今天,我主要带你学习了搭建分布式实验环境。
首先我以Kubernetes为例介绍了如何搭建 Kubernetes集群环境其中包括容器、Master节点、Worker节点等配置和安装。
然后在搭建好的Kubernetes集群的基础上我以Nginx服务为例展示了如何在Kubernetes集群上部署服务。
其实今天我演示的Demo只是冰山一角。在Kubernetes中有很多非常实用的功能比如Kubernetes可以让服务持续不断运行当有Pod出现故障时会自动重启另一个Pod来达到Deployment配置文件中规定的期望状态还可以自动实现版本更迭等。
相信通过本讲的学习,你会对分布式技术有更进一步的认知。加油,赶紧行动起来,为你的服务搭建一个分布式实验环境吧。
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@ -0,0 +1,65 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
特别放送 Jackey寄语天涯客轻寒底用愁
大家好我是Jackey目前在一家创业公司的架构组做Java开发工作。因为工作需要我也会做一些Node、Python以及运维相关的工作所以称自己为“半栈工程师”。今天我想和你说说我学习这个专栏的一些心得。
我平时是如何学习的?
我先来和你说说我的经历吧。
本科毕业后,我去了一家国企做项目开发,日常工作就是使用已有框架去快速搭建一套可用的内部管理系统,然后做些定制化。
做了一年多,我感觉工作内容大量重复,所用技术也与行业严重脱节。但同时我也发现,我竟然连自己做的项目是什么原理都说不清楚,没办法通过其他公司的面试。然后,我开始深入研究所用框架,为日后的面试做知识储备。
当我在调用框架提供的某个方法时不再停留在是什么、怎么用而是去研究源码看看它是如何实现的。比如NIO是怎么实现的为什么用select而不是epoll再比如某个类的实例为什么要用单例单例还有哪些写法又比如某处代码用到了线程池用的是哪种阻塞队列为什么要用有界队列队列的参数是怎么设置的。
每次点开一段源码,我都感觉打开了新世界的大门。每次遇到新知识点,我都会通过搜索引擎去查看官方文档、技术博客或其他资料。在阅读这些技术资料的过程中,我又会发现一些新的知识盲区,进而通过递归学习去触达更多的知识。
经过半年多的研究我终于搞清楚当时所用框架的基本原理了。而在这期间我也从一个写Bug的小菜鸟变成了带领他人开发项目的小组长顺利入职了一家AI公司负责自有商城的开发。
团队人数不多,但技术足够新、氛围也很好,并且开发流程非常规范,编码格式要求严格。这,对我的个人成长帮助很大,因为代码就是开发者的名片,“优雅可读”的代码给人的印象总是好于“惨不忍睹”的代码。
因此,我感谢自己之前对技术原理的积累,让我有了一个更好的发展平台。
我为什么要学习“分布式技术原理与算法解析”专栏?
后来,因为内部调岗等因素,不想远离技术的我再次选择了离开,这次我打算去一家大公司。
由于我之前积累了比较多的基础知识再加上读过一些Redis源码在一面中的表现都很不错。但二面问到关于系统设计的问题时我就有些无从下手了。
我记得在美团二面时面试官让我设计一个注册中心。我了解一些Eureka的原理所以简单阐述了下自己的想法然后面试官在此基础上增加难度问我如何保证高可用、如何应对高并发。这时我想到了要使用分布式技术但如何做分布式、机器间怎么联系在一起、如何保证机器间数据的一致性呢
当时,我对分布式技术并没有一个清晰的认识,也不了解这些问题具体要如何解决,更不清楚引入分布式系统后会提升多少复杂度。所以,我面试的回答大都止步于提出分布式概念。
这时,我意识到自己需要去学习一些分布式系统的设计方法。互联网发展到今天,稍微有些规模的软件几乎都离不开分布式部署,多数基础组件也都建议进行分布式部署,分布式技术更是高并发、高性能的重要保证。
所以说,作为一名互联网从业者,如果不了解分布式的原理,就快与时代脱节了。也许,你会用“分久必合”来反驳我,但目前来看,硬件的发展已经明显降速,可能很难再看到“合”的场景了。因此,我相信分布式技术将会成为程序员的一项必备基础技能,看到“分布式技术原理与算法解析”专栏时毫不犹豫地就入手了。
学习这个专栏,我有哪些收获?
我学习专栏的习惯是,每周一三五更新后,当天上班的地铁上就抓紧学习,如果遇到不懂的知识点,就第一时间去查阅相关资料。看完一篇文章后,我会在评论区留下说说哪里有困惑,以及对应的课后思考题的解答思路,并会关注其他同学的留言,加深对这篇文章的理解。
一路学习下来,所有的辛苦都是值得的,所有的付出都是有收获的。跟随专栏到现在,对于我来讲是“温故”和“知新”的过程。
比如我在学习“分布式事务all or nothing”这一篇后不仅对熟悉的二阶段提交有了更透彻的认识还学到了它的改良版三阶段提交的原理。
再比如我在学习“分布式锁关键重地非请勿入”这一篇后除了对基于缓存的分布式锁有了更深的理解外还学习到了基于ZooKeeper实现的分布式锁让我在日后的系统设计时又多了一种思路。
目前我们公司还处在搭建基础架构的初期。随着对分布式架构的不断了解我在公司担任的角色也更加重要了设计新系统时Leader也总会听听我的意见。同时我也希望能通过学习这个专栏在公司内做一些关于分布式技术的分享来提升自己的技术影响力。
我觉得,这都是这个专栏给我带来的收获吧。
给新同学的一些建议
如果你已经订阅了这个专栏,我的建议很简单,那就是抓紧时间学习吧,早学就是优势。当你养成学习习惯以后,你就发现自己已经比身边的大部分人都优秀了。
另外,我想说的是,学习专栏的过程不要局限于某个知识点,你可以再根据文章内容进行二次发散,去学习更多的知识,由点及面,构建自己的核心知识体系。
如果你还在犹豫的话,可以对比一下我的经历,如果你有所感触的话,真心建议你能认真学完整个专栏,再去挑战大厂,成功率会大很多。

View File

@ -0,0 +1,197 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
特别放送 分布式下的一致性杂谈
你好,我是聂鹏程。
我们常说:“众人齐心,其利断金。”其实说的就是团结一致的重要性。一致性对一个团队如此重要,对于一个分布式系统又何尝不是呢?人心散了,团队会不好带。分布式系统中出现不一致了,也会带来各种各样的问题,甚至导致业务不可用。
我在第23讲“CAP理论这顶帽子我不想要”时就解释了分布式系统中一致性和可用性就像是鱼与熊掌不可兼得。因此多年来在不同场景下保证一致性的同时尽可能提高可用性或者保证可用性的同时尽可能提高一致性成为了众多学术界、工业界仁人志士们研究的课题以及努力的方向。正可谓分布式技术如此多娇分布式一致性引无数英雄竞折腰。
今天,我特地邀请到我的朋友王启军,来与你分享他对分布式一致性的解读、思考和实践。
王启军华为云PaaS团队资深架构师负责 Java和Go微服务框架。他曾任当当网架构师主导电商平台架构设计曾就职于搜狐负责手机微博的研发著有《持续演进的Cloud Native》。
话不多说,我们来看看王启军的分享吧。
你好,我是王启军。今天,我来和你聊聊分布式下的一致性。
以前面试别人的时候,我经常会用一些开放性的问题来考察对方的能力。比如我最爱的一个问题是,“如果给你一份数据,要求支撑大规模的并发读写,同时具备横向扩展能力,你该如何拆分、如何同步数据呢?”
此时,候选人想到的通常是复制数据到多台服务器,以提升读的性能;然后对数据进行分区,分布到不同的服务器,以解决写的瓶颈。
如果到了这里,接下来我会问他怎么同步数据,一次性写多条数据,怎么保证多台服务器的一致性呢?如果数据同步存在延迟,怎么保证写后一定能读到呢?
这次追问之后,大部分候选人就会卡住。不知道你在面试的时候有没有遇到过类似的问题,今天我就想和你聊聊这个话题。
一致性的分类
业界对一致性的定义有很多种比如CAP理论中的一致性和ACID中的一致性描述的就不太一样不能一概而论。所以在讨论之前你最好弄清楚一致性的分类。
为了便于理解,业界通常会把一致性笼统地分为如下三类:
弱一致性Weak写入数据A成功后在数据副本上可能读出来也可能读不出来不能保证多长时间之后每个副本的数据一定是一致的。
最终一致性Eventually写入数据A成功后在其他副本有可能读不到A的最新值但在某个时间窗口之后保证最终能读到。最终一致性可以看作弱一致性的一个特例这里的重点是“时间窗口”。在读多写少的场景中例如CDN读写比非常悬殊如果网站的运营人员修改了一张图片最终用户延迟一段时间看到这个更新实际上问题不大。我们把这种一致性归结为最终一致性指的就是如果更新的时间间隔比较长那么所有的副本能够最终达到一致性。
强一致性Strong数据A一旦写入成功在任意副本任意时刻都能读到A的最新值。
但实际上,这种分类并不能描述清楚,弱一致性在生产环境中基本没什么应用场景,最终一致性范围太宽泛,可能会存在多种不同强度的一致性。
相对来说,我更喜欢另外一种对一致性的分类方法,一致性模型主要从以下两个角度去分类:
以数据为中心的一致性模型。从数据的角度来观察,全局考虑,比如从数据库的角度来看,北京和南京的数据是否一致。
以客户为中心的一致性模型。从用户的角度来观察,比如我购买了一个商品,存到了北京的数据中心,另外一个人购买了一个商品,存到了南京的数据中心,两个买家之间没有任何关系,无需保持一致,只要是跟我有关系的数据保证一致就行了。
这两种一致性模型,又可以细分为很多类。接下来,我们就一起看看吧。
以数据为中心的一致性模型又可以分为严格一致性Strict Consistency、顺序一致性Sequential Consistency、因果一致性Causal Consistency、FIFO一致性FIFO Consistency、弱一致性Weak Consistency、释放一致性Release Consistency和入口一致性Entry Consistency
限于篇幅,我不会详细介绍每一种一致性的定义,只和你说明几个常用的。
严格一致性,要求任何写操作都能立刻同步到其他所有进程,任何读操作都能读取到最新的修改。要实现这一点,要求存在一个全局时钟,也就是说每台服务器的时间都完全一致,但在分布式场景下很难做到。所以,严格一致性在实际生产环境中目前无法实现。
既然全局时钟导致严格一致性很难实现顺序一致性放弃了全局时钟的约束改为分布式逻辑时钟实现。分布式逻辑时钟可以理解为一个分布式ID。
顺序一致性是指所有的进程以相同的顺序看到所有的修改。读操作未必能及时得到此前其他进程对同一数据的写更新但是每个进程读到的该数据的不同值的顺序是一致的。举个例子你在手机和PC上看到的聊天室的顺序是一致的吗如果是一致的那就代表满足顺序一致性即使理论上某些先发的消息排在后发的消息后面了也是满足的。
因果一致性是一种弱化的顺序一致性,所有进程必须以相同的顺序看到具有潜在因果关系的写操作,不同进程可以以不同的顺序看到并发的写操作。举个例子,在聊天室你看到有人问你:“你吃饭了吗?”,你回答:“我吃过了。”,你是因为看到了问题所以才触发了回答,所以这两条消息之间就存在因果关系,原因必须排在前面。
以用户为中心的一致性模型包括4类
单调读一致性Monotonic-read Consistency
单调写一致性Monotonic-write Consistency
写后读一致性Read-your-writes Consistency
读后写一致性Writes-follow-reads Consistency
单调读一致性是指如果一个进程读取数据项a的值那么该进程对a执行的任何后续读操作总是得到第一次读取的那个值或更新的值。这个比较容易理解说白了就是不能读到新数据后再读到比这个数据还旧的数据如果没读到新数据一直读的还是旧数据单调读一致性并不关心这个问题。
单调写一致性是指一个进程对数据项a执行的写操作必须在该进程对a执行任何后续写操作前完成。这个很容易满足注意这里是一个进程所有的写操作都是顺序的。
写后读一致性是指一个进程对数据项a执行一次写操作的结果总是会被该进程对a执行的后续读操作看见。这个比较常见比如数据库采用Master-Slave结构部署时写完Master数据库如果从Slave读取有可能读不到就不满足写后读一致性了。
读后写一致性是指同一进程对数据项a执行的读操作之后的写操作保证发生在与a读取值相同或比其更新的值上。这个问题经常出现的场景是如果数据存储了多个副本因为没有及时同步在第一个副本上读了数据去第二个副本上写出现不一致的情况。
如何满足一致性需求?
当单个存储承受不住压力的时候在读多写少的情况下我们自然会想到使用主从的方式。为了保证写的一致性通常只在主节点写然后主从之间通过状态机Replicated State Machine的方式同步数据。
可以简单地理解为,主从都有相同的初始状态,在主上执行的所有命令,都同步一份相同顺序的命令到从,这样就可以保证最终数据也相同。
比如MySQL就可以采用一个Master多个Slave的方式所有的写都在Master上更新所有的读都在Slave上进行但这里存在一个问题就是怎么保证写后读一致性答案就是写后读都在Master上进行。注意并不是所有的读都作用在Master而只对写后读一致性有要求的场景才在Master上读。
当然这样Master的压力可能还是会很大。回到问题本身之所以满足不了写后读一致性原因是主从之间存在延迟。那么我们完全可以设定一个大于延迟时间的阈值小于阈值并且要求写后读一致性的操作作用在Master上其他所有的读都作用在Slave上。
还有一个问题就是,当主挂掉的时候,为了保证可用性,通常要将其中一个从提升为主,这里面就会涉及很多问题,到底选择哪个从作为主节点?发生网络分区的时候,如何避免脑裂?如果旧的主节点又恢复了,如何协调?
关于这些问题如何解决你可以参考Etcd和ZooKeeper的实现方案。
Quorum机制NWR模型
主从机制要求写必须在主上进行,那能不能让写在所有节点上都可以进行呢?当然可以,不过这已经不是主从模式了。
回到前面的问题如果同时写三份数据如何保证一定能够读取到最新的数据呢简单来说就是利用版本号假设一共三个节点每次写数据的同时我都会记录这条数据对应的版本号读数据的时候读所有的节点然后跟版本号进行对比版本最新的就是最终数据。实际上这就是Quorum机制的原理也可以叫作NWR模型。
简单来说Quorum机制就是要满足公式W+R > N。其中W表示必须至少写入成功的节点数R表示至少读取成功的节点数N表示总节点数。这个公式把选择权交给了业务用户让用户来做出最终决策。
假设现在一共有三个节点,为了满足这个公式:
如果我写数据的时候写入三个节点都成功才返回,那我读取的时候只要读取其中任意一个节点就可以得到最新的数据。
如果我写数据的时候写入两个节点成功就返回,那我读取的时候只要读取其中任意两个节点就可以得到至少一份最新的数据。
如果我写数据的时候写入一个节点成功就返回,那我读取的时候必须读取所有节点才可以得到至少一份最新的数据。
这里还存在另外一个问题那就是如果写的时候只写入一个节点就返回当存在并发操作时版本会存在冲突也就是说如果初始版本为1两个进程分别对其中两个节点写数据两个节点版本号都变成了1数据却不一样这个问题如何解决呢
就要求我们写数据的时候最好是遵循W>N/2也就是说写的时候最好大于总节点数的一半在写的过程中进行冲突检测而不是在读的时候检测。
N阶段提交
还有一种比较经典的做法常用在数据库中那就是N阶段提交。这里的N有一阶段、两阶段、三阶段其中两阶段用得最多。
这里我必须首先说明一下两阶段提交不等于XA协议两阶段提交是一种模式ZooKeeper中提交事务的过程实际上也类似于两阶段提交Google的分布式事务Percolator也是基于两阶段提交而XA只是一种协议。它是由X/Open国际联盟提出的X/Open Distributed Transaction ProcessingDTP模型简称XA协议。
顾名思义两阶段提交的整个过程分为两个阶段第一阶段询问是否可以提交锁定数据如果所有节点都返回可以提交第二阶段提交否则第二阶段回滚。基于XA的两阶段提交就是这种流程这种方式常用于同时更新两个数据库的场景一般常用的关系型数据库都支持这个协议。
由于第一阶段要给数据库加锁否则会出现不一致的情况这就会带来很多问题XA被诟病的大部分原因都跟这个锁有关。例如加锁后协调者挂掉怎么办加锁后性能大幅下降如何处理
为了解决死锁的问题,可以将加锁的时间缩短,降低死锁的概率,这就是三阶段提交。也就是说把第一阶段分为两个部分,询问是否可以提交,回复可以提交的时候,并不加锁,当所有节点都回复可以的时候,协调者再发一次加锁的请求。但这样的话,系统会变得更复杂。
当然两阶段提交并不是只有XA协议TCC也可以看成是一种两阶段提交协议TCC是Try-Confirm-Cancel的缩写。相对于两阶段提交事务机制它的特征在于不依赖于数据库的协议数据库不需要锁定数据事务过程由业务服务来实现这样也就不会出现死锁的问题性能也会高很多。还有一点容易被忽略这种做法降低了数据库的压力。
当然世界上没有免费的午餐TCC事务机制也有不好的地方
导致业务变得更复杂,数据库需要增加字段表示状态;
需要增加相应接口实现Confirm和Cancel方法
业务要自己保证每个方法的幂等,因为这里可能涉及到失败重试的问题。
而一阶段提交,就是没有询问是否可以提交的过程,直接提交,任意一个节点提交失败,就进行重试或者回滚。这种方案对业务的侵入性很大,需要对业务提供一个回滚操作。
实践案例
接下来,我就以在华为云的工作场景为例,来给你串下整体的思路吧。
华为云目前的分布式框架采用TCC的模式将协调者从业务中抽离独立为一个服务。主业务发起事务获得全局事务ID调用分支业务时将全局事务ID通过Header传递过去分支业务根据全局事务ID申请分支事务ID所有的状态都会存储到分布式事务服务端服务端根据执行情况进行全局协调。
事务执行的大概步骤,如下所示。
步骤1进入到发起全局事务的方法内时会先向DTM集群申请注册一个全局事务IDGlobal Transaction ID只有申请成功才可继续后续流程。
步骤2.1事务发起者将申请到的全局事务ID透传到所调用的事务参与者中。
步骤2.2事务参与者利用得到的全局事务ID向DTM集群注册申请一个分支事务IDBranch Transaction ID只有申请成功才可继续此事务参与者的后续流程。
步骤2.3事务参与者完成自身业务逻辑即完成TCC中的Try阶段
步骤2.4事务参与者将自身业务逻辑结果上传到DTM集群并示意分支事务结束。
步骤3.1~3.4与2.1~2.4类似,完成剩余事务参与者的业务逻辑。
步骤4事务发起者发起TCC二阶段。
步骤5.1DTM集群根据全局事务ID找到事务参与者发起TCC二阶段。
步骤5.2与5.1类似完成剩余事务参与者的TCC二阶段。
步骤6DTM集群通告事务发起者全局事务结束。
什么时候开始考虑一致性?
在分布式领域,一致性是个永恒的话题,因为在大多数场景下,出于成本考虑,通常会选择性地忽略或者降低对一致性的要求。但是,当系统达到一定规模的时候,不一致的概率就会大大增加,比如原本不一致的概率可能是一万年一次,如果数据量提升一万倍,那可能是一年一次。但,无论发生的概率是多少,研发成本都是一样的,这时候实现更强的一致性就非常有必要了。
凡是涉及钱的系统,对一致性的要求必然很高,除此之外,大多数场景实际上对一致性的要求并没有那么高,比如用户登录时给用户加一个积分,加积分这个操作并没有那么重要,延迟几秒影响也不大,那完全可以做成异步的,保证最终一致性就可以了。
有没有什么学习材料?
如果要推荐学习资料的话,我建议你可以多去看看分布式领域的论文,特别是大神莱斯利 · 兰波特Leslie Lamport以及Google、Amazon等硅谷著名企业发布的论文。比如下面这两篇我就非常推荐你去读一读因为很多数据库的分布式事务都是参考了它们 。
Percolator的论文可以认为是Spanner的类似实现TiDB的实现也是根据此论文得出。
DynamoDB的论文Dynamo: Amazons Highly Available Key-value Store。
另外你还可以看一些开源框架的实现比如Cassandra、Kafka等这些代码对于你理解分布式一致性会有非常大的帮助。
总结
我来简单和你总结一下今天的主要内容吧。
首先,要搞明白分布式一致性,我们就要从其分类开始,除了弱一致性、强一致性、最终一致性这种分类方式外,还有一种更好的分类方式,就是以数据为中心和以用户为中心的一致性分类。
其次如果要满足一致性的需求有很多种方案这里我们介绍了两种最常用的方案Quorum机制和N阶段提交并通过华为云的一个实践案例来加深你对一致性的理解。
最后,在分布式领域,一致性是目前为止解决得最不好的一个领域,也是最复杂的一个领域,解决方案五花八门,我只是与你介绍了其中的一部分内容,如果你要继续学习,可以多阅读一些论文和开源框架的代码。

View File

@ -0,0 +1,69 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
特别放送 崔新:追根溯源,拨开云雾见青天
你好,我是聂鹏程。今天,我要和你分享的是,一名专栏订阅用户“崔新”的一些学习方法,主题是“追根溯源,拨开云雾见青天”。
虽然专栏已经更新结束,但我仍会关注会回复你的留言,并针对因时间问题未做到最好的文章做一次迭代。所以,我希望你也可以继续关注专栏,继续与我分享你在学习、工作过程中遇到的问题。
话不多说,现在就来看看崔新的故事吧。
大家好,我是崔新,昵称“随心而至”,一名后端开发者,坐标上海,工作一年多,在这里分享下自己学习“分布式技术原理与算法解析”专栏的心得。
我为什么要学这个专栏?
说来惭愧,我对单机的很多内容(比如操作系统、计算机组成原理)还是一知半解,但就是对分布式特别感兴趣,想知道成千上万的机器是如何协作共同完成一个任务的,想让自己的见识更广阔些。所以,我学习这个专栏的初衷,更多的是兴趣,也坚信兴趣就是最好的老师。
聂老师在开篇词中提到“知识碎片化、不成体系、见树不见林”,感觉一下就点到了我的死穴。我也听说过很多分布式概念,但只知其一不知其二。所以,通过这个专栏,我想要系统化地学习分布式技术,了解其背后原理。
另外,分布式框架发展得实在是太快了,如果我只是跟随发展热度去学习,焦虑、疲惫不说,收效甚微也是个大问题。所以,我选择学习这个专栏的另一个目的是,想要看看技术的源头是什么样的,从根儿消除焦虑。
我去年大学毕业参加工作Redis、Kafka、MongoDB、Elasticsearch、Storm、Spark、Hadoop等框架几乎是一瞬间同时跳到我眼前只能一个个去学习。学习一段时间后我发现这些框架有很多共性比如通信、选主、复制、协调。之后我开始有针对地去看一些文章对比着学习这些框架但总感觉不得要领学不透。
直到看到聂老师的开篇词我豁然开朗也更坚信“Software comes and goes, but hardware is forever”。只有追溯到技术的源头明白它从哪里来要到哪里去学习起来才能事半功倍。
我是怎么学习这个专栏的?
在专栏更新的过程中,我都是按照专栏更新的节奏在学习,大多会利用上班路上的时间,像追剧一样。有时我需要反复看几遍才能理解一个知识点,有时我需要停下来思考一段时间才能搞明白一个知识点,所以充分的学习时间对我来说很重要。追专栏更新的这个过程,也帮助我养成了一个良好的学习习惯。
说到学习方法,我感触最深的有两点:一是,形象化和类比,可以帮助我更深入地理解知识;二是,实践很重要。
不知道你有没有注意到,每一篇文章的题目都恰到好处,比如“分布式选举:国不可一日无君”。用熟悉的事件来类比技术问题,一下子就让我明白了这个技术的精髓,可谓四两拨千斤。其实,我们也可以尝试用熟悉的事件将知识形象化,先有个感性的认识。
比如聂老师讲到“分布式技术是多台机器集群如何协同完成一件事儿”你会不会想到通过单机的多进程、多线程等场景来理解呢。我对Java还算熟悉所以会用JUC中的知识来类比。适当的类比、举一反三可以帮助我更好地理解新知识和巩固旧知识。
这个专栏的设定是“技术原理与算法解析”,估计很多同学和我开始的想法一样,希望能多一些实践性的内容。但学完几篇文章后,我细想,聂老师想讲的是“道”,是一般性、普适性的原理,而具体的框架、实现只是在这些核心原理的基础上又添加了很多细节。
在框架中验证原理,可以让我们理解得更深刻;将知识点与业务场景联系起来,才能真正发挥其作用,进而提升自己的业务能力。
所谓,师傅领进门修行在个人,我觉得要想搞清楚、弄明白一个知识点,学完技术原理后,一定要多实践,去看具体框架的官方文档、源码,并上手操作。
学到现在,我的收获和体会
因为我学习这个专栏的目的很明确,所以学完已经更新的这些文章,感觉收获也非常大。
首先我的眼界更开阔了知识也逐渐成体系了。学习时我经常会遇到自己不甚了解的技术比如Actor、单体调度等感觉非常爽。
同时,不得不说的是,聂老师对分布式技术体系“四纵四横”的划分,非常清晰明了,让我知道应该学习哪些知识,以及这些知识间的内在联系是什么样的。专栏每一站都会讲到最常用的那些技术点,各个站串联起来,就是一张分布式技术的知识地图,我要做的就是,按图索骥、多加实践。
其次我逐渐形成了一套自己的学习方法。我觉得这个专栏最大的一个特点就是都是分布式中最最核心的那部分技术。我可以集中精力去学这些最关键的主干知识然后再去丰富枝叶比如具体的框架实现、知识延伸。这种思路其实就符合80/20法则。
我感触比较深的另一个点是,学习知识要多思考、勤总结。聂老师在每篇文章后都提供了一个知识扩展模块和一张思维导图,不仅有助于我复习,还能帮我从广度和深度上完善自己的知识体系。对我来说,这是一个非常好的方法。
我想对专栏初学者说的话
工作一年多来,我总结了些学习方法,也有很多感触,想和你分享、交流。
首先,基础要扎实,学习要系统。虽然我是计算机专业出身,但大学时净忙着学习各种编程语言了,反倒是计算机基础知识没打牢。意识到这一点后,我开始系统地学习计算机组成原理、操作系统、数据库、数据结构与算法等基础知识了。
如果你的基础也和我一样不那么扎实的话,不要着急,静下心来一点一点地学习就好了。
其次,眼光放长远,关注复利效应。现在市面上充斥着各种速成系列,我觉得要离这些东西远一些,否则会一直在低水平重复。不求速成,日拱一卒才更适合我。只要保持增长,即使增长速度没那么快,日积月累的效果也会很可观。这,就是复利效应的魔力。

View File

@ -0,0 +1,95 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
特别放送 徐志强:学习这件事儿,不到长城非好汉
你好,我是聂鹏程。今天,我要和你分享的是,一名专栏订阅用户“徐志强”的学习经历与方法,主题是“学习这件事儿,不到长城非好汉”。
你在学习专栏的过程中,有没有什么独特的学习方法和心路历程呢?欢迎你写在留言区,我很期待能跟你在这里交流、讨论,一起学习进步。
话不多说,现在就请你返回文稿看看徐志强的故事吧。
大家好我是徐志强一名Java开发者从事互联网金融工作三年了。
从2016年毕业到现在我从一枚“小白”变成了“熟练工”。但我一直不甘心从“小熟练工”变成“老熟练工”所以我来了“极客时间”。回过头来看自学的这三年我收获很多也走了不少弯路。所以我想把自己的学习经历分享出来希望能帮你避开些弯路。
我是如何学习的?
我自学的三年,可以分为以下三个阶段。
第一阶段:啥都不懂,啥都想学。我买了非常多的书,数据库、操作系统、分布式等每个知识领域都买了一两本,但只是停留在“看了”的阶段。工作中遇到问题时,我不能融会贯通地用到所学知识,或者说我自学的知识基本用不到工作中。这就像大脑里装了很多武器,但我只记得它们的说明书,无法根据它们的特性做出选择。
其实在这个阶段,我们都很容易有个误解:觉得只要看了足够多的书、学了足够多的框架,能力自然就会得到提高,但其实蜻蜓点水般的学习收效甚微。所以,在我看来,广度学习虽然容易,但其实只算是到了长城脚下,只是起点。
第二阶段:调整思路,有选择地精读某些内容。觉察到不对后,我把之前那些书重新找了出来,并调整学习思路:挑选关键知识点采取精读的方式复习,并开始啃一些框架和库的源码。
“如果不了解核心原理,我始终都是一枚小白或者熟练工” 这是我经常暗示自己的话。为此我曾经通宵Debug就是为了搞明白一段很复杂的源码。每次我在源码中发现了书中描述的知识点后都非常有成就感原来就是这样呀这也让我持续有动力去啃源码。
但我发现,学习源码并没有提升程序设计能力,我又进入了新的困惑期:怎样才能更好地消化和理解知识、怎样才能做到举一反三,养成解决问题的能力。为了爬上长城,我继续寻找答案。
第三阶段:学习一些架构方面的知识,形成知识点和问题的映射。当精读某一领域的知识内容后发现,我在系统设计上短板暴露了出来。因为做系统设计需要有全局思维,需要对整个系统架构的技术栈有清楚的认识,能对比分析不同技术的差异和特性,而不能局限于一个分支。
于是,我开始学习架构方面的知识,学习知识点和问题间的映射关系。每遇到一个问题我都会多思考几套解决方案并进行对比分析,每学到一个新知识点就思考它可以用在哪些场景,以及各个知识点有什么共性和联系。
说到这里,我再分享一段我的面试经历吧。去年年初我去参加面试,因为平时看了不少书、也阅读了不少源码,可以很轻松地通过一面、二面,但最后一面我就有点招架不住了。后来,我请教面试官,自己还欠缺哪方面的知识和能力。他告诉我,单论某一个知识点我掌握得还不错,但我不能将所有知识由点串成线,由线串成面,最终形成网,建议我平时多思考、多总结。
他的这番话对我影响非常大,一下就点醒了我,帮我找到了困惑许久的问题答案。正处于第三阶段学习的我,或许后面我还会有新的困惑,但方法总比问题多。现在,虽然我不知道自己爬到了长城的第几级台阶,但我坚信自己终将站上最高的烽火台!
总结我的学习经历,我觉得有下面几个点值得注意:
一定要坚持学习充电,否则很快就会被淘汰。
不要惧怕学习新技术,新技术不管怎么变化,原理和本质都是不变的。
一定不要只漂在技术的表面,一定要学原理,否则漂的时间久了你自己都不知道漂到哪了。
师傅领进门很关键。学一门新技术,最好是找一本经典的书,或者一门经典的课程系统地学习。
多思考,才能构建自己的知识体系和思维模式,才能将知识化作解决问题的方案。在学习时,我们要思考各个知识点的共性和内在联系,思考这个知识点能解决什么问题,以及遇到的问题能用什么知识点去解决。这样,脑中储存的知识,才能真正为我们所用。
为什么要学习分布式技术和原理?
分布式是当前技术领域的趋势之一,它扩展了计算机的计算边界,是区块链、人工智能、机器学习、大数据等众多前沿技术的基石。
所以,如果你不想永远当一枚小白或者熟练工,也不想被行业淘汰的话,就必须学习分布式技术,且一定要把原理学明白,不能局限于皮毛。我们不能一直随着浪花漂荡,要尝试将自己沉入海底,这样才能经受得住惊涛骇浪的洗礼,最终到彼岸。
我学习的第一个中间件是Kafka刚开始学的时候就被它的分区、选主、备份等概念搞得晕头转向后来又学习了Redis和Elasticsearch。这时我发现这些中间件有一些共同特点都会考虑分区来提高吞吐量都会考虑备份来保证容错和可用性多个备份之间都会选举一个主来提供服务。
慢慢地,对这些概念越来越熟悉后,我发现它们在分布式场景下都会遇到些类似的问题,只是各自的解决方案不同而已。因此,我心中萌生了系统学习分布式技术和原理的想法,因为只有弄清楚了分布式技术的本质和原理,才能在各种眼花缭乱的技术框架中游刃有余。
于是,我开始在网上搜集各种学习资料,又来到了极客时间,来到了“分布式技术原理与算法解析”这个专栏。
我是怎么学习这个专栏的?
说到我是怎么学习这个专栏的,因为之前已经自学过些分布式技术原理,所以可能会和其他人的方法不太一样。
首先,我不是利用碎片时间单篇文章地去学习,而是会找一个比较长的空闲时间,一气呵成地学习完一个模块的内容。这样我可以联系上下文,一下搞定相关知识点。
其次,学完每篇文章,我都会梳理、总结自己对这篇文章的理解,并尝试回答课后思考题,在评论区留言。同时,我也会关注其他同学的留言问题,并尝试做解答。在这个过程中,针对文章中的疑惑点,我会先去搜索相关资料学习,然后将具体的疑惑点留言给老师,等他的解答。
最后,关于知识点的思考和联想很重要。老师讲的很多分布式技术知识点都是通用的,在很多的中间件和框架中都有体现,根据这些理论去思考具体的组件是如何实现的,可以帮助我理解得更深刻。
学习到现在,我最大的收获和体会
到现在为止整个专栏已经更新了18篇文章可以说帮助我建立了对分布式技术和原理比较全面、系统的认知越往后的体会越深。
其实在学习这个专栏前我就对分布式的一些概念比如分布式事务、BASE理论、CAP理论、分布式锁等有些了解但这些知识点在我脑中是独立没有关联起来的。所以这个专栏对我最大的帮助是帮我打通了这些知识点有了一个系统化的认知。
此外,这个专栏给了我极大的信心。因为,聂老师的讲述是由浅入深的,并且还有大量形象的比喻和深刻的类比。比如,用事务模型来解释共享状态的分布式调度,在我看来就很形象、很有深度。
因此,这个专栏给我的感觉是“分布式技术和原理原来就是这么回事呀”,完全消除了我之前的畏难情绪。虽然学完这个课程不代表能透彻掌握分布式的技术原理和算法,但这肯定是一个非常棒的开始。
留给朋友们的建议
如果你之前没有接触过分布式的技术和中间件的话,我建议你先花点时间去补些基础,但这也不妨碍你将这个专栏作为你的“分布式技术和原理导论”来学习。
如果你之前了解过一些分布式技术和分布式组件,但是学得不深入的话,我觉得这个专栏可以帮你建立全面的分布式知识体系,直接带你练习上乘内功。非常值得拥有,我现在也正处于这个阶段。
如果你已经对分布式技术和常用组件都很熟悉了的话,我觉得这个专栏可以带你到更高的层次,去思考分布式的核心本质,帮助你更好地造轮子。
每个人都有自己的一座长城,有些人爬了一年数个月,有些人爬了十年,而有些人爬了一辈子,他们在长城上看到风景肯定大有不同。技术学习件事,我希望自己当一回好汉,爬上自己的长城,也祝愿各位小伙伴能爬上自己的长城!

View File

@ -0,0 +1,143 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
特别放送 那些你不能错过的分布式系统论文
你好,我是聂鹏程。
古人云“以史为鉴,可以知兴替。”说的就是追本溯源的力量。通过学习和思考技术的发展和演进,我们方能更好地把握未来。而对分布式技术追本溯源的方式,无疑就是精读相关经典轮文了。
为此,今天我特地邀请了我的朋友刘梦馨,来与你系统分享下分布式系统领域的经典论文。你有时间和耐力的话,可以逐一阅读、学习下这些论文。
刘梦馨是灵雀云容器平台高级研发工程师负责容器平台的架构、容器网络方案的设计和实现也是开源Kubernetes网络插件 Kube-OVN 作者。他平时非常喜欢阅读论文,也总结了很多高效阅读论文的方法。
话不多说,我们来看刘梦馨的分享吧。
你好,我是刘梦馨。
分布式系统领域有着最令人费解的理论全链路的不确定性堪比物理中的量子力学。同时分布式系统领域又有着当代最宏伟的计算机系统Google、Facebook、亚马逊遍布全球的系统支撑着我们的信息生活。
显然,能够征服分布式系统的,都是理论和实践两手抓两手都要硬的强者。然而,分布式系统领域还有着最高的上手门槛,没有大规模的基础设施、没有潮水般的流量,分布式领域幽灵般的问题并不会浮出水面。
那么,我们应该如何开启征服分布式系统的征程呢?
好在这条路上我们并不孤独。学术大牛们在五十年前就开始探索各方面理论上的问题,全球规模的互联网公司也有着丰富的实践和经验。而这些分布式领域人类的智慧,最终都沉淀为了一篇篇的经典论文。
和普通的技术文章相比,论文的发表有着极为严格的要求,随之而来的也是极高的质量。通过阅读分布式领域的经典问题,我们可以快速吸收前人的智慧,领略大型系统的风采,并收获最为宝贵的实战经验。
现在,就让我们从一篇篇经典论文开始,踏上征战分布式系统的征程吧!
我按照从理论到实践的顺序,将经典的分布式系统论文分成了分布式理论基础、分布式一致性算法、分布式数据结构和分布式系统实战四类,帮助你快速找到自己需要的论文。
这些论文我都给到了标题你可以直接去Google 学术里搜索。
分布式理论基础
分布式理论基础部分的论文,主要从宏观的角度介绍分布式系统中最为基本的问题,从理论上证明分布式系统的不确定、不完美,以及相互间的制约条件。研读这部分论文,你可以了解经典的 CAP定理、BASE理论、拜占庭将军问题的由来及其底层原理。
有了这些理论基础,你就可以明白分布式系统复杂的根源。当再碰到一些疑难杂症,其他人不得其解时,你可以从理论高度上指明方向。
以下就是分布式理论基础部分的论文:
Time, Clocks, and the Ordering of Events in a Distributed System
The Byzantine Generals Problem
Brewers Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services
CAP Twelve Years Later: How the “Rules” Have Changed
BASE: An Acid Alternative
A Simple Totally Ordered Broadcast Protocol
Virtual Time and Global States of Distributed Systems
分布式一致性算法
只要脱离了单机系统,就会存在多机之间不一致的问题。因此,分布式一致性算法,就成了分布式系统的基石。
在分布式一致性算法这一部分我将与你推荐2PC、Paxos、Raft和ZAB等最知名的一致性算法。分布式算法的复杂度比普通算法要高出几个数量级所以这部分论文是最为烧脑的一部分。
搞明白这部分论文,你的空间想象力和统筹规划能力都会得到质的提升。
A Brief History of Consensus, 2PC and Transaction Commit
Paxos Made Simple
Paxos Made Practical
Paxos Made Live: An Engineering Perspective
Raft: In Search of an Understandable Consensus Algorithm
ZooKeeper: Wait-Free Coordination for Internet-Scale Systems
Using Paxos to Build a Scalable, Consistent, and Highly Available Datastore
Impossibility of Distributed Consensus With One Faulty Process
Consensus in the Presence of Partial Synchrony
分布式数据结构
分布式数据结构部分的论文,将与你介绍管理分布式存储问题的知名数据结构原理。通过它们,你可以构建自己的分布式系统应用。
这部分论文的涵盖范围大致包括两部分一是分布式哈希的四个著名算法Chord、Pastry、CAN 和 Kademlia二是Ceph 中使用的 CRUSH、LSM-Tree 和 Tango算法。
和分布式一致性算法类似,分布式数据结构也极其考验空间想象力和统筹规划能力。不过,在经过分布式一致性算法的锻炼后,相信这些对你来说已经不再是问题了。
Chord: A Scalable Peer-to-Peer Lookup Service for Internet Applications
Pastry: Scalable, Distributed Object Location, and Routing for Large-Scale Peer-to-Peer Systems
Kademlia: A Peer-to-Peer Information System Based on the XOR Metric
A Scalable Content-Addressable Network
Ceph: A Scalable, High-Performance Distributed File System
The Log-Structured-Merge-Tree
HBase: A NoSQL Database
Tango: Distributed Data Structure over a Shared Log
分布式系统实战
分布式系统实战部分的论文,将介绍大量互联网公司在分布式领域的实践、系统的架构,以及经验教训。
Google的新老三驾马车Facebook、Twitter、LinkedIn、微软、亚马逊等大公司的知名系统都会在这一部分登场。你将会领会到这些全球最大规模的分布式系统是如何设计、如何实现的以及它们在工程上又碰到了哪些挑战。
The Google File System
BigTable: A Distributed Storage System for Structured Data
The Chubby Lock Service for Loosely-Coupled Distributed Systems
Finding a Needle in Haystack: Facebooks Photo Storage
Windows Azure Storage: A Highly Available Cloud Storage Service with Strong Consistency
Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing
Scaling Distributed Machine Learning with the Parameter Server
Dremel: Interactive Analysis of Web-Scale Datasets
Pregel: A System for Large-Scale Graph Processing
Spanner: Googles Globally-Distributed Database
Dynamo: Amazons Highly Available Key-value Store
S4: Distributed Stream Computing Platform
Storm @Twitter
Large-scale Cluster Management at Google with Borg
F1 - The Fault-Tolerant Distributed RDBMS Supporting Googles Ad Business
Cassandra: A Decentralized Structured Storage System
MegaStore: Providing Scalable, Highly Available Storage for Interactive Services
Dapper, a Large-Scale Distributed Systems Tracing Infrastructure
Kafka: A distributed Messaging System for Log Processing
Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases
以上就是我为你准备的分布式系统经典论文清单了。这个清单里的每一篇论文,都是经典中的经典。很多论文对之后的工业界及学术界产生了翻天覆地的影响,开创了一个又一个火热的产业。
希望你没有被这个清单吓到,当你翻开这些论文后,就会发现它们的内容并不是高高在上,包含了很多很实际、很具体的问题。认真读下去,你甚至会有掌握了屠龙之技的快感,一发而不可收拾。
为了帮助你高效阅读这些论文,并汲取其中的精华,我再和你说说我阅读论文的一些心法吧。
如何高效地阅读论文?
一般来说,单篇论文大概会有 15 到 20 页的内容,如果你是第一次读论文可以把重点放在前面的背景介绍、相关工作和概要设计上。好的论文通常会很仔细地介绍背景知识,帮助你从宏观上先对整个问题有一个初步认识,了解当前现状。
接下来,你可以再根据自己的兴趣,选择是否仔细阅读论文涉及的详细原理和设计。这一部分,通常是论文中最精华的部分,包含了最具创新的理念和做法,内容通常也会比较长,需要花费较多的时间和精力去研究。这时,你可以根据自己的情况,选择一批论文重点突破。
论文最后通常是评测和数据展示部分。这部分内容对我们最大的参考价值在于,学习作者的评测方法、用到的测试工具和测试样例,以便将其运用到工作中。
阅读完一篇论文后,如果你觉得内容还不错的话,可以通过 Google 学术去搜索相关的文章,找到所有引用这篇论文的新作品。这样一来,你就可以通过一篇经典论文不断深入,全面掌握一个领域。
最后,我希望你可以通过经典论文的助力,迅速建立起自己的知识武器库,来攻克日常工作中的难题。

View File

@ -0,0 +1,51 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
结束语 为什么说提升职业竞争力要从尊重、诚实开始?
你好,我是聂鹏程。
斗转星移,时光如梭,一转眼就到了写结束语的时刻。写结束语,也就意味着这个专栏要告一段落了。
在这里,我首先要感谢你一路的鼓励与陪伴!你的孜孜不倦、坚持打卡让我备受感动,你的留言和肯定让我深受鼓舞,你对文中疏漏之处的提醒让我愈发严谨,你引人深思的提问更让我深受启发!
还记得专栏上线是在9月23日那是硕果累累的金秋而专栏结束时已是白雪皑皑的严冬。与此同时业内也正经历着一场“寒冬”各种裁员/辞退消息不绝于耳。
在这种大背景下,我看到有人在焦虑自己的职业生命力,还有人在大谈技术无用论。
记得专栏上线没多久,我就看到这么一条反馈:“分布式只有在面试中才用到,在工作中感觉没有什么用。”从中,我能感受到这位同学对分布式技术的爱恨交加,也有些担忧,有这种想法的同学若干年后会不会成为焦虑大军中的一员。
那么,如何提升职业竞争力,避免“中年危机”和“职业生命力的焦虑”呢?在我看来,无外乎两个方面。
第一要敬畏技术。我们经常听见各种议论说某种技术没有用某种技术太low了。毛主席说“战略上藐视敌人战术上重视敌人。”如果只是战略上轻视我觉得倒无伤大雅可怕的是还没有深入了解就从战术上轻视这些技术。殊不知我们耳熟能详的很多新兴技术都是这些“陈芝麻烂谷子”技术的组合、延伸。
比如Docker已成为业务发布、部署标配的容器组件但其底层依赖的是Linux Namespace环境隔离、cgroups资源限制等基础得不能再基础的技术。再比如区块链作为一种开创性的去中心化多方信任技术现如今成为了国家的战略但其所依赖的分布式共识、P2P网络、非对称加密算法等没有一个是全新的技术。这样的例子数不胜数。
这些案例告诉我们,不是技术没用,而是我们没有深入理解它们,没有找到它们的用武之地。我相信,当我们学会了尊重技术之后,化腐朽为神奇也就不再遥远了。
第二对自己要诚实。前一阵子我遇到之前带的一个小兄弟。了解了他的近况后我建议他在做一些重复工作之余提升一下自我。没想到他说“你看我是985名校毕业技术够好劝退这种事情根本不会发生到我头上。”殊不知那些被劝退的人是不是也曾有过这样的想法呢
如果我们都无法对自己诚实,那何谈去改变自己,更不要说突破自己、完善自己了。如果一个人选择欺骗自己,当时代抛弃他时,可是连一声再见都不会说的。
我想这也是乔布斯为什么会说“Stay hungry, stay foolish”的原因吧
所以,我始终认为提升职业竞争力,要从尊重和诚实开始。
当我们学会尊重和诚实后又应该树立什么样的技术目标呢于我来讲把自己塑造成一个“倒三角”人才或T型人才是一直以来的目标。
我博士的研究方向是并行与分布式技术按照T型人才的原则我努力把并行与分布式技术作为技术发展的根据地也就是字母T中的一竖。在不断做深、做厚分布式技术的同时我会努力探索分布式技术跨界到新兴领域的可能性或结合分布式技术去研究一些新兴技术提升自己的技术广度也就是字母T中的一横。
有了分布式技术这只抓手当IoT、人工智能、区块链、云计算、大数据、边缘计算等新兴技术涌现后我发现我基本上都能插上一手因为这些新兴技术和分布式技术都存在交叉的地方。
那么对你来说,一竖在哪里,一横又在哪里呢?
最后再次感谢你的支持和陪伴希望分布式专栏带给你的不仅仅是知识还有学习能力的提升和思维方式的变化。很幸运你我都处在一个对创新、对人才都格外尊重的时代请相信“Where there is a will, there is a way”。
专栏的结束,也是另外一种开始,我和编辑已经制定了内容迭代计划。比如,中间有一些留言还没来得及回复,我会花时间处理完。再比如,有同学反馈其中几篇文章说写得不通透,我看完觉得确实觉得有道理,所以会继续优化。
对于你来说也是这样,分布式博大精深,岂是我一个专栏能讲完的?所以,你的学习之旅,也才刚刚开始。让我们一起加油,一起去探寻那个最好的自己。