序言
当一项大工程需要大量人员共同开发,并保证它们分布在网络中的大量服务器节点能够同时运行,那么随着项目规模的增大、运行时间变长,它必然会受到墨菲定律的无情打击。
Murphy’s Law:Anything that can go wrong will go wrong.
墨菲定律:凡事只要又可能出错,那就一定会出错。
为了得到高质量的软件产品,我们应该把精力更多地集中在提升开发人员能力,还是放在整体流程和架构上。
两者都重要,前者重术,后者重道。前者是开发者个体水平决定,后者由技术决策者水平决定。
架构并不是被“发明”出来的,而是持续进化的结果。
springboot中的单体分层架构可以下载gitclone中的项目参考
git clone https://gitclone.com/github.com/fenixsoft/monolithic_arch_springboot
下载好后可以用docker启动该项目或者直接运行BookStoreApplication,然后在浏览器访问:http://localhost:8080,系统预置了一个用户(user:icyfenix,pw:123456),也可以注册新用户来测试了。注意我这次运行是由于jekenis占用了8080端口,可以修改jekenis的端口或者该项目端口。
运行docker时,输入命令docker version
默认输出docker版本信息,包含客户端和服务端两个部分,下面是客户端部分,参考Docker version 命令
大型单体系统
单体架构是绝大部分软件开发者都学习和实践过的一种软件架构,许多介绍微服务的书籍和技术资料中也常把这种架构风格的应用称作“巨石系统”(Monolithic Application)。
“单体架构”在整个软件架构演进的历史进程里,是出现时间最早、应用范围最广、使用人数最多、统治历史最长的一种架构风格,但“单体”这个名称,却是在微服务开始流行之后才“事后追认”所形成的概念。此前,并没有多少人将“单体”视作一种架构来看待,如果你去查找软件架构的开发资料,可以轻而易举地找出大量以微服务为主题的书籍和文章,却很难找出专门教你如何开发单体系统的任何形式的材料,这一方面体现了单体架构本身的简单性,另一方面,也体现出在相当长的时间尺度里,大家都已经习惯了软件架构就应该是单体这种样子。
剖析单体架构之前,我们有必要先厘清一个概念误区,许多微服务的资料里,单体系统往往是以“反派角色”的身份登场的,譬如著名的微服务入门书《微服务架构设计模式》,第一章的名字就是“逃离单体的地狱”。这些材料所讲的单体系统,其实都是有一个没有明说的隐含定语:“大型的单体系统”。对于小型系统——即由单台机器就足以支撑其良好运行的系统,单体不仅易于开发、易于测试、易于部署,且由于系统中各个功能、模块、方法的调用过程都是进程内调用,不会发生进程间通信(Inter-Process Communication,IPC。广义上讲,可以认为 RPC 属于 IPC 的一种特例,但请注意这里两个“PC”不是同个单词的缩写),因此也是运行效率最高的一种架构风格,完全不应该被贴上“反派角色”的标签,反倒是那些爱赶技术潮流却不顾需求现状的微服务吹捧者更像是个反派。
进程间通讯:Inter-Process Communication,IPC。RPC(Remote Procedure Call,远程过程调用) 属于 IPC 的一种特例。
单体系统的不足,必须基于软件的性能需求超过了单机,软件的开发人员规模明显超过了“2 Pizza Team”范畴的前提下才有讨论的价值,因此,本文后续讨论中所说的单体,均应该是特指“大型的单体系统”,也正因如此,本节中说到“单体是出现最早的架构风格”,与上一节介绍原始分布式时代开篇提到的“使用多个独立的分布式服务共同构建一个更大型系统的设想与实际尝试,反而要比今天大家所了解的大型单体系统出现的时间更早”实际并无矛盾。
可拆分的单体系统
单体系统
Monolith means composed all in one piece. The Monolithic application describes a single-tiered software application in which different components combined into a single program from a single platform.
—— Monolithic Application, Wikipedia
“单体”只是表明系统中主要的过程调用都是进程内调用,不会发生进程间通信,仅此而已。
单体系统在维基百科的定义是“All in One Piece”,翻译成“铁板一块”,其实更接近于自给自足(Self-Contained)的含义。
-
纵向角度
分层架构(Layered Architecture)已经是现在几乎所有的信息系统建设中,都普遍认可、普遍采用的软件设计方法了。无论是单体还是微服务,或者是其他架构风格,都会对代码进行纵向拆分,收到的外部请求会在各层之间,以不同形式的数据结构进行流转传递,在触及到最末端的数据库后依次返回响应。
-
横向角度
单体架构也可以支持按照技术、功能、职责等角度,把软件拆分为各种模块,以便重用和团队管理。
负载均衡器之后,同时部署若干个单体系统的副本,以达到分摊流量压力的效果,那么基于单体架构,也是轻而易举就可以实现的。
非独立的单体
不过,在“拆分”这方面,单体系统的真正缺陷实际上并不在于要如何拆分,而在于拆分之后,它会存在隔离与自治能力上的欠缺。
在单体架构中,所有的代码都运行在同一个进程空间之内,所有模块、方法的调用也都不需要考虑网络分区、对象复制这些麻烦事儿,也不担心因为数据交换而造成性能的损失。可是,在获得了进程内调用的简单、高效这些好处的同时,也就意味着,如果在单体架构中,有任何一部分的代码出现了缺陷,过度消耗进程空间内的公共资源,那所造成的影响就是全局性的、难以隔离的。
一旦架构中出现了内存泄漏、线程爆炸、阻塞、死循环等问题,就都将会影响到整个程序的运行,而不仅仅是某一个功能、模块本身的正常运作;而如果消耗的是某些更高层次的公共资源,比如端口占用过多或者数据库连接池泄漏,还将会波及到整台机器,甚至是集群中其他单体副本的正常工作。
因为所有代码都共享着同一个进程空间,如果代码无法隔离,那也就意味着,我们无法做到单独停止、更新、升级某一部分代码,因为不可能有“停掉半个进程,重启 1/4 个进程”这样不合逻辑的操作。所以,从动态可维护性的角度来说,单体系统也是有所不足的,对于程序升级、修改缺陷这样的工作,我们往往需要制定专门的停机更新计划,而且做灰度发布也相对会更加复杂。
由于隔离能力的缺失,除了会带来难以阻断错误传播、不便于动态更新程序的问题,还会给带来难以技术异构等困难。
技术异构
马丁 · 福勒(Martin Fowler)提出微服务的9 个特征,技术异构就是其中之一。它的意思是说允许系统的每个模块,自由选择不一样的程序语言、不一样的编程框架等技术栈去实现。单体系统的技术栈异构不是一定做不到,比如 JNI 就可以让 Java 混用 C/C++,但是这也是很麻烦的事,是迫不得已下的选择。
从根本上看,单体这种架构风格,潜在的观念是希望系统的每一个部件,甚至每一处代码都尽量可靠,不出、少出错误,致力于构筑一个 7×24 小时不间断的可靠系统。 这种观念在小规模软件上能运作良好,但当系统越来越大的时候,交付一个可靠的单体系统就会变得越来越有挑战性。记得一句话,随着代码量越来越庞大,系统的腐化是必然的。
总的来说单体架构的发展以及其优缺点如下图,
参考链接:
1、https://zhuanlan.zhihu.com/p/575500324
2、《周志明的软件架构课》——极客学院