您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

在SpingBoot中使用Redis对接口进行限流

时间:2020-08-10 12:54:37  来源:  作者:

一个基于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);
}

使用浏览器演示

在SpingBoot中使用Redis对接口进行限流

 

最后一些问题

怎么灵活的配置

都写到这个份儿上了,如果熟悉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,即可。


首发:https://springboot.io/t/topic/2383



Tags:接口限流   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
Sentinel是阿里巴巴开源的限流器熔断器,并且带有可视化操作界面。在日常开发中,限流功能时常被使用,用于对某些接口进行限流熔断,譬如限制单位时间内接口访问次数;或者按照某种规...【详细内容】
2021-04-30  Tags: 接口限流  点击:(251)  评论:(0)  加入收藏
前言在一个高并发系统中对流量的把控是非常重要的,当巨大的流量直接请求到我们的服务器上没多久就可能造成接口不可用,不处理的话甚至会造成整个应用不可用。那么何为限流呢?顾...【详细内容】
2020-12-15  Tags: 接口限流  点击:(121)  评论:(0)  加入收藏
一个基于Redis实现的接口限流方案,先说要实现的功能 可以限制指定的接口,在一定时间内,只能被请求N次,超过次数就返回异常信息 可以通过配置文件,或者管理后台,动态的修改限流配置...【详细内容】
2020-08-10  Tags: 接口限流  点击:(52)  评论:(0)  加入收藏
导读 前几天和一个朋友讨论了他们公司的系统问题,传统的单体应用,集群部署,他说近期服务的并发量可能会出现瞬时增加的风险,虽然部署了集群,但是通过压测后发现请求延迟仍然是很...【详细内容】
2020-03-10  Tags: 接口限流  点击:(56)  评论:(0)  加入收藏
▌简易百科推荐
近日只是为了想尽办法为 Flask 实现 Swagger UI 文档功能,基本上要让 Flask 配合 Flasgger, 所以写了篇 Flask 应用集成 Swagger UI 。然而不断的 Google 过程中偶然间发现了...【详细内容】
2021-12-23  Python阿杰    Tags:FastAPI   点击:(6)  评论:(0)  加入收藏
文章目录1、Quartz1.1 引入依赖<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version></dependency>...【详细内容】
2021-12-22  java老人头    Tags:框架   点击:(11)  评论:(0)  加入收藏
今天来梳理下 Spring 的整体脉络啦,为后面的文章做个铺垫~后面几篇文章应该会讲讲这些内容啦 Spring AOP 插件 (了好久都忘了 ) 分享下 4ye 在项目中利用 AOP + MybatisPlus 对...【详细内容】
2021-12-07  Java4ye    Tags:Spring   点击:(14)  评论:(0)  加入收藏
&emsp;前面通过入门案例介绍,我们发现在SpringSecurity中如果我们没有使用自定义的登录界面,那么SpringSecurity会给我们提供一个系统登录界面。但真实项目中我们一般都会使用...【详细内容】
2021-12-06  波哥带你学Java    Tags:SpringSecurity   点击:(18)  评论:(0)  加入收藏
React 简介 React 基本使用<div id="test"></div><script type="text/javascript" src="../js/react.development.js"></script><script type="text/javascript" src="../js...【详细内容】
2021-11-30  清闲的帆船先生    Tags:框架   点击:(19)  评论:(0)  加入收藏
流水线(Pipeline)是把一个重复的过程分解为若干个子过程,使每个子过程与其他子过程并行进行的技术。本文主要介绍了诞生于云原生时代的流水线框架 Argo。 什么是流水线?在计算机...【详细内容】
2021-11-30  叼着猫的鱼    Tags:框架   点击:(21)  评论:(0)  加入收藏
TKinterThinter 是标准的python包,你可以在linx,macos,windows上使用它,你不需要安装它,因为它是python自带的扩展包。 它采用TCL的控制接口,你可以非常方便地写出图形界面,如...【详细内容】
2021-11-30    梦回故里归来  Tags:框架   点击:(26)  评论:(0)  加入收藏
前言项目中的配置文件会有密码的存在,例如数据库的密码、邮箱的密码、FTP的密码等。配置的密码以明文的方式暴露,并不是一种安全的方式,特别是大型项目的生产环境中,因为配置文...【详细内容】
2021-11-17  充满元气的java爱好者  博客园  Tags:SpringBoot   点击:(25)  评论:(0)  加入收藏
一、搭建环境1、创建数据库表和表结构create table account(id INT identity(1,1) primary key,name varchar(20),[money] DECIMAL2、创建maven的工程SSM,在pom.xml文件引入...【详细内容】
2021-11-11  AT小白在线中  搜狐号  Tags:开发框架   点击:(29)  评论:(0)  加入收藏
SpringBoot开发的物联网通信平台系统项目功能模块 功能 说明 MQTT 1.SSL支持 2.集群化部署时暂不支持retain&will类型消 UDP ...【详细内容】
2021-11-05  小程序建站    Tags:SpringBoot   点击:(55)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条