OpenFegin+hystrix实现远程HTTP服务调用、服务降级(深度解析~保姆级)
迪丽瓦拉
2025-05-29 14:07:02
0

简介

OpenFeign 是 Spring Cloud 家族的一个成员, 它最核心的作用是为 HTTP 形式的 Rest API 提供了非常简洁高效的 RPC 调用方式。支持Hystrix 、Ribbon和SpringMVC 注解。

Feign和OpenFeign的区别?

1、Feign:

Feign是Netflix公司(第一代SpringCloud)研发的一个轻量级RESTful的伪HTTP服务客户端。

Feign内置了Ribbon逻辑,通过负载均衡算法可以从注册中心中寻找服务。

2、OpenFeign:

OpenFeign是SpringCloud自己研发的,在Feign的基础上做了增强。

OpenFeign除了原有Ribbon逻辑外,还支持了Hystrix和Spring MVC注解。

一、服务端Provider

注意: 当前是springboot整合OpenFegin 服务端提供端未配置任何注册中心 如需要 自行配置

Controller:

@CrossOrigin
@RestController
@RequestMapping("/customer/information")
public class CrmCustomersController {@Autowiredprivate CrmCustomersService crmCustomersService;/*** 服务数据*/@RequestMapping(value = "customerlist", method = {RequestMethod.GET,RequestMethod.POST})public PmpResult  queryCustomerList (@RequestParam(value="pageNum",defaultValue="1") int pageNum, @RequestParam(value="pageSize",defaultValue="10") int pageSize,String customersno, String customersname,String daogouid){try {PageInfo pageInfo = crmCustomersService.queryCustomerList(pageNum,pageSize,customersno,customersname,daogouid);logger.info("查询成功");return PmpResult.success(pageInfo);}catch (Exception e) {logger.error(e.getMessage(), e);String errorMessage = "查询异常";if (isDev){errorMessage = e.getMessage();}return PmpResult.paramError(errorMessage);}}}

二、消费端Consumer

消费端~例图

Pom.xml:

      org.springframework.cloudspring-cloud-starter-openfeign

Api :

  1. @FeignClient注解

@FeignClient常用的参数有:

name: 指定FeignClient的(服务)名称,在Eureka/Nacos/其他注册中心 中对应的服务名称。

url: 指定远程调用地址。

configuration: 指定配置类,可以自定义FeignClient的配置。

fallback: 指定降级类,在服务调用失败时返回降级类中的内容。

fallbackFactory: 指定降级工厂类,在服务调用失败时返回降级工厂类中的内容。

decode404: 指定404响应是否解码,默认为true。

path: 指定服务请求的基础路径。

contextId: 当一个服务有多个接口时,我们又不想把所有接口都写到一个类中是时候就用到了 contextId为当前类设置一个唯一ID。不然就回报如下错误 。

  1. [扩展]@FeignClient注解 通过属性 contextId 解决名称重复无法启动的问题 错误如下:

Description:
The bean 'optimization-user.FeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

导致 名称重复问题 复现:

/*
*Consumer1
*/
@FeignClient(name = "xj-user")
public interface UserRemoteConsumer1 {@GetMapping("/user1/get")public User getUser(@RequestParam("id") int id);
}/*
*Consumer2
*/
@FeignClient(name = "xj-user")
public interface UserRemoteConsumer2 {@GetMapping("/user2/get")public User getUser(@RequestParam("id") int id);
}

这种情况下启动就会报错了,因Bean的名称冲突。

原因:由于name重复,而又不允许BeanDefinition重复,所以导致在进行注册时报错。

解决方案一:

增加下面的配置,作用允许出现beanName一样的BeanDefinition。

 spring.main.allow-bean-definition-overriding=true

解释:

spring.main.allow-bean-definition-overriding=true应用程序的一个配置属性,它允许在应用程序上下文中覆盖bean定义。如果设置为true,则可以在应用程序上下文中定义多个具有相同名称的bean,后定义的bean将覆盖先前定义的bean。但是,这可能会导致不可预测的行为和错误,因此应该谨慎使用。

解决方案二(推荐):

每个FeignClient手动指定不同的contextId,这样就不会冲突了。

@FeignClient添加contextId属性

/*
*Consumer1
*/
@FeignClient(name = "xj-user" contextId = UserRemoteConsumer1)
public interface UserRemoteConsumer1 {@GetMapping("/user1/get")public User getUser(@RequestParam("id") int id);
}/*
*Consumer2
*/
@FeignClient(name = "xj-user" contextId = UserRemoteConsumer2)
public interface UserRemoteConsumer2 {@GetMapping("/user2/get")public User getUser(@RequestParam("id") int id);
}

总结:想创建多个具有相同名称或url的外部客户端,以便它们指向同一台服务器,但每个服务器都有不同的自定义配置,那么可以使用@FeignClient的contextId属性,以避免这些配置bean的名称冲突。

  1. @RequestMapping(value = "/customer/information/customerlist", method = RequestMethod.GET)是要调的接口名

import com.alibaba.fastjson.JSONObject;
import com.lt.crm.service.fallback.TransactionFallBackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;@Repository
@FeignClient(name = "service-client", url = "http://10.1.8.22:9001", contextId = "ConsumerService", fallbackFactory = TransactionFallBackFactory.class)
public interface ConsumerService {@RequestMapping(value = "/customer/information/customerlist", method = RequestMethod.GET)JSONObject queryCustomerList(@RequestParam(value = "pageNum") final int pageNum, @RequestParam(value = "pageSize") final int pageSize);}

Hystrix 服务降级处理

说明:

Hystrix配合OpenFeign进行降级,可以对应接口中定义的远程调用单独进行降级操作。

直白的说: 远程调用失败,我们添加一个替代方案,我们知道OpenFegin是以接口的形式来声明远程调用,只要是远程调用失效超时,就执行替代方案。创建一个实现类,对原有的接口方法进行替代方案实现。

  1. 使用@Component注解 交给String管理 注入IOC容器

  1. 对Api接口进行进行实现、重写调用的Api 远程接口 代码如下:

  1. @Component 注解是 Spring 框架中的注解,用于将一个类标记为 Spring 容器中的一个组件,让 Spring 自动扫描并管理这个组件的生命周期和依赖注入。

  1. 实现implements FallbackFactory原因:用于在使用 Feign 进行服务调用时,定义一个 FallbackFactory<> 来处理服务调用失败的情况的。[ import feign.hystrix.FallbackFactory; ]

  1. 以下码为例:服务降级内容我只是返回一段话,根据自己业务定性, 可判断参数是否为空等 返回具体错误信息。

import com.alibaba.fastjson.JSONObject;
import com.lt.crm.service.api.ConsumerService;
import feign.hystrix.FallbackFactory;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;@Component
public class TransactionFallBackFactory implements FallbackFactory  {private Logger logger = LoggerFactory.getLogger(getClass());@Overridepublic ConsumerService create(Throwable throwable) {return new ConsumerService() {@Overridepublic JSONObject queryCustomerList(int pageNum, int pageSize) {logger.error("TransactionMsgWaitingConfirm Error : {}" , ExceptionUtils.getFullStackTrace(throwable));String  fallbackMessage = "被Hystrix熔断,已作降级处理!";JSONObject jsonObject = new JSONObject();jsonObject.put("fallbackMessage",fallbackMessage);return jsonObject;}};}}

Controller:


import com.alibaba.fastjson.JSONObject;
import com.lt.crm.common.PmpResult;
import com.lt.crm.service.api.ConsumerService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
@RequestMapping("/customer/information")
@Api(tags = "跨服务调用数据")
public class ConsumerController {/*** 日志*/private final static Logger logger = (Logger) LoggerFactory.getLogger(ConsumerController.class);@Autowiredprivate ConsumerService consumerService;@ResponseBody@ApiOperation(value = "公共开放的端口")@RequestMapping(value = "customerlist", method = RequestMethod.GET)public PmpResult queryCustomerList(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum, @RequestParam(value = "pageSize", defaultValue = "10") int pageSize) {try {// String类型 转换成json 接收JSONObject GetJsonObject = consumerService.queryCustomerList(pageNum, pageSize);   if (null != GetJsonObject) {logger.info("调用数据成功");return PmpResult.success("数据服务", GetJsonObject);}} catch (Exception e) {e.printStackTrace();return PmpResult.serviceError("服务异常");}return null;}}

application.properties:

说明: OpenFegin 默认1ms 未响应会抛出异常 , 往往在调用远程接口的时候 、业务逻辑复杂、 没等业务处理完毕就抛出异常 、我们可以针对这类问题 可以配置延长时长。

如: 库存服务 订单服务 、 当订单服务,请求库存服务 ,库存1ms未响应 就会抛出异常

  1. 配置 Feign 超时问题

  1. 配置hystrix 超时时间

######################################openFegin######################################
# 默认开启
feign.httpclient.enabled=true
# 默认关闭
feign.okhttp.enabled=false
# 默认关闭
feign.hystrix.enabled=true
# 默认关闭
feign.sentinel.enabled=false# default context 连接超时时间 5000ms = 5秒
feign.client.config.default.connectTimeout = 5000
# default context 读超时时间
feign.client.config.default.readTimeout = 5000######################################Hystrix Config######################################
#开启hystrix超时管理
hystrix.command.default.execution.timeout.enabled=true
#hystrix超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000#开启ribbon超时管理
ribbon.http.client.enabled=false
#请求超时时间
ribbon.ReadTimeout=20000
#连接超时时间
ribbon.ConnectTimeout=20000
ribbon.MaxAutoRetries=0
ribbon.MaxAutoRetriesNextServer=1
ribbon.OkToRetryOnAllOperations=false

启动类:

启动类上使用注解 @EnableFeignClients 开启 openFeign 功能。

/*** 微服务应用服务启动类* 1、(@EnableDiscoveryClient)注解为链接微服务注册中心用,如实际环境中使用注册中心,请取消注释部分,*     与配置文件中相关注册中心配置信息结合使用。* @author xj**/
@EnableDiscoveryClient
@EnableFeignClients(clients= {ConsumerService.class} )//加入@EnableFeignClients注解 开启Feign远程服务调用,使Feign的bean可以被注入
@ServletComponentScan
@SpringBootApplication(scanBasePackages = {"com.lt.crm"})
@EnableCaching
public class OpenFeignApplication {public static void main(String[] args) {SpringApplication.run(OpenFeignApplication.class, args);}
}

测试

IDER启动了消费端Consumer,并没有启动服务端Provider ,过了超时时间,此时执行了服务降级,这样做避免了在微服务中服务雪崩的灾难。

后台也捕获了error :

连接被拒绝:连接执行GET: Connection refused: connect executing GET http://10.1.8.22:9001/customer/information/customerlist?pageNum=1&pageSize=10

2023-03-17 16:56:52.399 ERROR 20928 --- [ervice-client-1] c.l.c.s.f.TransactionFallBackFactory     : TransactionMsgWaitingConfirm Error : feign.RetryableException: Connection refused: connect executing GET http://10.1.8.22:9001/customer/information/customerlist?pageNum=1&pageSize=10at feign.FeignException.errorExecuting(FeignException.java:84)at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:113)at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:78)at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:106)at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302)at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298)at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35)at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)at rx.Observable.unsafeSubscribe(Observable.java:10327)at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:51)at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35)at rx.Observable.unsafeSubscribe(Observable.java:10327)at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41)at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30)at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)at rx.Observable.unsafeSubscribe(Observable.java:10327)at rx.internal.operators.OperatorSubscribeOn$SubscribeOnSubscriber.call(OperatorSubscribeOn.java:100)at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:56)at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:47)at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction.call(HystrixContexSchedulerAction.java:69)at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)at java.util.concurrent.FutureTask.run(FutureTask.java:266)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)at java.lang.Thread.run(Thread.java:748)

现在启动服务端再次调用,已经通过消费者9004端口 拿到了服务端9001提供的数据。

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...