服务分级模型
对于大型项目而言,考虑到容灾、就近访问等,微服务一般会部署在多个区域机房内。nacos提供了配置集群服务的功能。在nacos服务分级存储模型中,一级为服务,如userservice,二级为集群,以区域机房划分,如北京、上海、杭州等,三级为服务的实例,例如杭州机房某台部署了userservice的服务器。如下图
我们可以明白,服务调用尽可能选择本地集群的服务,因为访问延迟更低,本地集群不可用时,再去访问其它集群。
我们看下nacos服务列表,可以看到当前userservice服务有一个集群,两个实例,进入详情可以看到,当前集群是DEFAULT,因为我们还没有配置集群。
接下来,我们来配置下集群试试。为了测试方便,我们再添加一个UserApplication3,端口8083。
集群怎么配置,很简单,添加spring.cloud.nacos.discovery.cluster-name,这里我们设置为HZ
重启UserApplication和UserApplication2,可以看到,这里变成HZ集群,并且下面有8081和8082两个服务
接下来我们把HZ改成BJ,启动UserApplication3(注意:不要重启UserApplication和UserApplication2,不然它们也会变到BJ集群下面)
刷新nacos服务详情,可以看到,我们配的两个集群已经成功了

Nacos负载均衡策略
那么nacos里怎么做到集群的本地访问呢,我们来测试一下。先注释掉原先的随机策略,采用默认规则。然后将order-service也添加到HZ集群。启动order-service。可以看到,加入HZ集群成功。
接下来,我们来请求测试下http://localhost:8080/order/101~106,是否默认会选择HZ集群的userservice进行请求调用。
可以看到,请求并不仅限于HZ集群内部调用,8083也会请求到。为什么呢,因为负载均衡由IRule决定的,在没有配置的情况下,默认规则是轮询规则。我们改用NacosRule规则
@MapperScan("cn.lsy.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
public IRule rule(){
return new NacosRule();
}
}
重启order-service,请求测试
可以看到NacosRule的访问特点是,先选择本地集群HZ下的8081和8082,然后随机访问。
那么如果HZ集群都挂了会怎么样呢,是请求其它集群还是请求失败,我们来试一下,关闭localhost:8081和ocalhost:8082,请求测试。
答案是会请求其它集群的服务。
总结一下NacosRule负载均衡的策略:
- 优先选择同集群服务实例列表
- 本地集群找不到服务提供者,才去其它集群寻找
- 确定了可用实例列表后,再采用随机负载均衡挑选实例
权重设置
上面我们也看到了,同一个集群下实例的访问是随机的。那么如果有些机器性能好,有些机器性能差,怎么更好的利用区分呢,我们可以在Nacos控制台设置实例的权重。上面我们关掉了两个HZ集群下的userservice,我们重启这两个服务。
可以看到,默认权重都是1。我们可以点击编辑设置每个实例的权重,一般设置为0~1之间。同一个集群内部,权重越低,访问概率越小。当权重为0时,就不会访问到这个实例了。有啥用呢?服务升级重启就可以用到。这个大家就自行测试吧~
环境隔离
Nacos里有个概念,环境隔离——namespace,也就是命名空间。它有什么用呢,主要用来区分测试、开发、线上等环境。
如上图所示,最外层是一个namespace命名空间,不同的命名空间可以代表不同的环境,不同的namespace之间是相互隔离的。命名空间下面是一个group分组,这个主要是将关系度比较高的服务放在一个分组内管理,可以不设置分组。接下来就到service服务层了。这里还有一个Data,因为Nacos不仅可以做服务注册中心,还可以作为数据中心。
我们打开Nacos控制台看一下
Nacos默认有一个public的命名空间,我们的userservice和orderservice也在public命名空间里面。
我们创建一个新的命名空间,命名为dev,代表开发环境(图里描述测试环境应该是开发环境)
点击确定后,可以看到,多了一个dev的命名空间,ID的话是使用UUID自动生成的。每个namespace都有一个唯一ID。
在服务列表里,我们也能看到,这里多了一个dev环境
那如果我们想把服务order-service放到dev空间里,怎么做呢?只需要在order-service的配置文件里加上namespace配置
重启order-service,刷新nacos服务列表可以看到,orderservice已经从public空间变到了dev空间。
那么现在orderservice能调用userservice查询用户信息吗,当然是不能的。前面我们说过了,不同namespace的服务都是互相隔离,不可见的。它会报一个没有可用的userservice服务的错误。
Nacos和Eureka的比较
相同点:
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
区别:
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式,默认是临时实例。
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除,只会显示当前实例为不健康状态。非临时实例挂掉的话对服务性能影响较大,因为Nacos会持续主动检测直至该实例恢复(亲儿子)。
- Nacos支持服务列表变更的消息推送模式,缓存服务列表更新更及时。
- Nacos集群默认采用AP方式(可用性),当集群中存在非临时实例时,采用CP方式(一致性)。Eureka采用AP方式。
设置非临时实例,只需要在服务的配置文件里添加
spring.cloud.nacos.discovery.ephemeral=false
比如设置order-service为非临时实例
重启order-service,查看nacos控制台服务详情可以看到,orderservice已经变成了非临时实例。至于心跳健康状态,大家有兴趣的话就自己测试一下吧。
配置管理
Nacos还有一个很重要的特性,统一配置管理及配置更改热更新。
Nacos支持基于Namespace和Group的配置分组管理,以便用户更灵活的根据自己的需要按照环境或者应用、模块等分组管理微服务以及Spring的大量配置。
新建配置
打开Nacos控制台-配置列表,可以看到,目前配置列表是空的。我们来添加一个配置。
可以看到,这里要填写几个信息:
- Data ID:配置文件的唯一ID标识,用【服务名】-【环境】.【文件格式】命名,文件格式需要和配置格式一样
- Group:分组,默认就行
- 描述:配置文件的描述信息
- 配置格式:一般选择YAML
- 配置内容:这里一般配置需要在运行时切换调整的一些自定义属性值,不要一股脑把配置文件全部复制过来噢,那样维护起来比较麻烦。它支持和本地配置文件做合并的。
这里,我们新建一个配置,代表userservice的开发环境配置文件,配置了日期格式化格式。
点击发布,添加成功
拉取配置
那么,微服务要怎么拉取到Nacos中配置文件的信息呢。看下图。
我们原先的项目启动流程是读取application.yml再创建容器加载bean。现在我们需要读取nacos中的配置文件,那么我们首先要获取nacos的地址。而按照加载顺序来看,nacos地址配置在application.yml中会无法读取nacos配置文件,所以我们需要把nacos地址的配置提前,而bootstrap.yml会先于application.yml加载。
- 引入Nacos的配置管理客户端依赖,在user-service的pom.xml中添加依赖
<!--nacos客户端配置管理依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
-
在user-service的resources目录下添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml,这里的服务名+环境+后缀名要和我们上面在nacos控制台添加的配置data id对应,不然获取不到噢。application.yml里的nacos相关依赖注释掉。
spring: application: name: userservice #服务名 profiles: active: dev #环境 cloud: nacos: server-addr: localhost:8848 #nacos地址 discovery: username: nacos password: nacos config: username: nacos password: nacos file-extension: yaml #文件名后缀
然后在UserController中获取dateformat,调用测试
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("now")
public String now() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
/**
* @param id 用户id
* @return 用户信息
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
}
启动user-service,调用http://localhost:8081/user/now测试,可以看到,成功返回了格式化后的日期。
配置热更新
那么现在在nacos控制台修改配置文件就能实现热更新了吗,并不是。我们还需要在代码里加个配置。有两种方式
- 通过@Value注解注入,配合@RefreshScope来刷新
- 创建一个属性配置类,基于SpringBoot注解@ConfigurationProperties自动装配,实现自动刷新(推荐)
package cn.lsy.user.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Data @Component @ConfigurationProperties(prefix = "pattern") public class PatternProperties { private String dateformat; }
@Slf4j @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Autowired private PatternProperties patternProperties; @GetMapping("now") public String now() { return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat())); } /** * @param id 用户id * @return 用户信息 */ @GetMapping("/{id}") public User queryById(@PathVariable("id") Long id) { return userService.queryById(id); } }
测试一下
修改日期格式
再请求下,可以看到,在不重启服务的情况下实现了配置文件的热更新。
多环境配置共享
简单的说,就是我们把一些通用配置放在通用配置文件里如userservice.yaml,一些环境相关的配置放在各自的环境配置文件里如userservice-dev.yaml。读取配置文件的优先级是:服务名-profile.yaml>服务名.yaml>本地环境配置>本地通用配置
测试下。添加一个userservice.yaml,配置pattern.testValue,dev和本地环境也配置上pattern.testValue
属性类里添加testValue
@Data
@Component
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
private String testValue;
}
添加一个测试接口返回属性类信息
@Slf4j
@RestController
@RequestMapping("/user")
//@RefreshScope
public class UserController {
@Autowired
private UserService userService;
@Autowired
private PatternProperties patternProperties;
// @Value("${pattern.dateformat}")
// private String dateformat;
@GetMapping("prop")
public PatternProperties prop() {
return patternProperties;
}
@GetMapping("now")
public String now() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
}
/**
* @param id 用户id
* @return 用户信息
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
}
测试,可以看到,nacos中的环境配置文件dev优先级最高
我们去掉dev中的testValue
再测试下,可以看到,获取的是nacos共享配置文件的testValue。
nacos集群
实际企业应用中,为了保证高可用,一般会配置集群,这里不展开了。