Administrator
发布于 2023-04-22 / 14 阅读
0
0

微服务(六)——Nacos进阶

服务分级模型

对于大型项目而言,考虑到容灾、就近访问等,微服务一般会部署在多个区域机房内。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集群

实际企业应用中,为了保证高可用,一般会配置集群,这里不展开了。


评论