【Zookeeper】为什么需要zk?zk设计猜想?

2022-07-28,,,

1.为什么需要注册中心?

我们先来看看单体架构到微服务架构的变化:

  • 电商场景下的单体架构:
  • 电商场景下的微服务架构下会拆分出许多的服务,如订单服务,卡券服务,库存服务等

1.1 微服务通信手段

常见的服务间通信方式有以下两种:http协议和rpc协议。

手段一:Http(Hyper Text Transfer Protocol:超文本传输协议)

  • OSI七层模型中的应用层网路协议

  • Java中提供的http通信的组件:

    • Apache 的 HTTP Client
    • Square 的 OKHttp
    • Netflix 的 Feign(Spring cloud常用,可读性强)
    • Spring 的 RestTemplate(SpringBoot常用,易用,是对已有组件再封装)

简单来说,RestTemplate 是 Spring 提供的用来访问 REST 服务的客户端,以前我们使用 Apache HttpClient来进行远程调用时,需要写非常多的代码,还需要考虑各种资源回收的问题。而RestTemplate简化了Http服务的通信,我们只需要提供URL,RestTemplate会帮我 们搞定一切。

RestTemplate 需要使用一个实现了 ClientHttpRequestFactory 接口的类为其提供 ClientHttpRequest 实现。而 ClientHttpRequest 则实现封装了组装、发送 HTTP 消息,以及解析响应的的底层细节。 目前(5.1.8.RELEASE)的 RestTemplate 主要有四种 ClientHttpRequestFactory 的实现,它们分别是:

  1. 基于 JDK HttpURLConnection 的 SimpleClientHttpRequestFactory
  2. 基于 Apache HttpComponents Client 的 HttpComponentsClientHttpRequestFactory
  3. 基于 OkHttp 2(OkHttp 最新版本为 3,有较大改动,包名有变动,不和老版本兼容)的 OkHttpClientHttpRequestFactory
  4. 基于 Netty4 的 Netty4ClientHttpRequestFactory

所以,RestTemplate并没有重复造轮子,而是利用了现有的技术,如 JDK 或 Apache HttpClient 、OkHttp等来实现http远程调用。

下面我们就来看看RestTemplate的使用:

// 订单服务...
public class OrderController {
    @Autowried
    RestestTemplateBuilder restTemplateBulider;
    
    @PostMapping("/order")
    public String sayHello() {
        RestTemplate rt = restTemplateBuilder.build();
        // 发送put请求
        rt.put("http://localhost:8081/repo/{1}", null, 10001);
        return "Success";
    }
}
// 库存服务...
Public class RepoController {
    @PutMaping("/repo/{pid}")
    public vodi serverMsg(@PathVariable("pid") String pid) {
        System.out.Println("扣减库存,商品ID:" + pid);
    }
}

手段二:RPC(Remote Position Call:远程过程调用协议)

  • 类似一种自定义API;可以通过Http协议实现,也可以直接通过tcp实现(常用,高效)
  • 常见的Rpc框架有dubbo

虽然现在服务间的调用越来越多地使用了 RPC 和消息队列,但是 HTTP 依然有适合它的场景。

  • RPC 的优势在于高效的网络传输模型(常使用 NIO 来实现),以及针对服务调用场景专门设 计协议和高效的序列化技术。
  • HTTP 的优势在于它的成熟稳定、使用实现简单、被广泛支持、兼容性良好、防火墙友好、 消息的可读性高。所以http协议在开放 API、跨平台的服务间调用、对性能要求不苛刻的场景中有着广泛的使用。

将所有应用模块拆分成单独的服务在带来性能提升的同时,会带来什么问题呢?

1.2 微服务通信带来的问题

有了远程通信以后,我们势必会考虑几个问题 :

  1. 目标服务肯定会做扩容,扩容以后,客户端会带来一些变化
  2. 客户端对于目标服务如何进行负载均衡
  3. 客户端如何维护目标服务的地址信息
  4. 服务端的服务状态变化,如何让客户端进行感知

所以,为了解决以上问题,微服务需要引入服务注册中心,用于实现服务的注册和服务的发现功能,来对所有的服务进行统一管理。常见的注册中心的实现有两种,Dubbo体系中的Zookeeper、Spring Cloud中的Eureka和Consul。

下面我们我们就详细的来看看Zookeeper。

2.zookeeper是干什么的?

Zookeeper 的前世今生:Apache ZooKeeper 是一个高可靠的分布式协调中间件。它是 Google Chubby 的一个开源实现,那么它主要是解决什么问题的呢?那就得先了解Google Chubby Google Chubby是谷歌的一个用来解决分布式一致性问题的组件,同时,也是粗粒度的分布式锁服务

2.1 分布式一致性问题

什么是分布式一致性问题呢?简单来说,就是在一个分布式系统中,有多个节点,每个节点都会提出一个请求,但是在所有节点中只能确定一个请求被通过。而这个通过是需要所有节点达成一致的结果,所以所谓的一致性就是在提出的所有请求中能够选出最终一个确定请求。 并且这个请求选出来以后,所有的节点都要知道。

一致性:在分布式架构中,预期行为与实际行为的一致情况,以实现可预测

一致性可以细分为数据一致性,读写一致性,选举一致性等,但一般简化成以下三类:

数据一致性指的是在leader更新数据后,follower必须保证数据与leader一致;而读写一致性指的是先读后写;还有选举一致性,指的是所有follower最后投票出来的leader必须一致。

  • 强一致性:数据a一旦写入成功,在任意副本任意时刻都能读到a的最新值。也称为原子一致性(Atomic Consistency)线性一致性(Linearizable Consistency。

  • 弱一致性:写入一个数据a成功后,在数据副本上可能读出来,也可能读不出来。不能保证多长时间之后每个副本的数据一定是一致的,其实就是不需要一致。

  • 结果一致性:写入一个数据a成功后,在其他副本有可能读不到a的最新值,但在某个时间窗口之后保证最终能读到。 可以看做弱一致性的一个特例,这里面的重点是这个时间窗口。

一致性问题是分布式最重要问题之一,现在的解决方案有Raft协议,Paxos协议,Zab协议等

注:一个多核CPU也可以认为是一个小型分布式系统,因此一致性也可用于单机

2.2 分布式锁服务

Chubby提供了一种粗粒度的锁服务,chubby是通过创建文件的形式来提供锁的服务

  1. 加锁:server向chubby中创建文件
  2. 加锁成功:创建文件成功

总结:由于谷歌Chubby没有开源,所以雅虎公司基于chubby的思想,开发了一个类似的分布式协调组件Zookeeper,并捐给了Apache,所以Zookeeper的设计之初并不是为了注册中心而设计,注册中心只是它的一个功能而已

3.zookeeper设计猜想

基于Zookeeper本身的一个设计目标,zookeeper主要是解决分布式环境下的服务协调问题 而产生的,我们来猜想一下,如果我们要去设计一个zookeeper,需要满足那些功能呢?

防止单点故障

若所有Zookeeper节点都以单机形式存在,那么宕机后系统就停止服务了,而为了保证高可用,常见解决单点问题的办法就是集群。这个集群需要满足那些功能?

1. 集群中要有主节点和从节点(也就是集群要有角色) 
2. 集群要能做到数据同步,当主节点出现故障时,从节点能够顶替主节点继续工作
3. 主节点挂了以后,从节点如何接替成为主节点? 是人工干预?还是自动选举 

3.1 主从模型

接着上面那个结论再来思考,如果要满足这样的一个高性能集群,我们最直观的想法应该是, 每个节点都能接收到请求,并且每个节点的数据都必须要保持一致。要实现各个节点的数据 一致性,就势必要一个leader节点负责协调和数据同步操作。这个我想大家都知道,如果在这样一个集群中没有leader节点,每个节点都可以接收所有请求,那么这个集群的数据同步的复杂度是非常大。

所以,当客户端请求过来时,需要满足,事务型数据和非事务型数据的分开处理方式,就是 leader 节点可以处理事务和非事务型数据。而follower节点只能处理非事务型数据。原因是, 对于数据变更的操作,应该由一个节点来维护,使得集群数据处理的简化。同时数据需要能 够通过leader进行分发使得数据在集群中各个节点的一致性。

  • 主节点(Leader),Leader服务器是整个zookeeper集群的核心,主要的工作任务有两项
    • 接收事务请求(写请求,增删改)-> 处理 -> 给子节点数据同步
    • 接收非事务请求,并协调各节点
  • 从节点(Follower),Follower角色的主要职责是:
    • 处理非事务请求(读请求,查询),起到负载均衡作用

    • 转发事务请求给Leader -> 参与事务请求的Proposal投票(Leader提案,Follower投票半数以上通过,Leader才能commit数据)-> 待Leader处理完事务请求后,进行数据同步

    • 参与Leader选举的投票

Observer是zookeeper3.3开始引入的一个全新的服务器角色,从字面来理解,该角色充当了观察者的角色。 观察 zookeeper 集群中的最新状态变化并将这些状态变化同步到 observer 服务器上。
Observer 的工作原理与 follower 角色基本一致,而它和 follower 角色唯一的不同在于 observer 不参与任何形式的投票,包括事物请求 Proposal 的投票和 leader 选举的投票。
简单来说,observer服务器只提供非事物请求服务,通常在于不影响集群事物处理能力的前提下提升集群非事物处理的能力.

3.2 数据同步

在分布式系统中,每一 个机器节点虽然都能够明确知道自己进行的事务操作过程是成功和失败,但是却无法直接获取其他分布式节点的操作结果。所以当一个事务操作涉及到跨节点的时候,就需要用到分布式事务,那么如何保证跨节点数据处理一致性?

答:采用 2PC(Two Phase Commitment)协议或者 3PC协议。

关于 2PC 提交

当一个事务涉及多个分布式节点时,为了保证事务处理的ACID特性,就需要引入一个调度者(TM)来统一调度跨节点的执行逻辑,这些被调度的节点被称为(AP)。TM调度这些AP,并最终决定这些的AP处理后,事务是否真正进行提交;因为整个事务在TM处理下分为两个阶段提交,所以叫2PC。

阶段一:提交事务请求(投票)

  1. 事务询问:TM向所有的参与者发送事务内容,询问能否执行事务提交操作,并开始等待各参与者的响应
  2. 执行事务:各个参与者节点执行事务操作,并将Undo和Redo信息记录到事务日志中
  3. 反馈执行结果:如果参与者事务执行成功就返回yes,失败就返回no;向调度者返回结果就类似一个投票过程

阶段二:执行事务提交。根据AP的投票结果(执行结果)最终决定:执行事务 或 中断事务

3.3 选举机制

通常 zookeeper 是由 2n+1 台 server 组成,每个 server 都知道彼此的存在。每个 server 都 维护的内存状态镜像以及持久化存储的事务日志和快照。

对于 2n+1 台 server,只要有 n+1 台(大多数)server可用,整个系统保持可用。我们已经了解到,一个zookeeper集群如果要对外提供可用的服务,那么集群中必须要有过半的机器正常工作并且彼此之间能够正常通 信,基于这个特性,如果向搭建一个能够允许F台机器down掉的集群,那么就要部署2*F+1 台服务器构成的 zookeeper 集群。

之所以要满足这样一个等式,是因为一个节点要成为集群中的 leader,需要有超过及群众过 半数的节点支持,这个涉及到leader选举算法。同时也涉及到事务请求的提交投票

因此 3 台机器构成的 zookeeper 集群,能够在挂掉一台 机器后依然正常工作。一个5台机器集群的服务,能够对2台机器怪调的情况下进行容灾。 如果一台由6 台服务构成的集群,同样只能挂掉2 台机器。因此,5 台和 6台在容灾能力上并没有明显优势,反而增加了网络通信负担。系统启动时,集群中的 server 会选举出一台 server为Leader,其它的就作为follower(这里先不考虑observer角色)。

本文地址:https://blog.csdn.net/weixin_43935927/article/details/109605633

《【Zookeeper】为什么需要zk?zk设计猜想?.doc》

下载本文的Word格式文档,以方便收藏与打印。