Skip to content

服务注册与发现 ​

tio-boot-cloud 为 tio-boot 应用提供微服务能力,分为三个模块:

  • tio-boot-cloud-commons:服务实例、发现、注册、负载均衡、远程配置与按服务名调用的公共接口。
  • tio-boot-cloud-nacos:以 Nacos 实现注册、发现和配置中心。
  • tio-boot-cloud-gateway:API 网关。

本文介绍注册与发现。应用启动后自动注册到 Nacos,关闭时自动下线;其他服务通过服务名拿到它的实例列表,实例变化由 Nacos 实时推送。

依赖 ​

xml
<dependency>
  <groupId>nexus.io</groupId>
  <artifactId>tio-boot-cloud-nacos</artifactId>
  <version>${tio-boot-cloud.version}</version>
</dependency>
<dependency>
  <groupId>nexus.io</groupId>
  <artifactId>jfinal-aop</artifactId>
  <version>${jfinal-aop.version}</version>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>${logback.version}</version>
</dependency>

tio-boot-cloud-nacos 会带上 tio-boot、tio-boot-cloud-commons、Nacos 客户端和 OkHttp。

配置 ​

app.properties:

properties
app.name=demo-provider
server.port=18081
# SIGTERM / Ctrl+C 时执行关闭流程,先从 Nacos 下线再停止服务
server.shutdown.hook=true

nacos.server-addr=127.0.0.1:8848
nacos.namespace=hula-dev
nacos.username=nacos
nacos.password=nacos
# 注册的 IP,不配置时自动选择本机的内网 IPv4 地址
nacos.discovery.ip=127.0.0.1
# 实例元数据,nacos.discovery.metadata.<key>=<value>
nacos.discovery.metadata.version=v1

全部配置项:

  • nacos.server-addr:Nacos 地址,默认 127.0.0.1:8848,集群用逗号分隔。
  • nacos.namespace:命名空间 ID,不配置时使用 public。
  • nacos.group:分组,默认 DEFAULT_GROUP;nacos.discovery.group、nacos.config.group 可以分别覆盖。
  • nacos.username / nacos.password:Nacos 开启鉴权时的账号。
  • nacos.discovery.enabled:是否启用服务发现,默认 true。
  • nacos.discovery.register-enabled:是否把自己注册上去,默认 true。只调用别人、不对外提供接口的应用可以关闭。
  • nacos.discovery.service:注册的服务名,默认取 app.name。
  • nacos.discovery.extra-services:额外注册的服务名,逗号分隔,见下文“一个进程注册多个服务名”。
  • nacos.discovery.ip / nacos.discovery.port:注册的地址,端口默认取实际监听的 server.port。
  • nacos.discovery.weight:权重,默认 1。
  • nacos.discovery.cluster-name:集群名,默认 DEFAULT。
  • nacos.discovery.ephemeral:是否临时实例,默认 true,进程退出或断网后由 Nacos 自动摘除。
  • nacos.discovery.metadata.<key>:实例元数据。

所有配置都通过 EnvUtils 读取,可以写在 app.properties,也可以用命令行参数 --nacos.namespace=prod 或环境变量 NACOS_NAMESPACE 覆盖。

启动 ​

在 BootConfiguration 中调用 TioBootNacos.start():

java
package nexus.io.tio.boot.cloud.demo.provider;

import nexus.io.tio.boot.TioApplication;
import nexus.io.tio.boot.cloud.demo.provider.config.ProviderConfig;

public class ProviderApp {
  public static void main(String[] args) {
    TioApplication.run(ProviderApp.class, new ProviderConfig(), args);
  }
}
java
package nexus.io.tio.boot.cloud.demo.provider.config;

import nexus.io.context.BootConfiguration;
import nexus.io.tio.boot.cloud.demo.provider.handler.HelloHandler;
import nexus.io.tio.boot.cloud.nacos.TioBootNacos;
import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.tio.http.server.router.HttpRequestRouter;

public class ProviderConfig implements BootConfiguration {

  @Override
  public void config() throws Exception {
    TioBootNacos.start();

    HttpRequestRouter router = TioBootServer.me().getRequestRouter();
    router.get("/demo/hello", new HelloHandler()::handle);
  }
}

TioBootNacos.start() 做三件事:

  1. 让 CloudContext 通过 Nacos 发现服务。
  2. 按 cloud.loadbalancer.* 配置负载均衡(见服务调用与负载均衡)。
  3. 在服务开始监听端口之后注册实例,在服务停止之前注销实例。注册使用的是 tio-boot 的服务监听器,应用自己的 ServerListener 不受影响。

配置中心不需要额外调用:引入模块并配置 nacos.config.data-ids 后,配置在启动时自动加载,见配置中心。

业务代码 ​

Handler、Service、Model 分包,Handler 通过 Aop 获取 Service:

java
package nexus.io.tio.boot.cloud.demo.provider.handler;

import nexus.io.jfinal.aop.Aop;
import nexus.io.model.body.RespBodyVo;
import nexus.io.tio.boot.cloud.demo.provider.model.Greeting;
import nexus.io.tio.boot.cloud.demo.provider.service.GreetingService;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;

/** GET /demo/hello?name=tio */
public class HelloHandler {
  public HttpResponse handle(HttpRequest request) {
    String name = request.getParam("name", "tio-boot");
    GreetingService greetingService = Aop.get(GreetingService.class);
    Greeting greeting = greetingService.greet(name);
    return TioRequestContext.getResponse().respond(RespBodyVo.ok(greeting));
  }
}
java
package nexus.io.tio.boot.cloud.demo.provider.service;

import nexus.io.tio.boot.cloud.CloudContext;
import nexus.io.tio.boot.cloud.demo.provider.model.Greeting;
import nexus.io.tio.boot.cloud.discovery.ServiceInstance;
import nexus.io.tio.utils.environment.EnvUtils;

public class GreetingService {

  public Greeting greet(String name) {
    // demo.greeting 来自 Nacos 配置,修改后无需重启
    String greeting = EnvUtils.get("demo.greeting", "Hello");
    return new Greeting(greeting + ", " + name, instanceName());
  }

  public String instanceName() {
    ServiceInstance local = CloudContext.getLocalInstance();
    if (local == null) {
      return "port " + EnvUtils.get("server.port");
    }
    return local.getHost() + ":" + local.getPort();
  }
}
java
package nexus.io.tio.boot.cloud.demo.provider.model;

public class Greeting {
  private String message;
  private String instance;

  public Greeting() {
  }

  public Greeting(String message, String instance) {
    this.message = message;
    this.instance = instance;
  }

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }

  public String getInstance() {
    return instance;
  }

  public void setInstance(String instance) {
    this.instance = instance;
  }
}

CloudContext.getLocalInstance() 返回本应用注册的实例,便于在响应中标出是哪个实例处理的请求。

运行 ​

同一份程序启动两个实例,第二个实例换端口并带上不同的元数据:

shell
java -jar demo-provider.jar
java -jar demo-provider.jar --server.port=18083 --nacos.discovery.metadata.version=v2

日志中可以看到注册结果:

registered demo-provider to nacos 127.0.0.1:8848 as 127.0.0.1:18081
registered demo-provider to nacos 127.0.0.1:8848 as 127.0.0.1:18083

Nacos 控制台的服务列表中 demo-provider 有 2 个健康实例,元数据分别是 version=v1 和 version=v2(截图见 Nacos 安装)。

优雅下线 ​

开启 server.shutdown.hook=true 后,进程收到 SIGTERM(kill、docker stop、Kubernetes 停止 Pod)或 Ctrl+C 时会执行 tio-boot 的关闭流程,先从 Nacos 注销实例,其他服务和网关随即收到实例变化,不再把请求发过来:

deregistered [email protected]:18083 from nacos

进程被 kill -9 或宕机时,临时实例由 Nacos 根据心跳自动摘除;在摘除之前,按服务名调用和网关遇到连接失败会自动换一个实例重试。

查询服务实例 ​

任何地方都可以通过 CloudContext 查询实例,列表来自 Nacos 推送后的本地缓存,不会每次访问 Nacos:

java
package nexus.io.tio.boot.cloud.demo.consumer.service;

import java.util.List;

import nexus.io.tio.boot.cloud.CloudContext;
import nexus.io.tio.boot.cloud.discovery.ServiceInstance;

public class InstanceQueryService {

  public List<ServiceInstance> providers() {
    return CloudContext.getInstances("demo-provider");
  }

  public List<String> services() {
    return CloudContext.getDiscoveryClient().getServices();
  }
}

ServiceInstance 提供 getHost()、getPort()、getUri()(http://host:port)、getWebSocketUri()、getWeight() 和 getMetadata(key)。

一个进程注册多个服务名 ​

一个应用包含多个业务模块时,可以一次注册多个服务名。调用方和网关始终按服务名访问,之后把模块拆成独立服务,只需要分别启动,调用方不用修改:

properties
nacos.discovery.service=user-service
nacos.discovery.extra-services=order-service,pay-service

不使用注册中心 ​

本地开发或测试时可以不启动 Nacos:设置 nacos.discovery.enabled=false,并在配置中直接写服务地址,按服务名调用和网关同样可用:

properties
nacos.discovery.enabled=false
cloud.discovery.simple.demo-provider=http://127.0.0.1:18081,http://127.0.0.1:18083

MIT Licensed