Spring Cloud Zookeeper源碼解析-----服務註冊原理

zookeeper作爲一個分佈式文件系統,可用於註冊中心,配置中心(很少),分佈式鎖,命名服務等場景。同時已經集成到springcloud項目中----spring cloud zookeeper;接下來通過讀spring cloud zookeeper的源碼來了解它的服務註冊原理

1、application.yml配置文件配置

server:
  port: 9000
spring:
  application:
    name: lh-zookeeper-registry
  cloud:
    zookeeper:
      connect-string: localhost:2181
      discovery:
        register: true
        enabled: true
        root: lh-registry
        instance-host: 172.20.249.247

spring cloud zookeeper discovery屬性全部都來自於ZookeeperDiscoveryProperties類

1.1、ZookeeperDiscoveryProperties

@ConfigurationProperties("spring.cloud.zookeeper.discovery")
public class ZookeeperDiscoveryProperties {
    public static final String DEFAULT_URI_SPEC = "{scheme}://{address}:{port}";
    private HostInfo hostInfo;
    private boolean enabled = true;
    private String root = "/services";
    private String uriSpec = "{scheme}://{address}:{port}";
    private String instanceId;
    private String instanceHost;
    private String instanceIpAddress;
    private boolean preferIpAddress = false;
    private Integer instancePort;
    private Integer instanceSslPort;
    private boolean register = true;
    private Map<String, String> metadata = new HashMap();
    private String initialStatus = "UP";
    private int order = 0;
}

 該類中就包括了服務的一些基本信息以及後面創建zk節點的默認根節點("/services")

2、ZookeeperAutoServiceRegistrationAutoConfiguration

zookeeper服務自動註冊自動配置類

@Bean
    public ZookeeperAutoServiceRegistration zookeeperAutoServiceRegistration(ZookeeperServiceRegistry registry, ZookeeperRegistration registration, ZookeeperDiscoveryProperties properties) {
        return new ZookeeperAutoServiceRegistration(registry, registration, properties);
    }

    @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 {
            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().intValue());
            }

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

            return builder.build();
        }
    }

服務啓動之後,會先在這個類中根據application.yml文件中的配置內容生成一個ServiceInstanceRegistration服務實例註冊類;

這個類中包含了兩個對象:

  • ServiceInstance<ZookeeperInstance>服務實例對象
  • ServiceInstanceBuilder<ZookeeperInstance>服務實例構造對象 

ServiceInstance服務實例中的屬性就包含了服務的ip,port,name以及zk節點屬性等屬性;後面會根據這些信息創建一個zk節點

public class ServiceInstance<T> {
    private final String name;                   服務名
    private final String id;                     服務id
    private final String address;                服務地址(不填默認取主機名)
    private final Integer port;                  服務端口
    private final Integer sslPort;               https協議端口
    private final T payload;                     其實就是zookeeperinstance對象
    private final long registrationTimeUTC;      註冊時間
    private final ServiceType serviceType;       生成的zk節點屬性(默認是EPHEMERAL)
    private final UriSpec uriSpec;               包括了ip+端口
    private final boolean enabled;               該服務實例是否可用
}

ZookeeperInstance zk實例中包含了id,name以及metadata屬性

public class ZookeeperInstance {
    private String id;
    private String name;
    private Map<String, String> metadata = new HashMap();
}

2.1、ServiceInstanceRegistration

回到ZookeeperAutoServiceRegistrationAutoConfiguration中的ServiceInstanceRegistration生成Bean的邏輯

@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 {
            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().intValue());
            }

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

            return builder.build();
        }
    }

首先獲取服務名,默認爲"application";

然後獲取instance-host值(String host = properties.getInstanceHost();),如果配置文件中配置了instance-host的值(ip地址),那麼host就取配置文件中的值,如果配置文件中沒有配置的話,就默認取主機名;

接下來組裝ZookeeperInstance 對象信息(非重點)

接下來通過前面獲取的instance-host和name組裝成一個服務實例構造對象ServiceInstanceBuilder<ZookeeperInstance>

最後通過ServiceInstanceBuilder<ZookeeperInstance>.build()方法來創建一個ServiceInstanceRegistration 服務實例註冊對象

2.3、ZookeeperAutoServiceRegistration 

ZookeeperAutoServiceRegistration zk節點自動註冊服務;

@Bean
    public ZookeeperAutoServiceRegistration zookeeperAutoServiceRegistration(ZookeeperServiceRegistry registry, ZookeeperRegistration registration, ZookeeperDiscoveryProperties properties) {
        return new ZookeeperAutoServiceRegistration(registry, registration, properties);
    }

這裏通過ZookeeperServiceRegistry,ZookeeperRegistration和ZookeeperDiscoveryProperties來創建一個ZookeeperAutoServiceRegistration服務

public ZookeeperAutoServiceRegistration(ZookeeperServiceRegistry registry, ZookeeperRegistration registration, ZookeeperDiscoveryProperties properties, AutoServiceRegistrationProperties arProperties) {
        super(registry, arProperties);
        this.registration = registration;
        this.properties = properties;
        if (this.properties.getInstancePort() != null) {
            this.registration.setPort(this.properties.getInstancePort().intValue());
        }

    }

同時該類下有一個register()方法

protected void register() {
        if (!this.properties.isRegister()) {
            log.debug("Registration disabled.");
        } else {
            if (this.registration.getPort() == 0) {
                this.registration.setPort(this.getPort().get());
            }

            super.register();
        }
    }

一直進入register()方法,最後是在ZookeeperServiceRegistry類中調用register()方法

public void register(ZookeeperRegistration registration) {
        try {
            this.getServiceDiscovery().registerService(registration.getServiceInstance());
        } catch (Exception var3) {
            ReflectionUtils.rethrowRuntimeException(var3);
        }

    }

該方法中首先獲取ServiceDiscovery<ZookeeperInstance>對象

然後調用該對象中的registerService()方法

參數傳遞的是之前根據配置文件生成的ServiceInstanceRegistration對象得到的ServiceInstance對象

public ServiceInstance<ZookeeperInstance> getServiceInstance() {
        if (this.serviceInstance == null) {
            this.build();
        }

        return this.serviceInstance;
    }

最終的邏輯實現是由ServiceDiscoveryImpl實現了ServiceDiscovery<ZookeeperInstance>接口,然後實現了registerService()方法

public class ServiceDiscoveryImpl<T> implements ServiceDiscovery<T> {
    public void registerService(ServiceInstance<T> service) throws Exception {
        ServiceDiscoveryImpl.Entry<T> newEntry = new ServiceDiscoveryImpl.Entry(service, null);
        ServiceDiscoveryImpl.Entry<T> oldEntry = (ServiceDiscoveryImpl.Entry)this.services.putIfAbsent(service.getId(), newEntry);
        ServiceDiscoveryImpl.Entry<T> useEntry = oldEntry != null ? oldEntry : newEntry;
        synchronized(useEntry) {
            if (useEntry == newEntry) {
                useEntry.cache = this.makeNodeCache(service);
            }

            this.internalRegisterService(service);
        }
    }
}

核心邏輯是internalRegisterService()方法

@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;

        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);
            }
        }

    }

在這個方法中首先判斷得出節點的屬性,然後通過CuratorFramework來創建zk節點;至此服務就註冊到zk上面了

3、效果

啓動程序,通過ZooInspector連接zk客戶端;就可以看到zk上面的節點信息

NodeData節點數據爲:

{
	"name": "lh-zookeeper-registry",
	"id": "d1675575-baa4-4242-ab04-3d344238fd05",
	"address": "172.20.249.247",
	"port": 9000,
	"sslPort": null,
	"payload": {
		"@class": "org.springframework.cloud.zookeeper.discovery.ZookeeperInstance",
		"id": "application-1",
		"name": "lh-zookeeper-registry",
		"metadata": {}
	},
	"registrationTimeUTC": 1587371140572,
	"serviceType": "DYNAMIC",
	"uriSpec": {
		"parts": [{
			"value": "scheme",
			"variable": true
		}, {
			"value": "://",
			"variable": false
		}, {
			"value": "address",
			"variable": true
		}, {
			"value": ":",
			"variable": false
		}, {
			"value": "port",
			"variable": true
		}]
	}
}

可以得出NodeData的數據同application.yml配置文件中的數據是對應的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章