天天看点

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

spring cloud使用zookeeper作为注册中心和配置中心

  • 为什么使用zookeeper作为注册中心和配置中心
  • 构建product模块
    • 使用SpringInitializr搭建项目环境
    • 注册服务到zookeeper
    • 编写接口,暴露服务给consumer,并重启服务
  • 构建consumer模块
    • 使用Spring Initializr构建
    • 添加application.yml配置
    • 启动类开启服务发现注释
    • 编写controller,并使用loadBalancerClient来调用product服务
    • 启动服务,并访问zkui,查看注册情况
    • 访问consumer接口地址,获取生产者暴露的服务
  • zookeeper注册中心实现原理
    • 附加一份比较完善的服务发现配置
    • 跟踪源码,查看服务注册和服务发现的地方
      • 服务注册
      • 服务发现
  • 使用zookeeper做配置中心
    • 将application.yml重命名为bootstrap.yml
    • 取消pom里面关于zookeeper-config的注释
    • 在bootstrap.yml中添加config的配置信息
    • 然后将注册的配置放入zookeeper中进行维护
    • 修改product模块中controller代码,引用配置中心的参数
    • 启动product模块,通过浏览器访问暴露的接口
    • 测试配置更新
    • 修改consumer模块的配置文件,也使用zookeeper来维护配置
    • 启动consumer,调用product服务
  • 写在后面

为什么使用zookeeper作为注册中心和配置中心

1.因为zookeeper的数据结构比较简单,而且与传统的磁盘文件系统不同的是,zk将全量数据存储在内存中,可谓是高性能,并且支持集群,另外还支持事件监听。这些特点决定了zk特别适合作为注册中心(数据发布/订阅)。

2.zookeeper在cap领域中占据了数据一致性和分区容错性,这让zookeeper在拥有高数据一致性的需求情况下,有很大的优势,使我们在这种保证高数据一致性的需求下,能够满足我们的需求

构建product模块

使用SpringInitializr搭建项目环境

这里我使用的开发软件是idea,,如果有用到eclipse作为开发软件的话,那请创建一个maven项目,将我下面的pom文件复制过去,然后创建响应的目录结构即可

1.使用idea新建项目,选择Spring Initializr,点击next

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

2.填写maven项目结构信息,点击next

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

3.这里使用spring boot 2.3.10版本,然后选中以下图中组件,点击next

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

4.修改项目构建目录等信息,点击finish

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

5.项目结构如下

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

注册服务到zookeeper

注意: 这里我的zookeeper服务通过我之前写的博客搭建的,博客地址如下

: docker篇-(docker-compose安装zookeeper集群,并使用nginx实现负载均衡)

这里现在pom文件里面把spring-cloud-starter-zookeeper-config的依赖注释调,因为spring-cloud-starter-config需要的配置优先级比较高,需要加载bootstrap.yml里面的配置,所以这里先把组件先注释调,如下图

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

1.创建application.yml文件,创建如下配置信息

spring:
  application:
    name: product
  cloud:
    zookeeper:
      connect-string: 192.168.101.180:2181
server:
  port: 9090
           

2.启动程序,通过zk ui查看注册信息

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

编写接口,暴露服务给consumer,并重启服务

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面
package com.lhstack.cloud.product.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lhstack
 */
@RestController
@RequestMapping
public class ProductController {

    @GetMapping("product")
    public String product(){
        return "this is product";
    }
}

           

构建consumer模块

使用Spring Initializr构建

这里新增ribbon的依赖

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

同理,在开始之前,我们也先把config相关的依赖给注释掉

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

添加application.yml配置

spring:
  application:
    name: consumer
  cloud:
    zookeeper:
      connect-string: 192.168.101.180:2181
server:
  port: 8080
           
spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

启动类开启服务发现注释

因为这里要获取服务,所以需要使用EnableDiscoveryClient注释来开启服务发现

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

编写controller,并使用loadBalancerClient来调用product服务

package com.lhstack.cloud.consumer.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @author lhstack
 */
@RestController
@RequestMapping
public class ConsumerController {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("consumer")
    public String consumer(){
        ServiceInstance serviceInstance = loadBalancerClient.choose("product");
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.getForObject(serviceInstance.getUri().toString() + "/product",String.class);
    }
}

           
spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

启动服务,并访问zkui,查看注册情况

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

访问consumer接口地址,获取生产者暴露的服务

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

zookeeper注册中心实现原理

附加一份比较完善的服务发现配置

spring:
  application:
    name: product
  cloud:
    zookeeper:
      connect-string: 192.168.101.180:2181 #这里多个地址,用,隔开即可
      discovery:
        metadata:
          version: v1 #这个配置,就是在注册服务之后,可以添加一些业务数据,如版本,如region等等,可以在后续开发gateway的过程中,或者使用loadbalancer实现灰度发布,蓝绿发布等等的功能
        root: /discovery #这里,设置服务注册的跟节点,查看zkui,默认的根节点是/services,可以设置成多级目录,如第一级目录为项目名,第二级目录为环境名等等,如/cms/dev,则就是cms项目dev环境,千万不要使用spring.cloud.zookeeper.prefix,我也不知道为什么,加了这个参数之后,就无法发现服务了
server:
  port: 9090
           

跟踪源码,查看服务注册和服务发现的地方

  • 服务注册

...
public class ZookeeperAutoServiceRegistrationAutoConfiguration {
    ...

	//我们在这里可以扩展实现自己的ServiceInstanceRegistration
	//在这里我透露一下,spring-cloud-starter-zookeeper-discovery默认创建的zookeeper节点是临时节点,如果不能满足需求,可以自己通过这种方式扩展
	//这里我说明一下,zookeeper的零时节点是根据session失效来实现的,除了服务宕机,基本就不会出现服务下线的情况,这也是zookeeper的特点所在,通过session来实现心跳的功能,性能会比http协议高很多,因为zookeeper是基于tcp协议的,同时本身就附带心跳检测功能
    @Bean
    @ConditionalOnMissingBean({ZookeeperRegistration.class})
    public ServiceInstanceRegistration serviceInstanceRegistration(ApplicationContext context, ZookeeperDiscoveryProperties properties) {
        String appName = context.getEnvironment().getProperty("spring.application.name", "application");
        String host = properties.getInstanceHost();
        if (!StringUtils.hasText(host)) {
            throw new IllegalStateException("instanceHost must not be empty");
        } else {
            properties.getMetadata().put("instance_status", properties.getInitialStatus());
            ZookeeperInstance zookeeperInstance = new ZookeeperInstance(context.getId(), appName, properties.getMetadata());
			//这里,就是构建我们注册的服务实例信息
            RegistrationBuilder builder = ServiceInstanceRegistration.builder().address(host).name(appName).payload(zookeeperInstance).uriSpec(properties.getUriSpec());
            if (properties.getInstanceSslPort() != null) {
                builder.sslPort(properties.getInstanceSslPort());
            }

            if (properties.getInstanceId() != null) {
                builder.id(properties.getInstanceId());
            }

            return builder.build();
        }
    }
}

           
...
public class ServiceDiscoveryImpl<T> implements ServiceDiscovery<T> {
    ...
	//这个方法,就是向zookeeper里面写入服务注册的信息
    @VisibleForTesting
    protected void internalRegisterService(ServiceInstance<T> service) throws Exception {
        byte[] bytes = this.serializer.serialize(service);
        String path = this.pathForInstance(service.getName(), service.getId());
        int MAX_TRIES = true;
        boolean isDone = false;

		//这里,我们之前看到的服务builder的那个类,默认CreateMode就是EPHEMERAL节点
        for(int i = 0; !isDone && i < 2; ++i) {
            try {
                CreateMode mode;
                switch(service.getServiceType()) {
                case DYNAMIC:
                    mode = CreateMode.EPHEMERAL;
                    break;
                case DYNAMIC_SEQUENTIAL:
                    mode = CreateMode.EPHEMERAL_SEQUENTIAL;
                    break;
                default:
                    mode = CreateMode.PERSISTENT;
                }

                ((ACLBackgroundPathAndBytesable)this.client.create().creatingParentContainersIfNeeded().withMode(mode)).forPath(path, bytes);
                isDone = true;
            } catch (NodeExistsException var8) {
                this.client.delete().forPath(path);
            }
        }

    }

    public void unregisterService(ServiceInstance<T> service) throws Exception {
        ServiceDiscoveryImpl.Entry<T> entry = (ServiceDiscoveryImpl.Entry)this.services.remove(service.getId());
        this.internalUnregisterService(entry);
    }

    public ServiceProviderBuilder<T> serviceProviderBuilder() {
        return (new ServiceProviderBuilderImpl(this)).providerStrategy(new RoundRobinStrategy()).threadFactory(ThreadUtils.newThreadFactory("ServiceProvider"));
    }
	...
}
           
  • 服务发现

...
//这里,实现了spring提供的DiscoveryClient,然后内部调用ServiceDiscovery,就是上面那个服务注册那个类
public class ZookeeperDiscoveryClient implements DiscoveryClient {
    ...
    private final ServiceDiscovery<ZookeeperInstance> serviceDiscovery;
  
}

           

使用zookeeper做配置中心

将application.yml重命名为bootstrap.yml

因为application.yml是spring cloud应用初始化之后加载的配置文件,而bootstrap.yml是spring cloud应用初始化之前加载的配置,由于应用需要加载配置,所以spring-cloud-strater-zookeeper-config在加载的时候,是加载的bootstrap.yml的文件内容,保证spring cloud应用在初始化之前就已经拿到配置

取消pom里面关于zookeeper-config的注释

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

在bootstrap.yml中添加config的配置信息

spring:
  profiles:
    active: dev #指定dev环境
  application:
    name: product
  cloud:
    zookeeper:
      config:
        root: /discovery/config
        profileSeparator: "," #这是与环境连接的分隔符,如现在配置的是dev环境,那么加载的配置文件名称就是/discovery/config/product,dev  /discovery/config/product /discovery/application,dev  /discovery/application 优先级是环境,其次是spring.application.name然后再是 defaultContext,defaultContext是用来做默认配置的,用于比如全局的通用信息等等
        defaultContext: application #这是默认加载的命名空间
      connect-string: 192.168.101.180:2181 #这里多个地址,用,隔开即可
           

然后将注册的配置放入zookeeper中进行维护

1.将注册信息放在application,dev环境下面,这种信息应该由当前项目当前环境的所有服务共享

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

2.将特定信息放在当前服务的特定环境下面

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

修改product模块中controller代码,引用配置中心的参数

package com.lhstack.cloud.product.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lhstack
 */
@RestController
@RequestMapping
@RefreshScope //开启属性更新功能,让这个bean里面的属性会根据配置中心的修改而同步
public class ProductController {

    @Value("${msg:hello}")
    private String msg;

    @GetMapping("product")
    public String product(){
        return msg;
    }
}
           

启动product模块,通过浏览器访问暴露的接口

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

也打印出来了配置中心的配置信息

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

测试配置更新

修改zookeeper里面product,dev环境下面的msg的值,然后在访问接口

这里修改成2

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

查看控制台的日志,修改的key也打印出来了

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

通过浏览器访问

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

修改consumer模块的配置文件,也使用zookeeper来维护配置

对应的pom文件也要取消注释

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

application.yml也要重命名为bootstrap.yml

spring:
  profiles:
    active: dev
  application:
    name: consumer
  cloud:
    zookeeper:
      config:
        root: /discovery/config
        profileSeparator: "," #这是与环境连接的分隔符,如现在配置的是dev环境,那么加载的配置文件名称就是/discovery/config/consumer,dev  /discovery/config/consumer /discovery/application,dev  /discovery/application
        defaultContext: application #这是默认加载的命名空间
      connect-string: 192.168.101.180:2181 #这里多个地址,用,隔开即可
server:
  port: 8080
           

zookeeper中配置如下

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

启动consumer,调用product服务

spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面
spring cloud篇-(使用zookeeper作为注册中心和配置中心)为什么使用zookeeper作为注册中心和配置中心构建product模块构建consumer模块zookeeper注册中心实现原理使用zookeeper做配置中心写在后面

写在后面

zookeeper实现配置中心的原理,大家有兴趣可以去查看ZookeeperConfigAutoConfiguration和ZookeeperConfigBootstrapConfiguration两个类,ZookeeperConfigBootstrapConfiguration的作用是用于第一次启动的时候,去加载zookeeper里面的配置,然后记录所有加载的keys,交给ZookeeperConfigAutoConfiguration去创建ConfigWatcher监听对应的keys,通过监听zk里面的更新事件,去更新对应的keys,当然,要更新属性的对象,必须加上RefreshScope注解