SpringCloud根据cookie进行gateway路由

根据 cookie 进行 gateway 路由

01.01-根据 cookie 进行 gateway 路由

1. 设计

参与角色:gateway, service-provider

2. 实现

2.1 LoadBalancer 实现类,参照 RoundRibonLoadBalancer 实现。

功能:跟进前端的 cookie 中的 zone,来匹配 nacos 注册信息中 metadata(元数据)的 version。进行匹配。

其他完全根据 gateway 的路由完成。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package com.alibaba.cloud.examples.loadbalancer;

import com.alibaba.cloud.commons.lang.StringUtils;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;

/**
* @Title : MyLoadBalancer
* @Description: :
* @author: libo@juzishuke.com
* @date: 2023/9/11 16:51
* @Version:1.0
*/
//@Component
public class MyLoadBalancer implements ReactorServiceInstanceLoadBalancer {

private static final Log log = LogFactory.getLog(MyLoadBalancer.class);

final AtomicInteger position;
final String serviceId;
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

public MyLoadBalancer(
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String name) {
this(serviceInstanceListSupplierProvider, name, new Random().nextInt(1000));
}

public MyLoadBalancer(
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String name, int seedPosition) {
this.serviceId = name;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.position = new AtomicInteger(seedPosition);
}

@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(
NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(request, supplier,
serviceInstances));
}

private Response<ServiceInstance> processInstanceResponse(Request request,
ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
String zone = ((RequestDataContext) request.getContext()).getClientRequest().getCookies()
.getFirst("zone");
if (StringUtils.isNotBlank(zone)) {
serviceInstances = serviceInstances.stream()
.filter(e -> zone.equalsIgnoreCase(e.getMetadata().get("version"))).collect(Collectors.toList());
}
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(request,
serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(
serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}

private Response<ServiceInstance> getInstanceResponse(Request request,
List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}

// Do not move position when there is only 1 instance, especially some suppliers
// have already filtered instances
if (instances.size() == 1) {
return new DefaultResponse(instances.get(0));
}

// Ignore the sign bit, this allows pos to loop sequentially from 0 to
// Integer.MAX_VALUE
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;

ServiceInstance instance = instances.get(pos % instances.size());

return new DefaultResponse(instance);
}
}

2.2 LoadBalancerConfiguration 配置类

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
package com.alibaba.cloud.examples.config;

import com.alibaba.cloud.examples.loadbalancer.MyLoadBalancer;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

/**
* @Title : LoadBalancerConfiguration
* @Description: :
* @author: libo@juzishuke.com
* @date: 2023/9/11 17:16
* @Version:1.0
*/
@Configuration(proxyBeanMethods = false)
public class LoadBalancerConfiguration {


@Bean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new MyLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}

2.3 启动方法

在 service-provider 类上面添加启动参数

1
-Dserver.port=0 -Dspring.cloud.nacos.discovery.metadata.version=v2

-Dserver.port=0 标识随机端口

-Dspring.cloud.nacos.discovery.metadata.version=xxx 标识 snacos 注册的元数据特殊值

2.4 效果

服务 实例端口 meta.version zone=V1 zone=V2 zone=null
gateway 18085
service-provider 18086 V1 Y N N
service-provider 18087 V2 N P N
service-provider 18088 V2 N P N
service-provider 18089 N N Y

2.4.1 zone=V1

2.4.2 zone=V2

第 1 次请求

第 2 次请求

2.4.3 zone=null