首页 > 其他分享 >通过自定义feignclient 的LoadBalancerFeignClient实现灵活的负载均衡策略

通过自定义feignclient 的LoadBalancerFeignClient实现灵活的负载均衡策略

时间:2024-12-01 15:10:21浏览次数:5  
标签:feignclient 自定义 url upServers import org LoadBalancerFeignClient 节点

通过自定义feignclient 的LoadBalancerFeignClient 或IRule 能实现完全自定义的负载均衡策略,本文主要是通过实现自定义的LoadBalancerFeignClient而达到自定义的负载均衡策略

示例代码实现如下:

package cn.zuowenjun.demo;

import com.netflix.loadbalancer.Server;
import feign.Client;
import feign.Request;
import feign.Response;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.cloud.netflix.feign.ribbon.CachingSpringLoadBalancerFactory;
import org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

/**
 * FeignClient 服务BEAN(含自定义负载均衡请求机制),注意这里指定了configuration的类型
 */
@FeignClient(value ="zuowenjun-demo" , configuration = {DemoProviderDispatchService.Config.class})
public interface DemoProviderDispatchService {
    Logger LOGGER = LoggerFactory.getLogger(FileFmsDispatchService.class);

    //要RPC请求的接口,需要自定义负载均衡
    @RequestMapping(value = "/fileContent/compare", method = RequestMethod.POST)
    ResponseData<Integer> compare(@RequestBody FileBillCompareBO billCompareBO);

    /**
     * DemoProviderDispatchService 专用的配置类,在这个配置类里面添加的BEAN均可替换全局默认的BEAN
     * 注意:此处不能加上@Configuration,否则将变成全局配置了
     */
    static class Config {
        /**
         * 为FileFmsDispatchClient 重新定义专用的Client,在里面实现自定义的URL请求
         *
         * @param cachingFactory
         * @param clientFactory
         * @return
         */
        @Bean
        public Client requestClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) {

            //获取当前节点的ID与端口
            String currentIpAndPort = CommonUtils.getCurrentIpAndPort();

            //构建返回一个完全自定义的LoadBalancerFeignClient
            return new LoadBalancerFeignClient(new Client.Default(null, null) {
                @Override
                public Response execute(Request request, Request.Options options) throws IOException {
                    //通过重新创建Request,将Request 中的url指向自定义负载均衡的选中的URL
                    Request newRequest = reCreateRequestForLoadBalance(request);
                    if (newRequest == null) {
                        return Response.builder().reason("服务器繁忙,当前无可用服务节点").status(204).build();
                    }

                    return super.execute(newRequest, options);
                }

                private Request reCreateRequestForLoadBalance(Request request) throws IOException {
                    URL url = new URL(request.url());
                    //获取可用的服务实例节点列表
                    List<Server> upServers = clientFactory.getLoadBalancer(“zuowenjun-demo”).getReachableServers();
                    Server bestServer = choose(url, upServers, url.getFile());
                    if (bestServer == null) {
                        //找不到最佳可用服务器节点,说明所有服务节点都压力山大
                        return null;
                    }

                    url = new URL(url.getProtocol(), bestServer.getHost(), bestServer.getPort(), url.getFile());
                    return Request.create(request.method(), url.toString(), request.headers(), request.body(), request.charset());
                }


                /**
                 * 选择最优的服务节点
                 * @param url
                 * @param upServers
                 * @param apiPath
                 * @return
                 */
                private Server choose(URL url, List<Server> upServers, String apiPath) {

                    if (CollectionUtils.isEmpty(upServers)) {
                        throw new ApplicationException(500, "从注册中心获取不到可用的服务节点信息");
                    }

                    apiPath=apiPath.startsWith("/")?apiPath.substring(1):apiPath;
                    String hashKey = Constants.LOADBALANCE_API_PREFIX + apiPath.replace("/", "_");
                    Boolean existRequest = RedisUtils.existHashKey(hashKey, String.format("%s:%s", url.getHost(), url.getPort()));
                    Server bestServer = null;
                    if (!Boolean.TRUE.equals(existRequest)) {
                        //如果当前即将请求的URL的节点之前没有缓存标记请求处理中时,则可直接复用返回
                        bestServer = upServers.stream().filter(s -> s.getHost().equals(url.getHost()) && s.getPort() == url.getPort()).findFirst().orElse(null);
                        if (bestServer != null) {
                            return bestServer;
                        }
                    }

                    //先从缓存中找出当前API 的请求中的节点列表
                    Map<Object, Object> existRequestMap = RedisUtils.getHashEntries(hashKey);
                    Set<Object> existRequestIpAndPorts = new HashSet<>();
                    if (MapUtils.isNotEmpty(existRequestMap)) {
                        existRequestIpAndPorts.addAll(existRequestMap.keySet());
                    }

                    //排除API请求中的节点,保留空闲节点列表
                    upServers = upServers.stream().filter(s -> !existRequestIpAndPorts.contains(s.getHostPort()) && !s.getHostPort().equals(currentIpAndPort)).collect(Collectors.toList());
                    if (CollectionUtils.isEmpty(upServers)) {
                        //说明当前所有节点全部都有处理请求中,无空闲节点
                        return null;
                    }

                    //从空闲节点列表中随机返回一个节点
                    int rndNo = new Random().nextInt(upServers.size());
                    bestServer = upServers.get(rndNo);
                    LOGGER.debug("DemoProviderDispatchService.Config.requestClient.Client#choose {}", bestServer.getHostPort());
                    return bestServer;
                }

            }, cachingFactory, clientFactory);
        }


    }

}

调用时就正常注入DemoProviderDispatchService BEAN,并使用:demoProviderDispatchService.compare(...) 即可实现在自定义负载均衡的策略下请求远程服务API,这种自定义的负载均衡策略可以满足特定的性能要求

标签:feignclient,自定义,url,upServers,import,org,LoadBalancerFeignClient,节点
From: https://www.cnblogs.com/zuowj/p/18579778

相关文章

  • 从 0 到 1 制作自定义镜像并用于训练(MPI+CPU/GPU)
    本章节介绍如何从0到1制作镜像,并使用该镜像在ModelArts平台上进行训练。镜像中使用的AI引擎是MPI,训练使用的资源是CPU或GPU。说明:本实践教程仅适用于新版训练作业。场景描述本示例使用Linuxx86_64架构的主机,操作系统ubuntu-18.04,通过编写Dockerfile文件制作自定义镜像。......
  • 从 0 到 1 制作自定义镜像并用于训练(Pytorch+CPU/GPU)
    本章节介绍如何从0到1制作镜像,并使用该镜像在ModelArts平台上进行训练。镜像中使用的AI引擎是PyTorch,训练使用的资源是CPU或GPU。说明:本实践教程仅适用于新版训练作业。场景描述本示例使用Linuxx86_64架构的主机,操作系统ubuntu-18.04,通过编写Dockerfile文件制作自定义镜像......
  • 通过代码实现log4net自定义配置
    大家在使用log4net的时候,常规的用法都是在配置文件里面进行设置。但是配置文件里面的配置项非常多,不利于记忆,所以说我们希望他能直接在代码中设置。于是,我写了个自定义日志配置的方法,核心的配置对象为RollingFileAppender,只需要对他进行设置就可以了。下面给大家展示下,基于......
  • .NET 项目自定义 MSBuild Task
    ......
  • vue3实现自定义导航菜单
    一、创建项目    1.打开HBuilderX图1    2.新建一个空项目        文件->新建->项目->uni-app        填写项目名称:vue3demo        选择项目存放目录:D:/HBuilderProjects        一定要注意vue的版本,当前选择的版......
  • easyexcel导出头部样式设置,多个tab导出,头部自定义RGB颜色
    alibabaeasyexcel版本3.0.5,poi版本4.1.2,导出头部样式设置,多个tab导出,头部自定义RGB颜色 效果,头部三行,三个tab  下面贴出代码:packagecom.alpha.erp.dto.accounts;importcom.alibaba.excel.metadata.Head;importcom.alibaba.excel.metadata.data.WriteCellDa......
  • Unity Mask原理及自定义遮罩
    主要内容StencilBuffer是什么?自定义Shader来实现遮罩UnityMask的原理1.什么是StencilBufferGPU在渲染前会为每个像素点分配一个1字节(8位)大小的内存区域,即StencilBuffer。在决定是否要渲染某个像素点之前,会将它当前的StencilBuffer的值与某个参考值(stencilID)进行指定......
  • gofiber: 用go-playground/validator校验参数,自定义错误信息
    一,go-playground/validator官方代码地址https://github.com/go-playground/validator二,安装$goget-ugithub.com/go-playground/validator/v10go:downloadinggithub.com/go-playground/validator/v10v10.23.0go:downloadinggithub.com/gabriel-vasile/mimetypev1.4.......
  • ios短视频开发,自定义缓存策略的实现
    ios短视频开发,自定义缓存策略的实现缓存所占用的空间往往会成为迫使用户卸载应用的最后一根稻草。开发者不能无上限对音视频资源进行缓存,通常的维护手法是通过限制空间大小,比如,用户通常可以接受视频类应用有1G左右的缓存空间,即时通信类应用也许会更大些。因此在ios短视频开发......
  • uni-app vue3 获取 package.json 自定义环境变量
    一、初始化项目 二、添加 package.json 文件(必须)注意:文件里面不要写备注{ "uni-app":{ "scripts":{ "dev":{ "title":"开发版", "env":{ "ENV_TYPE":"dev", "UNI_PLATFORM&q......