Nacos到底怎么用?
1:Java-SDK引用配置
我这里的Nacos是跑在K8S上的集群,这个大家可以根据nacos官方提供的方法快速起一套,这个就不多讲了,这里还是主要讲一下环境
Java:1.8
Nacos:2.1.2
Kubernetes:1.24.3
1.1:创建项目
我们通过IDEA创建出来一个maven项目,然后创建src/main/java/com.layzer目录,然后创建一个Test的类,内容如下:
package com.layzer;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import java.util.Properties;
public class Test {
public static void main(String[] args) {
try {
String serverAddr = "10.0.0.14:30000"; // 声明nacos地址变量
String dataId = "test"; // 声明配置名称变量
String group = "DEFAULT_GROUP"; // 声明配置组变量
Properties properties = new Properties(); // 创建配置文件
properties.put("serverAddr", serverAddr); // 设置服务地址
ConfigService configService = NacosFactory.createConfigService(properties); // 创建配置服务
String content = configService.getConfig(dataId, group, 5000); // 获取配置
System.out.println(content); // 打印配置
} catch (Exception e) {
e.printStackTrace(); // 打印异常
}
}
}
<!--pom.xml 引入我们需要依赖-->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.6.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>2.1.2</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
在nacos创建对应的配置
发布配置之后运行项目测试是否可以获取到配置信息
可以看到配置文件的内容已经被拿到了,当然我们这个方法是使用Java-SDK的方法来获取的配置,当然,它还支持其他的更多的方法,比如:
getConfig
publishConfig
removeConfig
getServerStatus
......
那么这个时候有一个问题我们就好奇了,Nacos的配置我们是可以随意变更的,那么程序是怎么知道我变更了配置的呢?我们下面来看看
1.2:配置见更监听发布
package com.layzer;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import java.util.Properties;
import java.util.concurrent.Executor;
public class Test {
public static void main(String[] args) {
try {
String serverAddr = "10.0.0.14:30000"; // nacos server地址
String dataId = "test"; // 配置文件名
String group = "DEFAULT_GROUP"; // 默认分组
Properties properties = new Properties(); // 创建配置文件
properties.put("serverAddr", serverAddr); // 设置服务地址
ConfigService configService = NacosFactory.createConfigService(properties); // 创建配置服务
String content = configService.getConfig(dataId, group, 5000); // 获取配置
// System.out.println(content); // 打印配置
configService.addListener(dataId, group, new Listener() { // 添加监听器
@Override
public Executor getExecutor() { // 获取执行器
return null;
}
@Override
public void receiveConfigInfo(String configInfo) { // 监听到配置变化
System.out.println("receiveConfigInfo"); // 打印配置变化
System.out.println(configInfo); // 打印配置
}
});
System.in.read(); // 阻塞主线程
} catch (Exception e) {
e.printStackTrace(); // 打印异常
}
}
}
这个时候去跑程序的时候我们的程序就会被阻塞,然后我们去Nacos变更配置,随便改,
被阻塞的程序这个时候就有响应了,响应的结果就是我们变更后的配置,所以也就是说我们可以通过添加监听器来监听我们的配置,并且我们可以拿到修改后的最新数据。当然其他的API大家可以去官网学习,因为我是搞运维的不是专业的开发,所以这个大家也请见谅QAQ~~~
2:Spring引用配置
首先第一步就是我们需要去引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.6.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--导入这个依赖项我们就不用再导入nacos-client了-->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-spring-context</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
// 创建MyConfig类,UserService类
// Test类内容如下
package com.layzer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
userService.test();
}
}
// MyConfig类内容如下
package com.layzer;
import com.alibaba.nacos.api.annotation.NacosProperties;
import com.alibaba.nacos.spring.context.annotation.config.EnableNacosConfig;
import com.alibaba.nacos.spring.context.annotation.config.NacosPropertySource;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("com.layzer")
@EnableNacosConfig(globalProperties = @NacosProperties(serverAddr = "10.0.0.14:30000")) // 开启Nacos配置中心
@NacosPropertySource(dataId = "test", autoRefreshed = true) // 指定要读的配置文件 autoRefreshed = true 代表自动刷新
public class MyConfig {
}
// UserService类内容如下
package com.layzer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class UserService {
@Value("${password}")
private String password;
public void test() {
System.out.println(password);
}
}
懂的人基本上都能懂是什么意思了,这个其实就通过容器的方式然后调用了UserService下的类下的test()方法,然后test去拿变量password,然后这个变量在MyConfig通过方法从Nacos拿过来然后打印的结果就是nacos配置文件内的password的数据了,我们执行来看看
3:SpringBoot拉取配置与自动刷新
首先还是那句话,先引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.6.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>0.2.12</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
删除上面创建的MyConfig,UserService,新建一个UserController类,然后写入如下内容
package com.layzer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserContriller {
@Value("${password}")
private String password;
@GetMapping("/test")
public String test() {
return password;
}
}
然后在resources内创建application.properties文件写入如下内容
nacos.config.server-addr=10.0.0.14:30000
当然这个时候其实还是不行的,还是那句话,我们只是连接了Nacos而并没有指定去哪儿取值,这个时候我们其实取值就有两种方式了,第一种就是去用spring的那个方式,如下
package com.layzer;
import com.alibaba.nacos.spring.context.annotation.config.NacosPropertySource;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@NacosPropertySource(dataId = "test", autoRefreshed = true)
public class Test {
public static void main(String[] args) {
SpringApplication.run(Test.class, args);
}
}
这个时候可以启动spring boot项目我们去看看。
我们发现读取到了配置了,然后我们现在需要思考一个问题,如果我修改了配置它会自动发现我修改配置了么?我们来修改一下看看
我们发现它貌似并没有发现我的新配置,那么这个时候我们需要考虑如何不重启应用读取到新的配置呢?也就是热加载功能。我们看看如何实现
// 我们通过修改UserController去开启这个功能
package com.layzer;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserContriller {
@NacosValue(value = "${password}", autoRefreshed = true) // 开启自动刷新
private String password;
@GetMapping("/test")
public String test() {
return password;
}
}
我们再来测试一下
那么这就是Springboot的一种方法,当然我们还有另一种方法,我们直接去application.properties配置也是可以的
nacos.config.server-addr=10.0.0.14:30000
nacos.config.data-id=test
nacos.config.auto-refresh=true
# 注意这个时候要打开bootstrap渲染
nacos.config.bootstrap.enable=true
// 这样我们就不需要在应用内写了。
package com.layzer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Test {
public static void main(String[] args) {
SpringApplication.run(Test.class, args);
}
}
我们来测试一下
这就是第二种SpringBoot自动刷新配置的方法了,这里重点就是开启bootstrap渲染一定要开启。
4:SpringCloud拉取配置与自动刷新
还是老话引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.3.12.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--这里有个坑就是springboot的版本必须用指定版本,这个其实在nacos的wiki有讲到-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
因为spring-cloud默认会去读取bootstrap.properties的配置所以我们去新建一个resources/bootstrap.properties,然后我们老的那个就可以删掉了,然后写入如下内容
spring.cloud.nacos.server-addr=10.0.0.14:30000
spring.application.name=test
但是这个时候我们其实还是拿不到的,我们还需要去修改UserController代码
package com.layzer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserContriller {
@Value(value = "${password}") // 还要用回@value
private String password;
@GetMapping("/test")
public String test() {
return password;
}
}
这个时候就拿到了我们的配置了,那么关键的问题来了,我们能拿到,但是它能刷新么?我们来试试
emmmm,这是怎么回事呢?这个解决方法我们可以在UserController内加一个注解
package com.layzer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope // 添加此注解
public class UserContriller {
@Value(value = "${password}")
private String password;
@GetMapping("/test")
public String test() {
return password;
}
}
下面再来测试一下
这就是SpringCloud拉取配置和自动更新配置的方法了,当然了,我们可以用另一种方式来做这个事情,也就是我们使用一个CommonConfig来做这个配置的存储的调用其他的controller来调这个CommonConfig也可以,我们看看如何做
// CommonConfig
package com.layzer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
@Component
@RefreshScope
public class CommonConfig {
@Value(value = "${password}")
private String password;
public String getPassword() {
return password;
}
}
// UserController
package com.layzer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserContriller {
@Autowired
private CommonConfig commonConfig;
@GetMapping("/test")
public String test() {
return commonConfig.getPassword();
}
}
这样其实也是OK的哈。
4.1:按profile拉取配置
那么这种拉取配置的方法其实是针对nacos平台的配置,我们删除原来的配置然后创建一个名为 test.properties的配置
按道理来讲的话,我们前面的配置的spring.application.name=test,它应该拿不到配置的,但是,我们来看看它到底能不能拿到
奇怪的事情发生了,它居然拿到了,那是因为什么呢?这个其实就是因为nacos的一些规则了,我们指定的应用其实就可以读取同应用以.properties结尾的配置,这个其实是我们使用nacos中最常使用的方案,但是这个时候我们有个疑问,就是如果我有一个xxx和一个xxx.properties,并且里面有相同的配置,那么谁的优先级更高呢?这个我可以告诉大家xxx.properties的优先级更高,我们来试一下就知道了
这就是配置优先级,当然了如果xxx内的配置xxx.properties内没有,那么就会去引用xxx内的配置了。
其实它拉取配置分为三步
1:拉取dataid为user的配置
2:拉取dataid为user.properties的配置
3:拉取dataid为user-$(spring.profiles.active)-properties的配置
当然优先级依次为321,也就是说3的配置优先级为最高,依次才是2,1
我们再去创建一个配置
前提是bootstrap的配置内加了如下配置
spring.cloud.nacos.server-addr=10.0.0.14:30000
spring.application.name=test
# 加了此配置才可以
spring.profiles.active=dev
不过这种方式其实我们很多都是基于-Dspring.profiles.active=dev来指定而不会写在bootstrap内,那么这个时候我们来制造一个场景,比如说我dev和prod的环境引用的配置不同,那么我们是不是需要两套配置,然后我想一样的代码去引用两套不同的配置,那么我们使用-Dspring.profiles.active=xxx就非常方便了。我们来看看。我这里创建俩配置一个dev一个prod
这就是通过profile选择不同环境引入不同的配置文件了。
4.2:extension和shared拉取多个配置
这个时候我们再来思考一个问题,如果说好多个环境和好多个应用有用用到相同的配置怎么办?总不能配置多个配置吧,这个时候我们就用到了拉取多个配置了,我们看看场景,比如我dev和prod都需要一个timeout配置那么我总不能建两个,这个时候我们看看怎么解决,我先去创建配置 common.properites
我们去UserController建立一个配置项
package com.layzer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserContriller {
@Autowired
private CommonConfig commonConfig;
@Value("${timeout}")
private Integer timeout;
@GetMapping("/test")
public String test() {
return commonConfig.getPassword() + "--------" + timeout;
}
}
修改bootstrap的配置
spring.cloud.nacos.server-addr=10.0.0.14:30000
spring.application.name=test
# 指定公用配置文件
spring.cloud.nacos.config.shared-configs[0].data-id=common.properties
这个时候我们可以看到,俩环境居然引用了相同的配置并且还有不同的配置,这个就是我们讲的引用多配置的方法,不过这种就是通过指定配置文件的dataid来配置的,当然它支持指定多个的。比如
spring.cloud.nacos.config.shared-configs[0].data-id=common.properties
spring.cloud.nacos.config.shared-configs[1].data-id=xxxx.properties
spring.cloud.nacos.config.shared-configs[2].data-id=xxxx.properties
......
当然还有一个配置和这个配置非常相似,我们来看看
spring.cloud.nacos.server-addr=10.0.0.14:30000
spring.application.name=test
spring.cloud.nacos.config.shared-configs[0].data-id=common.properties
# 通常二者选其一就可以了
spring.cloud.nacos.config.extension-configs[0].data-id=common.properties
extension-configs:标识本应用特有的
shared-configs:表示多个应用共享的
其实大白话讲起来的话就是第一个配置引用配置表示只有这一个应用在用这个配置,但是别的应用用了我们也没办法控制,而第二个应用拉取配置说明这个配置是所有应用公用的,其实两者功能一模一样的。
我们再看看优先级
shared-configs[2] > shared-configs[1] > shared-configs[0]
extension-configs[2] > extension-configs[1] > extension-configs[0]
主配置 > extension-configs > shared-configs
当然了我们还要考虑到自动更新的问题,这个根据两个的配置也是分为两个自动更新的,分别如下
spring.cloud.nacos.server-addr=10.0.0.14:30000
spring.application.name=test
spring.cloud.nacos.config.shared-configs[0].data-id=common.properties
spring.cloud.nacos.config.shared-configs[0].refresh=true
# 同样的,两者用其一即可
spring.cloud.nacos.config.extension-configs[0].data-id=common.properties
spring.cloud.nacos.config.extension-configs[0].refresh=true
4.3:file-extension的作用
这个配置其实主要是针对我们配置文件的内容进行转换的,比如我们配置了一个
spring.cloud.nacos.config.shared-configs[0].file-extension=properties
那么它会把我们匹配到的common.properties内的配置或者其他的配置尝试转换成properties格式,他其实可以理解为是一种验证,它的主要作用其实就是转换我们的配置文件。
# 它也是相同的作用
spring.cloud.nacos.config.file-extension=properties
# 我们可以通过这个方式来指定从哪儿个组内获取配置
spring.cloud.nacos.config.shared-configs[0].group=DEFAULT_GROUP
4.4:历史版本回滚
这个其实没那个复杂,这个就是在Nacos点点点就可以实现了,我们来看看。
我们有时候想要删除某个配置的时候我们又不知道谁在用这个配置,那么这个时候监听查询的作用就来了,我们来看看它的作用。
它可以帮我们查到哪儿个应用在占用这个配置,从而我们可以知道这个配置是否可以删除。
5:Java-SDK服务注册
上面我们讲完了配置中心,那么我下面来讲nacos的另一个功能"注册中心",那么我们第一讲就是通过java-SDK来注册,还是那句话,导入依赖先
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.3.12.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--我们直接用spring cloud的discovery就可以了,因为它们是继承关系-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
package com.layzer;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
public class ServiceRegister {
public static void main(String[] args) throws Exception {
NamingService naming = NamingFactory.createNamingService(" http://10.0.0.14:30000"); // 创建一个namingService实例
naming.registerInstance("app", "10.0.0.100", 8080); // 注册一个实例
System.in.read(); // 阻塞主线程
}
}
这其实我们就完成了一个服务注册,但是这里需要讲一下如果你想注册一个服务的多实例,那么目前这种方式你需要再去创建一个或者对应实例数的NamingService才可以实现,我们来看看
package com.layzer;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
public class ServiceRegister {
public static void main(String[] args) throws Exception {
NamingService naming = NamingFactory.createNamingService(" http://10.0.0.14:30000"); // 创建一个namingService实例
naming.registerInstance("app", "10.0.0.100", 8080); // 注册一个实例
NamingService naming1 = NamingFactory.createNamingService(" http://10.0.0.14:30000");// 创建一个新的namingService实例
naming1.registerInstance("app", "10.0.0.101", 8080); // 注册另一个实例
System.in.read();
}
}
当然这里面还有一个参数就是你可以为这些应用分组,我们来看看,我创建6个应用三个集群
package com.layzer;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
public class ServiceRegister {
public static void main(String[] args) throws Exception {
NamingService naming1 = NamingFactory.createNamingService(" http://10.0.0.14:30000");
naming1.registerInstance("app", "10.0.0.101", 8080,"cluster1");
NamingService naming2 = NamingFactory.createNamingService(" http://10.0.0.14:30000");
naming2.registerInstance("app", "10.0.0.102", 8080,"cluster1");
NamingService naming3 = NamingFactory.createNamingService(" http://10.0.0.14:30000");
naming3.registerInstance("app", "10.0.0.103", 8080,"cluster2");
NamingService naming4 = NamingFactory.createNamingService(" http://10.0.0.14:30000");
naming4.registerInstance("app", "10.0.0.104", 8080,"cluster2");
NamingService naming5 = NamingFactory.createNamingService(" http://10.0.0.14:30000");
naming5.registerInstance("app", "10.0.0.105", 8080,"cluster3");
NamingService naming6 = NamingFactory.createNamingService(" http://10.0.0.14:30000");
naming6.registerInstance("app", "10.0.0.106", 8080,"cluster3");
System.in.read();
}
}
可以看到这里它给我分了三个集群,并且每个集群内有两个实实例,这个其实就是一个,当然这个其实我们也可以自己new一个 instance然后去自定义很多的信息。
6:Java-SDK服务发现
// DiscoveryService
package com.layzer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
public class DiscoveryService {
public static void main(String[] args) throws NacosException {
NamingService naming = NamingFactory.createNamingService(" http://10.0.0.14:30000"); // 创建一个namingService实例
System.out.println(naming.getAllInstances("app")); // 获取名称为app的所有实例
}
}
从这里可以看出我们已经发现了这个服务了,但是它有一个缺点,就是它只会去拿到全部的实例,它不管你的实例是否是健康的。
注册多个实例的时候我们可以循环列出多个instance
package com.layzer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
public class DiscoveryService {
public static void main(String[] args) throws NacosException {
NamingService naming = NamingFactory.createNamingService(" http://10.0.0.14:30000"); // 创建一个namingService实例
for (Instance instance : naming.getAllInstances("app")) {
System.out.println(instance);
}
}
}
当然我们如果只想拿健康的实例那么我们可以换一个API,也就是下面的这个代码
package com.layzer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
public class DiscoveryService {
public static void main(String[] args) throws NacosException {
NamingService naming = NamingFactory.createNamingService(" http://10.0.0.14:30000"); // 创建一个namingService实例
for (Instance instance : naming.selectInstances("app",true)) {
System.out.println(instance);
}
}
}
因为我的两个其实都是健康的,所以拿到的也都是健康实例,但是还是全部的健康的实例,那么我们再***钻一点呢?我想拿到健康实例中的一个实例,那么它也有API帮我们实现,我们来看看这个API
package com.layzer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
public class DiscoveryService {
public static void main(String[] args) throws NacosException {
NamingService naming = NamingFactory.createNamingService(" http://10.0.0.14:30000");
System.out.println(naming.selectOneHealthyInstance("app", "cluster")); // 这里调用时底层的随机算法
System.out.println(naming.selectOneHealthyInstance("app"));
System.out.println(naming.selectOneHealthyInstance("app"));
System.out.println(naming.selectOneHealthyInstance("app"));
}
}
可以看到这里是完全随机的,那么这个时候如果想控制的话,就需要去nacos配置权重,谁的权重高,谁被调用的次数就多,那么这个时候我们怎么去确定我们发现的服务是否删除或者新增了呢?这个时候我们就要看订阅服务了,这个也是一个API,我们来看一下
package com.layzer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.listener.NamingEvent;
import java.io.IOException;
public class DiscoveryService {
public static void main(String[] args) throws NacosException, IOException {
NamingService naming = NamingFactory.createNamingService(" http://10.0.0.14:30000");
System.out.println(naming.selectOneHealthyInstance("app"));
naming.subscribe("app", event -> { // 订阅服务
if (event instanceof NamingEvent) { // 服务发生变化
System.out.println(((NamingEvent) event).getInstances()); // 获取服务列表
System.out.println(((NamingEvent) event).getServiceName()); // 获取服务名称
}
});
System.in.read();
}
}
然后我们去扩容一下app服务的实例数。
这其实就是Java-SDK进行服务注册与发现的方法了。
7:Spring Cloud服务注册与服务发现
这里其实我们在上一个阶段已经引入了它的依赖了,我们就还用那个依赖,这里有一点提到的就是不管Spring / Spring boot其实都是通过java的SDK去进行服务注册和发现的,这里就不讲了,直接看Spring Cloud的,当然了,我们用Nacos肯定是配置中心和注册中心都要用的。所以我们把配置中心的依赖也搞进来
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.3.12.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
第一步我们需要去配置服务提供者,其实就是去配置一下nacos的注册中心的地址。
// Application
package com.layzer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// UserController
package com.layzer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserContriller {
@GetMapping("/")
public String test() {
return "Hello Spring Cloud";
}
}
# bootstrap
spring.cloud.nacos.server-addr=10.0.0.14:30000
spring.application.name=test
我们尝试跑一下看看什么效果
当然了还有一种用法。
spring.cloud.nacos.discovery.server-addr=10.0.0.14:30000
它其实就是为了应对如果我们有俩nacos,一个做注册中心一个做配置中心就可以用这个配置指定第二个nacos了,但是一般我们其实是不区分的。
可以看到这个时候其实就已经实现了服务的注册了,就这么简单,引入一下包然后配置一下nacos的地址和application.name就可以了。
当然我们也可以改端口和权重的信息,比如:
spring.cloud.nacos.server-addr=10.0.0.14:30000
spring.application.name=test
# 自定义端口
server.port=9090
# 权重
spring.cloud.nacos.discovery.weight=2
那么服务注册我们讲过了,那么它怎么做服务发现呢?我们下面来看看,我们启一个新包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>Nacosconsumer</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.3.12.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
// src/main/java/com.comsumer/Application
package com.comsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class Application {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// src/main/java/com.comsumer/ConsumerController
package com.layzer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping(value = "/consumer")
public String echo(){
return restTemplate.getForObject("http://provider/test", String.class); // 调用test服务的/接口
}
}
然后我们反过去去启动前面的那个test服务,切记端口不能冲突哦,不过这里启动Consumer还是会抛出一个问题
java.lang.IllegalArgumentException: Param 'serviceName' is illegal, serviceName is blank
它的意思就是我们不知知道从哪儿获取这个服务,所以我们需要去配置一下nacos
# application.properties
spring.cloud.nacos.server-addr=10.0.0.14:30000
spring.application.name=consumer
server.port=8080
这个是时候再去跑一下,我们顺便把原来的test改为provider,这个更改在bootstrap的配置内,同样的consumer也是要改调用地址的,这个在ConsumerController内,然后两个应用都启动之后我们去访问http://127.0.0.1:8080/consumer也就是consumer的接口但是这个时候会报错的,
可能会有些乱,不过我给大家整理下配置,首先是provider的配置
// Application
package com.layzer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// UserController
package com.layzer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@GetMapping("/test")
public String test() {
return "Hello Spring Cloud";
}
}
# properties
spring.cloud.nacos.server-addr=10.0.0.14:30000
spring.application.name=provider
server.port=9090
其次是Consumer的配置
// Application
package com.layzer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class Application {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// ConsumerController
package com.layzer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping(value = "/consumer")
public String echo(){
return restTemplate.getForObject("http://provider/test", String.class); // 调用provider服务的/test接口
}
}
spring.cloud.nacos.server-addr=10.0.0.14:30000
spring.application.name=consumer
server.port=8080
但是这个时候我们去访问consumer服务的/consumer接口是访问不到provider服务的,这个需要我们在consumer服务的Application加一个注解
// Application
package com.layzer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class Application {
@Bean
@LoadBalanced // 添加此注解之后再去调用一下
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
我们可以看到,调用consumer的接口居然访问到了provider的服务,这个其实就是一个服务调用的方法了,它们其实是走的nacos的dns解析的服务,那么这就是Spring Cloud服务注册和服务发现的流程了。
8:临时实例与持久实例
默认情况下注册到nacos的实例都是临时实例,临时实例表示会通过客户端与服务端之间的心跳来保活,默认情况下,客户端会每隔5s发送一次心跳,如果服务端超过15s没有收到客户端的心跳,那么它就会把这个实例标记为不健康状态,如果超过30s没收到客户端的心跳,那么就会删除实例。
而对于持久实例,即使实例下线了,那也不会被删除,我们可以这么做,在provider改一下properties的配置
spring.cloud.nacos.server-addr=10.0.0.14:30000
spring.application.name=provider
server.port=9090
# 添加这个配置
spring.cloud.nacos.discovery.ephemeral=false
不过这里有个坑,就是如果你的实例是临时实例,你想注册成持久实例你需要删除nacos的一些源信息。
rm -rf /nacos/data/protocol/raft/naming_instance_metadata/* 也就是说我们需要干掉这个目录下的三个文件夹,然后重启nacos才可以。否则临时实例是无法改成持久实例注册的。
当我们再次启动provider的时候它的临时实例就是false了,也就证明它是持久实例了,这个时候我们去停止provider的时候它的实例是不会被nacos删除的。我们来试试。
它的健康实例数为0了但是它没有被删除,这个就是持久实例的好处了,它能帮我们保存实例的原来的信息,这个就是持久实例的好处了。
9:保护阈值
在使用过程中我们可以设置一个0-1的一个比例,表示如果服务的所有实例中,健康实例的比重低于这个比重,就会触发保护,一旦触发了保护,在服务消费端侧会把所有的实例拉取下来,不管是否健康,这样就会起到一个保护健康实例的效果,因为正常来说消费者只会拿到健康实例,但是如果健康实例总比例比较小了,那么会导致流量都积压到健康实例上,这样仅剩的几个健康实例也会被压垮,所以只要触发了保护,消费端就会拉取所有实例,这样部分消费端仍会访问到不健康的的实例从而请求失败,但是也有一部分能访问到健康实例,达到保护作用。
比如:provider实例有100个保护阈值设置为0.2,那么这个时候如果我的实例挂了80个,这个时候就触发了保护阈值机制,这个时候消费者就不会只拿到健康实例了,它会转为去拿那100个实例,虽然请求会失败,但是这个做法就是为了保护仅剩的20个实例的操作,这里我就不测试了因为这边不太方便测试。
10:Spring Cloud配置权重
前面我们在Java-SDK内配置过权重,但是我们如何在Spring Cloud内配置权重呢?这个其实我们需要在消费端去配置,我们来看看如何配置。
package com.layzer;
import com.alibaba.cloud.nacos.ribbon.NacosRule;
import com.netflix.loadbalancer.IRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class Application {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
// 我们在消费端只需要引入一下这个就可以了
@Bean
public IRule ribbonRule() {
return new NacosRule();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
11:Cluster就近访问
我们都知道在前面看到了我们在注册实例的时候是可以指定集群的,默认是default,那么如果说我两个实例指定两个集群
provider ----- cluster-beijing
provider ----- cluster-shanghai
这两个实例分别是两个集群,那么我们分集群有什么用呢?这个其实主要是针对消费者应用的,如果说我的consumer应用在beijing地区,那么最好是让它调北京的provider,其他的也都一样,那么我们需要如何去配置这个Consumer才可以呢,我们看看如何配置
spring.cloud.nacos.server-addr=10.0.0.14:30000
spring.application.name=consumer
server.port=8080
# 我们可以通过这个配置指定某个地区的某个集群,这样可以实现就近访问这个集群的效果
spring.cloud.nacos.discovery.cluster-name=cluster-beijing