写在前面
本文参照Spring官方文档,写了相关代码 (源码地址),在此做下笔记;
官网关于 Nacos
的介绍是这样的:“ Nacos
是一个易于使用的动态服务发现、配置和服务管理平台,用于构建云本地应用程序”。一句话描述了 Nacos
能够实现的功能:服务发现、配置、服务治理。
那么,我们先从基础的服务发现开始吧!
入门
因为后续我们将陆续使用到 Spring-Cloud-Alibaba
中的其它组件,所以,我们在顶层模块中引入如下的 POM
管理:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
当然,不要忘了这个项目需要是 SpringBoot
项目,所以,我们之间让这个顶层模块归于 Spring-Boot
下,添加如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
</parent>
以上是整个大项目的 POM 文件描述,以后各子模块均在该项目下创建。现在,让我们按学习的进度来建立各个子项目(子模块)。
启动 Nacos 服务
首先,我们需要 下载 Nacos 服务,进入 /bin
目录下,通过 startup.cmd
启动它。启动成功之后,访问 http://<ip>:8848/nacos
可查看控制台:
官网在这里描述为
http://ip:8848
,但你访问这个地址会 404。我看了nacos-server.jar
包中的配置文件,发现地址应为如上的形式。
启动 Provider 应用
创建一个 provider_app
子模块,引入如下依赖:
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
并添加 application.yml
配置文件:
application.yml
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: provider-app
server:
port: 18083
新增启动类:
package com.duofei;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* 启动类
* @author duofei
* @date 2020/7/22
*/
@SpringBootApplication
@EnableDiscoveryClient
public class NacosProviderDemoApplication {
public static void main(String[] args) {
SpringApplication.run(NacosProviderDemoApplication.class, args);
}
@RestController
public class EchoController{
@GetMapping("/echo/{string}")
public String echo(@PathVariable String string){
return "Hello Nacos Discovery " + string;
}
}
}
启动应用程序,观察 Nacos
的控制台:
可以看出,我们刚运行的服务成功注册。如果仔细留意应用程序的启动日志,也能看见打印的注册完成的信息。
启动 Consumer 应用
调用者服务稍微复杂一点,因为它需要调用提供者的 RESTful
服务。这里使用最原始的 LoadBalanceClient
和 RestTemplate
来访问提供者提供的服务。
新建 consumer-app
项目,依赖不变,配置文件稍作修改:
application.yml
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: consumer-app
server:
port: 18084
启动类如下:
package com.duofei;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.sql.SQLOutput;
/**
* 启动类
* @author duofei
* @date 2020/7/22
*/
@SpringBootApplication
@EnableDiscoveryClient
public class NacosConsumerApp {
public static void main(String[] args) {
SpringApplication.run(NacosConsumerApp.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
@RestController
public class NacoController{
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
@Value("${spring.application.name}")
private String appName;
@GetMapping("/echo/app-name")
public String echoAppName(){
ServiceInstance serviceInstance = loadBalancerClient.choose("provider-app");
String path = String.format("http://%s:%s/echo/%s", serviceInstance.getHost(), serviceInstance.getPort(), appName);
System.out.println("request path: " + path);
return restTemplate.getForObject(path, String.class);
}
}
}
访问 http://192.168.3.18:18084/echo/app-name
,但在获取服务实例时,发现获取到的服务实例地址并不是 192.168.3.18
, 所以无法访问到注册的服务。这是因为服务提供者的配置文件并未配置 ip 地址,我们所拿到的 ip 地址是在启动过程中通过 Java 原生的 NetworkInterface
获取的。具体的底层执行代码则是在 NacosDiscoveryProperties
类的 init
方法。
所以,我们修改服务提供者类的配置文件,新增 ip
配置:
spring:
cloud:
nacos:
discovery:
ip: 192.168.3.18
然后重新访问 http://192.168.3.18:18084/echo/app-name
,就可以得到 Hello Nacos Discovery consumer-app
的响应了。
Nacos
的服务注册与发现流程很简单,但唯一让我有点困惑的便是这 ip
获取了。原因可能是因为我本地有建立过虚拟机,然后有比较多的虚拟网络接口,而 nacos
的筛选方式也比较简单粗暴,直接取了第一个符合条件的网络接口。
不过,在后续发现,可以在配置文件中指定网络接口的名称:
spring:
cloud:
nacos:
discovery:
network-interface: eth9
上面这个网口名称对应了我的 192.168.3.18
的 ip 地址,这样也可以正常注册 ip 地址。
服务发现端点
Nacos Discovery
在内部提供了一个具有 nacos-discovery
端点 id 的端点。
端点暴露的 json
内容为:
- subscribe: 展示了当前的服务订阅信息;
NacosDiscoveryProperties
: 展示了当前服务的Nacos
配置;
想要访问 /nacos-discovery
这个端点路径,需要引入 spring-boot-starter-actuator
组件,并在配置文件中新增如下内容:
management:
endpoints:
web:
exposure:
include: nacos-discovery
如上的修改,我们将它应用到了前面的服务提供者和服务调用者子模块中,然后,访问 http://192.168.3.18:18083/actuator/nacos-discovery
路径或者是将端口改为服务调用者的端口路径:
/actuator 这个基础路径,可以通过
management.endpoints.web.base-path
修改。
可以看到暴露的内容正是 NacosDiscoveryProperties
和 subscribe
。
但为什么 subscribe
的内容为空呢?这是因为,订阅信息需要主动去订阅。在我们的 consumer-app
的启动类中增加如下代码:
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
// 实现 InitializingBean 接口
@Override
public void afterPropertiesSet() throws Exception {
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
namingService.subscribe("provider-app", event -> {
System.out.println(event);
});
}
需要注意的是,注册好该监听器,会在启动后直接触发一次。现在,让我们重新访问端点,获取到如下信息:
我们可以在 nacos
的控制台,修改订阅的服务信息,也会触发该事件。但查看该事件的实现类只有一个,所以很难确定具体的事件行为是什么。
服务发现的配置
服务发现的配置可以参考 NacosDiscoveryProperties
类。大多配置都见名知意,可一切还是要以代码的执行结果为准,多思考为什么,切记不要先入为主。
因为我自己在这里就犯了一个先入为主的错误。我打算配置一个 namespace
,第一步是在 nacos
控制台上新建了一个命名空间,命名空间名为 demo
,意为测试代码运行时使用的命名空间。紧接着,第二步,我在 consumer-app
配置文件中新增了如下配置:
spring:
cloud:
nacos:
discovery:
namespace: demo
可结果并不如我所愿,consumer-app
始终无法注册到"demo"的命名空间。然后,我开始跟踪源码,结果发现在底层,namespace
最终对应的是 namespaceId
。所以,这里其实是需要一个命名空间 id 的。而该 id 值在你创建好命名空间后就可以得到。这个地方的值修改为命名空间 id 后,一切就正常了。
consumer-app
项目修改完命名空间后,记得将 provider-app
也修改为对应的命名空间,否则 consumer-app
无法访问到 provider-app
。
总结
nacos
的服务发现与注册真的比较简单。我这里整个配置项也就使用了三个,这可能是因为还没有集成 Dubbo
或者其它的 RPC
框架。
这里还有一点没有提到,那就是负载均衡。不过,你可以发现 nacos-discovery
使用了 Ribbon
。那么,nacos-discovery
自然会使用 ribbon
来实现负载均衡策略。想了解更多的可以去查看 ribbon
文档,还有其它疑问的也可以看下 nacos-discovery
下 ribbon
包中的源码。
文档中的内容也需要持怀疑的态度,代码才是检验“真理”的标准!
并不是指文档中的内容有错,而是说文档中的内容简洁,容易让我们刚入门的小白造成理解上的误差!
我与风来
认认真真学习,做思想的产出者,而不是文字的搬运工。
但行善事,莫问前程。