微服务-Ribbon,OpenFeign

本文详细介绍了SpringCloud OpenFeign如何增强Feign,使其支持SpringMVC注解,并整合Ribbon和Nacos,简化了微服务间的HTTP调用。OpenFeign的优势在于提供了一种类似本地方法调用的体验,隐藏了远程交互的复杂性。文章通过快速整合示例,展示了如何配置和使用OpenFeign,包括编写调用接口、启用注解、发起调用等步骤。同时,对比了Ribbon的使用方式,解释了Ribbon如何实现服务调用和负载均衡。最后,讨论了OpenFeign的配置,如启用OkHttp、日志和重试机制,以及其实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.OpenFeign

Spring Cloud openfeign对Feign进行了增强,使其支持Spring MVC注解,另外还整合了Ribbon和Nacos,从而使得Feign的使用更加方便.

1.1 优势

Feign可以做到使用 HTTP 请求远程服务时就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个 HTTP 请求。它像 Dubbo 一样,consumer 直接调用接口方法调用 provider,而不需要通过常规的 Http Client 构造请求再解析返回数据。它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。

1.2快速整合OpenFeign

1.2.1引入依赖
<!‐‐ openfeign 远程调用 ‐‐>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring‐cloud‐starter‐openfeign</artifactId>
</dependency>
1.2.2编写调用接口+@FeignClient注解

FeignClient注解详细解析:https://zhuanlan.zhihu.com/p/101383417

@FeignClient(value = "mall‐order",path = "/order")
public interface OrderFeignService {
	@RequestMapping("/findOrderByUserId/{userId}")
	public R findOrderByUserId(@PathVariable("userId") Integer userId);
}
1.2.3调用端在启动类上添加@EnableFeignClients注解
@SpringBootApplication
@EnableFeignClients
public class MallUserFeignDemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(MallUserFeignDemoApplication.class, args);
	}
}
1.2.4调用端发起调用,像调用本地方法一样调用远程服务
@RestController
@RequestMapping("/user")
public class UserController {
	@Autowired
	OrderFeignService orderFeignService;
	@RequestMapping(value = "/findOrderByUserId/{id}")
	public R findOrderByUserId(@PathVariable("id") Integer id) {
		//feign调用
		R result = orderFeignService.findOrderByUserId(id);
		return result;
	}
}

========================================================
使用配置

/**
 * <h1>使用 Ribbon 之前的配置, 增强 RestTemplate</h1>
 * */
@Component
public class RibbonConfig {

    /**
     * <h2>注入 RestTemplate</h2>
     * */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

创建UseRibbonService

**
 * <h2>通过 Ribbon 调用 Authority 服务获取 Token</h2>
 * */
public JwtToken getTokenFromAuthorityServiceByRibbon(
        UsernameAndPassword usernameAndPassword) {

    // 注意到 url 中的 ip 和端口换成了服务名称
    String requestUrl = String.format(
            "http://%s/ecommerce-authority-center/authority/token",
            CommonConstant.AUTHORITY_CENTER_SERVICE_ID
    );
    log.info("login request url and body: [{}], [{}]", requestUrl,
            JSON.toJSONString(usernameAndPassword));

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);

    // 这里一定要使用自己注入的 RestTemplate
    return restTemplate.postForObject(
            requestUrl,
            new HttpEntity<>(JSON.toJSONString(usernameAndPassword), headers),
            JwtToken.class
    );
}

Ribbon使用SERVICE_ID简化(ip+port)

使用原生的 Ribbon Api, 看看 Ribbon 是如何完成: 服务调用 + 负载均衡

  1. 找到服务提供方的地址和端口号
  • 构造 Ribbon 服务列表(com.netflix.loadbalancer.Serve)
  1. 使用负载均衡策略实现远端服务调用
  • 构建 Ribbon 负载实例
  • 设置负载均衡策略
/**
* <h2>使用原生的 Ribbon Api, 看看 Ribbon 是如何完成: 服务调用 + 负载均衡</h2>
* */
public JwtToken thinkingInRibbon(UsernameAndPassword usernameAndPassword) {

   String urlFormat = "http://%s/ecommerce-authority-center/authority/token";

   // 1. 找到服务提供方的地址和端口号
   List<ServiceInstance> targetInstances = discoveryClient.getInstances(
           CommonConstant.AUTHORITY_CENTER_SERVICE_ID
   );

   // 构造 Ribbon 服务列表
   List<Server> servers = new ArrayList<>(targetInstances.size());
   targetInstances.forEach(i -> {
       servers.add(new Server(i.getHost(), i.getPort()));
       log.info("found target instance: [{}] -> [{}]", i.getHost(), i.getPort());
   });

   // 2. 使用负载均衡策略实现远端服务调用
   // 构建 Ribbon 负载实例
   BaseLoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder()
           .buildFixedServerListLoadBalancer(servers);
   // 设置负载均衡策略
   loadBalancer.setRule(new RetryRule(new RandomRule(), 300));

   String result = LoadBalancerCommand.builder().withLoadBalancer(loadBalancer)
           .build().submit(server -> {

               String targetUrl = String.format(
                       urlFormat,
                       String.format("%s:%s", server.getHost(), server.getPort())
               );
               log.info("target request url: [{}]", targetUrl);

               HttpHeaders headers = new HttpHeaders();
               headers.setContentType(MediaType.APPLICATION_JSON);

               String tokenStr = new RestTemplate().postForObject(
                       targetUrl,
                       new HttpEntity<>(JSON.toJSONString(usernameAndPassword), headers),
                       String.class
               );

               return Observable.just(tokenStr);

           }).toBlocking().first().toString();

   return JSON.parseObject(result, JwtToken.class);
}

OpenFeign
在这里插入图片描述
OpenFeign只定义接口,不用实现,实现由OpenFeign生成代理

/**
 * <h1>与 Authority 服务通信的 Feign Client 接口定义</h1>
 * */
@FeignClient(
        contextId = "AuthorityFeignClient", value = "e-commerce-authority-center",
//        fallback = AuthorityFeignClientFallback.class
        fallbackFactory = AuthorityFeignClientFallbackFactory.class
)
public interface AuthorityFeignClient {

    /**
     * <h2>通过 OpenFeign 访问 Authority 获取 Token</h2>
     * */
    @RequestMapping(value = "/ecommerce-authority-center/authority/token",
            method = RequestMethod.POST,
            consumes = "application/json", produces = "application/json")
    JwtToken getTokenByFeign(@RequestBody UsernameAndPassword usernameAndPassword);
}

配置OpenFeign,让它更好用

在这里插入图片描述OpenFeign原生没有连接池,所以用okhttp替换

bootstrap.yml配置

# Feign 的相关配置
feign:
  # feign 开启 gzip 压缩
  compression:
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json
      # 大于1024才压缩
      min-request-size: 1024
    response:
      enabled: true
  # 禁用默认的 http, 启用 okhttp
  httpclient:
    enabled: false
  okhttp:
    enabled: true
  # OpenFeign 集成 Hystrix
  hystrix:
    enabled: true

OpenFeign 配置类

/**
 * <h1>OpenFeign 配置类</h1>
 * */
@Configuration
public class FeignConfig {

    /**
     * <h2>开启 OpenFeign 日志</h2>
     * */
    @Bean
    public Logger.Level feignLogger() {
        return Logger.Level.FULL;   //  需要注意, 日志级别需要修改成 debug
    }

    /**
     * <h2>OpenFeign 开启重试</h2>
     * period = 100 发起当前请求的时间间隔, 单位是 ms
     * maxPeriod = 1000 发起当前请求的最大时间间隔, 单位是 ms
     * maxAttempts = 5 最多请求次数
     * */
    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default(
                100,
                SECONDS.toMillis(1),
                5
        );
    }

    public static final int CONNECT_TIMEOUT_MILLS = 5000;
    public static final int READ_TIMEOUT_MILLS = 5000;

    /**
     * <h2>对请求的连接和响应时间进行限制</h2>
     * 1.连接超时时间
     * 2.读超时时间
     * 3.转发时间算不算在内true/false
     * */
    @Bean
    public Request.Options options() {

        return new Request.Options(
                CONNECT_TIMEOUT_MILLS, TimeUnit.MICROSECONDS,
                READ_TIMEOUT_MILLS, TimeUnit.MILLISECONDS,
                true
        );
    }
}

注入 OkHttp, 并自定义配置

/**
 * <h1>OpenFeign 使用 OkHttp 配置类</h1>
 * */
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignOkHttpConfig {

    /**
     * <h2>注入 OkHttp, 并自定义配置</h2>
     * */
    @Bean
    public okhttp3.OkHttpClient okHttpClient() {

        return new OkHttpClient.Builder()
                .connectTimeout(5, TimeUnit.SECONDS)    // 设置连接超时
                .readTimeout(5, TimeUnit.SECONDS)   // 设置读超时
                .writeTimeout(5, TimeUnit.SECONDS)  // 设置写超时
                .retryOnConnectionFailure(true)     // 是否自动重连
                // 配置连接池中的最大空闲线程个数为 10, 并保持 5 分钟
                .connectionPool(new ConnectionPool(
                        10, 5L, TimeUnit.MINUTES))
                .build();
    }
}

OpenFeign 实现原理

Feign实现流程图
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值