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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 使用过滤器配合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;
        }
    }
}
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
6月前
|
存储 算法 NoSQL
百度面试:如何用Redis实现限流?
百度面试:如何用Redis实现限流?
77 2
|
2月前
|
NoSQL Redis API
限流+共享session redis实现
【10月更文挑战第7天】
41 0
|
5月前
|
NoSQL Linux Redis
Redis性能优化问题之想确认Redis延迟变大是否因为fork耗时导致的,如何解决
Redis性能优化问题之想确认Redis延迟变大是否因为fork耗时导致的,如何解决
|
5月前
|
存储 缓存 NoSQL
高并发架构设计三大利器:缓存、限流和降级问题之Redis用于搭建分布式缓存集群问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之Redis用于搭建分布式缓存集群问题如何解决
102 1
|
6月前
|
缓存 NoSQL Redis
redis管道操作(节省网络IO开销)
pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此后的响应中得到信息;也就是pipeline并不是表达“所有command都一起成功”的语义,管道中前面命令失败,后面命令不会有影响,继续执行。
54 1
|
6月前
|
NoSQL Java Redis
如何在 Java 中操作这些 Redis 数据结构的基本方法
如何在 Java 中操作这些 Redis 数据结构的基本方法
41 2
|
6月前
|
NoSQL 数据管理 关系型数据库
数据管理DMS操作报错合集之控制台查看Redis时出现乱码是什么导致的
数据管理DMS(Data Management Service)是阿里云提供的数据库管理和运维服务,它支持多种数据库类型,包括RDS、PolarDB、MongoDB等。在使用DMS进行数据库操作时,可能会遇到各种报错情况。以下是一些常见的DMS操作报错及其可能的原因与解决措施的合集。
100 2
|
6月前
|
DataWorks NoSQL Java
DataWorks操作报错合集之数据集成使用公共数据集成资源组写入到redis数据源(使用的是VPC连接),提示以下错误:request action:[InnerVpcGrantVpcInstanceAccessToApp], message:[InvalidInstanceId.怎么解决
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
|
6月前
|
存储 NoSQL Go
轻松上手,使用Go语言操作Redis数据库
轻松上手,使用Go语言操作Redis数据库
|
6月前
|
NoSQL Redis
加速 Redis 操作:掌握管道技术提升性能与效率
加速 Redis 操作:掌握管道技术提升性能与效率