Nacos 官网:https://nacos.io/zh-cn/index.html
Nacos 是阿里巴巴的产品,现在是 SpringCloud 中的一个组件。相比 Eureka 功能更加丰富,在国内受欢迎程度较高。
Nacos 致力于帮助您发现、配置和管理微服务。
Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。
Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。
Nacos 的关键特性包括:服务发现和服务健康监测 、动态配置服务 、动态 DNS 服务 、服务及其元数据管理 …
GitHub 下载地址:https://github.com/alibaba/nacos/releases
Nacos 是绿色版软件,解压即安装。下载完毕后将 ZIP 包解压到指定非中文目录即可。
在 Nacos 的 bin 目录中打开命令行终端,以单机运行方式启动 Nacos 服务:startup.cmd -m standalone
登录地址:http://localhost:8848/nacos/index.html#/login
账号密码:nacos / nacos
在 Nacos 的 bin 目录中创建一个 start-nacos.bat
文件,将以下内容复制到该文件中。
然后双击该文件就可以直接启动 Nacos 了,可以将该文件发送一个快捷方式到桌面以便操作。
cd .
startup.cmd -m standalone
@pause
创建 Maven 父工程:spring-cloud-alibaba-nacos
,删除其它文件只留一个 pom.xml 文件,导入依赖:
<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.0modelVersion>
<groupId>com.studygroupId>
<artifactId>spring-cloudartifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.9.RELEASEversion>
<relativePath/>
parent>
<properties>
<spring-cloud-alibaba.version>2.2.5.RELEASEspring-cloud-alibaba.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
dependencies>
project>
创建 Maven 子工程:order-service
src/main/java/com/study/bean/Order.java
// 订单类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
private Long id;
private Long price;
private String name;
private Integer num;
private Long userId;
private User user;
}
src/main/java/com/study/bean/User.java
// 用户类
@Data
public class User {
private Long id;
private String username;
private String address;
}
src/main/java/com/study/controller/OrderController.java
// 订单控制器
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
// 根据ID获取订单信息
@GetMapping("/{id}")
public Order getById(@PathVariable("id") Long id) {
// 根据ID获取订单信息
Order order = new Order(id, 9999L, "HUAWEI P" + id, Math.toIntExact(id), id, null);
// 根据订单中的用户ID查询用户并返回
String url = "http://user-service/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
// 存入用户信息
order.setUser(user);
System.out.println("order = " + order);
return order;
}
}
src/main/java/com/study/OrderServiceApplication.java
@SpringBootApplication
public class OrderServiceApplication {
@Bean
@LoadBalanced // 负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
src/main/resources/application.yml
server:
port: 9001
spring:
application:
name: order-service #服务名称
cloud:
nacos:
discovery:
server-addr: localhost:8848 #服务地址
创建 Maven 子工程:user-service
src/main/java/com/study/bean/User.java
// 用户类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String username;
private String address;
}
src/main/java/com/study/controller/UserController.java
// 用户控制器
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
// 根据ID获取用户信息
@GetMapping("/{id}")
public User getById(@PathVariable("id") Long id) {
User user = new User(id, "张三" + id, "杭州西湖");
System.out.println("user = " + user);
return user;
}
}
src/main/java/com/study/UserServiceApplication.java
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
src/main/resources/application.yml
server:
port: 9002
spring:
application:
name: user-service #服务名称
cloud:
nacos:
discovery:
server-addr: localhost:8848 #服务地址
服务提供者:服务的被调用方(为其它服务提供服务的服务)。
启动服务消费者与服务提供者:UserServiceApplication、OrderServiceApplication。查看 Nacos 控制台:
查询订单:http://localhost:9001/order/1
{
"id": 1,
"price": 9999,
"name": "HUAWEI P1",
"num": 1,
"userId": 1,
"user": {
"id": 1,
"username": "张三1",
"address": "杭州西湖"
}
}
一个服务可以有多个实例,例如 user-service 可以有:127.0.0.1:8081、127.0.0.1:8082、127.0.0.1:8083 …
假如这些实例分布于全国各地的不同机房,例如:127.0.0.1:8081 和 127.0.0.1:8082 在杭州机房、127.0.0.1:8083 在上海机房 …
Nacos 将同一机房内的实例划分为一个集群。也就是说 user-service 是服务,一个服务可以包含多个集群,如杭州、上海,每个集群下可以有多个实例,形成分级模型如图:
修改 user-service 的 application.yml 文件,添加集群配置:
spring:
cloud:
nacos:
discovery:
cluster-name: Hangzhou #集群名称,默认DEFAULT
重启 user-service 服务,查看 Nacos 控制台:
再添加两个 user-service 服务,一个集群为杭州,一个集群为上海:
Name:UserServiceApplication-9003-Hangzhou
VM options:-Dserver.port=9003 -Dspring.cloud.nacos.discovery.cluster-name=Hangzhou
第二个添加操作同上:
Name:UserServiceApplication-9004-Shanghai
VM options:-Dserver.port=9004 -Dspring.cloud.nacos.discovery.cluster-name=Shanghai
再看 Nacos 控制台,user-service 服务杭州集群多了一个端口为 9003 的实例,并新增了一个上海集群及其实例。
此时清空三个用户服务的控制台日志,然后查询三次订单。
再看三个用户服务控制台,发现每个服务都访问了一次。这是因为订单服务启动类中:
@SpringBootApplication
@MapperScan("com.study.mapper")
public class OrderServiceApplication {
@Bean
@LoadBalanced // 负载均衡
public RestTemplate restTemplate() { // RestTemplate模板提供便捷的方式来访问远程HTTP服务
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
@LoadBalanced
默认的实现是 ZoneAvoidanceRule
,是一种轮询方案(一种内置负载均衡规则类),以区域可用的服务器为基础进行服务器的选择。使用 Zone 对服务器进行分类,这个 Zone 可以理解为一个机房、机架等,而后再对 Zone 内的多个服务实例做轮询。
同集群(同城市)中的实例互相访问的速度比跨集群(跨城市)访问的速度相对来说要快,而默认的这个并不能实现根据同集群优先来实现负载均衡。因此 Nacos中提供了一个 NacosRule
的实现,可以优先从同集群中挑选实例。
修改 order-service 的 application.yml 文件,添加集群配置和负载均衡规则:
spring:
cloud:
nacos:
discovery:
cluster-name: Hangzhou #集群名称
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #nacos负载均衡规则
重启 order-service 服务,清空三个用户服务的控制台日志,然后再查询四次订单。
发现两个杭州用户服务分别轮询了两次,而上海用户服务则没有被轮询到。
在实际部署中会出现服务器设备性能有差异的场景,部分实例所在机器性能较好,另一些较差,正常来说肯定希望性能好的机器承担更多的用户请求。
但默认情况下 NacosRule 是同集群内随机挑选,不会考虑机器的性能问题。因此 Nacos 提供了权重配置来控制访问频率,权重值在 0~1 之间,权重越大则访问频率越高,权重为 0 则该实例永远不会被访问。
在 Nacos 控制台,找到 user-service 服务的实例列表,点击编辑即可修改权重:
此时大概查询订单十次左右,相对来说才会轮询到该实例一次。
在服务升级的时候,有一种较好的方案就是可以通过调整权重来进行平滑升级。例如先把 user-service 的实例1 权重调节为 0,让用户访问逐渐流向其它实例,升级实例1后,再把权重从 0 调到 0.1,放进少数用户来测试有无问题,如果没问题就逐渐调大权重比例,此时用户是无感知的,起到平滑升级的效果。
Nacos 中服务存储和数据存储的最外层都是一个名为命名空间的东西,用来来实现环境隔离功能。
修改 order-service 的 application.yml 文件,添加命名空间配置:
spring:
cloud:
nacos:
discovery:
namespace: dev #填入新建命名空间时,自定义的命名空间ID或自动生成的ID
重启 order-service 服务,查看 Nacos 控制台的服务列表:
此时再查询订单会报错,控制台提示:java.lang.IllegalStateException: No instances available for user-service
找不到用户服务。因为不同命名空间的服务互相不可见。然后把命名空间配置注释掉重启服务,以便后续操作。
Nacos 的服务实例分为两种类型:
临时实例:
修改 order-service 的 application.yml 文件,添加永久实例配置:
spring:
cloud:
nacos:
discovery:
ephemeral: false # 设置为非临时实例
当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。
此时需要一种统一配置管理方案,可以集中管理所有实例的配置,并且可以在配置变更时,及时通知微服务,实现配置的热更新。
user-service 服务添加配置:
Data ID 格式:[服务名称]-[环境].[后缀名]
注意:项目的核心配置,需要热更新的配置才有放到 Nacos 管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。
微服务要拉取 Nacos 中管理的配置,并且与本地的 application.yml 配置合并,才能完成项目启动。
因此 Spring 引入了一种新的配置文件:bootstrap.yml,它会在 application.yml 之前被读取。
user-service 服务添加 bootstrap.yml 文件,并将 application.yml 文件中 Nacos 相关配置提取到 bootstrap.yml 文件。
父工程导入依赖:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
bootstrap.yml:
spring:
application:
name: user-service #服务名称
profiles:
active: dev #环境名称
cloud:
nacos:
server-addr:
discovery:
server-addr: localhost:8848 #服务地址
cluster-name: Hangzhou #集群名称
config:
server-addr: localhost:8848 #服务地址
file-extension: yaml #文件后缀名
application.yml:
server:
port: 9002
#spring:
# application:
# name: user-service #服务名称
# cloud:
# nacos:
# discovery:
# server-addr: localhost:8848 #服务地址
# cluster-name: Hangzhou #集群名称
UserController 中添加内容用以读取配置:
@Value("${pattern.dateformat}")
private String dateformat;
@RequestMapping("/now")
public String now() {
return new SimpleDateFormat(dateformat).format(new Date());
}
重启所有 user-service 服务,访问 http://localhost:9002/user/now ,http://localhost:9003/user/now 均返回 2022-05-07 14:54:30 。
Nacos 的配置文件变更后,微服务无需重启即可感知。通过以下两种配置实现:
方式1:在 @Value
注入的变量所在的类上添加 @RefreshScope
注解。
1、重启服务,访问 http://localhost:9002/user/now ,输出 2022-05-07 14:56:20
2、将 Nacos 中之前配置的日期格式改为 yyyy/MM/dd HH:mm:ss
3、无需重启服务,再访问 http://localhost:9002/user/now ,输出 2022/05/07 14:57:30
方式2:使用 @ConfigurationProperties
注解。(推荐
)
src/main/java/com/study/config/PatternProperties.java
@Data
@Component
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
UserController 中添加内容用以读取配置:
@Autowired
private PatternProperties patternProperties;
@RequestMapping("/now2")
public String now2() {
return new SimpleDateFormat(patternProperties.getDateformat()).format(new Date());
}
重启服务,访问 http://localhost:9002/user/now2 ,输出 2022/05/07 14:59:28
将 Nacos 中之前配置的日期格式改为 yyyy年MM月dd日 HH:mm:ss
无需重启服务,再访问 http://localhost:9002/user/now2 ,输出 2022年05月07日 15:00:36
微服务启动时会从 Nacos 读取多个配置文件:
无论 profiles 如何变化,[spring.application.name].yaml
这个文件一定会加载,因此多个环境的公共配置写入这个文件。
多种配置优先级:服务名称-环境.yaml > 服务名称.yaml > 本地配置
。即:Nacos环境配置 > Nacos公共配置 > 项目配置
。
Nacos 默认数据存储在内嵌数据库 Derby 中,不属于生产可用的数据库。这里切换为 MySQL 数据库。
先把 Nacos 服务和 项目服务全部暂停。
创建数据库:create database nacos; use nacos;
导入表结构:nacos\conf\nacos-mysql.sql
修改配置文件:nacos\conf\application.properties
#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root
重启 Nacos,重启 UserServiceApplication 服务,发现控制台报错,找不到 @Value("${pattern.dateformat}")
,这是因为切换数据库导致之前的配置信息丢失,注释这行代码即可。