zookeeper-笔记-大纲

zookeeper-笔记-大纲,第1张

一、什么是zookeeper

1、zookeeper是一个分布式协同服务

什么是分布式协调服务:主要解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某种临界资源,防止造成脏数据的后果。
什么是多个进程,不是多个线程,进程就是涉及到多个应用程序,比如一个docker就是一个进程。一个服务多个实例,就是多个进程。
例如:三个实例,去抢购库存服务,当三个实例分别去抢购5个库存,一共需要15件,那么库存不够,这个需要保证同步进行,不然就会超卖。

那么需要保障同步进行,就是协调服务,分布式协调服务的本质就是分布式锁。让他们有序的去访问某种临界资源。像上面的库存5就是临界资源,让实例1先取访问,完了之后实例2再去访问。
单体应用就没有这样的问题,因为可以使用同步代码块,这种就是控制线程。

2、哪些技术栈用了Zookeeper
1、HBase:保障集群只有一个Master
2、Kafka:集群成员管理,Controller节点选举

3、Zookeeper常用功能
1、配置管理
2、分布式锁
3、DNS服务
4、成员管理,比如人员分派
缺点:不适合存储大量数据

4、Zookeeper数据模型

层次模型,比如文件系统,层次模型和key-value模型是两种主流的数据模型。 层次模型也叫:data tree
每个节点叫Znode\n节点可以保存数据,每个节点都有一个版本。(文件系统,如果是文件夹是不能带数据的)
data tree提供了一套完整的API接口,用来增删改查各个节点。

为什么使用层次模型:
1、便以表达数据之间层次关系
2、方便给不同的应用分配独立的命名空间

5、Znode分类
分为持久性和临时性
1、持久性的znode:Zookeeper集群发生宕机也不会丢失
2、临时性的Znode:client在指定的时间内没有给Zookeeper集群发消息,节点就会消失

Znode也可以是顺序性的,这个单调递增整数是Znode名字的后缀。
3、持久顺序性
4、临时顺序性

平时用的比较多的也就以上四种类型。

6、ZK加锁的原理:就是通过创建一个临时的节点,只要不退出,那么就会一直占有这个锁,
其他节点如果也创建这个同名的临时节点就会失败,其他节点可以选择监听这个名字一样的零时节点状态,如果释放了就会返回一个状态,就表示锁持有者释放了,其他节点就可以共同去竞争这个锁。

7、Master-worker

1、一个Master,多个worker。master监听worker的状态,并为worker分配任务。

2、除了处于active状态的master,还有多个bakcup master,如果active master失败,backup master很快进入active状态

3、master实时监控worker状态,能够及时收到worker成员变化的通知,如果有变化,通常进行任务的重新分配

8、Zookeeper实现master-worker

1、创建一个临时Znode节点,表示master,创建成功表示在行使master的职能,进入active状态,也就是active-master
失败就否则进入backup-master状态,为什么会失败,一定是有其他实例已经创建了这个临时节点,抢占了锁,进入backup状态的实例,就可以通过watch机制监控active-master
如果active-master被删除,就会通知backup-master,backup-master需要在重行创建master去重新抢占master

2、worker的创建:通过在/workers下面创建临时节点来加入集群,注意是在/workers下面创建临时znode节点,/workers是永久的znode

3、处在active状态的master会通过watch机制监控/workers下面的znode列表,来实时获取worker成员的变化

九、zookeeper总体架构

zookeeper客户端库负责和zookeeper集群的交互

1、zookeeper集群可以有2种模式:standalone模式和quorum模式
standalone模式就是单节点模式、quorum就是多节点模式

2、Quorum模式
Zookeeper集群有三个及以上节点,其中只有一个leader节点,其他都是follower节点

leader节点处理读写请求,follower只处理读请求,follwer节点在收到写请求时,会把写请求转发给leader来处理。

3、Session
zookeeper客户端和zookeeper集群中的某一个节点,建立会话,就会创建一个session,session是有效时间的

服务端关闭会话:如果在timeout时间内没有收到客户端的消息,zookeeper服务端节点会关闭session
客户端关闭会话:客户端主动关闭session

注意:如果zookeeper客户端链接zookeeper出错,会自动和zookeeper服务端的其他节点建立连接。

十、zookeeper类

看先构造方法Zookeeper(connectString,sessionTimeout,watcher)

说下参数:
1、connectString:使用逗号分隔的列表,每个ZooKeeper节点是一个host:port对,host是机器名或者IP地址,port是ZooKeeper节点使用的端口号。客户端连接zookeeper集群时,会任意选取 connectString中的一个节点建立连接
格式:

2、sessionTimeout:session timeout 时间
3、watcher: 用于接收到来自ZooKeeper集群的所有事件,zookeeper集群会有哪些事件,zookeeper客户端连接进来的事件、客户端断开的事件、zookeeper服务节点新增和端口的事件

常用的API

1、create(path, data, flags): 创建一个给定路径的znode,并在znode保存data中的数据,flags指定znode的类型,是临时还是永久

2、delete(path, version):如果给定path上的znode的版本和给定的version匹配,删除znode,只有和参数的版本号一致才会执行删除,有点类似于cas方法。

3、exists(path, watch):判断给定 path 上的 znode 是否存在,并在znode设置一个watch

4、getData(path, watch):返回给定 path 上的 znode 数据,并在znode设置一个watch,设置一个watch,就是监听这个znode

5、setData(path, data, version):如果给定 path 上的 znode 的版本和给定的version匹配,设置znode数据

6、getChildren(path, watch):返回给定path上的znode的孩子znode名字,并在znode设置一个watch

7、sync(path):把客户端session连接节点和leader节点进行同步,客户端会随机连接集群中的一个数据,这个和客户端连接的节点如果不是leader节点,那么就会存在数据不一致的问题。

注意:上面只是部分API,还有很多没有列出来,下面对API进行一个简单的分类,读取数据的API、更新数据的API、同步异步API

1、读取znode数据的API,都可以设置一个watch用来监听znode的变化
2、更新数据的API,可以通过version版本号来作为更新条件,也可以不传版本号更新。
3、所有的方法都有同步和异步两个版本,如果是异步的,会把请求放在客户端的请求队列,然后马上返回,可以通过callback来接受服务端的响应

所有同步API两个异常
1、KeeperException: 表示ZooKeeper服务端出错。

注意:KeeperException异常有个子类ConnectionLossException:表示客户端和当前连接的ZooKeeper节点断开了连接,那么就会有两种情况,客户端的请求还没zookeeper服务端,网络断开了,或者是请求已经到了服务端,且已经执行了,然后节点挂了,这个时候,客户端会自动连接到其他的服务端节点上,所以在新的节点上,要判断一下,上一个请求是否已经被执行了。

2、InterruptedException:表示方法被中断了。我们可以使用 Thread.interrupt()来中断API的执行。

watch的作用,当我们监听一个节点时,我们就可以不需要轮训查询结果

条件更行的流程:

二、使用zookeeper设计一个队列

设计思路:
1、先创建一个/queue永久节点

2、在/queue节点下面创建n个节点,这n个永久节点都是queue的直接子节点,并且是有序

3、名字都是有相同前缀+数字n依次递增,数字越小,表示位置越靠前,数字越大表示位置越靠后

实践:

1、offer方法:在/queue下面创建一个顺序znode。因为znode的后缀数字是/queue里面,现有znode最大后缀数字加1,所以该znode对应的队列元素处于队尾
2、element方法:返回队列的队头元素,如果为空则抛出NoSuchElementException异常。
3、remove方法:删除队头的元素,并且返回。remove和element代码和类似,只是多了一个delete *** 作。

remove方法:执行大致逻辑

三、使用zookeeper的队列实现分布式锁

在/lock下面的znode 数字最小的表示是锁的持有者。

队列中的其他节点,watch 锁的持有者节点,收到通知后,各自判断自己是队列中最小的节点,如果是那就表示抢占锁成功。

但是上面那种情况会存在一个问题:就是羊群效应

所有的锁请求者都 watch锁持有者,当代表锁请求者的 znode 被删除以后,所有的锁请求者都会通知到,但是只有一个锁请求者能拿到锁。这就是羊群效应。为了避免羊群效应,每个锁请求者 watch它前面的锁请求者。每次锁被释放,只会有一个锁请求者会被通知到。这样做还让锁的分配具有公平性,锁定的分配遵循先到先得的原则。

zookeeper集群leader的选举


和分布式的锁的实现很类似,都是通过队列,最先进来的,成为leader

四、Zookeeper Observer实现跨区域部署

1、在没有Observer时,zookeeper是如何处理写请求的:

有几个点:

  1. follower收到写请求,会转给leader来处理
  2. leader节点通知所有follower节点需要更行数据
  3. leader收到follower节点的accept后,向其他follower节点发送commit请求

2、在有Observer时,zookeeper是如何处理写请求的:

先说下什么是Observer

Observer和ZooKeeper 机器其他节点唯一的交互是接收来自 leader 的 inform 消息,更新自己的本地存储,不参与提交和选举的投票过程。

Observer 应用场景 - 读性能提升 Observer 和 ZooKeeper 机器其他节点唯一的交互是接收来自 leader 的
inform 消息,更新 自己的本地存储,不参与提交和选举的投票过程。因此可以通过往集群里面添加 Observer 节点来提高整个集群的读性能。

所以再看上面图,省去了leader给follower节点的propose、follower给leader的accept的2个 *** 作

Observer 应用场景 - 跨数据中心部署

我们需要部署一个北京和香港两地都可以使用的 ZooKeeper 服务。我们要求北京和香港的客户端的读请求的延迟都低。因此,我们需要在北京和香港都部署 ZooKeeper 节点。我们假设 leader 节点在北京。那么每个写请求要涉及leader 和每个香港 follower 节点之间的 propose 、ack 和 commit三个跨区域消息。解决的方案是把香港的节点都设置成 observer 。 上面提的 propose 、ack 和 commit 消息三个消息就变成了 inform 一个跨区域消息消息。

3、如何配置Observer

在节点的后面添加一个:observer

小结:

  1. 和读 *** 作相比,写 *** 作更耗时。而且对容错的要求越高,越要有更多的节点,写 *** 作越耗时(因为写 *** 作必须由leader来执行,还需要收到大多数节点的响应)。
  2. 但是想要保证linearizable的写 *** 作数据一致性和高容错,据我所知也没有其他更好的办法。
  3. ZooKeeper最佳的应用场景是写少读多的场景。
五、服务注册与发现API

服务注册与发现要提供的功能有以下几点
1、服务注册
2、服务实例的获取
3、服务变化的通知机制

Curator有一个扩展叫作curator-x-discovery ,它就是基于ZooKeeper实现了服务发现

curator-x-discovery设计

1、使用一个 basePath 作为整个服务发现的根目录。在这个根目录下是各个服务的的目录。服务目录下面是服务实例,也就“两层” 的层级结构
2、服务实例对应的znode节点可以根据需要设置成持久性、临时性和顺序性。如果服务断掉了不能用,那么就要设计成临时节点

下面介绍三个重要的接口

1、ServiceDiscovery : 服务注册,其实他包含注册和发现功能。
2、ServiceProvider : 在服务cache之上支持服务发现 *** 作,封装了一些服务发现策略
3、ServiceCache : 服务cache,本地保存服务实例

通过ServiceDiscovery可以得到ServiceProvider和ServiceCache

ServiceInstance

用来表示服务的具体实例,包含这个实例具体的IP+端口+ID等信息,还提供一个payload成员让用户存自定义的信息。

疑问:这是表示具体某个服务的所有实例,还是某个服务的某个具体实例?答案:是后者

ServiceDiscovery

服务注册与发现的接口,可以创建多个ServiceProvider和多个ServiceCache

ServiceProvider

服务发现的接口,它封装了ProviderStraegy和InstanceProvider,后面会继续介绍该接口的实现类

ProviderStraegy

获取服务的的某个实例的策略,提供了三种策略:轮询、随机和sticky

InstanceProvider的数据来自一个服务Cache 。服务cache是ZooKeeper数据的一个本地cache ,服务cache里面的数据可能会比ZooKeeper里面的数据旧一些。

疑问:InstanceProvider的缓存什么时候更新

noteError提供了一个让服务使用者把服务使用情况反馈给 ServiceProvider 的机制。

服务发现和注册交互流程:

1、ServiceDiscovery 提供了服务注册和发现,其实是对znode的增删改查的 *** 作,比如服务发现方法其实是znode的读取 *** 作。同时它也是最核心的类,所有的服务发现 *** 作都要从这个类开始。

2、服务Cache会接受来自ZooKeeper的更新通知,也就会watch事件,然后在读取服务信息(也就是读取znode信息)

六、ZooKeeper 实现服务发现代码解读

NodeCache

NodeCache是curator包下的类,该类的作用如下

  1. 通过本地内存,来缓存服务上的某个实例数据。NodeCache通过Zookeeper的watch事件,来监听服务上面的变动,比如服务的znode里面有update、create、delete,在通过watch事件来更新本地的缓存数据。
  2. 用户如果想监听NodeCache缓存的变动,可以在Node Cache上面注册一个listener来获取cache更新的通知。

场景:因为znode有watch事件,一个zookeeper客户端最多只会收到一次通知,如果多个业务方需要监听这个事件,又不想开启多个zookeeper客户端,其实可以通过NodeCache来实现,向里面添加listener

PathCache

PathCache和NodeCache一样,原理其实和NodeCache差不多,不同之处是在于PathCache缓存一个znode目录下所有子节点。

container节点

1、这是一种新引入的znode,也可以下挂子节点。当一个container节点的所有子节点被删除之后,ZooKeeper会删除掉这个container节点。
2、前面我们说到的 《服务发现的base path节点》和《服务节点》就是containe类型的节点。

疑问:

containe节点是不是也分临时节点和永久节点?

ServiceDiscoveryImpl

该类包含服务注册与发现的实现逻辑,代码太多,挑下面2个功能重点看下
1、服务注册
2、服务发现
3、类包含哪些属性

1、先看属性

private final ConcurrentMap<String, Entry<T>> services = Maps.newConcurrentMap();
//注意属性services,他的key是服务实例的Id,value是一个Entry,下面看下Entry的数据结构
private static class Entry<T>{
    private volatile ServiceInstance<T> service;
    private volatile NodeCache cache;

    private Entry(ServiceInstance<T> service){
        this.service = service;
    }
}

ServiceInstance前面说了,是一个服务的某一个实例的对象,只能表示一个具体服务实例。
所以需要注意:services里面的Map存的是当前basePath下面的所有服务的所有实例

2、服务注册

@Override
public void registerService(ServiceInstance<T> service) throws Exception {

    Entry<T> newEntry = new Entry<T>(service);
    //1、判断当前service map是否包含这个实例,注意key是实例的Id
    Entry<T> oldEntry = services.putIfAbsent(service.getId(), newEntry);
    Entry<T> useEntry = (oldEntry != null) ? oldEntry : newEntry;
    synchronized(useEntry){
        if ( useEntry == newEntry ){
        	//2、如果是一个Service map里面没有的实例,那就给这个实例创建一个NodeCache
            useEntry.cache = makeNodeCache(service);
        }
        //3、添加实例到znode里面去
        internalRegisterService(service);
        //4、看下internalRegisterService的具体实现逻辑
    }
}

3、发现服务:

//获取所有实例名称
@Override
public Collection<String> queryForNames() throws Exception {
    List<String> names = client.getChildren().forPath(basePath);
    return ImmutableList.copyOf(names);
}
//下面是获取某个服务的所有实例

ServiceProviderImpl

主要是用于服务的发现,里面最重要的方法就是getInstance() 和 getAllInstances()
前者获取某一个实例,后者是获取全部实例

属性
private final InstanceProvider<T> instanceProvider;
private final ProviderStrategy<T> providerStrategy;

@Override
public Collection<ServiceInstance<T>> getAllInstances() throws Exception {
    return instanceProvider.getInstances();
}

@Override
public ServiceInstance<T> getInstance() throws Exception {
	//通过providerStrategy来获取某一个实例
    return providerStrategy.getInstance(instanceProvider);
}

疑问:

getInstance方法用来获取某一个实例,那到底获取到是哪个实例呢?

解答:

主要是通过providerStrategy来获取,providerStrategy是一个接口,它有三个实现类,也就是三种策略,每个策略实现了从多个实例中获取一个实例的逻辑,实现逻辑都不一样的。

1、随机获取
public class RandomStrategy<T> implements ProviderStrategy<T>{
	ServiceInstance<T>  getInstance(InstanceProvider<T> instanceProvider) throws Exception{
		int thisIndex = random.nextInt(instances.size());//产生一个随机数
        return instances.get(thisIndex);
	}
}

2public class RoundRobinStrategy<T> implements ProviderStrategy<T>{
	ServiceInstance<T>  getInstance(InstanceProvider<T> instanceProvider) throws Exception{
		int thisIndex = Math.abs(index.getAndIncrement());//返回一个数的绝对值
        return instances.get(thisIndex % instances.size());//取模
	}
}

3、返回上一次返回的实例
public class StickyStrategy<T> implements ProviderStrategy<T>{
	ServiceInstance<T> getInstance(InstanceProvider<T> instanceProvider) throws Exception{
		....这个有点复杂,大致就是返回上一次返回的那一个实例
	}
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/876848.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-13
下一篇 2022-05-13

发表评论

登录后才能评论

评论列表(0条)

保存