服务框架Dubbo和ZooKeeper

  • Post author:
  • Post category:其他


本篇为动物园之旅~

dubbo看作动物园的动物,那么zookeeper就是动物园

如果有人想去动物园看小老虎,那么动物园中有小老虎才能看到,否则看不到~

关系:dubbo与 zookeeper的关系 Dubbo建议使用Zookeeper作为服务的注册中心



一.Dubbo框架(远程过程调用)



1.分布式系统中的相关概念


  • 大型互联网项目的架构目标

传统项目和互联网项目

在这里插入图片描述

大型互联网项目的架构目标

在这里插入图片描述


  • 集群和分布式

两者区别

在这里插入图片描述

没有集群和分布式的服务器

在这里插入图片描述

进行集群的服务器-可以进行

负载均衡

,实现了高性能、高可用的目标

在这里插入图片描述

同时进行集群和分布式的服务器-除了集群实现的功能和目标,还可以实现可伸缩、高可扩展的目标

在这里插入图片描述

集群和分布式的理解

在这里插入图片描述


  • 架构演进

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述



2.Dubbo概述


  • Dubbo概念

Dubbo是一款高性能、轻量级的开源

Java RPC框架

,它提供了三大核心能力:

面向接口的远程方法调用



智能容错



负载均衡

,以及服务自动注册和发现。

Dubbo是微服务开发框架,提供

RPC通信



微服务治理的能力


  • Dubbo架构

Dubbo 基于消费端的自动服务发现能力

在这里插入图片描述

服务发现,c能力,是微服务框架需要具备的关键能力,借助于自动化的服务发现,微服务之间可以在无需感知对端部署位置与 IP 地址的情况下实现通信。

实现服务发现的方式有很多种,Dubbo 提供的是一种 Client-Based 的服务发现机制,通常还需要部署额外的第三方注册中心组件来协调服务发现过程,如常用的 Nacos、Consul、Zookeeper 等,Dubbo 自身也提供了对多种注册中心组件的对接,用户可以灵活选择。

服务发现的一个核心组件是注册中心,Provider 注册地址到注册中心,Consumer 从注册中心读取和订阅 Provider 地址列表。

下图是 Dubbo2 的服务发现模型:Provider 注册服务地址,Consumer 经过注册中心协调并发现服务地址,进而对地址发起通信,这是被绝大多数微服务框架的经典服务发现流程。而 Dubbo2 的特殊之处在于,它把 “RPC 接口”的信息也融合在了地址发现过程中,而这部分信息往往是和具体的业务定义密切相关的。

在这里插入图片描述



3.Dubbo快速入门


使用zookeeper作为注册中心

在这里插入图片描述

在这里插入图片描述



4.Dubbo高级特性


  • dubbo-admin管理平台

在这里插入图片描述


  • dubbo高级特性

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述



二.ZooKeeper框架(分布式协调服务)



1.ZooKeeper主要功能



1.1 配置管理


分布式项目

的配置总管理处

在这里插入图片描述



1.2 分布式锁

对于分布式项目修改共享数据时加入锁管理(同一时间只能有一个服务对数据进更改)

在这里插入图片描述



1.3 集群管理

最常见的功能,作为

注册中心

使用.

在这里插入图片描述



2.ZooKeeper命令操作

在这里插入图片描述



2.1 ZooKeeper数据模型

  • Zookeeper是一个树形目录服务,其数据模型和Unix的文件系统目录树很类似,拥有一个层次化结构

  • 这里面的每一个结点都被称为ZNode,每个节点都会保存自己的数据和节点信息

  • 节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下

  • 节点可以分为四大类

    • PERSISTENT 持久化节点

    • EPHEMRAL 临时节点 : -e

    • PERSISTENT_SENQUENTIAL 持久化顺序节点 : -s

    • EPHEMRAL_SEQUENTIAL 临时顺序节点 : -es

在这里插入图片描述



2.2 ZooKeeper 服务端常用命令

在安装目录的bin目录下:

  • 启动Zookeeper服务 : ./zkServer.sh start

  • 查看Zookeeper服务状态 : ./zkServer.sh statu

  • 停止Zookeeper服务 : ./zkServer.sh stop

  • 重启Zookeeper服务 : ./zkServer.sh restart



2.3 ZooKeeper 客户端常用命令

  • 连接ZooKeeper服务端
./zkCli.sh -server ip:port
  • 断开客户端连接
quit
  • 设置节点的值
set /节点path value
  • 查看帮助命令
help
  • 删除单个节点
delete /节点path
  • 显示指定目录下的节点
ls 目录
  • 删除带有子节点的节点
deleteall /节点path
  • 创建节点
create /节点path value
  • 获取节点的值
get /节点path
  • 创建临时节点
create -e /节点path value
  • 创建顺序节点
create -s /节点path value
  • 查询节点详细信息
ls -s /节点path
  • 节点详细信息
czxid : 节点被创建的事务ID

dataversion : 数据版本号

ctime : 创建时间

aclversion : 权限版本号

mzxid : 最后一次被更新的事务ID

ephemeralOwner : 用于临时节点 ,代表临时节点的事务ID,如果为持久节点则为0

pzxid : 子节点列表最后一次被更新的事务ID

dataLength : 节点存储的数据长度

cversion : 子节点的版本号

numChildren : 当前节点的子节点数



3.Java API-Curator

Curator是Apache ZooKeeper的Java客户端库

官网:

https://curator.apache.org/

依赖:

<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>



4.Java API 常用操作

  • 建立连接

  • 添加节点

  • 删除节点

  • 修改节点

  • 查询节点

  • Watch时间监听

  • 分布式锁实现



4.1 建立连接



4.1.1 方式一

/**
       * Create a new client
       * Params:
       * connectString – "192.168.36.100:2181,192.168.36.101:2181"
       * sessionTimeoutMs – 会话超时时间 单位ms
       * connectionTimeoutMs – 连接超时时间 单位ms
       * retryPolicy – 重试策略
       * Returns:
       * client
       */
      //重试策略 每间隔x一共重试x次  每隔3秒连接一次一共连接10次
      RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
      //1.第一种方式
      CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.36.100:2181"
              , 60 * 1000, 15 * 1000, retryPolicy);
      //开启连接
      client.start();



4.1.2 方式二 : 链式编程(有提示)

//2.第二种方式
client = CuratorFrameworkFactory.builder()
        .connectString("192.168.36.100:2181")
        .sessionTimeoutMs(60 * 1000)
        .connectionTimeoutMs(15 * 1000)
        .retryPolicy(retryPolicy)
        .namespace("jmpower")
        .build();

//开启连接
client.start();
  • namespace : 创建根节点jmpower(为了方便,不用之后每次进行客户端操作都写/根节点path)



4.2 添加节点

创建结点:create 持久化 临时 顺序 数据

1.基本创建 : create().forPath(“”)

2.创建结点,带有数据 : create().forPath(“”,data)

3.设计节点类型 : create().withMode().forPath(“”)

4.创建多级节点 /app1/p1 : create().creatingParentsIfNeeded().forPath(“”)



4.2.1 基本创建

 @Test
 public void testCreate() throws Exception {
     //1.基本创建
     String path = client.create().forPath("/app1");
     System.out.println(path);
 }



4.2.2 创建带有数据的结点

 @Test
 public void testCreate2() throws Exception {
     //2.创建结点,带有数据
     //如果创建的节点,没有指定数据,则默认将当前客户端的ip作为数据存储
     String path = client.create().forPath("/app2", "hehe".getBytes());
     System.out.println(path);
 }



4.2.3 设计节点类型

 @Test
 public void testCreate3() throws Exception {
     //3.设计节点类型
     //默认类型:持久化
     String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3");
     System.out.println(path);
 }



4.2.4 创建多级节点

 @Test
 public void testCreate4() throws Exception {
     //4.创建多级节点 /app1/p1
     //creatingParentsIfNeeded():如果父节点不存在,则创建父节点
     String path = client.create().creatingParentsIfNeeded().forPath("/app4/p1");
     System.out.println(path);
 }



4.3 删除节点

删除节点:delete deleteall

1.删除单个节点 : delete().forPath()

2.删除带有子节点的节点 : delete().deletingChildrenIfNeeded().forPath()

3.必须成功的删除(为了防止网路抖动,本质就是重试) : delete().guaranteed().forPath()

4.回调 : inBackground



4.3.1 删除单个节点

  @Test
  public void testDelete() throws Exception {
      //1.删除单个节点
      client.delete().forPath("/app1");
  }



4.3.2 删除带有子节点的节点

  @Test
  public void testDelete2() throws Exception {
      //2.删除带有子节点的节点
      client.delete().deletingChildrenIfNeeded().forPath("/app4");
  }



4.3.3 必须成功的删除

  @Test
  public void testDelete3() throws Exception {
      //3.必须成功的删除
      client.delete().guaranteed().forPath("/app2");
  }



4.3.4 回调

  @Test
  public void testDelete4() throws Exception {
      //4.回调
      client.delete().guaranteed().inBackground(new BackgroundCallback() {
          @Override
          public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
              System.out.println("我被删除了~");
              System.out.println(event);
          }
      }).forPath("/app1");
  }



4.4 修改节点

修改数据

1.修改数据 : setData().forPath()

2.根据版本修改 : setData().withVersion(version).forPath()

version是通过查询出来的。目的就是为了让其他客户端或线程不干扰我。



4.4.1 修改数据

@Test
public void testSet() throws Exception {
   client.setData().forPath("/app1","jm666".getBytes());
}



4.4.2 根据版本修改

@Test
public void testSetForVersion() throws Exception {
    Stat stat = new Stat();
    //3.查询节点的状态信息:ls -s
    client.getData().storingStatIn(stat).forPath("/app1");

    int version=stat.getVersion();//查询出来的版本号
    System.out.println(version);
    client.setData().withVersion(version).forPath("/app1","hahaha".getBytes());
}



4.5 查询节点

查询节点:

1.查询数据:get : getData().forPath(“”)

2.查询子节点:ls : getChildren().forPath(“”)

3.查询节点的状态信息:ls -s : getData().storingStatIn(stat).forPath(“”)



4.5.1 查询数据

@Test
public void testGet() throws Exception {
    //1.查询数据:get
    byte[] data = client.getData().forPath("/app1");
    System.out.println(new String(data));
}



4.5.2 查询子节点

@Test
public void testGet2() throws Exception {
    //2.查询子节点:ls
    List<String> path = client.getChildren().forPath("/");
    System.out.println(path);
}



4.5.3 查询节点的状态信息

@Test
public void testGet3() throws Exception {
    Stat stat = new Stat();
    //3.查询节点的状态信息:ls -s
    client.getData().storingStatIn(stat).forPath("/app1");
    System.out.println(stat);
}



5.Watch事件监听



5.1 基本概念

  • ZooKeeper允许用户在指定节点上注册一些Watchr,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制时ZooKeeper实现分布式协调服务的重要特性.

  • ZooKeeper中引入了Watcher机制来实现了发布/订阅功能.能够让多个订阅者同时监听某一个对象,当一个对象自身状态变化的时候,会通知所有订阅者.

  • ZooKeeper原生支持通过注册Watcher来进行事件监听,但是其使用并不是特别方便需要开发人员自己反复注册Watcher,比较繁琐.

  • Curator引入了Cache来实现对ZooKeeper服务端事件的监听.

  • ZooKeeper提供了三种Watcher:

    • NodeCache : 只是监听了某一特定节点

    • PathChildrenCache : 监控一个ZNode的子节点

    • TreeCache : 可以监控整个树上的所有节点,类似于PathChildrenCache和NodeCache组合.



5.1 NodeCache

/**
 * nodeCache:监听某个节点
 */
@Test
public void testNodeCatch() throws Exception {
    //1.创建NodeCatch对象
    NodeCache nodeCache = new NodeCache(client, "/app1");
    //2.注册监听
    nodeCache.getListenable().addListener(new NodeCacheListener() {
        @Override
        public void nodeChanged() throws Exception {
            System.out.println("节点变化了~");
            //获取节点更改后的数据
            byte[] data = nodeCache.getCurrentData().getData();
            System.out.println(new String(data));
        }
    });
    //3.开启监听,如果设置为true,则开启监听,加载缓冲数据
    nodeCache.start(true);

    while (true){

    }
}



5.2 PathChildrenCache

/**
 * PathChildrenCache:监听某个节点的所有子节点们
 */
@Test
public void testPathChildrenCache() throws Exception {
    //1.创建监听对象
    PathChildrenCache pathChildrenCache = new PathChildrenCache(client,"/app2",true);
    //2.绑定监听器
    pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
        @Override
        public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
            System.out.println("子节点变化了~");
            System.out.println(pathChildrenCacheEvent);
            //监听子节点的数据变更,并且拿到变更后的数据
            //1.获取类型
            PathChildrenCacheEvent.Type type = pathChildrenCacheEvent.getType();
            //2.判断类型是否为update
            if (type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
                System.out.println("数据变了!!!");
                byte[] data = pathChildrenCacheEvent.getData().getData();
                System.out.println(new String(data));
            }
        }
    });
    //3.开启
    pathChildrenCache.start();

    while (true) {

    }
}



5.3 TreeCache

/**
 * TreeCache:监听某个节点的所有子节点们
 */
@Test
public void testTreeCache() throws Exception {
    //1.创建监听器
    TreeCache treeCache = new TreeCache(client, "/app2");
    //2.注册监听器
    treeCache.getListenable().addListener(new TreeCacheListener() {
        @Override
        public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
            System.out.println("节点变化了~");
            System.out.println(treeCacheEvent);
            if(treeCacheEvent.getType()== TreeCacheEvent.Type.NODE_UPDATED){
                System.out.println("更改操作!!");
            }
        }
    });
    //3.开启
    treeCache.start();

    while (true){

    }
}



6.分布式锁

在这里插入图片描述



6.1 分布式锁

  • 在我们进行单机应用开发,涉及并发同步的时候,我们往往采用synchronized或者lock的方式来解决多线程见的代码同步问题,这时多线程的运行都是运行在同一 JVM下,没有任何问题

  • 但当我们的应用时分布式集群工作的情况下,属于JVM下的工作环境,跨JVM之间已经无法通过多线程的锁解决同步问题

  • 那么久需要一种更加高级的锁机制,来处理

    跨机器的进程之间的数据同步问题

    —这就是分布式锁.



6.2 分布式锁原理

  • 核心思想 : 当客户端要获取锁,则创建节点,使用完锁则删除该节点

    • 客户端获取锁时,在lock节点下创建临时顺序节点

    • 然后分别获取lock下面的所有子节点,客户端获取到所有子节点之后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁.

    • 如果发现自己创建的节点并非lock所有子节点中最小的,说明还没有获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件.

    • 如果发现比自己小得那个节点被删除,则客户端的Watcher会受到相应通知,此时再次判断自己创建的节点是否是lock子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤继续获取比自己小的一个节点并注册监听.



6.3 模拟12306售票案例

  • 在Curator中有五种锁方案

    • InterProcessSemaphoreMutex : 分布式排它锁 (非可重入锁)

    • InterProcessMutex : 分布式可重入排它锁

    • InterProcessReadWriteLock : 分布式读写锁

    • InterProcessMultiLock : 将多个锁作为单个实体管理的容器

    • InterProcessSemaphoreV2 : 共享信号量

在这里插入图片描述

Runnable

package com.jm.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

import java.util.concurrent.TimeUnit;

public class Ticket12306 implements Runnable{
    private int tickets=10;//数据库的票数

    private InterProcessMutex lock;

    public Ticket12306(){
        //重试策略
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("192.168.36.100:2181")
                .sessionTimeoutMs(60 * 1000)
                .connectionTimeoutMs(15 * 1000)
                .retryPolicy(retryPolicy)
                .build();

        //开启连接
        client.start();

        lock=new InterProcessMutex(client,"/lock");
    }

    @Override
    public void run() {
        while(true){
            //获取锁
            try {
                lock.acquire(3, TimeUnit.SECONDS);
                if(tickets>0){
                    System.out.println(Thread.currentThread()+":"+tickets);
                    Thread.sleep(100);
                    tickets--;
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }finally {
                //释放锁
                try {
                    lock.release();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

Main

package com.jm.curator;

public class LockTest {
    public static void main(String[] args) {
        Ticket12306 ticket12306=new Ticket12306();

        //创建客户端
        Thread t1=new Thread(ticket12306,"携程");
        Thread t2=new Thread(ticket12306,"飞猪");

        t1.start();
        t2.start();
    }
}



7.ZooKeeper集群



7.1 集群简介


最典型集群模式:Master/Slave 模式(主备模式)

。在这种模式中,通常 Master 服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。



7.2 ZooKeeper集群搭建

与创建单机环境类似

修改conf目录下的zoo_sample.cfg为zoo.cfg

修改data目录为指定目录,每个集群成员分别配置

在data目录下创建myid文件,内容分别为1,2,3

分别在conf目录下的zoo.cfg文件加入如下内容

server.1=192.168.149.135:2881:3881
server.2=192.168.149.135:2882:3882
server.3=192.168.149.135:2883:3883
  • 解释:server.服务器ID=服务器IP地址:服务器之间通信端口:服务器之间投票选举端口,搭建伪集群,端口可以写成127.0.0.1



7.3 ZooKeeper集群角色

  • Leader领导者

    • 处理事务请求(增删改)

    • 集群内部各个服务器的调度者

  • Follower跟随者

    • 处理客户端非事务请求(查),转发事务请求给Leader服务器.

    • 参数Leader选举投票

  • Observer观察者

    • 处理客户端非事务请求,转发事务请求给Leader服务器.



7.4 ZooKeeper 集群中的服务器状态


  • LOOKING

    :寻找 Leader。

  • LEADING

    :Leader 状态,对应的节点为 Leader。

  • FOLLOWING

    :Follower 状态,对应的节点为 Follower。

  • OBSERVING

    :Observer 状态,对应节点为 Observer,该节点不参与 Leader 选举。



7.5 ZooKeeper选举流程

  • Leader选举 :

    • Serverid ; 服务器ID

      • 比如有三台机器,编号为1,2,3.编号越大在选择算法中权重越大.
    • Zxid : 数据ID

      • 服务器中存放的最大数据ID,值越大说明数据越新,在选举算法中数据越新则权重越大
    • 在Leader选举的过程中,如果某台ZooKeeper获得了超过半数的选票,则此ZooKeeper就可以成为Leader了


每个节点都有自己携带的唯一id

  • 1.启动第一台机器1,发起选举,自己投票给自己,票数不过半,选举失败,服务器1保持looking状态。

  • 2.启动第二台机器2,发起选举,1和2都先给自己投一票,1发现2 比自己的id大,所以把自己的票也投给服务器2,此时服务器2有2票,服务器1有0票,票数不过半,选举失败。服务器1,2均保持looking 状态

  • 3.启动第三台机器3,发起选举,1,2,3都先给自己投上一票,但是1和2发现服务器3的id比自己大,所以把自己的票都投给服务器3,次数服务器3票数过半,选举成功,服务器3当选leader服务器1,2变为follower,服务器3变为leader.此时已经选举成功了,后面两台机器的选举不会改变结果

  • 4.启动第四台机器

    发起一次选举,此时服务器1,2,3是folllower 状态,不会更改选票信息。此时:服务器3为3票,服务器4为1票。服务器4少数服从多数,更改选票信息为服务器3。服务器4更改状态为follower

  • 5.启动第五台机器

    与服务器4一样投票给3,此时服务器3一共5票,服务器5为0票。服务器5更改状态为follower



版权声明:本文为m0_66689823原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。