直接在接口入口对集合类参数(如 List、数组、分页 size)设置数量上限,是防止内存耗尽型 DoS 最直接有效的手段。
先说结论:必须在服务端代码中显式校验集合长度,不能依赖前端限制,同时配合系统级资源配额作为兜底。
- 先判断:排查所有接收 List、数组、Set 或分页 size 参数的接口,确认是否有长度限制逻辑。
- 优先做:在 DTO 对象中使用注解限制集合大小,配合全局异常处理器统一拒绝超限请求。
- 再验证:通过 curl 构造超大参数请求测试服务响应,确认返回 400 错误而非进入业务逻辑。
- 最后兜底:配置 systemd 内存限制,防止极端情况下单个进程耗尽整机资源(注意会导致进程重启)。
核心解决方案:代码层全局校验
简单的 if 判断容易遗漏,建议利用 Spring Boot 的 Validation 组件实现全局拦截。这样无需在每个 Controller 方法中重复编写校验逻辑。
1. 定义 DTO 并添加 @Size 约束:
在接收参数的 DTO 类中,直接对集合字段添加 @Size 注解,指定最大元素数量。
public class BatchRequestDTO {
@NotNull
@Size(max = 1000, message = "请求参数数量不能超过 1000")
private List<Long> ids;
// getter setter
}2. Controller 层启用校验:
在 Controller 方法参数前添加 @Valid 或 @Validated 注解,触发自动校验。
@PostMapping("/batch/get")
public Result batchGet(@Valid @RequestBody BatchRequestDTO request) {
// 业务逻辑,校验失败不会执行到这里
}3. 全局异常处理:
使用 @ControllerAdvice 捕获校验失败的异常,返回统一的错误响应,避免暴露堆栈信息。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleValidationException(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldError().getDefaultMessage();
return Result.error(400, msg);
}
}系统级兜底配置
代码校验是第一位的,但为了防止代码失效或未知漏洞导致内存泄漏,可以在操作系统层面限制服务进程的最大内存。注意:systemd 的内存限制触发后会导致进程被 OOM Kill(强制杀死),服务会重启,而不是返回友好的错误提示。因此这仅作为最后一道防线。
配置步骤:
1. 编辑服务配置:
systemctl edit your-service-name2. 输入以下配置内容(覆盖或追加):
[Service]
MemoryMax=512M
CPUQuota=30%3. 重载配置并重启服务:
systemctl daemon-reload
systemctl restart your-service-name4. 验证限制是否生效:
systemctl show your-service-name | grep MemoryMax验证与压测
配置完成后,必须通过实际请求验证校验逻辑是否生效,以及系统限制是否起作用。
1. 验证代码层校验(预期返回 400):
使用 curl 构造超过 1000 个元素的 JSON 数组。
# 生成包含 1001 个 ID 的 JSON payload
PAYLOAD='{"ids":['$(seq -s, 1 1001 | sed 's/[0-9]*/&/g')']}'
# 发送请求
curl -X POST http://localhost:8080/batch/get \
-H "Content-Type: application/json" \
-d "$PAYLOAD"观察响应状态码是否为 400, body 中是否包含"请求参数数量不能超过 1000"的提示。
2. 验证系统级限制(预期进程重启):
此步骤有风险,建议在测试环境进行。通过压测工具(如 JMeter)构造大量并发或超大内存占用请求,观察服务状态。
# 查看服务是否因内存超限被重启
journalctl -u your-service-name -n 50 | grep -i "oom\|killed"常见坑与风险
- 仅依赖前端校验:前端限制容易被绕过,必须在服务端重复校验。
- 忽略嵌套结构:有时集合中的元素本身也是复杂对象或集合,需递归考虑深度和大小,防止嵌套对象耗尽内存。
- 数据库层压力:即使应用层限制了数量,也要确保数据库查询语句不会因为参数过多导致执行计划变差或锁竞争,建议在 SQL 层也做 IN 查询数量限制。
- 序列化开销:超大 JSON 或二进制数据的解析过程本身就会消耗大量 CPU 和内存,需在接收流式数据前就做长度预判(如配置 Tomcat maxPostSize)。
- systemd 副作用:再次强调,MemoryMax 触发的是 SIGKILL,会导致服务中断,不要将其作为主要的业务校验手段。