假设您正在编写一些代码,这些代码将调用具有 REST API 的服务。为了发出请求,您的代码需要知道服务实例的网络位置(IP 地址和端口)。在物理硬件上运行的传统应用程序中,服务实例的网络位置是相对静态的。例如,您的代码可以从偶尔更新的配置文件中读取网络位置。
但是,在现代的基于云的微服务应用程序中,这是一个要解决的难题,如下图所示:
服务实例具有动态分配的网络位置。而且,服务实例集会由于自动缩放,故障和升级而动态更改。因此,您的客户端代码需要使用更复杂的服务发现机制。
客户端查询服务注册表,该服务注册表是可用服务实例的数据库。然后,客户端使用负载平衡算法来选择可用的服务实例之一并发出请求。
服务实例的网络位置在启动时会在服务注册表中注册。实例终止时,将从服务注册表中将其删除。通常使用心跳机制定期刷新服务实例的注册。
什么是 CAP?
在一个分布式系统(指互相连接并共享数据的节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。
CAP 为什么不能同时满足?
因为网络本身无法做到 100% 可靠,有可能出故障,所以分区是一个必然的现象。如果我们选择了 CA 而放弃了 P,那么当发生分区现象时,为了保证 C,系统需要禁止写入,当有写入请求时,系统返回 error(例如,当前系统不允许写入),这又和 A 冲突了,因为 A 要求返回 no error 和 no timeout。因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。
CP - Consistency/Partition Tolerance:
出现网络分区时,往节点 A 插入新数据,但是由于分区故障导致数据无法同步,此时节点 A 和节点 B 数据不一致,为了保证一致性。客户端查询时只能返回 error,违背了 Availability。
AP - Availability/Partition Tolerance:
出现网络分区时,往节点 A 插入新数据,但是由于分区故障导致数据无法同步,此时节点 A 和节点 B 数据不一致,为了保证可用性,客户端查询时可能看到不同的两份数据,违背了 Consistency。
CAP 选择策略
对于涉及钱的交易时,数据的一致性至关重要,因此保 CP 弃 A 应该是最佳选择。 2015 年发生的支付宝光纤被挖断的事件,就导致支付宝就出现了不可用的情况。显然,支付宝当时的处理策略就是,保证了 CP 而牺牲了 A。
而对于其他场景,大多数情况下的做法是选择 AP 而牺牲 C,因为很多情况下不需要太强的一致性(数据始终保持一致),只要满足最终一致性即可。
保 CP 弃 A
如果一个分布式场景需要很强的数据一致性,或者该场景可以容忍系统长时间无响应的情况下,保 CP 弃 A 这个策略就比较适合。
一个保证 CP 而舍弃 A 的分布式系统,一旦发生网络分区会导致数据无法同步情况,就要牺牲系统的可用性,降低用户体验,直到节点数据达到一致后再响应用户。
保证 CP 的系统有很多,典型的有 Redis、HBase、ZooKeeper 等。
在 ZooKeeper 集群中,Leader 节点之外的节点被称为 Follower 节点,Leader 节点会专门负责处理用户的写请求:
当出现网络分区时,如果其中一个分区的节点数大于集群总节点数的一半,那么这个分区可以再选出一个Leader,仍然对用户提供服务,但在选出 Leader 之前,不能正常为用户提供服务;如果形成的分区中,没有一个分区的节点数大于集群总节点数的一半,那么系统不能正常为用户提供服务,必须待网络恢复后,才能正常提供服务。
这种设计方式保证了分区容错性,但牺牲了一定的系统可用性。
CAP 是忽略网络延迟的,理论中的 C 在实践中是不可能完美实现的,在数据复制的过程中,节点 A 和节点 B 的数据并不一致。
保 AP 弃 C
如果一个分布式场景需要很高的可用性,或者说在网络状况不太好的情况下,该场景允许数据暂时不一致,那这种情况下就可以牺牲一定的一致性了。
网络分区出现后,各个节点之间数据无法马上同步,为了保证高可用,分布式系统需要即刻 响应用户的请求。但此时可能某些节点还没有拿到最新数据,只能将本地旧的数据返回给用户,从而导致数据不一致的情况。
适合保证 AP 放弃 C 的场景有很多。比如,很多查询网站、电商系统中的商品查询等,用户体验非常重要,所以大多会保证系统的可用性,而牺牲一定的数据一致性。
目前,采用保 AP 弃 C 的系统也有很多,比如 CoachDB、Eureka、Cassandra、DynamoDB 等。
对比分析:
CP 模型,采用 CP 模型的分布式系统,一旦因为消息丢失、延迟过高发生了网络分区,就影响用户的体验和业务的可用性。因为为了防止数据不一致,集群将拒绝新数据的写入。
AP 模型,采用 AP 模型的分布式系统,实现了服务的高可用。用户访问系统的时候,都能得到响应数据,不会出现响应错误,但当出现分区故障时,相同的读操作,访问不同的节点,得到响应数据可能不一样。
CAP 理论的延伸:Base 理论
BASE 理论是 CAP 理论中的 AP 的延伸,是对互联网大规模分布式系统的实践总结,强调可用性。
它的核心就是基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency),核心思想是即使无法做到强一致性(CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性。
1、服务平台管理端先在 ZooKeeper 中创建一个服务根路径,可以根据接口名命名(例如: /dubbo/com.foo.BarService),在这个路径再创建服务提供方目录与服务调用方目录(例如:providers、consumers),分别用来存储服务提供方的节点信息和服务调用方的节点信息。
2、当服务提供方发起注册时,会在服务提供方目录中创建一个临时节点,节点中存储该服务提供方的注册信息。
创建临时节点是因为临时节点的生命周期与客户端会话相关,所以一旦提供者所在的机器出现故障导致提供者无法提供服务,该临时节点就会自动从 Zookeeper 删除。
3、当服务调用方发起订阅时,则在服务调用方目录中创建一个临时节点,节点中存储该服务调用方的信息,同时服务调用方 watch 该服务的服务提供方目录 (/dubbo/com.foo.BarService/providers)中所有的服务节点数据。
4、当服务提供方目录下有节点数据发生变更时,ZooKeeper 就会通知给发起订阅的服务调用方。
问题一:
ZooKeeper 的一大特点就是强一致性,ZooKeeper 集群的每个节点的数据每次发生更新操作,都会通知其它 ZooKeeper 节点同时执行更新。
当连接到 ZooKeeper 的节点数量特别多,对 ZooKeeper 读写特别频繁,且 ZooKeeper 存储的目录达到一定数量的时候,ZooKeeper 将不再稳定,CPU 持续升高,最终宕机。而宕机之后,由于各业务的节点还在持续发送读写请求,刚一启动,ZooKeeper 就因无法承受瞬间的读写压力,马上宕机。
问题二:
ZooKeeper 无法正确处理服务发现的网络分区。在 ZooKeeper 中,无法达到仲裁数量的分区的节点客户端完全无法与ZooKeeper 及其服务发现机制进行通信。
1、Eureka-Client 在初始化时会将服务实例信息注册到任意一个 Eureka-Server,并且每隔 30 秒发送心跳请求。
2、该 Eureka-Server 会将注册、心跳的请求,批量打包同步到其他 Eureka-Server。
问题一:
订阅端拿到的是服务的全量的地址:这个对于客户端的内存是一个比较大的消耗,特别在多数据中心部署的情况下,某个数据中心的订阅端往往只需要同数据中心的服务提供端即可。
问题二:
客户端采用周期性向服务端主动 pull 服务数据的模式(也就是客户端轮训的方式),这个方式存在实时性不足以及无谓的拉取性能消耗的问题。
问题三:
Eureka 集群的多副本的一致性协议采用类似“异步多写”的 AP 协议,每一个 server 都会把本地接收到的写请求发送给组成集群的其他所有的机器(Eureka 称之为 peer),特别是 hearbeat 报文是周期性持续不断的在 client->server->all peers 之间传送;这样的一致性算法,导致了如下问题
扩展:Eureka 2.0
Eureka 2.0主要就是为了解决上述问题而提出的,主要包含了如下的改进和增强:
注:Nacos 服务信息"推送"本质还是拉模式,具体流程为:
1、Nacos 客户端会循环请求服务端变更的数据,并且超时时间设置为 30s,当配置发生变化时,请求的响应会立即返回;
2、服务端数据变更后,找到具体的客户端请求中的 response,然后直接将结果写入 response 中;
3、Nacos 客户端就能够实时感知到服务端配置发生了变化。
对于服务发现而言,拥有可能包含虚假信息的信息要比根本不拥有任何信息更好,所以个人认为 AP 优于 CP。
在 AP 模式下,如果请求到不准确的服务实例信息,导致请求发送到一个宕机的服务端,只要做好失败重试机制和负载均衡,这次请求能够顺利的进行。
扩展阅读
CP vs AP for service discovery(consul)
Why not use Curator/Zookeeper as a service registry?
Eureka! Why You Shouldn’t Use ZooKeeper for Service Discovery
阿里巴巴为什么不用 ZooKeeper 做服务发现?
参考资料
Service Discovery in a Microservices Architecture
极客时间专栏:RPC实战与核心原理
极客时间专栏:分布式技术原理与算法解析
阿里巴巴服务注册中心产品ConfigServer 10年技术发展回顾