spring boot consul 客户端加载过程
1,引入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<version>3.0.2</version>
</dependency>
主要是下面几个包,还有starter不写了
com.ecwid.consul:consul-api:1.4.5
org.springframework.cloud:spring-cloud-consul-core:3.0.2
org.springframework.cloud:spring-cloud-consul-discovery:3.0.2
配置
spring.cloud.consul.host=xxx
spring.cloud.consul.port=8500
#注册到consul的服务名称
spring.cloud.consul.discovery.service-name=${spring.application.name}
spring.cloud.consul.discovery.prefer-ip-address=true
spring.cloud.consul.discovery.instance-id=${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
spring.cloud.consul.discovery.heartbeat.enabled=true
spring.cloud.consul.discovery.health-check-interval=3s
spring.cloud.consul.discovery.health-check-url=http://${spring.cloud.client.ip-address}:${server.port}/actuator/health
spring.cloud.consul.enabled=true
#服务停止时取消注册
spring.cloud.consul.discovery.deregister=true
#健康检查失败多长时间后,取消注册
spring.cloud.consul.discovery.health-check-critical-timeout=10s
consul用instance-id来区分实例的,同一个机器开多个服务都想注册到consul上就加上port
这样服务名相同,配合OpenFeign 和 spring-cloud-loadbalancer就实现 负载均衡了
上面的配置写好了就可以自动注册了
2,spring-cloud-consul-core
2.1 spring.fatories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.consul.ConsulAutoConfiguration
2.2 ConsulAutoConfiguration
主要功能:
- 根据配置生成ConsulClient对象 后面都会用到它
- 生成健康检查用到的对象 ConsulEndpoint,ConsulHealthIndicator
- 生成重试拦截器 RetryOperationsInterceptor
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnConsulEnabled
public class ConsulAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ConsulProperties consulProperties() {
return new ConsulProperties();
}
@Bean
@ConditionalOnMissingBean
public ConsulClient consulClient(ConsulProperties consulProperties) {
return createConsulClient(consulProperties);
}
public static ConsulClient createConsulClient(ConsulProperties consulProperties) {
final String agentPath = consulProperties.getPath();
final String agentHost = StringUtils.hasLength(consulProperties.getScheme())
? consulProperties.getScheme() + "://" + consulProperties.getHost() : consulProperties.getHost();
final ConsulRawClient.Builder builder = ConsulRawClient.Builder.builder().setHost(agentHost)
.setPort(consulProperties.getPort());
if (consulProperties.getTls() != null) {
ConsulProperties.TLSConfig tls = consulProperties.getTls();
TLSConfig tlsConfig = new TLSConfig(tls.getKeyStoreInstanceType(), tls.getCertificatePath(),
tls.getCertificatePassword(), tls.getKeyStorePath(), tls.getKeyStorePassword());
builder.setTlsConfig(tlsConfig);
}
if (StringUtils.hasLength(agentPath)) {
String normalizedAgentPath = StringUtils.trimTrailingCharacter(agentPath, '/');
normalizedAgentPath = StringUtils.trimLeadingCharacter(normalizedAgentPath, '/');
builder.setPath(normalizedAgentPath);
}
return new ConsulClient(builder.build());
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Endpoint.class)
@EnableConfigurationProperties(ConsulHealthIndicatorProperties.class)
protected static class ConsulHealthConfig {
@Bean
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint
public ConsulEndpoint consulEndpoint(ConsulClient consulClient) {
return new ConsulEndpoint(consulClient);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledHealthIndicator("consul")
public ConsulHealthIndicator consulHealthIndicator(ConsulClient consulClient,
ConsulHealthIndicatorProperties properties) {
return new ConsulHealthIndicator(consulClient, properties);
}
}
@ConditionalOnClass({ Retryable.class, Aspect.class, AopAutoConfiguration.class })
@Configuration(proxyBeanMethods = false)
@EnableRetry(proxyTargetClass = true)
@Import(AopAutoConfiguration.class)
@EnableConfigurationProperties(RetryProperties.class)
@ConditionalOnProperty(value = "spring.cloud.consul.retry.enabled", matchIfMissing = true)
protected static class RetryConfiguration {
@Bean(name = "consulRetryInterceptor")
@ConditionalOnMissingBean(name = "consulRetryInterceptor")
public RetryOperationsInterceptor consulRetryInterceptor(RetryProperties properties) {
return RetryInterceptorBuilder.stateless().backOffOptions(properties.getInitialInterval(),
properties.getMultiplier(), properties.getMaxInterval()).maxAttempts(properties.getMaxAttempts())
.build();
}
}
}
3,spring-cloud-consul-discovery
3.1 spring.fatories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.consul.discovery.configclient.ConsulConfigServerAutoConfiguration,\
org.springframework.cloud.consul.serviceregistry.ConsulAutoServiceRegistrationAutoConfiguration,\
org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistryAutoConfiguration,\
org.springframework.cloud.consul.discovery.ConsulDiscoveryClientConfiguration,\
org.springframework.cloud.consul.discovery.reactive.ConsulReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.consul.discovery.ConsulCatalogWatchAutoConfiguration, \
org.springframework.cloud.consul.support.ConsulHeartbeatAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.consul.discovery.configclient.ConsulDiscoveryClientConfigServiceBootstrapConfiguration
org.springframework.boot.Bootstrapper=\
org.springframework.cloud.consul.discovery.configclient.ConsulConfigServerBootstrapper
下面这3个自动配置,是跟配置中心相关的
这篇文章不讨论
org.springframework.cloud.consul.discovery.configclient.ConsulConfigServerAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.consul.discovery.configclient.ConsulDiscoveryClientConfigServiceBootstrapConfiguration
org.springframework.boot.Bootstrapper=\
org.springframework.cloud.consul.discovery.configclient.ConsulConfigServerBootstrapper
3.2 ConsulServiceRegistryAutoConfiguration
它要在ServiceRegistryAutoConfiguration 之前加载
ServiceRegistryAutoConfiguration是spring-cloud-commons包下的 等下看
spring-cloud-commons:它的主要功能
- DiscoveryClient接口
- ServiceRegistry接口
- RestTemplate用于使用DiscoveryClient解析主机名的工具
ConsulServiceRegistryAutoConfiguration
主要功能:
- 生成ConsulServiceRegistry
consulServiceRegistry
需要的参数 consulClient 来自 2,spring-cloud-consul-core 里生产的
返回值ConsulServiceRegistry 实现了ServiceRegistry 接口,要给ServiceRegistryAutoConfiguration用
@Configuration(proxyBeanMethods = false)
@ConditionalOnConsulEnabled
@Conditional(ConsulServiceRegistryAutoConfiguration.OnConsulRegistrationEnabledCondition.class)
@AutoConfigureBefore(ServiceRegistryAutoConfiguration.class)
public class ConsulServiceRegistryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ConsulServiceRegistry consulServiceRegistry(ConsulClient consulClient, ConsulDiscoveryProperties properties,
HeartbeatProperties heartbeatProperties, @Autowired(required = false) TtlScheduler ttlScheduler) {
return new ConsulServiceRegistry(consulClient, properties, ttlScheduler, heartbeatProperties);
}
@Bean
@ConditionalOnMissingBean
public HeartbeatProperties heartbeatProperties() {
return new HeartbeatProperties();
}
@Bean
@ConditionalOnMissingBean
// TODO: Split appropriate values to service-registry for Edgware
public ConsulDiscoveryProperties consulDiscoveryProperties(InetUtils inetUtils) {
return new ConsulDiscoveryProperties(inetUtils);
}
protected static class OnConsulRegistrationEnabledCondition extends AllNestedConditions {
。。。
}
}
3.3 ServiceRegistryAutoConfiguration
来自spring-cloud-commons包
但如果要走这个函数serviceRegistryEndpoint
必须要配置spring.jmx.enabled=true
我没有开启它,所以这里不会走到
至于JMX是啥,大家自己去查一下
@Configuration(proxyBeanMethods = false)
public class ServiceRegistryAutoConfiguration {
@ConditionalOnBean(ServiceRegistry.class)
@ConditionalOnClass(Endpoint.class)
protected class ServiceRegistryEndpointConfiguration {
@Autowired(required = false)
private Registration registration;
@Bean
@ConditionalOnAvailableEndpoint
public ServiceRegistryEndpoint serviceRegistryEndpoint(ServiceRegistry serviceRegistry) {
ServiceRegistryEndpoint endpoint = new ServiceRegistryEndpoint(serviceRegistry);
endpoint.setRegistration(this.registration);
return endpoint;
}
}
}
3.5 ConsulDiscoveryClientConfiguration
在ConsulAutoConfiguration之后加载
在下面两个类之前加载,这两个类都是spring-cloud-commons包的
- SimpleDiscoveryClientAutoConfiguration:用来找到服务名,hostname等
- CommonsClientAutoConfiguration:这个也是要配置spring.jmx.enabled=true
ConsulDiscoveryClientConfiguration
主要功能:
- 生产ConsulDiscoveryProperties对象
- 生产ConsulDiscoveryClient对象,用到了上面的ConsulClient
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
@ConditionalOnConsulEnabled
@ConditionalOnConsulDiscoveryEnabled
@EnableConfigurationProperties
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class })
@AutoConfigureAfter({ UtilAutoConfiguration.class, ConsulAutoConfiguration.class })
public class ConsulDiscoveryClientConfiguration {
@Bean
@ConditionalOnMissingBean
public ConsulDiscoveryProperties consulDiscoveryProperties(InetUtils inetUtils) {
return new ConsulDiscoveryProperties(inetUtils);
}
@Bean
@ConditionalOnMissingBean
public ConsulDiscoveryClient consulDiscoveryClient(ConsulClient consulClient,
ConsulDiscoveryProperties discoveryProperties) {
return new ConsulDiscoveryClient(consulClient, discoveryProperties);
}
}
3.5 ConsulAutoServiceRegistrationAutoConfiguration
在下面两个类之后加载
- AutoServiceRegistrationConfiguration:来自spring-cloud-commons包,没有函数,主要是加载AutoServiceRegistrationProperties
- ConsulServiceRegistryAutoConfiguration:3.2说明了
主要功能:
- 生产ConsulAutoServiceRegistration对象
- 生产ConsulAutoServiceRegistrationListener对象:这个对象很重要是真正的启动类
- 生产ConsulAutoRegistration对象
- 生产ConsulRegistrationCustomizer对象:用到了ServletContext
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnMissingBean(type = "org.springframework.cloud.consul.discovery.ConsulLifecycle")
@ConditionalOnConsulEnabled
@Conditional(ConsulAutoServiceRegistrationAutoConfiguration.OnConsulRegistrationEnabledCondition.class)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class, ConsulServiceRegistryAutoConfiguration.class })
public class ConsulAutoServiceRegistrationAutoConfiguration {
@Autowired
AutoServiceRegistrationProperties autoServiceRegistrationProperties;
@Bean
@ConditionalOnMissingBean
public ConsulAutoServiceRegistration consulAutoServiceRegistration(ConsulServiceRegistry registry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties, ConsulDiscoveryProperties properties,
ConsulAutoRegistration consulRegistration) {
return new ConsulAutoServiceRegistration(registry, autoServiceRegistrationProperties, properties,
consulRegistration);
}
@Bean
public ConsulAutoServiceRegistrationListener consulAutoServiceRegistrationListener(
ConsulAutoServiceRegistration registration) {
return new ConsulAutoServiceRegistrationListener(registration);
}
@Bean
@ConditionalOnMissingBean
public ConsulAutoRegistration consulRegistration(
AutoServiceRegistrationProperties autoServiceRegistrationProperties, ConsulDiscoveryProperties properties,
ApplicationContext applicationContext,
ObjectProvider<List<ConsulRegistrationCustomizer>> registrationCustomizers,
ObjectProvider<List<ConsulManagementRegistrationCustomizer>> managementRegistrationCustomizers,
HeartbeatProperties heartbeatProperties) {
return ConsulAutoRegistration.registration(autoServiceRegistrationProperties, properties, applicationContext,
registrationCustomizers.getIfAvailable(), managementRegistrationCustomizers.getIfAvailable(),
heartbeatProperties);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ServletContext.class)
protected static class ConsulServletConfiguration {
@Bean
public ConsulRegistrationCustomizer servletConsulCustomizer(ObjectProvider<ServletContext> servletContext) {
return new ConsulServletRegistrationCustomizer(servletContext);
}
}
protected static class OnConsulRegistrationEnabledCondition extends AllNestedConditions {
。。。
}
}
到目前为止好像什么都没发生,都是new一些对象
但实际上我们注册了一个Listener
ConsulAutoServiceRegistrationListener
它实现了SmartApplicationListener接口
SmartApplicationListener的用法请参考:使用Spring SmartApplicationListener实现业务解耦
我在项目里用的是tomcat所以在它启动后,onApplicationEvent会被调用
public class ConsulAutoServiceRegistrationListener implements SmartApplicationListener {
private final ConsulAutoServiceRegistration autoServiceRegistration;
public ConsulAutoServiceRegistrationListener(ConsulAutoServiceRegistration autoServiceRegistration) {
this.autoServiceRegistration = autoServiceRegistration;
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return WebServerInitializedEvent.class.isAssignableFrom(eventType);
}
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return true;
}
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if (applicationEvent instanceof WebServerInitializedEvent) {
WebServerInitializedEvent event = (WebServerInitializedEvent) applicationEvent;
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management".equals(((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {
return;
}
}
this.autoServiceRegistration.setPortIfNeeded(event.getWebServer().getPort());
this.autoServiceRegistration.start();
}
}
@Override
public int getOrder() {
return 0;
}
}
接下来就是ConsulAutoServiceRegistration
4,ConsulAutoServiceRegistration
它的bind覆盖掉了父类的,等下看为什么
public class ConsulAutoServiceRegistration extends AbstractAutoServiceRegistration<ConsulRegistration> {
。。。
public ConsulAutoServiceRegistration(ConsulServiceRegistry serviceRegistry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties, ConsulDiscoveryProperties properties,
ConsulAutoRegistration registration) {
super(serviceRegistry, autoServiceRegistrationProperties);
this.properties = properties;
this.registration = registration;
}
@Override
@Retryable(interceptor = "consulRetryInterceptor")
public void start() {
super.start();
}
@Override
public void bind(WebServerInitializedEvent event) {
// do nothing so we can listen for this event in a different class
// this ensures start() can be retried if spring-retry is available
}
// 这个实现方法 会被父类调用
@Override
protected ConsulAutoRegistration getRegistration() {
if (this.registration.getService().getPort() == null && this.getPort().get() > 0) {
this.registration.initializePort(this.getPort().get());
}
Assert.notNull(this.registration.getService().getPort(), "service.port has not been set");
return this.registration;
}
。。。
}
调用父类的start
4.1 AbstractAutoServiceRegistration
实现了:ApplicationListener,所以它可以触发监听事件,但它的bind被标记为过期方法了,所以上面 的子类,重写了个空的,防止start 被调用2次
public abstract class AbstractAutoServiceRegistration<R extends Registration>
implements AutoServiceRegistration, ApplicationContextAware, ApplicationListener<WebServerInitializedEvent> {
。。。
protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry,
AutoServiceRegistrationProperties properties) {
this.serviceRegistry = serviceRegistry;
this.properties = properties;
}
@Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
bind(event);
}
// 这个方法被标记为过期了
@Deprecated
public void bind(WebServerInitializedEvent event) {
。。。
this.start();
}
public void start() {
if (!isEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
return;
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get()) {
// 发布一个自定义事件InstancePreRegisteredEvent
this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
// 无论调用 register 还是 registerManagement 最终调用的都是 ConsulServiceRegistry.register
register();
if (shouldRegisterManagement()) {
registerManagement();
}
// 发布一个自定义事件InstanceRegisteredEvent
this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false, true);
}
}
。。。
protected void register() {
// getRegistration 为上面的子类的实现方法
this.serviceRegistry.register(getRegistration());
}
protected void registerManagement() {
R registration = getManagementRegistration();
if (registration != null) {
this.serviceRegistry.register(registration);
}
}
。。。
}
4.2 ConsulServiceRegistry
public class ConsulServiceRegistry implements ServiceRegistry<ConsulRegistration> {
public ConsulServiceRegistry(ConsulClient client, ConsulDiscoveryProperties properties, TtlScheduler ttlScheduler,
HeartbeatProperties heartbeatProperties) {
this.client = client;
this.properties = properties;
this.ttlScheduler = ttlScheduler;
this.heartbeatProperties = heartbeatProperties;
}
@Override
public void register(ConsulRegistration reg) {
log.info("Registering service with consul: " + reg.getService());
try {
this.client.agentServiceRegister(reg.getService(), this.properties.getAclToken());
NewService service = reg.getService();
if (this.heartbeatProperties.isEnabled() && this.ttlScheduler != null && service.getCheck() != null
&& service.getCheck().getTtl() != null) {
this.ttlScheduler.add(reg.getService());
}
}
catch (ConsulException e) {
if (this.properties.isFailFast()) {
log.error("Error registering service with consul: " + reg.getService(), e);
ReflectionUtils.rethrowRuntimeException(e);
}
log.warn("Failfast is false. Error registering service with consul: " + reg.getService(), e);
}
}
}
调用的是
ConsulClient.agentServiceRegister
4.3 ConsulClient
它在com.ecwid.consul:consul-api:1.4.5 包里
这个ConsulClient里组合了好多个Client
包括健康检查,事件,日志等
注册的话,是调用的agentClient
public class ConsulClient implements {
public ConsulClient(ConsulRawClient rawClient) {
aclClient = new AclConsulClient(rawClient);
agentClient = new AgentConsulClient(rawClient);
catalogClient = new CatalogConsulClient(rawClient);
coordinateClient = new CoordinateConsulClient(rawClient);
eventClient = new EventConsulClient(rawClient);
healthClient = new HealthConsulClient(rawClient);
keyValueClient = new KeyValueConsulClient(rawClient);
queryClient = new QueryConsulClient(rawClient);
sessionClient = new SessionConsulClient(rawClient);
statusClient = new StatusConsulClient(rawClient);
}
@Override
public Response<Void> agentServiceRegister(NewService newService, String token) {
return agentClient.agentServiceRegister(newService, token);
}
4.4 AgentConsulClient
public final class AgentConsulClient implements AgentClient {
private final ConsulRawClient rawClient;
public AgentConsulClient(ConsulRawClient rawClient) {
this.rawClient = rawClient;
}
@Override
public Response<Void> agentServiceRegister(NewService newService, String token) {
UrlParameters tokenParam = token != null ? new SingleUrlParameters("token", token) : null;
String json = GsonFactory.getGson().toJson(newService);
HttpResponse httpResponse = rawClient.makePutRequest("/v1/agent/service/register", json, tokenParam);
if (httpResponse.getStatusCode() == 200) {
return new Response<Void>(null, httpResponse);
} else {
throw new OperationException(httpResponse);
}
}
@Override
public Response<Map<String, Service>> getAgentServices() {
HttpResponse httpResponse = rawClient.makeGetRequest("/v1/agent/services");
if (httpResponse.getStatusCode() == 200) {
Map<String, Service> agentServices = GsonFactory.getGson().fromJson(httpResponse.getContent(),
new TypeToken<Map<String, Service>>() {
}.getType());
return new Response<Map<String, Service>>(agentServices, httpResponse);
} else {
throw new OperationException(httpResponse);
}
}
}
好的 我们看到了 HttpResponse,在这里是真正发起请求向服务器注册了
5,总结
Consul是在web服务启动完成后(监听WebServerInitializedEvent)向注册中心发起注册的
如果本地服务不是个web服务,或者启动失败了。是不会注册的。
也就保证本地服务的可用性