dongyukun
2024-12-31 6eeac9efdb16f92d19536bf23a2d1471705fe752
提交 | 用户 | 时间
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 }