zookeeper作为一个分布式文件系统,可用于注册中心,配置中心(很少),分布式锁,命名服务等场景。同时已经集成到springcloud项目中----spring cloud zookeeper;接下来通过读spring cloud zookeeper的源码来了解它的服务注册原理
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类
@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 metadata = new HashMap();
private String initialStatus = "UP";
private int order = 0;
}
该类中就包括了服务的一些基本信息以及后面创建zk节点的默认根节点("/services")
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服务实例中的属性就包含了服务的ip,port,name以及zk节点属性等属性;后面会根据这些信息创建一个zk节点
public class ServiceInstance {
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 metadata = new HashMap();
}
回到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
最后通过ServiceInstanceBuilder
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
然后调用该对象中的registerService()方法
参数传递的是之前根据配置文件生成的ServiceInstanceRegistration对象得到的ServiceInstance对象
public ServiceInstance getServiceInstance() {
if (this.serviceInstance == null) {
this.build();
}
return this.serviceInstance;
}
最终的逻辑实现是由ServiceDiscoveryImpl实现了ServiceDiscovery
public class ServiceDiscoveryImpl implements ServiceDiscovery {
public void registerService(ServiceInstance service) throws Exception {
ServiceDiscoveryImpl.Entry newEntry = new ServiceDiscoveryImpl.Entry(service, null);
ServiceDiscoveryImpl.Entry oldEntry = (ServiceDiscoveryImpl.Entry)this.services.putIfAbsent(service.getId(), newEntry);
ServiceDiscoveryImpl.Entry useEntry = oldEntry != null ? oldEntry : newEntry;
synchronized(useEntry) {
if (useEntry == newEntry) {
useEntry.cache = this.makeNodeCache(service);
}
this.internalRegisterService(service);
}
}
}
核心逻辑是internalRegisterService()方法
@VisibleForTesting
protected void internalRegisterService(ServiceInstance 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上面了
启动程序,通过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配置文件中的数据是对应的