天天看點

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注解