华为面试:如何防止接口被刷百万QPS?

28 阅读4分钟

沉默是金,总会发光

大家好,我是沉默

面试官:接口被恶意狂刷,怎么办?
:这个……没搞过(每天 CRUD,真没搞过)
面试官:那你现在设计一个?
:巴拉巴拉……(自己都不信的胡扯)
面试官:(明显不耐烦)那我们换个话题吧。

说实话,那一刻我就知道——
这场面试,已经凉了一半。

不是我不会写代码,
而是我从没系统想过「接口防刷」这种问题

但后来我才发现一件事
接口防刷,其实一点都不复杂,甚至可以「一个注解搞定」

今天这篇,我不跟你扯高深架构,
就用一个能在面试里讲清楚、在项目里用得上的防刷方案
让你下次被问到,至少能稳稳说出 123

**-**01-

接口防刷,本质在防什么?

很多人一听“防刷”,脑子里就冒出:

  • 风控
  • 限流算法
  • 网关
  • 黑名单
  • 滑块验证

其实对 80% 的业务系统来说,根本用不上这么重。

我们今天解决的是最常见的一类问题:

同一个用户 / 同一个接口,在短时间内被疯狂请求

所以防刷的核心只有一句话:

限制「同一个人」在「同一个接口」上的访问次数

图片

- 02-

一个注解搞定防刷

先别急着看代码,先把思路在脑子里跑通

整体设计思路(面试版)

  1. 用自定义注解标记哪些接口需要防刷
  2. 用拦截器统一拦截请求
  3. 用 Redis记录访问次数
  4. 超过阈值,直接拒绝

一句话就是:

注解定义规则,拦截器执行规则,Redis 负责记账

第一步:自定义一个防刷注解

我们先定义一个注解,用来描述「防刷规则」。

@Retention(RUNTIME)
@Target(METHOD)
public @interface AccessLimit {

    // 允许访问的最大次数
    intmaxCount();

    // 是否需要登录
    boolean needLogin()default false;
}

这个注解只干一件事:

把“防刷规则”写在方法上,而不是写死在代码里

用起来会非常优雅👇

@AccessLimit(maxCount = 5, needLogin = true)
@RequestMapping("/fangshua")
@ResponseBody
public Object fangshua() {
    return"请求成功";
}

第二步:Redis 负责「记账」

防刷一定绕不开 Redis。

我们需要它做的事情很简单:

  • key:请求标识
  • value:访问次数
  • TTL:有效期

Redis Key 设计(重点,面试加分)

URI + userId + yyyyMMdd

也就是说:

同一个用户,在同一天,对同一个接口的访问次数

第三步:拦截器里干“脏活累活”

所有真正的防刷逻辑,都在拦截器里。

核心逻辑拆解(一定要能讲)

  1. 判断当前请求是否是 Controller 方法
  2. 判断方法上有没有 @AccessLimit
  3. 组装 Redis key
  4. 从 Redis 取访问次数
  5. 超限 → 直接拦截

核心代码(精简但完整)

@Componentpublic class FangshuaInterceptor extends HandlerInterceptorAdapter {    @Resource    private RedisTemplate<String, Object> redisTemplate;    @Override    public boolean preHandle(HttpServletRequest request,                             HttpServletResponse response,                             Object handler) throws Exception {        if (!(handler instanceof HandlerMethod)) {            return true;        }        HandlerMethod hm = (HandlerMethod) handler;        AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);        if (accessLimit == null) {            return true;        }        int maxCount = accessLimit.maxCount();        boolean needLogin = accessLimit.needLogin();        String key = request.getRequestURI();        if (needLogin) {            Long userId = 101L; // 示例            String day = new SimpleDateFormat("yyyyMMdd").format(new Date());            key = key":" + userId + ":" + day;        }        Integer count = (Integer) redisTemplate.opsForValue().get(key);        if (count == null) {            redisTemplate.opsForValue().set(key11, TimeUnit.DAYS);        } else if (count < maxCount) {            redisTemplate.opsForValue().increment(key);        } else {            response.getWriter().write("访问次数已达上限!");            return false;        }        return true;    }}

注册拦截器(别忘了这一步)

@Configuration
public class WebConfigextends WebMvcConfigurerAdapter {

@Autowired
private FangshuaInterceptor interceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor);
    }
}

到这一步,防刷链路已经完整闭环了

效果验证

访问接口:

http://localhost:8080/fangshua
  • 前 5 次:请求成功
  • 第 6 次:访问次数已达上限

一个注解,防刷生效。

图片

- 03-

如果面试官继续追问,你可以这么答

Q1:这种方案适合什么场景?

适合中小系统、管理后台、业务接口级防刷
不适合超高并发网关级限流

Q2:有什么优化空间?

  • key 增加 IP 维度
  • 使用 Redis Lua 保证原子性
  • 与网关限流形成双层防护

Q3:为什么不用 AOP?

拦截器更贴近请求链路,
AOP 更适合业务横切,不适合请求级控制

图片

**-**04-

总结

接口防刷不是“高深架构”,而是“基础设计能力”

你不需要一上来就说:

  • Sentinel
  • Gateway
  • 滑块验证码

**
**

你只要能把:

  • 注解
  • 拦截器
  • Redis
  • Key 设计

这条链路讲清楚,
面试官就已经在心里给你加分了。

图片

**-**05-

粉丝福利

点点关注,送你互联网大厂面试题库,如果你正在找工作,又或者刚准备换工作。可以仔细阅读一下,或许对你有所帮助!

image.png

image.pngimage.png