前言

  • 上一章节通过日志信息定位ConsulServiceRegistry这个类会进行注册操作,通过断点调试发现是由ConsulAutoServiceRegistration这个类来指挥的,这一章节来介绍这个类

解析

ConsulAutoServiceRegistration的配置

  • 这个类是哪里配置呢,查看spring.factories里面配置了一个类ConsulAutoServiceRegistrationAutoConfiguration
1
2
3
4
5
6
7
8
9
10
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration,\
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.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.consul.discovery.configclient.ConsulDiscoveryClientConfigServiceBootstrapConfiguration
  • 查看ConsulAutoServiceRegistrationAutoConfiguration.java类可以发现里面配置了@Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* @author Spencer Gibb
*/
@Configuration
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnMissingBean(type = "org.springframework.cloud.consul.discovery.ConsulLifecycle")
@ConditionalOnConsulEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@AutoConfigureAfter({AutoServiceRegistrationConfiguration.class, ConsulServiceRegistryAutoConfiguration.class})
public class ConsulAutoServiceRegistrationAutoConfiguration {

@Autowired
AutoServiceRegistrationProperties autoServiceRegistrationProperties;
// 注册ConsulAutoServiceRegistration
@Bean
@ConditionalOnMissingBean
public ConsulAutoServiceRegistration consulAutoServiceRegistration(
ConsulServiceRegistry registry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
ConsulDiscoveryProperties properties,
ConsulAutoRegistration consulRegistration) {
return new ConsulAutoServiceRegistration(registry,
autoServiceRegistrationProperties, properties, consulRegistration);
}

@Bean
@ConditionalOnMissingBean
public ConsulAutoRegistration consulRegistration(AutoServiceRegistrationProperties autoServiceRegistrationProperties,
ConsulDiscoveryProperties properties, ApplicationContext applicationContext,
ObjectProvider<List<ConsulRegistrationCustomizer>> registrationCustomizers, HeartbeatProperties heartbeatProperties) {
return ConsulAutoRegistration.registration(autoServiceRegistrationProperties, properties,
applicationContext, registrationCustomizers.getIfAvailable(), heartbeatProperties);
}

@Configuration
@ConditionalOnClass(ServletContext.class)
protected static class ConsulServletConfiguration {
@Bean
public ConsulRegistrationCustomizer servletConsulCustomizer(ObjectProvider<ServletContext> servletContext) {
return new ConsulServletRegistrationCustomizer(servletContext);
}
}
}

ConsulAutoServiceRegistration 类解析

ConsulAutoServiceRegistration 继承关系图
  • 由上图可以看到此类实现了Lifecycle接口,也就是说Spring容器启动时或者关闭时会找出所有实现了LifeCycle及其子类接口的类,并一一调用其接口方法

    • 下面是Lifecycle接口, start()方法对应容器启动时执行的方法,stop()方法对应容器关闭时执行的方法

      1
      2
      3
      4
      5
      public interface Lifecycle {
      void start();
      void stop();
      boolean isRunning();
      }
    • 所以可以猜出ConsulAutoServiceRegistration就是借助了Lifecycle接口来达到启动应用时会自动注册到Consul,应用关闭时自动注销在Consul的注册信息

    • 回顾下上一章节的内容,可以看到是调用了start()方法
  • 下面来看看ConsulAutoServiceRegistration是怎么实现这两个方法的,ConsulAutoServiceRegistration对这两个方法的实现在AbstractDiscoveryLifecycle类上

start() 注册consul应用信息
  • org.springframework.cloud.client.discovery.AbstractDiscoveryLifecycle#start() 下面可以看到是调用了register()方法,这个方法是抽象方法,由子类来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override
public void start() {
if (!isEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
return;
}

// only set the port if the nonSecurePort is 0 and this.port != 0
if (this.port.get() != 0 && getConfiguredPort() == 0) {
setConfiguredPort(this.port.get());
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get() && getConfiguredPort() > 0) {
register();
if (shouldRegisterManagement()) {
registerManagement();
}
this.context.publishEvent(new InstanceRegisteredEvent<>(this,
getConfiguration()));
this.running.compareAndSet(false, true);
}
}
  • 进入此方法 org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#register(),可以看到调用了ServiceRegistry<R> serviceRegistry对象的注册方法
1
2
3
4
5
6
7
/**
* Register the local service with the {@link ServiceRegistry}
*/
@Override
protected void register() {
this.serviceRegistry.register(getRegistration());
}
  • 进入org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistry#register()方法,可以看到有两步操作
    • 添加ttlScheduler定时任务
    • agentServiceRegister注册服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void register(ConsulRegistration reg) {
log.info("Registering service with consul: " + reg.getService());
try {
client.agentServiceRegister(reg.getService(), properties.getAclToken());
if (heartbeatProperties.isEnabled() && ttlScheduler != null) {
ttlScheduler.add(reg.getInstanceId());
}
}
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);
}
}
  • 继续跳入,直到进入com.ecwid.consul.v1.agent.AgentConsulClient#agentServiceRegister(),这个方法是consul的包里面的,不是Spring的,可以发现底层就是发送了一个RESTFUL API请求
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public Response<Void> agentServiceRegister(NewService newService, String token) {
UrlParameters tokenParam = token != null ? new SingleUrlParameters("token", token) : null;

String json = GsonFactory.getGson().toJson(newService);
RawResponse rawResponse = rawClient.makePutRequest("/v1/agent/service/register", json, tokenParam);

if (rawResponse.getStatusCode() == 200) {
return new Response<Void>(null, rawResponse);
} else {
throw new OperationException(rawResponse);
}
}
  • 查看com.ecwid.consul.v1.agent.AgentConsulClient#agentServiceRegister()方法所在包

    • 可以看到是依赖的下面这个包

      1
      2
      3
      4
      5
      <dependency>
      <groupId>com.ecwid.consul</groupId>
      <artifactId>consul-api</artifactId>
      <version>1.3.0</version>
      </dependency>
    • 查看其源码可以发现这个包封装了与consul通信的各种接口,也就是说使用该包可以很方便的注册服务,注销服务

    • 里面有个ConsulClient类是个一站式的consul-api客户端,下面可以看到是继承各种Client,每种Client提供不同类型的接口实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      /**
      * Full consul-api client with all supported methods.
      * If you like to use more specific clients, please look at *Client classes (AclClient, AgentClient etc.)
      * <p>
      * Implementation notes:
      * Do not afraid of the class size :)
      * There aren't any 'smart' or specific methods - all methods in this class are just delegates and auto-generated by IntelliJ IDEA
      *
      * @author Vasily Vasilkov (vgv@ecwid.com)
      */
      public class ConsulClient implements
      AclClient,
      AgentClient,
      CatalogClient,
      CoordinateClient,
      EventClient,
      HealthClient,
      KeyValueClient,
      QueryClient,
      SessionClient,
      StatusClient {

      private final AclClient aclClient;
      private final AgentClient agentClient;
      private final CatalogClient catalogClient;
      private final CoordinateClient coordinateClient;
      private final EventClient eventClient;
      private final HealthClient healthClient;
      private final KeyValueClient keyValueClient;
      private final QueryClient queryClient;
      private final SessionClient sessionClient;
      private final StatusClient statusClient;

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

      /**
      * Consul client will connect to local consul agent on
      * 'http://localhost:8500'
      */
      public ConsulClient() {
      this(new ConsulRawClient());
      }
    • 查看方法

stop() 注销consul应用信息
  • org.springframework.cloud.client.discovery.AbstractDiscoveryLifecycle#stop()下面可以看到是调用了deregister()方法,这个方法是抽象方法,由子类来实现
1
2
3
4
5
6
7
8
9
@Override
public void stop() {
if (this.running.compareAndSet(true, false) && isEnabled()) {
deregister();
if (shouldRegisterManagement()) {
deregisterManagement();
}
}
}
  • 进入此方法 org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#deregister(),可以看到调用了ServiceRegistry<R> serviceRegistry对象的注册方法
1
2
3
4
5
6
7
/**
* De-register the local service with the {@link ServiceRegistry}
*/
@Override
protected void deregister() {
this.serviceRegistry.deregister(getRegistration());
}
  • 进入org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistry#deregister()方法,可以看到有两步操作

    • 去除ttlScheduler定时
    • agentServiceDeregister注销服务
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @Override
      public void deregister(ConsulRegistration reg) {
      if (ttlScheduler != null) {
      ttlScheduler.remove(reg.getInstanceId());
      }
      if (log.isInfoEnabled()) {
      log.info("Deregistering service with consul: " + reg.getInstanceId());
      }
      client.agentServiceDeregister(reg.getInstanceId());
      }
  • 继续跳入,直到进入com.ecwid.consul.v1.agent.AgentConsulClient#agentServiceDeregister()方法,可以发现底层就是发送了一个RESTFUL API请求

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public Response<Void> agentServiceDeregister(String serviceId, String token) {
UrlParameters tokenParam = token != null ? new SingleUrlParameters("token", token) : null;

RawResponse rawResponse = rawClient.makePutRequest("/v1/agent/service/deregister/" + serviceId, "", tokenParam);

if (rawResponse.getStatusCode() == 200) {
return new Response<Void>(null, rawResponse);
} else {
throw new OperationException(rawResponse);
}
}

总结

  • ConsulAutoServiceRegistration就是借助了Lifecycle接口来实现启动应用时会自动注册到Consul,应用关闭时自动注销在Consul的注册信息功能
  • 如果我们有需求在应用启动时或者关闭时做一些额外的事情,那么借助Lifecycle接口就可以达到我们的目的,不过需要注意的在使用Lifecycle接口方法时,如果Spring容器上下文没有显式的调用容器的startdestory(或者close,stop)等方法时是不会触发接口方法的,我们可以借助SmartLifecycle接口, 实现这个接口类会在所在的上下文在调用refresh时,希望能够自己自动进行回调

参考