服务注册与发现
tio-boot-cloud 为 tio-boot 应用提供微服务能力,分为三个模块:
tio-boot-cloud-commons:服务实例、发现、注册、负载均衡、远程配置与按服务名调用的公共接口。tio-boot-cloud-nacos:以 Nacos 实现注册、发现和配置中心。tio-boot-cloud-gateway:API 网关。
本文介绍注册与发现。应用启动后自动注册到 Nacos,关闭时自动下线;其他服务通过服务名拿到它的实例列表,实例变化由 Nacos 实时推送。
依赖
<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:
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():
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);
}
}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() 做三件事:
- 让
CloudContext通过 Nacos 发现服务。 - 按
cloud.loadbalancer.*配置负载均衡(见服务调用与负载均衡)。 - 在服务开始监听端口之后注册实例,在服务停止之前注销实例。注册使用的是 tio-boot 的服务监听器,应用自己的
ServerListener不受影响。
配置中心不需要额外调用:引入模块并配置 nacos.config.data-ids 后,配置在启动时自动加载,见配置中心。
业务代码
Handler、Service、Model 分包,Handler 通过 Aop 获取 Service:
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));
}
}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();
}
}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() 返回本应用注册的实例,便于在响应中标出是哪个实例处理的请求。
运行
同一份程序启动两个实例,第二个实例换端口并带上不同的元数据:
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:18083Nacos 控制台的服务列表中 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:
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)。
一个进程注册多个服务名
一个应用包含多个业务模块时,可以一次注册多个服务名。调用方和网关始终按服务名访问,之后把模块拆成独立服务,只需要分别启动,调用方不用修改:
nacos.discovery.service=user-service
nacos.discovery.extra-services=order-service,pay-service不使用注册中心
本地开发或测试时可以不启动 Nacos:设置 nacos.discovery.enabled=false,并在配置中直接写服务地址,按服务名调用和网关同样可用:
nacos.discovery.enabled=false
cloud.discovery.simple.demo-provider=http://127.0.0.1:18081,http://127.0.0.1:18083