Spring Cloud Gateway是目前最流行的网关实现之一,通常与Spring Cloud中其它框架一起用于构建微服务项目,Spring Cloud Gateway默认是按照负载均衡的方式将请求路由到后端服务实例的,但在某些特殊场景下则需要有状态路由,例如服务有两个接口其中一个用于生成文件存储在本地,下一个接口用于下载生成的文件。本文介绍有状态路由的实现方法
1. 项目准备
新建一个网关项目 gateway
和后端服务项目 backend
,并在 backend
中定义两个有状态的接口
@RestController
public class MyController {
private final Map<String, String> map = new HashMap<>();
@RequestMapping("/get")
public String get() {
return map.get("key");
}
@RequestMapping("/set/{value}")
public void set(@PathVariable String value) {
map.put("key", value);
}
}
backend
启动两个实例并在 gateway
中配置两个实例的地址,此处为 localhost:8082
和 localhost:8083
,对应的实例Id是 ins2
和 ins3
server:
port: 8081
spring:
cloud:
discovery:
client:
simple:
instances:
backend:
- uri: http://localhost:8082
instance-id: ins2
- uri: http://localhost:8083
instance-id: ins3
gateway:
routes:
- id: backend
uri: lb://backend
predicates:
- Path=/**
完整项目源码参考 GitHub |
此时通过网关先调用 /set/vv
接口再调用 /get
接口是显然无法获取到上一步保存的 vv
的,因为按照默认的负载均衡算法,两次请求被路由到了不同的 backend
实例上
C:\Users\pxzxj1>curl "http://localhost:8081/set/vv" C:\Users\pxzxj1>curl "http://localhost:8081/get" C:\Users\pxzxj1>
2. 配置验证
Spring Cloud LoadBalancer提供了 Request-based Sticky Session配置可以解决有状态路由问题,Spring Cloud Cloud也使用了Spring Cloud LoadBalancer实现路由选择
首先在网关的配置文件中添加如下内容
spring:
cloud:
loadbalancer:
configurations: request-based-sticky-session (1)
sticky-session:
add-service-instance-cookie: true (2)
-
基于请求中的Cookie路由,默认Cookie名称是
sc-lb-instance-id
-
请求转发到后端时将选中的后端实例信息添加到请求的Cookie中
为了将此次选中的实例信息返回客户端,后端接口也需要修改,将网关添加到请求中的Cookie添加到响应中
@RestController
public class MyController {
private final Map<String, String> map = new HashMap<>();
@RequestMapping("/get")
public String get() {
return map.get("key");
}
@RequestMapping("/set/{value}")
public void set(@PathVariable String value, HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
if(cookies != null) {
for(Cookie cookie : cookies) {
if("sc-lb-instance-id".equals(cookie.getName())) {
response.addCookie(cookie);
break;
}
}
}
map.put("key", value);
}
}
此时再次调用 /set/vv
接口可以在它的响应的Cookie中看到本次网关选中的后端实例为 ins2
,那么调用 /get
时也附带上此Cookie就可以使网关把请求也路由到 ins2
,从而获取到 vv
C:\Users\pxzxj1>curl "http://localhost:8081/set/vv" -v * Trying 127.0.0.1:8081... * Connected to localhost (127.0.0.1) port 8081 (#0) > GET /set/vv HTTP/1.1 > Host: localhost:8081 > User-Agent: curl/7.83.1 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Set-Cookie: sc-lb-instance-id=ins2 < Content-Length: 0 < Date: Wed, 08 Mar 2023 08:44:57 GMT < * Connection #0 to host localhost left intact C:\Users\pxzxj1>curl -H "Cookie: sc-lb-instance-id=ins2" "http://localhost:8081/get" vv C:\Users\pxzxj1>