最近整理一下微服务的文章,先拿一直用的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,主要是调用者这块,接下来写的也是这个
org.springframework.cloud
spring-cloud-starter-openfeign
@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/