引言
我们知道,分布式系统就是将具有独立计算能力的系统单元,部署在不同的机器上。那么,如何有效的管理这些机器之间的协同工作,就是一个很大的难题。目前,大体有两种典型的分布式系统架构:集中式与非集中式。
集中式架构
我们可以将分布式系统中的每一个节点比作操作系统中进程或者线程的概念。操作系统诞生的原因之一就是为了解决如何高效、合理的运行用户的多个任务请求。所以,操作系统对我们的任务请求抽象成一个个进程,然后将我们的CPU、内存等资源公平、合理的分配给每一个进程;并且为了最大化硬件资源的使用,操作系统提供了多种进程调度算法。我们看到,操作系统类似所有进程的“老大”,我们在分布式系统中也同样需要这样一个角色,用来协调和管理集群中的每一个节点,我们它称为 “Master” ,而把它管理的小弟们称为 “Slave”或者"worker" 。我们把它称为集中式架构。注意,这里的集中式并不代表我们传统的将所有服务或者数据部署到一台机器上的那种单机架构。集中式架构如图所示:
这种架构的应用场景有很多。其中一种表现形式是 MySQL 、 Redis 的读写分离策略,主负责写,从负责读, master 定期将数据同步给 slave ;而另外一种表现形式则是,系统内所有的业务也先由 master 处理,多个 slave 与 master 连接,并将自己的信息汇报给 master ,由 master 统一进行资源和任务调度并存储集群节点的状态,然后 master 根据这些信息,将任务下达给 slave ;slave 执行任务,并将结果反馈给 master 。比如 nginx 与 php-fpm 的 master-worker 机制,以及集群中的 Borg 、Kubernetes 的集群管理机制都是这样。上一篇文章中的分布式互斥中的协调者也是一种实现方式。也是下面我们以 Kubernetes 为例,看一下集中式架构的一种代表实现。
集中式架构的代表 — Kubernetes
Kubernetes 是用于自动部署、扩展和管理容器化应用程序的开源系统。在集群的节点上运行的容器化应用,可以进行自动化容器操作,包括自动化部署、调度和在节点间弹性伸缩等,大大降低了服务运维的成本。我们可以用几个典型的场景来阐述一下 Kubernetes 的作用:
- 随机关掉一台机器,看你的服务能否正常(调度)
- 减少的应用实例能否自动迁移并恢复到其他节点
- 服务能否随着流量进行自动伸缩
Kubernetes为了解决以上容器化应用的运维的难题而生,是典型的集中式结构。一个 Kubernetes 集群,主要由 Master 节点和 Worker 节点组成。
master节点
Master 节点由 API Server、Scheduler、Cluster State Store 和 Control Manger Server 四部分组成。Master 负责对集群进行调度管理,它是整个集群的大脑,类似我们刚才说的操作系统,负责整个容器集群的调度与生命周期管理。
- API Server:提供和外部交互的接口
- Scheduler:根据容器需要的资源、以及当前Worker节点所在机器上的资源信息,自动为容器选择合适的节点机器。
- Cluster State Store:集群状态存储,默认采用 etcd 。etcd 是一个分布式 key-value 存储,主要用来做共享配置和服务发现。
- Control Manager:用于执行大部分的集群层次的功能,比如执行生命周期功能(命名空间创建和生命周期、事件垃圾收集、已终止垃圾收集、级联删除垃圾收集等)和 API业务逻辑。
worker节点
类似我们刚才提到的操作系统进程,worker 节点内部运行了多个包含业务应用的容器,由master统一调度。这相当于我们操作系统中运行的多个任务程序,由操作系统统一调度。worker运行在从节点服务器,主要包括 kubelet 和 kube-proxy 这两大核心组件:
- kubelet:用于通过命令行与 API Server 进行交互,根据接收到的请求对 Worker 节点进行相应的操作。也就是说,通过与 API Server 进行通信,接收 Master 节点根据调度策略发出的请求或命令,在 Worker 节点上管控容器(Pod),并管理容器的运行状态(比如,重新启动出现故障的 Pod)等。
Pod 是 Kubernetes 的最小工作单元,每个 Pod 包含一个或多个容器。通过 Pod 机制,Kubernetes 实现了多个容器的协作,能够有效避免将太多功能集中到单一容器镜像,还可以向外扩展跨越多个 Pods 实现初步部署,且相关部署可随时进行规模伸缩。
- kube-proxy:具有相同服务的一组 Pod 可抽象为一个Service。它负责为 Service 下的多个 Pod 创建代理服务,实现了请求到 Pod 的路由和转发。并且在有多个副本的情况下,kube-proxy会实现负载均衡。
Service 用于将具备类似功能的多个 Pod 整合为一组,可轻松进行配置以实现其可发现性、可观察性、横向扩展以及负载均衡。 kube-proxy 就实现了这个机制。Kubernetes的整体架构图如下:
集中式架构的优劣
我们通过以上的案例可以看到,我们只需要 master 与各 slave 节点之间通信即可,各 slave 节点之间并不需要通信,这大大降低了网络通信的成本,且部署难度非常之简单。但是,集中式架构中心服务器性能要求很高,而且存在单点瓶颈和单点故障问题。一旦 master 挂掉,整个集群就会对外不可用。双 master 就能够解决这个问题。一旦主 master 挂掉,集群就会触发重新选主的机制,备 master 升为主 master。那么,有没有其他解决问题的方案呢?那就是非集中式架构。
非集中式架构
在非集中式结构中,服务的执行和数据的存储,被分散到不同的服务器集群节点,集群节点之间通过消息传递进行通信和协调。上一篇文章中,分布式互斥问题的解决方案之一:民主式协商算法,也是非集中式架构的一种体现:
非集中式架构的最大特点就是“众生平等”,没有老大这一说。节点与节点之间的地位相同,没有 master 和 slave 之分。下面我们以Redis Cluster 为例,看一下非集中式架构的典型实现:
非集中式架构的代表 — Redis Cluster
Redis 是一个开源的高性能分布式 key-value 数据库,它的数据可分片存储在不同的 Redis 节点上,来对外提供高性能与高可用性,而提供这项能力的就是 Redis 集群。Redis 提供了三种集群方案:主从、基于哨兵的主从、以及 Redis Cluster。主从机制就是我们刚讲过的集中式架构,而哨兵则是增加了对主从节点的监控,而最后面的Redis Cluster 则是典型的非集中式架构。
由于Redis Cluster中的节点对外提供的是数据存储服务,所以在设计时,需要考虑数据的分片存储以及可靠性这两个问题。
数据存储
为了解决数据分片存储的问题,Redis Cluster 中不存在 master 节点,是典型的去中心化结构,每个节点均可与其他节点通信。所有节点均可负责存储数据、记录集群的状态(包括键值到正确节点的映射),客户端可以访问或连接到任一节点上,而非集中式架构中仅仅能够访问 master 节点。集群节点同样能自动发现其他节点,检测故障的节点,并在需要的时候在从节点中推选出主节点。Redis 集群的架构图如下所示:
Redis Cluster 引入了哈希槽的概念将数据分散到多个节点中。Redis 集群内置了 16384 个哈希槽,每个节点负责一部分哈希槽。当客户端要存储一个数据或对象时,对该对象的 key 通过 CRC16 校验后对 16384 取模,也就是 HASH_SLOT = CRC16(key) mod 16384 来决定哈希槽,从而确定存储在哪个节点上。
举个例子,当前集群有 3 个节点,那么节点1包含 0 到 5460(图中不应该是5760) 号哈希槽,节点 B 包含 5416 到 10922 号哈希槽,节点 C 包含 10923 到 16383 号哈希槽:
那么问题来了,如果某个请求随机打到某个集群节点上,而请求需要的数据并不在这个节点上,应该怎么办呢?与节点取余和一致性哈希分区不同,哈希槽是服务端分区。客户端可以将数据提交到任意一个redis cluster节点上,如果存储该数据的槽不在这个节点上,则返回给客户端目标节点信息,告知客户端应该转而向目标节点提交数据即可:
所以我们可以看到,每一个节点上的数据都不会与其他节点上的数据重复,数据只有一份。节点与节点之间的数据是没有交集的,这样一来,Redis 将对数据的读写操作分摊到了多个节点上,提高了读写并发能力。
可靠性
但是这样仍然有一个问题,从全局上看,数据只会存一份,那么必然会存在数据的可靠性的问题。所以我们在每个节点内部,仍会采用 master - slave 1主1备的机制,如果某个节点挂掉了,仍然可以替补上阵。也就是说,每台服务器上都运行两个 Redis服务,分别为主备,主故障后,备升主。所以,我们可以看到,集中式架构与非集中式架构往往是可以搭配使用的,在合适的场景下,使用合适的架构即可。
非集中式架构的优劣
在非集中式架构中,每个节点之间都要互相知晓对方的信息与状态。而对于外部请求来说,我们可以将请求打到任意一个节点上。这样一来,相比于集中式架构,非集中式架构在解决了单点瓶颈和单点故障问题的同时,还提升了系统的并发度,比较适合大规模集群的管理。但是相比集中式来说,维护成本与部署复杂度较高。
下期预告
【分布式系统遨游】分布式事务
关注我们
欢迎对本系列文章感兴趣的读者订阅我们的公众号,关注博主下次不迷路~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。