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 {

    @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

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Feign是一个基于Java的声明式HTTP客户端,用于简化调用第三方接口的开发。使用Feign可以让我们像调用本地方法一样调用远程接口。 要使用Feign调用第三方接口,首先需要在项目中引入Feign的依赖。在Maven项目中,可以在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> ``` 接下来,在定义调用远程接口接口上添加`@FeignClient`注解,并指定要调用的远程服务的名称。例如: ```java @FeignClient(name = "third-party-service") public interface ThirdPartyServiceClient { @GetMapping("/api/some-resource") ResponseEntity<String> getSomeResource(); } ``` 在上述示例中,`ThirdPartyServiceClient`接口定义了一个`getSomeResource()`方法,用于调用第三方服务的`/api/some-resource`接口。 最后,在需要调用远程接口的地方注入`ThirdPartyServiceClient`接口,并使用该接口的方法进行调用。例如: ```java @RestController public class MyController { private final ThirdPartyServiceClient thirdPartyServiceClient; public MyController(ThirdPartyServiceClient thirdPartyServiceClient) { this.thirdPartyServiceClient = thirdPartyServiceClient; } @GetMapping("/my-endpoint") public ResponseEntity<String> myEndpoint() { return thirdPartyServiceClient.getSomeResource(); } } ``` 在上述示例中,`MyController`中的`myEndpoint()`方法通过调用`ThirdPartyServiceClient`接口的方法来获取第三方服务的资源。 需要注意的是,Feign还支持其他功能,如请求拦截器、熔断器等,可以根据具体需求进行配置和使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值