1、什么是API网关?
API网关:是所有服务的一个统一入口,同时提供过滤功能。
2、网关的组成
组成:路由转发+过滤
2.1、路由转发
接受外部请求,按照指定的路由规则,转发到特定的后端服务上
2.2、过滤器
过滤器提供25种内置功能,支持自定义功能,常用有容错、限流
3、spring cloud gateaway介绍
3.1、简介
spring cloud gateaway是spring cloud的二级子项目,提供微服务网管功能,包括鉴权,监控、指标、谓词校验、过滤、限流、容错等功能。
3.2、名词术语介绍
Route(路由):route是gateaway的主要内容,一个gateway项目可包含多个route。一个route是一个完整的路由映射节点,包含路由规则、校验、过滤、容错(路由规则通过谓词实现,校验、过滤、容错通过过滤器实现)。一个route配置包含id、url、predicate集合、filter集合。
predicate(中文:谓词):相当于路由附加条件及内容,路由规则及校验逻辑。
filter(过滤):在gateaway中负责在路由转发前后做一些事情。
3.3、执行过程
4、基础配置
lb(load balance)
5、过滤器:限流:限流算法
一、为什么需要限流
按照服务的调用方,可以分为以下几种类型服务
1、与用户打交道的服务
比如web服务、对外API,这种类型的服务有以下几种可能导致机器被拖垮:
用户增长过快(这是好事)
因为某个热点事件(微博热搜)
竞争对象爬虫
恶意的请求
这些情况都是无法预知的,不知道什么时候会有10倍甚至20倍的流量打进来,如果真碰上这种情况,扩容是根本来不及的(弹性扩容都是虚谈,一秒钟你给我扩一下试试)
2、对内的RPC服务
从上图可以看出上游的A、B服务直接依赖了下游的基础服务C、D和E,对于A,B服务都依赖的基础服务D这种场景,服务A和B其实处于某种竞争关系,如果服务A的并发阈值设置过大,当流量高峰期来临,有可能直接拖垮基础服务D并影响服务B,即雪崩效应来了
二、限流算法实现
常见的限流算法有:计数器、漏桶(Leaky Bucket) 、令牌桶(Token Bucket)。
1、计数器
采用计数器实现限流有点简单粗暴,一般我们会限制一段时间能够通过的请求数,比如限流qps为100/min
1)、访问量限流(通过限制单位时间段内调用量来限流)
原理:
确定方法的最大访问量MAX,每次进入方法前计数器+1,将结果和最大并发量MAX比较,如果大于等于MAX,则直接返回;如果小于MAX,则继续执行。
示例:
测试代码路径:https://github.com/1309893329/study/tree/master/dailystudy/src/main/java/lm/study/daily/run/limit
优缺点:
对于访问量限流这种限流方式,实现简单,适用于大多数场景,阈值可以通过服务端来动态配置,甚至可以当做业务开关来使用,
但也有一定的局限性,因为我们的阈值是通过分析单位时间段内调用量来设置的,如果它在单位时间段的前几秒就被流量突刺消耗完了,将导致该时间段内剩余的时间内该服务“拒绝服务”,可以将这种现象称为“突刺现象”。
2)、并发量限流(通过限制系统的并发调用程度来限流)
原理:
确定方法的最大并发量MAX,每次进入方法前计数器+1,将结果和最大并发量MAX比较,如果大于等于MAX,则直接返回;如果小于MAX,则继续执行;退出方法后计数器-1。
比如限制服务的并发访问数是100,而服务处理的平均耗时是10毫秒,那么1分钟内,该服务平均能提供( 1000 / 10 ) * 60 * 100 = 6000 次
示例:
测试代码路径:https://github.com/1309893329/study/tree/master/dailystudy/src/main/java/lm/study/daily/run/limit/aop
优缺点:
并发量限流一般用于对于服务资源有严格限制的场景,但是某个服务在业务高峰期和低峰期的并发量很难评估,这给并发阈值的设置带来了困难,但我们可以通过线上业务的监控数据来逐步对并发阈值进行调优,只要肯花时间,我们总能找到一个即能保证一定服务质量又能保证一定服务吞吐量的合理并发阈值,从表面上看并发量限流似乎很有用,但也不可否认,它仍然可以造成流量尖刺,即每台服务器上该服务的并发量从0上升到阈值是没有任何“阻力”的,这是因为并发量考虑的只是服务能力边界的问题。
2、漏桶(Leaky Bucket)
为了消除"突刺现象",可以采用漏桶算法实现限流,漏桶算法这个名字就很形象,算法内部有一个容器,类似生活用到的漏斗,
当请求进来时,相当于水倒入漏斗,然后从下端小口慢慢匀速的流出。不管上面流量多大,下面流出的速度始终保持不变。
不管服务调用方多么不稳定,通过漏桶算法进行限流,每10毫秒处理一次请求。因为处理的速度是固定的,请求进来的速度是未知的,
可能突然进来很多请求,没来得及处理的请求就先放在桶里,既然是个桶,肯定是有容量上限,如果桶满了,那么新进来的请求就丢弃
3、令牌桶(Token Bucket)
从某种意义上讲,令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。
在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。
每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。
放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,
这时进来的请求就可以直接拿到令牌执行,比如设置qps为100/s,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,
这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行
原文链接:https://blog.youkuaiyun.com/qq_35869079/article/details/88243754
gateway基于redis令牌桶算法进行微服务的限流
-
添加依赖
-
KeyResolver代码
import java.util.Objects;
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 reactor.core.publisher.Mono;
@Configuration
public class RequestRateLimiterConfig {
@Primary
@Bean
KeyResolver ipKeyResolver() {
// 按IP来限流
return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress());
}
@Bean
KeyResolver apiKeyResolver() {
// 按URL限流
return exchange -> Mono.just(exchange.getRequest().getPath().toString());
}
@Bean
KeyResolver userKeyResolver() {
// 按用户限流
return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getHeaders().getFirst("Authorization")));
}
}
- application.yml的配置
filters:
- name: RequestRateLimiter
args:
key-resolver: '#{@ipKeyResolver}'
redis-rate-limiter.replenishRate: 200
redis-rate-limiter.burstCapacity: 600
# RequestRateLimiter springcloud gateway限流过滤器
# 参数 key-resolver 使用SpEL 从request中提取限流的key
# 参数 replenishRate 向令牌桶中每秒填充的速率
# 参数 burstCapacity 令牌桶 容量
6、gateaway hystrix服务容错、降级
- 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
- 配置
filters:
- StripPrefix=1
- name: Hystrix
args:
name: default
fallbackUri: 'forward:/fallback'
注:开启Hystrix后,请求超时时间默认为1秒
- 实现降级
@RestController
public class DefaultHystrixController {
@GetMapping("/fallback")
public String fallback(){
return "服务降级";
}
}
7.自定义过滤器
gateaway自定义过滤器分为:全局过滤器、路由过滤器
7.1 全局过滤器
全局过滤器作用于所有的路由,不需要单独配置,我们可以用它来实现很多统一化处理的业务需求,比如权限认证,IP访问限制等等。
实现方式:实现GatewayFilter, Ordered接口
例:
@Configuration
public class ExampleConfiguration {
private Logger log = LoggerFactory.getLogger(ExampleConfiguration.class);
@Bean
@Order(-1)
public GlobalFilter a() {
return (exchange, chain) -> {
log.info("前置处理");
result = chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("执行请求");
}));
log.info("后置处理");
return result;
};
}
@Bean
@Order(0)
public GlobalFilter b() {
return (exchange, chain) -> {
log.info("second pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("second post filter");
}));
};
}
@Bean
@Order(1)
public GlobalFilter c() {
return (exchange, chain) -> {
log.info("third pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("first post filter");
}));
};
}
}
上面定义了3个GlobalFilter,通过@Order来指定执行的顺序,数字越小,优先级越高。
参考:https://www.cnblogs.com/yinjihuan/p/10474768.html
7.2 自定义路由过滤器
只对指定的路由起作用,需要配置。
实现方式:继承AbstractGatewayFilterFactory类,其实最核心的功能操作是需要实现GatewayFilter apply(C config)方法,编写自定义的功能。
例:
继承AbstractGatewayFilterFactory的方式其实差不多,我们尝试做一个相对复杂的改造:对每一个请求成功(状态码为200)的响应,添加一个自定义的cookie和一个响应头。
@Component
public class CustomResponseGatewayFilterFactory extends
AbstractGatewayFilterFactory<CustomResponseGatewayFilterFactory.Config> {
public CustomResponseGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return ((exchange, chain) -> {
ServerHttpResponse response = exchange.getResponse();
if (HttpStatus.OK.equals(response.getStatusCode())) {
for (Map.Entry<String, String> entry : toMap(config.getCookie()).entrySet()) {
response.addCookie(ResponseCookie.from(entry.getKey(), entry.getValue()).build());
}
for (Map.Entry<String, String> entry : toMap(config.getHeader()).entrySet()) {
response.getHeaders().add(entry.getKey(), entry.getValue());
}
return chain.filter(exchange.mutate().response(response).build());
} else {
return chain.filter(exchange);
}
});
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList(NAME_KEY);
}
private static Map<String, String> toMap(String value) {
String[] split = value.split("=");
Map<String, String> map = new HashMap<>(8);
map.put(split[0], split[1]);
return map;
}
public static class Config {
private String cookie;
private String header;
public String getCookie() {
return cookie;
}
public void setCookie(String cookie) {
this.cookie = cookie;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
}
}
配置
filters:
- name: CustomResponse
args:
cookie: customCookieName=customCookieValue
header: customHeaderName=customHeaderValue
注:filters集合下的name属性是必须的,指向AbstractGatewayFilterFactory实现类的name()方法,args属性是用于指定装配到Config类的属性。
参考:https://cloud.tencent.com/developer/article/1650119
附:SpringCloud Gateway自定义过滤器,对单独url设置超时时间:
https://blog.youkuaiyun.com/u014203449/article/details/105248914/