Feign自定义调用第三方接口并实现负载均衡
Feign自定义调用第三方接口并实现负载均衡
Feign简介:
Feign 是一个声明式的、模板化的HTTP客户端,用于简化HTTP客户端的开发。它是Spring Cloud Netflix微服务套件中的一部分,使得编写Java HTTP客户端变得更加容易。它的原理主要是代理模式的实现,用户只需要定义调用的HTTP接口,调用的具体逻辑由Feign框架调用接口的代理实现。Feign的主要特点如下:
- 声明式REST客户端:Feign 允许开发者通过接口和注解的方式定义一个服务客户端,而不需要编写实际的HTTP请求代码。
- 集成 Ribbon:Feign 集成了负载均衡器 Ribbon,可以提供客户端的负载均衡功能,这使得Feign客户端在调用服务时能够自动进行服务实例的选择。
- 集成 Hystrix:Feign 可以与断路器 Hystrix 集成,提供服务调用的容错机制,当服务调用失败时,可以执行回退策略。
- 注解驱动:Feign 使用注解来简化服务调用,常见的注解包括:
@FeignClient
:定义一个Feign客户端。@RequestMapping
:映射HTTP请求到方法上。@PathVariable
、@RequestParam
、@RequestHeader
:用于处理路径变量、请求参数和请求头。
- 可定制性: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中。
负载均衡算法的实现
为了实现通过配置选取不同的负载均衡算法,这里通过三步操作实现负载均衡算法:
-
定义配置类。配置文件中进行配置,算法名称为算法类的全路径名。
/*** @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
-
定义算法接口。并实现不同的负载均衡算法。本文实现了四种负载均衡算法,根据具体使用场景进行切换。轮询算法(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;} }
-
增加配置类,将配置的算法类注入到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
实现结果如下
这里配置的是轮询算法,可以看到请求以此路由到不同的地址上了。可以通过配置或实现不同的算法,关于常见的路由算法,可以看这里:Java中如何实现负载均衡策略_java负载均衡-CSDN博客
点个关注不迷路: