前言
为啥需要限制接口请求频率?这个是因为防止接口一直被刷,比如发送手机验证码的接口,一直被刷的话,费钱费资源的,至少做点基本的防护工作。以下分别使用Redis和Nginx实现限制接口请求频率方案。
一、基于Redis实现接口限流
1.ZADD 命令
(1)用法:ZADD key score_1 value_1 score_2 value_2 …
(2)作用:将一个或多个成员元素及其分数值加入到有序集当中。某个成员已经是有序集的成员,那么更新这个成员的分数值,并通过重新插入这个成员元素,来保证该成员在正确的位置上。分数值可以是整数值或双精度浮点数。
(3)返回值:被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员。
(4)示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | redis > ZADD runoobkey 1 redis ( integer ) 1 redis > ZADD runoobkey 2 mongodb ( integer ) 1 redis > ZADD runoobkey 3 mysql ( integer ) 1 redis > ZADD runoobkey 3 mysql ( integer ) 0 redis > ZADD runoobkey 4 mysql ( integer ) 0 redis > ZRANGE runoobkey 0 10 WITHSCORES 1) "redis" 2) "1" 3) "mongodb" 4) "2" 5) "mysql" 6) "4" |
2.ZREM 命令
(1)用法:ZREM key value_1 value_2 …
(2)作用:移除有序集中的一个或多个成员,不存在的成员将被忽略。
(3)返回值:被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员。
(4)示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | redis > ZRANGE runoobkey 0 10 WITHSCORES 1) "redis" 2) "1" 3) "mongodb" 4) "2" 5) "mysql" 6) "4" redis > ZREM mongodb ( integer ) 1 redis > ZRANGE runoobkey 0 10 WITHSCORES 1) "redis" 2) "1" 3) "mysql" 4) "4" |
3.ZCARD 命令
(1)用法:ZCARD key
(2)作用:获取有序集合中成员的数量。
(3)返回值:当key存在且是有序集类型时,返回有序集的基数。 当key不存在时,返回0 。
(4)示例
1 2 3 4 5 6 | redis > ZADD myzset 1 "one" ( integer ) 1 redis > ZADD myzset 2 "two" ( integer ) 1 redis > ZCARD myzset ( integer ) 2 |
4.ZREMRANGEBYSCORE 命令
(1)用法:ZREMRANGEBYSCORE key min max
(2)作用:移除有序集中,指定分数区间内的所有成员。
(3)返回值:被移除成员的数量。
(4)示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | redis > ZRANGE salary 0 -1 WITHSCORES 1) "tom" 2) "2000" 3) "peter" 4) "3500" 5) "jack" 6) "5000" redis > ZREMRANGEBYSCORE salary 1500 3500 ( integer ) 2 redis> ZRANGE salary 0 -1 WITHSCORES 1) "jack" 2) "5000" |
5.具体实现
(1)新建一个过滤器,如【/src/main/java/org/example/interceptor/RateLimiterInterceptor.java】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | package org.example.interceptor; import cn.hutool.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.concurrent.TimeUnit; /** * 限流拦截器 */ @Component public class RateLimiterInterceptor implements HandlerInterceptor { private static final String RATE_LIMITER_PREFIX = "Rate-Limiter:" ; private static final int LIMIT = 10 ; // 限流阈值 private static final int TIME_WINDOW = 60 ; // 时间窗口,单位为秒 @Autowired private StringRedisTemplate stringRedisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { response.setCharacterEncoding( "utf-8" ); response.setContentType( "application/json" ); String key = RATE_LIMITER_PREFIX + request.getRequestURI() + ":" + request.getRemoteAddr(); // Rate-Limiter:/api/sendCode:127.0.0.1 long currentTime = System.currentTimeMillis(); // 1703036748554 long beforeTime = currentTime - TIME_WINDOW * 1000 ; // 1703036748554 - 60000 = 1703036688554 // Long removeNum = stringRedisTemplate.opsForZSet().removeRangeByScore(K key, double min, double max); // 删除有序集合中分数在指定范围内的元素,返回删除元素的数量 stringRedisTemplate.opsForZSet().removeRangeByScore(key, 0 , beforeTime); // 删除有序集合中60秒之前存进去的所有数据,如: // Long memberNum = stringRedisTemplate.opsForZSet().size(K key); // 获取有序集合中元素的数量 long count = stringRedisTemplate.opsForZSet().size(key); // 3 if (count >= LIMIT) { HashMap responseObj = new HashMap(); responseObj.put( "code" , HttpStatus.TOO_MANY_REQUESTS.value()); responseObj.put( "success" , false ); responseObj.put( "msg" , HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase()); JSONObject json = new JSONObject(responseObj); response.getWriter().println(json); return false ; } else { // Boolean addFlag = stringRedisTemplate.opsForZSet().add(K var1, V var2, double var3); // 向有序集合中添加一个或多个元素,并指定其分数 stringRedisTemplate.opsForZSet().add(key, String.valueOf(currentTime), currentTime); // Boolean expireFlag = stringRedisTemplate.expire(K key, long timeout, TimeUnit unit); // 对指定key的数据设置过期时间 stringRedisTemplate.expire(key, TIME_WINDOW, TimeUnit.SECONDS); return true ; } } } |
(2)在SpringMVC配置类中注入此过滤器,如【/src/main/java/org/example/config/ResourceConfig.java】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package org.example.config; import org.example.interceptor.RateLimiterInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; @Configuration public class ResourceConfig extends WebMvcConfigurationSupport { @Autowired private RateLimiterInterceptor rateLimiterInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(rateLimiterInterceptor).addPathPatterns( "/abcd/api/sendCode" ); } } |
6.运行效果
// ~
二、基于Nginx实现接口限流
1.在nginx.conf文件中新增限流配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | user nginx; worker_processes 1; error_log /var/log/nginx/error .log warn; pid /var/run/nginx .pid; events { worker_connections 1024; } http { include /etc/nginx/mime .types; default_type application /octet-stream ; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"' ; access_log /var/log/nginx/access .log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; include /etc/nginx/conf .d/*.conf; # 负载均衡 upstream springboot { server xxx.xxx.xxx.xxx:8080; } # limit_req_zone $binary_remote_addr zone=limit_zone:10m rate=10r/s; # 定义了一个名为limit_zone的限流区域,使用IP地址进行限流,该区域的大小为10MB,限流速率为10个请求每秒。 limit_req_zone $binary_remote_addr zone=limit_zone:10m rate=10r /m ; # 定义了一个名为limit_zone的限流区域,使用IP地址进行限流,该区域的大小为10MB,限流速率为10个请求每分钟。 # 80端口的服务 server { listen 80; server_name xxx.xxx.xxx.xxx; location / { alias html; index index.html index.htm; try_files $uri $uri/ /love/index .html; proxy_pass http: //localhost ; } location ^~ /love/ { root html /love ; index index.html index.htm; proxy_pass http: //localhost ; } location ^~ /abcd/api/sendCode { # 在/xxx/api/sendCode接口的location中使用limit_req指令进行限流,限流区域为limit_zone,同时设置了一个瞬时突发流量为20个请求的阈值。 # 这样,当同一个IP地址在一秒钟内发送超过10个请求到此接口时,Nginx会返回503错误码,表示请求被限流了。 # limit_req zone=limit_zone burst=20; # 在/xxx/api/sendCode接口的location中使用limit_req指令进行限流,限流区域为limit_zone,同时设置了一个瞬时突发流量为5个请求的阈值。 # 这样,当同一个IP地址在一秒钟内发送超过10个请求到此接口时,如果在一分钟内有超过10个请求,则允许其中的5个请求通过,nodelay表示不延迟响应,即立即返回503错误码。 limit_req zone=limit_zone burst=5 nodelay; proxy_pass http: //springboot ; } } } |
2.运行效果
// ~
到此这篇关于Redis和Nginx实现限制接口请求频率的示例的文章就介绍到这了,更多相关Redis和Nginx限制接口请求频率内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!