月份:2018年9月

数据结构(二)数组

从这篇开始就研究数组这个最基本的数据结构。

数组基础:

把数据码成一排进行存放

image.png

如上图所示,数组应该有一个自己的名字用来和其他数组区分,数组的索引是从0开始的,也就是想取到数组中的第三个数字,那么得取索引为2的值,即arr[2];

接下来说一下Java中的数组,Java数组中的每一个元素需要我们存放相同类型的元素,下面我们来演示一下Java数组的基本操作。

Java数组:

package com.wjy329;

public class Main {

    public static void main(String[] args) {
        //初始化一个int数组
       int[] scores = new int[10];
        //结果输出为: 0 0  0  0 0 。。。
        //默认的值都为0
       for(int i : scores){
           System.out.println(i);
        }
        //给数组赋值为数组的索引
        //打印结果:0 1 2 3 4 5 。。。
        for(int j=0;j < scores.length;j++){
           scores[j] = j;
            System.out.println(scores[j]);
        }
        
    }
}

当然,上面是最简单的Java数组的操作;

数组最大的优点是快速查询,可以直接根据索引查找,所以数组最好应用于“索引有语意”的情况。

Java为我们提供的数组不能进行添加和删除操作,下面我们来自己做一个我们自己的数组来实现此类功能;

封装我们自己的数组:

开始的时候,我们只需要简单的声明以及写出每个方法即可,最后我会将完整的封装的数组代码写上。

package com.wjy329;
/**
 * @description: 自定义数组
 * @author: wjy329
 * @create: 2018-09-30 17:04
 **/
public class Array {
    private int[] data;
    private int size;

    //构造函数,传入数组容量,size为数组中的元素个数
    public Array(int capacity){
        data = new int[capacity];
        size= 0;
    }
    //无参数的构造函数,数组容量为10
    public Array(){
        this(10);
    }
    //获取数组中的元素个数
    public int getSize(){
        return size;
    }
    //获取数组的容量
    public int getCapacity(){
        return data.length;
    }
    //返回数组是否为空
    public boolean isEmpty(){
        return size == 0;
    }
}

以上就是自定义数组的初始化和一些简单获取数组容量和元素个数的方法;我们对数组需要进行等操作,所以下面我们也将一一实现。

增:

//向所有元素后添加一个元素
public void addLast(int e){
    if(size == data.length){
        throw new IllegalArgumentException("添加元素失败,数组容量已满");
    }
    data[size] = e;
    size ++;
    //复用添加方法
    //add(size,e);
}
//在所有元素前添加一个元素e
public void addFirst(int e){
    add(0,e);
}

//在第index位置插入一个新元素e
public void add(int index,int e){
    if(size == data.length){
        throw new IllegalArgumentException("添加元素失败,数组容量已满");
    }
    if(index < 0 || index > size){
        throw new IllegalArgumentException("添加元素失败,插入位置过大或为负数");
    }
    for(int i = size - 1;i >= index;i --){
        data[i + 1] = data[i];
    }
    data[index]=e;
    size++;
}

代码很好理解,我主要的说在指定位置添加元素方法,我们从后往前依次向后移一位,直到指定位置停止;size-1 为最后一个数的索引值,然后将其后移一位,接下来再后移其他位置的数据;

删:

//从数组中删除index位置的元素,返回删除的元素
public int remove(int index){
    if(index < 0 || index >= size){
        throw new IllegalArgumentException("非法索引");
    }
    int del = data[index];
    for(int i = index + 1;i < size;i++){
        data[i-1] = data[i];
    }
    size--;
    return del;
}

上面是删除指定位置元素,删除的思路就是指定位置后的元素都向前移一位即可;下面是删除指定元素,需要用到后面的查找方法,查找到元素的索引,然后删除。

//删除数组中指定的元素
public void delElement(int e){
    int index = find(e);
    if(index != -1){
        remove(index);
    }
}

查、

//获取指定位置的元素
 int get(int index){
    if(index < 0 || index >= size){
        throw new IllegalArgumentException("非法索引");
    }
    return data[index];
}
//将index位置的元素换为e
void set(int index,int e){
    if(index < 0 || index >= size){
        throw new IllegalArgumentException("非法索引");
    }
     data[index] = e;
}

//查找数组中是否存在元素e
public boolean contains(int e){
    for(int i=0;i < size; i++){
        if(data[i] == e){
            return true;
        }
    }
    return false;
}

//查找数组中元素e所在的索引,如果不存在元素e,返回-1
public int find(int e){
    for(int i=0;i < size; i++){
        if(data[i] == e){
            return i;
        }
    }
    return -1 ;
}

 上面就是基本的思路,当然其中有些不足,比如相同元素的查找和删除等,上面的代码只会查找一个或者删除一个,并不会全部删除,这是因为主要演示的是思路和逻辑,会了上面的,后面的设计也就会显得轻车熟路了。

下面给上封装数组的完整代码:

Array.java:

package com.wjy329;
/**
 * @description: 自定义数组
 * @author: wjy329
 * @create: 2018-09-30 17:04
 **/
public class Array {
    private int[] data;
    private int size;

    //构造函数,传入数组容量,size为数组中的元素个数
    public Array(int capacity){
        data = new int[capacity];
        size= 0;
    }
    //无参数的构造函数,数组容量为10
    public Array(){
        this(10);
    }
    //获取数组中的元素个数
    public int getSize(){
        return size;
    }
    //获取数组的容量
    public int getCapacity(){
        return data.length;
    }
    //返回数组是否为空
    public boolean isEmpty(){
        return size == 0;
    }
    //向所有元素后添加一个元素
    public void addLast(int e){
        if(size == data.length){
            throw new IllegalArgumentException("添加元素失败,数组容量已满");
        }
        data[size] = e;
        size ++;
        //复用添加方法
        //add(size,e);
    }
    //在所有元素前添加一个元素e
    public void addFirst(int e){
        add(0,e);
    }

    //在第index位置插入一个新元素e
    public void add(int index,int e){
        if(size == data.length){
            throw new IllegalArgumentException("添加元素失败,数组容量已满");
        }
        if(index < 0 || index > size){
            throw new IllegalArgumentException("添加元素失败,插入位置过大或为负数");
        }
        for(int i = size - 1;i >= index;i --){
            data[i + 1] = data[i];
        }
        data[index]=e;
        size++;
    }
    //获取指定位置的元素
     int get(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
        return data[index];
    }
    //将index位置的元素换为e
    void set(int index,int e){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
         data[index] = e;
    }

    //查找数组中是否存在元素e
    public boolean contains(int e){
        for(int i=0;i < size; i++){
            if(data[i] == e){
                return true;
            }
        }
        return false;
    }

    //查找数组中元素e所在的索引,如果不存在元素e,返回-1
    public int find(int e){
        for(int i=0;i < size; i++){
            if(data[i] == e){
                return i;
            }
        }
        return -1 ;
    }

    //从数组中删除index位置的元素,返回删除的元素
    public int remove(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
        int del = data[index];
        for(int i = index + 1;i < size;i++){
            data[i-1] = data[i];
        }
        size--;
        return del;
    }
    //删除数组中的第一个元素
    public int delFirst(){
        return remove(0);
    }
    //删除数组中的最后一个元素
    public int delLast(){
        return remove(size -1);
    }
    //删除数组中指定的元素
    public void delElement(int e){
        int index = find(e);
        if(index != -1){
            remove(index);
        }
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("Array:size = %d,capacity = %d\n",size,data.length));
        res.append("[");
        for(int i=0;i < size;i++){
            res.append(data[i]);
            if(i != size - 1){
                res.append(", ");
            }
        }
        res.append("]");
        return res.toString();
    }
}

上面仅仅是对int型数组的探索,为了让我们自定义的数组适合任意类型,我们将其定义为范型数组,下面直接给出代码:

package com.wjy329;
/**
 * @description: 自定义数组
 * @author: wjy329
 * @create: 2018-09-30 17:04
 **/
public class Array<E> {
    private E[] data;
    private int size;

    //构造函数,传入数组容量,size为数组中的元素个数
    public Array(int capacity){
        data = (E[])new Object[capacity];
        size= 0;
    }
    //无参数的构造函数,数组容量为10
    public Array(){
        this(10);
    }
    //获取数组中的元素个数
    public int getSize(){
        return size;
    }
    //获取数组的容量
    public int getCapacity(){
        return data.length;
    }
    //返回数组是否为空
    public boolean isEmpty(){
        return size == 0;
    }
    //向所有元素后添加一个元素
    public void addLast(E e){
        if(size == data.length){
            throw new IllegalArgumentException("添加元素失败,数组容量已满");
        }
        data[size] = e;
        size ++;
        //复用添加方法
        //add(size,e);
    }
    //在所有元素前添加一个元素e
    public void addFirst(E e){
        add(0,e);
    }

    //在第index位置插入一个新元素e
    public void add(int index,E e){
        if(size == data.length){
            throw new IllegalArgumentException("添加元素失败,数组容量已满");
        }
        if(index < 0 || index > size){
            throw new IllegalArgumentException("添加元素失败,插入位置过大或为负数");
        }
        for(int i = size - 1;i >= index;i --){
            data[i + 1] = data[i];
        }
        data[index]=e;
        size++;
    }
    //获取指定位置的元素
     public E get(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
        return data[index];
    }
    //将index位置的元素换为e
    public void set(int index,E e){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
         data[index] = e;
    }

    //查找数组中是否存在元素e
    public boolean contains(E e){
        for(int i=0;i < size; i++){
            if(data[i].equals(e)){
                return true;
            }
        }
        return false;
    }

    //查找数组中元素e所在的索引,如果不存在元素e,返回-1
    public int find(E e){
        for(int i=0;i < size; i++){
            if(data[i].equals(e)){
                return i;
            }
        }
        return -1 ;
    }

    //从数组中删除index位置的元素,返回删除的元素
    public E remove(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
        E del = data[index];
        for(int i = index + 1;i < size;i++){
            data[i-1] = data[i];
        }
        size--;
        return del;
    }
    //删除数组中的第一个元素
    public E delFirst(){
        return remove(0);
    }
    //删除数组中的最后一个元素
    public E delLast(){
        return remove(size -1);
    }
    //删除数组中指定的元素
    public void delElement(E e){
        int index = find(e);
        if(index != -1){
            remove(index);
        }
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("Array:size = %d,capacity = %d\n",size,data.length));
        res.append("[");
        for(int i=0;i < size;i++){
            res.append(data[i]);
            if(i != size - 1){
                res.append(", ");
            }
        }
        res.append("]");
        return res.toString();
    }
}

动态数组:

我们使用上面的代码一段时间后发现,这个TM的还是静态数组啊,还是指定的大小没法扩充啊,还是毫无卵用啊。。。别急,接下来,我们就添加几行代码使其动态的扩展。

动态的思路也是很简单的,当我们添加元素时,如果发现已经满了,那么我们再新建一个比之前更大的数组(假设为2倍),然后将其值都赋值给新的数组,这样实现了添加的扩容;当我们删除到一定量的时候,我们发现太多的空间被浪费了,那么我们将其按照一定的比例缩小数组(当数组内元素个数为当前容量的一半时,我们就缩减为一半),以此来保证数组的空间合理性。

扩容的方法代码:

private void resize(int newCapacity) {
    E[] newData = (E[])new Object[newCapacity];
    for(int i= 0;i < size;i++){
        newData[i] = data[i];
    }
    data = newData;
}

最终代码:

package com.wjy329;
/**
 * @description: 自定义数组
 * @author: wjy329
 * @create: 2018-09-30 17:04
 **/
public class Array<E> {
    private E[] data;
    private int size;

    //构造函数,传入数组容量,size为数组中的元素个数
    public Array(int capacity){
        data = (E[])new Object[capacity];
        size= 0;
    }
    //无参数的构造函数,数组容量为10
    public Array(){
        this(10);
    }
    //获取数组中的元素个数
    public int getSize(){
        return size;
    }
    //获取数组的容量
    public int getCapacity(){
        return data.length;
    }
    //返回数组是否为空
    public boolean isEmpty(){
        return size == 0;
    }
    //向所有元素后添加一个元素
    public void addLast(E e){
        if(size == data.length){
            throw new IllegalArgumentException("添加元素失败,数组容量已满");
        }
        data[size] = e;
        size ++;
        //复用添加方法
        //add(size,e);
    }
    //在所有元素前添加一个元素e
    public void addFirst(E e){
        add(0,e);
    }

    //在第index位置插入一个新元素e
    public void add(int index,E e){
        if(index < 0 || index > size){
            throw new IllegalArgumentException("添加元素失败,插入位置过大或为负数");
        }
        if(size == data.length){
            resize(2 * data.length);
        }

        for(int i = size - 1;i >= index;i --){
            data[i + 1] = data[i];
        }
        data[index]=e;
        size++;
    }

    //获取指定位置的元素
     public E get(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
        return data[index];
    }
    //将index位置的元素换为e
    public void set(int index,E e){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
         data[index] = e;
    }

    //查找数组中是否存在元素e
    public boolean contains(E e){
        for(int i=0;i < size; i++){
            if(data[i].equals(e)){
                return true;
            }
        }
        return false;
    }

    //查找数组中元素e所在的索引,如果不存在元素e,返回-1
    public int find(E e){
        for(int i=0;i < size; i++){
            if(data[i].equals(e)){
                return i;
            }
        }
        return -1 ;
    }

    //从数组中删除index位置的元素,返回删除的元素
    public E remove(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
        E del = data[index];
        for(int i = index + 1;i < size;i++){
            data[i-1] = data[i];
        }
        size--;
        if(size == data.length/2){
            resize(data.length/2);
        }

        return del;
    }
    //删除数组中的第一个元素
    public E delFirst(){
        return remove(0);
    }
    //删除数组中的最后一个元素
    public E delLast(){
        return remove(size -1);
    }
    //删除数组中指定的元素
    public void delElement(E e){
        int index = find(e);
        if(index != -1){
            remove(index);
        }
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("Array:size = %d,capacity = %d\n",size,data.length));
        res.append("[");
        for(int i=0;i < size;i++){
            res.append(data[i]);
            if(i != size - 1){
                res.append(", ");
            }
        }
        res.append("]");
        return res.toString();
    }

    private void resize(int newCapacity) {
        E[] newData = (E[])new Object[newCapacity];
        for(int i= 0;i < size;i++){
            newData[i] = data[i];
        }
        data = newData;
    }
}

数据结构(一)初识

数据结构我们可能并不陌生,科班的同学应该都接触过这门课程,由于当初年少无知,认为数据结构不是那么重要,导致后来失去了很多机会,所以接下来我要系统的学习一下数据结构,也希望正在大学的朋友们不要轻视这门课程。

数据结构研究的是数据如何在计算机进行组织和存储,使得我们可以高效的获取数据或者修改数据。总的来说,数据结构可以分为三种:线性结构、树结构、图结构;

线性结构:数组、栈、队列、链表、哈希表…

树结构:二叉树、二叉搜索树、AVL、红黑树、Treap、Splay、堆、Trie、线段树、K-D树、并查集、哈夫曼树…

图结构:邻接矩阵、邻接表 

=====================================================

数据结构(二)数组

数据结构(三)栈和队列

数据结构(四)链表

目录待更新

Spring Cloud 学习(三)服务调用

服务调用者需要我们新建一个项目来调用,步骤和前面的client基本相同。

1.完整pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.wjy329</groupId>
   <artifactId>eureka-consumer</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>eureka-consumer</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.0.5.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
      <spring-cloud.version>Finchley.SR1</spring-cloud.version>
   </properties>

   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

   <dependencyManagement>
      <dependencies>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>


</project>

2.配置文件

application.properties:

spring.application.name=spring-cloud-consumer
server.port=9001
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

3.启动类

启动类需要添加@EnableDiscoveryClient@EnableFeignClients 注解

package com.wjy329.eurekaconsumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;


@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class EurekaConsumerApplication {

   public static void main(String[] args) {
      SpringApplication.run(EurekaConsumerApplication.class, args);
   }
}

4.fegin调用

package com.wjy329.eurekaconsumer.service;


import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @Author wjy329
 * @Time 2018/9/2811:48 AM
 * @description
 */
@FeignClient(name= "wjy-client")
public interface HelloRemote {
    @RequestMapping(value = "/hello")
    public String hello(@RequestParam(value = "name") String name);
}
  • name是远程服务名,与服务提供者的名字需保持一致,方法需和服务提供者的controller方法名和参数个数保持一致。

5.controller

package com.wjy329.eurekaconsumer.controller;

import com.wjy329.eurekaconsumer.service.HelloRemote;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author wjy329
 * @Time 2018/9/2811:51 AM
 * @description
 */

@RestController
public class ConsumerController {

    @Autowired
    HelloRemote helloRemote;

    @RequestMapping("/hello/{name}")
    public String index(@PathVariable("name") String name) {
        return helloRemote.hello(name);
    }

}

到此,服务的注册与调用就基本完成了,我们开始按照注册中心、服务提供、服务消费的顺序依次启动程序。启动成功后,在浏览器输入:localhost:8080/hello?name=123  看到返回结果,说明服务提供者正常;

然后再输入 http://localhost:9001/hello/wjy  看到返回结果,说明调用远程服务成功。

image.png

Spring Cloud 学习(二)服务注册与发现

在第一篇的架构图中,我们可以看到最中间的是服务注册中心,毕竟作为微服务架构,服务提供者和消费者都得通过注册中心来工作,Spring Cloud中的注册中心是Spring Cloud Eureka

 1、Spring Cloud Eureka

  • 基于Netflix Eureka做了二次封装

  • 两个组件组成:

    – Eureka Server 注册中心

    – Eureka Client  服务注册

2、注册中心-Eureka Server

 接下来我们来建一个Eureka Server的项目;

在用idea构建时,我们选择eureka server即可

image.png

创建完成后,我们需要以下几步:

1、查看pom依赖

如果你是按照上述方法创建,那么依赖自动添加了,如果不是按上述方法创建,我们还需要在pom文件中添加相关的依赖,下面我给出完整的依赖,相关依赖在注释中写明。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.wjy329</groupId>
   <artifactId>eureka</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>eureka</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.0.5.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
      <spring-cloud.version>Finchley.SR1</spring-cloud.version>
   </properties>

   <dependencies>
      <!-- 需要添加的依赖 -->
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>

   <dependencyManagement>
      <dependencies>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>


</project>

2、在启动类上添加@EnableEurekaServer 注解

package com.wjy329.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {

   public static void main(String[] args) {
      SpringApplication.run(EurekaApplication.class, args);
   }
}

3、配置文件

spring.application.name=eureka

server.port=8080
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/
  • eureka.client.register-with-eureka :表示是否将自己注册到Eureka Server,默认为true。

  • eureka.client.fetch-registry :表示是否从Eureka Server获取注册信息,默认为true。

  • eureka.client.serviceUrl.defaultZone :设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址。默认是http://localhost:8761/eureka ;多个地址可使用 , 分隔。

这几步完成后,我们启动项目,访问http://localhost:8080 即可。

image.png

3、服务注册-Eureka Client

 由于一段时间我们不需要更改上面server的操作,为了方便,所以我们将上面的server打包成jar包,运行后,方便我们使用。

用控制台进入我们的项目目录,然后执行 mvn clean package 命令,打包项目。

image.png

我这里修改了端口号,一般使用8761端口,然后进入项目的target目录下,执行java -jar 项目.jar 启动服务。

image.png

为了更加方便使用后台运行方式,在控制台中输入  nohup java -jar eureka-0.0.1-SNAPSHOT.jar > /dev/null 2>&1 & 

也可以成功启动注册中心。

和注册中心的步骤大致相同,如下建立项目

image.png

步骤大致也分3步:

1、查看pom依赖

此处pom.xml需要特别注意要加入web的依赖,不然就没有tomcat,就不会启动成功

web依赖:

<dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

pom完整依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.wjy329</groupId>
   <artifactId>eureka-client</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>eureka-client</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.0.5.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
      <spring-cloud.version>Finchley.SR1</spring-cloud.version>
   </properties>

   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>

   <dependencyManagement>
      <dependencies>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>
</project>

2、在启动类上加@EnableDiscoveryClient注解

package com.wjy329.eurekaclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class EurekaClientApplication {

   public static void main(String[] args) {
      SpringApplication.run(EurekaClientApplication.class, args);
   }
}

3、配置文件

#服务名字
spring.application.name=wjy-client
server.port=8080
#注册中心的注册地址
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

然后我们启动项目,在注册中心可以看到服务注册成功。

image.png

4、Eureka高可用(集群)

一个server确实是不可靠的,万一一个注册中心挂了,那么所有的服务都会没用,所有必须要学会集群。

4.1 双节点注册中心

说白了,就是两个注册中心,即使一个挂了,还有另一个扛着,哈哈。

方法:按照上面注册中心的方法,建两个注册中心,用端口号区分,然后相互注册即可。

1、两个注册中心的配置文件

application1.properties:

spring.application.name=eureka

server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:8762/eureka/

application2.properties:

spring.application.name=eureka2

server.port=8762
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

2、服务提供者的配置文件

只需要将之前的配置中多添加一个注册中心的url即可:

spring.application.name=wjy-client
server.port=8080
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/,http://localhost:8762/eureka/

配置完成后,先启动两个注册中心,然后启动client,观察和测试效果。

image.png如图,分别在两个注册中心中都注册了服务。

4.2 集群注册中心

这里指三台及三台以上的情况,其实和双节点一样,在多个配置中心中,每个配置中心需要注册到除了自己外的其他注册中心,比如server1需要配置server2、server3、server4等,而server2需要配置server1、server3、server4等。

1、多个注册中心的配置文件

application1.properties:

spring.application.name=eureka

server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:8762/eureka/,http://localhost:8763/eureka/

application2.properties:

spring.application.name=eureka2

server.port=8762
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/,http://localhost:8763/eureka/

application3.properties:

spring.application.name=eureka2

server.port=8763
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/,http://localhost:8762/eureka/

2、服务提供者的配置文件

需要加上所有的配置中心:

spring.application.name=wjy-client
server.port=8080
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/,http://localhost:8762/eureka/,http://localhost:8763/eureka/

自行测试结果。。。

Linux 命令学习值nohup用法

今天在学习Spring Cloud的时候遇到了nohup命令,这里记录一下。

说起nohup那么就不得不谈一谈&  

&后台运行,执行 ./shell.sh & 的时候, 即使使用Ctrl C,  .sh也会继续运行。 但是如果直接关掉控制台后,.sh的进程就会被杀掉

nohup : 运行nohup ./shell.sh 的时候, 关闭控制台, .sh进程还是存在的。但是如果使用Ctrl C, 那么.sh进程也是会被杀掉

综上,又想Ctrl C,又想直接关闭shell,那么可以结合使用,即: nohup ./xxx.sh &

下图是我的一个例子,用组合启动了java程序后,退出并关闭控制台,java程序还是运行的,进程还存在。

image.png

image.png

当我们再次打开控制台,输入命令查看进程,发现进程依然存在。

image.png

Spring Cloud 学习(一)微服务介绍

之前了解过Spring Cloud的一系列知识,但是没有做记录,现在从头开始学习一下,记录下来。

1、什么是微服务

1.1 微服务的提出

1.2 微服务原文

  • 一系列微小的服务共同组成

  • 跑在自己的进程里

  • 每个服务为独立的业务开发

  • 独立部署

  • 分布式的管理

1.3 应用架构的发展

dubbo-architecture-roadmap.jpg

单一应用架构=》垂直应用架构=》分布式服务架构=》流动计算架构

这是从dubbo官网弄下来的一张图,这张图可以直观的看出架构的发展。

1.4 分布式定义

旨在支持应用程序和服务的开发,可以利用物理架构由多个自治的处理元素不共享主内存,但通过网络发送消息合作。–Leslie Lamport

1.5 简单的微服务架构

image.png

1.6 微服务架构的基础框架/组件

  • 服务注册发现

  • 服务网关

  • 后端通用服务(也称为中间层服务)

  • 前端服务(也称为边缘服务)

1.7 微服务两大系

阿里系:Dubbo、Zookeeper、SpringMVC\SpringBoot

Spring Cloud:Spring Cloud Netflix Eureka、SpringBoot

2、Spring Cloud是什么

  • Spring Cloud是一个开发工具集,含了多个子项目

    -利用Spring Boot的开发便利

    -主要是基于对Netflix开源组件的进一步封装

  • Spring Cloud简化了分布式开发

Redis学习(三)Java客户端Jedis

Redis的客户端有很多,比如Java客户端Jedis,Python客户端redis-py,Go客户端redigo等;我们学习的是java语言,这里我们介绍和学习java客户端Jedis。

1、什么是Jedis

Redis提供的以Java API方式使用redis的客户端,就是在java上使用redis的工具。

2、获取Jedis

Maven依赖方式:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
    <type>jar</type>
    <scope>complie</scope>
</dependency>

3、Jedis直连

#1.生成一个Jedis对象,这个对象负责和指定Redis节点进行通信
Jedis jedis = new Jedis("127.0.0.1",6379);
#2.jedis执行set操作
jedis.set("hello","world");
#3.jedis执行get操作,value=“world”
String value = jedis.get("hello");

Jedis的构造函数:

Jedis(String host,int port,int connectionTimeout,int soTimeout)

host:Redis节点的所在机器的IP

port:Redis节点的端口

connectionTimeout:客户端连接超时

soTimeout:客户端读写超时

4、简单使用

1.String

//1.string
//输出结果:ok
jedis.set("hello","world");
//输出结果:world
jedis.get("hello");
//输出结果:1
jedis.incr("counter");

2.hash

//2.hash
jedis.hset("myhash","f1","v1");
jedis.hset("myhash","f2","v2");
//输出结果:{f1=v1,f2=v2}
jedis.hgetAll("myhash");

3.list

//3.list
jedis.rpush("mylist","1");
jedis.rpush("mylist","2");
jedis.rpush("mylist","3");
//输出结果:[1,2,3]
jedis.lrange("mylist",0,-1);

4.set

//4.set
jedis.sadd("myset","a");
jedis.sadd("myset","b");
jedis.sadd("myset","a");
//输出结果:[b,a]
jedis.smembers("myset");

5.zset

//5.zset
jedis.zadd("myzset",99,"tom");
jedis.zadd("myzset",66,"peter");
jedis.zadd("myzset",33,"james");
//输出结果:[[["james"],33.0],[["peter"],66.0],[["tom"],99.0]]
jedis.zrangeWithScores("myzset",0,-1);

5、Jedis连接池

Jedis直连:1.生成Jedis对象2.Jedis执行命令3.返回执行结果4.关闭Jedis连接

Jedis连接池:1.从资源池借Jedis对象2.Jedis执行命令3.返回执行结果4.归还Jedis对象给连接池

优点 缺点
直连
  • 简单方便

  • 适用于少量长期连接的场景

  • 存在每次新建/关闭TCP开销

  • 资源无法控制,存在连接泄漏的可能

  • Jedis对象线程不安全

连接池
  • Jedis预先生成,降低开销使用

  • 连接池的形式保护和控制资源的使用

相对于直连,使用相对麻烦,尤其在资源的管理上需要很多参数来保证,一旦规划不合理也会出现问题

简单使用:

//初始化Jedis连接池,通常来讲JedisPool是单例的。
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig,"ip","port");
Jedis jedis = null;
try{
    //1.从连接池获取jedis对象
    jedis = jedisPool.getResource();
    //2.执行操作
    jedis.set("hello","world");
}catch(Exeception e){
    e.printStackTrace();
}finally{
    if(jedis != null)
    //如果使用JedisPool,close操作不是关闭连接,代表归还连接池
    jedis.close();
}

Redis学习(二)redis API的使用

1、通用命令

这里先简单的列出6个常用的命令,之后逐步的学习更多的命令;

命令名 功能 用法  示例
keys 遍历key

keys [pattern]

生产环境不推荐使用

keys *  1537325301674010.png

dbsize
计算key的总数

dbsize

可以在线上使用

1537325651874645.png
exists
检查key是否存在

exists key

可以在线上使用 

1537325812209989.png
del 删除指定key-value del key,可以删除多个
1537326389630969.png
expire、ttl、persist key过期命令

#key在seconds秒后过期

expir

e key seconds 

#查看key剩余的过期时间

ttl key

#去掉key的过期时间

persist key

1537326307872919.png
type 返回key的类型 type key 1537327573348376.png

2、字符串

2.1  字符串键值结构

key为字符串类型,值可以为字符串、整型、二进制、json、xml等;(值不能大于512MB)

2.2 字符串使用场景

缓存、计数器、分布式锁等

2.3 API

API 功能描述 时间复杂度
get key 获取key对应的value o(1)
set key value 设置key-value o(1)
del key 删除key-value o(1)
incr key  key自增1,如果key不存在,自增后get(key)=1 o(1)
decr key key自减1,如果key不存在,自减后get(key)=-1 o(1)
incrby key k key自增k,如果key不存在,自增后get(key)=k o(1)
decrby key k key自减k,如果key不存在,自减后get(key)=-k o(1)
set key value 不管key是否存在,都设置 o(1)
setnx key value key不存在,才设置 (理解为添加) o(1)
set key value xx key存在,才设置    (理解为更新) o(1)
mget key1 key2 key3… 批量获取key,原子操作 o(n)
mset key1 value1 key2 value2 key3 value3 批量设置key-value o(n)
getset key newvalue set key newvalue并返回旧的value  o(1)
append key value
将value追加到旧的value o(1)
strlen key 返回字符串的长度(注意中文) o(1)
incrybyfloat key 3.5
增加key对应的值3.5 o(1)
getrange key start end 获取字符串指定下标所有的值 o(1)
setrange key index value 设置指定下标所有对应的值 o(1)

3、HASH

3.1  哈希键值结构

key是字符串类型,value分为field和value即属性和value;需要注意field不能相同,value可以相同

例如:

image.png

3.2 API

API 功能描述 时间复杂度
hget key field
获取hash key对应的field的value o(1)
hset key field value 设置hash key对应field的value o(1)
hdel key field 删除hash key对应field的value o(1)
hexists key field 判断hash key是否有field o(1)
hlen key 获取hash key field的数量 o(1)
hmget key field1 field2 … fieldN 批量获取hash key的一批field对应的值 o(n)
hmset key field1 value1 field2 value2 …fieldN valueN 批量设置hash key的一批field value o(n)
hgetall key
返回hash key对应所有的field和value o(n)
hvals key 返回hash key对应所有的field的value o(n)
hkeys key 返回hash key对应所有field o(n)
hsetnx key field value
设置hash key对应field的value(如果field已经存在,则失败) o(1)
hincrby key field intCounter hash key对应的field的value自增intCounter o(1)
hincrbyfloat key field floatCounter
hincrby浮点数版 o(1)

4、列表(List)

4.1 列表结构

 image.png

4.2特点

有序、可以重复、左右两边插入弹出

4.3 API

API 功能描述 时间复杂度
rpush key value1 value2 … valueN 从列表右端插入值(1-N个) o(1~n)
lpush key value1 value2 … valueN 从列表左端插入值(1-N个) o(1~n)
linsert key before|after value newValue
在list指定的值前|后插入newValue
o(n)
lpop key
从列表左侧弹出一个item
o(1)
rpop key
从列表右侧弹出一个item o(1)
lrem key count value

根据count值,从列表删除所有value相等的项

(1)count>0,从左到右,删除最多count个value相等的项

(2)count<0,从右到左,删除最多Math.abs(count)个value相等的项

(3)count=0,删除所有value相等的项

o(n)
ltrim key start end 按照索引范围修剪列表 o(n)
lrange key start end(包含end) 获取列表指定索引范围所有item o(n)
lindex key index
获取列表指定索引的item o(n)
llen key
获取列表长度 o(1)
lset key index newValue 设置列表指定索引值为newValue o(n)
blpop key timeout
lpop阻塞版本,timeout是阻塞超时时间,timeout=0为永远不阻塞 o(1)
brpop key timeout
rpop阻塞版本,timeout是阻塞超时时间,timeout=0为永远不阻塞 o(1)

4.4 列表应用

时间线功能

小技巧:

  1. LRUSH + LPOP = Stack     实现栈

  2. LPUSH + RPOP = Queue    实现队列

  3. LPUSH + LTRIM = Capped Collection 有固定数量的列表

  4. LPUSH + BRPOP = Message Queue 消息队列

5、集合

5.1 集合结构

image.png

5.2 集合特点

无序、无重复、支持集合间操作

5.3 集合内API

API 功能描述 时间复杂度
sadd key element 向集合key添加element(如果element已经存在,添加失败) o(1)
srem key element
将集合key中的element移除掉 o(1)
scard key
计算集合大小
sismember key element 判断元素是否在集合中
srandmember key count 从集合中随机取出count个元素
smembers key 取出集合中的所有元素
spop key 从集合中随机弹出一个元素

5.4 集合内操作应用

微博抽奖系统、微博点赞、用户标签

5.5 集合间API

API 功能描述 时间复杂度
sdiff
差集
sinter 交集
sunion 并集
sdiff|sinter|suion + store destkey ..
将差集、交集、并集结果保存在destkey中

5.6 集合间操作应用

社交app中的共同关注功能

集合简单的使用:

1.SADD = Tagging 标签

2.SPOP/SRANDMEMBER = Random item 随机数

3.SADD + SINTER = Social Graph 社交相关应用

6、有序集合

6.1 有序集合结构

image.png

按照score来指定顺

6.2 有序集合API

API 功能描述 时间复杂度
zadd key score element(score可以重复)
添加score和element o(longN)
zrem key element(可以删除多个)
删除元素
o(1)
zscore key element
返回元素的分数 o(1)
zincrby key increScore element
增加或减少元素分数 o(1)
zcard key 返回元素的总个数 o(1)
zrange key start end[WITHSCORES]
返回指定索引范围内的升序元素[分值] o(log(n)+m)
zrangebyscore key minScore maxScore[WITHSCORES]
返回指定分数范围内的升序元素[分值] o(log(n)+m)
zcount key minScore maxScore
返回有序集合内在指定分数范围内的个数 o(log(n)+m)
zremrangebyrank key start end 删除指定排名内的升序元素 o(log(n)+m)
zremrangebyscore key minScore maxScore 删除指定分数内的升序元素 o(log(n)+m)
zrank key 获取升序排名
zrevrank
获取降序排名
zrevrange key start end[WITHSCORES] 返回指定索引范围内的降序元素[分值]
zrevrangebyscore key minScore maxScore[WITHSCORES] 返回指定分数范围内的降序元素[分值]
zinterstore
交集运算存储
zunionstore 并集运算存储

6.3 有序集合应用

排行榜

Redis学习(一)redis简介与安装

1、何为Redis

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。                                                             — 百度百科

说白了,就是很牛逼的存储系统。

2、Redis的特性

  • 速度快 

数据存储在内存中、redis使用C语言编写、单线程的线程模型
  • 持久化

对数据的更新异步保存到磁盘
  • 多种数据结构 

Strings、HashTables、Linked Lists、Sets、Sorted Sets、BitMaps、HyperLogLog、GEO
  • 支持多种编程语言

Java、PHP、Python、Ruby、Lua、node.js等
  • 功能丰富

发布订阅、Lua脚本、事务、pipeline
  • 简单

源码少、不依赖外部库、单线程模型
  • 主从复制

主服务器数据可以复制到从服务器上
  • 高可用、分布式

Redis-Sentinel(v2.8)支持高可用、Redis-Cluster(v3.0)支持分布式

3、Redis典型应用场景

缓存系统、计数器、消息队列系统、排行榜、社交网络、实时系统

4、Redis的安装

#下载redis
wget  
#解压
tar -xzf redis-3.0.7.tar.gz
#建立软连接
ln -s redis-3.0.7 redis
#进入目录
cd redis
#编译和安装
make && make install

image.png

5、Redis的启动方式

最简启动:redis-server(使用了默认配置)

动态参数启动:redis-server –port 6380

配置文件启动:redis-server configPath (推荐)

      常用配置:daemonize — 是否是守护进程(no|yes) 建议使用yes

                     port  — Redis对外端口号

                     logfile — Redis系统日志

                     dir — Redis工作目录(日志文件及持久化文件存放的目录)

最简启动示例:

image.png

动态参数启动示例:

image.png

配置文件启动示例:

/opt/soft/redis目录下新建config目录,用来存放配置文件,然后复制一份默认的配置文件redis.conf 到 config目录下,为了方便查看和修改  使用  cat redis.conf | grep -v "#" | grep -v "^$" > redis-6381.conf  去掉注释和空格重定向为新的配置文件,一般都是用端口号来命名不同的配置文件,所以新的配置文件叫 redis-6381.conf  然后就可以修改配置和删除不需要的配置;这里配置文件仅有如下四个,以后的配置在之后的学习中逐渐的了解和学习。

image.png

修改完配置后,运行命令启动: redis-server config/redis-6381.conf   这样就启动成功了,详情看图:

image.png

6、Redis客户端连接

redis-cli -h ip(ip地址) -p(端口号) port

image.png

以上就是学习redis的第一步。。。坚持下去我们必定成功!!!

Zookeeper学习(五)Apache Curator客户端的使用

1、curator与原生客户端之间的差异

原生api的不足:

  • 超时重连,不支持自动,需要手动操作

  • watch注册一次后会失效

  • 不支持递归创建节点

Apache Curator的优势:

  • Apache的开源项目

  • 解决watcher的注册一次就失效

  • Api更加简单易用

  • 提供更多解决方案并且实现简单

  • 提供常用的ZooKeeper工具类

2、Apache Curator的使用

会话连接与关闭:

package com.wjy329.curatordemo.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;


/**
 * @Author wjy329
 * @Time 2018/9/16上午11:02
 * @description curator会话连接
 */

public class CuratorConnect {
    //声明一个客户端
    public CuratorFramework client = null;
    //客户端ip
    public static final String zkServerPath = "172.16.106.130:2181";

    /**
     * 实例化zk客户端
     */
    public CuratorConnect() {
        /**
         * curator链接zookeeper的策略:RetryNTimes
         * n:重试的次数
         * sleepMsBetweenRetries:每次重试间隔的时间
         */
        RetryPolicy retryPolicy = new RetryNTimes(3, 5000);

        client = CuratorFrameworkFactory.builder()
                .connectString(zkServerPath)
                .sessionTimeoutMs(10000).retryPolicy(retryPolicy)
                .namespace("workspace").build();
        client.start();
    }

    /**
     *
     * @Description: 关闭zk客户端连接
     */
    public void closeZKClient() {
        if (client != null) {
            this.client.close();
        }
    }

    public static void main(String[] args) throws Exception {
        // 实例化
        CuratorConnect curatorConnect = new CuratorConnect();
        boolean isZkCuratorStarted = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

        Thread.sleep(3000);

        curatorConnect.closeZKClient();
        boolean isZkCuratorStarted2 = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
    }

}

curator连接zookeeper的策略大致有5种,上面的一种是推荐使用的,下面列出所有的策略:

      /**
       * curator链接zookeeper的策略:ExponentialBackoffRetry
       * baseSleepTimeMs:初始sleep的时间
       * maxRetries:最大重试次数
       * maxSleepMs:最大重试时间
       */
     RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
      
      /**
       * curator链接zookeeper的策略:RetryNTimes
       * n:重试的次数
       * sleepMsBetweenRetries:每次重试间隔的时间
       */
      RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
      
      /**
       * curator链接zookeeper的策略:RetryOneTime
       * sleepMsBetweenRetry:每次重试间隔的时间
       */
     RetryPolicy retryPolicy2 = new RetryOneTime(3000);
      
      /**
       * 永远重试,不推荐使用
       */
     RetryPolicy retryPolicy3 = new RetryForever(retryIntervalMs)
      
      /**
       * curator链接zookeeper的策略:RetryUntilElapsed
       * maxElapsedTimeMs:最大重试时间
       * sleepMsBetweenRetries:每次重试间隔
       * 重试时间超过maxElapsedTimeMs后,就不再重试
       */
     RetryPolicy retryPolicy4 = new RetryUntilElapsed(2000, 3000);

然后运行代码,连接成功后关闭。image.png

zookeeper命名空间及创建节点:

命名空间:

上面构造函数中的namespace就是命名空间,创建命名空间后,后续的操作都将在该命名空间下进行。

节点的增删改查:

添加节点:

package com.wjy329.curatordemo.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;


/**
 * @Author wjy329
 * @Time 2018/9/16上午11:02
 * @description curator会话连接
 */

public class CuratorConnect {
    public CuratorFramework client = null;
    public static final String zkServerPath = "172.16.106.130:2181";

    /**
     * 实例化zk客户端
     */
    public CuratorConnect() {
        /**
         * curator链接zookeeper的策略:RetryNTimes
         * n:重试的次数
         * sleepMsBetweenRetries:每次重试间隔的时间
         */
        RetryPolicy retryPolicy = new RetryNTimes(3, 5000);

        client = CuratorFrameworkFactory.builder()
                .connectString(zkServerPath)
                .sessionTimeoutMs(10000).retryPolicy(retryPolicy)
                .namespace("workspace").build();
        client.start();
    }

    /**
     *
     * @Description: 关闭zk客户端连接
     */
    public void closeZKClient() {
        if (client != null) {
            this.client.close();
        }
    }

    public static void main(String[] args) throws Exception {
        // 实例化
        CuratorConnect curatorConnect = new CuratorConnect();
        boolean isZkCuratorStarted = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

        //创建节点
        String nodePath = "/super/wjy";
        byte[] data = "wjy329".getBytes();
        curatorConnect.client.create().creatingParentsIfNeeded()
         .withMode(CreateMode.PERSISTENT)
         .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
         .forPath(nodePath, data);

        Thread.sleep(3000);

        curatorConnect.closeZKClient();
        boolean isZkCuratorStarted2 = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
    }

}

上面注释中写了创建节点的方法,运行后,我们在服务器中可以看到创建的命名空间和节点:

image.png
修改节点:

package com.wjy329.curatordemo.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;


/**
 * @Author wjy329
 * @Time 2018/9/16上午11:02
 * @description curator会话连接
 */

public class CuratorConnect {
    public CuratorFramework client = null;
    public static final String zkServerPath = "172.16.106.130:2181";

    /**
     * 实例化zk客户端
     */
    public CuratorConnect() {
        /**
         * curator链接zookeeper的策略:RetryNTimes
         * n:重试的次数
         * sleepMsBetweenRetries:每次重试间隔的时间
         */
        RetryPolicy retryPolicy = new RetryNTimes(3, 5000);

        client = CuratorFrameworkFactory.builder()
                .connectString(zkServerPath)
                .sessionTimeoutMs(10000).retryPolicy(retryPolicy)
                .namespace("workspace").build();
        client.start();
    }

    /**
     *
     * @Description: 关闭zk客户端连接
     */
    public void closeZKClient() {
        if (client != null) {
            this.client.close();
        }
    }

    public static void main(String[] args) throws Exception {
        // 实例化
        CuratorConnect curatorConnect = new CuratorConnect();
        boolean isZkCuratorStarted = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

     
        String nodePath = "/super/wjy";

        // 更新节点数据
        byte[] newData = "update".getBytes();
        curatorConnect.client.setData().withVersion(0).forPath(nodePath, newData);

        Thread.sleep(3000);

        curatorConnect.closeZKClient();
        boolean isZkCuratorStarted2 = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
    }

}

同样,修改的代码也在注释中写明了,这里需要注意的就是版本号,运行程序后:

image.png

删除节点:

package com.wjy329.curatordemo.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;


/**
 * @Author wjy329
 * @Time 2018/9/16上午11:02
 * @description curator会话连接
 */

public class CuratorConnect {
    public CuratorFramework client = null;
    public static final String zkServerPath = "172.16.106.130:2181";

    /**
     * 实例化zk客户端
     */
    public CuratorConnect() {
        /**
         * curator链接zookeeper的策略:RetryNTimes
         * n:重试的次数
         * sleepMsBetweenRetries:每次重试间隔的时间
         */
        RetryPolicy retryPolicy = new RetryNTimes(3, 5000);

        client = CuratorFrameworkFactory.builder()
                .connectString(zkServerPath)
                .sessionTimeoutMs(10000).retryPolicy(retryPolicy)
                .namespace("workspace").build();
        client.start();
    }

    /**
     *
     * @Description: 关闭zk客户端连接
     */
    public void closeZKClient() {
        if (client != null) {
            this.client.close();
        }
    }

    public static void main(String[] args) throws Exception {
        // 实例化
        CuratorConnect curatorConnect = new CuratorConnect();
        boolean isZkCuratorStarted = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

        //创建节点
        String nodePath = "/super/wjy";

        // 删除节点
        curatorConnect.client.delete()
              .guaranteed()                // 如果删除失败,那么在后端还是继续会删除,直到成功
              .deletingChildrenIfNeeded()  // 如果有子节点,就删除
              .withVersion(1)
              .forPath(nodePath);

        Thread.sleep(3000);

        curatorConnect.closeZKClient();
        boolean isZkCuratorStarted2 = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
    }

}

删除节点的部分也通过注释注明,删除的方法也应注意版本号,利用此方法删除的是指定路径下所有的子节点包括路径中的最后一个父节点,例如 节点为 /school/class/student/xiaowang   指定删除路径为 /school/class ,那么运行程序后,节点为:/school ,其余节点都被删掉,并且命名空间是不会被删掉的,这个可以自行验证。

image.png

读取节点数据:

package com.wjy329.curatordemo.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.Stat;


/**
 * @Author wjy329
 * @Time 2018/9/16上午11:02
 * @description curator会话连接
 */

public class CuratorConnect {
    public CuratorFramework client = null;
    public static final String zkServerPath = "172.16.106.130:2181";

    /**
     * 实例化zk客户端
     */
    public CuratorConnect() {
        /**
         * curator链接zookeeper的策略:RetryNTimes
         * n:重试的次数
         * sleepMsBetweenRetries:每次重试间隔的时间
         */
        RetryPolicy retryPolicy = new RetryNTimes(3, 5000);

        client = CuratorFrameworkFactory.builder()
                .connectString(zkServerPath)
                .sessionTimeoutMs(10000).retryPolicy(retryPolicy)
                .namespace("workspace").build();
        client.start();
    }

    /**
     *
     * @Description: 关闭zk客户端连接
     */
    public void closeZKClient() {
        if (client != null) {
            this.client.close();
        }
    }

    public static void main(String[] args) throws Exception {
        // 实例化
        CuratorConnect curatorConnect = new CuratorConnect();
        boolean isZkCuratorStarted = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

        //创建节点
        String nodePath = "/super";

        //读取节点数据
      Stat stat = new Stat();
      byte[] data = curatorConnect.client.getData().storingStatIn(stat).forPath(nodePath);
      System.out.println("节点" + nodePath + "的数据为: " + new String(data));
      System.out.println("该节点的版本号为: " + stat.getVersion());

        Thread.sleep(3000);

        curatorConnect.closeZKClient();
        boolean isZkCuratorStarted2 = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
    }

}

运行程序:

image.png

读取子节点:

package com.wjy329.curatordemo.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.Stat;

import java.util.List;


/**
 * @Author wjy329
 * @Time 2018/9/16上午11:02
 * @description curator会话连接
 */

public class CuratorConnect {
    public CuratorFramework client = null;
    public static final String zkServerPath = "172.16.106.130:2181";

    /**
     * 实例化zk客户端
     */
    public CuratorConnect() {
        /**
         * curator链接zookeeper的策略:RetryNTimes
         * n:重试的次数
         * sleepMsBetweenRetries:每次重试间隔的时间
         */
        RetryPolicy retryPolicy = new RetryNTimes(3, 5000);

        client = CuratorFrameworkFactory.builder()
                .connectString(zkServerPath)
                .sessionTimeoutMs(10000).retryPolicy(retryPolicy)
                .namespace("workspace").build();
        client.start();
    }

    /**
     *
     * @Description: 关闭zk客户端连接
     */
    public void closeZKClient() {
        if (client != null) {
            this.client.close();
        }
    }

    public static void main(String[] args) throws Exception {
        // 实例化
        CuratorConnect curatorConnect = new CuratorConnect();
        boolean isZkCuratorStarted = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

        //创建节点
        String nodePath = "/super";

        // 查询子节点
      List<String> childNodes = curatorConnect.client.getChildren()
                                 .forPath(nodePath);
      System.out.println("开始打印子节点:");
      for (String s : childNodes) {
         System.out.println(s);
      }


        // 判断节点是否存在,如果不存在则为空
//    Stat statExist = cto.client.checkExists().forPath(nodePath + "/abc");
//    System.out.println(statExist);

        Thread.sleep(3000);

        curatorConnect.closeZKClient();
        boolean isZkCuratorStarted2 = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
    }

}

可以看到控制台输出子节点:

image.png

上述代码还有一个能判断节点是否存在的代码,自行运行看效果吧。

使用watcher:

package com.wjy329.curatordemo.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.Stat;

import java.util.List;


/**
 * @Author wjy329
 * @Time 2018/9/16上午11:02
 * @description curator会话连接
 */

public class CuratorConnect {
    public CuratorFramework client = null;
    public static final String zkServerPath = "172.16.106.130:2181";

    /**
     * 实例化zk客户端
     */
    public CuratorConnect() {
        /**
         * curator链接zookeeper的策略:RetryNTimes
         * n:重试的次数
         * sleepMsBetweenRetries:每次重试间隔的时间
         */
        RetryPolicy retryPolicy = new RetryNTimes(3, 5000);

        client = CuratorFrameworkFactory.builder()
                .connectString(zkServerPath)
                .sessionTimeoutMs(10000).retryPolicy(retryPolicy)
                .namespace("workspace").build();
        client.start();
    }

    /**
     *
     * @Description: 关闭zk客户端连接
     */
    public void closeZKClient() {
        if (client != null) {
            this.client.close();
        }
    }

    public static void main(String[] args) throws Exception {
        // 实例化
        CuratorConnect curatorConnect = new CuratorConnect();
        boolean isZkCuratorStarted = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

        //创建节点
        String nodePath = "/super/aaa";

        // watcher 事件  当使用usingWatcher的时候,监听只会触发一次,监听完毕后就销毁
        curatorConnect.client.getData().usingWatcher(new MyCuratorWatcher()).forPath(nodePath);
//      curatorConnect.client.getData().usingWatcher(new MyWatcher()).forPath(nodePath);

        Thread.sleep(100000);

        curatorConnect.closeZKClient();
        boolean isZkCuratorStarted2 = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
    }

}

上述我们看到有两个watcher的方式,下面都给出两个wathcer的实现类,只演示第一种。

package com.wjy329.curatordemo.curator;

import org.apache.curator.framework.api.CuratorWatcher;
import org.apache.zookeeper.WatchedEvent;

public class MyCuratorWatcher implements CuratorWatcher {

   @Override
   public void process(WatchedEvent event) throws Exception {
      System.out.println("触发watcher,节点路径为:" + event.getPath());
   }

}


=============================================

package com.wjy329.curatordemo.curator;

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

public class MyWatcher implements Watcher {

   @Override
   public void process(WatchedEvent event) {
      System.out.println("触发watcher,节点路径为:" + event.getPath());
   }

}

运行程序后,控制台连接成功后开始等待触发,注意线程睡眠时间稍微设置长一些,然后在服务器中作出相应的修改操作,就会触发watcher事件。

image.png

image.png

一次注册N次监听:

上面代码虽然实现了watcher的监听,但是发现是一次性的,在触发监听完成后就会销毁,下面我们再来做一个永久监听的操作:

package com.wjy329.curatordemo.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.Stat;

import java.util.List;


/**
 * @Author wjy329
 * @Time 2018/9/16上午11:02
 * @description curator会话连接
 */

public class CuratorConnect {
    public CuratorFramework client = null;
    public static final String zkServerPath = "172.16.106.130:2181";

    /**
     * 实例化zk客户端
     */
    public CuratorConnect() {
        /**
         * curator链接zookeeper的策略:RetryNTimes
         * n:重试的次数
         * sleepMsBetweenRetries:每次重试间隔的时间
         */
        RetryPolicy retryPolicy = new RetryNTimes(3, 5000);

        client = CuratorFrameworkFactory.builder()
                .connectString(zkServerPath)
                .sessionTimeoutMs(10000).retryPolicy(retryPolicy)
                .namespace("workspace").build();
        client.start();
    }

    /**
     *
     * @Description: 关闭zk客户端连接
     */
    public void closeZKClient() {
        if (client != null) {
            this.client.close();
        }
    }

    public static void main(String[] args) throws Exception {
        // 实例化
        CuratorConnect curatorConnect = new CuratorConnect();
        boolean isZkCuratorStarted = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

        //创建节点
        String nodePath = "/super/aaa";

// 为节点添加watcher
        // NodeCache: 监听数据节点的变更,会触发事件
      final NodeCache nodeCache = new NodeCache(curatorConnect.client, nodePath);
      // buildInitial : 初始化的时候获取node的值并且缓存,不加true或者为false,则初始化不缓存数据
      nodeCache.start(true);
      if (nodeCache.getCurrentData() != null) {
         System.out.println("节点初始化数据为:" + new String(nodeCache.getCurrentData().getData()));
      } else {
         System.out.println("节点初始化数据为空...");
      }
      nodeCache.getListenable().addListener(new NodeCacheListener() {
         public void nodeChanged() throws Exception {
            if (nodeCache.getCurrentData() == null) {
               System.out.println("空");
               return;
            }
            String data = new String(nodeCache.getCurrentData().getData());
            System.out.println("节点路径:" + nodeCache.getCurrentData().getPath() + "数据:" + data);
         }
      });


        Thread.sleep(100000);

        curatorConnect.closeZKClient();
        boolean isZkCuratorStarted2 = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
    }

}

image.png

image.png

子节点监听:

package com.wjy329.curatordemo.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.Stat;

import java.util.List;


/**
 * @Author wjy329
 * @Time 2018/9/16上午11:02
 * @description curator会话连接
 */

public class CuratorConnect {
    public CuratorFramework client = null;
    public static final String zkServerPath = "172.16.106.130:2181";

    /**
     * 实例化zk客户端
     */
    public CuratorConnect() {
        /**
         * curator链接zookeeper的策略:RetryNTimes
         * n:重试的次数
         * sleepMsBetweenRetries:每次重试间隔的时间
         */
        RetryPolicy retryPolicy = new RetryNTimes(3, 5000);

        client = CuratorFrameworkFactory.builder()
                .connectString(zkServerPath)
                .sessionTimeoutMs(10000).retryPolicy(retryPolicy)
                .namespace("workspace").build();
        client.start();
    }

    /**
     *
     * @Description: 关闭zk客户端连接
     */
    public void closeZKClient() {
        if (client != null) {
            this.client.close();
        }
    }

    public static void main(String[] args) throws Exception {
        // 实例化
        CuratorConnect curatorConnect = new CuratorConnect();
        boolean isZkCuratorStarted = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

        //创建节点
        String nodePath = "/super";

        // 为子节点添加watcher
        // PathChildrenCache: 监听数据节点的增删改,会触发事件
        String childNodePathCache =  nodePath;
        // cacheData: 设置缓存节点的数据状态
        final PathChildrenCache childrenCache = new PathChildrenCache(curatorConnect.client, childNodePathCache, true);
        /**
         * StartMode: 初始化方式
         * POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件
         * NORMAL:异步初始化
         * BUILD_INITIAL_CACHE:同步初始化
         */
        childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);

        List<ChildData> childDataList = childrenCache.getCurrentData();
        System.out.println("当前数据节点的子节点数据列表:");
        for (ChildData cd : childDataList) {
            String childData = new String(cd.getData());
            System.out.println(childData);
        }

        childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
                if(event.getType().equals(PathChildrenCacheEvent.Type.INITIALIZED)){
                    System.out.println("子节点初始化ok...");
                }

                else if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_ADDED)){
                    String path = event.getData().getPath();
                    if (path.equals(ADD_PATH)) {
                        System.out.println("添加子节点:" + event.getData().getPath());
                        System.out.println("子节点数据:" + new String(event.getData().getData()));
                    } else if (path.equals("/super/imooc/e")) {
                        System.out.println("添加不正确...");
                    }

                }else if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)){
                    System.out.println("删除子节点:" + event.getData().getPath());
                }else if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
                    System.out.println("修改子节点路径:" + event.getData().getPath());
                    System.out.println("修改子节点数据:" + new String(event.getData().getData()));
                }
            }
        });


        Thread.sleep(100000);

        curatorConnect.closeZKClient();
        boolean isZkCuratorStarted2 = curatorConnect.client.isStarted();
        System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
    }
    public final static String ADD_PATH = "/super/imooc/d";

}

这部分代码的演示其实是差不多的,这就不过多说明。

扩展部分:Zookeeper学习之Apache Curator使用实例-统一更新N台机器的节点配置

ACL相关操作:

package com.wjy329.curatordemo.curator;


import com.wjy329.curatordemo.utils.AclUtils;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs.Perms;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;

import java.util.ArrayList;
import java.util.List;

public class CuratorAcl {

   public CuratorFramework client = null;
   public static final String zkServerPath = "172.16.106.130:2181";

   public CuratorAcl() {
      RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
      client = CuratorFrameworkFactory.builder().authorization("digest", "imooc1:123456".getBytes())
            .connectString(zkServerPath)
            .sessionTimeoutMs(10000).retryPolicy(retryPolicy)
            .namespace("workspace").build();
      client.start();
   }
   
   public void closeZKClient() {
      if (client != null) {
         this.client.close();
      }
   }
   
   public static void main(String[] args) throws Exception {
      // 实例化
      CuratorAcl cto = new CuratorAcl();
      boolean isZkCuratorStarted = cto.client.isStarted();
      System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));
      
      String nodePath = "/acl/father";
      
      List<ACL> acls = new ArrayList<ACL>();
      Id wjy1 = new Id("digest", AclUtils.getDigestUserPwd("wjy1:123456"));
      Id wjy2 = new Id("digest", AclUtils.getDigestUserPwd("wjy2:123456"));
      acls.add(new ACL(Perms.ALL, wjy1));
      acls.add(new ACL(Perms.READ, wjy2));
      acls.add(new ACL(Perms.DELETE | Perms.CREATE, wjy2));

      // 创建节点
      byte[] data = "wjy329".getBytes();
      cto.client.create().creatingParentsIfNeeded()
            .withMode(CreateMode.PERSISTENT)
            .withACL(acls, true)
            .forPath(nodePath, data);

      //也可以使用这种方式设置权限,如果节点已经有权限需要认证后才能设置权限
      //cto.client.setACL().withACL(acls).forPath("/curatorNode");

      
      cto.closeZKClient();
      boolean isZkCuratorStarted2 = cto.client.isStarted();
      System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
   }
   
}

acl部分就直接上相关代码了,就不进行演示了。