OpenFeign 是 Spring Cloud 家族的一个成员, 它最核心的作用是为 HTTP 形式的 Rest API 提供了非常简洁高效的 RPC 调用方式。支持Hystrix 、Ribbon和SpringMVC 注解。
1、Feign:
Feign是Netflix公司(第一代SpringCloud)研发的一个轻量级RESTful的伪HTTP服务客户端。
Feign内置了Ribbon逻辑,通过负载均衡算法可以从注册中心中寻找服务。
2、OpenFeign:
OpenFeign是SpringCloud自己研发的,在Feign的基础上做了增强。
OpenFeign除了原有Ribbon逻辑外,还支持了Hystrix和Spring MVC注解。
注意: 当前是springboot整合OpenFegin 服务端提供端未配置任何注册中心 如需要 自行配置
@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);}}}
org.springframework.cloud spring-cloud-starter-openfeign
@FeignClient注解
@FeignClient常用的参数有:
name: 指定FeignClient的(服务)名称,在Eureka/Nacos/其他注册中心 中对应的服务名称。
url: 指定远程调用地址。
configuration: 指定配置类,可以自定义FeignClient的配置。
fallback: 指定降级类,在服务调用失败时返回降级类中的内容。
fallbackFactory: 指定降级工厂类,在服务调用失败时返回降级工厂类中的内容。
decode404: 指定404响应是否解码,默认为true。
path: 指定服务请求的基础路径。
contextId: 当一个服务有多个接口时,我们又不想把所有接口都写到一个类中是时候就用到了 contextId为当前类设置一个唯一ID。不然就回报如下错误 。
[扩展]@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的名称冲突。
@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配合OpenFeign进行降级,可以对应接口中定义的远程调用单独进行降级操作。
直白的说: 远程调用失败,我们添加一个替代方案,我们知道OpenFegin是以接口的形式来声明远程调用,只要是远程调用失效超时,就执行替代方案。创建一个实现类,对原有的接口方法进行替代方案实现。
使用@Component注解 交给String管理 注入IOC容器
对Api接口进行进行实现、重写调用的Api 远程接口 代码如下:
@Component 注解是 Spring 框架中的注解,用于将一个类标记为 Spring 容器中的一个组件,让 Spring 自动扫描并管理这个组件的生命周期和依赖注入。
实现implements FallbackFactory原因:用于在使用 Feign 进行服务调用时,定义一个 FallbackFactory<> 来处理服务调用失败的情况的
以下码为例:服务降级内容我只是返回一段话,根据自己业务定性, 可判断参数是否为空等 返回具体错误信息。
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;}};}}
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;}}
说明: OpenFegin 默认1ms 未响应会抛出异常 , 往往在调用远程接口的时候 、业务逻辑复杂、 没等业务处理完毕就抛出异常 、我们可以针对这类问题 可以配置延长时长。
如: 库存服务 订单服务 、 当订单服务,请求库存服务 ,库存1ms未响应 就会抛出异常
配置 Feign 超时问题
配置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提供的数据。