这里要说的是替换Eureka方案,将服务的注册与发现替换为Zookeeper,这里使用的是3.6.2版本。macOS安装zookeeper可以参照这篇博客

springboot项目利用Zookeeper实现配置管理_ide

1、注册中心是Zookeeper

Zookeeper是一个分布式协调工具,可以实现注册中心的功能。

2、创建服务提供者注册到Zookeeper注册中心中

新建cloud-provider-payment8004模块,修改pom.xml,注意这里新加的spring-cloud-starter-zookeeper-discovery依赖,其他的没变。

cloud-provider-payment8004模块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">
    <!-- 引入父级模块名称 -->
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.king.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <!-- 子模块名称 -->
    <artifactId>cloud-provider-payment8004</artifactId>

    <dependencies>
        <!-- spring boot整合zookeeper客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
        </dependency>
        <!-- 引用eureka-client注册服务客户端依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 引用cloud-api-common公共模块 -->
        <dependency>
            <groupId>com.king.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- 引用父级spring boot的依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- 引用父级的mybatis跟spring boot的依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!-- 引用父级的druid阿里巴巴连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <!-- 引用父级的mysql的依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- 配置热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!-- 引用父级的lombok依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

cloud-provider-payment8004模块application.yml文件

# 配置zookeeper服务器的支付服务提供者端口号
server:
  port: 8004

# 配置应用信息
spring:
  application:
    name: cloud-provider-payment # 配置注册到zookeeper中的应用名称
  cloud:
    zookeeper:
      connect-string: 192.168.1.59:2181 # Zookeeper的地址

cloud-provider-payment8004模块主启动类

package com.king.springcloud;

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

/**
 * 微服务提供者服务端支付模块主启动类
 * @EnableEurekaClient:表示是Eureka服务注册中心客户端
 * @EnableDiscoveryClient:表示开启DiscoveryClient服务发现,用于向使用Consul或Zookeeper注册中心时注册服务
 */
@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain8004 {

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

cloud-provider-payment8004模块控制层

package com.king.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@Slf4j
@RestController
public class PaymentController {

    /**
     * 获取服务器端口号
     */
    @Value("${server.port}")
    private String serverPort;

    @RequestMapping("/payment/zookeeper")
    public String paymentZookeeper() {
        return "Spring Cloud With Zookeeper:" + serverPort + "\t" + UUID.randomUUID();
    }
}

启动主程序类,发现报错了,提示Zookeeperjar包冲突,因为spring-cloud-starter-zookeeper-discovery中会引入zookeeper-3.5.3-beta版本,这里将剔除zookeeper-3.5.3-beta版本,导入与本机zookeeper版本匹配的zookeeper依赖即可。

springboot项目利用Zookeeper实现配置管理_ide_02


此处还有一个bug,就是zookeeper依赖不能与eureka依赖同在,错误如下:

springboot项目利用Zookeeper实现配置管理_spring cloud_03


eureka依赖剔除就好,同时也要排除zookeeper依赖中的slf4j-log4j12<依赖,不然会报错:

springboot项目利用Zookeeper实现配置管理_spring_04

完整的cloud-provider-payment8004模块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">
    <!-- 引入父级模块名称 -->
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.king.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <!-- 子模块名称 -->
    <artifactId>cloud-provider-payment8004</artifactId>

    <dependencies>
        <!-- spring boot整合zookeeper客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
            <exclusions>
                <!-- 因不兼容先排除自带的zookeeper3.5.3 -->
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
                <!-- 因不兼容先排除slf4j -->
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 添加zookeeper3.6.2 -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <exclusions>
                <!-- 因不兼容先排除slf4j -->
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
            <version>3.6.2</version>
        </dependency>
        <!-- 引用cloud-api-common公共模块 -->
        <dependency>
            <groupId>com.king.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- 引用父级spring boot的依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- 引用父级的mybatis跟spring boot的依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!-- 引用父级的druid阿里巴巴连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <!-- 引用父级的mysql的依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- 配置热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!-- 引用父级的lombok依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

进入 Zookeeper,执行zkCli.sh,查看注册进来的服务,这里的cloud-provider-payment就是在application.yml里指定的应用名称。

[zk: localhost:2181(CONNECTED) 0] ls /
[dubbo, testNode, zookeeper]
[zk: localhost:2181(CONNECTED) 1] ls /
[dubbo, services, testNode, zookeeper]
[zk: localhost:2181(CONNECTED) 2] ls /services
[cloud-provider-payment]
[zk: localhost:2181(CONNECTED) 3] ls /services/cloud-provider-payment
[1c9fd2f2-a590-4d2a-bce5-65303636745e]
[zk: localhost:2181(CONNECTED) 4] ls /services/cloud-provider-payment/1c9fd2f2-a590-4d2a-bce5-65303636745e
[]
[zk: localhost:2181(CONNECTED) 5] get /services/cloud-provider-payment/1c9fd2f2-a590-4d2a-bce5-65303636745e
{"name":"cloud-provider-payment","id":"1c9fd2f2-a590-4d2a-bce5-65303636745e","address":"192.168.1.59","port":8004,"sslPort":null,"payload":{"@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance","id":"application-1","name":"cloud-provider-payment","metadata":{}},"registrationTimeUTC":1631589854599,"serviceType":"DYNAMIC","uriSpec":{"parts":[{"value":"scheme","variable":true},{"value":"://","variable":false},{"value":"address","variable":true},{"value":":","variable":false},{"value":"port","variable":true}]}}
[zk: localhost:2181(CONNECTED) 6] get /services/cloud-provider-payment/1c9fd2f2-a590-4d2a-bce5-65303636745e -s
{"name":"cloud-provider-payment","id":"1c9fd2f2-a590-4d2a-bce5-65303636745e","address":"192.168.1.59","port":8004,"sslPort":null,"payload":{"@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance","id":"application-1","name":"cloud-provider-payment","metadata":{}},"registrationTimeUTC":1631589854599,"serviceType":"DYNAMIC","uriSpec":{"parts":[{"value":"scheme","variable":true},{"value":"://","variable":false},{"value":"address","variable":true},{"value":":","variable":false},{"value":"port","variable":true}]}}
cZxid = 0x50a
ctime = Tue Sep 14 11:24:15 CST 2021
mZxid = 0x50a
mtime = Tue Sep 14 11:24:15 CST 2021
pZxid = 0x50a
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x100085f9a6c0009
dataLength = 533
numChildren = 0
[zk: localhost:2181(CONNECTED) 7]

ls /services/cloud-provider-payment1c9fd2f2-a590-4d2a-bce5-65303636745e值是这个cloud-provider-payment模块的临时节点。

Zookeeper有7个节点类型:持久节点、持久顺序节点、临时节点、临时顺序节点、容器节点、持久TTL节点、持久顺序 TTL节点。

这里补充一点关于ZookeeperEureka的比较,那么,需要先说一下CAP理论的东西。

CAP理论是分布式系统的一个概念,CConsistency(一致性)的首字母,AAvailability(可用性)的首字母,PPartition tolerance(分区容错性)的首字母。任何一个分布式系统都不能同时满足CAP,只能满足其中的两个。因为在分布式系统中,分区容错性是必须要保证的,那么A和C就二选一了。

为什么CA不能同时存在?

如果要保证一致性,那么,在一个结点写操作的时候,其他结点必须是锁定读写的,只有完成了数据同步,才能放开读写,锁定期间,其他结点是不可用的。

如果要保证可用性,那么,在一个结点写操作的时候,其他结点就不能锁定,此时,可能还没有完成同步操作,于是,读取到的数据就是旧数据,无法保证一致性。

Zookeeper保证CP

一致性的意思是:写操作后的读操作,必须返回该值。Zookeeper不能保证每次的请求可用性,比如在leader选举时候,集群就是不可用的。选举leader的时间为30-120s,这段时间Zookeeper集群都是不可用的。

Eureka保证AP

可用性的意思是:集群中各个节点是平等的,如果有几个结点挂掉不影响正常结点的工作,剩余结点依旧可以提供服务,只要有一台Eureka还存着,就能保证注册服务的可用,不过,信息可能不是最新的。

浏览器访问http://localhost:8004/payment/zookeeper可以请求到结果:

springboot项目利用Zookeeper实现配置管理_spring_05

3、创建服务消费者注册到Zookeeper注册中心中

创建cloud-consumerzk-order80模块,修改pom.xml

cloud-consumerzk-order80模块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">
    <!-- 引入父级模块名称 -->
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.king.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <!-- 子模块名称 -->
    <artifactId>cloud-consumerzk-order80</artifactId>

    <dependencies>
        <!-- spring boot整合zookeeper客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
            <exclusions>
                <!-- 因不兼容先排除自带的zookeeper3.5.3 -->
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
                <!-- 因不兼容先排除slf4j -->
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 添加zookeeper3.6.2 -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <exclusions>
                <!-- 因不兼容先排除slf4j -->
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
            <version>3.6.2</version>
        </dependency>
        <!-- 引用cloud-api-common公共模块 -->
        <dependency>
            <groupId>com.king.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- 引用父级spring boot的依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- 引用父级的mybatis跟spring boot的依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!-- 引用父级的druid阿里巴巴连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <!-- 引用父级的mysql的依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- 配置热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!-- 引用父级的lombok依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

cloud-consumerzk-order80模块application.yml文件

# 配置zookeeper服务器的支付服务消费者端口号
server:
  port: 80

# 配置应用信息
spring:
  application:
    name: cloud-consumer-order # 配置注册到zookeeper中的应用名称
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource # 数据源类型
    driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动
    url: jdbc:mysql://localhost:3306/cloud_DB_2020?useUnicode=true&charcaterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai # 数据库连接
    username: root # 数据库用户名
    password: rootroot # 数据库密码
  cloud:
    zookeeper:
      connect-string: 192.168.1.59:2181 # Zookeeper的地址

cloud-consumerzk-order80模块主启动类

package com.king.springcloud;

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

/**
 * 消费者客户端订单模块主启动类
 * @EnableDiscoveryClient:表示开启DiscoveryClient服务发现,用于向使用Consul或Zookeeper注册中心时注册服务
 */
@EnableDiscoveryClient
@SpringBootApplication
public class OrderZKMain80 {

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

cloud-consumerzk-order80模块配置类

package com.king.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * 配置类
 */
@Configuration
public class ApplicationContextConfig {

    /**
     * 获取RestTemplate对象
     * @LoadBalanced:开启RestTemplate负载均衡,轮询方式
     * @return 返回RestTemplate对象
     */
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){

        return new RestTemplate();
    }
}

cloud-consumerzk-order80模块控制层

package com.king.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@Slf4j
@RestController
public class OrderZkController {

    /**
     * zookeeper注册中心的提供服务模块的应用名称
     */
    private static final String INVOKE_URL = "http://cloud-provider-payment";

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 消费者端调用提供者查询方法
     * @return
     */
    @GetMapping("/consumer/payment/zookeeper")
    public String paymentInfo() {
        // getForObject读操作
        return restTemplate.getForObject(INVOKE_URL + "/payment/zookeeper", String.class);
    }
}

启动cloud-provider-payment8004cloud-consumerzk-order80的主启动类,在zkCli.sh下,通过命令ls /services可以查看到两个服务,这就表明服务注册成功了。

[zk: localhost:2181(CONNECTED) 0] ls /services
[cloud-provider-payment]
[zk: localhost:2181(CONNECTED) 1] ls /services
[cloud-consumer-order, cloud-provider-payment]

通过浏览器访问http://localhost:8004/payment/zookeeperhttp://localhost/consumer/payment/zookeeper都可以看到信息,其中http://localhost/consumer/payment/zookeeper请求会调用http://cloud-provider-payment/payment/zookeeper,其中cloud-provider-payment是服务名,从Zookeeper中找到真实的地址,发送/payment/zookeeper请求,此时,就发送到了提供者的controller上面,从而完成请求。

@LoadBalanced的理解停留在:加上它就可以实现负载均衡了,除了负载均衡,它还有将服务名转换成IP的功能,也就是根据服务名cloud-provider-payment,找到192.168.1.59地址。

我们查看LoadBalancerAutoConfiguration类,在restTemplateCustomizer()方法中,会给RestTemplate加上一个拦截器,从而让RestTemplate成为一个具有负载均衡功能的请求器。这个拦截器是ClientHttpRequestInterceptor类型的,这是一个接口,我们关注它的实现类LoadBalancerInterceptor,找到interceptor()方法,通过getHost()获取服务名,调用this.loadBalancer.execute()方法,发送请求。这里的loadBalancerLoadBalancerClient类型的,而LoadBalancerClient是一个接口,我们关注它的实现类RibbonLoadBalancerClient,找到execute()方法,根据serviceId(也就是服务名)通过getLoadBalancer()方法,获取一个ILoadBalancer对象,再调用getServer()方法,在getServer()方法中调用chooseServer()方法拿到server(也就是根据服务名获取到ip地址和端口号)