最近整理一下微服务的文章,先拿一直用的OpenFeign开刀
思考:微服务之间如何方便优雅的实现服务间的远程调用
说到这个,那不得不先说说RPC
RPC 全称是 Remote Procedure Call ,即远程过程调用,其对应的是我们的本地调用。RPC 的目的是:让我们调用远程方法像调用本地方法一样。
- //本地调用
- R result = orderService.findOrderByUserId(id);
- //RPC远程调用 orderService为代理对象
- R result = orderService.findOrderByUserId(id);
RPC框架设计架构
Feign是Netflix开发的声明式、模板化的HTTP客户端,Feign可帮助我们更加便捷、优雅地调用HTTP API。
Feign可以做到使用 HTTP 请求远程服务时就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个 HTTP 请求。它像 Dubbo 一样,consumer 直接调用接口方法调用 provider,而不需要通过常规的 Http Client 构造请求再解析返回数据。它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。
Spring Cloud openfeign对Feign进行了增强,使其支持Spring MVC注解,另外还整合了Ribbon和Eureka,从而使得Feign的使用更加方便。
Feign
Feign是SpringCloud组件中的一个轻量级RESTful的Http服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。
OpenFeign
OpenFeign是SpringCloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
Ribbon+RestTemplate进行微服务调用
- @Bean
- @LoadBalanced
- public RestTemplate restTemplate() {
- return new RestTemplate();
- }
-
- //调用方式
- String url = "http://mall-order/order/findOrderByUserId/"+id;
- R result = restTemplate.getForObject(url,R.class);
Feign进行微服务调用
- @FeignClient(value = "mall-order",path = "/order")
- public interface OrderFeignService {
- @RequestMapping("/findOrderByUserId/{userId}")
- public R findOrderByUserId(@PathVariable("userId") Integer userId);
- }
-
- //调用
- @Autowired
- OrderFeignService orderFeignService;
- //feign调用
- R result = orderFeignService.findOrderByUserId(id);
下面是Springboot集成OpenFeign Demo,完全照着来妥妥的没问题
服务提供者我就不写了,这就是正常的controller,主要是调用者这块,接下来写的也是这个
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-openfeignartifactId>
- dependency>
- @EnableAsync
- @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class })
- @EnableDiscoveryClient
- @EnableFeignClients //扫描和注册feign客户端的beanDefinition
- @MapperScan("com.**.**.**.**.mapper")
- public class Application {
-
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
- }
创建一个FeignClient接口,用于定义要调用的远程服务的方法:
- @FeignClient(value = "base-application", path = "/base")
- @RequestMapping("/api/testDemo")
- public interface OpenFeignBaseDemo {
-
- @PostMapping("/queryList")
- String queryList(@RequestBody JSONObject jsonObject);
- }
在上面的代码中,我们使用@FeignClient注解指定了要调用的远程服务的名称。然后,我们定义了一个名为queryList的方法,并使用@PostMapping注解指定了要调用的远程服务的API路径。
在其他类中使用OpenFeignBaseDemo 来调用远程服务的方法
- @RestController
- @RequestMapping("/openFeignDemo")
- public class OpenFeignControllerDemo {
-
- @Autowired
- private OpenFeignBaseDemo openFeignBase;
-
- @PostMapping(value = "/test", produces = { "application/json;charset=utf-8" })
- public ResponseEntity test(@RequestBody JSONObject jsonObject) {
- //feign调用
- String res = openFeignBase.queryList(jsonObject);
- return new ResponseEntity(res, HttpStatus.OK);
- }
-
- }
启动两个工程,postmain调用 请求url :localhost:8088/server/openFeignDemo/test。
- public @interface FeignClient {
- @AliasFor("name")
- String value() default "";
-
- String contextId() default "";
-
- @AliasFor("value")
- String name() default "";
-
- String qualifier() default "";
-
- String url() default "";
-
- boolean decode404() default false;
-
- Class>[] configuration() default {};
-
- Class> fallback() default void.class;
-
- Class> fallbackFactory() default void.class;
-
- String path() default "";
-
- boolean primary() default true;
- }
下面就对各个属性进行分析
value、name
value和name的作用一样,如果没有配置url那么配置的值将作为服务名称,用于服务发现。反之只是一个名称。
contextId
我们不想将所有的调用接口都定义在一个类中,有一种解决方案就是为每个Client手动指定不同的contextId,这样就不会冲突了。
url
url用于配置指定服务的地址,相当于直接请求这个服务,不经过Ribbon的服务选择。像调试等场景可以使用。
decode404
当调用请求发生404错误时,decode404的值为true,那么会执行decoder解码,否则抛出异常。
configuration
configuration是配置Feign配置类,在配置类中可以自定义Feign的Encoder、Decoder、LogLevel、Contract等。
fallback
定义容错的处理类,也就是回退逻辑,fallback的类必须实现Feign Client的接口,无法知道熔断的异常信息。
fallbackFactory
也是容错的处理,可以知道熔断的异常信息。
path
path定义当前FeignClient访问接口时的统一前缀,比如接口地址是/user/get, 如果你定义了前缀是user, 那么具体方法上的路径就只需要写/get 即可。
primary
primary对应的是@Primary注解,默认为true,官方这样设置也是有原因的。当我们的Feign实现了fallback后,也就意味着Feign Client有多个相同的Bean在Spring容器中,当我们在使用@Autowired进行注入的时候,不知道注入哪个,所以我们需要设置一个优先级高的,@Primary注解就是干这件事情的。
qualifier
qualifier对应的是@Qualifier注解,使用场景跟上面的primary关系很淡,一般场景直接@Autowired直接注入就可以了。
如果遇到这样的错误:feign.RetryableException: Read timed out executing POST
可以做如下处理:yaml配置
- feign:
- client:
- config:
- default:
- connectTimeout: 1000
- readTimeout: 6000
或者propertis配置
- feign.client.config.default.connect-timeout=20000
- feign.client.config.default.read-timeout=20000
https://cloud.spring.io/spring-cloud-openfeign/reference/html/