当前位置: 首页 > news >正文

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

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 {@Resourceprivate LoadBalanceService loadBalanceService;@Overridepublic 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.RoundRobinaddress:- http://127.0.0.1:18080- http://127.0.0.1:18081- http://127.0.0.1:18082weight:'[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);@Overridepublic 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;@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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 {@Beanpublic 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 {@Overridepublic 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: truehttpclient:enabled: true # HttpClient的开关max-connections: 200  # 线程池最大连接数max-connections-per-route: 50   # 单个请求路径的最大连接数okhttp:enabled: true
load:balance:config:algorithm: com.wry.wry_test.feign.config.RoundRobinaddress:- http://127.0.0.1:18080- http://127.0.0.1:18081- http://127.0.0.1:18082weight:'[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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Ansible的脚本-----playbook剧本【下】
  • Mac m1安装 MongoDB 7.0.12
  • 一款好看的某社区/空间/论坛/官方软件下载页源码
  • JDBC(Java访问数据库)
  • 【ESP01开发实例】-驱动OLED SSD1306显示屏
  • Web安全:Web体系架构存在的安全问题和解决方室
  • 视觉巡线小车(STM32+OpenMV)——总结
  • Dify中HTTP请求节点的常见操作
  • 数据url
  • C++中 cin、cin.get()、cin.getline()、getline() 的区别
  • Blender材质-PBR与纹理材质
  • scratch聊天机器人 2024年6月scratch四级 中国电子学会图形化编程 少儿编程等级考试四级真题和答案解析
  • sql server 连接报错error 40
  • 基于内容的音乐推荐网站/基于ssm的音乐推荐系统/基于协同过滤推荐的音乐网站/基于vue的音乐平台
  • 基于Element UI内置的Select下拉和Tree树形组件,组合封装的树状下拉选择器
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • 《用数据讲故事》作者Cole N. Knaflic:消除一切无效的图表
  • 【391天】每日项目总结系列128(2018.03.03)
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • 【技术性】Search知识
  • 11111111
  • AngularJS指令开发(1)——参数详解
  • JDK9: 集成 Jshell 和 Maven 项目.
  • Mysql优化
  • Promise面试题2实现异步串行执行
  • spring cloud gateway 源码解析(4)跨域问题处理
  • tensorflow学习笔记3——MNIST应用篇
  • 表单中readonly的input等标签,禁止光标进入(focus)的几种方式
  • 产品三维模型在线预览
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 收藏好这篇,别再只说“数据劫持”了
  • 腾讯大梁:DevOps最后一棒,有效构建海量运营的持续反馈能力
  • 小程序上传图片到七牛云(支持多张上传,预览,删除)
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 找一份好的前端工作,起点很重要
  • 走向全栈之MongoDB的使用
  • 组复制官方翻译九、Group Replication Technical Details
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​十个常见的 Python 脚本 (详细介绍 + 代码举例)
  • #{}和${}的区别是什么 -- java面试
  • $(this) 和 this 关键字在 jQuery 中有何不同?
  • (30)数组元素和与数字和的绝对差
  • (4) PIVOT 和 UPIVOT 的使用
  • (6) 深入探索Python-Pandas库的核心数据结构:DataFrame全面解析
  • (day 12)JavaScript学习笔记(数组3)
  • (html转换)StringEscapeUtils类的转义与反转义方法
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (四)进入MySQL 【事务】
  • (未解决)macOS matplotlib 中文是方框
  • (五)IO流之ByteArrayInput/OutputStream
  • (转)大型网站的系统架构
  • .a文件和.so文件
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .NET 8 跨平台高性能边缘采集网关
  • .net core 3.0 linux,.NET Core 3.0 的新增功能