zookeeper

分布式协调服务-zookeeper

分布式环境的特点

分布性

并发性

程序运行过程中,并发性操作是很常见的。比如同一个分布式系统中的多个节点,同时访问一个共享资源。数据库、分布式存储

无序性

进程之间的消息通信,会出现顺序不一致问题

分布式环境下面临的问题

网络通信

网络本身的不可靠性,因此会涉及到一些网络通信问题

网络分区(脑裂)

当网络发生异常导致分布式系统中部分节点之间的网络延时不断增大,最终导致组成分布式架构的所有节点,只有部分节点能够正常通信

三态

在分布式架构里面,成功、失败、超时

分布式事务

ACID(原子性、一致性、隔离性、持久性)

中心化和去中心化

冷备或者热备

分布式架构里面,很多的架构思想采用的是:当集群发生故障的时候,集群中的人群会自动“选举”出一个新的领导。

最典型的是: zookeeper / etcd

经典的CAP/BASE理论

CAP

C(一致性 Consistency): 所有节点上的数据,时刻保持一致

可用性(Availability):每个请求都能够收到一个响应,无论响应成功或者失败

分区容错 (Partition-tolerance):表示系统出现脑裂以后,可能导致某些server与集群中的其他机器失去联系

CP / AP

CAP理论仅适用于原子读写的Nosql场景,不适用于数据库系统

BASE

基于CAP理论,CAP理论并不适用于数据库事务(因为更新一些错误的数据而导致数据出现紊乱,无论什么样的数据库高可用方案都是

徒劳) ,虽然XA事务可以保证数据库在分布式系统下的ACID特性,但是会带来性能方面的影响;

eBay尝试了一种完全不同的套路,放宽了对事务ACID的要求。提出了BASE理论

Basically available : 数据库采用分片模式, 把100W的用户数据分布在5个实例上。如果破坏了其中一个实例,仍然可以保证

80%的用户可用

soft-state: 在基于client-server模式的系统中,server端是否有状态,决定了系统是否具备良好的水平扩展、负载均衡、故障恢复等特性。

Server端承诺会维护client端状态数据,这个状态仅仅维持一小段时间, 这段时间以后,server端就会丢弃这个状态,恢复正常状态

Eventually consistent:数据的最终一致性

初步认识zookeeper

zookeeper是一个开源的分布式协调服务,是由雅虎创建的,基于google chubby。

zookeeper

zookeeper是什么

分布式数据一致性的解决方案

zookeeper能做什么

  • ​ 数据的发布/订阅(配置中心:disconf)
  • ​ 负载均衡(dubbo利用了zookeeper机制实现负载均衡)
  • ​ 命名服务
  • ​ master选举(kafka、hadoop、hbase)
  • ​ 分布式队列
  • ​ 分布式锁

zookeeper的特性

顺序一致性

​ 从同一个客户端发起的事务请求,最终会严格按照顺序被应用到zookeeper中

原子性

​ 所有的事务请求的处理结果在整个集群中的所有机器上的应用情况是一致的,也就是说,要么整个集群中的所有机器都成功应用了某一事务、要么全都不应用

可靠性

​ 一旦服务器成功应用了某一个事务数据,并且对客户端做了响应,那么这个数据在整个集群中一定是同步并且保留下来的

实时性

​ 一旦一个事务被成功应用,客户端就能够立即从服务器端读取到事务变更后的最新数据状态;(zookeeper仅仅保证在一定时间内,近实时)

zookeeper安装

单机环境安装

  1. 下载zookeeper的安装包

    http://apache.fayea.com/zooke...

  2. 解压zookeeper

    tar -zxvf zookeeper-3.4.10.tar.gz

  3. cd 到 ZK_HOME/conf , copy一份zoo.cfg

​ cp zoo_sample.cfg zoo.cfg

  1. sh zkServer.sh

​ {start|start-foreground|stop|restart|status|upgrade|print-cmd}

  1. sh zkCli.sh -server ip:port

    默认端口 2181

    127.0.0.1:2181

    zkServer.sh start-foreground

集群环境

建议使用奇数

对于复制模式,至少需要三台服务器,强烈建议您使用奇数个服务器。如果您只有两台服务器,那么您处于这样的情况:如果其中一台服务器出现故障,则没有足够的机器来构成多数仲裁。两台服务器本质上 不如 单一服务器稳定,因为有两个单点故障。

zookeeper

zookeeper集群, 包含三种角色:

leader :

​ 所有写和更改操作接受,转发到其他节点,接受所有Follower的提案请求并统一协调发起提案的投票,负责与所有的Follower进行内部的数据交换(同步)

follower :

​ 直接为客户端服务并参与提案的投票,同时与Leader进行数据交换(同步)

observer:

​ 直接为客户端服务但不参与提案的投票,同时也与Leader进行数据交换(同步)

observer

observer 是一种特殊的zookeeper节点。可以帮助解决zookeeper的扩展性(如果大量客户端访问我们zookeeper集群,需要增加zookeeper集群机器数量。从而增加zookeeper集群的性能。 导致zookeeper写性能下降, zookeeper的数据变更需要半数以上服务器投票通过。造成网络消耗增加投票成本)

  1. observer不参与投票。 只接收投票结果。
  2. 不属于zookeeper的关键部位。

zookeeper

  1. 在zoo.cfg里面增加

    在集群中的每一台服务器必须感知其他服务器(1,2,3表示id,取值范围 1-255)

    peerType=observer

    server.1=host:2181:3181:observer

    server.2=host:2181:3181

    server.3=host:2181:3181

    quorumListenOnAllIPs=true

第一步: 修改配置文件

​ server.id=host:port:port

​ id的取值范围: 1~255; 用id来标识该机器在集群中的机器序号

​ 2181是zookeeper的端口; //3306

​ 3181表示leader选举的端口

​ server.1=host:2181:3181

​ server.2=host:2181:3181

​ server.3=host:2181:3181

第二步:创建myid

​ 在每一个服务器的dataDir目录下创建一个myid的文件,文件就一行数据,数据内容是每台机器对应的server ID的数字

第三步:启动zookeeper

  1. 分布式系统里面的特点
  2. 分布式系统架构存在的问题
  3. 中心化和去中心化
  4. CAP和BASE
  5. zookeeper的安装 单机环境安装/集群环境安装
  6. zookeeper的特性

1. zookeeper的客户端使用

2. zoo.cfg里面配置信息的讲解

3. zookeeper的一些常见概念模型

4. zookeeper java客户端的使用

集群的角色:

leader :

follower

observer:避免写性能下降,不需要参与写

集群的搭建

修改zoo.cfg

​ 129/135/136

​ server.id=ip:port:port

​ server.1=host:2888:3181 2888表示follower节点与leader节点交换信息的端口号 3181 如果leader节点挂掉了, 需要一个端口来重新选举。

​ server.2=host:2888:3181

​ server.3=host:2888:3181

zoo.cfg中有一个dataDir = /tmp/zookeeper

​ $dataDir/myid 添加一个myid文件。

  1. 启动服务

如果需要增加observer节点

zoo.cfg中 增加 ;peerType=observer

server.1=host:2888:3181

server.2=host:2888:3181

server.3=host:2888:3181:observer

zoo.cfg配置文件分析

tickTime=2000 zookeeper中最小的时间单位长度 (ms)

initLimit=10 follower节点启动后与leader节点完成数据同步的时间

syncLimit=5 leader节点和follower节点进行心跳检测的最大延时时间

dataDir=/tmp/zookeeper 表示zookeeper服务器存储快照文件的目录

dataLogDir 表示配置 zookeeper事务日志的存储路径,默认指定在dataDir目录下

clientPort 表示客户端和服务端建立连接的端口号: 2181

zookeeper中的一些概念

数据模型

zookeeper的数据模型和文件系统类似,每一个节点称为:znode. 是zookeeper中的最小数据单元。每一个znode上都可以保存数据和挂载子节点。 从而构成一个层次化的属性结构

节点特性

持久化节点 : 节点创建后会一直存在zookeeper服务器上,直到主动删除

持久化有序节点 :每个节点都会为它的一级子节点维护一个顺序

临时节点 : 临时节点的生命周期和客户端的会话保持一致。当客户端会话失效,该节点自动清理,临时节点下面不能挂子节点

临时有序节点 : 在临时节点上多勒一个顺序性特性

会话

zookeeper

Watcher

zookeeper提供了分布式数据发布/订阅,zookeeper允许客户端向服务器注册一个watcher监听。当服务器端的节点触发指定事件的时候

会触发watcher。服务端会向客户端发送一个事件通知watcher的通知是一次性,一旦触发一次通知后,该watcher就失效

ACL

zookeeper提供控制节点访问权限的功能,用于有效的保证zookeeper中数据的安全性。避免误操作而导致系统出现重大事故。

CREATE

READ

WRITE

DELETE

ADMIN

zookeeper的命令操作

1. create [-s]【-e】 path data acl

​ -s 表示节点是否有序

​ -e 表示是否为临时节点

​ 默认情况下,是持久化节点

​ path需要/开头

2. get path [watch]

​ 获得指定 path的信息

3.set path data [version]

​ 修改节点 path对应的data

​ 乐观锁的概念

​ 每个节点创建好之后会有一个dataversion,默认是0,如果后续此值更改之后,dataversion会更改,我们可以通过version来保证乐观锁

​ 数据库里面有一个 version 字段去控制数据行的版本号

4.delete path [version]

​ 删除节点

stat信息

​ cversion = 0 子节点的版本号

​ aclVersion = 0 表示acl的版本号,修改节点权限

​ dataVersion = 1 表示的是当前节点数据的版本号

​ czxid 节点被创建时的事务ID

​ mzxid 节点最后一次被更新的事务ID

​ pzxid 当前节点下的子节点最后一次被修改时的事务ID

​ ctime = Sat Aug 05 20:48:26 CST 2017 创建时间

​ mtime = Sat Aug 05 20:48:50 CST 2017 修改时间

​ cZxid = 0x500000015

​ ctime = Sat Aug 05 20:48:26 CST 2017

​ mZxid = 0x500000016

​ mtime = Sat Aug 05 20:48:50 CST 2017

​ pZxid = 0x500000015

​ cversion = 0

​ dataVersion = 1

​ aclVersion = 0

​ ephemeralOwner = 0x0 创建临时节点的时候,会有一个sessionId 。 该值存储的就是这个sessionid,客户端有一定时间生效时间,具有重试机制,过一段时间判断是否有会话,如果没有,则删除

​ dataLength = 3 数据值长度

​ numChildren = 0 子节点数

java API的使用

导入jar包

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.8</version>
</dependency>

public class CreateNodeDemo implements Watcher {
    private final static String CONNECT_STRING = "host:2181," +
            "host:2181," +
            "host:2181";
    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    private static ZooKeeper zooKeeper = null;
    private static Stat stat = new Stat();

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        zooKeeper = new ZooKeeper(CONNECT_STRING, 5000, new CreateNodeDemo());
        countDownLatch.await();
        System.out.println(zooKeeper.getState());
        String path = zooKeeper.create(
                "/java1", "123".getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        //watcher是一次性的,所以使用一次之后需要重新注册
        zooKeeper.getData("/java1", true, stat);
        System.out.println("节点创建成功:" + path);
        zooKeeper.setData("/java1", "231".getBytes(), -1);
        zooKeeper.delete("/java1", -1);
        zooKeeper.create("/java1/dd", "da".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        Thread.sleep(3000);

        List<String> children = zooKeeper.getChildren("/java1", true);
        for (String child : children) {
            System.out.println(child);
        }

        //权限模式
    }

    public void process(WatchedEvent watchedEvent) {
        if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {

            if (watchedEvent.getType() == watchedEvent.getType() && null == watchedEvent.getPath()) {
                countDownLatch.countDown();
                System.out.println(watchedEvent.getState() + "-->" + watchedEvent.getType());
            }
            if (watchedEvent.getType() == Event.EventType.NodeCreated) {
                try {
                    System.out.println("创建节点" + watchedEvent.getPath() + "改变后的值是" + zooKeeper.getData(watchedEvent.getPath(), true, stat));
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (watchedEvent.getType() == Event.EventType.NodeDeleted) {
                System.out.println("删除节点" + watchedEvent.getPath());
            }
            if (watchedEvent.getType() == Event.EventType.NodeChildrenChanged) {
                try {
                    System.out.println("子节点改变" + watchedEvent.getPath() + "改变后的值是" + zooKeeper.getData(watchedEvent.getPath(), true, stat));
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (watchedEvent.getType() == Event.EventType.NodeDataChanged) {
                System.out.println("--------------");
                try {
                    System.out.println("数据改变" + watchedEvent.getPath() + "改变后的值是" + zooKeeper.getData(watchedEvent.getPath(), true, stat));
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        System.out.println(watchedEvent.getPath());
        System.out.println(watchedEvent.getType());
    }
}

权限控制模式

schema:授权对象

ip : host

Digest : username:password

world : 开放式的权限控制模式,数据节点的访问权限对所有用户开放。 world:anyone

super :超级用户,可以对zookeeper上的数据节点进行操作

连接状态

KeeperStat.Expired 在一定时间内客户端没有收到服务器的通知, 则认为当前的会话已经过期了。

KeeperStat.Disconnected 断开连接的状态

KeeperStat.SyncConnected 客户端和服务器端在某一个节点上建立连接,并且完成一次version、zxid同步

KeeperStat.authFailed 授权失败

事件类型

NodeCreated 当节点被创建的时候,触发

NodeChildrenChanged 表示子节点被创建、被删除、子节点数据发生变化

NodeDataChanged 节点数据发生变化

NodeDeleted 节点被删除

None 客户端和服务器端连接状态发生变化的时候,事件类型就是None

zkclient

<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>

提供了递归创建父节点递归删除节点的功能

curator

Curator本身是Netflix公司开源的zookeeper客户端;

curator提供了各种应用场景的实现封装

curator-framework 提供了fluent风格api,

curator-replice 提供了实现封装

curator连接的重试策略

ExponentialBackoffRetry() 衰减重试

RetryNTimes 指定最大重试次数

RetryOneTime 仅重试一次

RetryUnitilElapsed 一直重试知道规定的时间

zookeeper的实际应用场景

zookeeper能够实现哪些场景

订阅发布

watcher机制

统一配置管理(disconf)

分布式锁

redis

​ setnx

zookeeper 节点特性

数据库

负载均衡

ID生成器

分布式队列

统一命名服务

master选举

分布式锁

master选举

相关推荐