SpringCloudGateway重写负载均衡策略

背景

gateway中多实例请求转发,默认采用轮训转发策略。在有些场景下,某些请求想固定到某一台实例上,这里通过重写默认负载均衡策略的方式实现。

以下代码为,大文件分片上传,多实例场景,根据文件md5和实例总数取模,选取处理服务实例。保证同一文件在固定实例上进行处理,保证最后的文件合并不会有问题。

实现

Gateway有两种客户端负载均衡器,LoadBalancerClientFilter和ReactiveLoadBalancerClientFilter。LoadBalancerClientFilter使用一个Ribbon的阻塞式LoadBalancerClient,Gateway建议使用ReactiveLoadBalancerClientFilter。

以下通过重写LoadBalancerClientFilter实现。

重写代码

java 复制代码
public class IdUploadLoadBalancerClientFilter extends LoadBalancerClientFilter {


    private static final Log log = LogFactory.getLog(IdUploadLoadBalancerClientFilter.class);
    private static final String ID_INTEGRATION = "id-integration";


    private static final List<String> UPLOAD_URL_LIST = Lists.newArrayList("/v5/base/access/data/multipart/upload", "/v5/base/access/data/multipart/merge");

    private LoadBalancerProperties properties;

    @Resource
    private NacosDiscoveryClient nacosDiscoveryClient;


    public IdUploadLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
        super(loadBalancer, properties);
        this.properties = properties;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
        String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
        if (url == null
                || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
            return chain.filter(exchange);
        }
        // preserve the original url
        addOriginalRequestUrl(exchange, url);

        if (log.isTraceEnabled()) {
            log.trace("LoadBalancerClientFilter url before: " + url);
        }

        ServiceInstance instance = choose(exchange);
        String serviceId = instance.getServiceId();
        //判断当前实例id是否为id-integration
        if (StringUtils.equalsIgnoreCase(serviceId, ID_INTEGRATION)) {
            ServerHttpRequest request = exchange.getRequest();
            HttpMethod httpMethod = request.getMethod();
            String path = request.getURI().getPath();
            String contentType = String.join(";", Objects.requireNonNull(request.getHeaders().get("Content-Type")));
            if (httpMethod != null && httpMethod.matches(HttpMethod.POST.name()) && UPLOAD_URL_LIST.contains(path)) {
                boolean isUploadLoad = StringUtils.containsIgnoreCase(contentType, MediaType.MULTIPART_FORM_DATA_VALUE);
                String fileMd5 = null;
                if (isUploadLoad) {
                    fileMd5 = String.join(";", Objects.requireNonNull(request.getHeaders().get("fileMd5")));
                } else {
                    List<String> fileMd5List = request.getQueryParams().get("fileMd5");
                    if (fileMd5List != null && fileMd5List.size() > 0) {
                        fileMd5 = fileMd5List.get(0);
                    }
                }

                if (StringUtils.isNotBlank(fileMd5)) {
                    List<ServiceInstance> instances = nacosDiscoveryClient.getInstances(ID_INTEGRATION);
                    int targetIndex = Math.abs(fileMd5.hashCode()) % instances.size();
                    ServiceInstance serviceInstance = instances.get(targetIndex);
                    RibbonLoadBalancerClient.RibbonServer ribbonServer = (RibbonLoadBalancerClient.RibbonServer) instance;
                    NacosServer nacosServer = (NacosServer) ribbonServer.getServer();
                    nacosServer.setHost(serviceInstance.getHost());
                    nacosServer.setPort(serviceInstance.getPort());
                    Map<String, String> metadata = ribbonServer.getMetadata();
                    boolean secure = ribbonServer.isSecure();
                    instance = new RibbonLoadBalancerClient.RibbonServer(serviceId, nacosServer, secure, metadata);
                }
            }
        }


        if (instance == null) {
            throw NotFoundException.create(properties.isUse404(),
                    "Unable to find instance for " + url.getHost());
        }

        URI uri = exchange.getRequest().getURI();

        // if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
        // if the loadbalancer doesn't provide one.
        String overrideScheme = instance.isSecure() ? "https" : "http";
        if (schemePrefix != null) {
            overrideScheme = url.getScheme();
        }

        URI requestUrl = loadBalancer.reconstructURI(
                new DelegatingServiceInstance(instance, overrideScheme), uri);

        if (log.isTraceEnabled()) {
            log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
        }

        exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
        return chain.filter(exchange);
    }


}

bean配置

java 复制代码
@Bean
 public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client,
                                                             LoadBalancerProperties properties) {
        return new IdUploadLoadBalancerClientFilter(client, properties);
 }
相关推荐
迪康Defender4 小时前
迪康端点安全一体化管理系统文档安全:构建企业文档资产全链路防护体系
运维·网络·安全·web安全·终端安全管理
zhangguojia74 小时前
权限弹窗点击允许的授权流程
运维·服务器·数据库
AI备忘录4 小时前
(十九)华为华三锐捷迈普思科 交换机链路聚合配置命令(LACP/静态聚合五厂商对照)
运维·服务器·网络·网络协议·tcp/ip·华为
lucybean014 小时前
抗老化曝气管采购:主流品牌优劣及选型策略深度解析
运维
come112344 小时前
剪映零基础教程
java·linux·运维
瀚高PG实验室4 小时前
HAC 集群主节点状态在starting、running之间频繁切换
运维·数据库·postgresql·瀚高数据库
其实防守也摸鱼4 小时前
教育信息技术应用创新---基础软件信息赛
运维·服务器·数据库·github·copilot
苏生Susheng4 小时前
【软件实施】Windows 服务器运维实战
java·运维·服务器·windows·spring boot·javaweb·实施
Gauss松鼠会5 小时前
【GaussDB】GaussDB 组件、节点和AZ故障仲裁与切换流程
运维·服务器·数据库·gaussdb
数字孪生视频孪生5 小时前
空间智能技术|跨镜轨迹全域可溯,无感定位无扰赋能落地
大数据·运维·人工智能·重构·架构