用 1 个程序实现所有的功能,这是一个单体( monolithic )应用。运行程序,所有的功能都属于同一个进程,它们之间的调用属于进程内通信。这就保证功能模块之间的同步通信,调用速度快。单体应用 可能存在 的问题包括:
- 一个进程集成了所有的功能,意味着它需要更多的系统资源。是否存在能够提供这些资源的主机?用户是否能够或者愿意负担这样的主机?
- 修改或者升级任何功能模块,必须先停止运行整个应用,导致应用暂时不可用。用户是否接受这一点?
把所有的功能划分为 n 个部分,分别用 1 个程序实现,这就是一个多体应用。多体应用既可以部署在单机上,也可以部署在多机上。
借用 Kubernetes 的概念,称单机多体应用为一个 pod 。组成 pod 的多个程序运行在同一台主机上,它们之间的调用属于单机进程间通信,仍然是一种同步通信,调用速度也比较快。单机多体应用 有可能实现 单独升级某一实体,其它实体继续提供服务。但是,把多体应用部署在单机上,仍然需要考虑单机能否提供运行应用所需的系统资源。
把多体应用分别部署在多台独立的主机(节点)上,这是一种分布式应用。在实践中,通常用 TCP/IP 网络连接主机,不同节点之间只能通过消息传递通信。分布式应用不需要提供一个具有超强计算或存储能力的节点,而是把所需的系统资源分布在多个普通节点上。分布式应用有可能提供高可用性和扩展性。但是,分布式应用也引入了一个魔鬼: 异步 。
首先,时钟是异步的。每个节点都有一个物理计时器,通常是一个按照固定频率振荡的石英晶体。晶体在震荡固定次数后会产生一个中断,即一个时钟滴答( clock tick )。节点操作系统的中断服务程序会把存储的时间值增加 1 。但是,不同节点的石英晶体的振荡频率有略微的差异。随着时间的推移,不同i节点的当前时间值也会出现差异,即时钟偏移( clock skew )。这意味着节点的本地时间不应视为真正的物理时间,不能依靠本地时间戳判断全局事件的先后顺序。
其次,计算是异步的。不同的节点并发地( concurrently )执行各自的程序。不同节点的失效和恢复是独立的。这意味着一个节点的进程,不能判断另外一个节点的进程是否已经执行完、是否已经失效。
最后,通信是异步的。传递的消息有可能有延迟,甚至丢失。很难区分网络失效和节点失效。
上述三个异步,可以总结为:
- 计算:进程的执行时间没有上限;
- 通信:消息传递的延迟没有上限;
- 时钟:本地时钟的偏移没有上限。
分布式应用的不同进程需要相互配合和协调,才能够完成指定的任务。这就要求在承认异步的前提下,提供可靠的故障检测机制、可靠的点对点通信和群组通信、可靠的数据一致性(核心点之一是定义逻辑顺序)等。这正是分布式系统研究的核心内容。
再看微服务( microservice )的概念。首先,「微」意味着要拆分所有的功能,且分得相对较小,再实现为对应的程序;其次,「服务」意味着这个程序有多个(种)消费者,这些消费者的编程语言、框架和支撑系统不尽相同。这就要求提供标准化的命名和发现机制、访问协议和交换数据格式。像现在流行的一种实现方式,是以 RESTful 风格 API 形式提供微服务,用 DNS 作为服务命名和发现机制, HTTP(S) 作为访问协议, JSON 作为数据格式。
总之,忘掉 monolithic 和 microservice 吧。我们需要考虑下列问题:
- 要不要拆分功能,拆分的粒度多大?
- 拆分后的不同组件,是部署在单机上还是多机上?
- 其中的一些组件,需要为多个(种)消费者提供服务吗?
</div>