Sentinel 是阿里巴巴开源的一款面向分布式服务架构的轻量级高可用流量控制组件,主要用于流量控制、熔断降级和系统负载保护。 虽然 Sentinel 主要用于微服务场景下的流量管理和故障隔离,但也可以通过一些策略和配置来辅助防御 DDoS 攻击和异常爬虫请求。
DDoS攻击
DDoS(Distributed Denial of Service)是一种恶意攻击手段,攻击者通过控制大量计算机设备(如僵尸网络),向目标服务器发送大量的数据包或请求,从而耗尽服务器的带宽、CPU资源或其他系统资源,导致合法用户无法正常访问服务。
常见类型:
- Volume-based Attacks (体积型攻击):
- 例如ICMP Flood、UDP Flood。
- 攻击者发送大量无用的数据包,占用带宽。
- Protocol Attacks (协议型攻击):
- 例如SYN Flood、ACK Flood。
- 攻击者利用TCP/IP协议漏洞,发送特定的数据包使服务器崩溃。
- Application Layer Attacks (应用层攻击):
- 例如HTTP Flood、Slowloris。
- 攻击者模拟真实用户的行为,发送大量的HTTP请求,消耗服务器的应用层资源。
防御措施:
- 使用CDN: 内容分发网络可以帮助分散流量,减轻单个服务器的压力。
- 负载均衡: 分散请求到多个服务器上,提高系统的可用性。
- 防火墙和入侵检测系统: 防止非法流量进入服务器。
- Rate Limiting (限流): 控制每个IP地址或来源的请求速率,防止过载。
- Traffic Shaping (流量整形): 调整进出网络的数据包传输速率,优化流量分配。
- Anycast IP Addressing: 使用多条路径将流量引导至最近的健康节点,提高冗余性和抗攻击能力。
异常爬虫请求
异常爬虫是指那些不符合正常爬虫行为规范的自动化程序,它们可能会对网站造成负担,甚至破坏网站的正常运行。这些爬虫可能用于抓取敏感信息、进行竞争情报收集、参与SEO欺诈等活动。
特点:
- 高频率请求: 在短时间内发送大量请求,可能导致服务器过载。
- 不遵循robots.txt: 忽略网站的爬虫协议文件,访问受保护的内容。
- 伪装成普通用户: 使用伪造的User-Agent字符串,难以识别。
- 频繁更改IP: 使用代理或VPN频繁更换IP地址,增加追踪难度。
防御措施:
- 设置Robots.txt: 明确告知爬虫哪些内容可以抓取,哪些不可以。
- Rate Limiting (限流): 限制每个IP地址或来源的请求速率,防止滥用。
- CAPTCHA (验证码): 在关键操作前要求用户提供验证码,区分人机。
- IP黑名单/白名单: 阻止已知恶意IP地址的访问,允许信任的IP地址。
- User-Agent过滤: 检查请求的User-Agent字段,阻止非标准的爬虫请求。
- Session Management: 使用会话管理技术,识别和限制可疑的爬虫行为。
- Dynamic Content Delivery: 动态生成内容,使得爬虫难以抓取有用的信息。
- Monitoring and Logging: 实时监控和记录异常请求,及时发现和响应潜在威胁。
实现思路
- 流控(Flow Control):
- 流控用于限制某个资源的访问速率,防止系统过载。
- 通过设置每秒允许的最大请求数,当超过这个阈值时,Sentinel会阻止多余的请求,并返回相应的错误信息。
- 降级(Degrade):
- 降级用于在系统压力过大时自动降低服务的可用性,保护核心业务不受影响。
- 可以根据不同的策略(如RT、异常比例、异常数)来进行降级处理。
- 热点参数限流(Hotspot Parameter Flow Control):
- 热点参数限流用于针对特定参数进行限流,防止某些参数导致的服务过载。
- 全局异常处理器:
- 捕获并处理由Sentinel抛出的异常,返回友好的错误信息给客户端。
- 自定义异常处理器:
- 根据不同的异常类型(如
FlowException
和DegradeException
),返回具体的错误信息。
先启动Nacos服务器
我已经在本地启动了Nacos服务器。
你也可以从Nacos GitHub https://github.com/alibaba/nacos 下载并按照说明启动。
上传Sentinel规则到Nacos
在Nacos配置管理中创建两个配置文件:
- Data ID:
sentinel-demo-flow-rules
, Group:DEFAULT_GROUP
[
{
"resource": "/api/hello",
"limitApp": "default",
"grade": 1,
"count": 10,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
- Data ID:
sentinel-demo-degrade-rules
, Group:DEFAULT_GROUP
[]
代码实操
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/><!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>sentinel-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sentinel-demo</name>
<description>Demo project for Spring Boot with Sentinel</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Alibaba Sentinel Starter -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- Lombok for reducing boilerplate code -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot Test Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!-- Spring Boot Maven Plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port:8080
spring:
cloud:
sentinel:
transport:
dashboard:localhost:8080# 配置Sentinel控制台地址
datasource:
ds1:
nacos:
server-addr:localhost:8848# Nacos服务器地址
data-id:${spring.application.name}-flow-rules# 流控规则数据ID
group:DEFAULT_GROUP# 流控规则组名
rule-type:flow# 规则类型为流控规则
logging:
level:
root:INFO# 设置根日志级别为INFO
com.example.sentineldemo:DEBUG# 设置应用包的日志级别为DEBUG
logback-spring.xml
<!-- Logback日志配置文件 -->
<configuration>
<!-- 定义控制台输出器 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern><!-- 日志格式 -->
</encoder>
</appender>
<!-- 根日志记录器配置 -->
<root level="info">
<appender-ref ref="STDOUT"/><!-- 将日志输出到控制台 -->
</root>
</configuration>
flow-rules.json
“
放在
src/main/resources/sentinel/
[
{
"resource": "/api/hello", // 资源路径
"limitApp": "default", // 默认限流应用
"grade": 1, // QPS模式
"count": 10, // 每秒最大请求数
"strategy": 0, // 直接模式
"controlBehavior": 0, // 快速失败策略
"clusterMode": false // 非集群模式
}
]
- 定义了一个流控规则,限制
/api/hello
接口每秒最多允许 10 个请求。 - 如果超过这个阈值,Sentinel 会阻止多余的请求,并返回
"Too many requests, please try again later."
。
SentinelDemoApplication.java
package com.example.sentineldemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Spring Boot应用主类
*/
@SpringBootApplication
public class SentinelDemoApplication {
/**
* 应用程序入口点
*
* @param args 命令行参数
*/
public static void main(String[] args) {
SpringApplication.run(SentinelDemoApplication.class, args);
}
}
Sentinel配置类
package com.example.sentineldemo.config;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.List;
/**
* Sentinel配置类
*/
@Configuration
publicclass SentinelConfig {
/**
* 初始化Sentinel规则
*/
@PostConstruct
private void initRules() {
String serverAddr = "localhost"; // Nacos服务器地址
String groupId = "DEFAULT_GROUP"; // 规则组名
String dataId = "${spring.application.name}-flow-rules"; // 流控规则数据ID
// 从Nacos读取流控规则
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(serverAddr, groupId, dataId,
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
String degradeDataId = "${spring.application.name}-degrade-rules"; // 降级规则数据ID
// 从Nacos读取降级规则
ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource = new NacosDataSource<>(serverAddr, groupId, degradeDataId,
source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {}));
DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());
}
/**
* 自定义请求来源解析器
*
* @return RequestOriginParser实例
*/
@Bean
public RequestOriginParser requestOriginParser() {
return request -> request.getHeader("origin"); // 使用HTTP头中的origin字段作为请求来源
}
}
Controller
- 使用
@SentinelResource
注解来标识需要保护的方法。 - 当方法被调用时,Sentinel 会根据预先定义的规则进行检查。
package com.example.sentineldemo.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.example.sentineldemo.exception.BlockExceptionHandler;
import com.example.sentineldemo.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 控制器类,处理API请求
*/
@RestController
publicclass HelloController {
@Autowired
private HelloService helloService; // 注入服务层对象
/**
* 处理GET /api/hello请求
*
* @param name 请求参数,用户名
* @return 返回问候语
*/
@GetMapping("/api/hello")
@SentinelResource(value = "hello", blockHandlerClass = BlockExceptionHandler.class, blockHandler = "handleException")
public String sayHello(@RequestParam(required = false) String name) {
if (name == null || name.isEmpty()) {
name = "World"; // 如果未提供名字,默认为"World"
}
return helloService.getGreeting(name); // 调用服务层获取问候语
}
}
全局异常处理器
- 捕获并处理由 Sentinel 抛出的
BlockException
异常。 - 返回友好的错误信息给客户端。
package com.example.sentineldemo.exception;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* 全局异常处理器
*/
@ControllerAdvice
publicclass GlobalExceptionHandler {
/**
* 处理Sentinel阻塞异常
*
* @param ex 异常对象
* @return 返回错误信息和状态码
*/
@ExceptionHandler(BlockException.class)
public ResponseEntity<String> handleBlockException(BlockException ex) {
returnnew ResponseEntity<>("Blocked by Sentinel: " + ex.getClass().getSimpleName(), HttpStatus.TOO_MANY_REQUESTS);
}
}
Sentinel资源块处理异常处理器
package com.example.sentineldemo.exception;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
/**
* Sentinel资源块处理异常处理器
*/
publicclass BlockExceptionHandler {
/**
* 处理Sentinel资源块异常
*
* @param ex 异常对象
* @return 返回错误信息
*/
public static String handleException(BlockException ex) {
if (ex instanceof FlowException) {
return"Too many requests, please try again later."; // 流控异常处理
} elseif (ex instanceof DegradeException) {
return"Service is degraded, please try again later."; // 降级异常处理
}
return"Request blocked by Sentinel."; // 其他异常处理
}
}
服务层
package com.example.sentineldemo.service;
import org.springframework.stereotype.Service;
/**
* 服务层类,处理业务逻辑
*/
@Service
public class HelloService {
/**
* 获取问候语
*
* @param name 用户名
* @return 返回问候语
*/
public String getGreeting(String name) {
return "Hello, " + name + "!"; // 构造问候语
}
}