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配置文件中的數據是對應的