使用过滤器配合redis,完成限流操作

简介: 使用过滤器配合redis,完成限流操作

限流

创建filter,使用redis实现,且执行顺序在跨域后,防止浪费资源

_EZ{NQSIL388$33DHN6M)08.png

`328@P_8BV2OH95HO~X{GGA.png

实现逻辑

  1. 在过滤器中,先获取请求的IP地址。
  2. 调用tryCount方法,检查该IP是否已经被限制访问。如果被限制访问,则返回false。
  3. 如果未被限制访问,调用limitPeriodCheck方法进行周期限制检查。
  4. 在limitPeriodCheck方法中,使用synchronized关键字对IP进行同步操作,保证多线程环境下的数据一致性。
  5. 判断该IP在周期内的访问次数是否超过阈值。如果超过阈值,则将该IP加入到限制访问列表中,并返回false。
  6. 如果未超过阈值,则将周期内的访问次数加1,并返回true。
  7. 如果tryCount方法返回true,则继续执行过滤器链,否则返回一个拒绝访问的响应。

需要注意的是,在访问次数超过阈值后,将该IP加入到限制访问列表中,并设置了一个30秒的过期时间。在这个过期时间内,该IP将无法继续访问接口,这样就完成了限流操作。

实现

看代码吧比较简单

package com.jinze.filter;
import com.jinze.constant.Const;
import com.jinze.domain.RestBean;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
 * @author jinze
 * @version 1.0
 * @description: 限制用户访问同一接口
 * @date 2023/8/18 9:18
 */
@Component
@Order(Const.ORDER_Flow)
public class FlowLimitFilter extends HttpFilter {
    @Resource
    StringRedisTemplate stringRedisTemplate;
    /**
     * IP访问频率限制过滤器
     */
    @Override
    protected void doFilter(HttpServletRequest request,
                            HttpServletResponse response,
                            FilterChain chain) throws IOException, ServletException {
        // 获取请求来源的IP地址
        String ip = request.getRemoteAddr();
        // 判断该IP的访问次数是否超过限制
        if (tryCount(ip)){
            // 没有超过限制,继续处理请求
            chain.doFilter(request,response);
        }else {
            // 超过限制,返回提示信息
            this.writeBlockMessage(response);
        }
    }
    /**
     * 为响应编写拦截内容,提示用户操作频繁
     *
     * @param response 响应
     * @throws IOException 可能的异常
     */
    private void writeBlockMessage(HttpServletResponse response) throws IOException {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(RestBean.forbidden("操作频繁,请稍后再试").toJsonString());
    }
    /**
     * 检查同一IP是否达到访问上限
     *
     * @param ip 用户IP地址
     * @return 是否达到上限
     */
    private boolean tryCount(String ip) {
        // 使用 synchronized 将代码块加锁,确保同一时间只有一个线程进入
        synchronized (ip.intern()) {
            // 如果 Redis 中已存在指定键(Const.FLOW_LIMIT_BLOCK + ip),表示该IP已被限制访问
            if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(Const.FLOW_LIMIT_BLOCK + ip))) {
                return false;
            }
            // 进一步检查同一IP在限定时间内请求的次数是否超过上限
            return this.limitPeriodCheck(ip);
        }
    }
    /**
     * 检查同一IP在限定时间内请求的次数是否超过上限
     *
     * @param ip 用户IP地址
     * @return 是否达到上限
     */
    private boolean limitPeriodCheck(String ip) {
        // 使用 synchronized 将代码块加锁,确保同一时间只有一个线程进入
        synchronized (ip.intern()) {
            // 如果 Redis 中已存在指定键(Const.FLOW_LIMIT_COUNTER + ip),表示该IP已有请求记录
            if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(Const.FLOW_LIMIT_COUNTER + ip))) {
                // 将计数器加1,如果不存在默认为0
                Long increment = Optional.ofNullable(stringRedisTemplate.opsForValue()
                                .increment(Const.FLOW_LIMIT_COUNTER + ip))
                        .orElse(0L);
                // 如果请求次数大于10,将该IP加入限制列表,并返回 false
                if (increment > 10) {
                    // 设置限制键(Const.FLOW_LIMIT_BLOCK + ip)的值为空字符串,并设置过期时间为30秒
                    stringRedisTemplate.opsForValue()
                            .set(Const.FLOW_LIMIT_BLOCK + ip, "", 30, TimeUnit.SECONDS);
                    return false;
                }
            }
            // 请求次数未达到上限,返回 true
            return true;
        }
    }
}
目录
相关文章
|
存储 算法 NoSQL
百度面试:如何用Redis实现限流?
百度面试:如何用Redis实现限流?
453 2
|
NoSQL 算法 Java
Java Redis多限流
通过本文的介绍,我们详细讲解了如何在Java中使用Redis实现三种不同的限流策略:固定窗口限流、滑动窗口限流和令牌桶算法。每种限流策略都有其适用的场景和特点,根据具体需求选择合适的限流策略可以有效保护系统资源和提高服务的稳定性。
675 18
|
NoSQL Linux Redis
Redis性能优化问题之想确认Redis延迟变大是否因为fork耗时导致的,如何解决
Redis性能优化问题之想确认Redis延迟变大是否因为fork耗时导致的,如何解决
|
NoSQL Redis API
限流+共享session redis实现
【10月更文挑战第7天】
269 0
|
存储 缓存 NoSQL
高并发架构设计三大利器:缓存、限流和降级问题之Redis用于搭建分布式缓存集群问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之Redis用于搭建分布式缓存集群问题如何解决
429 1
|
缓存 NoSQL Redis
redis管道操作(节省网络IO开销)
pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此后的响应中得到信息;也就是pipeline并不是表达“所有command都一起成功”的语义,管道中前面命令失败,后面命令不会有影响,继续执行。
300 1
|
NoSQL Java Redis
如何在 Java 中操作这些 Redis 数据结构的基本方法
如何在 Java 中操作这些 Redis 数据结构的基本方法
265 2
|
NoSQL 数据管理 关系型数据库
数据管理DMS操作报错合集之控制台查看Redis时出现乱码是什么导致的
数据管理DMS(Data Management Service)是阿里云提供的数据库管理和运维服务,它支持多种数据库类型,包括RDS、PolarDB、MongoDB等。在使用DMS进行数据库操作时,可能会遇到各种报错情况。以下是一些常见的DMS操作报错及其可能的原因与解决措施的合集。
328 2
|
存储 NoSQL Go
轻松上手,使用Go语言操作Redis数据库
轻松上手,使用Go语言操作Redis数据库
314 2
|
NoSQL Redis
加速 Redis 操作:掌握管道技术提升性能与效率
加速 Redis 操作:掌握管道技术提升性能与效率
293 2

热门文章

最新文章