spring boot gateway自定义限流
参考:https://blog.csdn.net/ErickPang/article/details/84680132
采用自带默认网关请参照微服务架构spring cloud - gateway网关限流,参数与其唯一的区别是header中多了参数userLevel,值为A或者B
此处实现按传入参数取到不同配置
userLvl.A.replenishRate: 10
userLvl.A.burstCapacity: 100
userLvl.B.replenishRate: 20
userLvl.B.burstCapacity: 1000 自定义限流器
package com.gatewayaop.filter; import com.iot.crm.gatewayaop.common.config.UserLevelRateLimiterConf;
import org.springframework.beans.BeansException;
import org.springframework.cloud.gateway.filter.ratelimit.AbstractRateLimiter;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import javax.validation.constraints.Min;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean; public class UserLevelRedisRateLimiter extends AbstractRateLimiter<UserLevelRedisRateLimiter.Config> implements ApplicationContextAware {
//这些变量全部从RedisRateLimiter复制的,都会用到。
public static final String REPLENISH_RATE_KEY = "replenishRate"; public static final String BURST_CAPACITY_KEY = "burstCapacity"; public static final String CONFIGURATION_PROPERTY_NAME = "sys-redis-rate-limiter";
public static final String REDIS_SCRIPT_NAME = "redisRequestRateLimiterScript";
public static final String REMAINING_HEADER = "X-RateLimit-Remaining";
public static final String REPLENISH_RATE_HEADER = "X-RateLimit-Replenish-Rate";
public static final String BURST_CAPACITY_HEADER = "X-RateLimit-Burst-Capacity"; //处理速度
private static final String DEFAULT_REPLENISHRATE="default.replenishRate";
//容量
private static final String DEFAULT_BURSTCAPACITY="default.burstCapacity"; private ReactiveRedisTemplate<String, String> redisTemplate;
private RedisScript<List<Long>> script;
private AtomicBoolean initialized = new AtomicBoolean(false); private String remainingHeader = REMAINING_HEADER; /** The name of the header that returns the replenish rate configuration. */
private String replenishRateHeader = REPLENISH_RATE_HEADER; /** The name of the header that returns the burst capacity configuration. */
private String burstCapacityHeader = BURST_CAPACITY_HEADER; private Config defaultConfig; public UserLevelRedisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
RedisScript<List<Long>> script, Validator validator) {
super(Config.class , CONFIGURATION_PROPERTY_NAME , validator);
this.redisTemplate = redisTemplate;
this.script = script;
initialized.compareAndSet(false,true);
} public UserLevelRedisRateLimiter(int defaultReplenishRate, int defaultBurstCapacity){
super(Config.class , CONFIGURATION_PROPERTY_NAME , null);
defaultConfig = new Config()
.setReplenishRate(defaultReplenishRate)
.setBurstCapacity(defaultBurstCapacity); }
//具体限流实现,此处调用的是lua脚本
@Override
public Mono<Response> isAllowed(String routeId, String id) {
if (!this.initialized.get()) {
throw new IllegalStateException("RedisRateLimiter is not initialized");
}
if (ObjectUtils.isEmpty(rateLimiterConf) ){
throw new IllegalArgumentException("No Configuration found for route " + routeId);
}
//获取的是自定义的map
Map<String , Integer> rateLimitMap = rateLimiterConf.getRateLimitMap();
//缓存的key,此处routeId为userSev,Id为header参数userLevel的值(A或者B)
String replenishRateKey = routeId + "." + id + "." + REPLENISH_RATE_KEY;
//若map中不存在则采用默认值,存在则取值。
int replenishRate = ObjectUtils.isEmpty(rateLimitMap.get(replenishRateKey)) ? rateLimitMap.get(DEFAULT_REPLENISHRATE) : rateLimitMap.get(replenishRateKey);
//容量key
String burstCapacityKey = routeId + "." + id + "." + BURST_CAPACITY_KEY;
//若map中不存在则采用默认值,存在则取值。
int burstCapacity = ObjectUtils.isEmpty(rateLimitMap.get(burstCapacityKey)) ? rateLimitMap.get(DEFAULT_BURSTCAPACITY) : rateLimitMap.get(burstCapacityKey); try {
List<String> keys = getKeys(id); List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
Instant.now().getEpochSecond() + "", "1");
Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs); return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
.reduce(new ArrayList<Long>(), (longs, l) -> {
longs.addAll(l);
return longs;
}) .map(results -> {
boolean allowed = results.get(0) == 1L;
Long tokensLeft = results.get(1); RateLimiter.Response response = new RateLimiter.Response(allowed, getHeaders(replenishRate , burstCapacity , tokensLeft)); return response;
});
} catch (Exception e) {
e.printStackTrace();
} return Mono.just(new RateLimiter.Response(true, getHeaders(replenishRate , burstCapacity , -1L)));
} private UserLevelRateLimiterConf rateLimiterConf; @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.rateLimiterConf = applicationContext.getBean(UserLevelRateLimiterConf.class);
} public HashMap<String, String> getHeaders(Integer replenishRate, Integer burstCapacity , Long tokensLeft) {
HashMap<String, String> headers = new HashMap<>();
headers.put(this.remainingHeader, tokensLeft.toString());
headers.put(this.replenishRateHeader, String.valueOf(replenishRate));
headers.put(this.burstCapacityHeader, String.valueOf(burstCapacity));
return headers;
} static List<String> getKeys(String id) {
// use `{}` around keys to use Redis Key hash tags
// this allows for using redis cluster // Make a unique key per user.
//此处可以自定义redis前缀信息
String prefix = "request_sys_rate_limiter.{" + id; // You need two Redis keys for Token Bucket.
String tokenKey = prefix + "}.tokens";
String timestampKey = prefix + "}.timestamp";
return Arrays.asList(tokenKey, timestampKey);
} @Validated
public static class Config{
@Min(1)
private int replenishRate;
@Min(1)
private int burstCapacity = 1; public int getReplenishRate() {
return replenishRate;
} public Config setReplenishRate(int replenishRate) {
this.replenishRate = replenishRate;
return this;
} public int getBurstCapacity() {
return burstCapacity;
} public Config setBurstCapacity(int burstCapacity) {
this.burstCapacity = burstCapacity;
return this;
} @Override
public String toString() {
return "Config{" +
"replenishRate=" + replenishRate +
", burstCapacity=" + burstCapacity +
'}';
}
}
}
读取自定义配置类
package com.gatewayaop.common.config; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; //使用配置文件的方式进行初始化 @Component
@ConfigurationProperties(prefix = "comsumer.ratelimiter-conf")
//@EnableConfigurationProperties(UserLevelRateLimiterConf.class)
public class UserLevelRateLimiterConf {
//处理速度
private static final String DEFAULT_REPLENISHRATE="default.replenishRate";
//容量
private static final String DEFAULT_BURSTCAPACITY="default.burstCapacity"; //默认配置
private Map<String , Integer> rateLimitMap = new ConcurrentHashMap<String , Integer>(){
{
put(DEFAULT_REPLENISHRATE , 10);
put(DEFAULT_BURSTCAPACITY , 100);
}
}; public Map<String, Integer> getRateLimitMap() {
return rateLimitMap;
} public void setRateLimitMap(Map<String, Integer> rateLimitMap) {
this.rateLimitMap = rateLimitMap;
}
}
定义限流器种类
package com.gatewayaop.common.config; import com.iot.crm.gatewayaop.filter.UserLevelRedisRateLimiter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.validation.Validator;
import reactor.core.publisher.Mono; import java.util.List; @Configuration
public class RequestRateLimiterConfig {
@Bean
@Primary
KeyResolver apiKeyResolver() {
//按URL限流
return exchange -> Mono.just(exchange.getRequest().getPath().toString());
} @Bean
KeyResolver userKeyResolver() {
//按用户限流
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
} @Bean
KeyResolver ipKeyResolver() {
//按IP来限流
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
} @Bean
KeyResolver userLevelKeyResolver() {
//按IP来限流
return exchange -> Mono.just(exchange.getRequest().getHeaders().getFirst("userLevel"));
} @Bean
@Primary
//使用自己定义的限流类
UserLevelRedisRateLimiter userLevelRedisRateLimiter(
ReactiveRedisTemplate<String, String> redisTemplate,
@Qualifier(UserLevelRedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> script,
@Qualifier("defaultValidator") Validator validator){
return new UserLevelRedisRateLimiter(redisTemplate , script , validator);
} }
yml配置
server:
port: 9701 spring:
application:
name: gateway-aop-dev
profiles:
active: dev
index: 62
cloud:
gateway:
discovery:
locator:
enabled: true
# 服务名小写
lower-case-service-id: true
routes:
#与customer.中key相同即是java代码中的routeID
- id: userSev
# lb代表从注册中心获取服务,且已负载均衡方式转发
uri: lb://hello-dev
predicates:
- Path=/hello-dev/**
# 加上StripPrefix=1,否则转发到后端服务时会带上consumer前缀
filters:
- StripPrefix=1
# 限流过滤器,使用gateway内置令牌算法
- name: RequestRateLimiter
args:
# # 令牌桶每秒填充平均速率,即行等价于允许用户每秒处理多少个请求平均数
# redis-rate-limiter.replenishRate: 10
# # 令牌桶的容量,允许在一秒钟内完成的最大请求数
# redis-rate-limiter.burstCapacity: 20
# 用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
key-resolver: "#{@userLevelKeyResolver}"
rate-limiter: "#{@userLevelRedisRateLimiter}"
comsumer:
ratelimiter-conf:
#配置限流参数与RateLimiterConf类映射
rateLimitMap:
#格式为:routeid(gateway配置routes时指定的).系统名称.replenishRate(流速)/burstCapacity令牌桶大小
userSev.A.replenishRate: 10
userSev.A.burstCapacity: 100
userSev.B.replenishRate: 20
userSev.B.burstCapacity: 1000
spring boot gateway自定义限流的更多相关文章
- Spring Cloud Gateway 网关限流
Spring Cloud Gateway 限流 一.背景 二.实现功能 三.网关层限流 1.使用默认的redis来限流 1.引入jar包 2.编写配置文件 3.网关正常响应 4.网关限流响应 2.自定 ...
- spring cloud gateway 之限流篇
转载请标明出处: https://www.fangzhipeng.com 本文出自方志朋的博客 在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方 ...
- 微服务架构spring cloud - gateway网关限流
1.算法 在高并发的应用中,限流是一个绕不开的话题.限流可以保障我们的 API 服务对所有用户的可用性,也可以防止网络攻击. 一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池.线程池). ...
- 深入了解springcloud gateway 的限流重试机制
前言 前面给大家介绍了Spring Cloud Gateway的入门教程,这篇给大家探讨下Spring Cloud Gateway的一些其他功能. Spring Cloud Gateway中的重试 我 ...
- Gateway的限流重试机制详解
前言 想要源码地址的可以加上此微信:Lemon877164954 前面给大家介绍了Spring Cloud Gateway的入门教程,这篇给大家探讨下Spring Cloud Gateway的一些其 ...
- spring cloud gateway自定义过滤器
在API网关spring cloud gateway和负载均衡框架ribbon实战文章中,主要实现网关与负载均衡等基本功能,详见代码.本节内容将继续围绕此代码展开,主要讲解spring cloud g ...
- Spring Boot2 系列教程(十八)Spring Boot 中自定义 SpringMVC 配置
用过 Spring Boot 的小伙伴都知道,我们只需要在项目中引入 spring-boot-starter-web 依赖,SpringMVC 的一整套东西就会自动给我们配置好,但是,真实的项目环境比 ...
- Spring Boot Web 自定义注解篇(注解很简单很好用)
自从spring 4.0 开放以后,可以添加很多新特性的注解了.使用系统定义好的注解可以大大方便的提高开发的效率. 下面我贴一段代码来讲解注解: 通过小小的注解我们支持了以下功能: 使 spring. ...
- Spring cloud gateway自定义filter以及负载均衡
自定义全局filter package com.example.demo; import java.nio.charset.StandardCharsets; import org.apache.co ...
随机推荐
- javascript百度地图使用(根据地名定位、根据经纬度定位)
需要购买阿里云产品和服务的,点击此链接领取优惠券红包,优惠购买哦,领取后一个月内有效: https://promotion.aliyun.com/ntms/yunparter/invite.html? ...
- 简洁的Asp.net菜单控件
http://www.cnblogs.com/ruinet/archive/2009/11/10/1599984.html asp.net自带的菜单控件采用的table和javascript,导致生成 ...
- JS调用PageMethods
http://www.cnblogs.com/Ren_Lei/archive/2010/07/14/1777413.html JS调用PageMethods 操作步骤: 1.新建一个WebApplic ...
- 1-ES简单介绍
一.ES简单介绍 ES:Elastic Search,一个分布式.高扩展.高实时的搜索与数据分析引警.它可以准实时地快速存储.搜索.分析海量的数据. 1.ES实现原理 a.用户数据提交到ES数据库中 ...
- AFNetworking2.0源码解析<三>
本篇说说安全相关的AFSecurityPolicy模块,AFSecurityPolicy用于验证HTTPS请求的证书,先来看看HTTPS的原理和证书相关的几个问题. HTTPS HTTPS连接建立过程 ...
- 四、绑定SignaIR的用户管理
一.用户分组(第一个默认我的好友,禁删和更改) 没有分组id,更改layim代码: 更改id即可. layui.define('jquery', function (exports) { " ...
- HMP许可更新
1.打开HMP License Manager,显示路径(License File Name)下的文件为最新许可,点击Activate License后,点击Show License Details, ...
- Flask 框架app = Flask(__name__) 解析
#!/usr/local/bin/python # coding=utf-8 from flask import Flask app = Flask(__name__) @app.route('/') ...
- 将数据转为tfrecord格式
假设emo文件夹下,有1,2,3,4等文件夹,每个文件夹代表一个类别 import tensorflow as tf from PIL import Image from glob import gl ...
- qthread线程
一般调用quit()函数之后可以紧接着调用wait()函数确保线程退出.sleep()等让线程休眠的函数不需要调用,因为Qt中线程是事件驱动机制.但是如果是继承的QTHread类,在run()函数中使 ...