服务注册与服务发现

1,515 阅读5分钟

微服务架构演进

在单体应用时代,所有的功能(代码)耦合在一起,并且部署到同一个进程中。

随着业务的增加,单体应用面临的问题也会越来越多,例如:

  • 当部分模块出现问题的时候,会影响整个应用。
  • 应用功能越多,部署成本越高。
  • 技术栈受限,要求所有的开发者必须使用相同的开发语言。

这种情况就像搭积木一样,积木搭的越高,整体结构就会变得越不稳定,越上面加的新积木(添加功能)或拿掉一些积木(删除功能)都非常危险。随时可能导致整个积木结构崩溃。

微服务架构,即把一个单体应用拆分成为各个子应用,各个子应用在单独的进程中。 下图是Martin Fowler(ThoughtWorks首席科学家,当今世界软件开发领域最具影响力的大师之一)在其博客上介绍的单体应用与微服务应用之间的区别:单体应用会把所有的功能放到一个进程里,扩容时相当于复制单体应用到多个服务器上。微服务架构会把每个功能单独放到一个进程里,扩容时根据需要对不同的微服务进行不同的操作。

sketch.png 微服务架构的优点:

  • 每个微服务可以独立开发、独立运行、独立部署,可以使用任意一种开发语言。
  • 每个微服务之间是独立的,如果某个服务宕机,只会影响当前服务,不会对整个业务系统产生影响。
  • 不同团队维护不同的微服务,职责单一。
  • 可以针对不同的微服务做不同的扩/缩容策略,不会造成资源浪费。

Spring Cloud统一服务注册/发现编程模型

Spring Cloud统一了服务注册和服务发现编程模型,其代码在spring-cloud-commons模块里

统一编程模型优点

  • 无须关注底层服务注册/发现的实现细节,只需了解上层统一的抽象。
  • 更换注册中心非常简单,只需要修改maven依赖和对应的注册中心配置信息。

接口

作用

org.springframework.cloud.client.discovery.DiscoveryClient代表服务发现常见的读取操作
org.springframework.cloud.client.discovery.EnableDiscoveryClient使用该注解表示开启服务发现功能
org.springframework.cloud.client.discovery.ReactiveDiscoveryClient基于响应式的代表服务发现的常用读取操作
org.springframework.cloud.client.serviceregistry.ServiceRegistry注册与注销服务的封装操作
org.springframework.cloud.client.ServiceInstance代表服务的一个实例

DiscoveryClient和ReactiveDiscoveryClient

DiscoveryClientReactiveDiscoveryClient代表Consumer从注册中心发现Provider的服务发现操作。

public interface DiscoveryClient extends Ordered {

   /**
    * 默认的优先级,多个DiscoveryClient存在的情况下以优先级排序
    */
   int DEFAULT_ORDER = 0;

   /**
    * 具体服务发现组件的描述信息,在HealthIndicator中会被用到
    * @return 描述信息
    */
   String description();

   /**
    * 根据服务名查询所有的服务实例
    * @param serviceId 服务名
    * @return 服务实例集合
    */
   List<ServiceInstance> getInstances(String serviceId);

   /**
    * @return 返回注册中心所有的服务名
    */
   List<String> getServices();

   /**
    * 具体的服务发现组件的优先级,默认为0
    * @return 优先级
    */
   @Override
   default int getOrder() {
      return DEFAULT_ORDER;
   }

}
  • Alibaba Nacos实现:NacosDiscoveryClient
  • Netflix Eureka实现:EurekaDiscoveryClient
public interface ReactiveDiscoveryClient extends Ordered {

   int DEFAULT_ORDER = 0;

   String description();

   Flux<ServiceInstance> getInstances(String serviceId);

   Flux<String> getServices();

   @Override
   default int getOrder() {
      return DEFAULT_ORDER;
   }

}

ReactiveDiscoveryClient接口中的方法与DiscoveryClient几乎相同,只是把List类型转换成了Reactor里的Flux类型。

ServiceInstance和Registration

ServiceInstance表示客户端从注册中心获取的实例数据结构; Registration表示客户端注册到注册中心的实例数据结构。

ServiceInstance接口定义:

public interface ServiceInstance {

   /**
    * @return 实例ID,可以不实现,默认返回null
    */
   default String getInstanceId() {
      return null;
   }

   /**
    * @return 注册的服务ID
    */
   String getServiceId();

   /**
    * @return 服务实例的hostname
    */
   String getHost();

   /**
    * @return 服务实例的端口
    */
   int getPort();

   /**
    * @return 是否使用HTTPS
    */
   boolean isSecure();

   /**
    * @return 服务实例的URI地址
    */
   URI getUri();

   /**
    * @return 服务实例的key/value(键值对)形式对metadata信息
    */
   Map<String, String> getMetadata();

   /**
    * @return The scheme of the service instance.
    */
   default String getScheme() {
      return null;
   }

}

使用DiscoveryClient可以基于服务名获取到这个服务下所有的ServiceInstance集合。

Registration接口定义:

public interface Registration extends ServiceInstance {

}

这个接口继承ServiceInstance,并且没有额外的方法定义。

ServiceRegistry

ServiceRegistry用于服务信息的注册和注销,接口定义:

public interface ServiceRegistry<R extends Registration> {

   /**
    * 基于实例信息将其注册到注册中心
    * @param registration 实例信息
    */
   void register(R registration);

   /**
    * 基于实例信息将其从注册中心注销
    * @param registration 实例信息
    */
   void deregister(R registration);

   /**
    * 关闭ServiceRegistry,这是一个生命周期方法
    */
   void close();

   /**
    * 设置服务实例的状态
    * @param registration 服务实例
    * @param status 状态
    * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
    */
   void setStatus(R registration, String status);

   /**
    * 获取服务实例的状态
    * @param registration 服务实例
    * @param <T> The type of the status.
    * @return 服务实例当前状态
    * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
    */
   <T> T getStatus(R registration);

}

服务注册和注销过程:

image.png

AutoServiceRegistration是一个空接口,表示自动完成服务注册过程。接口具体的实现类(如NacosAutoServiceRegistration)会在NacosServiceRegistryAutoConfiguration自动化配置类中自动构造。

AbstractAutoServiceRegistration抽象类实现了AutoServiceRegistration接口,同时也实现了ApplicationListener接口并监听WebServerInitializedEvent。收到事件后,使用ServiceRegistry完成服务注册。应用程序关闭时触发@PreDestroy注解使用ServiceRegistry完成服务注销,这就是Spring Cloud服务信息的注册和注销时机。

Spring Cloud 2.2.0.RELEASE之后的版本通过WebServerInitializedEvent事件的监听完成服务注册已被声明为过期方法。Spring Cloud把注册时机的决定权交给了各个注册中心实现。

ServiceRegistryEndpoint

ServiceRegistryEndpoint是Spring Cloud服务注册/发现功能对外暴漏到Endpoint,其ID是service-registry,用于获取和设置服务实例的状态。获取和设置的动作由ServiceRegistrygetStatussetStatus方法完成。

@Endpoint(id = "service-registry")
public class ServiceRegistryEndpoint {

   private final ServiceRegistry serviceRegistry;

   private Registration registration;

   public ServiceRegistryEndpoint(ServiceRegistry<?> serviceRegistry) {
      this.serviceRegistry = serviceRegistry;
   }

   public void setRegistration(Registration registration) {
      this.registration = registration;
   }

   @WriteOperation
   public ResponseEntity<?> setStatus(String status) {
      Assert.notNull(status, "status may not by null");

      if (this.registration == null) {
         return ResponseEntity.status(HttpStatus.NOT_FOUND)
               .body("no registration found");
      }

      this.serviceRegistry.setStatus(this.registration, status);
      return ResponseEntity.ok().build();
   }

   @ReadOperation
   public ResponseEntity getStatus() {
      if (this.registration == null) {
         return ResponseEntity.status(HttpStatus.NOT_FOUND)
               .body("no registration found");
      }

      return ResponseEntity.ok()
            .body(this.serviceRegistry.getStatus(this.registration));
   }

}

ServiceRegistryEndpoint可以完成应用的无损下线。