Web应用架构受系统用户量、开发人员组织方式影响严重。过去二十年互联网迅速发展,Web架构也从单体式演进出微服务,背后还有比如 Martin Fowler 提出的理论支撑。虽然每个人都听说过微服务,但是很多人并不太清楚为什么要这么做,应该怎么做,怎么拆。要回答这个问题我认为需要从Web架构的演化历史的高度去理解这些架构设计中的取舍。
首先我们改进系统架构的目的是为了满足系统可靠性、并发量以及快速开发的需求。所有的改进方案都是为了解决这其中一个或多个问题而产生的。
单体结构
单体结构
最开始Web服务器、数据库全部部署在同一台服务器上,这也是最简单的应用架构,通常公司早期项目都采用这种方式。在很长一段时间里单体结构可以满足系统快速开发与并发量的需求。当用户量越来越大,通常会数据库性能会成为系统瓶颈,此时可以将Web业务与数据库部署在不同服务器上,增强数据库服务器的配置并做读写分离等提高系统的吞吐量与可用性。
与此同时也可以将业务系统等价部署在多台服务器上来提高系统吞吐量,但整体上这仍然是一个单体应用。
单体等价部署
随着用户、数据量进一步增大,单体应用的缺点会进一步显露出来,比如:
- 耦合严重、复杂度高、可靠性差 :单体应用越来越来很多业务会耦合在一起,一但某些模块出现Bug会影响整个系统正常运行,业务代码的耦合也会形成开发人员的依赖造成新业务难以推进
- 增加技术债、部署困难效率差
- 系统吞吐量受限、阻碍技术进步 :单体应用难以进一步扩展使系统吞吐量受限,同时单体应用要求使用统一技术平台或解决方案,要想引入新语言或框架会非常困难
拆分
应用规模越来越大,首先遇到瓶颈的可能就是数据库系统,面对数据库压力通常我们可以对数据库做拆分把负载分担到不同的服务器上来解决,通常数据库拆分有两种方案:
- 垂直拆分:对不同的业务系统如账户、搜索、推荐系统使用不同的数据库
- 水平拆分:对于大表,比如十亿百亿级别的,进行多表拆分
数据库水平拆分与业务逻辑耦合紧密,需要具体问题具体分析,通常这是一个非常复杂的问题。后来人们引入 NoSQL、NewSQL 用分布式概念在数据库层屏蔽掉数据库的水平拆分,比如 NoSQL 的 MongoDB Sharding,NewSQL 的 TiDB。
同样的在业务层上我们也可以通过垂直拆分和水平拆分将单体业务拆成不同的服务,服务之间通过约定好的协议通信,以提高人员开发效率,实现多机部署冗余部署来提高系统可用性与吞吐量。
微服务
我们都知道微服务是一种提倡将单一服务拆分成一组小服务、服务之间相互协调、配合,提高开发效率,最终为用户提供价值的思路。说到微服务那么这里面最重要的一个问题就是服务应该怎么拆。微服务作为 SOA(Service Oriented Architecture)思想的一种具体实践我们首先想到的就是按照不同的业务系统做垂直拆分,如下图所示:
SOA垂直拆分
按业务系统对单体应用做垂直拆分,不同的业务线完全可以独立配备产品经历与工程师同步开发维护,将不同业务线解耦出来有不同团队维护。但上图是一种理想情况,各系统拆分力度比较大,系统之间不需要更详细的通信。如果是被拆除出了的子系统之间有大量的数据交互与调用,网关模式便不是一种很好的实践,通常会将各业务子系统接入一个数据总线用 ESB(Enterprise Service Bus)模式来进行数据交互,各子系统与数据总线进行数据交换便需要对子系统做统一管理,这遍有了 服务治理 的概念,用一套统一的保准来处理各子系统的注册、权限、监控等,目前有很多 ESB 开源或闭源的解决方案,这里不再赘述。
垂直拆分将各业务子系统解耦出来,但是每次请求在不同阶段遇到的瓶颈与负载是不一样的,因此我们对可以使用水平拆分的思路对服务进行拆分:
水平拆分
首先用户请求通过http协议到达网关,网关将json数据格式转为protobuf,通过tcp长链接与服务层、数据层通信获取目标数据然后返回给用户。这样拆分加长了用户请求链路时延,但是如果服务全部部署在同一内网,而且使用protobuf格式通信那么这个时延在几十毫秒内是完全可以接受的。业务层与数据层完全解耦便可以轻松将不同类型的服务进入冗余部署,同时在不动业务层的同时修改它的数据存储方式。
如果我们对系统即做垂直拆分也做水分拆分,那么就有了微服务的样子,
水平拆分
每级服务只能调用比他低级别的服务,如果搜索服务层只能掉账户接口层服务而不能调账户服务层接口,这样可以用来避免服务A调用服务B,而服务B同时又调用了服务A的循环调用问题。但是这样的拆分粒度仍然不够的,比如搜索系统和推荐系统都要调用账户系统的一些基础查询、修改逻辑,那么需要在搜索与推荐的服务层两次实现同样的代码吗,这样显然是不合理了,任何不能复用的设计显然都是有问题的。如果通过编写SDK库提供Jar包的模式去实现这个功能呢?,显然也存在问题比如推荐系统是Python实现,而搜索系统是Java实现的呢?所以这里我们将每个子系统可共用代码部分也单独抽取出来作为一个服务。
水平拆分2
这样拆分后的系统可以灵活部署,独立开发,并且各模块服务使用的技术栈相对独立不受限制。但是同时拆分也将系统的网络拓扑便的复杂,运维负担加重,服务间的依赖使得服务接口的调整成本非常高。服务增多的同时对服务治理的要求也更高,需要专门做服务的发现、注册、鉴权、监控等系统功能。