Demo
见 https://blog.csdn.net/justsomebody126/article/details/102993806
spring-cloud-zookeeper 源码
https://github.com/spring-cloud/spring-cloud-zookeeper (参照其说明文档进行编译测试)
以下分析 spring-cloud-zookeeper 源码来分析 Producer 是如何注册自身,Customer 又是如何发现 Producer 的。
测试
- 编译 mvn --settings .settings.xml package -Dmaven.test.skip (跳过测试)
- 执行 sample
java -jar ./spring-cloud-zookeeper-sample/target/spring-cloud-zookeeper-sample-2.2.0.BUILD-SNAPSHOT.jar
- 测试 localhost:8085 (端口在 application.yml 中配置)
localhost: 8085 访问的后端代码,可见是通过 loadBalancer 从当前已注册的 server 中选取一个。
@RequestMapping("/")
public ServiceInstance lb() {
return this.loadBalancer.choose(this.appName);
}
返回结果:
{
"serviceId": "testZookeeperApp",
"server": {
"host": "30.5.99.156",
"port": 8085,
"scheme": null,
"id": "30.5.99.156:8085",
"zone": "UNKNOWN",
"readyToServe": true,
"metaInfo": {
"instanceId": "75d35968-0dcd-426b-96e2-9ec3afde4613",
"appName": "testZookeeperApp",
"serverGroup": null,
"serviceIdForDiscovery": null
},
"instance": {
"name": "testZookeeperApp",
"id": "75d35968-0dcd-426b-96e2-9ec3afde4613",
"address": "30.5.99.156",
"port": 8085,
"sslPort": null,
"payload": {
"id": "testZookeeperApp-1",
"name": "testZookeeperApp",
"metadata": {}
},
"registrationTimeUTC": 1573445760704,
"serviceType": "DYNAMIC",
"uriSpec": {
"parts": [{
"value": "scheme",
"variable": true
}, {
"value": "://",
"variable": false
}, {
"value": "address",
"variable": true
}, {
"value": ":",
"variable": false
}, {
"value": "port",
"variable": true
}]
},
"enabled": true
},
"alive": true,
"hostPort": "30.5.99.156:8085"
},
"secure": false,
"metadata": {},
"scheme": null,
"host": "30.5.99.156",
"port": 8085,
"instanceId": "30.5.99.156:8085",
"uri": "http://30.5.99.156:8085"
}
-
启动另外一个 sample
java -jar ./spring-cloud-zookeeper-sample/target/spring-cloud-zookeeper-sample-2.2.0.BUILD-SNAPSHOT.jar --server.port=8086
再执行两次 localhost: 8086,因有 loadBalancer,故返回的结果不同。
Producer 注册原理
- Producer 注册自己到 ZK 的代码
// org/springframework/cloud/zookeeper/serviceregistry/ZookeeperServiceRegistry.java
@Override
public void register(ZookeeperRegistration registration) {
try {
getServiceDiscovery().registerService(registration.getServiceInstance());
}
catch (Exception e) {
rethrowRuntimeException(e);
}
}
- 相应执行堆栈
- 那么是什么控制 Producer 启动时调用 ZookeeperServiceRegistry.register() 将自己注册到 ZK 的呢?
// org/springframework/cloud/zookeeper/serviceregistry/ZookeeperAutoServiceRegistration.java
@Override
protected void register() {
if (!this.properties.isRegister()) {
log.debug("Registration disabled.");
return;
}
if (this.registration.getPort() == 0) {
this.registration.setPort(getPort().get());
}
super.register();
}
// properties 是 ZookeeperDiscoveryProperties.java 这个类的实例,
// 可以通过 setRegister(false) 来决定是否注册。
// ZookeeperDiscoveryProperties 与 application.properties 中配置绑定
@ConfigurationProperties("spring.cloud.zookeeper.discovery")
public class ZookeeperDiscoveryProperties { ... }
// ZookeeperAutoServiceRegistration 唯一被用到的地方
// org/springframework/cloud/zookeeper/serviceregistry/ZookeeperAutoServiceRegistrationAutoConfiguration.java
@Configuration
@ConditionalOnMissingBean(type = "org.springframework.cloud.zookeeper.discovery.ZookeeperLifecycle")
@ConditionalOnZookeeperDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@AutoConfigureAfter({ ZookeeperServiceRegistryAutoConfiguration.class })
@AutoConfigureBefore({ AutoServiceRegistrationAutoConfiguration.class,
ZookeeperDiscoveryAutoConfiguration.class })
public class ZookeeperAutoServiceRegistrationAutoConfiguration {
@Bean
public ZookeeperAutoServiceRegistration zookeeperAutoServiceRegistration(
ZookeeperServiceRegistry registry, ZookeeperRegistration registration,
ZookeeperDiscoveryProperties properties) {
return new ZookeeperAutoServiceRegistration(registry, registration, properties);
}
// @ConditionalOnMissingBean(type = "org.springframework.cloud.zookeeper.discovery.ZookeeperLifecycle") :
如果容器中已经存在 ZookeeperLifecycle bean 了,
就不需要装载 ZookeeperAutoServiceRegistrationAutoConfiguration,否则装载。
// @ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) :
如果 application.properties 中 spring.cloud.service-registry.auto-registration.enabled
属性为 true,就装载本 bean,否则不装载,默认为 true。
// @AutoConfigureAfter({ ZookeeperServiceRegistryAutoConfiguration.class }) :
本 bean 在 ZookeeperServiceRegistryAutoConfiguration 后装载。
// @AutoConfigureBefore({ AutoServiceRegistrationAutoConfiguration.class, ZookeeperDiscoveryAutoConfiguration.class }) :
本 bean 在 AutoServiceRegistrationAutoConfiguration 和 ZookeeperDiscoveryAutoConfiguration 前装载
// @ConditionalOnZookeeperDiscoveryEnabled :
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@ConditionalOnZookeeperEnabled
@ConditionalOnProperty(value = "spring.cloud.zookeeper.discovery.enabled", matchIfMissing = true)
public @interface ConditionalOnZookeeperDiscoveryEnabled {
}
// @ConditionalOnZookeeperEnabled:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@ConditionalOnProperty(value = "spring.cloud.zookeeper.enabled", matchIfMissing = true)
public @interface ConditionalOnZookeeperEnabled {
}
// ZookeeperAutoServiceRegistration 的构造函数
// org/springframework/cloud/zookeeper/serviceregistry/ZookeeperAutoServiceRegistration.java
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());
}
}
- 综上分析,并没有用户代码来明确调用 ZookeeperServiceRegistry.register() 将 Producer 注册到 ZK,而是通过 bean + application.properties 文件的方式,在容器启动时,决定是否注册。
Cusumer 发现 Producer 原理
(以下分析基于 Demo https://blog.csdn.net/justsomebody126/article/details/102993806 中的 consumer)
这里要解决这两个问题:
1. Consumer 怎么从 zookeeper 获取到 Producer 的 server 列表。
2. Consumer 怎么通过 Ribbon 实现负载均衡。
通过参考 https://my.oschina.net/13426421702/blog/3064709 以及本地 debug,可知:
- 请求通过 LoadBalancerFeignClient 调用 executeWithLoadBalancer 来实现负载均衡
获取要访问的真实 producer 地址的地方。
获取到的真实 Producer 的 server
- 下面分析一下 reconstructURIWithServer 是怎么通过 zookeeper 获取真实的 Producer server 的
跟一下代码,可见采用的是 Round Robin 算法。
getAllServerList:
- 那么 allServerList 是什么时候更新的?
从代码看,有一个 serverListUpdater 定时更新 Producer server 列表,就本例来说,就是从 zookeeper 获取。