zookeeper快速入门(合集)

news2024/11/29 2:33:58

zookeeper作为一个分布式协调框架,它的创建就是为了方便或者简化分布式应用的开发。除了服务注册与发现之外,它还能够提供更多的功能,但是对于入门来说,看这一篇就够了。后续会讲zookeeper的架构设计与原理,比如zookeeper的原子协议,leader选举算法等。欢迎关注

目录

zookeeper安装与启动

一、zookeeper下载

二、安装zookeeper

三、linux下启动zookeeper

四、windows下启动zookeeper

zookeeper基本概念

一、zookeeper的存储结构

二、什么是znode

三、znode节点的四种类型

四、权限控制ACL(Access Control List)

五、事件监听watcher

zookeeper的基本操作

 一、节点的增删改查

二、zookeeper的其他命令

2.1   ls path:列出path下的文件

2.2  stat path:查看节点状态

2.3  ls2 path:列出path节点的子节点及状态

三、其他

在java客户端中操作zookeeper

用zookeeper实现服务注册与发现中心 


zookeeper安装与启动

一、zookeeper下载

镜像站下载:http://mirrors.hust.edu.cn/apache/zookeeper/

记住选择带bin的。从版本3.5.5开始,带有bin名称的包才是我们想要的下载可以直接使用的里面有编译后的二进制的包,而之前的普通的tar.gz的包里面是只是源码的包无法直接使用。不然会爆:

错误: 找不到或无法加载主类 org.apache.zookeeper.server.quorum.QuorumPeerMain

下载后解压到自己的电脑位置,比如:D:\apache-zookeeper-3.5.8-bin

若用wsl,请将apache-zookeeper-3.5.8-bin.tar.gz拷贝到wsl下面后再解压,可以参考WSL访问windows下的文件

解压后目录结构:

  • bin目录

  • zk的可执行脚本目录,包括zk服务进程,zk客户端,等脚本。其中,.sh是Linux环境下的脚本,.cmd是Windows环境下的脚本。
  • conf目录
    配置文件目录。zoo_sample.cfg为样例配置文件,需要修改为自己的名称,一般为zoo.cfg。log4j.properties为日志配置文件。
  • lib
    zk依赖的包。
  • contrib目录
    一些用于操作zk的工具包。
  • recipes目录
    zk某些用法的代码示例

二、安装zookeeper

ZooKeeper的安装包括单机模式安装,以及集群模式安装。

开发情况下由于资源有限一般用单机模式,我们先讲单机模式,让zookeeper跑起来。后面实践案例再讲集群模式。

在启动zookeeper之前,我们需要先修改zookeeper的配置信息,我们先进入zookeeper-3.5.8-bin/conf目录,修改zoo_sample.cfg文件为:

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/tmp/zookeeper(修改为自己的目录)

dataLogDir=/tmp/zookeeper(修改为自己的目录)
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1

主要修改项为dataDir和dataLogDir,dataDir是zookeeper存放数据的地方,dataLogDir是存放zookeeper日志的地方。

如果只配置dataDir,则数据和日志都会创建在dataDir目录下。默认情况下zookeeper会占有8080端口,如果你不想8080端口被占用,增加一行admin.serverPort=8082,指定你自己的端口。

其他配置项的意思我们留到后面再说。  

注意:如果你是在windows下使用zookeeper,需要将zoo_sample.cfg改名为zoo.cfg

三、linux下启动zookeeper

我们需要先启动zookeeper服务端,再启动客户端。

首先进入 zookeeper-3.5.8-bin/bin目录

输入命令 ./zkServer.sh start  (我之前安装的是zookeeper-3.4.13版本,所以图里的版本和文章的版本不一致,不影响)

 可以看到STARTED,zookeeper服务端启动成功了。

接下来启动客户端。输入命令 ./zkCli.sh -server 127.0.0.1:2181 (-server参数就代表我们要连接哪个zookeeper服务端)

连接成功出现:

这样就算启动成功了。如果不放心,可以输入下面两条命令(创建节点和获取节点)测试一下。

四、windows下启动zookeeper

windows和linux大同小异。只不过执行文件从zkServer.sh替换成zkServer.cmd,zkCli.sh替换成zkCli.cmd。

如果你前面没有改名的话,需要将conf目录下的zoo_sample.cfg改名为zoo.cfg

用cmd进入我们zookeeper的bin目录。

输入zkServer.cmd

双击zkCli.cmd

出现:

同样输入create /zk "test" 和get /zk测试一下

至此,zookeeper安装与启动完成。

zookeeper基本概念

一、zookeeper的存储结构

zookeeper的存储结构极其类似于文件系统,都是树形结构,如下图所示。

ZooKeeper's Hierarchical Namespace

与文件系统不同的是,文件系统分为目录和文件,目录是没有数据的。而zookeeper则全部称为节点(znode),每个节点既能保存数据又有孩子节点。

zookeeper的根节点都是“/"。

每一个节点(znode)的命名空间(类似于java中的包名)都由其路径组成。zookeeper称上面这种结构为分层命名空间(Hierarchical Namespace)。

例如,根节点的命名空间为“/",第二层左节点的命名空间为”/app1",右节点的命名空间为“/app2”。

节点的命名空间我们又可以理解为是每个节点的标识符,程序能够根据名称定位到具体是哪个节点。

二、什么是znode

上图中的每个节点在zookeeper中称为znode。在zookeeper推荐在znode中存储的数据不超过1M,这是从性能和效率的角度出发。zookeeper作为协调分布式应用的服务中心,一般是存储状态信息、配置信息和本地数据等等。从设计的初衷上看也不是为了存储大量数据准备的。如果真的要存储大数据,应该把数据存储在别的地方比如数据库上,然后在znode上存储他们的引用。

znode在每次更新数据时,都是全量更新,直接覆盖以前的值,不存在追加或者修改其中某个地方的操作。读取数据也是全部读取。同时,znode的读取和写入都是原子操作。

znode还存储了znode版本信息有三个版本号dataversion(数据版本号)、cversion(子节点版本号)、aclversion(节点所拥有的ACL版本号)。每个版本号其实是一个数字,每次修改对应的版本号就会增加。

比如我们创建一个节点,create /zk "test"后,在linux下用get /zk后返回的信息如下:

test  #znode的数据

cZxid = 0x1e   #znode的创建事务id
ctime = Tue Sep 29 06:45:54 CST 2020 #znode的创建时间
mZxid = 0x1e  #znode的修改事务id
mtime = Tue Sep 29 06:45:54 CST 2020  #修改时间
pZxid = 0x1e  #该节点的子节点列表最后一次修改的版本号,添加子节点或删除子节点就会影响子节点列表,但是修改子节点的数据内容则不影响该值,孙子节点的操作也不影响
cversion = 0  #children节点的版本号,每次子节点修改加1,下同
dataVersion = 0 #数据版本号
aclVersion = 0  #ACL(权限控制列表)版本号
ephemeralOwner = 0x0  #如果节点为临时节点,那么它的值为这个节点拥有者的session ID;如果该节点不是ephemeral节点, ephemeralOwner值为0.
dataLength = 4 #数据长度
numChildren = 0 #孩子节点的数量

版本号的作用

Zookeeper里面的版本号和我们理解的版本号不同,它表示的是对数据节点的内容、子节点列表或者ACL信息的修改次数。节点创建时dataversion、aclversion,cversion都为0,每次修改响应内容其对应的版本号加1。

这个版本号的用途就和分布式场景的一个锁概念有关。比如演出售票中的一个座位,显然每个场次中的每个座位都只有一个,不可能卖出2次。如果A下单的时候显示可售,他想买,那么为了保证他可以下单成功,此时别人就不能买。这时候就需要有一种机制来保证同一时刻只能有一个人去修改该座位的库存。这就用到了锁。锁有悲观锁和乐观锁。

  • 悲观锁:它会假定所有不同事务的处理一定会出现干扰,数据库中最严格的并发控制策略,如果一个事务A正在对数据处理,那么在整个事务过程中,其他事务都无法对这个数据进行更新操作,直到A事务释放了这个锁。

  • 乐观锁:它假定所有不同事务的处理不一定会出现干扰,所以在大部分操作里不许加锁,但是既然是并发就有出现干扰的可能,如何解决冲突就是一个问题。在乐观锁中当你在提交更新请求之前,你要先去检查你读取这个数据之后该数据是否发生了变化,如果有那么你此次的提交就要放弃,如果没有就可以提交。

Zookeeper中的版本号就是乐观锁,你修改节点数据之前会读取这个数据并记录该数据版本号,当你需要更新时会携带这个版本号去提交,如果你此时携带的版本号(就是你上次读取出来的)和当前节点的版本号相同则说明该数据没有被修改过,那么你的提交就会成功,如果提交失败说明该数据在你读取之后和提交之前这段时间内被修改了。

三、znode节点的四种类型

zookeeper有四种节点,临时节点,临时顺序节点,持久节点和持久顺序节点。

   3.1  临时节点

       当zookeeper的客户端申请zookeeper服务端创建临时节点时,节点的ephemeralOwner为此客户端与服务端的sessionId。当客户端与服务端断开连接时,临时节点也会被删除。

   3.2  临时顺序节点

当创建的是临时顺序节点时,会在节点名称后面增加序号,不断递增。

举个例子。比如申请创建的是/zk临时顺序节点,如果此时服务端没有/zk的节点,那么就会创建/zk-1节点。这时第二个请求过来了,也是创建临时顺序节点/zk,那么服务端就会创建/zk-2,依次是/zk-3,/zk-4......不断递增下去,一直到2^32。与临时节点一样的是,当客户端断开链接时,临时顺序节点也会被删除。

   3.3 持久节点

顾名思义,就是创建后除非主动删除,否则会一直存在的节点。

   3.4 持久顺序节点

当创建的是持久顺序节点时,举个例子。比如申请创建的是/zk临时顺序节点,如果此时服务端没有/zk的节点,那么就会创建/zk-1节点。这是有第二个请求过来了,也是创建临时顺序节点/zk,那么服务端就会创建/zk-2,不断递增下去,一直到2^32。与持久节点一样的是,创建后除非主动删除,否则会一直存在。

四、权限控制ACL(Access Control List)

ZooKeeper的权限控制是基于每个znode节点的,需要对每个节点设置权限。

zookeeper每个节点的权限类型有五种:create、read、delete、write、admin

CREATE:创建子节点的权限

READ:读节点数据的权限包括获取它的子节点列表的权限

DELETE:有删除子节点的权限

WRITE:写节点数据的权限

ADMIN:可以设置节点访问控制列表权限

zookeeper的授权策略(Scheme)有5种:world、auth、digest、ip、x509(有一些博客写了Super即超级管理员模式这个类型,可能是老版本,从3.4开始,官方文档介绍ACL的时候就没看到super了)

world:默认方式,全部都能访问,

auth:认证用户可以使用,(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户)

digest:即用户名:密码这种方式认证,这也是业务系统中最常用的。用 username:password 字符串来产生一个MD5串,然后该串被用来作为ACL ID。认证是通过明文发送username:password 来进行的,当用在ACL时,表达式为username:base64 ,base64是password的SHA1摘要的编码。

ip:使用客户端的主机IP作为ACL ID 。这个ACL表达式的格式为addr/bits ,此时addr中的有效位与客户端addr中的有效位进行比对。

       x509 :uses the client X500 Principal as an ACL ID identity. The ACL expression is the exact X500 Principal name of a client. When using the secure port, clients are automatically authenticated and their auth info for the x509 scheme is set.

ACL 权限控制,使用:scheme:id:perm 来标识,主要涵盖 3 个方面:权限模式(Scheme):授权的策略   授权对象(ID):授权的对象    权限类型(Permission):授予的权限。权限模式和权限类型就是上文所讲,授权对象(ID)很好理解,就是权限赋予的用户或者一个实体,例如:IP 地址或者机器。

五、事件监听watcher

zookeeper可以向节点注册一个watcher,用来监听节点的变化。当节点状态发生变化时,比如说被删除或者修改,那么它就会发送通知到监听这个节点的客户端,客户端因此而做出自己的操作。

每个注册仅使用一次,也就是当发生一次节点改变,通知完客户端之后,如果你需要这个节点下次发生改变时也发送通知到这个客户端,那么就需要再注册一次监听。

zookeeper的基本操作

在zookeeper的bin目录下,输入./zkServer.sh start和./zkCli.sh启动服务端和客户端,然后我们就可以进行zookeeper的基本操作了

 一、节点的增删改查

zookeeper节点的增删改查命令很简单,唯一需要注意的是create命令有两个参数,-s代表顺序节点,-e代表临时节点。

我们先用create /temp 123命令创建一个名为temp的znode节点,123是这个节点保存的data值。

用get /temp命令查看

如果你输入的是get temp,那么会出现Command failed: java.lang.IllegalArgumentException: Path must start with /character错误,这是因为在zookeeper中,没有相对路径的概念,所有的节点都需要用绝对路径表示,也即所有节点名称都会以“/”开头。

如果我们要创建临时节点,则给create命令增加一个-e的参数。

修改temp节点的数据,set /temp  456

再用get /temp查看

可以看到,/temp节点的数据已经从123变成了456。在zookeeper中,对于数据的修改都是全量修改,没有只修改某一部分这种说法。这也是为什么zookeeper的修改命令是set而不是update的原因。

需要注意的是,但我们修改了数据之后,可以看到mZxid(修改事务id)已经从原来的0X26变成了0x27。

如果要删除某个节点,则用delete path即可。

需要注意的是:

set path data [version]命令,如果我们多次修改,会发现  dataVersion ,也就是数据版本,在不停得发生变化(自增)如果我们在set的时候手动去指定了版本号,就必须和上一次查询出来的结果一致,否则 就会报错。

这个可以用于我们在修改节点数据的时候,保证我们修改前数据没被别人修改过。因为如果别人修改过了,我们这次修改是不会成功的

delete path [version]

删除指定节点数据,其version参数的作用于set指定一致

二、zookeeper的其他命令

2.1   ls path:列出path下的文件

与linux命令类似,ls命令用于列出给定路径下的zookeeper节点

2.2  stat path:查看节点状态

我们查看持久节点temp和临时节点short的状态,主要不同是画红线部分,从ephemeralOwner的值可以判断这个节点是持久节点还是临时节点。0X0代表的是持久节点。如果节点为临时节点,那么它的值为这个节点拥有者的session ID。

2.3  ls2 path:列出path节点的子节点及状态

我们先给temp节点创建一个child1的子节点,接着用ls2 /temp查看它的子节点和状态。

三、其他

我们可以输入-h获取zookeeper的所有命令

在java客户端中操作zookeeper

 先启动zookeeper服务端。

在maven引入zookeeper依赖。

<dependency>
   <groupId>org.apache.hadoop</groupId>
   <artifactId>zookeeper</artifactId>
   <version>3.3.1</version>
</dependency>

org.apache.zookeeper.Zookeeper是客户端入口主类,负责建立与server的会话。它提供了以下 所示几类主要方法。 

 

在java中启动客户端,注册一个watcher监听链接的建立。

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

public class ZookeeperClient {
    private static final String connectString = "127.0.0.1:2181";
    private static final int sessionTimeout = 2000;

    private static ZooKeeper zkClient = null;

    public void init() throws Exception {
        zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                // 收到事件通知后的回调函数(应该是我们自己的事件处理逻辑)
                System.out.println("zookeeper链接建立");
            }
        });
    }
}

在main方法里测试我们的init方法,用Thread.sleep方法等待zookeeper连接创建,不然在zookeeper客户端建立连接之前主线程就已经退出。

    public static void main(String[] args)throws Exception{
        new ZookeeperClient().init();
        Thread.sleep(5000);
    }

控制台输出:

接下来我们创建一个名为“/java”的节点,节点数据为“data”,ZooDefs.Ids.OPEN_ACL_UNSAFE的意思是不节点能被所有人访问,CreateMode.PERSISTENT:节点的类型为持久节点。

    public void createZnode()throws Exception{
        zkClient.create("/java", "data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

判断节点是否存在,false代表不注册监听事件,如果是true,则注册我们在new zookeeper方法里面传递的watcher。

    public void testExist() throws Exception{
        Stat stat = zkClient.exists("/java", false);
        System.out.println(stat==null?"节点不存在":"节点存在");
    }

测试一下我们的方法。

    public static void main(String[] args)throws Exception{
        new ZookeeperClient().init();
        Thread.sleep(5000);
        new ZookeeperClient().createZnode();
        new ZookeeperClient().testExist();
    }

 获取节点的数据。false和上面exists方法参数含义一样,表示不注册连接建立时的watcher,第三个stat对象则存储了除了节点数据之外的其他信息,如czxid、mzxid等。如果为null则表示不保存节点的这些信息。

    public void getNodeData()throws Exception{
        byte[] res = zkClient.getData("/java",false,new Stat());
        System.out.println(new String(res));
    }

同样测试我们的方法。

    public static void main(String[] args)throws Exception{
        new ZookeeperClient().init();
        Thread.sleep(3000);
        new ZookeeperClient().getNodeData();
    }

 获取ACL控制列表

    public void getACl()throws Exception{
        List<ACL> res = zkClient.getACL("/java",new Stat());
        for(ACL acl : res){
            System.out.println(acl.getId().toString()+acl.getPerms());
        }
    }

测试:

    public static void main(String[] args)throws Exception{
        new ZookeeperClient().init();
        Thread.sleep(3000);
        new ZookeeperClient().getACl();
    }

在/java下创建子节点,获取子节点列表。

    public void createChildZnode()throws Exception{
        zkClient.create("/java/child", "child data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        zkClient.create("/java/child2", "child2 data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }
    public void getChildNode()throws Exception{
        List<String> res = zkClient.getChildren("/java",false);
        for(String s : res){
            System.out.println(s);
        }
    }

测试:

    public static void main(String[] args)throws Exception{
        new ZookeeperClient().init();
        Thread.sleep(3000);
        new ZookeeperClient().createChildZnode();
        new ZookeeperClient().getChildNode();
    }

全部代码如下:

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;

import java.util.List;

public class ZookeeperClient {
    private static final String connectString = "127.0.0.1:2181";
    private static final int sessionTimeout = 2000;

    private static ZooKeeper zkClient = null;

    public void init() throws Exception {
        zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                // 收到事件通知后的回调函数(应该是我们自己的事件处理逻辑)
                System.out.println("zookeeper链接建立");
            }
        });
    }
    public void createZnode()throws Exception{
        zkClient.create("/java", "data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }
    public void createChildZnode()throws Exception{
        zkClient.create("/java/child", "child data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        zkClient.create("/java/child2", "child2 data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }
    public void getChildNode()throws Exception{
        List<String> res = zkClient.getChildren("/java",false);
        for(String s : res){
            System.out.println(s);
        }
    }
    public void testExist() throws Exception{
        Stat stat = zkClient.exists("/java", false);
        System.out.println(stat==null?"节点不存在":"节点存在");
    }
    public void getNodeData()throws Exception{
        byte[] res = zkClient.getData("/java",false,new Stat());
        System.out.println(new String(res));
    }

    public void getACl()throws Exception{
        List<ACL> res = zkClient.getACL("/java",new Stat());
        for(ACL acl : res){
            System.out.println(acl.getId().toString()+acl.getPerms());
        }
    }
    public static void main(String[] args)throws Exception{
        new ZookeeperClient().init();
        Thread.sleep(3000);
        new ZookeeperClient().createChildZnode();
        new ZookeeperClient().getChildNode();
    }

}

用zookeeper实现服务注册与发现中心 

经过前面的讲解,我们已经对zookeeper建立起初步的概念,这里就来做一个小小的实践,用zookeeper实现一个简单版的服务注册与发现中心。

zookeeper的一个常见功能就是作为服务注册与发现中心。

我们先创建一个节点/services。

        Stat stat = zkClient.exists("/services",false);
        if (stat == null ){
            zkClient.create("/services","".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }

每当有一个服务上线时,我们就向我们的服务注册与发现中心zookeeper注册我们的应用。

比如,我们注册一个user服务,服务地址是localhost:8080,那么我们就在/services下面建立一个user子节点,子节点数据为user服务的真实url地址,比如localhost:8080,子节点类型为临时节点。

    public void registerService()throws Exception{
        zkClient.create("/services/user","localhost:8080".getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);

    }

当我们向user请求服务时,首先通过/services节点获取user服务,判断user服务是否存在。进而获取它的地址,发起真正的请求。同时,我们注册一个监听事件,监听节点的状态变化。当user服务出现故障或其他因素而下线时,/services/user节点会被删除,zookeeper server会通知到监听这个节点的客户端,从而使客户端做出自己的响应,同样的,当user服务上线或地址修改,客户端也能收到通知。

    public void invokeUserService()throws Exception{
        Stat stat = zkClient.exists("/services/user",false);
        if (stat == null){
            System.out.println("未能找到user服务,服务未注册或已下线");
        }
        byte[] url = zkClient.getData("/services/user", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                if (watchedEvent.getType() == Event.EventType.NodeDeleted){
                    System.out.println("服务下线");
//                    处理业务逻辑
                }
                if (watchedEvent.getType() == Event.EventType.NodeCreated){
                    System.out.println("服务上线");
//                    处理业务逻辑
                }
                if (watchedEvent.getType() == Event.EventType.NodeDataChanged){
                    System.out.println("服务地址修改了");
                }
            }
        }, null);
//        处理业务逻辑
         System.out.println("向"+new String(url)+"发起请求");
    }

如果对前面有印象的话,应该记得zookeeper的watcher只触发一次,当节点状态改变一次之后,节点状态的第二次改变就不能监听到了。为了能够持续监听,我们需要修改一下我们的代码。

我们把判断服务上线的代码挪到上面来,并且在下面的监听事件里回调invokeUserService方法,实现持续监听的功能。

为了简单易懂,这里代码写得并不够好,如果是实际项目,需要再做点拆分与封装。

    public void invokeUserService()throws Exception{
        Stat stat = zkClient.exists("/services/user",false);
        if (stat == null){
            System.out.println("未能找到user服务,服务未注册或已下线");
            zkClient.exists("/services/user", new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
                    if (watchedEvent.getType() == Event.EventType.NodeCreated){
                        System.out.println("服务上线");
//                    处理业务逻辑
                    }
                }
            });
        }else{
            byte[] url = zkClient.getData("/services/user", new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
                    if (watchedEvent.getType() == Event.EventType.NodeDeleted){
                        System.out.println("服务下线");
//                    处理业务逻辑
                    }
                    if (watchedEvent.getType() == Event.EventType.NodeDataChanged){
                        System.out.println("服务地址修改了");
                    }
                    try {
                        invokeUserService();
                    }catch (Exception e){

                    }
                }
            }, null);
//        处理业务逻辑
            System.out.println("向"+new String(url)+"发起请求");
        }

    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1529987.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL 数据库设计范式

第一范式&#xff08;1NF&#xff09; 每一列都是不可分割的原子数据项第二范式&#xff08;2NF&#xff09; 在1NF的基础上&#xff0c;非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖) 1.函数依赖A->B&#xff0c;如果通过A属性(属性组)的值…

[LeetBook]【学习日记】排序算法——归并排序

主要思想 归并排序是一种分治算法&#xff0c;其排序过程包括分和治分是指将要排序的序列一分为二、二分为四&#xff0c;直到单个序列中只有一个数治是指在分完后&#xff0c;将每两个元素重新组合&#xff0c;四合为二、二合为一&#xff0c;最终完成排序 图片作者&#xf…

python 实现把内层文件夹的文件,复制/剪切到外层文件夹

文章目录 如下图所示&#xff0c;收集了很多省市的文件&#xff0c;结果发现市一级的文件与区县一级的文件混在一起了。 接下来使用代码实现&#xff1a; 根据关键词识别出 市一级的文件&#xff1b;把市一级的文件&#xff0c;移动或者复制到省文件夹下&#xff1b;给出了py…

FPGA高端项目:FPGA基于GS2971+GS2972架构的SDI视频收发+GTX 8b/10b编解码SFP光口传输,提供2套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博主所有FPGA工程项目-->汇总目录本博已有的 SDI 编解码方案本方案的SDI接收发送本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收HLS图像缩放HLS多路视频拼接应用本方案的SDI…

前端静态开发案例-基于H5C3开发的仿照视频网站的前端静态页面-2 样式表部分和效果展示

原创作者&#xff1a;田超凡&#xff08;程序员田宝宝&#xff09; 版权所有&#xff0c;引用请注明原作者&#xff0c;严禁复制转载 charset "utf-8"; /* 程序员田宝宝原创版权所有&#xff0c;仿冒必究&#xff0c;该界面是仿照某视频网站官网开发的静态页面 */ …

【Thread 线程】线程的方法与状态

SueWakeup 个人中心&#xff1a;SueWakeup 系列专栏&#xff1a;学习Java 个性签名&#xff1a;保留赤子之心也许是种幸运吧 本文封面由 凯楠&#x1f4f7; 友情赞助播出&#xff01; 目录 一个线程的生命周期 线程终止的原因 线程的方法 Thread 类的静态方法 1. 设置线程…

linux单机部署hadoop

1.下载安装包 https://archive.apache.org/dist/hadoop/common/ 2.上传压缩 3.修改配置文件 1)设置JDK的路径 cd /usr/local/software/hadoop-3.1.3/etc/hadoop vi hadoop-env.sh export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.402.b06-1.el7_9.x86_64/ 查看…

【数据结构和算法初阶(C语言)】二叉树的顺序结构--堆的实现/堆排序/topk问题详解---二叉树学习日记②1

目录 ​编辑 1.二叉树的顺序结构及实现 1.1 二叉树的顺序结构 2 堆的概念及结构 3 堆的实现 3.1堆的代码定义 3.2堆插入数据 3.3打印堆数据 3.4堆的数据的删除 3.5获取根部数据 3.6判断堆是否为空 3.7 堆的销毁 4.建堆以及堆排序 4.1堆排序---是一种选择排序 4.2升序建大堆&a…

微信小程序 canvas层级过高覆盖原生组件

一、背景 微信小程序中使用signature第三方插件完成签名效果&#xff0c;但真机调试时发现canvas层级过高遮挡了按钮 二、具体问题 问题原因&#xff1a;签名后点击按钮无法生效 问题代码&#xff1a; <template><view class"sign_page" v-cloak>&l…

nodejs基于vue超市信息管理系统flask-django-php

互联网的快速发展&#xff0c;使世界各地的各种组织的管理方式发生了根本性的变化&#xff0c;我国政府、企业等组织在上个世纪90年代就已开始考虑使用互联网来管理信息。由于以前的种种因素&#xff0c;比如网络的普及率不高&#xff0c;用户对它的认知度不够&#xff0c;以及…

计算机网络:数据交换方式

计算机网络&#xff1a;数据交换方式 电路交换分组交换报文交换传输对比 本博客介绍计算机之间数据交换的三种方式&#xff0c;分别是电路交换、分组交换以及报文交换。 电路交换 我们首先来看电路交换&#xff0c;在电话问世后不久&#xff0c;人们就发现要让所有的电话机都…

基于Spring Boot网络相册设计与实现

摘 要 网络相册设计与实现的目的是让使用者可以更方便的将人、设备和场景更立体的连接在一起。能让用户以更科幻的方式使用产品&#xff0c;体验高科技时代带给人们的方便&#xff0c;同时也能让用户体会到与以往常规产品不同的体验风格。 与安卓&#xff0c;iOS相比较起来&am…

【OCR】OCR开源文字识别工具

在日常的工作中&#xff0c;例如自动化测试开展时&#xff0c;经常涉及到一些验证码识别、文本识别、图像识别的场景&#xff0c;市面上虽也有很多识别工具&#xff0c;但质量、准确性参差不齐。 今天给大家推荐一个开源OCR项目:Umi-OCR&#xff0c;功能很强大&#xff0c;而且…

jquery 列表框可以手动修改(调用接口修改)

类似于这种 直接上代码 列表框 <td>//目的主要是获取属性名的(要更改的属性名) 在下面juqery的这一行(var field $(thisobj).prev(input).attr(name);)有体现<input type"hidden" name"voyage" value"${M_PSI_PERIOD_INFO.port}">…

Vulnhub - Hacker_Kid

希望和各位大佬一起学习&#xff0c;如果文章内容有错请多多指正&#xff0c;谢谢&#xff01; 个人博客链接&#xff1a;CH4SER的个人BLOG – Welcome To Ch4sers Blog Hacker_Kid 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/hacker-kid-101,719/ WP 参考&…

路由器里如何设置端口映射?

在互联网时代&#xff0c;我们经常需要将内部网络的服务暴露到公网以便其他人访问。直接将内部网络暴露在公网上存在一定的安全风险。为了解决这个问题&#xff0c;我们可以利用路由器里设置端口映射来实现将特定端口的访问请求转发到内部网络的特定设备上。 端口映射的原理 端…

机器人离散化阻抗控制

机器人离散化阻抗控制是一种控制策略&#xff0c;它结合了阻抗控制的思想与离散化方法&#xff0c;以实现对机器人运动与外力之间动态关系的精细调节。这种控制方法旨在使机器人在与环境交互时能够表现出期望的阻抗特性&#xff0c;从而实现对接触力和位置的精确控制。 在离散…

AnyGo for Mac最新激活版:位置模拟软件打破地域限制

AnyGo for Mac&#xff0c;一款专为Mac用户打造的位置模拟软件&#xff0c;让您能够轻松打破地域限制&#xff0c;畅享无限可能。 软件下载&#xff1a;AnyGo for Mac v7.0.0最新激活版 通过AnyGo&#xff0c;您可以随时随地模拟出任何地理位置&#xff0c;无论是国内热门景点还…

轻松解锁微博视频:基于Perl的下载解决方案

引言 随着微博成为中国最受欢迎的社交平台之一&#xff0c;其内容已经变得丰富多彩&#xff0c;特别是视频内容吸引了大量用户的关注。然而&#xff0c;尽管用户对微博上的视频内容感兴趣&#xff0c;但却面临着无法直接下载这些视频的难题。本文旨在介绍一个基于Perl的解决方…

3D开发工具HOOPS如何助力3D项目实现扩展现实技术?

在当今数字化时代&#xff0c;扩展现实&#xff08;Augmented Reality&#xff0c;AR&#xff09;技术的应用已经逐渐深入到各行各业&#xff0c;为用户带来了前所未有的沉浸式体验。而在实现这种技术的开发过程中&#xff0c;HOOPS技术的运用无疑是一种强大的助力。HOOPS是一种…