朋也的博客 » 首页 » 文章

SpringCloud学习记录 - Nacos

作者:朋也
日期:2021-02-02


版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

Nacos是啥?

Nacos是alibaba将不在更新的 config, ribbon, bus等模块给整合的一个包,并且在维护更新,相当于以后用config或者负载均衡等服务就只需要引入一个nacos就够了

Hello World

首先下载nacos,官网:https://nacos.io/zh-cn/docs/quick-start.html

本文用的版本是 1.4.0

启动成功后,访问:http://localhost:8848/nacos/index.html 用户名: nacos 密码: nacos

接下来创建项目, 我这创建了两个服务提供者用于测试

nacos-provider1

依赖

<!-- 这个依赖是配置在根目录下的pom.xml里的 -->
<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>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <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>
    </dependency>
</dependencies>

application.properties

server.port=28081

spring.application.name=nacos-provider

spring.cloud.nacos.discovery.server-addr=localhost:8848

management.endpoints.web.exposure.include=*

启动类 NacosProvider1Application.java

package com.example.springcloudtutorial;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient // 一定要加上这个注解, 否则nacos发现不了服务
@SpringBootApplication
public class NacosProvider1Application {

    public static void main(String[] args) {
        SpringApplication.run(NacosProvider1Application.class, args);
    }

}

第二个服务的端口为 28082

分别启动两个服务, 然后打开nacos的控制台

负载均衡

nacos自带了负载均衡的功能,原因是它把ribbon给引入了

下面实现一下负载均衡

先在两个服务提供者中添加上一个服务

package com.example.springcloudtutorial;

import org.springframework.beans.factory.annotation.Value;
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.RestController;

@EnableDiscoveryClient
@SpringBootApplication
@RestController
public class NacosProvider1Application {

    @Value("${server.port}")
    private int port;

    @GetMapping("/getOrder")
    public Object getOrder() {
        return "order list " + port;
    }

    public static void main(String[] args) {
        SpringApplication.run(NacosProvider1Application.class, args);
    }
}

创建消费者模块 nacos-consumer

依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <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>
    </dependency>
</dependencies>

配置文件

server.port=28083

spring.application.name=nacos-consumer

spring.cloud.nacos.discovery.server-addr=localhost:8848

management.endpoints.web.exposure.include=*

启动类

package com.example.springcloudtutorial;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
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;

@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class NacosConsumerApplication {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @GetMapping("/consumer/getOrder")
    public Object getOrder() {
        return restTemplate().getForEntity("http://nacos-provider/getOrder", String.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(NacosConsumerApplication.class, args);
    }
}

消费者中请求的地址写的是 http://nacos-provider 这个 nacos-provider 就是服务提供者的 spring.application.name

在注入 restTemplate 的时候添加上 @LoadBalanced 实现负载均衡的实现,默认是轮询

分别启动服务,nacos控制台里可以看到有2个服务提供者,一个服务消费者

浏览器访问 http://localhost:/consumer/getOrder 不停的刷新会看到输出的端口是轮换输出的

配置管理Config

nacos控制台自带了配置文件编写发布的功能

打开配置页面如下

其中 dataId 配置规则是 ${prefix}-${spring.profiles.active}.${file-extension}

创建一个配置文件,名为:nacos-consumer-dev.properties 内容如下,然后保存发布

site.name=nacos-consumer,version=1

在模块 nacos-consumer1 里添加下面依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

其中这个依赖 spring-cloud-starter-bootstrap 必须要加,如果不加的话,bootstrap.properties 文件将不加载

application.properties 修改如下

spring.profiles.active=dev
server.port=28083

新建 bootstrap.properties

spring.application.name=nacos-consumer

spring.cloud.nacos.discovery.server-addr=localhost:8848

spring.cloud.nacos.config.server-addr=localhost:8848
spring.cloud.nacos.config.file-extension=properties
spring.cloud.nacos.config.prefix=${spring.application.name}

management.endpoints.web.exposure.include=*

启动类上添加 @RefreshScope 注解用来更新修改的配置信息,同时获取一下配置文件里的值

package com.example.springcloudtutorial;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.context.config.annotation.RefreshScope;
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;

@SpringBootApplication
@EnableDiscoveryClient
@RestController
@RefreshScope
public class NacosConsumerApplication {

    @Value("${site.name:}")
    private String name;

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @GetMapping("/consumer/getOrder")
    public Object getOrder() {
        return restTemplate().getForEntity("http://nacos-provider/getOrder", String.class).getBody() + " " + name;
    }

    public static void main(String[] args) {
        SpringApplication.run(NacosConsumerApplication.class, args);
    }
}

浏览器访问:http://localhost:28083/consumer/getOrder 输出内容 order list 28082 nacos-consumer,version=1

去nacos控制台里修改一下配置文件内容后保存发布,回到IDE,会看到已经通知到项目刷新配置信息了

再次访问,输出的 site.name 内容也是最新的了

上面介绍了dataid的配置规则跟项目中的配置,nacos除了dataid还提供了两个划分环境的变量,group,namespace

在nacos控制台上创建配置文件的时候,有个选项默认值是 DEFAULT_GROUP, 通过更改这个值就可以将配置文件分配到不同的组中,在项目中只需要通过 spring.cloud.nacos.config.group=DEFAULT_GROUP 来指定不同的组即可

命名空间 namespace

在上一小段里说到了配置文件有三个变量,其中dataid和group都介绍了,还剩一个namespace,单独拿出来介绍是因为服务(不止配置中区分)也可以区分命名空间

首先在控制台上创建一个namespace

然后复制命名空间ID,贴到项目中的配置文件里,对应的配置项如下

# 服务的namespace
spring.cloud.nacos.discovery.namespace=58ee0281-11e6-4ffe-90f2-e84f00d15bb0

# 配置文件的namespace
spring.cloud.nacos.config.namespace=58ee0281-11e6-4ffe-90f2-e84f00d15bb0

注意,如果服务配置了命名空间,那么互相调用的服务都要保持在一个命名空间里才能被发现到

MySQL

nacos默认数据存储在一个内置的数据库里(derby),下面介绍一下怎么切换到MySQL数据库

官方文档:https://nacos.io/zh-cn/docs/deployment.html

首先创建一个数据库,名为: nacos-config, 然后打开nacos安装目录下的 conf 文件夹,找到 nacos-mysql.sql 文件直接在数据库里执行即可

最后找到 conf/application.properties 文件,在最下面添加下面配置即可,注意数据库名密码要换成自己的

spring.datasource.platform=mysql

db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123123

重启nacos即可

Sentinel

hystric不更新后,对服务的熔断,降级,限流等操作现在大都使用alibaba/sentinel

首先下载一个sentinel的控制台,下载地址:https://github.com/alibaba/Sentinel/releases

启动控制台 java -jar sentinel-dashboard-1.8.0.jar 启动成功后浏览器访问: http://localhost:8080 用户名密码都是 sentinel

登录成功可以看到如下界面

接下来创建一个模块,让sentinel来监控这个服务的运行情况

服务名:nacos-sentinel

依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <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>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
</dependencies>

配置文件 application.properties

server.port=28084

spring.application.name=nacos-sentinel

spring.cloud.nacos.discovery.server-addr=localhost:8848

# sentinel配置
spring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.transport.port=8719

management.endpoints.web.exposure.include=*

启动类 NacosSentinelApplication.java

package com.example.springcloudtutorial;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
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.RestController;

@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class NacosSentinelApplication {

    @GetMapping("/test")
    @SentinelResource("test")
    public Object test() {
        return "success";
    }

    public static void main(String[] args) {
        SpringApplication.run(NacosSentinelApplication.class, args);
    }
}

启动服务,并浏览器访问:http://localhost:28084/test 多刷新几次,打开sentinel的控制台,就可以看到监控数据了

Sentinel 流控规则

阈值类型

后面配置上阈值数量

流控模式有三种

流控效果

Sentinel 降级规则

熔断策略

Sentinel 热点规则

官方文档:https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

用法如下:

@GetMapping("/test")
@SentinelResource(value = "test", blockHandler = "test_blockHandler", fallback = "test_fallback")
public Object test(String name, Integer age) {
//        int a = 10 / 0;
    return "success";
}

// 当被热点规则限流后的处理方法
public Object test_blockHandler(String name, Integer age, BlockException exception) {
    return "test_blockHandler...";
}

// 当程序运行时异常后的处理方法
public Object test_fallback(String name, Integer age) {
    return "test_fallback...";
}

sentinel配置如下

@SentinelResource里还配置了两个参数

另外@SentinelResource注解除了能指定默认的处理方法外还能配置上单独的类,然后指定类中的方法来作为默认的处理方法,比如

MyFallbackHandler.java

package com.example.springcloudtutorial;

import com.alibaba.csp.sentinel.slots.block.BlockException;

public class MyFallbackHandler {

    public Object test_blockHandler(String name, Integer age, BlockException exception) {
        System.out.println(exception.getMessage());
        return "test_blockHandler...";
    }

    public Object test_fallback(String name, Integer age) {
        return "test_fallback...";
    }
}
@GetMapping("/test")
@SentinelResource(
        value = "test",
        fallbackClass = MyFallbackHandler.class,
        fallback = "test_fallback",
        blockHandlerClass = MyFallbackHandler.class,
        blockHandler = "test_blockHandler"
)
public Object test(String name, Integer age) {
//        int a = 10 / 0;
    return "success";
}

集成Openfeign

新建模块 nacos-openfeign

依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <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>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>

配置文件 application.properties

server.port=28085

spring.application.name=nacos-openfeign

# 服务注册中心
spring.cloud.nacos.discovery.server-addr=localhost:8848

# sentinel配置
spring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.transport.port=8719

# 打开 sentinel 支持
feign.sentinel.enabled=true

启动类

package com.example.springcloudtutorial;

import com.example.springcloudtutorial.service.ProviderService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@RestController
public class NacosOpenfeignApplication {

    @Resource
    private ProviderService providerService;

    @GetMapping("/consumer/getOrder")
    public Object getOrder() {
        return providerService.getOrder();
    }

    public static void main(String[] args) {
        SpringApplication.run(NacosOpenfeignApplication.class, args);
    }
}

ProviderService.java

package com.example.springcloudtutorial.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(value = "nacos-provider", fallback = ProviderServiceImpl.class)
public interface ProviderService {

    @GetMapping("/getOrder")
    public Object getOrder();
}

openfeign支持的fallback默认实现

package com.example.springcloudtutorial.service;

import org.springframework.stereotype.Component;

@Component
public class ProviderServiceImpl implements ProviderService {
    @Override
    public Object getOrder() {
        return "ProviderServiceImpl -- getOrder";
    }
}

启动一个nacos-provider,并启动当前模块,浏览器访问:http://localhost:28085/getOrder 即可进行测试

另外因为集成了sentinel,还可以在sentinel控制台里配置限流规则,用于测试限流时的fallback默认实现

说一下这里面的坑,之前学 spring-cloud 配置中心的时候,因为spring-cloud版本问题将 spring-boot版本从 2.2.2.RELEASE 升级到 2.4.1 spring-cloud的版本从 Hoxton.SR9 升级到 2020.0.0 才解决报错问题,详情可以参见:https://tomoya92.github.io/2021/01/11/spring-cloud-config/

当我继续使用升级后的spring-cloud与spring-boot版本配置spring-cloud-alibaba里的openfeign与sentinel时,一直报 Caused by: java.lang.IllegalStateException: No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer? 这个异常,一直没找到解决办法,直到我把版本号又降回去后,它神奇的好使了。。