JAVA 服务间调用限流?结合 Sentinel + Nacos 实现动态限流规则

好的,我们开始今天的讲座,主题是“JAVA 服务间调用限流:结合 Sentinel + Nacos 实现动态限流规则”。

在微服务架构中,服务间的调用非常频繁,如果不加以限制,高并发的请求可能会导致服务雪崩,影响整个系统的稳定性。限流就是一种有效的保护机制,它可以限制服务的请求速率,防止过载。今天我们将深入探讨如何使用 Sentinel 结合 Nacos 来实现动态的限流规则。

1. 为什么需要限流?

在深入技术细节之前,我们先来明确一下为什么需要限流。想象一下,你正在举办一个免费的演唱会,如果没有任何入场限制,所有人都想挤进场地,最终的结果很可能是踩踏事故。在微服务架构中,如果某个服务突然面临大量的请求,它可能会因为资源耗尽而崩溃,进而影响依赖它的其他服务,最终导致整个系统瘫痪。

限流就像演唱会的入场券,它控制了进入系统的请求数量,确保服务在可承受的范围内运行。它可以防止恶意攻击、应对突发流量,保证服务的可用性和稳定性。

2. Sentinel 简介

Sentinel 是阿里巴巴开源的一款流量控制、熔断降级框架。它以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来保障服务的稳定性。

Sentinel 的核心概念包括:

  • 资源 (Resource): Sentinel 保护的目标,可以是任何东西,例如一个服务、一个方法、甚至是一段代码。
  • 规则 (Rule): 定义如何保护资源。Sentinel 提供了多种规则类型,包括流量控制规则、熔断降级规则、系统保护规则等。
  • Slot Chain: Sentinel 的核心处理链路,负责执行各种规则。

3. Nacos 简介

Nacos 是阿里巴巴开源的配置中心和服务注册发现组件。它可以帮助我们管理服务的配置信息、服务注册信息等,并提供动态配置更新的能力。

在限流场景中,我们可以使用 Nacos 来存储 Sentinel 的限流规则,并实现动态更新。当我们需要调整限流策略时,只需要修改 Nacos 中的配置,Sentinel 就会自动加载新的规则,无需重启服务。

4. Sentinel + Nacos 实现动态限流的步骤

下面我们将详细介绍如何使用 Sentinel 结合 Nacos 来实现动态限流。

4.1. 项目准备

我们需要一个简单的 Spring Boot 项目,包含两个服务:服务 A 和 服务 B。服务 A 调用服务 B。

  • 服务 A (Consumer): 发起调用的服务,模拟用户请求。
  • 服务 B (Provider): 提供服务的服务,需要进行限流保护。

首先,创建两个 Spring Boot 项目,分别命名为 service-aservice-b

4.2. 添加依赖

service-b (Provider) 的 pom.xml 文件中添加以下依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

同样,在 service-a (Consumer) 的 pom.xml 文件中添加以下依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-openfeign</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

说明:

  • spring-cloud-starter-alibaba-sentinel: Sentinel 的 Spring Cloud Starter,用于集成 Sentinel。
  • spring-cloud-starter-alibaba-nacos-config: Nacos 配置中心的 Spring Cloud Starter,用于从 Nacos 加载配置。
  • spring-cloud-starter-alibaba-nacos-discovery: Nacos 服务发现的 Spring Cloud Starter,用于服务注册与发现。
  • spring-boot-starter-web: Spring Web 的 Starter,用于构建 RESTful API。
  • spring-cloud-starter-openfeign: Feign Client 的 Starter,用于服务间的调用 (服务A)。

4.3. 配置 Nacos

启动 Nacos 服务。你可以在 Nacos 官网下载并安装 Nacos,或者使用 Docker 启动 Nacos。

service-bservice-aapplication.propertiesapplication.yml 文件中添加 Nacos 配置:

# application.yml (service-b)
spring:
  application:
    name: service-b
  cloud:
    nacos:
      config:
        server-addr: localhost:8848  # Nacos 服务器地址
        file-extension: yaml
      discovery:
        server-addr: localhost:8848
server:
  port: 8082 # 服务B的端口
# application.yml (service-a)
spring:
  application:
    name: service-a
  cloud:
    nacos:
      config:
        server-addr: localhost:8848  # Nacos 服务器地址
        file-extension: yaml
      discovery:
        server-addr: localhost:8848
server:
  port: 8081 # 服务A的端口
feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000

4.4. 配置 Sentinel

在 Nacos 中创建名为 service-b.sentinel.flow 的 Data ID,Group 默认为 DEFAULT_GROUP,内容为 Sentinel 的流量控制规则。 文件类型选择 YAML。

# service-b.sentinel.flow
- resource: /hello # 受保护的资源
  limitApp: default # 限制的应用,default 表示不区分调用来源
  grade: 1 # 限流的维度,1 表示 QPS
  count: 10 # QPS 的阈值,每秒最多允许 10 个请求
  controlBehavior: 0 # 流控效果,0 表示直接拒绝

说明:

  • resource: 需要进行限流的资源名称,这里我们设置为 /hello
  • limitApp: 限制的应用,default 表示不区分调用来源,对所有请求都进行限流。
  • grade: 限流的维度,1 表示按照 QPS (每秒请求数) 进行限流。
  • count: 限流的阈值,这里设置为 10,表示每秒最多允许 10 个请求通过。
  • controlBehavior: 流控效果,0 表示直接拒绝超出阈值的请求。

4.5. Provider (service-b) 代码实现

service-b 中,创建一个 Controller,并添加 Sentinel 的注解:

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    @SentinelResource("hello") // 定义资源名称
    public String hello() {
        return "Hello from service-b!";
    }
}

说明:

  • @SentinelResource("hello"): 使用 Sentinel 的 @SentinelResource 注解来定义一个资源,resource 属性指定资源名称,与 Nacos 中配置的 resource 保持一致。
  • blockHandler: 可选属性,指定当请求被限流时执行的方法。
  • fallback: 可选属性,指定当方法抛出异常时执行的方法。

4.6. Consumer (service-a) 代码实现

service-a 中,我们需要使用 Feign Client 来调用 service-b

首先,创建一个 Feign Client 接口:

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

@FeignClient(name = "service-b") // 指定调用的服务名称
public interface ServiceBClient {

    @GetMapping("/hello") // 指定调用的 API 路径
    String hello();
}

然后,在 service-a 的主类上添加 @EnableFeignClients 注解,启用 Feign Client:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class ServiceAApplication {

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

最后,创建一个 Controller,使用 Feign Client 调用 service-b

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Autowired
    private ServiceBClient serviceBClient;

    @GetMapping("/call-hello")
    public String callHello() {
        return serviceBClient.hello();
    }
}

4.7. 启动服务并测试

依次启动 Nacos、service-bservice-a

访问 service-a/call-hello 接口,例如:http://localhost:8081/call-hello

在短时间内快速刷新页面,或者使用工具(如 ab 命令)模拟高并发请求。你会发现,当请求超过 service-b 配置的 QPS 阈值 (10) 时,Sentinel 会拒绝超出阈值的请求,service-a 会收到错误信息。

4.8. 动态修改限流规则

修改 Nacos 中 service-b.sentinel.flow 的内容,例如将 count 修改为 20

# service-b.sentinel.flow
- resource: /hello # 受保护的资源
  limitApp: default # 限制的应用,default 表示不区分调用来源
  grade: 1 # 限流的维度,1 表示 QPS
  count: 20 # QPS 的阈值,每秒最多允许 20 个请求
  controlBehavior: 0 # 流控效果,0 表示直接拒绝

稍等片刻,Sentinel 会自动加载新的规则。再次访问 service-a/call-hello 接口,你会发现 service-b 的 QPS 阈值已经变为 20

5. 更多配置和高级用法

  • 流控效果 (controlBehavior):
    • 0: 快速失败,直接拒绝超出阈值的请求。
    • 1: 匀速排队,让请求以均匀的速度通过,适用于应对突发流量。
    • 2: 预热,在一段时间内逐渐增加请求的通过速率,防止系统被瞬间的流量冲击。
  • 熔断降级规则: 当服务出现故障时,自动熔断,防止故障蔓延。
  • 系统保护规则: 从系统负载的角度进行保护,例如限制 CPU 使用率、Load、RT 等。
  • 自定义 BlockException 处理: 通过实现 BlockExceptionHandler 接口,可以自定义当请求被限流或降级时的处理逻辑。
  • 热点参数限流: 针对热点参数进行限流,例如限制某个用户的访问频率。

6. 代码示例

下面是一个更完整的 service-b 的代码示例,包含了自定义 BlockException 处理:

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    @SentinelResource(value = "hello", blockHandler = "handleHelloBlock")
    public String hello() {
        return "Hello from service-b!";
    }

    // 处理 BlockException 的方法
    public String handleHelloBlock(BlockException ex) {
        return "Service is busy, please try again later.";
    }
}

在这个示例中,我们使用 blockHandler 属性指定了当请求被限流时执行的方法 handleHelloBlock

7. 总结

我们学习了如何使用 Sentinel 结合 Nacos 实现动态限流。通过 Nacos,我们可以动态地修改限流规则,而无需重启服务。Sentinel 提供了多种限流策略和高级功能,可以满足各种复杂的限流需求。希望这次讲座能帮助你更好地理解和使用 Sentinel,保护你的微服务系统。

服务保护,Sentinel + Nacos 实现流量控制。

灵活配置,动态调整限流规则。

确保稳定,防止服务雪崩。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注