Spring Cloud

Spring Cloud

RestTemplate接口调用

Spring 提供的一个用于发送 HTTP 请求的客户端工具类,在 Java 代码中方便地调用其他 HTTP 服务

示例

1.注册RestTemplate实例

1
2
3
4
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}

2.通过restTemplate发送http来远程调用

1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowired
private RestTemplate restTemplate;

public Order queryOrderById(Long orderId) {
// 查询订单
Order order = orderMapper.findById(orderId);
// 用restTemplate发送get请求查询user信息
String url = "http://localhost:8081/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
// 返回
return order;
}

Eureka注册中心

注册中心:在微服务架构中往往会有一个注册中心,每个微服务都会向注册中心去注册自己的地址及端口信息,注册中心维护着服务名称与服务实例的对应关系。每个微服务都会定时从注册中心获取服务列表,同时汇报自己的运行情况,这样当有的服务需要调用其他服务时,就可以从自己获取到的服务列表中获取实例地址进行调用

Eureka架构中的三个角色

  • 服务注册中心

Eureka的服务端应用,提供服务注册和发现功能

  • 服务提供者

提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。

  • 服务消费者

消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。

搭建Eureka

创建服务端

  1. 导入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>2021.0.6</spring-cloud.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    </dependencies>

    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>
  2. 在启动类上加@EnableEurekaServer注解

    1
    2
    3
    4
    5
    6
    7
    @EnableEurekaServer
    @SpringBootApplication
    public class EurekaApplication {
    public static void main(String[] args) {
    SpringApplication.run(EurekaApplication.class, args);
    }
    }
  3. 编写配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    server:
    port: 8081
    spring:
    application:
    name: eureka-server
    eureka:
    instance:
    hostname: localhost #指定主机地址
    client:
    fetch-registry: false #指定是否要从注册中心获取服务(注册中心不需要开启)
    register-with-eureka: false #指定是否要注册到注册中心(注册中心不需要开启)
    server:
    enable-self-preservation: false #关闭保护模式
  4. 通过localhost:8081访问注册中心界面

创建客户端

  1. 导入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>2021.0.6</spring-cloud.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>
  2. 在启动类上加@EnableEurekaClient注解

  3. 编写配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    server:
    port: 8082 #运行端口号
    spring:
    application:
    name: eureka-client #服务名称
    eureka:
    client:
    register-with-eureka: true #注册到Eureka的注册中心
    fetch-registry: true #获取注册实例列表
    service-url:
    defaultZone: http://localhost:8081/eureka/ #配置注册中心地址

服务发现

  1. 在注册RestTemplate上加上@LoadTemplate注解

    1
    2
    3
    4
    5
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
    return new RestTemplate();
    }
  2. 请求的url中用服务名称替换ip

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Autowired
    private RestTemplate restTemplate;

    public Order queryOrderById(Long orderId) {
    // 1.查询订单
    Order order = orderMapper.findById(orderId);
    //用restTemplate发送http请求查询user信息
    String url = "http://userservice/user/" + order.getUserId();
    User user = restTemplate.getForObject(url, User.class);
    order.setUser(user);
    // 4.返回
    return order;
    }

常用配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
eureka:
client: #eureka客户端配置
register-with-eureka: true #是否将自己注册到eureka服务端上去
fetch-registry: true #是否获取eureka服务端上注册的服务列表
service-url:
defaultZone: http://localhost:8001/eureka/ # 指定注册中心地址
enabled: true # 启用eureka客户端
registry-fetch-interval-seconds: 30 #定义去eureka服务端获取服务列表的时间间隔
instance: #eureka客户端实例配置
lease-renewal-interval-in-seconds: 30 #定义服务多久去注册中心续约
lease-expiration-duration-in-seconds: 90 #定义服务多久不去续约认为服务失效
metadata-map:
zone: jiangsu #所在区域
hostname: localhost #服务主机名称
prefer-ip-address: false #是否优先使用ip来作为主机名
server: #eureka服务端配置
enable-self-preservation: false #关闭eureka服务端的保护机制

Ribbon负载均衡

负载均衡可以增加系统的可用性和扩展性

在注入RestTemplate时加上@LoadBalanced注解开启负载均衡

image-20210713224517686

image-20210713224724673

流程

  • LoadBalanceInterceptor拦截器获取请求,RibbonLoadBalancerClient从url中获取服务名称
  • DynamicServerListLoadBalancer根据服务名称拉取eureka服务列表
  • IRule根据负载均衡策略选取一个
  • RibbonLoadBalancerClient将url中的服务名称替换为ip端口,再发起请求

自定义负载均衡策略

一般使用默认策略(ZoneAvoidanceRule)

方式一(全局)

  • 导入坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.6</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
  • 注册IRule
1
2
3
4
@Bean
public IRule randomRule() {
return new RandomRule();
}

方式二(局部)

  • 在application.yml中添加配置
1
2
3
userservice:  #指定服务名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则

开启饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。

而饥饿加载则会在项目启动时创建,降低第一次访问的耗时

1
2
3
4
ribbon:
eager-load:
enabled: true
clients: userservice #加载的客户端

常用配置

此为全局配置,局部配置要服务名:ribbon下配置

1
2
3
4
5
6
7
ribbon:
ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
OkToRetryOnAllOperations: true #对超时请求启用重试机制
MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
MaxAutoRetries: 1 # 切换实例后重试最大次数
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法

Gateway网关

官网文档

核心功能

  • 请求路由和负载均衡:
  • 权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。
  • 限流: 当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大
  • Predicate(断言):指的是Java 8 的 Function Predicate。 输入类型是Spring框架中的ServerWebExchange。 这使开发人员可以匹配HTTP请求中的所有内容,例如请求头或请求参数。如果请求与断言相匹配,则进行路由;
  • Filter(过滤器):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前后对请求进行修改。

基本使用

  • 创建gateway模块

    gateway模块

  • 导入依赖坐标

    基于spring-boot-starter-webflux(非阻塞)。不支持spring-boot-starter-web(阻塞),不能在依赖中加入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!-- nacos服务发现 -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- gateway网关 -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!-- 负载均衡如果没有生效,或者从网关没法访问其他服务,说明当前版本的Cloud没有包含这个依赖,需要添加上 -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
  • 编写路由配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    server:
    port: 10010 #网关地址
    spring:
    application:
    name: gateway #服务名
    cloud:
    nacos:
    discovery:
    server-addr: localhost:8848 #nacos地址
    gateway:
    routes: #路由配置
    - id: user-service #路由id,可自取保证唯一即可
    uri: lb://userservice #路由目标地址,服务注册的名字(lb: loadBalance)
    predicates: #断言,根据这些路径来匹配
    - Path=/user/**
    - id: order-service #路由id,可自取
    uri: lb://orderservice #路由目标地址(lb: loadBalance)
    predicates: #断言,根据这些路径来匹配
    - Path=/order/**

  • 通过网关地址访问服务

    1
    http://localhost:10010/user/1

跨域问题

浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题

解决跨域问题

在gateway服务的application.yml文件中,添加下面的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
cloud:
gateway:
# 。。。
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求, *为允许所有
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期

流控鉴权

通过过滤器实现流量控制和登录鉴权

过滤器工厂提供的过滤器的作用都是固定的,可以通过全局过滤器来自定义过滤逻辑

  • 限流,使用redis实现,以下为参考代码,如果并发量大,可以改用 Redis Lua 脚本,确保计数与过期操作原子性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    import com.google.gson.Gson;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.core.annotation.Order;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.util.MimeTypeUtils;
    import org.springframework.web.server.ServerWebExchange;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import reactor.core.publisher.Mono;

    import java.nio.charset.StandardCharsets;
    import java.util.concurrent.TimeUnit;

    @Component
    @Order(0) // 优先级高于 SecurityFilterToken(-1),防止恶意请求先打满流量
    public class IPLimitFilter implements GlobalFilter {

    /** 连续请求上限 */
    private static final int CONTINUE_COUNTS = 3;

    /** 统计时间窗口(秒) */
    private static final int TIME_INTERVAL = 20;

    /** 黑名单持续时间(秒) */
    private static final int LIMIT_DURATION = 30;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    return doLimit(exchange, chain);
    }

    private Mono<Void> doLimit(ServerWebExchange exchange, GatewayFilterChain chain) {

    // 1. 获取请求 IP
    ServerHttpRequest request = exchange.getRequest();
    String ip = IPUtil.getIP(request);

    // 2. 拼接 Redis key
    String ipCountKey = "gateway:ip:count:" + ip;
    String ipBanKey = "gateway:ip:ban:" + ip;

    // 3. 检查是否已被拉黑
    Long ttl = redisTemplate.getExpire(ipBanKey, TimeUnit.SECONDS);
    if (ttl != null && ttl > 0) {
    // 在封禁期内,直接拦截
    return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP);
    }

    // 4. 自增访问计数
    Long count = redisTemplate.opsForValue().increment(ipCountKey);

    // 5. 第一次访问时,设置过期时间窗口
    if (count != null && count == 1L) {
    redisTemplate.expire(ipCountKey, TIME_INTERVAL, TimeUnit.SECONDS);
    }

    // 6. 超出访问次数阈值,加入黑名单
    if (count != null && count > CONTINUE_COUNTS) {
    redisTemplate.opsForValue().set(ipBanKey, "1", LIMIT_DURATION, TimeUnit.SECONDS);
    return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP);
    }

    // 7. 放行请求
    return chain.filter(exchange);
    }

    /**
    * 统一错误响应构造
    */
    private Mono<Void> renderErrorMsg(ServerWebExchange exchange,
    ResponseStatusEnum statusEnum) {

    ServerHttpResponse response = exchange.getResponse();

    // 设置响应头为 JSON
    response.getHeaders().set("Content-Type", MimeTypeUtils.APPLICATION_JSON_VALUE);

    // 状态码使用 429 Too Many Requests 更符合限流语义
    response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);

    // 构建返回对象
    GraceJSONResult result = GraceJSONResult.exception(statusEnum);
    String json = new Gson().toJson(result);

    byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
    return response.writeWith(Mono.defer(() ->
    Mono.just(response.bufferFactory().wrap(bytes)))
    );
    }
    }
  • 登录鉴权过滤,以下为参考代码,使用redis实现,只检查了是否有token

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    @Component
    @Order(-1) // 优先级高,越小越先执行
    @RefreshScope // 支持 Nacos / Config 动态刷新配置
    public class SecurityFilterToken implements GlobalFilter {

    /**
    * 注入 RedisTemplate,用于访问 Redis
    */
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
    * 注入免登录 URL 配置
    */
    @Resource
    private ExcludeUrlProperties excludeUrlProperties;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

    // 1. 获取请求路径
    String url = exchange.getRequest().getURI().getPath();

    // 2. 获取免验证路径列表
    List<String> excludeList = excludeUrlProperties.getUrls();

    // 3. 检查是否命中免验证路径
    if (excludeList != null && !excludeList.isEmpty()) {
    for (String excludeUrl : excludeList) {
    // 使用 match() 而不是 matchStart(),避免 /login123 被误放行
    if (antPathMatcher.match(excludeUrl, url)) {
    return chain.filter(exchange); // 放行
    }
    }
    }

    // 4. 从请求头中获取用户 ID 和 Token
    String userId = exchange.getRequest().getHeaders().getFirst(HEADER_USER_ID);
    String userToken = exchange.getRequest().getHeaders().getFirst(HEADER_USER_TOKEN);

    // 5. 校验 Token 是否存在
    if (StringUtils.isNotBlank(userId) && StringUtils.isNotBlank(userToken)) {

    // 拼接 Redis 中的键
    String redisKey = REDIS_USER_TOKEN + ":" + userId;

    // 从 Redis 获取 Token(使用 RedisTemplate)
    String redisToken = redisTemplate.opsForValue().get(redisKey);

    // 6. 校验 Token 是否匹配
    if (StringUtils.isNotBlank(redisToken) && redisToken.equals(userToken)) {
    // 校验成功,放行
    return chain.filter(exchange);
    }
    }

    // 7. 未登录或 Token 不匹配,返回错误响应
    return renderErrorMsg(exchange, ResponseStatusEnum.UN_LOGIN);
    }

    /**
    * 构造统一错误响应
    *
    * @param exchange 当前请求交换器
    * @param statusEnum 状态码枚举(自定义枚举类)
    * @return Mono<Void> 异步返回
    */
    public Mono<Void> renderErrorMsg(ServerWebExchange exchange,
    ResponseStatusEnum statusEnum) {

    // 1. 获取响应对象
    ServerHttpResponse response = exchange.getResponse();

    // 2. 设置响应头为 JSON 类型(强制覆盖)
    response.getHeaders().set("Content-Type", MimeTypeUtils.APPLICATION_JSON_VALUE);

    // 3. 设置 HTTP 状态码为 401(未授权)
    response.setStatusCode(HttpStatus.UNAUTHORIZED);

    // 4. 封装返回的 JSON 对象
    GraceJSONResult jsonResult = GraceJSONResult.exception(statusEnum);
    String resultJson = new Gson().toJson(jsonResult);

    // 5. 写入响应内容
    byte[] bytes = resultJson.getBytes(StandardCharsets.UTF_8);
    return response.writeWith(Mono.defer(() ->
    Mono.just(response.bufferFactory().wrap(bytes)))
    );
    }
    }

过滤器执行顺序

  • order值越小,优先级越高
  • 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增
  • 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。

访问条件

通过断言来设置访问条件

官方文档

通过predicates:后加断言来设置访问条件

名称 说明 示例
After 是某个时间点后的请求 - After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before 是某个时间点之前的请求 - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between 是某两个时间点之前的请求 - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Cookie 请求必须包含某些cookie - Cookie=chocolate, ch.p
Header 请求必须包含某些header - Header=X-Request-Id, \d+
Host 请求必须是访问某个host(域名) - Host=.somehost.org,.anotherhost.org
Method 请求方式必须是指定方式 - Method=GET,POST
Path 请求路径必须符合指定规则 - Path=/red/{segment},/blue/**
Query 请求参数必须包含指定参数 - Query=name, Jack或者- Query=name
RemoteAddr 请求者的ip必须是指定范围 - RemoteAddr=192.168.1.1/24
Weight 权重处理

修改请求/响应

通过过滤器工厂,路由过滤器允许以某种方式修改传入的 HTTP 请求或传出的 HTTP 响应

官方文档

  • 添加过滤器(针对某个服务)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    gateway:
    routes: #路由配置
    - id: user-service #路由id
    uri: lb://userservice #路由目标地址(lb: loadBalance)
    predicates: #断言,根据这些路径来匹配
    - Path=/user/**
    filters:
    - AddRequestHeader=Truth, aaa # 添加请求头
    - id: order-service #路由id
    uri: lb://orderservice #路由目标地址(lb: loadBalance)
    predicates: #断言,根据这些路径来匹配
    - Path=/order/**
  • 添加默认过滤器(对所有服务都有效)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    gateway:
    routes: #路由配置
    - id: user-service #路由id
    uri: lb://userservice #路由目标地址(lb: loadBalance)
    predicates: #断言,根据这些路径来匹配
    - Path=/user/**
    filters:
    - AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
    - id: order-service #路由id
    uri: lb://orderservice #路由目标地址(lb: loadBalance)
    predicates: #断言,根据这些路径来匹配
    - Path=/order/**
    default-filters: # 默认过滤项
    - AddRequestHeader=Truth, aaa

Spring Cloud Alibaba

Nacos服务管理

版本: v3.1.0

官网:https://nacos.io/docs/latest/quickstart/quick-start

安装

Windows

要求本地JDK 17+

  1. github仓库下载压缩包nacos-server-$version.zip,解压

  2. bin目录下运行startup.cmd,单机模式非集群

    1
    startup.cmd -m standalone
  3. 第一次启动会要求设置

    也可在conf/application.properties中设置

    1
    2
    3
    4
    5
    # Server端之间 Inner API的身份标识
    nacos.core.auth.server.identity.key
    nacos.core.auth.server.identity.value
    # 用于生成JWT Token的密钥,使用长度大于32字符的字符串,再经过Base64编码
    nacos.core.auth.plugin.nacos.token.secret.key
  4. 访问http://127.0.0.1:8080/index.html,会要求设置密码

  5. 通过双击shutdown.cmd关闭nacos

Linux

启动命令为sh startup.sh -m standalone,其他与Windows类似

Docker

  1. 从docker hub下载镜像

    https://hub.docker.com/r/nacos/nacos-server

  2. 执行命令,使用docker desktop的话改成全在一行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    docker run --name nacos-standalone-derby \
    -e MODE=standalone \
    -e NACOS_AUTH_TOKEN=${your_nacos_auth_secret_token} \
    -e NACOS_AUTH_IDENTITY_KEY=${your_nacos_server_identity_key} \
    -e NACOS_AUTH_IDENTITY_VALUE=${your_nacos_server_identity_value} \
    -p 8080:8080 \
    -p 8848:8848 \
    -p 9848:9848 \
    -d nacos/nacos-server:latest
    • 8080: Nacos Web 控制台
    • 8848: Nacos Server 与客户端交互的主入口
    • 9848: gRPC 通信端口
  3. 访问http://127.0.0.1:8080/index.html,会要求设置密码

配置管理

官方文档

基本使用

在nacos中添加配置

项目的核心配置,需要热更新的配置才有放到nacos管理的必要

统一配置

配置信息

从nacos中拉取配置
  1. 父模块导入依赖

    maven仓库查看alibaba版本

    查看SpringCloud和SpringBoot对应的版本:https://spring.io/projects/spring-cloud

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!-- 依赖管理 -->
    <!-- scope=import 的作用不是引入具体的 JAR 包,而是引入这个 POM 中的 <dependencyManagement> 配置。 -->
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2023.0.3.2</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>
  2. 子模块导入配置管理坐标

    1
    2
    3
    4
    5
    <!--nacos配置管理依赖-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
  3. application.yml添加配置信息

    bootstrap.yml 以及 bootstrap.properties 已不推荐使用,请使用 application.yml 或者 application.properties

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    spring:
    cloud:
    nacos:
    serverAddr: 127.0.0.1:8848 # 注意访问控制台时端口是8080,这里是8848
    config:
    import:
    - nacos:nacos-config-example.yml?refreshEnabled=true

    server:
    port: 18084
    management:
    endpoints:
    web:
    exposure:
    include: "*"
    • nacos-config-example.yml为配置的Data Id
  4. 通过@Value注解来读取nacos中的配置信息

    1
    2
    @Value("${pattern.dataformat}")
    private String dataformat;
读取配置方式

Nacos中配置nacos-config-example.yml

1
2
3
4
5
6
pattern: 
dataformat: yyyy-MM-dd

example:
name: Tom
age: 20
  1. 通过@Value注解读取

    在类上加@RefreshScope可以热更新

    1
    2
    @Value("${pattern.dataformat}")
    private String pattern;
  2. 通过配置类读取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    @Configuration
    @RefreshScope // 热更新
    @ConfigurationProperties(prefix = "example")
    public class ExampleConfig {
    private String name;
    private Integer age;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public Integer getAge() {
    return age;
    }

    public void setAge(Integer age) {
    this.age = age;
    }

    @Override
    public String toString() {
    return "ExampleConfig{" +
    "name='" + name + '\'' +
    ", age=" + age +
    '}';
    }
    }

多环境配置

显示添加配置

1
2
3
4
5
6
7
8
spring:
application:
name: basic-service
config:
import:
- nacos:${spring.application.name}-${spring.profiles.active}.yml?refreshEnabled=true
profiles:
active: develop

只要修改spring.profiles.active的值,就能切换不同环境的配置

如上面的配置就会加载basic-service-develop.yml

命名空间

Namespace 的常用场景之一是不同环境下配置的区分隔离

使用

  1. 创建命名空间并添加配置

  2. 在配置文件中指定命名空间id,就能读取到不同命名空间下的配置

    1
    2
    3
    4
    5
    6
    spring:
    cloud:
    nacos:
    config:
    # 注意是命名空间的id,默认值为public
    namespace: f9983feb-a567-4113-8f6c-2ab242c3223f

服务注册与发现

服务注册

在 Spring Cloud 应用的启动阶段,监听了 WebServerInitializedEvent 事件,当 Web 容器初始化完成后,即收到 WebServerInitializedEvent 事件后,会触发注册的动作,调用 ServiceRegistry 的 register 方法,将服务注册到 Nacos Server。

  1. 项目中导入依赖

    1
    2
    3
    4
    5
    <!--nacos服务发现-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
  2. 编写配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    spring:
    application:
    name: basic-service
    cloud:
    nacos:
    discovery:
    server-addr: 127.0.0.1:8848
    config:
    server-addr: 127.0.0.1:8848
    config:
    import:
    - nacos:nacos-config-example.yml?refreshEnabled=true

    server:
    port: 18084
    management:
    endpoints:
    web:
    exposure:
    include: "*"
  3. 在启动类上加注解@EnableDiscoveryClient

Nacos搭建集群

通过nginx反向代理多个nacos实现负载均衡

Nacos集群

步骤:

  • 搭建数据库
  • 配置nacos
  • 配置nginx反向代理

配置nacos

  • 进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf

  • 添加nacos集群的ip和端口

    1
    2
    3
    127.0.0.1:8845
    127.0.0.1:8846
    127.0.0.1:8847
  • 修改配置文件application.properties,添加数据库信息

    1
    2
    3
    4
    5
    6
    7
    8
    spring.datasource.platform=mysql

    db.num=1

    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=123
  • 在application.properties中修改每个nacos的端口

  • 分别启动nacos

    1
    startup.cmd

配置nginx反向代理

  • 下载解压nginx到非中文目录下

  • 修改conf/nginx.conf文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    upstream nacos-cluster {
    server 127.0.0.1:8845; #nacos的ip和端口
    server 127.0.0.1:8846;
    server 127.0.0.1:8847;
    }

    server {
    listen 80;
    server_name localhost;

    location /nacos {
    proxy_pass http://nacos-cluster;
    }
    }
  • 代码中nacos的地址要改为nginx的

    1
    2
    3
    4
    spring:
    cloud:
    nacos:
    server-addr: localhost:80 # Nacos地址

配置集群

image-20210713232522531

  • 修改配置

    1
    2
    3
    4
    5
    6
    spring:
    cloud:
    nacos:
    server-addr: localhost:8848
    discovery:
    cluster-name: HZ # 集群名称

同集群优先的负载均衡

Nacos中提供了一个NacosRule的实现,可以优先从同集群中挑选实例

  • 修改配置文件application.yml

    1
    2
    3
    userservice:	#服务名称
    ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则

权重优先的负载均衡

权重越高,优先级越高

权重

  • 通过编辑来设置权重

设置实例类型

实例分为临时实例和非临时实例

临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型

非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例

设置为非临时实例

1
2
3
4
5
spring:
cloud:
nacos:
discovery:
ephemeral: false # 设置为非临时实例

Feign服务消费

Feign是声明式的服务调用工具,我们只需创建一个接口并用注解的方式来配置它,就可以实现对某个服务接口的调用,简化了直接使用RestTemplate来调用服务接口的开发量

实例

服务提供方

  1. 导入坐标

    1
    2
    3
    4
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
  2. 配置

    1
    2
    3
    4
    spring:
    application:
    name: user-service # 调用方会通过服务名来调用
    # ...
  3. Controller类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @RestController
    @RequestMapping("/user")
    public class UserController {

    @GetMapping("/hello/{name}")
    public String hello(@PathVariable("name") String name) {
    return "hello " + name;
    }
    }

服务调用方

  1. 导入坐标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- feign client -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    <!-- 负载均衡 -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
  2. 启动类上加入@EnableFeignClients注解开启Feign的功能

    1
    @EnableFeignClients
  3. 配置一个Feign Client

    1
    2
    3
    4
    5
    @FeignClient(name = "user-service")
    public interface FeignService {
    @GetMapping(value = "/user/hello/{name}")
    String hello(@PathVariable("name") String name);
    }
  4. 注入Feign Client来使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @RestController
    public class BasicServiceController {

    @Resource
    private FeignService feignService;

    @GetMapping("/test/{str}")
    public String test(@PathVariable String str) {
    return feignService.hello(str);
    }
    }

自定义配置

类型 作用 说明
feign.Logger.Level 修改日志级别 包含四种不同的级别:NONE、BASIC、HEADERS、FULL
feign.codec.Decoder 响应结果的解析器 http远程调用的结果做解析,例如解析json字符串为java对象
feign.codec.Encoder 请求参数编码 将请求参数编码,便于通过http请求发送
feign. Contract 支持的注解格式 默认是SpringMVC的注解
feign. Retryer 失败重试机制 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试

配置方式:

  1. 配置文件方式

    1
    2
    3
    4
    5
    feign:
    client:
    config:
    userservice: # 针对某个服务, 不加针对所有服务
    loggerLevel: FULL # 日志等级
  2. 代码方式

    1
    2
    3
    4
    5
    6
    7
    public class DefaultFeignConfiguration {

    @Bean
    public Logger.Level feignLevel() {
    return Logger.Level.BASIC;
    }
    }

    通过注入Bean的方式来自定义配置

    1
    2
    3
    4
    5
    6
    //如果要所有服务生效,则加在@EnableFeignClient上
    @EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)

    //如果只要某个服务生效,则加在指定的feignClient上
    @FeignClient(value = "userservice", configuration = DefaultFeignConfiguration.class)

服务降级

降级指系统将某些业务或者接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能。降级的核心思想就是丢车保帅,优先保证核心业务

优化性能

可以通过一下两点优化性能:

  1. 日志级别尽量用basic及以下
  2. 使用HttpClient或OKHttp代替URLConnection

替换连接池

Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:

•URLConnection:默认实现,不支持连接池

•Apache HttpClient :支持连接池

•OKHttp:支持连接池

因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。

  • 导入依赖坐标

    1
    2
    3
    4
    5
    <!--httpClient的依赖 -->
    <dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    </dependency>
  • 配置使用的连接池

    1
    2
    3
    4
    5
    6
    7
    8
    9
    feign:
    client:
    config:
    userservice: # 针对某个服务
    loggerLevel: BASIC # 日志等级
    httpclient:
    enabled: true # 开启feign对HttpClient的支持
    max-connections: 200 # 最大的连接数
    max-connections-per-route: 50 # 每个路径的最大连接数

抽取Feign客户端

将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用

降低代码冗余

feign抽取

  • 创建新模块feign-api

  • 将FeignClient及feign配置类、实体类移到feign-api中

    feignApi

  • 在需要feign的类中导入feign-api模块

    导入feignApi

  • 在@EnableFeignClients注解中说明feign所在的包或类

    1
    2
    3
    4
    5
    @EnableFeignClients(clients = {UserClient.class})

    //或者

    @EnableFeignClients(basePackages = "cn.itcast.feign.clients")

常用配置

1
2
3
4
5
6
7
8
9
10
11
12
13
feign:
hystrix:
enabled: true #在Feign中开启Hystrix
compression:
request:
enabled: false #是否对请求进行GZIP压缩
mime-types: text/xml,application/xml,application/json #指定压缩的请求数据类型
min-request-size: 2048 #超过该大小的请求会被压缩
response:
enabled: false #是否对响应进行GZIP压缩
logging:
level: #修改日志级别
com.macro.cloud.service.UserService: debug

SchedulingTasks分布式定时任务

防止定时任务被多个节点重复执行

使用MySQL作为分布式锁

  1. 建表

    1
    2
    3
    4
    5
    6
    7
    CREATE TABLE `shedlock` (
    `NAME` varchar(64) NOT NULL,
    `lock_until` timestamp(3) NOT NULL,
    `locked_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    `locked_by` varchar(255) NOT NULL,
    PRIMARY KEY (`NAME`)
    );
  2. 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <!-- 分布式任务需要的锁 -->
    <dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>6.9.2</version>
    </dependency>

    <!-- shedlock的JDBC实现方式 -->
    <dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>6.9.2</version>
    </dependency>

    <!-- JDBC -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    </dependency>
  3. 创建lockProvider的Bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * 分布式定时任务锁
    */
    @Configuration
    public class ScheduledLockConfig {

    // 数据库连接池对象
    @Resource
    private DataSource dataSource;

    @Bean
    public LockProvider lockProvider() {
    return new JdbcTemplateLockProvider(
    JdbcTemplateLockProvider.Configuration.builder()
    // 指定锁的持久化方式是通过 JdbcTemplate 操作数据库
    .withJdbcTemplate(new JdbcTemplate(dataSource))
    // 指定数据库中存储锁时间使用 UTC 时间,防止时区差异导致锁失效时间计算错误
    .withTimeZone(TimeZone.getTimeZone("UTC"))
    .build()
    );
    }
    }
  4. 创建定时任务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    @Component
    public class SpringJob {

    /**
    * 每5分钟跑一次
    *
    * cron表达式:秒、分、时、日、月、星期、年
    * name: 锁的唯一名称
    * lockAtMostFor: 最长持锁时间。如果任务卡死没释放锁,ShedLock会在2分钟后自动释放,避免锁永远卡死
    * lockAtLeastFor: 最短持锁时间。即使任务很快执行完,也至少保留锁1分钟,防止任务立即被另一个实例重复触发
    */
    @Scheduled(cron = "0 */5 * * * ?")
    @SchedulerLock(name = "SpringJob.job1", lockAtMostFor = "2m", lockAtLeastFor = "1m")
    public void job1() {
    System.out.println("time=" + DateTime.now().toString("YYYY-MM-dd HH:mm:ss") + " do job1...");
    }

    /**
    * 每5秒跑一次
    */
    @Scheduled(fixedRate = 5000)
    @SchedulerLock(name = "SpringJob.job2", lockAtMostFor = "4s", lockAtLeastFor = "4s")
    public void job2() {
    System.out.println("time=" + DateTime.now().toString("YYYY-MM-dd HH:mm:ss") + " do job2...");
    }

    /**
    * 上次跑完之后隔5秒再跑
    * @throws InterruptedException
    */
    @Scheduled(fixedDelay = 5000)
    @SchedulerLock(name = "SpringJob.job3", lockAtMostFor = "4s", lockAtLeastFor = "4s")
    public void job3() throws InterruptedException {
    System.out.println("time=" + DateTime.now().toString("YYYY-MM-dd HH:mm:ss") + " do job3...");
    Thread.sleep(10000);
    }
    }
  5. 开启定时任务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    // 开启定时任务
    @EnableScheduling
    // 开启分布式定时任务锁
    @EnableSchedulerLock(defaultLockAtMostFor = "3m")
    public class BasicServiceApplication {
    ...

Sentinel限流

官方文档

Sentinel控制台

下载运行

JDK版本:1.8及以上

  • 下载jar包:https://github.com/alibaba/Sentinel/releases

  • 运行jar包

    1
    java -Dserver.port=18080 -Dcsp.sentinel.dashboard.server=localhost:18080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
    • -Dserver.port:应用启动的端口号
    • -Dcsp.sentinel.dashboard.server=localhost:Sentinel 客户端要连接的控制台服务器地址
    • -Dproject.name:当前运行项目的名称
    • -Dsentinel.dashboard.auth.username:控制台账号
    • -Dsentinel.dashboard.auth.password:控制台密码

    账号密码默认为sentinel

基本使用

和Spring Cloud Alibaba整合参考这个文档

  1. 引入依赖

    1
    2
    3
    4
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
  2. 添加配置

    1
    2
    3
    4
    5
    6
    7
    8
    spring:
    cloud:
    sentinel:
    transport:
    # 会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互
    port: 8720
    # sentinel控制台ip端口
    dashboard: 127.0.0.1:18080
  3. 定义资源

    1
    2
    3
    4
    5
    6
    7
    8
    @Service
    public class TestService {

    @SentinelResource(value = "sayHello")
    public String sayHello(String name) {
    return "Hello, " + name;
    }
    }
  4. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @RestController
    public class TestController {

    @Autowired
    private TestService service;

    @GetMapping(value = "/hello/{name}")
    public String apiHello(@PathVariable String name) {
    return service.sayHello(name);
    }
    }
  5. 需要访问资源后,才可在Sentinel控制台查看到

定义资源

通过@SentinelResource注解来定义资源

参数:

  • value:资源名
  • blockHandler:被限流时调用的方法。方法必须为public,和原函数在同一个类,返回值和参数与原函数一致,还有多一个BlockException类型的变量
  • fallback:抛出异常时被调用的方法。方法必须为public,和原函数在同一个类,返回值和参数与原函数一致,还有多一个Throwable类型的变量
  • exceptionsToIgnore(since 1.6.0):指定哪些方法不会触发fallback

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class TestService {

@SentinelResource(value = "sayHello", blockHandler = "blockHandler", fallback = "fallback")
public String sayHello(String name) {
if(name.equals("a")) {
throw new NullPointerException("报错信息");
}
return "Hello, " + name;
}

// 限流后执行的方法
public String blockHandler(String name, BlockException e) {
return "被限流了";
}

// 报错后执行的方法
public String fallback(String name, Throwable e) {
return "报错了:" + e.getMessage();
}
}

流量控制

实时统计信息

通过http://localhost:服务端口/cnode?id=资源名查看实时统计信息

  • thread: 代表当前处理该资源的线程数;
  • pass: 代表一秒内到来到的请求;
  • blocked: 代表一秒内被流量控制的请求数量;
  • success: 代表一秒内成功处理完的请求;
  • total: 代表到一秒内到来的请求以及被阻止的请求总和;
  • RT: 代表一秒内该资源的平均响应时间;
  • 1m-pass: 一分钟内到来的请求;
  • 1m-block: 一分钟内被阻止的请求;
  • 1m-all: 一分钟内到来的请求和被阻止的请求的总和;
  • exception: 一秒内业务本身异常的总和。

控制方式

  • 并发线程数

    用于保护业务线程数不被耗尽。流控效果只有直接拒接请求

  • QPS

    QPS 超过某个阈值的时候,则采取措施进行流量控制。

    流控效果有:

    • 直接拒绝:默认的流量控制方式。当QPS超过任意规则的阈值后,新的请求就会被立即拒绝
    • Warm Up(冷启动):让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮的情况。
    • 排队等待:控制了请求通过的间隔时间,让请求以均匀的速度通过。主要用于处理间隔性突发的流量

熔断降级

如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。

作用是异常达到设置的阈值后,该资源变得不可调用,防止调用链路中某个不稳定的服务,导致整个链路不可用

熔断策略

黑体为可自己设置的部分

因为流量控制产生的异常不算在内

  • 慢调用比例:需要设置RT(最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态,若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断
  • 异常比例:当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态,若接下来的一个请求成功完成则结束熔断
  • 异常数 :当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态,若接下来的一个请求成功完成则结束熔断

热点规则

看做特殊的流量控制,仅对包含热点参数的资源生效。

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流

可设置参数索引、单机阈值及统计窗口时长,和流量控制的使用类似

配置持久化

如果在Sentinel DashBoard中添加的配置,每当Sentinel或服务重启后,配置都会消失

因此若要持久化,则需要通过外部来保存配置

基于Nacos

  1. 添加依赖

    1
    2
    3
    4
    5
    <!-- 以Nacos作为Sentinel配置的数据源 -->
    <dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
  2. 添加配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    spring:
    cloud:
    sentinel:
    datasource:
    nacos-flow-rule: # 自定义数据源名称
    nacos:
    server-addr: 127.0.0.1:8848 # nacos地址
    namespace: f9983feb-a567-4113-8f6c-2ab242c3223f # 命名空间id
    dataId: sentinel-flow-sayHello-rule # nacos创建的配置DataId
    data-type: json # 数据类型
    rule-type: flow # 规则类型
    • rule-type取值
      • flow:限流规则
      • degrade:熔断降级规则
      • authority:授权规则
      • param-flow:热点参数限流
      • system:系统保护规则
      • gw-flow:网关限流规则
      • gw-api-group:网关自定义分组规则
  3. nacos中添加配置

    Data ID:sentinel-flow-sayHello-rule

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [ 
    {
    "resource": "sayHello",
    "controlBehavior": 0,
    "count": 300,
    "grade": 1,
    "limitApp": "default",
    "strategy": 0
    }
    ]
    • resource:资源名,即限流规则的作用对象
    • controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)
    • count: 限流阈值
    • grade: 限流阈值类型(QPS 或并发线程数)
    • limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
    • strategy: 调用关系限流策略

    要找配置项,可以去官网找对应的名字

  4. 在Sentinel DashBoard中可以看到同步的配置。修改Nacos中的配置可以同步修改控制台里的配置,但是从控制台修改不会同步到Nacos


Spring Cloud
http://xwww12.github.io/2022/09/03/微服务/SpringCloud/
作者
xw
发布于
2022年9月3日
许可协议