分布式系统设计新手入门---1,微服务的拆分

在我的文章Web Services的分布式方法中介绍了分布式设计的方法。但读者反映太过学术化而无法理解。促使我开始这个系列文章的创作,以方便新手能够在实践中使用分布式技术。虽然分布式是一个历史悠久的概念,最早的分布式系统出现在20世纪60年代末推出的ARPANET。但时到今日分布式系统设计都对新手非常的不友好。也可能你学习过大量的分布式的理论,但面对复杂的软件系统仍然也感到束手无策。那么希望这个系列的文章能帮助你重新梳理分布式的知识,建立正确设计分布式系统的方法论。首先分布式的入门要求并不高,需要你是个有一定开发经验的软件工程师,了解基本的并发编程知识。并发编程是分布式设计的基础。你会发现并发编程的知识在分布式系统设计中被经常的使用。但请不要混淆并发编程和分布式系统设计,这是两个完全不同的概念。这里的并发编程特指使用多线程开发软件系统的方法。分布式系统设计是比并发编程更高级的软件系统设计开发行为。在本文中我们先快速的描述一个典型的服务,以及如何一步一步的拆分这个服务为微服务。通过对这个典型的案例,介绍拆分服务的基本方法。然后我们再逐步讨论为什么使用这个方法论,以及这个方法论的使用条件和原理。

当我们依据产品定义开发软件系统时,随着用户访问数量的增加,单台服务器硬件的计算和存储能力将无法满足产品需求时。我们就会试图拆分软件系统到不同的硬件服务器中。以增加系统整体计算和存储能力的行为称为对软件系统的分布式操作。微服务的概念就是由此产生。将一个服务拆分为多个更小的服务,从而可以将服务放入到更多的硬件中。或者将不经常使用的服务合并到一个硬件。通过增加或减少系统硬件,调节系统整体的承载能力。既然是拆分服务那么首先要了解服务是如何构成的。一个典型的Web services是一个能够处理多个http请求的服务端软件系统。例如在spring boot下每个http请求被放入到controller目录里面。在nodejs的express框架下被放在routes里面。而在更早以前对服务的定义是由能够被消息触发的任务所构成的软件系统。

我们知道了服务是由多个任务构成的,这些任务分别属于不同的产品功能。虽然他们都被放在同一个服务内,但是彼此间有的关系较为密切,有的则看起来毫无关系。因为产品设计的原因这些任务通常没有独立的和清晰的描述。一个服务内由少则10个多则上万个任务组成。即使对于项目开发人员来说任务也太多了。在大型项目里一个开发人员一天就可以开发1个甚至更多个任务。当任务多到难以理解的时候,人们引入了另一个概念“产品功能”来帮助划分任务的类别。这里首先要抛弃产品功能的概念,因为产品功能的划分不属于软件系统的范围。原因是计算机只能识别任务而不能识别产品功能,产品功能仅仅是帮助人们在软件开发中记忆及沟通交流的辅助工具,不能作微服务拆分的标准。

进行微服务拆分的第一步是消除任务之间的时序和数据的耦合。将所有的数据全部剥离放入内存数据库,做到服务的无状态化。首先在任务或服务内保存数据是非常危险的行为。服务的崩溃,硬件宕机都会引发数据丢失或混乱。任务私有数据与私自交换数据将会导致任务的时序问题,即任务的执行顺序产生依赖关系。例如,购买商品的任务直接将数据传递并触发支付的任务,并等待支付任务的完成,就是一种任务之间的时序依赖关系。消除这种互相依赖的耦合关系最简单直接的方法就是将所有数据都放入内存数据库。这种消除耦合关系的方法我称为“AP”方法,即“服务的可用化分区或操作”。将数据都放入内存数据库之后,任务的执行过程就变为:1,接受消息触发。2,读取内存数据库。3,执行数据处理逻辑。4,将处理完成的数据写回内存数据库。

在完成上述准备工作之后就可以开始准备拆分服务了。因为每个任务的数据都是从内存数据库中读取的。这里你要把每个任务各自单独放在一个文件内。在使用搜索工具“grep”或“Search and Replace”搜索关键字“SET”就会得到每个任务的写入数据集合。在并发编程中我们学习过如果两个线程同时写入数据就会产生冲突错误。这个问题在分布式系统中一样会产生。如果把两个有写入数据交集的任务分布到不同的服务器内,就会产生写入数据互相覆盖的冲突问题。这种两个任务间有写入交集的状况称为有原子性关系的任务。所谓原子性指的就是并发编程上的互斥锁。在并发编程中如果遇到有原子性需求的状况,我们需要创建一个互斥锁来保护数据的写入或读取正确。而在分布式系统中服务的容器本身就具备原子性。单线程的服务器容器,例如单线程的nginx,tomcat或web service都具有天生的原子性。也就是说这些容器本身就是互斥锁或分布式锁。任务从内存数据库读取和写入数据的过程也具有了天然的事务性。在任务计算过程中不会存储到内存数据库,只有任务计算成功后数据被提交到内存数据库才算执行成功。其间如果任务执行失败没有提交数据到内存数据库也不会污染或损坏数据。

很显然具有原子性关系的任务不能拆分到不同的服务器。而没有原子性关系的任务可以拆分到不同的服务器。例如我们有任务“A,B,C”。分别写入数据“A{pants, skirt, coat},B{Jeans,coat},C{hat , glove}”。显然因为A和B都需要写入数据“coat”所以任务AB具有原子关系只能放在相同的服务器容器内。而任务C与其他的任务没有原子关系所以不需要放在相同的容器内。进而我们可以将服务拆分为两部分。

这个方法的神奇之处在于,其实你并不需要真正的把工程文件拆分两个不同的部分。只要依据写入数据的不同,在路由器对请求进行分流就可以了。

我们将依据写入数据集的原子关系对任务进行分类并拆分的方法称为RP方法。服务在这里像被撕裂开但是仍然互相链接,服务间维持着一种奇妙的关系。虽然在《人月神化》讲述了不存在银弹,但AP&RP方法确实让我们用简单的方法论实现的对微服务的拆分。我想这基于如下几个方面,第一,AP&RP方法并没有试图去解决产品功能实现的问题。第二,AP&RP方法首先是将任务的时序性转为了数据性。然后通过对数据性的划分实现了系统分布。第三,AP&RP方法指出了服务容器是一个由多个潜在属性组成的复合体,其包括了原子性,事务等等。在分布式设计中要充分考虑这些潜在的复合属性所带来的影响。依据并发编程的经验,凭空创建互斥锁或者分布式锁会使问题进一步的复杂化。到这里我们介绍了基本的分布式知识和微服务拆分的方法。如果您有什么问题可以提出来,在后续的文章中我们进一步的讨论。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券