提交 | 用户 | 时间
|
e7c126
|
1 |
package com.iailab.gateway.filter.grey; |
H |
2 |
|
|
3 |
import cn.hutool.core.collection.CollUtil; |
|
4 |
import cn.hutool.core.util.StrUtil; |
|
5 |
import com.iailab.framework.common.util.collection.CollectionUtils; |
|
6 |
import com.iailab.gateway.util.EnvUtils; |
|
7 |
import com.alibaba.cloud.nacos.balancer.NacosBalancer; |
|
8 |
import lombok.RequiredArgsConstructor; |
|
9 |
import lombok.extern.slf4j.Slf4j; |
|
10 |
import org.springframework.beans.factory.ObjectProvider; |
|
11 |
import org.springframework.cloud.client.ServiceInstance; |
|
12 |
import org.springframework.cloud.client.loadbalancer.*; |
|
13 |
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier; |
|
14 |
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; |
|
15 |
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; |
|
16 |
import org.springframework.http.HttpHeaders; |
|
17 |
import reactor.core.publisher.Mono; |
|
18 |
|
|
19 |
import java.util.List; |
|
20 |
|
|
21 |
/** |
|
22 |
* 灰度 {@link GrayLoadBalancer} 实现类 |
|
23 |
* |
|
24 |
* 根据请求的 header[version] 匹配,筛选满足 metadata[version] 相等的服务实例列表,然后随机 + 权重进行选择一个 |
|
25 |
* 1. 假如请求的 header[version] 为空,则不进行筛选,所有服务实例都进行选择 |
|
26 |
* 2. 如果 metadata[version] 都不相等,则不进行筛选,所有服务实例都进行选择 |
|
27 |
* |
|
28 |
* 注意,考虑到实现的简易,它的权重是使用 Nacos 的 nacos.weight,所以随机 + 权重也是基于 {@link NacosBalancer} 筛选。 |
|
29 |
* 也就是说,如果你不使用 Nacos 作为注册中心,需要微调一下筛选的实现逻辑 |
|
30 |
* |
|
31 |
* @author iailab |
|
32 |
*/ |
|
33 |
@RequiredArgsConstructor |
|
34 |
@Slf4j |
|
35 |
public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer { |
|
36 |
|
|
37 |
private static final String VERSION = "version"; |
|
38 |
|
|
39 |
/** |
|
40 |
* 用于获取 serviceId 对应的服务实例的列表 |
|
41 |
*/ |
|
42 |
private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider; |
|
43 |
/** |
|
44 |
* 需要获取的服务实例名 |
|
45 |
* |
|
46 |
* 暂时用于打印 logger 日志 |
|
47 |
*/ |
|
48 |
private final String serviceId; |
|
49 |
|
|
50 |
@Override |
|
51 |
public Mono<Response<ServiceInstance>> choose(Request request) { |
|
52 |
// 获得 HttpHeaders 属性,实现从 header 中获取 version |
|
53 |
HttpHeaders headers = ((RequestDataContext) request.getContext()).getClientRequest().getHeaders(); |
|
54 |
// 选择实例 |
|
55 |
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new); |
|
56 |
return supplier.get(request).next().map(list -> getInstanceResponse(list, headers)); |
|
57 |
} |
|
58 |
|
|
59 |
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, HttpHeaders headers) { |
|
60 |
// 如果服务实例为空,则直接返回 |
|
61 |
if (CollUtil.isEmpty(instances)) { |
|
62 |
log.warn("[getInstanceResponse][serviceId({}) 服务实例列表为空]", serviceId); |
|
63 |
return new EmptyResponse(); |
|
64 |
} |
|
65 |
|
|
66 |
// 筛选满足 version 条件的实例列表 |
|
67 |
String version = headers.getFirst(VERSION); |
|
68 |
List<ServiceInstance> chooseInstances; |
|
69 |
if (StrUtil.isEmpty(version)) { |
|
70 |
chooseInstances = instances; |
|
71 |
} else { |
|
72 |
chooseInstances = CollectionUtils.filterList(instances, instance -> version.equals(instance.getMetadata().get("version"))); |
|
73 |
if (CollUtil.isEmpty(chooseInstances)) { |
|
74 |
log.warn("[getInstanceResponse][serviceId({}) 没有满足版本({})的服务实例列表,直接使用所有服务实例列表]", serviceId, version); |
|
75 |
chooseInstances = instances; |
|
76 |
} |
|
77 |
} |
|
78 |
|
|
79 |
// 基于 tag 过滤实例列表 |
|
80 |
chooseInstances = filterTagServiceInstances(chooseInstances, headers); |
|
81 |
|
|
82 |
// 随机 + 权重获取实例列表 TODO iailab:目前直接使用 Nacos 提供的方法,如果替换注册中心,需要重新失败该方法 |
|
83 |
return new DefaultResponse(NacosBalancer.getHostByRandomWeight3(chooseInstances)); |
|
84 |
} |
|
85 |
|
|
86 |
/** |
|
87 |
* 基于 tag 请求头,过滤匹配 tag 的服务实例列表 |
|
88 |
* |
|
89 |
* copy from EnvLoadBalancerClient |
|
90 |
* |
|
91 |
* @param instances 服务实例列表 |
|
92 |
* @param headers 请求头 |
|
93 |
* @return 服务实例列表 |
|
94 |
*/ |
|
95 |
private List<ServiceInstance> filterTagServiceInstances(List<ServiceInstance> instances, HttpHeaders headers) { |
|
96 |
// 情况一,没有 tag 时,直接返回 |
|
97 |
String tag = EnvUtils.getTag(headers); |
|
98 |
if (StrUtil.isEmpty(tag)) { |
|
99 |
return instances; |
|
100 |
} |
|
101 |
|
|
102 |
// 情况二,有 tag 时,使用 tag 匹配服务实例 |
|
103 |
List<ServiceInstance> chooseInstances = CollectionUtils.filterList(instances, instance -> tag.equals(EnvUtils.getTag(instance))); |
|
104 |
if (CollUtil.isEmpty(chooseInstances)) { |
|
105 |
log.warn("[filterTagServiceInstances][serviceId({}) 没有满足 tag({}) 的服务实例列表,直接使用所有服务实例列表]", serviceId, tag); |
|
106 |
chooseInstances = instances; |
|
107 |
} |
|
108 |
return chooseInstances; |
|
109 |
} |
|
110 |
|
|
111 |
} |