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 {
@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中。
负载均衡算法的实现
为了实现通过配置选取不同的负载均衡算法,这里通过三步操作实现负载均衡算法:
-
定义配置类。配置文件中进行配置,算法名称为算法类的全路径名。
/** * @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
-
定义算法接口。并实现不同的负载均衡算法。本文实现了四种负载均衡算法,根据具体使用场景进行切换。轮询算法(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; } }
-
增加配置类,将配置的算法类注入到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
实现结果如下
这里配置的是轮询算法,可以看到请求以此路由到不同的地址上了。可以通过配置或实现不同的算法,关于常见的路由算法,可以看这里:Java中如何实现负载均衡策略_java负载均衡-CSDN博客
点个关注不迷路: