Feign自定义调用第三方接口并实现负载均衡

news2024/11/20 14:42:53

Feign自定义调用第三方接口并实现负载均衡

Feign简介:

Feign 是一个声明式的、模板化的HTTP客户端,用于简化HTTP客户端的开发。它是Spring Cloud Netflix微服务套件中的一部分,使得编写Java HTTP客户端变得更加容易。它的原理主要是代理模式的实现,用户只需要定义调用的HTTP接口,调用的具体逻辑由Feign框架调用接口的代理实现。Feign的主要特点如下:

  1. 声明式REST客户端:Feign 允许开发者通过接口和注解的方式定义一个服务客户端,而不需要编写实际的HTTP请求代码。
  2. 集成 Ribbon:Feign 集成了负载均衡器 Ribbon,可以提供客户端的负载均衡功能,这使得Feign客户端在调用服务时能够自动进行服务实例的选择。
  3. 集成 Hystrix:Feign 可以与断路器 Hystrix 集成,提供服务调用的容错机制,当服务调用失败时,可以执行回退策略。
  4. 注解驱动:Feign 使用注解来简化服务调用,常见的注解包括:
    • @FeignClient:定义一个Feign客户端。
    • @RequestMapping:映射HTTP请求到方法上。
    • @PathVariable@RequestParam@RequestHeader:用于处理路径变量、请求参数和请求头。
  5. 可定制性:Feign 允许自定义编码器、解码器、错误处理等,可以根据需要进行扩展和定制。

在使用SpringCloud进行分布式开发时,Feign通常作为服务之间调用的组件。但是Feign也可以通过设置URL,实现调用第三方接口的功能。本文实现了用Feign调用第三方接口并实现负载均衡。

使用到的依赖

  	   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
            <version>13.2.1</version>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
            <version>13.3</version>
        </dependency>
       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
            <version>3.1.1</version>
        </dependency>

Feign如何实现调用第三方接口?

只需在加了@FeignClient的接口上设置URL参数即可。

@FeignClient(value = "api-service", url = "127.0.0.1:18080"
        , fallback = ApiServiceHystrix.class, configuration = {ThirdServiceInterceptor.class})

Feign如何调用第三方接口的负载均衡?

我们知道,Feign 其实是集成了负载均衡器 Ribbon 的,但是 Ribbon 的使用必须在微服务体系内部才能实现,在调用第三方接口时就不能满足需求了。本文主要使用Feign的拦截器功能实现负载均衡,通过实现RequestInterceptor接口,我们就可以在拦截器中添加负载均衡的逻辑。最后,在@FeignClient上将实现的拦截器配置到configuration属性上。拦截器代码如下:

import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Target;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;

/**
 * @Author: wangrongyi
 * @Date: 2024/7/15 13:37
 * @Description:
 */
@Slf4j
public class ThirdServiceInterceptor implements RequestInterceptor {

    @Resource
    private LoadBalanceService loadBalanceService;

    @Override
    public void apply(RequestTemplate requestTemplate) {

        Target.HardCodedTarget<?> target = (Target.HardCodedTarget<?>) requestTemplate.feignTarget();

        // 反射设置target属性
        String addr = loadBalanceService.getServerAddress();

        try {
            log.info("请求地址:{}", addr);
            Field field = Target.HardCodedTarget.class.getDeclaredField("url");
            field.setAccessible(true);
            field.set(target, addr);

        } catch (NoSuchFieldException | IllegalAccessException e) {
            log.error("设置url失败", e);
        }
    }
}

注意:因为Feign调用中,代理对象的请求地址是final修饰的,所以只能通过反射将负载均衡后得到的地址设置到url中。

image-20240728130821209

负载均衡算法的实现

为了实现通过配置选取不同的负载均衡算法,这里通过三步操作实现负载均衡算法:

  1. 定义配置类。配置文件中进行配置,算法名称为算法类的全路径名。

    /**
     * @Author: wangrongyi
     * @Date: 2024/7/24 15:30
     * @Description: 负载均衡配置参数
     */
    @Component("loadBalanceProperties")
    @ConfigurationProperties(prefix = "load.balance.config")
    @Data
    public class LoadBalanceProperties {
    
        /**
         * 算法名称
         */
        private String algorithm;
        /**
         * 服务地址
         */
        private List<String> address;
        /**
         * 服务地址权重配置
         */
        private Map<String, Integer> weight;
        
    }
    
    load:
      balance:
        config:
          algorithm: com.wry.wry_test.feign.config.RoundRobin
          address:
            - http://127.0.0.1:18080
            - http://127.0.0.1:18081
            - http://127.0.0.1:18082
          weight:
            '[http://127.0.0.1:18080]': 1
            '[http://127.0.0.1:18081]': 2
            '[http://127.0.0.1:18082]': 3
    
  2. 定义算法接口。并实现不同的负载均衡算法。本文实现了四种负载均衡算法,根据具体使用场景进行切换。轮询算法(RoundRobin)、加权轮询算法(WeightedRoundRobin)、随机算法(RandomAlgo)、加权随机算法(WeightRandom)。

    /**
     * @Author: wangrongyi
     * @Date: 2024/7/24 15:52
     * @Description: 负载均衡算法接口
     */
    public interface LoadBalanceService {
    
        /**
         * 获取服务地址
         * @return 负载均衡后的服务地址
         */
        String getServerAddress();
    }
    

    四种实现类:

    轮询算法(RoundRobin):

    /**
     * @Author: wangrongyi
     * @Date: 2024/7/24 15:56
     * @Description: 轮询算法
     */
    @Slf4j
    public class RoundRobin implements LoadBalanceService {
        private final LoadBalanceProperties properties;
    
        public RoundRobin(LoadBalanceProperties loadBalanceProperties) {
            this.properties = loadBalanceProperties;
        }
    
        private final AtomicInteger index = new AtomicInteger(0);
    
        @Override
        public synchronized String getServerAddress() {
            if (properties.getAddress().isEmpty()) {
                throw new RuntimeException("服务地址为空");
            }
            int i = index.get() % properties.getAddress().size();
            index.set((i + 1) % properties.getAddress().size());
            return properties.getAddress().get(i);
        }
    }
    

    加权轮询算法(WeightedRoundRobin)

    /**
     * @Author: wangrongyi
     * @Date: 2024/7/24 17:36
     * @Description: 加权轮询算法
     */
    public class WeightedRoundRobin implements LoadBalanceService {
        private final LoadBalanceProperties properties;
    
        public WeightedRoundRobin(LoadBalanceProperties loadBalanceProperties) {
            this.properties = loadBalanceProperties;
        }
    
        private int weightCount = 0;
        private int index = 0;
    
        @Override
        public synchronized String getServerAddress() {
            int weight = properties.getWeight().get(properties.getAddress().get(index));
            if (weightCount == weight) {
                weightCount = 0;
                index = (index + 1) % properties.getAddress().size();
            }
            weightCount++;
            return properties.getAddress().get(index);
    
        }
    }
    

    随机算法(RandomAlgo)

    /**
     * @Author: wangrongyi
     * @Date: 2024/7/24 17:28
     * @Description: 随机算法
     */
    public class RandomAlgo implements LoadBalanceService {
        private final LoadBalanceProperties properties;
    
        public RandomAlgo(LoadBalanceProperties loadBalanceProperties) {
            this.properties = loadBalanceProperties;
        }
    
        @Override
        public synchronized String getServerAddress() {
            if (properties.getAddress().isEmpty()) {
                throw new RuntimeException("服务地址为空");
            }
            int index = new Random().nextInt(properties.getAddress().size());
            return properties.getAddress().get(index);
        }
    }
    

    加权随机算法(WeightRandom)

    /**
     * @Author: wangrongyi
     * @Date: 2024/7/24 16:16
     * @Description: 加权随机算法
     */
    public class WeightRandom implements LoadBalanceService {
        private final LoadBalanceProperties properties;
    
        private final Random random = new Random();
    
        public WeightRandom(LoadBalanceProperties loadBalanceProperties) {
            this.properties = loadBalanceProperties;
        }
    
        @Override
        public synchronized String getServerAddress() {
    
            int totalWeight = 0;
            for (Integer value : properties.getWeight().values()) {
                totalWeight += value;
            }
            int randomWeight = random.nextInt(totalWeight);
            for (Map.Entry<String, Integer> entry : properties.getWeight().entrySet()) {
                randomWeight -= entry.getValue();
                if (randomWeight < 0) {
                    return entry.getKey();
                }
            }
            return null;
        }
    }
    
  3. 增加配置类,将配置的算法类注入到spring容器中

    /**
     * @Author: wangrongyi
     * @Date: 2024/7/24 16:16
     * @Description: 负载均衡算法注入配置
     */
    @Configuration
    @Slf4j
    public class LoadBalanceConfig {
    
        @Bean
        public LoadBalanceService loadBalanceService(LoadBalanceProperties loadBalanceProperties) {
            String className = loadBalanceProperties.getAlgorithm();
            // 反射加载负载均衡算法类
            try {
                Class<?> clazz = Class.forName(className);
                return (LoadBalanceService) clazz.getConstructor(LoadBalanceProperties.class).newInstance(loadBalanceProperties);
            } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
                     InvocationTargetException e) {
                log.error("算法注入失败 class path = {}", className);
                throw new RuntimeException(e);
            }
        }
    }
    

==注音:==因为这里采用配置的全路径名加载算法类,所以当实现了新的算法类时只需在配置文件中配置即可。

定义Feign接口实现负载均衡调用

/**
 * @Author: wangrongyi
 * @Date: 2024/7/12 17:08
 * @Description: 调用平台接口
 */
@FeignClient(value = "api-service", url = "127.0.0.1:18080"
        , fallback = ApiServiceHystrix.class, configuration = {ThirdServiceInterceptor.class})
public interface ApiServiceFeign {

    /**
     * 平台接口预览 方法名可以随便取
     */
    @GetMapping("/iServerOpenApi/query")
    Response getAllUrl(@RequestParam("path") String path,
                       @RequestParam("method") String method);

}

这里配置了熔断降级,如果有需要,可以在这里实现不可用地址的剔除策略。比如某个地址多次调用不成功,便可以把这个地址从配置类中删除,避免再次路由到这个地址上。值得注意的是:如果加上地址剔除策略,那么在某些地方可能就需要考虑一下并发问题。

/**
 * @Author: wangrongyi
 * @Date: 2024/7/12 17:08
 * @Description:
 */
@Slf4j
@Component
public class ApiServiceHystrix implements ApiServiceFeign {
    
    @Override
    public Response getAllUrl(String path, String method) {
        // 设置调用失败时的降级处理
        log.error("远程调用失败!");
        return null;
    }
}

完整配置如下:

关于熔断配置这里也有需要注意的地方,我在这里踩坑了,花了几个小时才解决。在网上看,熔断配置只需要这样配置hystrix:enabled:true就可以了,但是我怎么配置都不起作用,后来发现新版本依赖引错了,应该引入spring-cloud-starter-circuitbreaker-resilience4j这个,并将配置改成circuitbreaker:enabled:true

spring:
  cloud:
    openfeign:
      circuitbreaker:
        enabled: true

完整配置:

server:
  port:
    18888
spring:
  cloud:
    openfeign:
      circuitbreaker:
        enabled: true
      httpclient:
        enabled: true # HttpClient的开关
        max-connections: 200  # 线程池最大连接数
        max-connections-per-route: 50   # 单个请求路径的最大连接数
      okhttp:
        enabled: true
load:
  balance:
    config:
      algorithm: com.wry.wry_test.feign.config.RoundRobin
      address:
        - http://127.0.0.1:18080
        - http://127.0.0.1:18081
        - http://127.0.0.1:18082
      weight:
        '[http://127.0.0.1:18080]': 1
        '[http://127.0.0.1:18081]': 2
        '[http://127.0.0.1:18082]': 3

实现结果如下

image-20240728134752382

这里配置的是轮询算法,可以看到请求以此路由到不同的地址上了。可以通过配置或实现不同的算法,关于常见的路由算法,可以看这里:Java中如何实现负载均衡策略_java负载均衡-CSDN博客

点个关注不迷路:Snipaste_2024-07-16_18-11-27

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1954790.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Rust |了解+ 环境配置(rust+vscode)

1 了解rust 1️⃣0️⃣0️⃣秒了解Rust_哔哩哔哩_bilibili 2 安装rust 前提安装过vs&#xff0c;有c环境 1.下载 根据自己的系统下载对应的版本&#xff1a;安装地址 查看自己版本&#xff1a; 右键 此电脑 &#xff1b;点击 属性 &#xff1b;查看 系统类型 点击 下载RU…

智慧城管解决方案

1. 项目整体概述 智慧城管项目面临历史发展机遇&#xff0c;十九大提出以人为核心保障民生&#xff0c;推进新型城镇化。市民对政府服务有新诉求&#xff0c;同时云计算、物联网、移动互联网等技术迅速发展。 2. 传统城管业务模式问题 传统城管业务模式存在问题&#xff0c;…

树莓派学习记录

一&#xff0c;型号 第一代Raspberry Pi 1 Model B 第一代升级版 Raspberry Pi 1 B 第二代 Rasberry Pi 2 Model B 第三代及升级版 Rasberry Pi 3 Model B/B 第四代 Rasberry Pi 4 Model B Model A版 比B版便宜 Zero 版 售价更便宜 总结 二&#xff0c;树莓派接口 如下图…

Hello 算法:动画图解、一键运行的数据结构与算法教程

Hello 算法 《Hello 算法》是一份开源、免费的数据结构与算法入门教程&#xff0c;特别适合新手。全书采用动画图解&#xff0c;内容清晰易懂&#xff0c;学习曲线平滑&#xff0c;引导初学者探索数据结构与算法的知识地图。源代码可以一键运行&#xff0c;帮助读者通过练习提…

【教学类-70-01】20240728一个茶壶两个茶杯(果茶)

‘ 背景需求&#xff1a; 用通义万相下载简笔画茶壶、茶杯 茶杯&#xff0c;简单笔画&#xff0c;卡通&#xff0c;黑白&#xff0c;未着色&#xff0c;幼儿插图&#xff0c;线条画&#xff0c;没有背景&#xff0c;没有颜色&#xff0c;黑白漫画线条艺术:&#xff0c;空背景…

JAVAWeb实战(后端篇)

因为前后端代码内容过多&#xff0c;这篇只写后端的代码&#xff0c;前端的在另一篇写 项目实战一&#xff1a; 1.创建数据库,表等数据 创建数据库 create database schedule_system 创建表&#xff0c;并添加内容 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------…

十一、Dockerfile解析

目录 一、Dockerfile简介 二、dockerfile的构建的三个步骤 三、Dockerfile的构建过程 1、DockerFile内容的基础知识 2、Docker执行Dockerfile的大致流程 四、dockerfile常用的保留字 1、FROM 2、MAINTAINER 3、RUN 4、EXPOSE 5、WORKDIR 6、USER 7、ENV 8、VOLUME …

VScode使用Github Copilot插件时出现read ECONNREST问题的解决方法

文章目录 read ECONNREST查看是否仍是 Copilot 会员查看控制台输出网络连接问题浏览器设置问题笔者的话 read ECONNREST 最近使用 Copilot 时一直出现 read ECONNREST 问题&#xff0c;这个表示连接被对方重置了&#xff0c;就是说在读取数据时连接被关闭。 我首先怀疑是不是…

QT常用的控件

QT常用控件 一.控件概述二.QWidget 核心属性2.1 核心属性概览2.2 enabled代码示例&#xff1a;使用代码创建一个禁用状态的按钮代码示例: 通过按钮2 切换按钮1 的禁用状态. 2.3 geometry代码示例: 控制按钮的位置代码示例: ⼀个随机按钮程序代码示例: 感受 geometry 和frameGeo…

Python | TypeError: ‘NoneType’ object is not iterable

Python | TypeError: ‘NoneType’ object is not iterable 在Python编程中&#xff0c;TypeError: NoneType object is not iterable 是一个常见的错误&#xff0c;通常表明你尝试对一个值为 None 的对象进行迭代操作&#xff0c;而 None 类型的对象是不可迭代的。本文将深入…

python count返回什么

描述 count() 方法用于统计字符串中某个子字符串出现的次数&#xff0c;可选参数为开始搜索与结束搜索的位置索引。 语法 count() 方法语法&#xff1a; S.count(sub[,start0[,endlen(S)]]) 参数 sub -- 搜索的子字符串。 S -- 父字符串。 start -- 可选参数&#xff0c;…

Windows11和Win10如何彻底永久关闭Windows defender

Windows11和Win10如何彻底永久关闭Windows defender 亲测简单快捷有效关闭病毒和威胁防护。 使用关闭windows defender工具。 随时可以重新开启病毒和威胁防护。

【Spring Boot】Spring 的安全框架:Spring Security

Spring 的安全框架&#xff1a;Spring Security 1.Spring Security 初识1.1 核心概念1.2 认证和授权1.2.1 验证&#xff08;authentication&#xff09;1.2.2 授权&#xff08;authorization&#xff09; 1.3 模块 2.核心类2.1 Securitycontext2.2 SecurityContextHolder2.2.1 …

刷题心得之位运算技巧 gcd 和 lcm

位运算 gcd 和 lcm 位运算技巧遍历 n 的所有子集, 不包括空集遍历 n 的所有子集, 包括空集提取出 n 二进制中第一次出现的1消除 n 二进制中第一次出现的1判断 n 是否是 2 的幂次方 gcd [最大公约数]lcm [最小公倍数] 位运算技巧 遍历 n 的所有子集, 不包括空集 #include <…

52.TFT_LCD液晶屏字符显示

&#xff08;1&#xff09;实验目标&#xff1a;在5寸显示屏&#xff08;800 * 480 60&#xff09;中央显示汉字&#xff08;黄色&#xff09;&#xff0c;且背景颜色为青色。 &#xff08;2&#xff09;代码编写&#xff1a; tft_ctrl模块&#xff1a; module tft_ctrl(inp…

电子电气架构---域控制器的软硬件趋势

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

TiDB-从0到1-sync-diff-inspector数据校验工具

TiDB从0到1系列 TiDB-从0到1-体系结构TiDB-从0到1-分布式存储TiDB-从0到1-分布式事务TiDB-从0到1-MVCCTiDB-从0到1-部署篇TiDB-从0到1-配置篇TiDB-从0到1-集群扩缩容TiDB-从0到1-数据导出导入TiDB-从0到1-BR工具 一、sync-diff-inspector工具 sync-diff-inspector是TiDB原生…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第六十二章 定时器按键消抖实验

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

详解Mysql InnoDB引擎 04

文章目录 1. InnoDB 简介2. 逻辑存储结构2.1 表空间 idb文件2.2 段2.3 区 1M2.4 页 16KB2.5 行 3. 架构3.1 内存结构3.1.1 Buffer Pool 缓冲池3.1.2 Change Buffer 更改缓冲区3.1.3 Adaptive Hash Index3.1.4 Log Buffer 3.2 磁盘结构 4. 后台线程5. 事务原理5.1 redo log 重做…

Pytorch深度学习实践(9)卷积神经网络

卷积神经网络 全连接神经网络 神经网络中全部是线性模型&#xff0c;是由线性模型串联起来的 全连接网络又叫全连接层 卷积神经网络 在全连接神经网络中&#xff0c;由于输入必须是一维向量&#xff0c;因此在处理图像时必须要对图像矩阵进行拉伸成一维的形式&#xff0c;…