SpingBoot中使用Redis对接口进行限流的实现

目录
  • 实现的思路
    • 使用 Hash 存储接口的限流配置
    • 使用普通kv,存储api的请求次数
  • 使用SpringBoot实现
    • RedisKeys
    • ObjectRedisTemplate
    • RedisConfigration
    • RequestLimitConfig
    • RequestLimitInterceptor
    • Controller
    • WebMvcConfigration
  • 最后一些问题
    • 怎么灵活的配置
    • 针对指定的用户限流
    • Restful 接口的问题

一个基于Redis实现的接口限流方案,先说要实现的功能

  • 可以限制指定的接口,在一定时间内,只能被请求N次,超过次数就返回异常信息
  • 可以通过配置文件,或者管理后台,动态的修改限流配置

实现的思路

使用 Hash 存储接口的限流配置

request_limit_config    "/api2" : {"limit": 10, "time": 1, "timeUnit": "SECONDS"}

hash中的key就是请求的uri路径,value是一个对象。通过3个属性,描述限制策略

  • limit 最多请求次数
  • time 时间
  • timeUnit 时间单位

使用普通kv,存储api的请求次数

request_limit:/api  1

处理请求的时候,通过increment对该key进行 +1 操作,如果返回1,则表示是第一次请求,此时设置它的过期时间。为限制策略中定义时间限制信息。再通过命名的返回值,判断是否超出了限制。

increment 指令是线程安全的,不用担心并发的问题。

使用SpringBoot实现

创建SpringBoot工程,添加

spring-boot-starter-data-redis依赖,并且给出正确的配置。

这里不做工程的创建,配置,以及其他额外代码的演示,仅仅给出关键的代码。

RedisKeys

定义两个Key,限流用到的2个Key

public interface RedisKeys {
    /**
     * api的限制配置,hash key
     */
    String REQUEST_LIMIT_CONFIG = "request_limit_config";

    /**
     * api的请求的次数
     */
    String REQUEST_LIMIT = "request_limit";
}

ObjectRedisTemplate

为了提高hash value的序列化效率,自定义一个RedisTemplate的实现。使用jdk的序列化,而不是json。

import org.springframework.data.redis.core.RedisTemplate;

public class ObjectRedisTemplate extends RedisTemplate<String, Object> {

}

RedisConfigration

把自定义的ObjectRedisTemplate配置到IOC

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializer;

import io.springboot.jwt.redis.ObjectRedisTemplate;

@Configuration
public class RedisConfiguration {
    @Bean
    public ObjectRedisTemplate objectRedisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory) {

        ObjectRedisTemplate objectRedisTemplate = new ObjectRedisTemplate();
        objectRedisTemplate.setConnectionFactory(redisConnectionFactory);

        objectRedisTemplate.setKeySerializer(RedisSerializer.string());
        objectRedisTemplate.setValueSerializer(RedisSerializer.java());

        // hash的key使用String序列化
        objectRedisTemplate.setHashKeySerializer(RedisSerializer.string());
        // hash的value使用jdk的序列化
        objectRedisTemplate.setHashValueSerializer(RedisSerializer.java());
        return objectRedisTemplate;
    }
}

RequestLimitConfig

用于描述限制策略的对象。

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

public class RequestLimitConfig implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 1101875328323558092L;

    // 最大请求次数
    private long limit;
    // 时间
    private long time;
    // 时间单位
    private TimeUnit timeUnit;
    public RequestLimitConfig() {
        super();
    }
    public RequestLimitConfig(long limit, long time, TimeUnit timeUnit) {
        super();
        this.limit = limit;
        this.time = time;
        this.timeUnit = timeUnit;
    }
    public long getLimit() {
        return limit;
    }
    public void setLimit(long limit) {
        this.limit = limit;
    }
    public long getTime() {
        return time;
    }
    public void setTime(long time) {
        this.time = time;
    }
    public TimeUnit getTimeUnit() {
        return timeUnit;
    }
    public void setTimeUnit(TimeUnit timeUnit) {
        this.timeUnit = timeUnit;
    }
    @Override
    public String toString() {
        return "RequestLimitConfig [limit=" + limit + ", time=" + time + ", timeUnit=" + timeUnit + "]";
    }
}

RequestLimitInterceptor

通过拦截器,来完成限流的实现。

import java.nio.charset.StandardCharsets;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import io.springboot.jwt.redis.ObjectRedisTemplate;
import io.springboot.jwt.redis.RedisKeys;
import io.springboot.jwt.web.RequestLimitConfig;

public class RequestLimitInterceptor extends HandlerInterceptorAdapter {

    private static final Logger LOGGER = LoggerFactory.getLogger(RequestLimitInterceptor.class);

    @Autowired
    private ObjectRedisTemplate objectRedisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        /**
         * 获取到请求的URI
         */
        String contentPath = request.getContextPath();
        String uri = request.getRequestURI().toString();
        if (!StringUtils.isEmpty(contentPath) && !contentPath.equals("/")) {
            uri =  uri.substring(uri.indexOf(contentPath) + contentPath.length());
        }
        LOGGER.info("uri={}",  uri);

        /**
         * 尝试从hash中读取得到当前接口的限流配置
         */
        RequestLimitConfig requestLimitConfig = (RequestLimitConfig) this.objectRedisTemplate.opsForHash().get(RedisKeys.REQUEST_LIMIT_CONFIG, uri);
        if (requestLimitConfig == null) {
            LOGGER.info("该uri={}没有限流配置", uri);
            return true;
        }

        String limitKey = RedisKeys.REQUEST_LIMIT + ":" + uri;

        /**
         * 当前接口的访问次数 +1
         */
        long count = this.objectRedisTemplate.opsForValue().increment(limitKey);
        if (count == 1) {
            /**
             * 第一次请求,设置key的过期时间
             */
            this.objectRedisTemplate.expire(limitKey, requestLimitConfig.getTime(), requestLimitConfig.getTimeUnit());
            LOGGER.info("设置过期时间:time={}, timeUnit={}", requestLimitConfig.getTime(), requestLimitConfig.getTimeUnit());
        }

        LOGGER.info("请求限制。limit={}, count={}", requestLimitConfig.getLimit(), count);

        if (count > requestLimitConfig.getLimit()) {
            /**
             * 限定时间内,请求超出限制,响应客户端错误信息。
             */
            response.setContentType(MediaType.TEXT_PLAIN_VALUE);
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.getWriter().write("服务器繁忙,稍后再试");
            return false;
        }
        return true;
    }
}

Controller

一个用于测试的接口类

import java.util.Collections;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping
    public Object test () {
        return Collections.singletonMap("success", true);
    }
}

WebMvcConfigration

拦截器的配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import io.springboot.jwt.web.interceptor.RequestLimitInterceptor;

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this.requestLimitInterceptor())
            .addPathPatterns("/test");
    }

    @Bean
    public RequestLimitInterceptor requestLimitInterceptor() {
        return new RequestLimitInterceptor();
    }
}

通过@Test测试,初始化一个限流配置

@Autowired
private ObjectRedisTemplate objectRedisTemplate;

@Test
public void test () {
    // 3秒内,只能请求2次
    RequestLimitConfig requestLimitConfig = new RequestLimitConfig(2, 3, TimeUnit.SECONDS);
    // 限制的uri是 /test
    this.objectRedisTemplate.opsForHash().put(RedisKeys.REQUEST_LIMIT_CONFIG, "/test", requestLimitConfig);
}

使用浏览器演示

最后一些问题

怎么灵活的配置

都写到这个份儿上了,如果熟悉Redis以及客户端,我想提供一个“限流管理”接口的并不是难事儿。

针对指定的用户限流

这里演示的方法是,针对接口的限流。有时候,也有一些特殊的需求,需要“针对不同”的用户来做限流。打个比方。针对A用户,允许有他1分钟请求20次接口,针对B用户,允许他1分钟请求10次接口。 这个其实也简单,只需要修改一下上面的两个限制key,在key中添加用户的唯一标识(例如:ID)

request_limit_config    "/api2:{userId}" : {"limit": 10, "time": 1, "timeUnit": "SECONDS"}
request_limit:{userId}:/api  1

在拦截器中获取到用户的ID,加上用户ID进行检索和判断,就可以完成针对用户的限流。

Restful 接口的问题

@GetMapping("/user/{id}")  // restful的检索接口,往往把ID信息放在了URI中

这就会导致上面的代码有问题,因为这里采用的是根据URI来完成的限流操作。检索不同ID的用户,会导致URI不同。 解决办法我认为也很简单。那就不要使用URI,可以通过 自定义注解,方式,不同的接口,定义不同的唯一标识。在拦截器中获取到注解,读取到唯一的编码,代替原来的URI,即可。

到此这篇关于SpingBoot中使用Redis对接口进行限流的实现的文章就介绍到这了,更多相关SpingBoot Redis接口限流内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于Redis的限流器的实现(示例讲解)

    1 概述 系统中的接口通常都有限流,比如 70次/秒 ,如何保证我们的接口的调用次数在超过第三方接口限流的时候快速失败呢?这时候就需要限流器了.下面是笔者用redis实现限流器的流程图. 2 代码 /** * 获取限流权限 * @param key * @param millisecond 毫秒数 * @param limitCount 限流次数 * @return */ public static boolean getCurrentLimiting(String key, Long mill

  • Redis和Lua实现分布式限流器的方法详解

    主要是依靠 redis + lua 来实现限流器, 使用 lua 的原因是将多条命令合并在一起作为一个原子操作, 无需过多考虑并发. 计数器模式 原理 计数器算法是指在一段窗口时间内允许通过的固定数量的请求, 比如10次/秒, 500次/30秒. 如果设置的时间粒度越细, 那么限流会更平滑. 实现 所使用的 Lua 脚本 -- 计数器限流 -- 此处支持的最小单位时间是秒, 若将 expire 改成 pexpire 则可支持毫秒粒度. -- KEYS[1] string 限流的key -- AR

  • springboot+redis 实现分布式限流令牌桶的示例代码

    1.前言 网上找了很多redis分布式限流方案,要不就是太大,需要引入第三方jar,而且还无法正常运行,要不就是定时任务定时往key中放入数据,使用的时候调用,严重影响性能,所以着手自定义实现redis令牌桶. 只用到了spring-boot-starter-data-redis包,并且就几行代码. 2.环境准备 a.idea新建springboot项目,引入spring-data-redis包 b.编写令牌桶实现方法RedisLimitExcutor c.测试功能,创建全局拦截器,测试功能 3

  • 详解Redis实现限流的三种方式

    面对越来越多的高并发场景,限流显示的尤为重要. 当然,限流有许多种实现的方式,Redis具有很强大的功能,我用Redis实践了三种的实现方式,可以较为简单的实现其方式.Redis不仅仅是可以做限流,还可以做数据统计,附近的人等功能,这些可能会后续写到. 第一种:基于Redis的setnx的操作 我们在使用Redis的分布式锁的时候,大家都知道是依靠了setnx的指令,在CAS(Compare and swap)的操作的时候,同时给指定的key设置了过期实践(expire),我们在限流的主要目的就

  • redis限流的实际应用

    为什么要做限流 首先让我们先看一看系统架构设计中,为什么要做"限流". 旅游景点通常都会有最大的接待量,不可能无限制的放游客进入,比如故宫每天只卖八万张票,超过八万的游客,无法买票进入,因为如果超过八万人,景点的工作人员可能就忙不过来,过于拥挤的景点也会影响游客的体验和心情,并且还会有安全隐患:「只卖N张票,这就是一种限流的手段」. 软件架构中的服务限流也是类似,也是当系统资源不够的时候,已经不足以应对大量的请求,为了保证服务还能够正常运行,那么按照规则,「系统会把多余的请求直接拒绝掉

  • Springboot使用redis进行api防刷限流过程详解

    这篇文章主要介绍了Springboot使用redis进行api防刷限流过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 api限流的场景 限流的需求出现在许多常见的场景中 秒杀活动,有人使用软件恶意刷单抢货,需要限流防止机器参与活动 某api被各式各样系统广泛调用,严重消耗网络.内存等资源,需要合理限流 淘宝获取ip所在城市接口.微信公众号识别微信用户等开发接口,免费提供给用户时需要限流,更具有实时性和准确性的接口需要付费. api限流实

  • 详解基于redis实现的四种常见的限流策略

    目录 一.引言 二.固定时间窗口算法 三.滑动时间窗口算法 四.漏桶算法 五.令牌桶算法 一.引言 在web开发中功能是基石,除了功能以外运维和防护就是重头菜了.因为在网站运行期间可能会因为突然的访问量导致业务异常.也有可能遭受别人恶意攻击 所以我们的接口需要对流量进行限制.俗称的QPS也是对流量的一种描述 针对限流现在大多应该是令牌桶算法,因为它能保证更多的吞吐量.除了令牌桶算法还有他的前身漏桶算法和简单的计数算法 下面我们来看看这四种算法 二.固定时间窗口算法 固定时间窗口算法也可以叫做简单

  • SpingBoot中使用Redis对接口进行限流的实现

    目录 实现的思路 使用 Hash 存储接口的限流配置 使用普通kv,存储api的请求次数 使用SpringBoot实现 RedisKeys ObjectRedisTemplate RedisConfigration RequestLimitConfig RequestLimitInterceptor Controller WebMvcConfigration 最后一些问题 怎么灵活的配置 针对指定的用户限流 Restful 接口的问题 一个基于Redis实现的接口限流方案,先说要实现的功能 可以

  • SpringBoot中使用Redis对接口进行限流的实现

    目录 实现的思路 使用 Hash 存储接口的限流配置 使用普通kv,存储api的请求次数 使用SpringBoot实现 RedisKeys ObjectRedisTemplate RedisConfigration RequestLimitConfig RequestLimitInterceptor Controller WebMvcConfigration 最后一些问题 怎么灵活的配置 针对指定的用户限流 Restful 接口的问题 一个基于Redis实现的接口限流方案,先说要实现的功能 可以

  • 解决ASP.NET Core中使用漏桶算法限流的问题

    目录 算法原理 算法实现 进程内即内存漏桶算法 基于Redis的漏桶算法 应用算法 1.安装Nuget包 2.使用中间件 漏桶算法是限流的四大主流算法之一,其应用场景各种资料中介绍的不多,一般都是说应用在网络流量控制中.这里举两个例子: 1.目前家庭上网都会限制一个固定的带宽,比如100M.200M等,一栋楼有很多的用户,那么运营商怎么保证某些用户没有使用过多的带宽,从而影响到别人呢?这时就可以使用漏桶算法,限制每个用户访问网络的最大带宽,当然实际会比这复杂很多. 2.有一个祖传接口,当时写的时

  • 基于 Redis 实现接口限流的方式

    目录 基于 Redis 实现接口限流 1. 准备工作 2. 限流注解 3. 定制 RedisTemplate 4. 开发 Lua 脚本 5. 注解解析 6. 接口测试 7. 全局异常处理 总结 基于 Redis 实现接口限流 Redis 除了做缓存,还能干很多很多事情:分布式锁.限流.处理请求接口幂等性...太多太多了- 今天想和小伙伴们聊聊用 Redis 处理接口限流,这也是最近的 TienChin 项目涉及到这个知识点了,我就拎出来和大家聊聊这个话题,后面视频也会讲. 1. 准备工作 首先我

  • 使用SpringBoot + Redis 实现接口限流的方式

    目录 配置 限流注解 定制 RedisTemplate Lua 脚本 注解解析 接口测试 全局异常处理 Redis 除了做缓存,还能干很多很多事情:分布式锁.限流.处理请求接口幂等性...太多太多了 配置 首先我们创建一个 Spring Boot 工程,引入 Web 和 Redis 依赖,同时考虑到接口限流一般是通过注解来标记,而注解是通过 AOP 来解析的,所以我们还需要加上 AOP 的依赖,最终的依赖如下: <dependency> <groupId>org.springfra

  • 基于redis+lua进行限流的方法

    1,首先我们redis有很多限流的算法(比如:令牌桶,计数器,时间窗口)等,但是都有一定的缺点,令牌桶在单项目中相对来说比较稳定,但是在分布式集群里面缺显的不那么友好,这时候,在分布式里面进行限流的话,我们则可以使用redis+lua脚本进行限流,能抗住亿级并发 2,下面说说lua+redis进行限流的做法开发环境:idea+redis+lua第一:打开idea的插件市场,然后搜索lua,点击右边的安装,然后安装好了,重启即可 第二:写一个自定义限流注解 package com.sport.sp

  • SpringBoot+Redis+Lua分布式限流的实现

    Redis支持LUA脚本的主要优势 LUA脚本的融合将使Redis数据库产生更多的使用场景,迸发更多新的优势: 高效性:减少网络开销及时延,多次redis服务器网络请求的操作,使用LUA脚本可以用一个请求完成 数据可靠性:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入. 复用性:LUA脚本执行后会永久存储在Redis服务器端,其他客户端可以直接复用 可嵌入性:可嵌入JAVA,C#等多种编程语言,支持不同操作系统跨平台交互 简单强大:小巧轻便,资源占用率低,支持过程化和对象化的编程

  • SpringBoot使用自定义注解+AOP+Redis实现接口限流的实例代码

    目录 为什么要限流 限流背景 实现限流 1.引入依赖 2.自定义限流注解 3.限流切面 4.写一个简单的接口进行测试 5.全局异常拦截 6.接口测试 为什么要限流 系统在设计的时候,我们会有一个系统的预估容量,长时间超过系统能承受的TPS/QPS阈值,系统有可能会被压垮,最终导致整个服务不可用.为了避免这种情况,我们就需要对接口请求进行限流. 所以,我们可以通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统或避免不必要的资源浪费,一旦达到限制速率则可以拒绝服务.排队或等待

  • Go+Redis实现常见限流算法的示例代码

    目录 固定窗口 滑动窗口 hash实现 list实现 漏桶算法 令牌桶 滑动日志 总结 限流是项目中经常需要使用到的一种工具,一般用于限制用户的请求的频率,也可以避免瞬间流量过大导致系统崩溃,或者稳定消息处理速率.并且有时候我们还需要使用到分布式限流,常见的实现方式是使用Redis作为中心存储. 这个文章主要是使用Go+Redis实现常见的限流算法,如果需要了解每种限流算法的原理可以阅读文章 Go实现常见的限流算法 下面的代码使用到了go-redis客户端 固定窗口 使用Redis实现固定窗口比

随机推荐