SpringBoot與Sentinel整合,解決異常爬蟲請(qǐng)求問題
Sentinel 是阿里巴巴開源的一款面向分布式服務(wù)架構(gòu)的輕量級(jí)高可用流量控制組件,主要用于流量控制、熔斷降級(jí)和系統(tǒng)負(fù)載保護(hù)。 雖然 Sentinel 主要用于微服務(wù)場(chǎng)景下的流量管理和故障隔離,但也可以通過一些策略和配置來輔助防御 DDoS 攻擊和異常爬蟲請(qǐng)求。
DDoS攻擊
DDoS(Distributed Denial of Service)是一種惡意攻擊手段,攻擊者通過控制大量計(jì)算機(jī)設(shè)備(如僵尸網(wǎng)絡(luò)),向目標(biāo)服務(wù)器發(fā)送大量的數(shù)據(jù)包或請(qǐng)求,從而耗盡服務(wù)器的帶寬、CPU資源或其他系統(tǒng)資源,導(dǎo)致合法用戶無法正常訪問服務(wù)。
常見類型:
- Volume-based Attacks (體積型攻擊):
- 例如ICMP Flood、UDP Flood。
- 攻擊者發(fā)送大量無用的數(shù)據(jù)包,占用帶寬。
- Protocol Attacks (協(xié)議型攻擊):
- 例如SYN Flood、ACK Flood。
- 攻擊者利用TCP/IP協(xié)議漏洞,發(fā)送特定的數(shù)據(jù)包使服務(wù)器崩潰。
- Application Layer Attacks (應(yīng)用層攻擊):
- 例如HTTP Flood、Slowloris。
- 攻擊者模擬真實(shí)用戶的行為,發(fā)送大量的HTTP請(qǐng)求,消耗服務(wù)器的應(yīng)用層資源。
防御措施:
- 使用CDN: 內(nèi)容分發(fā)網(wǎng)絡(luò)可以幫助分散流量,減輕單個(gè)服務(wù)器的壓力。
- 負(fù)載均衡: 分散請(qǐng)求到多個(gè)服務(wù)器上,提高系統(tǒng)的可用性。
- 防火墻和入侵檢測(cè)系統(tǒng): 防止非法流量進(jìn)入服務(wù)器。
- Rate Limiting (限流): 控制每個(gè)IP地址或來源的請(qǐng)求速率,防止過載。
- Traffic Shaping (流量整形): 調(diào)整進(jìn)出網(wǎng)絡(luò)的數(shù)據(jù)包傳輸速率,優(yōu)化流量分配。
- Anycast IP Addressing: 使用多條路徑將流量引導(dǎo)至最近的健康節(jié)點(diǎn),提高冗余性和抗攻擊能力。
異常爬蟲請(qǐng)求
異常爬蟲是指那些不符合正常爬蟲行為規(guī)范的自動(dòng)化程序,它們可能會(huì)對(duì)網(wǎng)站造成負(fù)擔(dān),甚至破壞網(wǎng)站的正常運(yùn)行。這些爬蟲可能用于抓取敏感信息、進(jìn)行競(jìng)爭(zhēng)情報(bào)收集、參與SEO欺詐等活動(dòng)。
特點(diǎn):
- 高頻率請(qǐng)求: 在短時(shí)間內(nèi)發(fā)送大量請(qǐng)求,可能導(dǎo)致服務(wù)器過載。
- 不遵循robots.txt: 忽略網(wǎng)站的爬蟲協(xié)議文件,訪問受保護(hù)的內(nèi)容。
- 偽裝成普通用戶: 使用偽造的User-Agent字符串,難以識(shí)別。
- 頻繁更改IP: 使用代理或VPN頻繁更換IP地址,增加追蹤難度。
防御措施:
- 設(shè)置Robots.txt: 明確告知爬蟲哪些內(nèi)容可以抓取,哪些不可以。
- Rate Limiting (限流): 限制每個(gè)IP地址或來源的請(qǐng)求速率,防止濫用。
- CAPTCHA (驗(yàn)證碼): 在關(guān)鍵操作前要求用戶提供驗(yàn)證碼,區(qū)分人機(jī)。
- IP黑名單/白名單: 阻止已知惡意IP地址的訪問,允許信任的IP地址。
- User-Agent過濾: 檢查請(qǐng)求的User-Agent字段,阻止非標(biāo)準(zhǔn)的爬蟲請(qǐng)求。
- Session Management: 使用會(huì)話管理技術(shù),識(shí)別和限制可疑的爬蟲行為。
- Dynamic Content Delivery: 動(dòng)態(tài)生成內(nèi)容,使得爬蟲難以抓取有用的信息。
- Monitoring and Logging: 實(shí)時(shí)監(jiān)控和記錄異常請(qǐng)求,及時(shí)發(fā)現(xiàn)和響應(yīng)潛在威脅。
實(shí)現(xiàn)思路
- 流控(Flow Control):
- 流控用于限制某個(gè)資源的訪問速率,防止系統(tǒng)過載。
- 通過設(shè)置每秒允許的最大請(qǐng)求數(shù),當(dāng)超過這個(gè)閾值時(shí),Sentinel會(huì)阻止多余的請(qǐng)求,并返回相應(yīng)的錯(cuò)誤信息。
- 降級(jí)(Degrade):
- 降級(jí)用于在系統(tǒng)壓力過大時(shí)自動(dòng)降低服務(wù)的可用性,保護(hù)核心業(yè)務(wù)不受影響。
- 可以根據(jù)不同的策略(如RT、異常比例、異常數(shù))來進(jìn)行降級(jí)處理。
- 熱點(diǎn)參數(shù)限流(Hotspot Parameter Flow Control):
- 熱點(diǎn)參數(shù)限流用于針對(duì)特定參數(shù)進(jìn)行限流,防止某些參數(shù)導(dǎo)致的服務(wù)過載。
- 全局異常處理器:
- 捕獲并處理由Sentinel拋出的異常,返回友好的錯(cuò)誤信息給客戶端。
- 自定義異常處理器:
- 根據(jù)不同的異常類型(如
FlowException
和DegradeException
),返回具體的錯(cuò)誤信息。
先啟動(dòng)Nacos服務(wù)器
我已經(jīng)在本地啟動(dòng)了Nacos服務(wù)器。
你也可以從Nacos GitHub https://github.com/alibaba/nacos 下載并按照說明啟動(dòng)。
上傳Sentinel規(guī)則到Nacos
在Nacos配置管理中創(chuàng)建兩個(gè)配置文件:
- 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
[]
代碼實(shí)操
<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控制臺(tái)地址
datasource:
ds1:
nacos:
server-addr:localhost:8848# Nacos服務(wù)器地址
data-id:${spring.application.name}-flow-rules# 流控規(guī)則數(shù)據(jù)ID
group:DEFAULT_GROUP# 流控規(guī)則組名
rule-type:flow# 規(guī)則類型為流控規(guī)則
logging:
level:
root:INFO# 設(shè)置根日志級(jí)別為INFO
com.example.sentineldemo:DEBUG# 設(shè)置應(yīng)用包的日志級(jí)別為DEBUG
logback-spring.xml
<!-- Logback日志配置文件 -->
<configuration>
<!-- 定義控制臺(tái)輸出器 -->
<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"/><!-- 將日志輸出到控制臺(tái) -->
</root>
</configuration>
flow-rules.json
“
放在
src/main/resources/sentinel/
[
{
"resource": "/api/hello", // 資源路徑
"limitApp": "default", // 默認(rèn)限流應(yīng)用
"grade": 1, // QPS模式
"count": 10, // 每秒最大請(qǐng)求數(shù)
"strategy": 0, // 直接模式
"controlBehavior": 0, // 快速失敗策略
"clusterMode": false // 非集群模式
}
]
- 定義了一個(gè)流控規(guī)則,限制
/api/hello
接口每秒最多允許 10 個(gè)請(qǐng)求。 - 如果超過這個(gè)閾值,Sentinel 會(huì)阻止多余的請(qǐng)求,并返回
"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應(yīng)用主類
*/
@SpringBootApplication
public class SentinelDemoApplication {
/**
* 應(yīng)用程序入口點(diǎn)
*
* @param args 命令行參數(shù)
*/
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規(guī)則
*/
@PostConstruct
private void initRules() {
String serverAddr = "localhost"; // Nacos服務(wù)器地址
String groupId = "DEFAULT_GROUP"; // 規(guī)則組名
String dataId = "${spring.application.name}-flow-rules"; // 流控規(guī)則數(shù)據(jù)ID
// 從Nacos讀取流控規(guī)則
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"; // 降級(jí)規(guī)則數(shù)據(jù)ID
// 從Nacos讀取降級(jí)規(guī)則
ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource = new NacosDataSource<>(serverAddr, groupId, degradeDataId,
source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {}));
DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());
}
/**
* 自定義請(qǐng)求來源解析器
*
* @return RequestOriginParser實(shí)例
*/
@Bean
public RequestOriginParser requestOriginParser() {
return request -> request.getHeader("origin"); // 使用HTTP頭中的origin字段作為請(qǐng)求來源
}
}
Controller
- 使用
@SentinelResource
注解來標(biāo)識(shí)需要保護(hù)的方法。 - 當(dāng)方法被調(diào)用時(shí),Sentinel 會(huì)根據(jù)預(yù)先定義的規(guī)則進(jìn)行檢查。
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請(qǐng)求
*/
@RestController
publicclass HelloController {
@Autowired
private HelloService helloService; // 注入服務(wù)層對(duì)象
/**
* 處理GET /api/hello請(qǐng)求
*
* @param name 請(qǐng)求參數(shù),用戶名
* @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"; // 如果未提供名字,默認(rèn)為"World"
}
return helloService.getGreeting(name); // 調(diào)用服務(wù)層獲取問候語
}
}
全局異常處理器
- 捕獲并處理由 Sentinel 拋出的
BlockException
異常。 - 返回友好的錯(cuò)誤信息給客戶端。
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 異常對(duì)象
* @return 返回錯(cuò)誤信息和狀態(tài)碼
*/
@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 異常對(duì)象
* @return 返回錯(cuò)誤信息
*/
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."; // 降級(jí)異常處理
}
return"Request blocked by Sentinel."; // 其他異常處理
}
}
服務(wù)層
package com.example.sentineldemo.service;
import org.springframework.stereotype.Service;
/**
* 服務(wù)層類,處理業(yè)務(wù)邏輯
*/
@Service
public class HelloService {
/**
* 獲取問候語
*
* @param name 用戶名
* @return 返回問候語
*/
public String getGreeting(String name) {
return "Hello, " + name + "!"; // 構(gòu)造問候語
}
}