一、分布式介绍

1.1 分布式环境特点:

1)分布性:异地多活;

2)并发性:程序运行过程中,并发性操作很常见;同一个分布式系统中的多个结点同时访问一个共享资源,比如数据库或者分布式存储。分布式并发是基于多进程的。

3)无序性:进程之间的消息通信会出现顺序不一致的问题。

1.2 分布式环境下面临的问题:

1)网络通信:网络通信异常导致分布式各结点间的消息收发过程出现问题。另外即使分布式系统各个结点之间的网络通信能够正常进行,其时延也会大于单机操作。(单机内存访问时延为纳秒数量级;分布式访问时延0.1~1ms);

2)网络分区(脑裂):网络发生异常,导致分布式系统中部分结点之间的网络遗憾吃不断增大,最终导致分布式架构中的所有结点只有部分能够正常运行;

3)三态:分布式下有三种状态(成功、失败、超时);超时通常包括以下两种情况 1.由于网络原因,该请求并没有被成功地发送到接收方,而是在发送过程中就发生了消息丢失现象;2. 该请求成功地被接收方接收后,进行了处理,但是在将响应反馈给发送方的过程中,发生了消息丢失现象。当出现这样的超时现象时,网络通信的发起方是无法确定当前请求是否被成功处理的;

4)节点故障:常见问题,分布式中节点出现宕机现象。

1.3 分布式的一致性:

  1. 强一致性

这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大。

  1. 弱一致性

这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不久承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。

  1. 最终一致性

最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型。

1.4 分布式事务

一个分布式事务可以看做是多个分布式的操作序列组成的。分布式事务也可以被定义为一种嵌套型的事物,同时也就具有了 ACID事物特性。但由于在分布式事务中,各个子事务的执行是分布式的,因此要实现一种能够保证ACID特性的分布式事务处理系统就显得格外复杂。在此期间出现了诸如CAP和BASE这样的分布式系统理论。

1.5 CAP理论

一个分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容错性(P:Partition tolerance)这三个基本需求,最多只能同时满足其中两项。

  1. 一致性:在分布式环境下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态;
  2. 可用性:可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。这里的重点是”有限时间内”和”返回结果”。
  3. 分区容错性:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。

1.6 BASE理论

BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结。

  1. 基本可用:分布式系统在出现不可预知故障的时候,允许损失部分可用性。但这不等价于系统不可用。
  2. 软状态:允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性。即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
  3. 最终一致性:最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能够达到一个一致的状态。

总的来说,BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,ACID特性和BASE理论往往又会结合在一起。

二、zookeeper介绍

  1. 概念image-20210319170626026

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

  1. zookeeper 用来做什么?
  • 数据的发布/订阅(配置中心:disconf,watcher机制);
  • 负载均衡(dubbo利用zookeeper机制实现负载均衡);
  • 命名服务;
  • master选举(kafka,hadoop,hbase);
  • 分布式队列;分布式锁;
  1. zookeeper 的特性
  • 顺序一致性:从同一个客户端发送的事务请求,最终都会严格按照顺序被应用到 zookeeper中;
  • 原子性:所有事务请求的处理结果在整个集群中的所有机器的应用情况是一致的。要么所有机器都成功应用某个事务,要么都不用;
  • 可靠性:一旦服务器成功应用了某一个事务数据,并且对客户端做出了响应,那么这个数据在整个集群中一定是同步并且保存下来。
  • 实时性:一单一个事务被成功应用,客户端能够立即从服务器读取到事务变更后的最新数据状态。

三、zookeeper 安装

官网下载安装

image-20210319173132506

3.1 服务启动及关闭

(1)先到 conf 目录下面拷贝一份 配置文件 zoo.cfg

cp zoo_sample.cfg zoo.cfg

(2)启动 zookeeper: sh zkServer.sh start

​ 关闭 zookeeper: sh zkServer.sh stop

​ 重启 zookeeper: sh zkServer.sh restart

​ 查看运行状态: sh zkServer.sh status

(3)启动客户端: sh zkCli.sh -server localhost:2182

​ 注意开端口 2182

3.2 配置文件 zoo.cfg

tickTime: zookeeper 最小时间单位长度;

initLimit:10*tickTime follower节点启动之后与leader节点同步数据的时间;

syncLimit:follower与 leader节点进行心跳检测最大延迟时间;

dataDir:存储快照文件的目录;

dataLogDir:事务日志的存储位置;默认在dataDir下;

clientPort:客户端和服务器建立连接的端口号(默认2182);

3.3 zookeeper 数据模型

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

zookeeper 中节点可以分为以下几类:

  • 持久化节点:节点建立后会一直存在 zookeeper 服务器上,直到主动删除;
  • 持久化有序节点:每个节点会为它的一级子节点维护一个顺序;
  • 临时节点:临时节点的生命周期与客户端会话保持一致,会话失效,节点删除;
  • 临时有序节点:同上;
  • 注意只有持久化节点才有子节点

会话状态分类:

  • NOT CONNECTED 未连接
  • CONNECTING 连接中
  • CONNECTED 已连接
  • CLOSED 关闭

watcher:

zookeeper 提供了分布式的数据订阅,它允许客户端向服务器注册一个 watcher 监听,当服务器的节点触发指定事件时会触发 watcher,服务端回向客户端发送一个通知(watcher)。但是该通知是一次性的,一旦触发一次后就会失效。

acl:

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

3.4 zookeeper 命令

  1. 查看 zookeeper的命令

1) create [-s] [-e] path data [acl]:创建节点

1
2
3
4
5
-s : 代表是否有序
-e : 代表是否是临时节点,默认是持久化节点
paht : 代表路径
data : 代表数据
acl : 代表访问权限

2)get path [watch]:获取指定节点的值

3)set path data [version]:修改节点 path对应的值

4)delete path [version]:删除指定节点

这里的 version代表使用的是乐观锁,数据库中乐观锁的实现方法(版本号+CAS算法)

5)stat path:查看对应节点的状态信息

1
2
3
4
5
6
7
8
9
10
11
cZxid:0x5          节点被创建时的事务id
ctime:xxxxxx 节点的创建时间
mZxid:0x7 节点被修改时的事务id
mtime:xxxxxx 节点被修改时的时间
pZxid:0x8 子节点最后一次修改时的事务id
cversion:2 子节点的版本号
dataVersion:1 数据版本号
aclVersion:0 节点修改权限
ephemeralOwner:0x0 创建临时节点时生成的一个 sessionid
dataLength:3 数据长度
numChildren:0 子节点数目

四、Java操作 Zookeeper

4.1 测试连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class testConnect {
private static final String address = "39.100.119.221:2181";

//通过使用 CountDownLatch 计时器来判断Zookeeper的连接是否连通
private static CountDownLatch countDownLatch = new CountDownLatch(1);

public static void main(String[] args) throws Exception{
ZooKeeper zooKeeper = new ZooKeeper(address, 5000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
if(watchedEvent.getState() == Event.KeeperState.SyncConnected)
{
//只有连通时countDownLatch的值才会减1,当countDownLatch中等待的线程数为0时才会执行await()后面的方法
countDownLatch.countDown();
System.out.println(watchedEvent.getState());
}
}
});
countDownLatch.await();
System.out.println(zooKeeper.getState());
}
}

补充: CountDownLatch:这个类使一个线程等待其它线程各自执行完毕后再执行,用作计数器。

4.2 创建节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class testCreateNode implements Watcher {
private final static String address = "39.100.119.221:2181";
private static CountDownLatch countDownLatch = new CountDownLatch(1);
private static Stat stat = new Stat();
private static ZooKeeper zooKeeper = null;

public static void main(String[] args) throws Exception{
//1. 建立连接
zooKeeper = new ZooKeeper(address,5000,new testCreateNode());
countDownLatch.await();
System.out.println(zooKeeper.getState());

//2. 创建节点
String filePath = "/FXP";
byte[] data = "1234".getBytes();
List<ACL> aclList = ZooDefs.Ids.OPEN_ACL_UNSAFE;
CreateMode createMode = CreateMode.EPHEMERAL; //创建临时节点
String result = zooKeeper.create(filePath,data,aclList,createMode);
byte[] res = zooKeeper.getData(filePath,true,stat); //添加一个 watcher
System.out.println("创建成功"+result);
System.out.println("获取的结果信息为:"+new String(res));

//3. 修改数据
zooKeeper.setData(filePath,"6789".getBytes(),-1); //-1 表示不管它的版本号

//4. 删除节点
zooKeeper.delete(filePath,-1);
Thread.sleep(3000);

//5. 创建节点和子节点(只有持久化节点才有子节点)
zooKeeper.create("/luff","666".getBytes(),aclList,CreateMode.PERSISTENT);
zooKeeper.create("/luff/child","777".getBytes(),aclList,CreateMode.PERSISTENT);
}

@Override
public void process(WatchedEvent watchedEvent) {
//通过计数器去控制当前连接
if(watchedEvent.getState() == Event.KeeperState.SyncConnected){
countDownLatch.countDown();
System.out.println(watchedEvent.getState());
}
else if(watchedEvent.getType() == Event.EventType.NodeCreated){
System.out.println("========创建了节点");
try {
System.out.println("路径:"+watchedEvent.getPath()+"节点值:"+zooKeeper.getData(watchedEvent.getPath(),true,new Stat()));
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
else if(watchedEvent.getType() == Event.EventType.NodeDataChanged){
System.out.println("========更改了节点的值");
try {
System.out.println("路径:"+watchedEvent.getPath()+"更改后的值:"+zooKeeper.getData(watchedEvent.getPath(),true,new Stat()));
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
else if(watchedEvent.getType() == Event.EventType.NodeDeleted){
System.out.println("=======删除了节点");
}
}
}

4.3 访问权限控制

schema:授权对象

ip:授权地址

digest:username password (授权用户名和密码)

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

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

4.4 连接状态

连接状态包括 KeeperState、EventType

KeeperState:连接状态

  • Disconnected:断开连接
  • NoSyncConnected:连接失败
  • SyncConnected:客户端和服务器端在某个节点上建立连接,并且完成一次ersion,
    zxid的同步
  • AuthFailed:授权失败

EventType:节点操作状态

  • NodeCreated 当节点被创建时触发
  • NodeDeleted 节点被删除
  • NodeDataChanged 节点数据发生改变
  • NodeChildrenChanged 标识子节点被创建,被删除,子节点数据发生变化
  • None 客户端和服务器连接状态发生变化

参考文章:

Zookeeper学习

countDownLatch详解