Gateway Detailed explanation and practice
An introduction
1.1 Introduction
Spring Cloud GateWay is a brand new project of Spring Cloud, aiming to replace Netflix Zuul. It is based on Spring5.0+SpringBoot2.0+WebFlux (based on the high-performance Reactor mode responsive communication framework Netty, asynchronous non-blocking model), etc. Technology development, performance is higher than Zuul, official testing, GateWay is 1.6 times that of Zuul, aiming to provide a simple, effective and unified API routing management method for microservice architecture.
Spring Cloud GateWay not only provides a unified routing method (reverse proxy) but also provides basic gateway functions based on the Filter (define filters to filter requests and complete some functions) chain method, such as: authentication, traffic Control, fusing, path rewriting, log monitoring, etc.
The location of the gateway in the architecture
1.2.GateWay core concept
Zuul1.x blocking IO 2.x is based on Netty.
Spring Cloud GateWay is inherently asynchronous and non-blocking, based on the Reactor model.
A request -> the gateway matches according to certain conditions - after successful matching, the request can be forwarded to the specified service address; and in this process, we can perform some more specific controls (current limiting, logging , black list)
路由(route)
: The most basic part of the gateway and the relatively basic working unit of the gateway. Routing consists of an ID, a target URL (the address to which the final route is directed), a series of assertions (matching condition judgment) and filters (refined control) . If the assertion is true, the route is matched.断⾔(predicates)
: With reference to the assertion java.util.function.Predicate in Java8, developers can match all content in the HTTP request (including request headers, request parameters, etc.) (similar to location matching in nginx). If the assertion is interrupted If the language matches the request, it will be routed.过滤器(filter)
: A standard Spring webFilter that uses filters to execute business logic before or after requests.
In a picture from the official website
, Predicates assertion is our matching condition, and Filter can be understood as an omnipotent interceptor. With these two elements, combined with the target URL, you can achieve a A specific route forwarding.
1.3.GateWay core functions
- Routing : After the gateway is added, all requests must first go through the gateway, so the gateway must forward the request to a certain microservice according to certain rules. This process is called routing.
- Permission control : When a request passes through routing, we can determine whether the requester is qualified to request the request, and intercept it if not.
- Current limiting : When the request traffic is too high, the gateway will release the request at the speed that the downstream microservices can accept to avoid excessive service pressure.
2. Gateway entry case
Source code address
Gateway’s routing function, the basic steps are as follows:
- Create SpringBoot project gateway_server and introduce gateway dependencies
- Write startup class
- Write basic configuration: service port, application name
- Write routing rules
- Start the gateway service for testing
2.1 gateway dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2.2 gateway configuration
server:
port: 10010
spring:
application:
name: gateway-server # 服务名
cloud:
gateway:
routes: #路由规则的列表
- id: consumer-service # 当前路由的唯一标识
uri: http://127.0.0.1:8080 # 路由的目标微服务地址
predicates: # 断言,定义请求的匹配规则
- Path=/consumer/** # Path代表按照路径匹配的规则,/consumer/**是指路径必须以/consumer开头
2.3 gateway test
Start the following three services.
When we access http://localhost:10010/consumer/depart/get/1, we will first enter the gateway service. The assertion determines that it matches =/consumer/**, so the request will be proxied to http:/ /localhost:8080/consumer/depart/get/1
Three Gateway service-oriented routing
3.1. Door case problem
In the entry-level case, the target address of the route is hard-coded. In the case of microservices, it may be that the target service is a cluster, so it is obviously unreasonable to do so. We should go to the Eureka registration center to find a list of all instances corresponding to the service based on the name of the service, and load balance the service list!
Case module
3.2.gateway-server combined with eureka steps
feign-consumer-8080 feign-eureka-server feign-provider-8081 These three modules follow the previous old parts, and the specific construction steps will not be repeated. Focus on the combination of gateway-server and eureka.
3.2.1. Add Eureka client dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
3.2.2. Add Eureka configuration
Inject gateway service into eure
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:10086/eureka/
instance:
prefer-ip-address: true #偏好使用ip地址,而不是host主机名
ip-address: 127.0.0.1
instance-id: ${
spring.application.name}.${
eureka.instance.ip-address}.${
server.port}
3.2.3 Modify mapping configuration
Because we already have the Eureka client, we can get the address information of the service from Eureka, so there is no need to specify the IP address when mapping, but access it through the service name, and Zuul has integrated Ribbon's load balancing function.
server:
port: 10010
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:10086/eureka/
instance:
prefer-ip-address: true #偏好使用ip地址,而不是host主机名
ip-address: 127.0.0.1
instance-id: ${
spring.application.name}.${
eureka.instance.ip-address}.${
server.port}
spring:
application:
name: gateway-server # 服务名
cloud:
gateway:
routes: #路由规则的列表,可以有多个
- id: feign-consumer # 当前路由的唯一标识
uri: lb://feign-consumer # 路由的目标微服务,lb:代表负载均衡,feign-consumer:代表服务id
predicates: # 断言,定义请求的匹配规则
- Path=/consumer/** # Path代表按照路径匹配的规则,/consumer/**是指路径必须以/consumer开头
The routing method of uri is modified here:
- lb: Load balancing protocol, Ribbon will be used to achieve load balancing in the future
- feign-consumer: service id
3.2.4. Start testing
-
Start module
-
View eureka client
-
Visit and observe the results
When we access http://localhost:10010/consumer/depart/get/1, we will first enter the gateway service and assert that the judgment is consistent with =/consumer/**, so the request will be proxied to http://localhost:8080/ consumer/depart/get/1
3.3 Local filter
GatewayFilter Factories are local filter factories in Gateway that act on a specific route and allow incoming HTTP requests or returned HTTP responses to be modified in some way.
3.3.1.Hystrix
The gateway does request routing and forwarding. If the called request is blocked, Hystrix needs to be used for thread isolation and circuit breaker to prevent failures.
1) Introduce Hystrix dependencies
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2) Turn on Hystrix and add @EnableHystrix
@EnableHystrix
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3) Define downgrade processing rules
It can be configured through default-filter and will apply to all routing rules.
spring:
application:
name: gateway-server # 服务名
cloud:
gateway:
routes: #路由规则的列表,可以有多个
- id: feign-consumer # 当前路由的唯一标识
uri: lb://feign-consumer # 路由的目标微服务,lb:代表负载均衡,feign-consumer:代表服务id
predicates: # 断言,定义请求的匹配规则
- Path=/consumer/** # Path代表按照路径匹配的规则,/consumer/**是指路径必须以/consumer开头
default-filters: # 默认过滤项
- name: Hystrix # 指定过滤工厂名称(可以是任意过滤工厂类型)
args: # 指定过滤的参数
name: fallbackcmd # hystrix的指令名
fallbackUri: forward:/fallbackTest # 失败后的跳转路径
hystrix:
command:
default:
execution.isolation.thread.timeoutInMilliseconds: 1000 # 失败超时时长
- default-filters: Default filter items, applied to all routing rules
- name: filter factory name, specify Hystrix here, which means configuring the Hystrix type
- args: Configure the configuration of the filter factory
- name: Hystrix command name, used to configure information such as timeout duration and other information
- fallbackUri: Jump path when downgrade fails
4) Define the downgrade processing function
Define a controller to write failure handling logic:
@RestController
public class FallbackController {
@RequestMapping(value = "/fallbackTest")
public Map<String, String> fallBackController() {
Map<String, String> response = new HashMap<>();
response.put("code", "502");
response.put("msg", "服务超时");
return response;
}
}
5) Test
Restart the gateway without starting the consumer. After visiting http://localhost:10010/consumer/depart/get/1, observe the results after one second and find that the timeout method is used.
3.3.2.Routing prefix
1) Problem demonstration
We previously used a mapping path like /consumer/** to represent the feign-consumer service. Therefore, all paths requesting the feign-consumer service must start with /consumer/**
For example, accessing: localhost:10010/consumer/depart/get/1 will be proxied to: http://localhost:8080/consumer/depart/get/1
Now, we define a new interface in the controller in feign-consumer:
@RestController
@RequestMapping("/test/depart")
public class TestController {
@GetMapping("/get/{id}")
public DepartVO getHandle(@PathVariable("id") int id) {
DepartVO departVO = new DepartVO();
departVO.setId(id);
departVO.setName("测试名称");
return departVO;
}
}
The path of this interface is /test/depart/get/1, which does not start with /consumer/. When accessing: localhost:10010/test/depart/get/, it does not match the mapping path, so you will get 404.
Both /consumer/** and /test/** are controller paths in feign-consumer and cannot be used as the mapping path from the gateway to feign-consumer.
Therefore we need to define an additional mapping path, for example: /feign-consumer, the configuration is as follows
# 路由前缀配置
spring:
application:
name: gateway-server # 服务名
cloud:
gateway:
routes: #路由规则的列表,可以有多个
- id: feign-consumer # 当前路由的唯一标识
uri: lb://feign-consumer # 路由的目标微服务,lb:代表负载均衡,feign-consumer:代表服务id
predicates: # 断言,定义请求的匹配规则
- Path=/feign-consumer/** # Path代表按照路径匹配的规则,feign-consumer:代表服务id。/feign-consumer/**是指路径必须以/feign-consumer
default-filters: # 默认过滤项
- name: feign-consumer # 指定过滤工厂名称(可以是任意过滤工厂类型)
args: # 指定过滤的参数
name: fallbackcmd # hystrix的指令名
fallbackUri: forward:/fallbackTest # 失败后的跳转路径
So here comes the question:
When we visit:: localhost:10010/feign-consumer/consumer/depart/get/1
, the mapping path/feign-consumer points to the user service and will be proxied to:http://localhost:8080/feign-consumer/consumer/depart/get/1
When we access: localhost:10010/feign-consumer/test/depart/get/1
, the mapping path/feign-consumer points to the user service and will be proxied to:http://localhost:8080/feign-consumer/test/depart/get/1
In feign-consumer
, both are /feign-consumer/consumer/depart/get/1
wrong /feign-consumer/test/depart/get/1
because there is one more /feign-consumer .
This /feign-consumer is the mapping path in the gateway and should not be proxied to the microservice. What should I do?
2) Remove routing prefix
The solution is very simple. When we access http://localhost:10010/feign-consumer/consumer/depart/get/1
, the gateway uses the /feign-consumer mapping path to match the user microservice. When requesting the proxy, we only need to remove the /feign-consumer mapping path.
There happens to be a filter: one StripPrefixFilterFactory
that meets our needs.
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/#the-stripprefix-gatewayfilter-factory
Let’s modify the routing configuration just now:
# 路由前缀配置
spring:
application:
name: gateway-server # 服务名
cloud:
gateway:
routes: #路由规则的列表,可以有多个
- id: feign-consumer # 当前路由的唯一标识
uri: lb://feign-consumer # 路由的目标微服务,lb:代表负载均衡,feign-consumer:代表服务id
predicates: # 断言,定义请求的匹配规则
- Path=/feign-consumer/** # Path代表按照路径匹配的规则,feign-consumer:代表服务id。/feign-consumer/**是指路径必须以/feign-consumer
filters:
- StripPrefix=1
default-filters: # 默认过滤项
- name: feign-consumer # 指定过滤工厂名称(可以是任意过滤工厂类型)
args: # 指定过滤的参数
name: fallbackcmd # hystrix的指令名
fallbackUri: forward:/fallbackTest # 失败后的跳转路径
At this time, when the gateway acts as a routing proxy, /feign-consumer will not be used as part of the target request path.
When we access:: localhost:10010/feign-consumer/consumer/depart/get/1
, we will be proxy to:http://localhost:8080/consumer/depart/get/1
When we access: localhost:10010/feign-consumer/test/depart/get/1
, the mapping path/feign-consumer points to the user service and will be proxied to:http://localhost:8080/test/depart/get/1
access test
3.4 Global filters
The global filter Global Filter and the local GatewayFilter will be merged into a filter chain at runtime, and then org.springframework.core.Ordered
executed according to order. The order can be specified through getOrder()
methods or @Order
annotations.
3.4.1.GlobalFilter interface
The top-level interface of the global filter:
To implement the interface, you must implement the filter method and complete the filtering logic inside the method. The parameters include:
-
ServerWebExchange: A domain object similar to Context that encapsulates service-related attributes such as Request and Response.
-
GatewayFilterChain: filter chain, used to release requests to the next filter
3.4.2. Filter order
By adding @Order
annotations, you can control the priority of filters, thereby determining the order in which filters are executed.
The execution of a filter includes
"pre"
two"post"
processes: the logic written
in the method belongs to the pre stage , and the use stage belongs to the Post stage .GlobalFilter.filter()
GatewayFilterChain.filter().then()
The filter with the highest priority will be executed first in the pre process and last in the post process, as shown in the figure:
There are many things we can do in the pre-stage, such as:
- Login status judgment
- Permission verification
- Request current limit, etc.
3.4.3. Custom filters
To define a filter, you only need to implement GlobalFilter, but we have many ways to do it:
- Method 1: Define a filter class and implement the GlobalFilter interface
- Method 2: Combine lambda expression through @Configuration class
3.4.3.1. Login interceptor (implementing GlobalFilter interface method)
Now, through custom filters, a login verification function is simulated. The logic is very simple:
- Get the access-token parameter in the user request parameters
- Determine whether it is "admin"
- If not, prove that you are not logged in and intercept the request.
- If yes, prove that you have logged in and release the request.
code show as below:
@Order(0) // 通过注解声明过滤器顺序
@Component
public class LoginFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取token
String token = exchange.getRequest().getQueryParams().toSingleValueMap().get("access-token");
// 判断请求参数是否正确
if(StringUtils.equals(token, "admin")){
// 正确,放行
return chain.filter(exchange);
}
// 错误,需要拦截,设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 结束任务
return exchange.getResponse().setComplete();
}
}
test:
-
With wrong parameters:
http://localhost:10010/feign-consumer/consumer/depart/get/1 -
With correct parameters:
http://localhost:10010/feign-consumer/consumer/depart/get/1?access-token=admin
3.4.3.2.Multiple filter demonstration (lambda expression)
@Configuration
public class FilterConfiguration {
@Bean
@Order(-2)
public GlobalFilter globalFilter1(){
return ((exchange, chain) -> {
System.out.println("过滤器1的pre阶段!");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
System.out.println("过滤器1的post阶段!");
}));
});
}
@Bean
@Order(-1)
public GlobalFilter globalFilter2(){
return ((exchange, chain) -> {
System.out.println("过滤器2的pre阶段!");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
System.out.println("过滤器2的post阶段!");
}));
});
}
@Bean
@Order(0)
public GlobalFilter globalFilter3(){
return ((exchange, chain) -> {
System.out.println("过滤器3的pre阶段!");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
System.out.println("过滤器3的post阶段!");
}));
});
}
}
http://localhost:10010/feign-consumer/consumer/depart/get/1?access-token=admin
3.5. Gateway current limiting
In addition to request routing and authentication, the gateway also plays a very important role: request current limiting. When the system faces high concurrent requests, in order to reduce the pressure on business processing services, it is necessary to limit the request flow in the gateway and release the requests at a certain rate.
Common current limiting algorithms include:
- Counter algorithm
- leaky bucket algorithm
- Token Bucket Algorithm
3.5.1. Principle of Token Bucket Algorithm
SpringGateway uses the token bucket algorithm. The principle of the token bucket algorithm is:
- Prepare a token bucket with a fixed capacity, usually the upper limit of service concurrency.
- At a fixed rate, tokens are generated and stored in the token bucket. If the number of tokens in the bucket reaches the upper limit, the tokens are discarded.
- Each request call needs to obtain a token first. Only when the token is obtained can the execution continue. Otherwise, you can choose to wait or reject it directly.
3.5.2. Current limiting in Gateway
SpringCloudGateway uses the token bucket algorithm, and its token-related information is recorded in redis, so we need to install redis and introduce Redis-related dependencies.
1) Introduce redis
Introduce Redis related dependencies:
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
Note: This is not an ordinary redis dependency, but a responsive Redis dependency, because SpringGateway is a responsive project based on WebFlux.
Configure the Redis address in application.yml:
spring:
redis:
host: localhost
2) Configure filter condition key
Gateway will record token-related information in Redis. We can define the rules of the token bucket ourselves, for example:
- Set different token buckets for different request URI paths
- Set different token buckets for different logged in users
- Set different token buckets for different request IP addresses
A Key and Value pair in Redis is a token bucket. Therefore, the key generation rules are the bucket definition rules. The key generation rules in SpringCloudGateway are defined in KeyResolver
the interface:
public interface KeyResolver {
Mono<String> resolve(ServerWebExchange exchange);
}
The return value of the method in this interface is the key generated for the token bucket. API description:
- Mono: It is a single element container used to store the key of the token bucket.
- ServerWebExchange: Context object, which can be understood as ServletContext, from which request, response, cookie and other information can be obtained
For example, for the above three token bucket rules, the key generation method is as follows:
-
Set different token buckets for different request URI paths, sample code:
return Mono.just(exchange.getRequest().getURI().getPath());// 获取请求URI
-
Set different token buckets for different logged in users
return exchange.getPrincipal().map(Principal::getName);// 获取用户
-
Set different token buckets for different request IP addresses
return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());// 获取请求者IP
Here we choose the last one, using the token bucket key of the IP address.
We config
define a class in and configure a Bean instance of IpKeyResolver:
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class IpKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
}
3) Configure bucket parameters
In addition, the parameters of the token bucket need to be configured through the yaml file. There are 2 parameters:
-
replenishRate
: The rate at which tokens are generated per second, basically the maximum number of requests allowed per second -
burstCapacity
: The capacity of the token bucket is the maximum number of tokens stored in the token bucket.
The complete configuration is as follows:
spring:
application:
name: ly-gateway
cloud:
gateway:
default-filters: # 默认过滤项
- StripPrefix=1 # 去除路由前缀
- name: Hystrix # 指定过滤工厂名称(可以是任意过滤工厂类型)
args: # 指定过滤的参数
name: fallbackcmd # hystrix的指令名
fallbackUri: forward:/hystrix/fallback # 失败后的跳转路径
- name: RequestRateLimiter #请求数限流 名字不能随便写
args:
key-resolver: "#{@ipKeyResolver}" # 指定一个key生成器
redis-rate-limiter.replenishRate: 2 # 生成令牌的速率
redis-rate-limiter.burstCapacity: 2 # 桶的容量
A filter is configured here: RequestRateLimiter, and three parameters are set:
-
key-resolver
:"#{@ipKeyResolver}"
It is a SpEL expression, written as#{@bean的名称}
, ipKeyResolver is the name of the Bean we defined -
redis-rate-limiter.replenishRate
: The rate at which tokens are generated per second -
redis-rate-limiter.burstCapacity
: Capacity of token bucket
The effects that such a current limiting configuration can achieve:
- Each IP address can initiate up to 2 requests per second.
- If there are more than 2 requests per second, an exception status code of 429 will be returned.
3.8.3.Testing
If we quickly access http://localhost:10010/feign-consumer/consumer/depart/get/1?access-token=admin multiple times in the browser, we will get an error:
429: Indicates that the number of requests is too many and current limiting is triggered.
Source code address
Source code address
05-gateway-eureka
Previous article: Detailed explanation and practice of Hystrix—SpringCloud components (4)