分布式服务框架——ZooKeeper

Zookeeper

Zookeeper 是 Apache 的一个分布式服务框架
它主要是用来解决分布式应用中经常遇到的一些数据管理问题, 如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等
简单来说 zookeeper=文件系统+监听通知机制

存储结构

在ZooKeeper中,znode是一个跟Unix文件系统路径相似的节点,可以向节点存储数据或者获取数据。Zookeeper 底层是一套数据结构。这个存储结构是一个树形结构,其上的每一个节点, 我们称之为“znode” Zookeeper 中的数据是按照“树”结构进行存储的。而且 znode 节点还分为 4 中不同 的类型。每一个 znode 默认能够存储 1MB 的数据**(对于记录状态性质的数据来说,够了)**

Znode节点类型

  • PERSISTENT-持久化目录节点:客户端与zookeeper断开后,该节点依旧存在
  • PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点:在PERSISTENT节点的基础上,还给该节点名称进行顺序编号
  • EPHEMERAL-临时目录节点:客户端与zookeeper断开后,该节点被删除
  • EPHEMERAL-SEAUENTIAL-临时目录节点在EPHEMEEAL节点的基础上,还给该节点名称进行顺序编号

监听通知机制

ZooKeeper是使用观察者设计模式来设计的
当客户端注册监听它关心的目录节点时,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,ZooKeeper会通知客户端

安装ZooKeeper

在使用ZooKeeper之前,Linux系统中需要有jdk环境,在这里我就不过多赘述了。

[root@localhost temp]# tar -zxf zookeeper-3.6.0.tar.gz 
[root@localhost temp]# cp zookeeper-3.6.0 /usr/local/zookeeper -r

ZooKeeper目录结构

  1. bin:放置运行脚本和工具脚本
  2. conf:zookeeper默认读取配置的目录里面会有默认的配置文件
  3. docs:zookeeper相关的文档
  4. lib:zookeeper核心的jar
  5. logs:zookeeper日志
  6. data:zookeeper数据存放的位置,但需要自己创建

配置ZooKeeper

ZooKeeper在启动时默认的去conf目录下去查找一个名称为zoo.cfg的配置文件。但是conf目录中没有这个文件,只有zoo_sample.cfg,顾名思义,这就是要寻找的zoo.cfg文件的模板。我们需要基于该文件配置zoo.cfg

cp zoo_sample.cfg zoo.cfg
vim zoo.cfg

在这里插入图片描述
其中的

  • datadir:data文件夹的绝对路径,需要根据自己data的实际路径进行修改
  • clientPort:要监听的端口号,默认是2181,单个zookeeper时可不做修改

启动ZooKeeper

下述命令皆是在bin目录下执行

  • 默认加载配置文件:./zkServer.sh start:默认会去conf目录下加载zoo.cfg配置文件
  • 指定加载配置文件:./zkServer.sh start 配置文件的路径

停止ZooKeeper

./zkServer.sh stop

查看ZooKeeper状态

./zkServer.sh status

使用客户端连接单机版

  • ./zkClient.sh:默认连接地址为本机地址,默认端口2181
  • ./zkClient.sh -server ip:port:连接指定ip和端口
    用第一种方式实现的功能用第二种方式实现:
./zkClient.sh -server 192.168.126.168:2181

ZooKeeper集群

ZooKeeper集群中的角色

  • leader:负责进行发现投票和决议,更新系统状态
  • follower:Follower用于接收客户请求并向客户端返回结果,在选主过程中参加投票
  • observer:Observer可以接收客户端连接,将写请求转发给leader节点。但Observer不参加投票过程,只同步leader的状态。Observer的目的是为了拓展系统,提高读取速度
  • client:请求发起方

集群安装

创建一个zookeepercluster文件夹,然后复制三份zookeeper,如下
在这里插入图片描述
修改三个zookeeper的zoo.cfg文件,如下:
在这里插入图片描述
其中

  • datadir输入对应的data路径
  • clientPort:由于有三个zookeeper,所以分别填2181、2182、2183即可
  • server部分:server.标识:ip:2881:2883,有几个zookeeper就要填几行,但是不同的zoo.cfg文件都是一样的

注意:server.标识:后面的标识是代表不同zookeeper文件,故集群时需要在不同zookeeper的data中创建myid文件,如下:

快捷方法:
在zookeepercluster路径下

echo 1 >> zookeeper01/data/myid
echo 2 >> zookeeper02/data/myid
echo 3 >> zookeeper03/data/myid

1、2、3就对应我截图里面的标识,还有就是如果没有对应的myid,会自动创建myid
此时,一个zookeeper集群就配置好了。
但是这样子需要一个个zookeeper启动,再一个个zookeeper是不是很麻烦。为了不那么麻烦,我们可以把这些命令写成sh文件

vim startall.sh

再将这几个命令写进去

输入:wq保存
同理,我们也可以写一个shutdown.sh文件停止zookeeper运行

但是这样子的话,这个sh文件还不能执行,那就用liunx命令给其添加权限

chomd 777 startall.sh
chomd 777 shutdown.sh

此时就可以大功告成了。
在这里插入图片描述

启动集群

关闭集群

在这里插入图片描述

连接集群

可以使用任意一个客户端连接

./zkClient -server 192.168.126.128:2181

ZooKeeper常用命令

  • ls:查看子节点
ls /path

使用 ls 命令查看 zookeeper 中的内容。在 ZooKeeper 控制台客户端中,没有默认列表功 能,必须指定要列表资源的位置

  • create:创建Znode节点
create [-e] [-s] /path [data]

加-e表示是临时节点,-s表示需要进行顺便编号,/path表示要创建的节点,data表示该节点的值,以上带[]的都表示可选参数

  • get:获取对应节点的值
get [-s] /path

-s 表示是否查看详细信息

  • set:添加或修改节点的值
set /path [data]
  • delete:删除节点
delete /path

Java操作ZooKeeper

添加pom依赖

<dependency>
 <groupId>org.apache.zookeeper</groupId>
  <artifactId>zookeeper</artifactId> 
<version>3.6.0</version> 
</dependency>
public class ZnodeDemo implements Watcher {
    public static void main(String[] args) throws InterruptedException, KeeperException, IOException {
        ZooKeeper zooKeeper = new ZooKeeper("192.168.126.128:2181,192.168.126.128:2182,192.168.126.128:2183",150000,new ZnodeDemo());

        //创建Znode
        String path = zooKeeper.create("/lanh/try","ni".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
        System.out.println(path);

        //获取指定节点的数据
        byte[] data = zooKeeper.getData("/lanh/try0000000002",new ZnodeDemo(),new Stat());
        System.out.println(new String(data));

        //获取指定节点中所有子节点中的数据
        List<String> list  = zooKeeper.getChildren("/lanh",new ZnodeDemo());
        for (String s:list){
            System.out.println(new String(zooKeeper.getData("/lanh/"+s,new ZnodeDemo(),new Stat())));
        }

        //设置节点值,-1表示所有版本
        Stat stat = zooKeeper.setData("/lanh/try0000000002","get it a try".getBytes(),-1);
        System.out.println(stat);

        //删除节点值
        zooKeeper.delete("/lanh/try0000000001",-1);
    }

    @Override
    public void process(WatchedEvent watchedEvent) {
        if(watchedEvent.getState()==Event.KeeperState.SyncConnected){
            System.out.println("连接成功");
        }
    }
}

使用 Zookeeper 作为注册中心实现 RPC

RMI的API介绍

  • Remote接口
    java.rmi.Remote定义了此接口为远程调用接口,如果接口被外部调用,需要继承此接口
  • RemoteException类
    java.rmi.RemoteException类
    继承了Remote接口的接口,如果方法是允许被远程调用,需要抛出此异常
  • UnicastRemoteObject类
    java.rmi.UnicastRemoteObject
    此类实现了Remote接口和Serializable接口
    自定义接口实现类除了实现自定义接口,还需继承此类
  • LocateRegistry
    java.rmi.registry.LocateRegistry
    可以通过LocateRegistry,通过特定的端口就可以访问这个Registry
  • Naming类
    java.rmi.Naming
    Naming定义了发布内容可访问RMI名称。也是通过Naming获取到指定的远程方法

pom依赖

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

自定义接口

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface UsersService extends Remote {
    String findUsers(String str) throws RemoteException;
}

自定义接口实现类

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

/**
 * @Author Lanh
 **/
public class UsersServiceImpl extends UnicastRemoteObject implements UsersService {
    public UsersServiceImpl() throws RemoteException {
    }

    public String findUsers(String str) throws RemoteException {
        return "Hello zookeeper "+str;
    }
}

服务端

import org.apache.zookeeper.*;


import java.io.IOException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

/**
 * @Author Lanh
 **/
public class ServerDemo implements Watcher {
    public static void main(String[] args) throws IOException, AlreadyBoundException, InterruptedException, KeeperException {
        UsersService usersService = new UsersServiceImpl();
        LocateRegistry.createRegistry(8888);
        String url = "rmi://localhost:8888/user";
        Naming.bind(url,usersService);

        //将url信息放到Znode中
        ZooKeeper zooKeeper = new ZooKeeper("192.168.126.128:2181,192.168.126.128:2182,192.168.126.128:2183",150000,new ServerDemo());
        zooKeeper.delete("/lanh/service",-1);
        zooKeeper.create("/lanh/service",url.getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
        System.out.println("服务发布成功");
    }

    public void process(WatchedEvent watchedEvent) {
        if (watchedEvent.getState()==Event.KeeperState.SyncConnected){
            System.out.println("连接成功");
        }
    }
}

客户端模块

也要有对应的接口

package com.lanh.service;
public interface UsersService{
    String findUsers(String str);
}

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

import java.io.IOException;
import java.rmi.Naming;
import java.rmi.NotBoundException;

/**
 * @Author Lanh
 **/
public class ClientDemo implements Watcher {
    public static void main(String[] args) throws IOException, NotBoundException, InterruptedException, KeeperException {
        ZooKeeper zooKeeper = new ZooKeeper("192.168.126.128:2181,192.168.126.128:2182,192.168.126.128:2183",150000,new ClientDemo());
        byte[] bytes = zooKeeper.getData("/lanh/service",new ClientDemo(),null);
        String url = new String(bytes);
        UsersService usersService = (UsersService) Naming.lookup(url);
        String result = usersService.findUsers("Lanh");
        System.out.println(result);
    }

    public void process(WatchedEvent watchedEvent) {
        if (watchedEvent.getState()==Event.KeeperState.SyncConnected){
            System.out.println("连接成功");
        }
    }
}

至此结束,也欢迎大家来讨论

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绿豆蛙给生活加点甜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值