简介
从业务架构的角度,服务端主要是实现一个多租户的 Model 层。Model 层本身最重要的是自然体现业务逻辑,它和具体行业的领域问题相关。但服务端程序还是有它很鲜明的特点,有一些和领域无关的业务架构通用问题。比如:网络协议、帐号与授权、RPC 框架、单元测试等等。
服务端程序依赖的基础软件不只是操作系统和编程语言,还依赖负载均衡和存储中间件。负载均衡的最大价值是对客户的访问流量进行调度,存储中间件即数据结构,极大地解放了生产效率,让开发人员可以把精力放在具体的业务需求上。它是服务端程序能够提供高并发访问和 24 小时不间断服务的基础,几乎所有服务端程序扛不住压力,往往都是因为存储没有扛住压力。
通信协议
整体结构
1+N+M 模型
来自《软件架构设计》 将一个请求的处理分为3道工序:监听、IO、业务逻辑处理。worker 线程还可以继续拆分成编解码、业务逻辑计算等环节,进一步提高并发度。请求与请求之间是并行的,一次请求处理的多个环节之间也是并行的。
- 监听线程,负责accept 事件的注册和处理
- io线程,负责每个socket rw事件的注册和实际的socket 读写
- worker线程,纯粹的业务线程,没有socket的读写操作
不同的系统实现方式会有一些差异,比如Tomcat6 的NIO网络模型
业务状态
桌面程序的业务状态是如何表示的?内存中的数据结构。我们在上一章中提到,桌面程序的 Model 层是一棵 DOM 树,根结点通常叫 Document。这棵 DOM 树其实就是桌面程序的业务状态。服务端程序的业务状态如何表示?用内存中的数据结构可以吗?答案当然是不能。如果业务状态在内存中,服务端程序一挂,数据就丢了。某种意义上来说更重要的原则是:坚决不能丢失用户的数据,即他认为已经完成的业务状态。服务端对用户来说是个黑盒,既然用户收到某个 “网络 API 请求” 成功的反馈,那么他会认为这个成功是确认的。
和桌面开发不同,桌面端的数据结构基本上都是基于内存的,实现难度较低。但是在服务端不同。我们每一次的业务状态改变都需要考虑持久化,所以服务端的核心数据结构都是基于外存的。业务服务器往往是无状态的,压力大了新增加一台业务服务器非常容易。但是存储压力大了,并不能简单加一台机器了事,可能涉及数据的重新划分和搬迁工作。这意味着,在服务端实现一个数据结构是非常困难的。我们举一个很简单的例子,在内存中我们实现一个 KV 存储非常容易,很多语言都有 Dictionary 或者 Map 这样的数据结构来做这事。但是,一个服务端的 KV 存储非常非常复杂,绝非一个人花上一天两天就可以干出来。就算干出来了,也需要经过非常庞大的测试案例进行方方面面的验证,才敢投入生产环境。正因为服务端的数据结构实现如此之难,所以对于服务端来说,所有业务需要涉及的数据结构都需要抽象出来,成为一个存储中间件。存储中间件会有多少?这与服务端开发的模型抽象有关。今天没有比较系统性的理论告诉大家,有了这样一些数据结构就完备了。但是从更长远发展的角度来看,我们很可能需要回答这个问题。
服务端案例
客户端访问案例
服务治理
就像桌面的领域特征是强交互,以事件为输入,GUI 为输出一样,服务端的领域特征是大规模的用户请求,以及 24 小时不间断的服务。相比桌面程序而言,服务端程序依赖的基础软件不只是操作系统和编程语言,还多了两类:
- 负载均衡(Load Balance);
- 数据库或其他形式的存储(DB/Storage)。
所以,服务端技术的迭代,虽然一开始沿用了桌面操作系统的整套体系框架,但它正逐步和桌面操作系统分道而行,转向数据中心操作系统(DCOS)之路。服务端技术的迭代,有一些和服务端开发相关,会影响到业务架构。而更多则和业务架构无关,属于服务治理的范畴。服务端开发与服务治理的边界在于,服务端开发致力于设计合适的业务架构来满足用户需求,而服务治理则致力于让服务端程序健康地为客户提供不间断的服务。