first commit
This commit is contained in:
68
专栏/分布式技术原理与算法解析/00开篇词四纵四横,带你透彻理解分布式技术.md
Normal file
68
专栏/分布式技术原理与算法解析/00开篇词四纵四横,带你透彻理解分布式技术.md
Normal file
@ -0,0 +1,68 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
00 开篇词 四纵四横,带你透彻理解分布式技术
|
||||
你好,我是聂鹏程,智载云帆的CTO。在接下来的三个月时间里,我将带你打卡分布式技术。
|
||||
|
||||
在众多计算机技术当中,分布式技术无疑是最璀璨的明珠之一。毫不夸张地说,没有分布式技术就没有互联网,也就没有现在的阿里巴巴、腾讯、亚马逊、Facebook、谷歌等科技巨头,更不会有以信息技术为核心的、对人类历史产生巨大变革的第三次工业革命。万维网、Email、DNS等,都是分布式系统的典型代表。
|
||||
|
||||
随着分布式技术的不断发展,它也早已不再局限于传统的互联网等应用场景。在今年的两会期间,全国政协委员、360董事长周鸿祎更是大唱I’M 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个纵向技术线。由于分布式追踪、分布式部署虽属于支撑技术,但并不会影响业务的构成,因此我不会在本专栏中进行讲解。
|
||||
|
||||
最后,如果说现在分布式领域里各种包装出来的、五花八门的新技术,像是令人高不可攀的女神、男神的话,那么这个分布式通识课程中所提炼出来的体系和核心知识点无疑就是女神、男神素颜的样子。我想说,等你看尽素颜,无论是女神、男神也好,还是各种高大上的技术也好,也就不会觉得那么高不可攀了。
|
||||
|
||||
既然你已经看到了这里,相信你也看到了学习分布式技术知识的迫切需求,那么不妨请你在留言区做个自我介绍,给我说说你的困惑,也说说你想通过这个专栏收获些什么,这样我后续也可以根据你的情况进行有针对性的讲解。
|
||||
|
||||
我是聂鹏程,今天的内容就到这里了,我们下一讲再会。
|
||||
|
||||
|
||||
|
||||
|
117
专栏/分布式技术原理与算法解析/01分布式缘何而起:从单兵,到游击队,到集团军.md
Normal file
117
专栏/分布式技术原理与算法解析/01分布式缘何而起:从单兵,到游击队,到集团军.md
Normal file
@ -0,0 +1,117 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
01 分布式缘何而起:从单兵,到游击队,到集团军
|
||||
你好,我是聂鹏程。这是专栏的第一篇文章,我们先来聊聊什么是分布式。
|
||||
|
||||
与其直接用些抽象、晦涩的技术名词去给分布式下一个定义,还不如从理解分布式的发展驱动因素开始,我们一起去探寻它的本质,自然而然地也就清楚它的定义了。
|
||||
|
||||
在今天这篇文章中,我将带你了解分布式的起源,是如何从单台计算机发展到分布式的,进而帮助你深入理解什么是分布式。为了方便你更好地理解这个演进过程,我将不考虑多核、多处理器的情况,假定每台计算机都是单核、单处理器的。
|
||||
|
||||
分布式起源
|
||||
|
||||
单兵模式:单机模式
|
||||
|
||||
1946年情人节发布的ENIAC是世界上的第一台通用计算机,它占地170平米重达30吨,每秒可进行5000次加法或者400次乘法运算,标志着单机模式的开始。
|
||||
|
||||
所谓单机模式是指,所有应用程序和数据均部署在一台电脑或服务器上,由一台计算机完成所有的处理。
|
||||
|
||||
以铁路售票系统为例,铁路售票系统包括用户管理、火车票管理和订单管理等模块,数据包括用户数据、火车票数据和订单数据等,如果使用单机模式,那么所有的模块和数据均会部署在同一台计算机上,也就是说数据存储、请求处理均由该计算机完成。这种模式的好处是功能、代码和数据集中,便于维护、管理和执行。
|
||||
|
||||
单机模式的示意图,如下所示:
|
||||
|
||||
|
||||
|
||||
这里需要注意的是,本文的所有示意图中,紫色虚线表示在一台计算机内。
|
||||
|
||||
事物均有两面性,我们再来看看单机模式的缺点。单个计算机的处理能力取决于CPU和内存等,但硬件的发展速度和性能是有限的,而且升级硬件的性价比也是我们要考虑的,由此决定了CPU和内存等硬件的性能将成为单机模式的瓶颈。
|
||||
|
||||
你有没有发现,单机模式和单兵作战模式非常相似,单台计算机能力再强,就好比特种兵以一敌百,但终归能力有限。此外,将所有任务都交给一台计算机,也会存在将所有鸡蛋放到一个篮子里的风险,也就是单点失效问题。
|
||||
|
||||
归纳一下,单机模式的主要问题是:性能受限、存在单点失效问题。
|
||||
|
||||
游击队模式:数据并行或数据分布式
|
||||
|
||||
既然单机模式存在性能和可用性的问题。那么,有没有什么更好的计算模式呢?答案是肯定的。
|
||||
|
||||
为解决单机模式的问题,并行计算得到了发展,进而出现了数据并行(也叫作数据分布式)模式。并行计算采用消息共享模式使用多台计算机并行运行或执行多项任务,核心原理是每台计算机上执行相同的程序,将数据进行拆分放到不同的计算机上进行计算。
|
||||
|
||||
请注意,并行计算强调的是对数据进行拆分,任务程序在每台机器上运行。要达到这个目的,我们必须首先把单机模式中的应用和数据分离,才可能实现对数据的拆分。这里的应用就是执行任务的程序,任务就是提交的请求。以铁路售票系统为例,运行在服务器上的用户管理、火车票管理和订单管理等程序就是应用,用户提交的查询火车票、购买火车票的请求就是任务。
|
||||
|
||||
在单机模式中,应用和数据均在一台计算机或服务器上,要实现数据的并行,首先必须将应用和数据分离以便将应用部署到不同的计算机或服务器上;然后,对同类型的数据进行拆分,比方说,不同计算机或服务器上的应用可以到不同的数据库上获取数据执行任务。
|
||||
|
||||
以铁路售票系统的数据并行为例,主要包括两个步骤,如下所示:
|
||||
|
||||
第一步,将应用与数据分离,分别部署到不同的服务器上:
|
||||
|
||||
|
||||
|
||||
第二步,对数据进行拆分,比如把同一类型的数据拆分到两个甚至更多的数据库中,这样应用服务器上的任务就可以针对不同数据并行执行了。
|
||||
|
||||
对于铁路售票系统来说,根据线路将用户、火车票和订单数据拆分到不同的数据库中,部署到不同的服务器上,比如京藏线的数据放在数据库服务器1上的数据库中,沪深线的数据放在数据库服务器2上的数据库中。
|
||||
|
||||
|
||||
|
||||
需要注意的是,为了更好地帮助你理解这个数据拆分的过程,我在这里选择拆分数据库的方式进行讲解。由于数据库服务器本身的并发特性,因此你也可以根据你的业务情况进行选择,比方说所有业务服务器共用一个数据库服务器,而不一定真的需要去进行数据库拆分。
|
||||
|
||||
可以看出,在数据并行或数据分布式模式中,每台计算机都是全量地从头到尾一条龙地执行一个程序,就像一个全能的铁道游击队战士。所以,你也可以将这种模式形象地理解成游击队模式,就和铁道游击队插曲的歌词有点类似:“我们扒飞车那个搞机枪,撞火车那个炸桥梁……”
|
||||
|
||||
这种模式的好处是,可以利用多台计算机并行处理多个请求,使得我们可以在相同的时间内完成更多的请求处理,解决了单机模式的计算效率瓶颈问题。但这种模式仍然存在如下几个问题,在实际应用中,我们需要对其进行相应的优化:
|
||||
|
||||
|
||||
相同的应用部署到不同的服务器上,当大量用户请求过来时,如何能比较均衡地转发到不同的应用服务器上呢?解决这个问题的方法是设计一个负载均衡器,我会在“分布式高可靠”模块与你讲述负载均衡的相关原理。
|
||||
当请求量较大时,对数据库的频繁读写操作,使得数据库的IO访问成为瓶颈。解决这个问题的方式是读写分离,读数据库只接收读请求,写数据库只接收写请求,当然读写数据库之间要进行数据同步,以保证数据一致性。
|
||||
当有些数据成为热点数据时,会导致数据库访问频繁,压力增大。解决这个问题的方法是引入缓存机制,将热点数据加载到缓存中,一方面可以减轻数据库的压力,另一方面也可以提升查询效率。
|
||||
|
||||
|
||||
从上面介绍可以看出,数据并行模式实现了多请求并行处理,但如果单个请求特别复杂,比方说需要几天甚至一周时间的时候,数据并行模式的整体计算效率还是不够高。
|
||||
|
||||
由此可见,数据并行模式的主要问题是:对提升单个任务的执行性能及降低时延无效。
|
||||
|
||||
集团军模式:任务并行或任务分布式
|
||||
|
||||
那么,有没有办法可以提高单个任务的执行性能,或者缩短单个任务的执行时间呢?答案是肯定的。任务并行(也叫作任务分布式)就是为解决这个问题而生的。那什么是任务并行呢?
|
||||
|
||||
任务并行指的是,将单个复杂的任务拆分为多个子任务,从而使得多个子任务可以在不同的计算机上并行执行。
|
||||
|
||||
我们仍以铁路售票系统为例,任务并行首先是对应用进行拆分,比如按照领域模型将用户管理、火车票管理、订单管理拆分成多个子系统分别运行在不同的计算机或服务器上。换句话说,原本包括用户管理、火车票管理和订单管理的一个复杂任务,被拆分成了多个子任务在不同计算机或服务器上执行,如下图所示:
|
||||
|
||||
|
||||
|
||||
可以看出,任务并行模式完成一项复杂任务主要有两个核心步骤:首先将单任务拆分成多个子任务,然后让多个子任务并行执行。这种模式和集团军模式很像,任务拆分者对应领导者,不同子系统对应不同兵种,不同子程序执行不同任务就像不同的兵种执行不同的命令一样,并且运行相同子系统或子任务的计算机又可以组成一个兵团。
|
||||
|
||||
在集团军模式中,由于多个子任务可以在多台计算机上运行,因此通过将同一任务待处理的数据分散到多个计算机上,在这些计算机上同时进行处理,就可以加快任务执行的速度。因为,只要一个复杂任务拆分出的任意子任务执行时间变短了,那么这个任务的整体执行时间就变短了。
|
||||
|
||||
当然,nothing is perfect。集团军模式在提供了更好的性能、扩展性、可维护性的同时,也带来了设计上的复杂性问题,毕竟对一个大型业务的拆分也是一个难题。不过,对于大型业务来讲,从长远收益来看,这个短期的设计阵痛是值得的。这也是许多大型互联网公司、高性能计算机构等竞相对业务进行拆分以及重构的一个重要原因。
|
||||
|
||||
分布式是什么?
|
||||
|
||||
讲了半天,那到底什么是分布式呢?
|
||||
|
||||
总结一下,分布式其实就是将相同或相关的程序运行在多台计算机上,从而实现特定目标的一种计算方式。
|
||||
|
||||
从这个定义来看,数据并行、任务并行其实都可以算作是分布式的一种形态。从这些计算方式的演变中不难看出,产生分布式的最主要驱动力量,是我们对于性能、可用性及可扩展性的不懈追求。
|
||||
|
||||
总结
|
||||
|
||||
在今天这篇文章中,我和你分享了分布式的起源,即从单机模式到数据并行(也叫作数据分布式)模式,再到任务并行(也叫作任务分布式)模式。
|
||||
|
||||
单机模式指的是,所有业务和数据均部署到同一台机器上。这种模式的好处是功能、代码和数据集中,便于维护、管理和执行,但计算效率是瓶颈。也就是说单机模式性能受限,也存在单点失效的问题。
|
||||
|
||||
数据并行(也叫作数据分布式)模式指的是,对数据进行拆分,利用多台计算机并行执行多个相同任务,通过在相同的时间内完成多个相同任务,从而缩短所有任务的总体执行时间,但对提升单个任务的执行性能及降低时延无效。
|
||||
|
||||
任务并行(也叫作任务分布式)模式指的是,单任务按照执行流程,拆分成多个子任务,多个子任务分别并行执行,只要一个复杂任务中的任意子任务的执行时间变短了,那么这个业务的整体执行时间也就变短了。该模式在提高性能、扩展性、可维护性等的同时,也带来了设计上的复杂性问题,比如复杂任务的拆分。
|
||||
|
||||
在数据并行和任务并行这两个模式的使用上,用户通常会比较疑惑,到底是采用数据并行还是任务并行呢?一个简单的原则就是:任务执行时间短,数据规模大、类型相同且无依赖,则可采用数据并行;如果任务复杂、执行时间长,且任务可拆分为多个子任务,则考虑任务并行。在实际业务中,通常是这两种模式并用。赶紧行动起来,去分析一下你的业务到底应该采用哪种分布式模式吧,加油!
|
||||
|
||||
课后思考
|
||||
|
||||
你觉得分布式与传统的并行计算的区别是什么?你可以结合多核、多处理器的情况进行思考。
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!
|
||||
|
||||
|
||||
|
||||
|
106
专栏/分布式技术原理与算法解析/02分布式系统的指标:啥是分布式的三围.md
Normal file
106
专栏/分布式技术原理与算法解析/02分布式系统的指标:啥是分布式的三围.md
Normal file
@ -0,0 +1,106 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
02 分布式系统的指标:啥是分布式的三围
|
||||
你好,我是聂鹏程。
|
||||
|
||||
在上一篇文章中,通过对分布式发展历程的学习,我们对分布式技术有了一个整体印象。接下来,我们就再来看看可以用哪些指标去具体地衡量一个分布式系统。如果你已经对分布式系统的指标了解得很清楚了,可以直接跳过这篇文章,学习下一讲的内容。
|
||||
|
||||
分布式系统的指标
|
||||
|
||||
从分布式技术的起源可以看出,分布式系统的出现就是为了用廉价的、普通的机器解决单个计算机处理复杂、大规模数据和任务时存在的性能问题、资源瓶颈问题,以及可用性和可扩展性问题。换句话说,分布式的目的是用更多的机器,处理更多的数据和更复杂的任务。
|
||||
|
||||
由此可以看出,性能、资源、可用性和可扩展性是分布式系统的重要指标。没错,它们就是分布式系统的“三围”。接下来,我们一起来看看这几个指标吧。
|
||||
|
||||
性能(Performance)
|
||||
|
||||
性能指标,主要用于衡量一个系统处理各种任务的能力。无论是分布式系统还是单机系统,都会对性能有所要求。
|
||||
|
||||
不同的系统、服务要达成的目的不同,关注的性能自然也不尽相同,甚至是相互矛盾。常见的性能指标,包括吞吐量(Throughput)、响应时间(Response Time)和完成时间(Turnaround Time)。
|
||||
|
||||
吞吐量指的是,系统在一定时间内可以处理的任务数。这个指标可以非常直接地体现一个系统的性能,就好比在客户非常多的情况下,要评判一个银行柜台职员的办事效率,你可以统计一下他在1个小时内接待了多少客户。常见的吞吐量指标有QPS(Queries Per Second)、TPS(Transactions Per Second)和BPS(Bits 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(或3N,3 Nines)、5个9(或9N,9 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(高性能计算)、大数据、云计算、区块链等业务或系统的不同诉求,进而得出了系统设计者需要关注哪些指标。你在设计其他类型的系统时,可以按照这个思路进行取舍。
|
||||
|
||||
我在文中提到了,分布式系统的指标之间会存在一些冲突或约束。那你不妨思考一下:我们今天讲解的指标中,哪些指标之间是相互制约、相互冲突的,它们又是如何制约的呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
179
专栏/分布式技术原理与算法解析/03分布式互斥:有你没我,有我没你.md
Normal file
179
专栏/分布式技术原理与算法解析/03分布式互斥:有你没我,有我没你.md
Normal 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类典型的分布式互斥方法,即:集中式算法、分布式算法,以及令牌环算法,并列举了对应的适用场景,相信你通过今天的学习,一定可以为你的场景选择一个合适的分布式互斥算法了,加油!
|
||||
|
||||
接下来,我把今天的内容通过下面的一张思维导图再全面总结下。
|
||||
|
||||
|
||||
|
||||
课后问题
|
||||
|
||||
最后,我想请你思考以下两个问题:
|
||||
|
||||
|
||||
你认为集中式算法、分布式算法和令牌环算法,还有什么可以改进的地方吗?
|
||||
传统单机上的互斥方法,为什么不能用于分布式环境呢?
|
||||
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
170
专栏/分布式技术原理与算法解析/04分布式选举:国不可一日无君.md
Normal file
170
专栏/分布式技术原理与算法解析/04分布式选举:国不可一日无君.md
Normal 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;
|
||||
Follower,Leader的跟随者,不可以发起选举。
|
||||
|
||||
|
||||
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算法
|
||||
|
||||
ZAB(ZooKeeper 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表示本节点的唯一ID;server_zxID表示本节点存放的数据ID,数据ID越大表示数据越新,选举权重越大;epoch表示当前选取轮数,一般用逻辑时钟表示。
|
||||
|
||||
ZAB选举算法的核心是“少数服从多数,ID大的节点优先成为主”,因此选举过程中通过(vote_id, vote_zxID)来表明投票给哪个节点,其中vote_id表示被投票节点的ID,vote_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算法,并通过实例与你展示了各类方法的选举流程。
|
||||
|
||||
我将今天的主要内容总结为了如下所示的思维导图,来帮助你加深理解与记忆。
|
||||
|
||||
|
||||
|
||||
思考题
|
||||
|
||||
|
||||
分布式选举和一致性的关系是什么?
|
||||
你是否见到过一个集群中存在双主的场景呢?
|
||||
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
171
专栏/分布式技术原理与算法解析/05分布式共识:存异求同.md
Normal file
171
专栏/分布式技术原理与算法解析/05分布式共识:存异求同.md
Normal file
@ -0,0 +1,171 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
05 分布式共识:存异求同
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
首先,我们来回忆下上篇文章的分布式选举。分布式选举问题,是从多个节点中选出一个主节点,相关的选举方法几乎都有一个共同特点:每个节点都有选举权和被选举权。大部分选举方法采用多数策略,也就是说一个节点只有得到了大部分节点的同意或认可才能成为主节点,然后主节点向其他节点宣告主权。
|
||||
|
||||
其实,这个选主过程就是一个分布式共识问题,因为每个节点在选出主节点之前都可以认为自己会成为主节点,也就是说集群节点“存异”;而通过选举的过程选出主节点,让所有的节点都认可该主节点,这叫“求同”。由此可见,分布式共识的本质就是“存异求同”。
|
||||
|
||||
所以,从本质上看,分布式选举问题,其实就是传统的分布式共识方法,主要是基于多数投票策略实现的。基于多数投票策略的分布式选举方法,如果用于分布式在线记账一致性问题中,那么记账权通常会完全掌握到主节点的手里,这使得主节点非常容易造假,且存在性能瓶颈。因此,分布式选举不适用于分布式在线记账的一致性问题。在今天这篇文章中,我就带你了解另外一种用于解决分布式在线记账一致性问题的分布式共识技术。
|
||||
|
||||
这里所说的分布式在线记账,是指在没有集中的发行方,也就是没有银行参与的情况下,任意一台接入互联网的电脑都能参与买卖,所有看到该交易的服务器都可以记录这笔交易,并且记录信息最终都是一致的,以保证交易的准确性。而如何保证交易的一致性,就是该场景下的分布式共识问题。
|
||||
|
||||
接下来,我们就一起学习下分布式共识技术吧。
|
||||
|
||||
什么是分布式共识?
|
||||
|
||||
假设,现在有5台服务器,分散在美国华盛顿、英国伦敦、法国巴黎、中国北京、中国上海,分别对应着用户{A,B,C,D,E}。现在,用户A给用户B转了100元。
|
||||
|
||||
在传统方法中,我们通过银行进行转账并记录该笔交易。但分布式在线记账方法中,没有银行这样的一个集中方,而是由上述5台服务器来记录该笔交易。但是,这5台服务器均是有各自想法的个体,都可以自主操作或记录,那么如何保证记录的交易是一致的呢?这,就是分布式共识技术要解决的问题。
|
||||
|
||||
可以看出,分布式共识就是在多个节点均可独自操作或记录的情况下,使得所有节点针对某个状态达成一致的过程。通过共识机制,我们可以使得分布式系统中的多个节点的数据达成一致。
|
||||
|
||||
看到这里,相信你已经看出来了,我在这里说的分布式在线记账,就是近几年比较火的区块链技术解决的问题。而分布式共识技术,就是区块链技术共识机制的核心。
|
||||
|
||||
接下来,请和我一起看看分布式共识是如何实现的,有哪些方法吧。
|
||||
|
||||
分布式共识方法
|
||||
|
||||
为了不影响你理解分布式共识的核心技术,我会先和你分享区块链中的一个核心概念:挖矿。
|
||||
|
||||
在传统的交易方式中,用户A给用户B转账,需要银行来实行具体的转账操作并记录交易,银行会从中收取相应的手续费。而采用分布式在线记账的话,参与记录这笔交易的服务器,也可以从中获得一些奖励(这些奖励,在区块链技术中可以换成钱)。所有服务器帮助记录交易并达成一致的过程,就是区块链中的“挖矿”。
|
||||
|
||||
区块链是一种链式数据结构,由包含交易信息的区块通过哈希指针、根据时间顺序连接而成,也是一种分布式数据库。区块是区块链的主要组成部分,每个区块由区块头和区块内容数据构成。区块头记录了时间戳,并用于保证区块链的连接性;区块内容数据中包含了多条交易信息。如果你对区块链技术的其他概念感兴趣的话,可以自行查阅更多资料。
|
||||
|
||||
接下来,我将与你介绍3种主流的解决分布式在线记账一致性问题的共识技术,即:PoW(Proof-of-Work,工作量证明)、PoS(Proof-of-Stake,权益证明)和DPoS(Delegated 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,仅能够包含 3000~4000 笔交易,平均每秒只能够处理 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在EOS(Enterprise 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。希望通过这篇文章,你能对共识技术有一定的了解,能勇敢、自信地去探索分布式共识技术和区块链技术。
|
||||
|
||||
最后,我再用思维导图概括一下今天的内容。
|
||||
|
||||
|
||||
|
||||
思考题
|
||||
|
||||
你能描述出拜占庭将军问题是什么吗?你认为可以如何解决拜占庭将军的容错问题呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
275
专栏/分布式技术原理与算法解析/06分布式事务:Allornothing.md
Normal file
275
专栏/分布式技术原理与算法解析/06分布式事务:Allornothing.md
Normal 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 Protocol,2PC),用于保证分布式系统中事务提交时的数据一致性,是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 Protocol,3PC),是对二阶段提交(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 Queue,MQ,消息队列),用于在多个应用之间进行消息传递。实际使用中,阿里就是采用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理论,因此适用于柔性事务。
|
||||
|
||||
在分布式系统的设计与实现中,分布式事务是不可或缺的一部分。可以说,没有实现分布式事务的分布式系统,不是一个完整的分布式系统。分布式事务的实现过程看似复杂,但将方法分解剖析后,你就会发现分布式事务的实现是有章可循的。
|
||||
|
||||
我将实现分布式事务常用的三个算法整理为了一张思维导图,以帮助你加深理解与记忆。
|
||||
|
||||
|
||||
|
||||
思考题
|
||||
|
||||
你觉得分布式互斥与分布式事务之间的关系是什么呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
210
专栏/分布式技术原理与算法解析/07分布式锁:关键重地,非请勿入.md
Normal file
210
专栏/分布式技术原理与算法解析/07分布式锁:关键重地,非请勿入.md
Normal 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表示锁id,value = currentTime + timeOut,表示当前时间+超时时间。也就是说,某个进程获得key这把锁后,如果在value的时间内未释放锁,系统就会主动释放锁。
|
||||
|
||||
setnx函数的返回值有0和1:
|
||||
|
||||
|
||||
返回1,说明该服务器获得锁,setnx将key对应的value设置为当前时间 + 锁的有效时间。
|
||||
返回0,说明其他服务器已经获得了锁,进程不能进入临界区。该服务器可以不断尝试setnx操作,以获得锁。
|
||||
|
||||
|
||||
我还是以电商售卖吹风机的场景为例,和你说明基于缓存实现的分布式锁,假设现在库存数量是足够的。
|
||||
|
||||
用户A的请求因为网速快,最先到达Server2,setnx操作返回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中就有所涉及。通过今天这篇文章对分布式锁原理及方法的讲解,我相信你会发现分布式锁不再那么神秘、难懂,然后以此为基础对分布式锁进行更深入的学习和应用。
|
||||
|
||||
接下来,我把今天的内容通过下面的一张思维导图再全面总结下。
|
||||
|
||||
|
||||
|
||||
思考题
|
||||
|
||||
分布式锁与分布式互斥的关系是什么呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
165
专栏/分布式技术原理与算法解析/08分布式技术是如何引爆人工智能的?.md
Normal file
165
专栏/分布式技术原理与算法解析/08分布式技术是如何引爆人工智能的?.md
Normal file
@ -0,0 +1,165 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
08 分布式技术是如何引爆人工智能的?
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
通过前面课程,相信你已经对分布式的起源以及什么是分布式有了一定的了解,从用户留言来看很多同学对分布式技术、分布式技术的应用,以及分布式技术的重要性非常感兴趣。所以,我将以人工智能技术为例,带你了解分布式技术的应用及其重要性。
|
||||
|
||||
什么是人工智能?
|
||||
|
||||
2016年3月,Google AlphaGo以4:1的比分赢得了世界围棋冠军李世石。这场围棋人机大战,将人工智能技术推向了高潮。现在,人工智能已经广泛渗透到了我们的生活中,比如手机拍照美化、人脸识别、平安城市、自然语言处理、语音识别等。
|
||||
|
||||
那么,到底什么是人工智能呢?
|
||||
|
||||
所谓人工智能,其实就是希望机器能够模拟人的思维,像人一样智能。目前,对人工智能的定义大多可划分为四类,即机器“像人一样思考”“像人一样行动”“理性地思考”和“理性地行动”。这里的行动,指的是采取行动或制定行动的决策。
|
||||
|
||||
那人工智能是如何让机器像人那样智能呢?人并不是天生就会解决问题的,我们经常会听到一句经典的话“见多识广”,人遇到新的问题,是通过学习新知识,然后结合自己的经验去解决的。比如,人并不是生来就认识香蕉,而是通过后天的学习(包括学习香蕉的形状、颜色、口味等)来获取识别香蕉的经验,当下次再看到香蕉时,就知道这是香蕉了。
|
||||
|
||||
人工智能要模拟人的智能也类似,需要通过大量的数据进行学习和分析获得规律(即建立一个模型),然后利用该规律或模型对未知数据进行预测,以判断是否与建模数据具有相同特征。
|
||||
|
||||
从人工智能的定义可以看出,数据、模型(也叫作算法)、算力是人工智能的三大核心。在一定程度上可以说,数据决定了机器学习能达到的上限,模型提供了方法。因此数据处理和模型训练是人工智能的关键技术,算力决定了数据处理和模型训练的实用性能,而分布式技术就是解决算力的不二妙招。
|
||||
|
||||
接下来,我就对数据处理和模型训练进行具体分析,来帮助你了解人工智能中需要用到哪些分布式技术来解决算力问题。
|
||||
|
||||
数据处理
|
||||
|
||||
数据处理又称数据预处理,是指通过数据统计、数据集成、数据清理、数据规约、数据变换等方法,对数据缺失、数据噪声、数据冗余、多数据源等问题进行处理以得到高质量数据,为模型训练提供高质量输入,是人工智能不可缺少的环节。
|
||||
|
||||
其实,数据处理类似于我们的知识整理过程。一个精心打造的、体系化梳理过的专栏文章,可以帮助我们在学习一门课程时,少走弯路、避免踩雷、达到事半功倍的效果。同样地,一个精心处理过的数据集,对于人工智能的模型训练也能起到事半功倍的效果,一方面可以缩短机器学习的周期,另一方面也可以提高机器学习的质量。
|
||||
|
||||
所以接下来,我们就一起看看数据预处理的方法吧。
|
||||
|
||||
数据统计(Data Statistics)。数据统计是数据预处理的第一步,其范围、规模、方式等会直接影响数据分析的结果。常见的统计特征有最大值、最小值、均值、中位数、方差、标准差等。
|
||||
|
||||
数据集成(Data Integration)。数据的收集有多种途径,比如文件数据、数据库数据、问卷数据等,而不同的数据源,其数据的存储方式、命名规则、单位等不尽相同,所以我们需要数据集成来将多个数据源的数据整合到一起,以保证数据结构、属性的一致性,并去除冗余数据,方便后续分析。
|
||||
|
||||
数据清理(Data Cleaning)。由于用户忘记或设备损坏,经常会造成部分数据缺失;由于仪器故障或用户填写错误,经常会出现数据错误(噪声数据)等。如果不对这些数据做任何处理,后面的模型训练过程将产生严重偏差。数据清理过程就是用来解决这个问题的,它可以通过平均值或众数等来填充丢失值或修改这些噪声值。
|
||||
|
||||
数据规约(Data Reduction)。由于机器学习中的数据量很大,因此会导致很多重复的特征,或者很多不重要的特征(比如ID号等)。数据规约通常指通过主成分分析法 (Principal Component Analysis,PCA)、小波变换(Wavelet Transform,WT)等方法去除重复特征及不重要的特征,从而减少数据的维度或者数据量,降低问题复杂度,同时不影响后面训练的结果。
|
||||
|
||||
数据变换(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种方法,并介绍了其中涉及的关键分布式技术,比如数据的分布式存储和管理、分布式通信等。没有这些关键的分布式技术,分布式模型训练其实就是空谈了。
|
||||
|
||||
现在,我将人工智能中涉及的关键分布式技术整理为了一张表格,以方便你学习。
|
||||
|
||||
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
192
专栏/分布式技术原理与算法解析/09分布式体系结构之集中式结构:一人在上,万人在下.md
Normal file
192
专栏/分布式技术原理与算法解析/09分布式体系结构之集中式结构:一人在上,万人在下.md
Normal 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:集群状态存储,默认采用etcd,etcd是一个分布式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 Master,Mesos Master再将请求转发给Mesos Agent,Mesos 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是否存活呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
192
专栏/分布式技术原理与算法解析/10分布式体系结构之非集中式结构:众生平等.md
Normal file
192
专栏/分布式技术原理与算法解析/10分布式体系结构之非集中式结构:众生平等.md
Normal file
@ -0,0 +1,192 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
10 分布式体系结构之非集中式结构:众生平等
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
在上一篇文章中,我带你了解了分布式体系结构中的集中式结构。虽然很多云上的管理都采用了集中式结构,但是这种结构对中心服务器性能要求很高,而且存在单点瓶颈和单点故障问题。
|
||||
|
||||
为了解决这个问题,分布式领域中又出现了另一个经典的系统结构,即非集中式结构,也叫作分布式结构。那什么是非集中式结构呢,它的原理是什么样的,又有哪些集群采用了这种结构呢?
|
||||
|
||||
今天,我们就一起打卡非集中式结构,揭开它的神秘面纱吧。
|
||||
|
||||
什么是非集中式结构?
|
||||
|
||||
在非集中式结构中,服务的执行和数据的存储被分散到不同的服务器集群,服务器集群间通过消息传递进行通信和协调。
|
||||
|
||||
|
||||
|
||||
也就是说,在非集中式结构中,没有中央服务器和节点服务器之分,所有的服务器地位都是平等(对等)的,也就是我们常说的“众生平等”。这样一来,相比于集中式结构,非集中式结构就降低了某一个或者某一簇计算机集群的压力,在解决了单点瓶颈和单点故障问题的同时,还提升了系统的并发度,比较适合大规模集群的管理。
|
||||
|
||||
所以近几年来,Google、 Amazon、Facebook、阿里巴巴、腾讯等互联网公司在一些业务中也相继采用了非集中式结构。
|
||||
|
||||
接下来,我将为你介绍3种典型的非集中式架构系统,包括Akka集群、Redis集群和Cassandra集群,来帮助你深入理解非集中式架构。
|
||||
|
||||
Akka集群
|
||||
|
||||
在介绍Akka集群的结构之前,我带你了解一下什么是Akka框架吧。
|
||||
|
||||
Akka是一个开发库和运行环境,用于构建可扩展的、弹性的、快速响应的应用程序。Akka框架是基于Actor模型实现的,Actor模型是一个封装了状态和行为的对象,它接收消息并基于该消息执行计算。Actor之间通信的唯一机制就是消息传递,每个Actor都有自己的MailBox。
|
||||
|
||||
比如,在分布式系统中,一个服务器或一个节点可以视为一个Actor,Actor与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集群都是典型的非集中式集群组织结构,目前应用已经非常广泛了,所以有很多的实现案例可供你借鉴了。对于具体集群使用配置可参考相应的官网手册,讲得比较全和细。
|
||||
|
||||
相信你通过对今天的学习,很快就可以上手非集中式架构的集群管理应用和实践了。加油,挑战一下自己吧!
|
||||
|
||||
思考题
|
||||
|
||||
边缘计算中边缘设备的管理,你认为适合非集中式结构还是集中式结构呢,原因又是什么呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
160
专栏/分布式技术原理与算法解析/11分布式调度架构之单体调度:物质文明、精神文明一手抓.md
Normal file
160
专栏/分布式技术原理与算法解析/11分布式调度架构之单体调度:物质文明、精神文明一手抓.md
Normal file
@ -0,0 +1,160 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
11 分布式调度架构之单体调度:物质文明、精神文明一手抓
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
在前两篇文章中,我和你分析了云资源管理的集中式架构和非集中式架构。可以看出,分布式系统架构的目的是,将多个服务器资源管理起来,寻找合适的服务器去执行用户任务。
|
||||
|
||||
那,什么是合适的服务器呢?衡量一个服务器是否合适会涉及很多条件或约束,比如在一些场景下,任务存在优先级,那当我们需要执行多个任务的时候,通常需要满足优先级高的任务优先执行的条件。但在这些条件中,服务器资源能够满足用户任务对资源的诉求是必须的。
|
||||
|
||||
而为用户任务寻找合适的服务器这个过程,在分布式领域中叫作调度。在分布式系统架构中,调度器就是一个非常重要的组件。它通常会提供多种调度策略,负责完成具体的调度工作。
|
||||
|
||||
当然,不同的分布式架构的调度器原理也不一样,最常见或最直观的是单体调度,就是任务和分布式系统中的空闲资源直接进行匹配调度。也就是说,调度器同时管理任务和资源,如果把资源比作“物质文明”,把任务比作“精神文明”,那么单体调度就是“物质文明和精神文明一手抓”。
|
||||
|
||||
接下来,我带你一起打卡分布式调度架构之单体调度。
|
||||
|
||||
首先,让我们先了解一下什么是单体调度。
|
||||
|
||||
什么是单体调度?
|
||||
|
||||
分布式系统中的单体调度是指,一个集群中只有一个节点运行调度进程,该节点对集群中的其他节点具有访问权限,可以对其他节点的资源信息、节点状态等进行统一管理,同时根据用户下发的任务对资源的需求,在调度器中进行任务与资源匹配,然后根据匹配结果将任务指派给合适的节点。
|
||||
|
||||
单体调度器拥有全局资源视图和全局任务,可以很容易地实现对任务的约束并实施全局性的调度策略。目前很多集群管理系统采用了单体调度设计,比如我们第9讲中提到的Google Borg、Kubernetes等。
|
||||
|
||||
如下图所示,图中展示了一个典型的单体调度框架。Master节点上运行了调度进程(负责资源管理、Tasks和资源匹配);Node 1,Node 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个CPU,200MB内存。根据任务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是采用什么技术实现的资源隔离吗?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
179
专栏/分布式技术原理与算法解析/12分布式调度架构之两层调度:物质文明、精神文明两手抓.md
Normal file
179
专栏/分布式技术原理与算法解析/12分布式调度架构之两层调度:物质文明、精神文明两手抓.md
Normal 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 Fairness,MMF)和主导资源公平算法(Dominant Resource Fairness,DRF)。
|
||||
|
||||
首先,我们看看最大最小公平算法。这是一种在兼顾公平的前提下,尽可能让更多人满意的资源分配算法。为什么这么说呢?因为这个算法有3个主要原则:
|
||||
|
||||
|
||||
按照用户对资源需求量递增的顺序进行空闲资源分配;
|
||||
不存在用户得到的资源超过自己需求的情况;
|
||||
对于分配的资源不满足需求的用户,所获得的资源是相等的。
|
||||
|
||||
|
||||
在执行资源分配时,最大最小公平算法按照上述3条原则进行多次迭代,每次迭代中资源均平均分配,如果还有剩余资源,就进入下一次迭代,一直到所有用户资源得到满足或集群资源分配完毕,迭代结束。
|
||||
|
||||
接下来,我们通过一个具体的例子来看看最大最小公平算法的资源分配流程吧。
|
||||
|
||||
假设,现在有总量为100的空闲资源,有4个用户A、B、C、D对该资源的需求量分别为(35,10,25,45),分配流程如下所示:
|
||||
|
||||
|
||||
按照用户对资源的需求量升序排列,则4个用户的需求量为(B:10,C:25,A:35,D:45)。
|
||||
平均分配空闲资源。资源空闲总量100,除以用户数4,则平均空闲资源量为25;按照第一步中需求量分配后,用户资源需求量为(0,0,10,20),且用户B由于资源需求量小于25,因此会剩余资源。此时空闲资源量为15,资源需求人数为2。
|
||||
重复第二步,平均分配资源,15/2=7.5,即分别为用户A和D分配7.5份资源,此时用户资源需求量为(0,0,2.5,12.5),空闲资源量为0,资源需求人数为2。
|
||||
所有资源已分配完,算法终止。
|
||||
|
||||
|
||||
最大最小公平算法的执行流程,如下图所示。
|
||||
|
||||
|
||||
|
||||
在这个案例中,最大最小公平算法是由于所有资源全部分配完才终止的。至此,对于需求量为(10,25,35,45)的用户们来说,分配到的资源是(10,25,32.5,32.5)。这个算法的另外一个结束条件是,资源分配满足了所有用户的资源需求,即当没有用户有资源需求时,算法也会终止。
|
||||
|
||||
接下来,我们再看看主导资源公平算法。
|
||||
|
||||
最大最小公平算法采用了绝对公平的方式分配资源,会导致大量的资源浪费,比如用户需求量为35和45的用户A和用户D,均分配了32.5的空闲资源,但由于资源不满足需求,这两个用户均无法使用。
|
||||
|
||||
而主导资源公平算法在考虑用户公平性的前提下,还考虑了用户对不同资源类型的需求,以尽可能地合理分配资源。也就是说,同样的资源量,主导资源公平算法可以尽可能地满足更多的用户。
|
||||
|
||||
在Mesos中,框架对资源的需求往往包括对CPU、内存等多种类型资源的需求。针对多种资源的需求,主导资源公平算法首先计算已经分配给用户的每一种资源的占用率(Resource Share),比如已经分配的CPU占总资源量的多少,已经分配的内存占总资源量的多少。所有资源占用率中的最大值称作该用户的主导资源占用率,而主导资源占用率对应的资源就是用户的主导资源。
|
||||
|
||||
我们通过一个具体的案例,看看如何判断用户的主导资源吧。如下图所示,假设系统中的资源共包括18个 CPU 和36 GB 内存,有两个Framework(Framework A和Framework B)分别运行了两种任务,假设Framework A运行内存密集型任务,Framework B运行CPU密集型任务,且每个任务所需要的资源量是一致的,分别是 和 。
|
||||
|
||||
|
||||
|
||||
第一步:计算资源分配量。
|
||||
|
||||
假设x和y分别是Framework A和Framework B分配的任务数,那么Framework A消耗的资源为{2x CPU,8x GB},Framework B消耗的资源数为{6y CPU,2y GB},分配给两个Framework的总资源量为(2x+6y)个CPU和(8x+2y)GB内存。
|
||||
|
||||
第二步:确定主导资源。
|
||||
|
||||
对于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双层调度机制容易导致资源碎片问题吗?原因又是什么呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
172
专栏/分布式技术原理与算法解析/13分布式调度架构之共享状态调度:物质文明、精神文明多手协商抓.md
Normal file
172
专栏/分布式技术原理与算法解析/13分布式调度架构之共享状态调度:物质文明、精神文明多手协商抓.md
Normal 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采用了传统数据库中的乐观锁(MVCC,Multi-Version Concurrency Control,基于多版本的并发访问控制),即每一个应用都发放了所有的可用资源,在更新集群状态时使用乐观并发控制来解决资源冲突问题,来提高Omega的并发度。
|
||||
|
||||
不同的Omega调度器可以实现不同的策略,但有一些调度规则是所有调度器必须达成一致的,比如哪些资源是允许分配的、如何评估作业的优先级等。
|
||||
|
||||
因此,Omega调度器将两层调度器中的集中式资源调度模块简化成了一些持久化的共享数据(状态)和针对这些数据的验证代码。而这里的“共享数据”,实际上就是整个集群的实时资源状态信息,而验证代码就是解决调度冲突的调度规则。
|
||||
|
||||
知识扩展:单体调度、两层调度和共享调度的区别是什么?
|
||||
|
||||
现在,我已经带你学习了单体调度、双层调度和共享调度,那么这三种调度的区别是什么呢?接下来,我们就一起回忆并对比下吧。
|
||||
|
||||
我把这三种调度的架构示意图放到一起,先帮你有一个整体认识。
|
||||
|
||||
|
||||
|
||||
单体调度,是由一个中央调度器去管理整个集群的资源信息和任务调度,也就是说所有任务只能通过中央调度器进行调度。
|
||||
|
||||
这种调度架构的优点是,中央调度器拥有整个集群的节点资源信息,可以实现全局最优调度。但它的缺点是,无调度并发性,且中央服务器存在单点瓶颈问题,导致支持的调度规模和服务类型受限,同时会限制集群的调度效率。因此,单体调度适用于小规模集群。
|
||||
|
||||
两层调度,是将资源管理和任务调度分为两层来调度。其中,第一层调度器负责集群资源管理,并将可用资源发送给第二层调度;第二层调度接收到第一层调度发送的资源,进行任务调度。
|
||||
|
||||
这种调度架构的优点是,避免了单体调度的单点瓶颈问题,可以支持更大的服务规模和更多的服务类型。但其缺点是,第二层调度器往往只对全局资源信息有部分可观察性,因此任务匹配算法无法实现全局最优。双层调度适用于中等规模集群。
|
||||
|
||||
共享状态调度,多个调度器,每个调度器都可以看到集群的全局资源信息,并根据这些信息进行任务调度。相较于其他两个调度架构来说,共享状态调度架构适用的集群规模最大。
|
||||
|
||||
这种调度架构的优点是,每个调度器都可以获取集群中的全局资源信息,因此任务匹配算法可以实现全局最优性。但,也因为每个调度器都可以在全局范围内进行任务匹配,所以多个调度器同时调度时,很可能会匹配到同一个节点,从而造成资源竞争和冲突。
|
||||
|
||||
虽然Omega的论文宣称可以通过乐观锁机制,避免冲突。但在工程实践中,如果没有妥善处理资源竞争的问题,则很可能会产生资源冲突,从而导致任务调度失败。这时,用户就需要对调度失败的任务进行处理,比如重新调度、任务调度状态维护等,从而进一步增加了任务调度操作的复杂度。
|
||||
|
||||
我将单体调度、两层调度、共享状态调度总结在了一张表格中:
|
||||
|
||||
|
||||
|
||||
总结
|
||||
|
||||
今天,我主要与你分享了分布式调度架构设计中的共享状态调度。我们一起来总结下今天的核心知识点吧。
|
||||
|
||||
首先,我讲述了什么是共享状态调度。概括地说,共享状态调度是将单体调度器分解为多个服务,由多个服务共享集群状态,包括资源状态和任务状态等。
|
||||
|
||||
接下来,我以Google的Omega集群管理系统为例,和你分享了共享状态调度的架构和工作原理。共享状态调度包含多个调度器,每个调度器都可以看到集群的全局资源信息,并根据这些信息进行任务调度。
|
||||
|
||||
最后,我要和你说明的是,共享状态调度是乐观并发调度,调度器将其调度的结果以原子的方式提交给资源维护模块,由其决定是否进行本次调度。
|
||||
|
||||
接下来,我整理一张思维导图来帮助你巩固今天的核心知识点。
|
||||
|
||||
|
||||
|
||||
我想让你知道的是,在分布式领域中,共享状态调度,是Google号称的下一代集群管理系统Omega的调度机制,可以解决双层调度无法实现全局最优的问题,同时也避免了单体调度的单点瓶颈问题。
|
||||
|
||||
但,说到这儿你可能会回想起曾经看到的两句话:
|
||||
|
||||
|
||||
为了达到设计目标,Omega 的实现逻辑变得越来越复杂。 在原有的 Borg 共享状态模型已经能满足绝大部分需要的情况下,Omega 的前景似乎没有那么乐观。
|
||||
Omega系统缺点是,在小集群下没有优势。
|
||||
|
||||
|
||||
这里,我再与你解释下,为什么说Omega是Google准备打造的下一代集群管理系统。
|
||||
|
||||
从调度架构方面来看,Borg无法支持同时存在多种业务类型的场景,并且存在单点瓶颈问题。而Omega解决了Borg的这两个问题,但是当多个调度器并行调度时,可能存在资源冲突,当资源申请产生冲突时,会导致大量任务或任务多次调度失败,增加了任务调度失败的故障处理的复杂度,比如需要进行作业回滚、任务状态维护等。
|
||||
|
||||
因此,设计一个好的冲突避免策略是共享状态调度的关键。对于小规模集群来说,其集群规模、任务数量等都不大,使用单体调度就可以满足其任务调度的需求,避免了考虑复杂的冲突避免策略。也就是说,共享状态调度比较适合大规模、同时存在多种业务类型的场景,不太适合小规模集群。
|
||||
|
||||
思考题
|
||||
|
||||
共享状态调度的核心是解决并发冲突,那你认为有没有什么好的方法可以解决冲突呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
138
专栏/分布式技术原理与算法解析/14答疑篇:分布式事务与分布式锁相关问题.md
Normal file
138
专栏/分布式技术原理与算法解析/14答疑篇:分布式事务与分布式锁相关问题.md
Normal 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消息后,才会真正进入提交阶段。
|
||||
|
||||
|
||||
这样说明后,相信你对这个问题的疑惑应该解决了吧。
|
||||
|
||||
现在,我们继续延展一下这个问题吧。
|
||||
|
||||
追问1:3PC在预提交阶段,才开始执行事务操作,那协调者发送CanCommit给参与者的时候,参与者根据什么返回Yes或者No消息呢?
|
||||
|
||||
3PC在投票阶段(CanCommit阶段),协调者发送CanCommit询问后,参与者会根据自身情况,比如自身空闲资源是否足以支撑事务、是否会存在故障等,预估自己是否可以执行事务,但不会执行事务,参与者根据预估结果给协调者返回Yes或者No消息。
|
||||
|
||||
追问2:3PC出现的目的是,解决2PC的同步阻塞和数据不一致性问题。那么,我们不可以在2PC中直接去解决这些问题吗?3PC多了预提交和超时机制,就真的解决这些问题了吗?
|
||||
|
||||
我们先来看看同步阻塞的问题。
|
||||
|
||||
在2PC中,参与者必须等待协调者发送的事务操作指令,才会执行事务,比如提交事务或回滚等操作,如果协调者故障,也就是说参与者无法收到协调者的指令了,那么参与者只能一直等待下去。这就好比在一个班级里面,班主任是协调者,学生是参与者,班主任告诉学生今天下午6点组织一个比赛,但班主任今天生病了,根本到不了学校,并且也无法发送信息告诉学生,那么学生们就只能一直等待。
|
||||
|
||||
3PC在协调者和参与者中都引入了超时机制(2PC只是在协调者引入了超时),也就是说当参与者在一定时间内,没有接收到协调者的通知时,会执行默认的操作,从而减少了整个集群的阻塞时间。这就好比班主任生病了,学生默认等待半个小时,如果班主任还没有任何通知,那么默认比赛取消,学生可以自由安排,做自己的事情去了。
|
||||
|
||||
但其实,阻塞在实际业务中是不可能完全避免的。在上面的例子中,学生等待超时的半个小时中,其实还是阻塞的,只是阻塞的时间缩短了。所以,相对于2PC来说,3PC只是在一定程度上减少(或者说减弱)了阻塞问题。
|
||||
|
||||
接下来,我们再看看数据不一致的问题吧。
|
||||
|
||||
通过上面的分析可以看到,同步阻塞的根本原因是协调者发生故障,想象一下,比如现在有10个参与者,协调者在发送事务操作信息的时候,假设在发送给了5个参与者之后发生了故障。在这种情况下,未收到信息的5个参与者会发生阻塞,收到信息的5个参与者会执行事务,以至于这10个参与者的数据信息不一致。
|
||||
|
||||
3PC中引入了预提交阶段,相对于2PC来讲是增加了一个预判断,如果在预判断阶段协调者出现故障,那就不会执行事务。这样,可以在一定程度上减少故障导致的数据不一致问题,尽可能保证在最后提交阶段之前,各参与节点的状态是一致的。
|
||||
|
||||
所以说,3PC是研究者们针对2PC中存在的问题做的一个改进,虽然没能完全解决这些问题,但也起到了一定的效果。
|
||||
|
||||
在实际使用中,通常采用多数投票策略来代替第一阶段的全票策略,类似采用Raft算法选主的多数投票策略,即获取过半参与者的投票数即可。关于Raft算法的选主的具体原理,你可以再回顾下第4篇文章“分布式选举:国不可一日无君”中的相关内容。
|
||||
|
||||
追问3:3PC也是只有一个协调者,为什么就不会有单点故障问题了?
|
||||
|
||||
首先,我先明确下这里所说的单点故障问题。
|
||||
|
||||
因为系统中只有一个协调者,那么协调者所在服务器出现故障时,系统肯定是无法正常运行的。所以说,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篇文章留言涉及的问题,进行了归纳总结,从中摘取了分布式事务和分布式锁这两个知识点,串成了今天这篇答疑文章。
|
||||
|
||||
今天没来得及和你扩展的问题,后续我会再找机会进行解答。最后,我要和你说的是,和我一起打卡分布式核心技术,一起遇见更优秀的自己吧。
|
||||
|
||||
篇幅所限,留言区见。
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
172
专栏/分布式技术原理与算法解析/15分布式计算模式之MR:一门同流合污的艺术.md
Normal file
172
专栏/分布式技术原理与算法解析/15分布式计算模式之MR:一门同流合污的艺术.md
Normal 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操作流程。其中图中的“step1,step2,…,step6”表示操作步骤。
|
||||
|
||||
step1:User Program将任务下发到MRAppMaster中。然后,MRAppMaster执行任务拆分步骤,把User Program下发的任务划分成M个子任务(M是用户自定义的数值)。假设,MapReduce函数将任务划分成了5个,其中Map作业有3个,Reduce作业有2个;集群内的MRAppMaster以及Worker节点都有任务的副本。
|
||||
|
||||
step2:MRAppMaster分别为Mapper和Reducer分配相应的Map和Reduce作业。Map作业的数量就是划分后的子任务数量,也就是3个;Reduce作业是2个。
|
||||
|
||||
step3:被分配了Map作业的Worker,开始读取子任务的输入数据,并从输入数据中抽取出键值对,每一个键值对都作为参数传递给map()函数。
|
||||
|
||||
step4:map()函数的输出结果存储在环形缓冲区kvBuffer中,这些Map结果会被定期写入本地磁盘中,被存储在R个不同的磁盘区。这里的R表示Reduce作业的数量,也是由用户定义的。在这个案例中,R=2。此外,每个Map结果的存储位置都会上报给MRAppMaster。
|
||||
|
||||
step5:MRAppMaster通知Reducer它负责的作业在哪一个分区,Reducer远程读取相应的Map结果,即中间键值对。当Reducer把它负责的所有中间键值对都读过来后,首先根据键值对的key值对中间键值对进行排序,将相同key值的键值对聚集在一起,从而有利于Reducer对Map结果进行统计。
|
||||
|
||||
step6:Reducer遍历排序后的中间键值对,将具有相同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属于批量处理任务类型吗?你能说说其中的原因吗?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
151
专栏/分布式技术原理与算法解析/16分布式计算模式之Stream:一门背锅的艺术.md
Normal file
151
专栏/分布式技术原理与算法解析/16分布式计算模式之Stream:一门背锅的艺术.md
Normal 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构建有向无环图代表流计算逻辑,以实现流计算,以加深你对流计算原理的理解。
|
||||
|
||||
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
|
||||
|
||||
|
||||
|
||||
思考题
|
||||
|
||||
离线计算和批量计算,实时计算和流式计算是等价的吗?你能和我说说你做出判断的原因吗?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
148
专栏/分布式技术原理与算法解析/17分布式计算模式之Actor:一门甩锅的艺术.md
Normal file
148
专栏/分布式技术原理与算法解析/17分布式计算模式之Actor:一门甩锅的艺术.md
Normal 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 Programming,OOP)类似,一个对象接收到一个方法的调用请求(类似于一个消息),从而去执行该方法。
|
||||
|
||||
但是,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是否可以采用阻塞方式去运行呢,原因是什么呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
167
专栏/分布式技术原理与算法解析/18分布式计算模式之流水线:你方唱罢我登场.md
Normal file
167
专栏/分布式技术原理与算法解析/18分布式计算模式之流水线:你方唱罢我登场.md
Normal 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进行小狗分类模型训练为例,通过讲述数据输入、数据处理、特征提取(卷积、池化等操作)、模型训练、模型验证等过程,带你进一步理解了流水线计算模式在实际应用中的原理。
|
||||
|
||||
现在,我再通过一张思维导图来归纳一下今天的核心知识点吧。
|
||||
|
||||
|
||||
|
||||
流水线计算模式适合同类型任务,且每个任务可以拆分为多个步骤(子任务)进行执行的场景,通过重叠执行多个不同任务间的不同步骤实现数据并行。在实际应用场景中,有很多例子,最常见的就是机器学习。相信你在理解了本文的计算原理之后,一定可以将这种研究方法运用在你的工作中,加油!
|
||||
|
||||
思考题
|
||||
|
||||
流水线计算模式和流计算的区别是什么?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
229
专栏/分布式技术原理与算法解析/19分布式通信之远程调用:我是你的千里眼.md
Normal file
229
专栏/分布式技术原理与算法解析/19分布式通信之远程调用:我是你的千里眼.md
Normal file
@ -0,0 +1,229 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
19 分布式通信之远程调用:我是你的千里眼
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
在前面三个模块中,我带你学习了分布式领域中的分布式协调与同步、分布式资源管理与负载调度,以及分布式计算技术,相信你对分布式技术已经有了一定的了解。
|
||||
|
||||
通过前面的学习,不知道你有没有发现分布式的本质就是多进程协作,共同完成任务。要协作,自然免不了通信。那么,多个进程之间是如何通信的呢?这也就是在“第四站:分布式通信技术”模块中,我将要为你讲解的问题。
|
||||
|
||||
话不多说,接下来我们就一起进入分布式通信的世界吧。今天,我首先带你打卡的是,分布式通信中的远程调用。
|
||||
|
||||
什么是远程调用?
|
||||
|
||||
首先,我通过一个例子,来让你对远程调用和本地调用有一个直观了解。
|
||||
|
||||
以电商购物平台为例,每一笔交易都涉及订单系统、支付系统和库存系统,假设三个系统分别部署在三台机器A、B、C中独立运行,订单交易流程如下所示:
|
||||
|
||||
|
||||
用户下单时,调用本地(机器A)的订单系统进行下单;
|
||||
下单完成后,会远程调用机器B上的支付系统进行支付,待支付完成后返回结果,之后在本地更新订单状态;
|
||||
在本地远程调用机器C上的仓库系统出货,出货完成后返回出货结果。
|
||||
|
||||
|
||||
在整个过程中,“下单”和“订单状态更新”两个操作属于本地调用,而“支付”和“出货”这两个操作是通过本地的订单系统调用其他两个机器上的函数(方法)实现的,属于远程调用。
|
||||
|
||||
整个订单交易流程如下图所示。
|
||||
|
||||
|
||||
|
||||
通过这个例子,你应该对本地调用和远程调用有了一个初步的认识了。那到底什么是本地调用,什么是远程调用呢?
|
||||
|
||||
本地调用通常指的是,进程内函数之间的相互调用;而远程调用,是进程间函数的相互调用,是一种进程间通信模式。通过远程调用,一个进程可以看到其他进程的函数、方法等,这是不是与我们通常所说的“千里眼”有点类似呢?
|
||||
|
||||
在分布式领域中,一个系统由很多服务组成,不同的服务由各自的进程单独负责。因此,远程调用在分布式通信中尤为重要。
|
||||
|
||||
根据进程是否部署在同一台机器上,远程调用可以分为如下两类:
|
||||
|
||||
|
||||
本地过程调用(Local Procedure Call,LPC),是指同一台机器上运行的不同进程之间的互相通信,即在多进程操作系统中,运行的不同进程之间可以通过LPC进行函数调用。
|
||||
远程过程调用(Remote Procedure Call,RPC),是指不同机器上运行的进程之间的相互通信,某一机器上运行的进程在不知道底层通信细节的情况下,就像访问本地服务一样,去调用远程机器上的服务。
|
||||
|
||||
|
||||
在这两种远程调用中,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,其中结果可使用XDR(External 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框架的设计者,你会如何设计呢?
|
||||
|
||||
首先必须得有服务的提供方和调用方。如下图所示,假设服务提供方1~4为调用方1~4提供服务,每个调用方都可以任意访问服务提供方。
|
||||
|
||||
|
||||
|
||||
当服务提供方和服务调用方越来越多时,服务调用关系会愈加复杂。假设服务提供方有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的典型实现框架有EJB(Enterprise 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中引入了一个注册中心来存储服务提供方的地址列表,若服务消费方每次调用时都去注册中心查询地址列表,如果频繁查询,会导致效率比较低,你会如何解决这个问题呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
180
专栏/分布式技术原理与算法解析/20分布式通信之发布订阅:送货上门.md
Normal file
180
专栏/分布式技术原理与算法解析/20分布式通信之发布订阅:送货上门.md
Normal file
@ -0,0 +1,180 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
20 分布式通信之发布订阅:送货上门
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
在上一篇文章中,我带你一起学习了分布式通信中的远程调用。远程调用的核心是在网络服务层封装了通信协议、序列化、传输等操作,让用户调用远程服务如同进行本地调用一样。
|
||||
|
||||
其实,这种方式就是通过网络服务层的封装实现了不同机器上不同进程之间的直接通信,因为是直接通信,所以通过线程阻塞的方式实现同步调用比较容易,因此通常被用于同步调用。比如,机器1上的进程A调用机器2上的进程B,进程A被挂起,进程B开始执行,当进程B将值返回给A时,A继续执行。
|
||||
|
||||
虽然这种方式也可以用于异步通信,但因为进程之间是直接交互的,所以当进程比较多时,会导致进程维护通信的复杂度非常高,且一个进程通信接口改变,与其通信的进程都会受到影响。
|
||||
|
||||
随着业务和分布式计算规模的逐渐增大和复杂化,远程调用模型有点心有余力而不足了,为此出现了专门的异步通信模式,也就是消息发布订阅模式和消息队列模式。在接下来的两篇文章中,我将与你详细讲述这两种通信模式。
|
||||
|
||||
话不多说,今天,我就带你一起打卡分布式通信中的发布订阅模式吧。
|
||||
|
||||
什么是发布订阅?
|
||||
|
||||
其实,发布订阅的思想在我们的生活中随处可见。
|
||||
|
||||
比如,学术届电子论文的订阅方式。通常,各个会议方或出版社会将学术论文发布到论文网站(或平台上,比如ACM、知网等),然后学生或老师向论文网站订阅自己感兴趣的论文,比如分布式相关的、AI相关的等。
|
||||
|
||||
当会议方或出版社将论文发布到论文网站后,论文网站会根据订阅信息,将相应的论文推送给订阅者(比如通过邮件的方式)。这里的会议方或出版社就相当于生产者,负责发布论文,学生或老师就相当于消费者,而论文网站就相当于一个消息中心。
|
||||
|
||||
|
||||
|
||||
由此可以看出,发布订阅的三要素是生产者、消费者和消息中心,生产者负责产生数据放到消息中心,消费者向消息中心订阅自己感兴趣的消息,当发布者推送数据到消息中心后,消息中心根据消费者订阅情况将相关数据推送给对应的订阅者。这种将数据送到消费者手里的行为,是不是和我们现在常说的“送货上门”一样呢?
|
||||
|
||||
发布订阅的原理及应用
|
||||
|
||||
这个论文订阅的例子,充分体现了发布订阅的思想。接下来,我就与你进一步分析下发布订阅的原理吧。
|
||||
|
||||
发布订阅的基本工作原理
|
||||
|
||||
在分布式通信领域中,消息系统一般有两种典型的模式。一种是点对点模式(P2P,Point to Point),另一种是发布订阅模式(Pub/Sub,Publish/Subscribe)。接下来,我们就一起看看这两种模式,以帮助你深入理解发布订阅模式的原理。
|
||||
|
||||
首先,我们一起看一下什么是点对点模式。
|
||||
|
||||
生产者将消息发送到消息中心,然后消费者从消息中心取出对应的消息进行消费。消息被消费后,消息中心不再存储该消息,因此其他消费者无法再消费该消息。也就是说,点对点模式虽然支持多个消费者,但一个消息只能被一个消费者消费,不允许重复消费。
|
||||
|
||||
这种模式就好比,限定了每篇论文只能被一个用户消费,比如现在有一篇分布式相关的论文,这篇论文推送给学生A之后,论文网站就必须将其删除或下架,也就是说其他用户无法再获取或阅读该论文了。(当然实际情况并不是这样的,这里只是为了方便你理解,我做了相应的假设。)
|
||||
|
||||
|
||||
|
||||
接下来,我们看一下发布订阅模式。
|
||||
|
||||
生产者可以发送消息到消息中心,而消息中心通常以主题(Topic)进行划分,每条消息都会有相应的主题,消息会被存储到自己所属的主题中,订阅该主题的所有消费者均可获得该消息进行消费。
|
||||
|
||||
|
||||
|
||||
比如图中假设生产者1发布一个Topic相关数据或消息,消费者1~3均订阅了该Topic消息,则该消息会推送消费者1~3,也就是说同一个消息被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看同一部电视剧,当然这个电视剧可以是被不同的生产者发布。点对点模式由于其局限性,一般适用于需要进行点对点通信的场景,比如近场投屏等。
|
||||
|
||||
相信你通过本讲的学习后,可以针对不同的分布式场景选择合适的通信模式,加油!
|
||||
|
||||
思考题
|
||||
|
||||
发布订阅模式下,当发布者消息量很大时,单个订阅者的处理能力是有限的,那么能否实现订阅者负载均衡消费呢?又该如何实现呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
168
专栏/分布式技术原理与算法解析/21分布式通信之消息队列:货物自取.md
Normal file
168
专栏/分布式技术原理与算法解析/21分布式通信之消息队列:货物自取.md
Normal 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 Slave,Master 既可以写又可以读,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为例对其架构、核心组件和工作原理做了更深入的讲解,以帮助你进一步了解消息队列模型。
|
||||
|
||||
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
|
||||
|
||||
|
||||
|
||||
加油,行动起来,试着将发布订阅和消息队列这两种通信模式用到你的业务场景中吧,相信你可以的。如果你需要进一步了解这两种通信模式对应的产品源码的话,相信你结合这两篇文章中讲述的原理,可以比较容易地开启你的源码之旅了。
|
||||
|
||||
思考题
|
||||
|
||||
消息队列模型中,消费者是主动去消息队列获取消息的,而消息队列需要保证多个消费者可以获取到消息,也就是说一个消费者获取消息后并不会删除该消息,那么如何保证同一个消息不被同一个消费者重复消费呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
127
专栏/分布式技术原理与算法解析/22答疑篇:分布式体系架构与分布式计算相关问题.md
Normal file
127
专栏/分布式技术原理与算法解析/22答疑篇:分布式体系架构与分布式计算相关问题.md
Normal 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),消息量也特别大,甚至会导致网络风暴,应该怎么办。
|
||||
|
||||
其实,与集中式架构中的心跳包不同,非集中式架构中采用的心跳方式的核心思想是,每个节点被b(1≤b)个节点监控,以减少心跳信息量。
|
||||
|
||||
接下来,我们以Akka的原理为例,先来看看b的取值原则吧。
|
||||
|
||||
|
||||
如果用户不设置b的值,那么b默认取值的原则是:若集群中节点总数n小于6,b=n-1;若n大于等于6,b=5。
|
||||
若用户设置b的值,则b以用户设置的值为准。
|
||||
|
||||
|
||||
接下来,我们再看看Akka集群中具体是如何通过心跳方式判断节点是否存活的。
|
||||
|
||||
|
||||
Akka中集群组建完成后,每个节点拥有整个集群中的节点列表。
|
||||
每个节点根据集群节点列表,计算哈希值(比如根据节点ID计算一个哈希值),然后基于哈希值,将所有节点组成一个哈希环(比如,从小到大的顺序),如下图所示。由于每个节点上的计算方法一致,因此虽然每个节点独立计算,但每个节点上维护的哈希环是一致的。
|
||||
根据哈希环,针对每个节点逆时针或顺时针方向选择b(图中设置b=2)个临近节点作为监控节点,比如图中Node 2和 Node3监控Node1,Node 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篇文章中,大家针对思考题的讨论和困惑,筛选出了分布式系统架构中如何判断节点是否存活,以及四种分布式计算模式的异同,做了进一步展开,梳理成了今天的这篇答疑文章。
|
||||
|
||||
如果还有哪些思考题或者留言问题,还没来得及扩展的话,你可以留言给我,后续我会再找机会进行解答。最后,我要和你说的是,和我一起打卡分布式核心技术,一起遇见更优秀的自己吧。
|
||||
|
||||
篇幅所限,留言区见。
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
210
专栏/分布式技术原理与算法解析/23CAP理论:这顶帽子我不想要.md
Normal file
210
专栏/分布式技术原理与算法解析/23CAP理论:这顶帽子我不想要.md
Normal 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,牺牲可用性A:Server2选择让User2的请求阻塞,一直等到网络恢复正常,Server1被修改的数据同步更新到Server2之后,即DB2中数据a修改成最新值2后,再给用户User2响应。
|
||||
|
||||
|
||||
|
||||
第二种处理方式是,保证可用性A,牺牲一致性C:Server2选择将旧的数据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的数据会同步到C,C更新数据为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理论的区别是什么?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
165
专栏/分布式技术原理与算法解析/24分布式数据存储系统之三要素:顾客、导购与货架.md
Normal file
165
专栏/分布式技术原理与算法解析/24分布式数据存储系统之三要素:顾客、导购与货架.md
Normal file
@ -0,0 +1,165 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
24 分布式数据存储系统之三要素:顾客、导购与货架
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
在上一篇文章中,我们一起学习了CAP理论。该理论指出,在分布式系统中,不能同时满足一致性、可用性和分区容错性,指导了分布式数据存储系统的设计。
|
||||
|
||||
随着数据量和访问量的增加,单机性能已经不能满足用户需求,分布式集群存储成为一种常用方式。把数据分布在多台存储节点上,可以为大规模应用提供大容量、高性能、高可用、高扩展的存储服务。而,分布式存储系统就是其具体实现。
|
||||
|
||||
在今天这篇文章,我将带你学习分布式存储系统的关键三要素,让你对分布式数据存储系统有一个直观的理解。在后面几篇文章中,我会针对这三要素中的关键技术进一步展开,以帮助你更深入地理解分布式数据存储系统。
|
||||
|
||||
接下来,我们就一起打卡分布式存储系统的三要素:顾客、导购与货架。
|
||||
|
||||
什么是分布式数据存储系统?
|
||||
|
||||
分布式存储系统的核心逻辑,就是将用户需要存储的数据根据某种规则存储到不同的机器上,当用户想要获取指定数据时,再按照规则到存储数据的机器里获取。
|
||||
|
||||
如下图所示,当用户(即应用程序)想要访问数据D,分布式操作引擎通过一些映射方式,比如Hash、一致性Hash、数据范围分类等,将用户引导至数据D所属的存储节点获取数据。
|
||||
|
||||
|
||||
|
||||
静下心来想一下,获取数据的整个过程与你到商店购物的过程是不是有些类似呢?
|
||||
|
||||
顾客到商店购物时,导购会根据顾客想要购买的商品引导顾客到相应的货架,然后顾客从这个货架上获取要购买的商品,完成购物。这里的顾客就是图中的应用程序,导购就相当于分布式操作引擎,它会按照一定的规则找到相应的货架,货架就是存储数据的不同机器节点。
|
||||
|
||||
其实,这个过程就是分布式存储系统中获取数据的通用流程,顾客、导购和货架组成了分布式存储系统的三要素,分别对应着分布式领域中的数据生产者/消费者、数据索引和数据存储。
|
||||
|
||||
接下来,我们就详细看看这三个要素吧。
|
||||
|
||||
分布式数据存储系统三要素
|
||||
|
||||
顾客就是数据的生产者和消费者,也就是说顾客代表两类角色,生产者会生产数据(比如,商店购物例子中的供货商就属于生产类顾客),将数据存储到分布式数据存储系统中,消费者是从分布式数据存储系统中获取数据进行消费(比如,商店购物例子中购买商品的用户就属于消费类顾客);导购就是数据索引,将访问数据的请求转发到数据所在的存储节点;货架就是存储设备,用于存储数据。
|
||||
|
||||
顾客:生产和消费数据
|
||||
|
||||
顾客相当于分布式存储系统中的应用程序,而数据是应用程序的原动力。根据数据的产生和使用,顾客分为生产者和消费者两种类型。生产者负责给存储系统添加数据,而消费者则可以使用系统中存储的数据。
|
||||
|
||||
就像是火车票存储系统,如图所示,铁路局就相当于生产者类型的顾客,而乘客就相当于消费者类型的顾客。铁路局将各个线路的火车票信息发布到订票网站的后台数据库中,乘客通过订票网站访问数据库,来进行查询余票、订票、退票等操作。
|
||||
|
||||
|
||||
|
||||
生产者和消费者生产和消费的数据通常是多种多样的,不同应用场景中数据的类型、格式等都不一样。根据数据的特征,这些不同的数据通常被划分为三类:结构化数据、半结构化数据和非结构化数据。
|
||||
|
||||
|
||||
结构化数据通常是指关系模型数据,其特征是数据关联较大、格式固定。火车票信息比如起点站、终点站、车次、票价等,就是一种结构化数据。结构化数据具有格式固定的特征,因此一般采用分布式关系数据库进行存储和查询。
|
||||
半结构化数据通常是指非关系模型的,有基本固定结构模式的数据,其特征是数据之间关系比较简单。比如HTML文档,使用标签书写内容。半结构化数据大多可以采用键值对形式来表示,比如HTML文档可以将标签设置为key,标签对应的内容可以设置为value,因此一般采用分布式键值系统进行存储和使用。
|
||||
非结构化数据是指没有固定模式的数据,其特征是数据之间关联不大。比如文本数据就是一种非结构化数据。这种数据可以存储到文档中,通过ElasticSearch(一个分布式全文搜索引擎)等进行检索。
|
||||
|
||||
|
||||
导购:确定数据位置
|
||||
|
||||
导购是分布式存储系统必不可少的要素,如果没有导购, 顾客就需要逐个货架去寻找自己想要的商品。
|
||||
|
||||
想象一下,如果你去订票网站订火车票,按照自己的需求点击查询车票后,系统会逐个扫描分布式存储系统中每台机器的数据,寻找你想要购买的火车票。如果系统中存储的数据不多,响应时间也不会太长,毕竟计算机的速度还是很快的;但如果数据分布在几千台甚至上万台机器中,系统逐个机器扫描后再给你响应,我相信你会对这个订票网站很失望。
|
||||
|
||||
这种定位数据存储位置的方式会浪费你很多时间,严重影响购票体验。因此,在分布式存储系统中,必须有相应的数据导购,否则系统响应会很慢,效率很低。为解决这个问题,数据分片技术就走入了分布式存储系统的大家庭。
|
||||
|
||||
数据分片技术,是指分布式存储系统按照一定的规则将数据存储到相对应的存储节点中,或者到相对应的存储节点中获取想要的数据,这是一种很常用的导购技术。这种技术,一方面可以降低单个存储节点的存储和访问压力;另一方面,可以通过规定好的规则快速找到数据所在的存储节点,从而大大降低搜索延迟,提高用户体验。
|
||||
|
||||
也就是说,当铁路局发布各个线路的火车票信息时,会按照一定规则存储到相应的机器中,比如北京到上海的火车票存储到机器A中,西安到重庆的火车票存储到机器B中。当乘客查询火车票时,系统就可以根据查询条件迅速定位到相对应的存储机器,然后将数据返回给用户,响应时间就大大缩短了。如图所示,当查询北京-上海的火车票相关信息时,可以与机器A进行数据交互。
|
||||
|
||||
|
||||
|
||||
这个例子中按照数据起点、终点的方式划分数据,将数据分为几部分存储到不同的机器节点中,就是数据分片技术的一种。当查询数据时,系统可以根据查询条件迅速找到对应的存储节点,从而实现快速响应。
|
||||
|
||||
上述的例子中,按照数据特征进行了数据分片,当然,还有其他很多数据分片的方案。比如,按照数据范围,采用哈希映射、一致性哈希环等对数据划分。我会在下一篇文章中,与你详细讲述哈希和一致性哈希的内容。
|
||||
|
||||
接下来,我就针对数据范围这种数据分片方案做一个具体介绍吧。
|
||||
|
||||
针对数据范围的数据分片方案是指,按照某种规则划分数据范围,然后将在这个范围内的数据归属到一个集合中。这就好比数学中通常讲的整数区间,比如1~1000的整数,[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篇文章中进行详细介绍。
|
||||
|
||||
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
|
||||
|
||||
|
||||
|
||||
相信通过今天的学习,你对分布式数据存储有了更深入的理解,对其中的核心角色和关键技术也有个更清晰的认识。加油,和我一起学习后面的章节,一起揭开分布式数据存储系统的神秘面纱吧!
|
||||
|
||||
思考题
|
||||
|
||||
传统单机关系型数据库与分布式数据库的区别是什么?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
201
专栏/分布式技术原理与算法解析/25数据分布方式之哈希与一致性哈希:“掐指一算”与“掐指两算”的事.md
Normal file
201
专栏/分布式技术原理与算法解析/25数据分布方式之哈希与一致性哈希:“掐指一算”与“掐指两算”的事.md
Normal 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将存储数据D2(300%3=0)和D5(600%3=0),Node2将存储数据D0(100%3=1)、D3(400%3=1)和D6(700%3=1),Node3将存储数据D1(200%3=2)和D4(500%3=2)。
|
||||
|
||||
|
||||
|
||||
可以看出,哈希算法的一个优点是,只要哈希函数设置得当,可以很好地保证数据均匀性,但有一个较为严重的缺点,就是稳定性较差。
|
||||
|
||||
比如,随着数据量的增加,三个节点的容量无法再满足存储需求了,需要再添加一个节点。这时,哈希函数变成了 id%4,原先存储在那三个节点的数据需要重新计算,然后存入相应节点,即需要大规模的数据迁移,显然会降低稳定性。
|
||||
|
||||
所以,哈希方法适用于同类型节点且节点数量比较固定的场景。目前,Redis就使用了哈希方法,你可以再回顾下第10篇文章“分布式体系结构之非集中式结构:众生平等”中的相关内容。
|
||||
|
||||
接下来,我们再看看一致性哈希吧。
|
||||
|
||||
一致性哈希
|
||||
|
||||
一致性哈希是指将存储节点和数据都映射到一个首尾相连的哈希环上,存储节点可以根据IP地址进行哈希,数据通常通过顺时针方向寻找的方式,来确定自己所属的存储节点,即从数据映射在环上的位置开始,顺时针方向找到的第一个存储节点。
|
||||
|
||||
我们看看如何用一致性哈希方法,来实现上述案例的数据存储吧。
|
||||
|
||||
如图所示,假设数据D0~D7按照ID进行等值映射,即映射值与ID值相等,比如数据D0映射到哈希环上的值为100,数据D1映射到哈希环上的值为200······;同时,假设存储节点Node1、Node2和Node3映射到哈希环上的值分别为400、600、900。
|
||||
|
||||
按照规则,D0,D1,D2和D3顺时针方向的下一个存储节点为Node1,因此Node1将存储数据D0(id = 100)、D1(id = 200)、D2(id = 300)和D3(id = 400);同理,Node2将存取数据D4(id = 500)和D5(id = 600),Node3将存取数据D6(id = 700)。
|
||||
|
||||
|
||||
|
||||
可以看出,一致性哈希是对哈希方法的改进,在数据存储时采用哈希方式确定存储位置的基础上,又增加了一层哈希,也就是在数据存储前,对存储节点预先进行了哈希。
|
||||
|
||||
这种改进可以很好地解决哈希方法存在的稳定性问题。当节点加入或退出时,仅影响该节点在哈希环上顺时针相邻的后继节点。比如,当Node2发生故障需要移除时,由于Node3是Node2顺时针方向的后继节点,本应存储到Node2的数据就会存储到Node3中,其他节点不会受到影响,因此不会发生大规模的数据迁移。
|
||||
|
||||
所以,一致性哈希方法比较适合同类型节点、节点规模会发生变化的场景。目前,Cassandra就使用了一致性哈希方法,你可以再回顾下第10篇文章“分布式体系结构之非集中式结构:众生平等”中的相关内容。
|
||||
|
||||
一致性哈希方法虽然提升了稳定性,但随之而来的均匀性问题也比较明显,即对后继节点的负载会变大。有节点退出后,该节点的后继节点需要承担该节点的所有负载,如果后继节点承受不住,便会出现节点故障,导致后继节点的后继节点也面临同样的问题。
|
||||
|
||||
那么,有没有更好的方法来解决这个问题呢?
|
||||
|
||||
Google在2017年提出了带有限负载的一致性哈希算法,就对这个问题做了一些优化。
|
||||
|
||||
带有限负载的一致性哈希
|
||||
|
||||
带有限负载的一致性哈希方法的核心原理是,给每个存储节点设置了一个存储上限值来控制存储节点添加或移除造成的数据不均匀。当数据按照一致性哈希算法找到相应的存储节点时,要先判断该存储节点是否达到了存储上限;如果已经达到了上限,则需要继续寻找该存储节点顺时针方向之后的节点进行存储。
|
||||
|
||||
我们看看如何用带有限负载的一致性哈希方法,来实现上述案例的数据存储吧。
|
||||
|
||||
如图所示,假设每个存储节点设置的上限值为3,按照一致性哈希算法,当存储数据D3(id = 400)时,会发现应该存储到Node1中,但Node1已经存储了三个数据D0(id = 100)、D1(id = 200)和D2(id = 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_1,Node2对应2个虚拟节点Node2_1和Node2_2,Node3对应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将会存储数据D0(id = 100)和D6(id = 700);同理,Node2将会存储数据D1(id = 200)和D2(id = 300),Node3将会存储数据D3(id = 400)、D4(id = 500)和D5(id = 600)。
|
||||
|
||||
|
||||
|
||||
可以看出,带虚拟节点的一致性哈希方法比较适合异构节点、节点规模会发生变化的场景。目前Memcached缓存系统实现了该方法,我会在第27篇文章中与你详细分析。
|
||||
|
||||
这种方法不仅解决了节点异构性问题,还提高了系统的稳定性。当节点变化时,会有多个节点共同分担系统的变化,因此稳定性更高。
|
||||
|
||||
比如,当某个节点被移除时,对应该节点的多个虚拟节点均会移除,而这些虚拟节点按顺时针方向的下一个虚拟节点,可能会对应不同的物理节点,即这些不同的物理节点共同分担了节点变化导致的压力。
|
||||
|
||||
当然,这种方法引入了虚拟节点,增加了节点规模,从而增加了节点的维护和管理的复杂度,比如新增一个节点或一个节点故障时,对应到虚拟节点构建的哈希环上需要新增和删除多个节点,数据的迁移等操作相应地也会很复杂。
|
||||
|
||||
四种数据分布方法对比
|
||||
|
||||
为方便理解与记忆,我再通过一个表格和你对比分析下这四种方法吧。请注意,以下方法之间的对比都是相对的比较,实际性能优劣与哈希函数的设定以及具体的数据场景密切相关。
|
||||
|
||||
|
||||
|
||||
知识扩展:数据分片和数据分区,有何区别?
|
||||
|
||||
首先,我们一起回忆一下第20篇文章中提到的数据分区。
|
||||
|
||||
数据分区是从数据存储块的维度进行划分,不同的分区物理上归属于不同的节点。比如,现在有2个节点Node1和Node2,2个数据分区Partition1和Partition2,Partition1属于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的Partition2,id%3=2的所有用户信息存储到节点3的Partition3。
|
||||
|
||||
总结
|
||||
|
||||
今天,我主要带你学习了数据分布式方法中的哈希与一致性哈希。
|
||||
|
||||
首先,我带你了解了分布式数据存储系统中,设计数据分布方法需要考虑的原则,主要包括数据均匀性、稳定性和节点异构性。
|
||||
|
||||
其次,基于数据分布设计原则,我为你介绍了哈希、一致性哈希、带有限负载的一致性哈希和带虚拟节点的一致性哈希方法,并以例子进行辅助讲解,以便于你理解和学习。
|
||||
|
||||
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
|
||||
|
||||
|
||||
|
||||
加油,相信你通过对本讲的学习,对分布式数据存储系统中“导购”的角色有了更深入的理解,对分布式数据分布方法的原理,以及如何针对不同场景进行选型有了一定的认知。加油,赶紧行动起来,为你的应用场景选择一种合适的数据分布方法,并实践起来吧!
|
||||
|
||||
思考题
|
||||
|
||||
今天,我主要与你分享的是哈希方法相关的数据分布方法,你还知道哪些常用的数据分布方法吗?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
158
专栏/分布式技术原理与算法解析/26分布式数据复制技术:分身有术.md
Normal file
158
专栏/分布式技术原理与算法解析/26分布式数据复制技术:分身有术.md
Normal 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相等位置,即找到了两者最后达成一致的地方。
|
||||
|
||||
比如,对于变量X,Leader节点记录的操作是{(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等开源软件采用的复制技术的原理了。
|
||||
|
||||
思考题
|
||||
|
||||
本讲主要是从应用或分布式数据系统的角度介绍了三种比较基本的数据复制技术,你还知道一些其他的基于本讲介绍的复制技术的演进或变种的复制方法吗?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
183
专栏/分布式技术原理与算法解析/27分布式数据之缓存技术:“身手钥钱”随身带.md
Normal file
183
专栏/分布式技术原理与算法解析/27分布式数据之缓存技术:“身手钥钱”随身带.md
Normal 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数据库,为了方便支持多应用的缓存,比如缓存文本类型、数据库的查询结果(字段与字段对应的值)等等,支持的数据结构不仅有简单的k/v类型,还可以支持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。
|
||||
|
||||
RDB(Redis DataBase),也称快照方式,简单来说就是Redis会定时将内存中的数据备份到磁盘中,形成一个快照,比如每天保存一下过去一周的数据。这样当节点出现故障时,可以根据快照恢复到不同版本的数据。这种方式有一个明显的缺点,是会造成数据丢失,即当节点出现故障时,新数据可能还未备份到磁盘中。
|
||||
|
||||
AOF(Append 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仅支持简单的k/v数据类型,如果想要存储复杂的数据类型,比如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分布式数据缓存系统,你还知道哪些主流的分布式数据缓存系统呢?它们的缓存核心技术是什么呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
196
专栏/分布式技术原理与算法解析/28分布式高可靠之负载均衡:不患寡,而患不均.md
Normal file
196
专栏/分布式技术原理与算法解析/28分布式高可靠之负载均衡:不患寡,而患不均.md
Normal 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分配了优先级{4,1,1},这6个请求到来时,还当成6个步骤,如表所示。
|
||||
|
||||
|
||||
请求1由优先级最高的服务器1处理,服务器1的优先级相应减1,此时各服务器优先级为{3,1,1};
|
||||
请求2由目前优先级最高的服务器1进行处理,服务器1优先级相应减1,此时各服务器优先级为{2,1,1}。
|
||||
以此类推,直到处理完这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,之后会根据系统运行情况动态变化。
|
||||
|
||||
|
||||
假设,各服务器的优先级是{4,1,1},我还是将6个请求分为6步来进行讲解,如表所示:
|
||||
|
||||
|
||||
遍历集群中所有服务节点,使用current_weight = current_weight + effective_weight,计算此时每个服务节点的current_weight,得到current_weight为{4,1,1},total为4+1+1=6。选出current_weight值最大的服务节点即服务器1来处理请求,随后服务器1对应的current_weight减去此时的total值,即4 - 6,变为了-2 。
|
||||
按照上述步骤执行,首先遍历,按照current_weight = current_weight + effective_weight计算每个服务节点current_weight的值,结果为{2,2,2},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的请求会落在同一个服务节点上,所以可以用于有状态请求的场景。
|
||||
|
||||
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
|
||||
|
||||
|
||||
|
||||
加油,相信通过本讲的学习,你对分布式系统中的负载均衡有了一定的理解,也可以进一步对电商系统、火车票系统等涉及的请求负载均衡的问题进行分析了。加油,行动起来吧!
|
||||
|
||||
思考题
|
||||
|
||||
在分布式系统中,负载均衡技术除了各节点共同分担请求外,还有什么好处呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
176
专栏/分布式技术原理与算法解析/29分布式高可靠之流量控制:大禹治水,在疏不在堵.md
Normal file
176
专栏/分布式技术原理与算法解析/29分布式高可靠之流量控制:大禹治水,在疏不在堵.md
Normal 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指标进行流量控制的两种方式。
|
||||
|
||||
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
|
||||
|
||||
|
||||
|
||||
加油,相信通过本讲的学习,你对分布式系统中的流量控制有了一定的理解,也可以进一步对电商系统中抢购、秒杀中的流量控制问题进行分析了。加油,行动起来吧!
|
||||
|
||||
思考题
|
||||
|
||||
除了漏桶策略和令牌桶策略,你还知道哪些流量控制策略吗?它们的原理是什么呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
146
专栏/分布式技术原理与算法解析/30分布式高可用之故障隔离:当断不断,反受其乱.md
Normal file
146
专栏/分布式技术原理与算法解析/30分布式高可用之故障隔离:当断不断,反受其乱.md
Normal 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篇文章中关于负载均衡技术的相关内容。
|
||||
|
||||
以数据分片为例,系统可以将不同用户的数据存储到不同的数据库,即一个数据库只存储部分用户的信息。这样当某个数据库出现故障时,仅影响该故障数据库存储的用户,而不会影响全部用户。
|
||||
|
||||
负载均衡也是这个道理。当处理请求的某个服务器出现故障时,只影响该故障服务器负责的用户请求,而不会影响其他服务器负责的用户请求。
|
||||
|
||||
总结
|
||||
|
||||
今天,我主要带你学习了分布式高可用技术中的故障隔离技术。
|
||||
|
||||
首先,我以汽车故障为例,带你了解了故障隔离的概念,并引出分布式系统中的故障隔离。分布式系统中的故障隔离技术是,在进行分布式系统可用性设计时,考虑故障隔离的设计,也就是提前预防或避免出现故障后对整个系统造成影响。
|
||||
|
||||
然后,我与你介绍了常见的故障隔离策略,包括线程级隔离、进程级隔离和资源隔离策略。其中,线程级隔离和进程级隔离是从对功能/服务模块进行隔离的维度进行划分的,借助了系统本身对线程或进程的隔离机制实现故障隔离;资源隔离是从资源的维度进行隔离,主要通过容器、服务器、集群、机房等物理资源维度进行隔离。
|
||||
|
||||
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
|
||||
|
||||
|
||||
|
||||
加油,相信通过今天的学习,你对分布式系统中的故障隔离技术有了一定的理解,也可以进一步对容器、虚拟机等的隔离技术进行深入分析了。加油,行动起来吧!
|
||||
|
||||
思考题
|
||||
|
||||
分布式系统难免会发生故障,那么评判一个系统故障的指标有哪些呢?或者说通过哪些指标可以判断故障的健康度呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
183
专栏/分布式技术原理与算法解析/31分布式高可用之故障恢复:知错能改,善莫大焉.md
Normal file
183
专栏/分布式技术原理与算法解析/31分布式高可用之故障恢复:知错能改,善莫大焉.md
Normal file
@ -0,0 +1,183 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
31 分布式高可用之故障恢复:知错能改,善莫大焉
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
在上一篇文章,我带你学习了故障隔离。故障隔离的目的是,对故障组件进行隔离,以避免其影响系统中的其他组件,尽可能保证分布式系统的可用性。
|
||||
|
||||
在分布式系统中,故障在所难免,发生故障后仅仅进行隔离还远远不够,还需要进行故障恢复。比如,现在集群中有3个节点,节点1故障后,对节点1进行隔离,如果节点2、节点3紧接着故障了,又隔离了这两个节点。那么,整个集群就无法继续提供服务了,何谈分布式系统的高可用呢?
|
||||
|
||||
为了解决这种问题,分布式领域还有一个关键技术来保证系统的高可用,即故障恢复。
|
||||
|
||||
接下来,我们就一起打卡分布式系统的故障恢复技术吧。
|
||||
|
||||
分布式故障基础知识
|
||||
|
||||
在介绍故障恢复之前,我先与你说说分布式系统中会有哪些故障类型。
|
||||
|
||||
故障类型
|
||||
|
||||
在任何一个分布式系统中,故障都是不可避免的。这里的故障,通常包括两类:
|
||||
|
||||
|
||||
一类是物理故障,比如硬盘损坏、断电断网、硬件升级等;
|
||||
另一类是软件层故障,比如系统存在Bug导致系统崩溃、系统负载过高导致系统崩溃等。
|
||||
|
||||
|
||||
在讨论分布式系统故障时,我们通常还会从是否是网络导致的故障的角度来进行故障划分,包括节点故障和网络故障,而这两类故障可能同时包括物理故障和软件层故障。由于软件层故障和具体的程序实现等相关,因此主要由开发者根据自己的实现去解决;而物理故障通常具有很多共同特征,因此今天我主要针对物理故障导致软件不可用的情况进行讲解。
|
||||
|
||||
首先,我们看一下节点故障。
|
||||
|
||||
简单地说,节点故障就是单个机器自身出现故障。比如,由机器A、B,……,Z构成的分布式集群中,机器A自身出现故障,而不是非机器之间的网络连接出现故障,就是节点故障。
|
||||
|
||||
|
||||
|
||||
节点故障有很多种,大体可以分为两类:
|
||||
|
||||
|
||||
一类是硬件故障,比如机器硬盘损坏、内存接触不良等;
|
||||
另一类是软件故障,比如由于请求过多,超过服务器处理能力上限,导致无法处理,又或者是机器被攻击,导致机器瘫痪等。
|
||||
|
||||
|
||||
节点故障在软件层的表现结果是,该机器无法为用户提供服务。
|
||||
|
||||
其次,我们看一下网络故障。
|
||||
|
||||
简单地说,网络故障就是分布式集群中,节点之间无法完成通信。比如,由机器A,B,……,Z构成的分布式集群中,机器间比如机器A和B之间无法完成通信,就属于网络故障。
|
||||
|
||||
|
||||
|
||||
网络故障也有很多种,比如路由器故障、DNS故障、网络线路断裂等。这些物理故障在软件层的表现结果是,机器间无法通信,影响分布式应用正常提供服务。
|
||||
|
||||
了解了故障的类型,我们还要搞明白如何检查到故障,也就是如何进行故障检测,因为这是故障恢复的前提。
|
||||
|
||||
故障检测
|
||||
|
||||
故障检测,就是指通过一定的方式识别或发现故障。就好比,我们把火灾、地震等危险事件看作是故障,采用火灾报警器、地震仪等来检测发现火灾或地震。
|
||||
|
||||
如果可以提前检测到事件的发生,就能将损失降到最小。在分布式系统中,检测硬件故障通常比较麻烦,因此会通过查看软件层的表现结果来进行故障检测。比如,网络故障导致服务器之间无法通信,因此就可以通过检测服务器之间是否可以通信(比如,服务器之间心跳包是否可以正常地发送和接收),来检测是否存在网络故障。
|
||||
|
||||
关于故障检测的具体策略,我会在后文与你展开。当检测到故障后,就需要进行故障恢复了。
|
||||
|
||||
故障恢复
|
||||
|
||||
故障恢复,就是指修复分布式系统中出现的故障,使系统恢复正常。简单来说,故障恢复就是故障发生之后的弥补方案,可以理解为对故障进行修正或修复,以保证服务正常运行,有点类似“知错能改,善莫大焉”。
|
||||
|
||||
接下来,我们就具体看看故障检测和故障恢复的原理或者说策略吧。
|
||||
|
||||
分布式故障检测原理
|
||||
|
||||
在分布式系统中,常见的故障检测方法是心跳机制。基于心跳进行故障检测的策略主要分为两类,固定心跳检测策略和根据历史心跳信息预测故障策略。
|
||||
|
||||
还记得我在第22篇文章中,与你介绍的通过心跳方式判断集中式架构和非集中式架构中节点是否存活的方法吗?其实,这里用到的就是固定心跳检测策略。具体的检测原理,你可以再回顾下这篇文章。
|
||||
|
||||
所以接下来,我主要与你分享基于历史心跳消息预测故障的策略,也就是我们常说的\(φ\)值故障检测。
|
||||
|
||||
\(φ\)值故障检测是基于心跳间隔符合正态分布的假设进行计算的。其中,\(φ\)值是用来评估心跳是否超时的概率,是对心跳间隔的概率求对数,将非整数转换为整数以便于理解。
|
||||
|
||||
\(φ\)值故障检测方法中,通常会设置一个阈值Ф,若当前心跳计算得到的\(φ≥Ф\),则判断心跳超时,否则心跳未超时。
|
||||
|
||||
那么,\(φ\)值是如何计算的呢?
|
||||
|
||||
从流程上来讲,\(φ\)值的计算可以分为三步,即:
|
||||
|
||||
|
||||
采样窗口存储心跳到达的时间;
|
||||
通过样本计算出心跳到达时间间隔的分布;
|
||||
使用得到的正态分布计算当前的\(φ\)值。
|
||||
|
||||
|
||||
接下来,我们就具体看看这三个阶段吧。
|
||||
|
||||
第一步:采样窗口存储心跳到达的时间。
|
||||
|
||||
采样窗口就是一个具有固定容量的容器,一般存储近k次的心跳信息,每次心跳到达时,会将到达时间存储到采样窗口,如果采样窗口已满,则会删除窗口中最旧的数据。
|
||||
|
||||
比如,采样窗口最多存储最近10次心跳到达的时间,t1,t2,……, t10,当第11次心跳到来时,采样窗口会将t1删除,存入t11。到达时间的间隔很容易得到,比如第11次心跳到来后,到达时间的间隔是t3 - t2,t4 – 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级,比如200ms,k设置为1000/200=5次。但,这样一来容易导致误判。因为判断超时的时间设置得太短,很可能是系统做内存回收或系统本身有高任务在运行导致心跳回复延后。
|
||||
|
||||
而对于时延不太敏感的场景,k或T可以设置得大一些,降低误判率,但却会增加发现故障的时间。
|
||||
|
||||
接下来,我们看一下\(φ\)值故障检测。\(φ\)值故障检测是基于心跳间隔符合正态分布的假设,通过对历史心跳数据采样来预测当前心跳是否超时的。也就是说,心跳间隔符合比较平稳或符合规律的情况下,比较适合,但对于具有突发情况或心跳间隔无规律的场景误判率比较高。
|
||||
|
||||
在网络状况确定且比较稳定的场景下,大多数系统会采用固定心跳检测策略,因为其可以根据网络状况与业务场景自主设定合适的k和T值,简单有效;而当网络状况有所变化,且变化有规律的场景,则可以使用\(φ\)值故障检测策略。
|
||||
|
||||
总结
|
||||
|
||||
今天,我主要带你学习了分布式高可用技术中的故障恢复技术。
|
||||
|
||||
首先,我为你介绍了分布式系统中的故障类型,主要包括物理故障和软件故障,软件故障主要是由于程序或软件Bug等导致,通常由开发者在开发或测试过程中解决,而物理故障导致软件不可用的故障类型主要分为两类,节点故障和网络故障。
|
||||
|
||||
其次,我为你介绍了故障检测方法。故障检测方法主要是心跳检测方法,包括固定心跳策略和基于历史信息的心跳策略。其中,固定心跳策略的具体原理见第22篇文章中的相关内容;基于历史心跳策略的核心是通过统计历史数据规律,以预测当前心跳是否超时以进行故障检测;
|
||||
|
||||
紧接着,我为你介绍了故障恢复策略,主要涉及备升主、数据复制等关键技术,相关关键技术原理,你可以再回顾第4篇文章中的分布式选举算法,以及第26篇文章中的数据复制技术。
|
||||
|
||||
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
|
||||
|
||||
|
||||
|
||||
加油,相信通过今天的学习,分布式系统中的故障恢复技术对你来说不再陌生了,你可以根据业务场景设计出对应的故障检测和故障恢复策略了。行动起来吧,加油!
|
||||
|
||||
思考题
|
||||
|
||||
在分布式系统中,网络分区是一个非常重要的故障问题。那么如何判断网络分区呢?以及出现网络分区后,如何处理呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
156
专栏/分布式技术原理与算法解析/32答疑篇:如何判断并解决网络分区问题?.md
Normal file
156
专栏/分布式技术原理与算法解析/32答疑篇:如何判断并解决网络分区问题?.md
Normal 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{主Master,Slave1, Slave2}和子集群2{备Master,Slave3, 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总节点数,所以也不适用于处理多个分区的情况;而基于仲裁和共享资源的网络分区处理方法,其实都是依赖一个三方的节点或组件,所以适用于有一个稳定可靠的三方节点或组件的场景。
|
||||
|
||||
如果还有哪些思考题或者留言问题,还没来得及扩展的话,你可以留言给我,后续我会再找机会进行解答。最后,我要和你说的是,和我一起打卡分布式核心技术,一起遇见更优秀的自己吧。
|
||||
|
||||
篇幅所限,留言区见。
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
143
专栏/分布式技术原理与算法解析/33知识串联:以购买火车票的流程串联分布式核心技术.md
Normal file
143
专栏/分布式技术原理与算法解析/33知识串联:以购买火车票的流程串联分布式核心技术.md
Normal file
@ -0,0 +1,143 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
33 知识串联:以购买火车票的流程串联分布式核心技术
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
还记得在专栏之初,我和你分享的“分布式四纵四横知识体系”吗?截止到目前,我已经带你学习了四横和三纵,包括分布式计算、分布式存储与管理、分布式通信、分布式资源池化、分布式协同、分布式调度和分布式追踪与高可用的关键技术(由于分布式追踪、分布式部署虽属于支撑技术,但并不会影响业务的构成,因此我没有在专栏中展开)。
|
||||
|
||||
但学以致用才是最终目的,所以在接下来的模块中,我将通过两篇总结性质的文章,为你串联起前面讲到的核心知识点,看看它们在业务中是如何应用的。
|
||||
|
||||
今天,我就先以购买火车票的流程,带你串联下整个专栏涉及的分布式核心技术吧。
|
||||
|
||||
首先,为方便你理解,并抓住其中涉及的核心技术,我对购买火车票的流程做了一个简化,大致划分为三大核心步骤:
|
||||
|
||||
|
||||
首先,铁路局向购票系统发布火车票;
|
||||
然后,用户通过系统查询火车票,找到需要的火车票后购买;
|
||||
最后,购票系统给用户响应,完成购票。
|
||||
|
||||
|
||||
这个流程看似简单,但涉及了我们之前讲过的很多知识。
|
||||
|
||||
这里,我有个小建议,在学习后面的内容前,你可以先自己思考下这个过程涉及了哪些知识点,然后再与我接下来的讲述进行对比,以验证自己对之前内容的掌握程度。这样一来,你可以加深对已掌握知识的理解深度,也可以查漏补缺进而有针对性地复习其他内容。
|
||||
|
||||
那么接下来,我就主要分为三部分进行讲解:铁路局发布火车票、用户查询火车票,以及用户购买火车票。
|
||||
|
||||
铁路局发布火车票
|
||||
|
||||
铁路局发布火车票的过程,主要涉及分布式数据存储这一站的知识,包括存储系统三要素、数据分布和数据复制等技术。
|
||||
|
||||
铁路局向购票系统发布火车票的过程,主要是将火车票信息发送到服务器集群进行存储,需要用到存储系统三要素的相关技术。其中,铁路局就是存储系统三要素中的“顾客”,火车票存储到具体哪个服务器需要构建数据索引,也就是我们说的三要素中的“导购”,而存储数据的服务器就是三要素中的“货架”。
|
||||
|
||||
由于涉及多个服务器存储火车票信息,不可避免地需要考虑服务器之间数据存储的均衡,以及快速确定火车票信息存储位置以方便后续查询和购买火车票。因此,这里需要用到分布式存储中的数据分布技术。
|
||||
|
||||
铁路局按照火车线路将火车票发布到不同的服务器上,即在数据分片技术中提到的按照数据范围进行分片。比如,京广线的车票信息存储在京广线服务器集群、京沪线的车票信息存储在京沪线服务器集群等。
|
||||
|
||||
除此之外,为了保证可靠性,也就是当一台服务器故障后,该服务器存储的火车票信息可以恢复或不丢失,通常会进行数据备份。也就是说,同一份数据可能会有多台服务器一起存储,比如京广线的火车票数据存储到服务器A1、A2和A3上,京沪线的火车票数据存储在服务器B1、B2和B3上。
|
||||
|
||||
而数据备份,用到的就是分布式存储中的数据复制技术。由于同一份数据被多台服务器存储,自然就需要保证数据的一致性。关于数据一致性,你可以参考CAP理论这篇文章。
|
||||
|
||||
|
||||
|
||||
接下来,我们再看看用户查询火车票过程中涉及了哪些相关技术吧。
|
||||
|
||||
用户查询火车票
|
||||
|
||||
对于用户查询火车票来说,大致过程是用户向服务器发起查询请求,服务器根据用户请求,通过数据索引,也就是导购技术,定位到火车票信息存储的位置,然后获取数据返回给用户。
|
||||
|
||||
从这个流程可以看出,服务器接收用户查询请求是第一步。
|
||||
|
||||
正常情况下,用户并发请求量比较小,很少会出现服务器能力有限导致系统崩溃的情况。但,遇到节假日或春节,用户请求量通常会非常大,这时如果不采取一定策略的话,大概率会因为服务器能力受限导致系统崩溃。
|
||||
|
||||
而这里的策略,通常就是保证分布式高可靠的负载均衡和流量控制。比如,每年春运,火车票发布的瞬间,就有大量的用户抢票,如果购票系统后台不使用负载均衡和流量控制的话,服务器一下子就被击垮了。
|
||||
|
||||
除此之外,服务器还不可避免地会出现一些小故障,比如磁盘损坏、网络故障等问题。如何检测服务器故障,以及如何进行故障恢复,就需要用到分布式高可用的故障隔离与故障恢复的相关技术了。
|
||||
|
||||
接收用户请求后,接下来需要将请求转发至相应的服务器集群,然后再从中选择某一台服务器处理用户请求,也就是获取数据并返回给用户。本质上,这就是在进行数据索引,设计分布式数据存储中的数据分布方式的相关技术。
|
||||
|
||||
以用户查询从北京到上海的火车票信息为例,查询流程如下:
|
||||
|
||||
|
||||
首先,根据查询条件,系统将请求转发至存储京沪线火车票信息的服务器集群中;
|
||||
然后,服务器集群再使用一次负载均衡,比如使用轮询算法, 将请求转发至某一台服务器;
|
||||
最后,这台服务器将火车票的车次、余票等信息返回给用户。
|
||||
|
||||
|
||||
|
||||
|
||||
在这个过程中,还可以使用限流算法,比如漏桶、令牌桶等策略来限制用户流量,保证系统高可用。
|
||||
|
||||
除此之外,在存储火车票信息的服务器中,各个服务器的服务单独运行,也就相当于做了一定的故障隔离,比如图中的服务器A1~A3、B1~B3。
|
||||
|
||||
当然,这里还需要注意一个问题。在上面的过程中,我一直提到的是服务器集群。既然是集群,就会涉及集群架构、分布式选主等策略。
|
||||
|
||||
以集中式架构Master/Slave为例,服务器集群中会通过分布式选举算法选出一个Master,Master和其他服务器节点之间会维持心跳,并通过心跳来感知服务器节点的存活状态。比如,京广线服务器集群选出的Master节点为A2,A2与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、消息队列等。
|
||||
|
||||
如图所示,用户购买火车票后,会首先在订单系统下单,下单成功后会调用支付系统的支付操作进行支付,之后将支付成功的消息存放到消息队列中,通知系统到消息队列中获取消息,最后通知用户购买成功。
|
||||
|
||||
|
||||
|
||||
总结
|
||||
|
||||
今天,我主要以购买火车票为例,为你串联了分布式技术在实践中的应用。
|
||||
|
||||
为方便理解,我将购买火车票的模型简化为三个核心步骤,即铁路局发布火车票、用户查询火车票和用户购买火车票。
|
||||
|
||||
其次,我分别与你分析了这三个核心步骤涉及的关键的分布式技术。
|
||||
|
||||
对于铁路局发布火车票这个流程来说,铁路局是数据的生产者,需要将数据发布到服务器进行存储,主要涉及的是分布式数据存储相关技术,对应专栏“第五站:分布式数据存储”的内容。
|
||||
|
||||
对于用户查询火车票来说,主要是读请求,涉及负载均衡、流量控制、集群管理及选主等技术,对应专栏“第一站:分布式协调与同步”“第二站:分布式资源管理与负载调度”和“第六站:分布式高可靠”的内容。
|
||||
|
||||
而对于用户购买火车票来说,是写请求,涉及数据的改变,因此除了用户查询火车票涉及的技术外,还额外涉及一致性、分布式事务、远程通信等技术,对应专栏“第一站:分布式协调与同步”、“第四站:分布式通信技术”、“第五站:分布式数据存储”和第六站:“分布式高可用”等内容。
|
||||
|
||||
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
|
||||
|
||||
|
||||
|
||||
思考题
|
||||
|
||||
结合购买火车票这个案例,你能和我分享你身边的应用场景或系统,都涉及或采用了哪些分布式技术吗?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
456
专栏/分布式技术原理与算法解析/34搭建一个分布式实验环境:纸上得来终觉浅,绝知此事要躬行.md
Normal file
456
专栏/分布式技术原理与算法解析/34搭建一个分布式实验环境:纸上得来终觉浅,绝知此事要躬行.md
Normal 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
|
||||
|
||||
|
||||
|
||||
这时,我们就可以使用如下命令来安装kubelet、kubectl和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的报错信息
|
||||
|
||||
|
||||
最后,使用如下命令安装kubelet、kubectl和kubeadm:
|
||||
|
||||
apt-get install -y kubelet kubeadm kubectl --allow-unauthenticated
|
||||
|
||||
|
||||
安装好这三个组件之后,我们就可以使用kubeadm来一键部署集群节点了。
|
||||
|
||||
首先,我们来部署Master节点。
|
||||
|
||||
3. 部署Master节点
|
||||
|
||||
这一步其实就是容器化启动Master节点中的各个组件,直接使用kubeadm工具提供的一条命令kubeadm init即可自动安装。
|
||||
|
||||
kubeadm init这条命令底层其实就是将Master的各个组件,比如API Server、etcd等,以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. 安装网络插件
|
||||
|
||||
网络插件有很多,比如Canal、Flannel、Weave等。不同的插件命令不一致,具体命令可参考官网。
|
||||
|
||||
这里,我以安装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配置文件中规定的期望状态;还可以自动实现版本更迭等。
|
||||
|
||||
相信通过本讲的学习,你会对分布式技术有更进一步的认知。加油,赶紧行动起来,为你的服务搭建一个分布式实验环境吧。
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
|
||||
|
65
专栏/分布式技术原理与算法解析/特别放送Jackey:寄语天涯客,轻寒底用愁.md
Normal file
65
专栏/分布式技术原理与算法解析/特别放送Jackey:寄语天涯客,轻寒底用愁.md
Normal file
@ -0,0 +1,65 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
特别放送 Jackey:寄语天涯客,轻寒底用愁
|
||||
大家好,我是Jackey,目前在一家创业公司的架构组做Java开发工作。因为工作需要,我也会做一些Node、Python以及运维相关的工作,所以称自己为“半栈工程师”。今天,我想和你说说我学习这个专栏的一些心得。
|
||||
|
||||
我平时是如何学习的?
|
||||
|
||||
我先来和你说说我的经历吧。
|
||||
|
||||
本科毕业后,我去了一家国企做项目开发,日常工作就是使用已有框架去快速搭建一套可用的内部管理系统,然后做些定制化。
|
||||
|
||||
做了一年多,我感觉工作内容大量重复,所用技术也与行业严重脱节。但同时我也发现,我竟然连自己做的项目是什么原理都说不清楚,没办法通过其他公司的面试。然后,我开始深入研究所用框架,为日后的面试做知识储备。
|
||||
|
||||
当我在调用框架提供的某个方法时,不再停留在是什么、怎么用,而是去研究源码看看它是如何实现的。比如,NIO是怎么实现的,为什么用select而不是epoll;再比如,某个类的实例为什么要用单例,单例还有哪些写法;又比如,某处代码用到了线程池,用的是哪种阻塞队列,为什么要用有界队列,队列的参数是怎么设置的。
|
||||
|
||||
每次点开一段源码,我都感觉打开了新世界的大门。每次遇到新知识点,我都会通过搜索引擎去查看官方文档、技术博客或其他资料。在阅读这些技术资料的过程中,我又会发现一些新的知识盲区,进而通过递归学习去触达更多的知识。
|
||||
|
||||
经过半年多的研究,我终于搞清楚当时所用框架的基本原理了。而在这期间,我也从一个写Bug的小菜鸟变成了带领他人开发项目的小组长,顺利入职了一家AI公司,负责自有商城的开发。
|
||||
|
||||
团队人数不多,但技术足够新、氛围也很好,并且开发流程非常规范,编码格式要求严格。这,对我的个人成长帮助很大,因为代码就是开发者的名片,“优雅可读”的代码给人的印象总是好于“惨不忍睹”的代码。
|
||||
|
||||
因此,我感谢自己之前对技术原理的积累,让我有了一个更好的发展平台。
|
||||
|
||||
我为什么要学习“分布式技术原理与算法解析”专栏?
|
||||
|
||||
后来,因为内部调岗等因素,不想远离技术的我再次选择了离开,这次我打算去一家大公司。
|
||||
|
||||
由于我之前积累了比较多的基础知识,再加上读过一些Redis源码,在一面中的表现都很不错。但二面问到关于系统设计的问题时,我就有些无从下手了。
|
||||
|
||||
我记得在美团二面时,面试官让我设计一个注册中心。我了解一些Eureka的原理,所以简单阐述了下自己的想法,然后,面试官在此基础上增加难度,问我:如何保证高可用、如何应对高并发。这时,我想到了要使用分布式技术,但如何做分布式、机器间怎么联系在一起、如何保证机器间数据的一致性呢?
|
||||
|
||||
当时,我对分布式技术并没有一个清晰的认识,也不了解这些问题具体要如何解决,更不清楚引入分布式系统后会提升多少复杂度。所以,我面试的回答大都止步于提出分布式概念。
|
||||
|
||||
这时,我意识到自己需要去学习一些分布式系统的设计方法。互联网发展到今天,稍微有些规模的软件几乎都离不开分布式部署,多数基础组件也都建议进行分布式部署,分布式技术更是高并发、高性能的重要保证。
|
||||
|
||||
所以说,作为一名互联网从业者,如果不了解分布式的原理,就快与时代脱节了。也许,你会用“分久必合”来反驳我,但目前来看,硬件的发展已经明显降速,可能很难再看到“合”的场景了。因此,我相信分布式技术将会成为程序员的一项必备基础技能,看到“分布式技术原理与算法解析”专栏时毫不犹豫地就入手了。
|
||||
|
||||
学习这个专栏,我有哪些收获?
|
||||
|
||||
我学习专栏的习惯是,每周一三五更新后,当天上班的地铁上就抓紧学习,如果遇到不懂的知识点,就第一时间去查阅相关资料。看完一篇文章后,我会在评论区留下说说哪里有困惑,以及对应的课后思考题的解答思路,并会关注其他同学的留言,加深对这篇文章的理解。
|
||||
|
||||
一路学习下来,所有的辛苦都是值得的,所有的付出都是有收获的。跟随专栏到现在,对于我来讲是“温故”和“知新”的过程。
|
||||
|
||||
比如,我在学习“分布式事务:all or nothing”这一篇后,不仅对熟悉的二阶段提交有了更透彻的认识,还学到了它的改良版,三阶段提交的原理。
|
||||
|
||||
再比如,我在学习“分布式锁:关键重地,非请勿入”这一篇后,除了对基于缓存的分布式锁有了更深的理解外,还学习到了基于ZooKeeper实现的分布式锁,让我在日后的系统设计时又多了一种思路。
|
||||
|
||||
目前,我们公司还处在搭建基础架构的初期。随着对分布式架构的不断了解,我在公司担任的角色也更加重要了,设计新系统时Leader也总会听听我的意见。同时,我也希望能通过学习这个专栏,在公司内做一些关于分布式技术的分享,来提升自己的技术影响力。
|
||||
|
||||
我觉得,这都是这个专栏给我带来的收获吧。
|
||||
|
||||
给新同学的一些建议
|
||||
|
||||
如果你已经订阅了这个专栏,我的建议很简单,那就是抓紧时间学习吧,早学就是优势。当你养成学习习惯以后,你就发现自己已经比身边的大部分人都优秀了。
|
||||
|
||||
另外,我想说的是,学习专栏的过程不要局限于某个知识点,你可以再根据文章内容进行二次发散,去学习更多的知识,由点及面,构建自己的核心知识体系。
|
||||
|
||||
如果你还在犹豫的话,可以对比一下我的经历,如果你有所感触的话,真心建议你能认真学完整个专栏,再去挑战大厂,成功率会大很多。
|
||||
|
||||
|
||||
|
||||
|
197
专栏/分布式技术原理与算法解析/特别放送分布式下的一致性杂谈.md
Normal file
197
专栏/分布式技术原理与算法解析/特别放送分布式下的一致性杂谈.md
Normal 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 Processing(DTP)模型,简称XA协议。
|
||||
|
||||
|
||||
|
||||
顾名思义,两阶段提交的整个过程分为两个阶段,第一阶段询问是否可以提交,锁定数据,如果所有节点都返回可以提交,第二阶段提交,否则第二阶段回滚。基于XA的两阶段提交就是这种流程,这种方式常用于同时更新两个数据库的场景,一般常用的关系型数据库都支持这个协议。
|
||||
|
||||
由于第一阶段要给数据库加锁,否则会出现不一致的情况,这就会带来很多问题,XA被诟病的大部分原因都跟这个锁有关。例如,加锁后,协调者挂掉怎么办?加锁后,性能大幅下降如何处理?
|
||||
|
||||
为了解决死锁的问题,可以将加锁的时间缩短,降低死锁的概率,这就是三阶段提交。也就是说把第一阶段分为两个部分,询问是否可以提交,回复可以提交的时候,并不加锁,当所有节点都回复可以的时候,协调者再发一次加锁的请求。但这样的话,系统会变得更复杂。
|
||||
|
||||
当然,两阶段提交并不是只有XA协议,TCC也可以看成是一种两阶段提交协议,TCC是Try-Confirm-Cancel的缩写。相对于两阶段提交事务机制,它的特征在于不依赖于数据库的协议,数据库不需要锁定数据,事务过程由业务服务来实现,这样也就不会出现死锁的问题,性能也会高很多。还有一点容易被忽略,这种做法降低了数据库的压力。
|
||||
|
||||
|
||||
|
||||
当然,世界上没有免费的午餐,TCC事务机制也有不好的地方:
|
||||
|
||||
|
||||
导致业务变得更复杂,数据库需要增加字段表示状态;
|
||||
需要增加相应接口实现Confirm和Cancel方法;
|
||||
业务要自己保证每个方法的幂等,因为这里可能涉及到失败重试的问题。
|
||||
|
||||
|
||||
而一阶段提交,就是没有询问是否可以提交的过程,直接提交,任意一个节点提交失败,就进行重试或者回滚。这种方案对业务的侵入性很大,需要对业务提供一个回滚操作。
|
||||
|
||||
实践案例
|
||||
|
||||
接下来,我就以在华为云的工作场景为例,来给你串下整体的思路吧。
|
||||
|
||||
华为云目前的分布式框架采用TCC的模式,将协调者从业务中抽离,独立为一个服务。主业务发起事务,获得全局事务ID,调用分支业务时将全局事务ID通过Header传递过去,分支业务根据全局事务ID申请分支事务ID,所有的状态都会存储到分布式事务服务端,服务端根据执行情况进行全局协调。
|
||||
|
||||
事务执行的大概步骤,如下所示。
|
||||
|
||||
|
||||
|
||||
|
||||
步骤1:进入到发起全局事务的方法内时,会先向DTM集群申请注册一个全局事务ID(Global Transaction ID),只有申请成功才可继续后续流程。
|
||||
步骤2.1:事务发起者将申请到的全局事务ID透传到所调用的事务参与者中。
|
||||
步骤2.2:事务参与者利用得到的全局事务ID,向DTM集群注册申请一个分支事务ID(Branch Transaction ID),只有申请成功才可继续此事务参与者的后续流程。
|
||||
步骤2.3:事务参与者完成自身业务逻辑(即完成TCC中的Try阶段)。
|
||||
步骤2.4:事务参与者将自身业务逻辑结果上传到DTM集群,并示意分支事务结束。
|
||||
步骤3.1~3.4:与2.1~2.4类似,完成剩余事务参与者的业务逻辑。
|
||||
步骤4:事务发起者发起TCC二阶段。
|
||||
步骤5.1:DTM集群根据全局事务ID,找到事务参与者,发起TCC二阶段。
|
||||
步骤5.2:与5.1类似,完成剩余事务参与者的TCC二阶段。
|
||||
步骤6:DTM集群通告事务发起者全局事务结束。
|
||||
|
||||
|
||||
什么时候开始考虑一致性?
|
||||
|
||||
在分布式领域,一致性是个永恒的话题,因为在大多数场景下,出于成本考虑,通常会选择性地忽略或者降低对一致性的要求。但是,当系统达到一定规模的时候,不一致的概率就会大大增加,比如原本不一致的概率可能是一万年一次,如果数据量提升一万倍,那可能是一年一次。但,无论发生的概率是多少,研发成本都是一样的,这时候实现更强的一致性就非常有必要了。
|
||||
|
||||
凡是涉及钱的系统,对一致性的要求必然很高,除此之外,大多数场景实际上对一致性的要求并没有那么高,比如用户登录时给用户加一个积分,加积分这个操作并没有那么重要,延迟几秒影响也不大,那完全可以做成异步的,保证最终一致性就可以了。
|
||||
|
||||
有没有什么学习材料?
|
||||
|
||||
如果要推荐学习资料的话,我建议你可以多去看看分布式领域的论文,特别是大神莱斯利 · 兰波特(Leslie Lamport)的,以及Google、Amazon等硅谷著名企业发布的论文。比如,下面这两篇,我就非常推荐你去读一读,因为很多数据库的分布式事务都是参考了它们 。
|
||||
|
||||
|
||||
Percolator的论文,可以认为是Spanner的类似实现,TiDB的实现也是根据此论文得出。
|
||||
DynamoDB的论文,Dynamo: Amazon’s Highly Available Key-value Store。
|
||||
|
||||
|
||||
另外,你还可以看一些开源框架的实现,比如Cassandra、Kafka等,这些代码对于你理解分布式一致性会有非常大的帮助。
|
||||
|
||||
总结
|
||||
|
||||
我来简单和你总结一下今天的主要内容吧。
|
||||
|
||||
首先,要搞明白分布式一致性,我们就要从其分类开始,除了弱一致性、强一致性、最终一致性这种分类方式外,还有一种更好的分类方式,就是以数据为中心和以用户为中心的一致性分类。
|
||||
|
||||
其次,如果要满足一致性的需求,有很多种方案,这里我们介绍了两种最常用的方案,Quorum机制和N阶段提交,并通过华为云的一个实践案例来加深你对一致性的理解。
|
||||
|
||||
最后,在分布式领域,一致性是目前为止解决得最不好的一个领域,也是最复杂的一个领域,解决方案五花八门,我只是与你介绍了其中的一部分内容,如果你要继续学习,可以多阅读一些论文和开源框架的代码。
|
||||
|
||||
|
||||
|
||||
|
69
专栏/分布式技术原理与算法解析/特别放送崔新:追根溯源,拨开云雾见青天.md
Normal file
69
专栏/分布式技术原理与算法解析/特别放送崔新:追根溯源,拨开云雾见青天.md
Normal 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法则。
|
||||
|
||||
我感触比较深的另一个点是,学习知识要多思考、勤总结。聂老师在每篇文章后都提供了一个知识扩展模块和一张思维导图,不仅有助于我复习,还能帮我从广度和深度上完善自己的知识体系。对我来说,这是一个非常好的方法。
|
||||
|
||||
我想对专栏初学者说的话
|
||||
|
||||
工作一年多来,我总结了些学习方法,也有很多感触,想和你分享、交流。
|
||||
|
||||
首先,基础要扎实,学习要系统。虽然我是计算机专业出身,但大学时净忙着学习各种编程语言了,反倒是计算机基础知识没打牢。意识到这一点后,我开始系统地学习计算机组成原理、操作系统、数据库、数据结构与算法等基础知识了。
|
||||
|
||||
如果你的基础也和我一样不那么扎实的话,不要着急,静下心来一点一点地学习就好了。
|
||||
|
||||
其次,眼光放长远,关注复利效应。现在市面上充斥着各种速成系列,我觉得要离这些东西远一些,否则会一直在低水平重复。不求速成,日拱一卒才更适合我。只要保持增长,即使增长速度没那么快,日积月累的效果也会很可观。这,就是复利效应的魔力。
|
||||
|
||||
|
||||
|
||||
|
95
专栏/分布式技术原理与算法解析/特别放送徐志强:学习这件事儿,不到长城非好汉.md
Normal file
95
专栏/分布式技术原理与算法解析/特别放送徐志强:学习这件事儿,不到长城非好汉.md
Normal file
@ -0,0 +1,95 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
特别放送 徐志强:学习这件事儿,不到长城非好汉
|
||||
|
||||
你好,我是聂鹏程。今天,我要和你分享的是,一名专栏订阅用户“徐志强”的学习经历与方法,主题是“学习这件事儿,不到长城非好汉”。
|
||||
|
||||
你在学习专栏的过程中,有没有什么独特的学习方法和心路历程呢?欢迎你写在留言区,我很期待能跟你在这里交流、讨论,一起学习进步。
|
||||
|
||||
话不多说,现在就请你返回文稿看看徐志强的故事吧。
|
||||
|
||||
|
||||
大家好,我是徐志强,一名Java开发者,从事互联网金融工作三年了。
|
||||
|
||||
从2016年毕业到现在,我从一枚“小白”变成了“熟练工”。但,我一直不甘心从“小熟练工”变成“老熟练工”,所以我来了“极客时间”。回过头来看,自学的这三年,我收获很多,也走了不少弯路。所以,我想把自己的学习经历分享出来,希望能帮你避开些弯路。
|
||||
|
||||
我是如何学习的?
|
||||
|
||||
我自学的三年,可以分为以下三个阶段。
|
||||
|
||||
第一阶段:啥都不懂,啥都想学。我买了非常多的书,数据库、操作系统、分布式等每个知识领域都买了一两本,但只是停留在“看了”的阶段。工作中遇到问题时,我不能融会贯通地用到所学知识,或者说我自学的知识基本用不到工作中。这就像大脑里装了很多武器,但我只记得它们的说明书,无法根据它们的特性做出选择。
|
||||
|
||||
其实在这个阶段,我们都很容易有个误解:觉得只要看了足够多的书、学了足够多的框架,能力自然就会得到提高,但其实蜻蜓点水般的学习收效甚微。所以,在我看来,广度学习虽然容易,但其实只算是到了长城脚下,只是起点。
|
||||
|
||||
第二阶段:调整思路,有选择地精读某些内容。觉察到不对后,我把之前那些书重新找了出来,并调整学习思路:挑选关键知识点采取精读的方式复习,并开始啃一些框架和库的源码。
|
||||
|
||||
“如果不了解核心原理,我始终都是一枚小白或者熟练工” ,这是我经常暗示自己的话。为此,我曾经通宵Debug就是为了搞明白一段很复杂的源码。每次我在源码中发现了书中描述的知识点后,都非常有成就感,原来就是这样呀!这也让我持续有动力去啃源码。
|
||||
|
||||
但我发现,学习源码并没有提升程序设计能力,我又进入了新的困惑期:怎样才能更好地消化和理解知识、怎样才能做到举一反三,养成解决问题的能力。为了爬上长城,我继续寻找答案。
|
||||
|
||||
第三阶段:学习一些架构方面的知识,形成知识点和问题的映射。当精读某一领域的知识内容后发现,我在系统设计上短板暴露了出来。因为做系统设计需要有全局思维,需要对整个系统架构的技术栈有清楚的认识,能对比分析不同技术的差异和特性,而不能局限于一个分支。
|
||||
|
||||
于是,我开始学习架构方面的知识,学习知识点和问题间的映射关系。每遇到一个问题我都会多思考几套解决方案并进行对比分析,每学到一个新知识点就思考它可以用在哪些场景,以及各个知识点有什么共性和联系。
|
||||
|
||||
说到这里,我再分享一段我的面试经历吧。去年年初我去参加面试,因为平时看了不少书、也阅读了不少源码,可以很轻松地通过一面、二面,但最后一面我就有点招架不住了。后来,我请教面试官,自己还欠缺哪方面的知识和能力。他告诉我,单论某一个知识点我掌握得还不错,但我不能将所有知识由点串成线,由线串成面,最终形成网,建议我平时多思考、多总结。
|
||||
|
||||
他的这番话对我影响非常大,一下就点醒了我,帮我找到了困惑许久的问题答案。正处于第三阶段学习的我,或许后面我还会有新的困惑,但方法总比问题多。现在,虽然我不知道自己爬到了长城的第几级台阶,但我坚信自己终将站上最高的烽火台!
|
||||
|
||||
总结我的学习经历,我觉得有下面几个点值得注意:
|
||||
|
||||
|
||||
一定要坚持学习充电,否则很快就会被淘汰。
|
||||
不要惧怕学习新技术,新技术不管怎么变化,原理和本质都是不变的。
|
||||
一定不要只漂在技术的表面,一定要学原理,否则漂的时间久了你自己都不知道漂到哪了。
|
||||
师傅领进门很关键。学一门新技术,最好是找一本经典的书,或者一门经典的课程系统地学习。
|
||||
多思考,才能构建自己的知识体系和思维模式,才能将知识化作解决问题的方案。在学习时,我们要思考各个知识点的共性和内在联系,思考这个知识点能解决什么问题,以及遇到的问题能用什么知识点去解决。这样,脑中储存的知识,才能真正为我们所用。
|
||||
|
||||
|
||||
为什么要学习分布式技术和原理?
|
||||
|
||||
分布式是当前技术领域的趋势之一,它扩展了计算机的计算边界,是区块链、人工智能、机器学习、大数据等众多前沿技术的基石。
|
||||
|
||||
所以,如果你不想永远当一枚小白或者熟练工,也不想被行业淘汰的话,就必须学习分布式技术,且一定要把原理学明白,不能局限于皮毛。我们不能一直随着浪花漂荡,要尝试将自己沉入海底,这样才能经受得住惊涛骇浪的洗礼,最终到彼岸。
|
||||
|
||||
我学习的第一个中间件是Kafka,刚开始学的时候就被它的分区、选主、备份等概念搞得晕头转向,后来又学习了Redis和Elasticsearch。这时,我发现这些中间件有一些共同特点:都会考虑分区来提高吞吐量,都会考虑备份来保证容错和可用性,多个备份之间都会选举一个主来提供服务。
|
||||
|
||||
慢慢地,对这些概念越来越熟悉后,我发现它们在分布式场景下都会遇到些类似的问题,只是各自的解决方案不同而已。因此,我心中萌生了系统学习分布式技术和原理的想法,因为只有弄清楚了分布式技术的本质和原理,才能在各种眼花缭乱的技术框架中游刃有余。
|
||||
|
||||
于是,我开始在网上搜集各种学习资料,又来到了极客时间,来到了“分布式技术原理与算法解析”这个专栏。
|
||||
|
||||
我是怎么学习这个专栏的?
|
||||
|
||||
说到我是怎么学习这个专栏的,因为之前已经自学过些分布式技术原理,所以可能会和其他人的方法不太一样。
|
||||
|
||||
首先,我不是利用碎片时间单篇文章地去学习,而是会找一个比较长的空闲时间,一气呵成地学习完一个模块的内容。这样我可以联系上下文,一下搞定相关知识点。
|
||||
|
||||
其次,学完每篇文章,我都会梳理、总结自己对这篇文章的理解,并尝试回答课后思考题,在评论区留言。同时,我也会关注其他同学的留言问题,并尝试做解答。在这个过程中,针对文章中的疑惑点,我会先去搜索相关资料学习,然后将具体的疑惑点留言给老师,等他的解答。
|
||||
|
||||
最后,关于知识点的思考和联想很重要。老师讲的很多分布式技术知识点都是通用的,在很多的中间件和框架中都有体现,根据这些理论去思考具体的组件是如何实现的,可以帮助我理解得更深刻。
|
||||
|
||||
学习到现在,我最大的收获和体会
|
||||
|
||||
到现在为止,整个专栏已经更新了18篇文章,可以说帮助我建立了对分布式技术和原理比较全面、系统的认知,越往后的体会越深。
|
||||
|
||||
其实,在学习这个专栏前,我就对分布式的一些概念,比如分布式事务、BASE理论、CAP理论、分布式锁等有些了解,但这些知识点在我脑中是独立没有关联起来的。所以,这个专栏对我最大的帮助是,帮我打通了这些知识点,有了一个系统化的认知。
|
||||
|
||||
此外,这个专栏给了我极大的信心。因为,聂老师的讲述是由浅入深的,并且还有大量形象的比喻和深刻的类比。比如,用事务模型来解释共享状态的分布式调度,在我看来就很形象、很有深度。
|
||||
|
||||
因此,这个专栏给我的感觉是“分布式技术和原理原来就是这么回事呀”,完全消除了我之前的畏难情绪。虽然学完这个课程不代表能透彻掌握分布式的技术原理和算法,但这肯定是一个非常棒的开始。
|
||||
|
||||
留给朋友们的建议
|
||||
|
||||
如果你之前没有接触过分布式的技术和中间件的话,我建议你先花点时间去补些基础,但这也不妨碍你将这个专栏作为你的“分布式技术和原理导论”来学习。
|
||||
|
||||
如果你之前了解过一些分布式技术和分布式组件,但是学得不深入的话,我觉得这个专栏可以帮你建立全面的分布式知识体系,直接带你练习上乘内功。非常值得拥有,我现在也正处于这个阶段。
|
||||
|
||||
如果你已经对分布式技术和常用组件都很熟悉了的话,我觉得这个专栏可以带你到更高的层次,去思考分布式的核心本质,帮助你更好地造轮子。
|
||||
|
||||
每个人都有自己的一座长城,有些人爬了一年数个月,有些人爬了十年,而有些人爬了一辈子,他们在长城上看到风景肯定大有不同。技术学习件事,我希望自己当一回好汉,爬上自己的长城,也祝愿各位小伙伴能爬上自己的长城!
|
||||
|
||||
|
||||
|
||||
|
143
专栏/分布式技术原理与算法解析/特别放送那些你不能错过的分布式系统论文.md
Normal file
143
专栏/分布式技术原理与算法解析/特别放送那些你不能错过的分布式系统论文.md
Normal 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
|
||||
Brewer’s 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: Facebook’s 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: Google’s Globally-Distributed Database
|
||||
Dynamo: Amazon’s 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 Google’s 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 学术去搜索相关的文章,找到所有引用这篇论文的新作品。这样一来,你就可以通过一篇经典论文不断深入,全面掌握一个领域。
|
||||
|
||||
最后,我希望你可以通过经典论文的助力,迅速建立起自己的知识武器库,来攻克日常工作中的难题。
|
||||
|
||||
|
||||
|
||||
|
51
专栏/分布式技术原理与算法解析/结束语为什么说提升职业竞争力要从尊重、诚实开始?.md
Normal file
51
专栏/分布式技术原理与算法解析/结束语为什么说提升职业竞争力要从尊重、诚实开始?.md
Normal 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”。
|
||||
|
||||
专栏的结束,也是另外一种开始,我和编辑已经制定了内容迭代计划。比如,中间有一些留言还没来得及回复,我会花时间处理完。再比如,有同学反馈其中几篇文章说写得不通透,我看完觉得确实觉得有道理,所以会继续优化。
|
||||
|
||||
对于你来说也是这样,分布式博大精深,岂是我一个专栏能讲完的?所以,你的学习之旅,也才刚刚开始。让我们一起加油,一起去探寻那个最好的自己。
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user