微服务间可以使用 HTTP 协议,RESTful 规范进行通信。Spring Cloud 提供了 2 种 RESTful 调用方式:Ribbon 和 Feign 。
客户端软负载组件,支持Eureka对接,支持多种可插拔LB策略。依赖 spring-cloud-starter-netflix-eureka-client
中已经默认加载了 Ribbon 的依赖。
Ribbon作为Spring Cloud的负载均衡机制的实现:
Ribbon 的自定义配置以及一些高级使用可以参考官方文档: Client Side Load Balancer: Ribbon
官网首页: https://github.com/Netflix/ribbon
ribbon-core: 核心的通用性代码。api一些配置。
ribbon-eureka:基于eureka封装的模块,能快速集成eureka。
ribbon-examples:学习示例。
ribbon-httpclient:基于apache httpClient封装的rest客户端,集成了负载均衡模块,可以直接在项目中使用。
ribbon-loadbalancer:负载均衡模块。
ribbon-transport:基于netty实现多协议的支持。比如http,tcp,udp等。
@Configuration
public class LoadBalancerClientConfig{
/**
* 负载均衡的RestTemplate。
*/
@LoadBalanced
@Bean(name = "loadBalanced")
RestTemplateloadBalanced(){
return new RestTemplate();
}
/**
* 常规的RestTemplate。
*/
@Primary
@Bean(name = "restTemplate")
RestTemplaterestTemplate(){
return new RestTemplate();
}
}
以上代码中使用 @LoadBalanced
注解,这样就可以让 RestTemplate 在请求时拥有客户端负载均衡的能力。
@Autowired
@Qualifier("loadBalanced")
private RestTemplate loadBalanced;
public String getTime(){
return loadBalanced.getForEntity("http://service-producer/get_time", String.class).getBody();
}
@Autowired
private DiscoveryClient discoveryClient;
public List<String> getAllInstance(@RequestParam("service_id")String serviceId){
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
return instances.stream()
.map(instance -> String.format("http://%s:%s", instance.getHost(), instance.getPort()))
.collect(Collectors.toList());
}
Ribbon 提供了很多负载均衡的策略。详情可见:
| 策略名称 | 策略描述 |
|---|---|
| BestAvailableRule(最低并发策略) | 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。逐个找服务,如果断路器打开,则忽略。 |
| AvailabilityFilteringRule(可用过滤策略) | 会先过滤掉多次访问故障而处于断路器跳闸状态的服务和过滤并发的连接数量超过阀值得服务,然后对剩余的服务列表安装轮询策略进行访问。 |
| WeightedResponseTimeRule(响应时间加权策略) | 据平均响应时间计算所有的服务的权重,响应时间越快服务权重越大,容易被选中的概率就越高。刚启动时,如果统计信息不中,则使用RoundRobinRule(轮询)策略,等统计的信息足够了会自动的切换到WeightedResponseTimeRule。响应时间长,权重低,被选择的概率低。反之,同样道理。此策略综合了各种因素(网络,磁盘,IO等),这些因素直接影响响应时间。 |
| RetryRule(重试策略) | 先按照RoundRobinRule(轮询)的策略获取服务,如果获取的服务失败则在指定的时间会进行重试,进行获取可用的服务。如多次获取某个服务失败,就不会再次获取该服务。主要是在一个时间段内,如果选择一个服务不成功,就继续找可用的服务,直到超时。 |
| RoundRobinRule(轮询策略) | 以简单轮询选择一个服务器。按顺序循环选择一个server。 |
| RandomRule(随机策略) | 随机选择一个服务器。 |
| ZoneAvoidanceRule(区域权衡策略)【默认实现】 | 复合判断Server所在区域的性能和Server的可用性,轮询选择服务器。 |
@Configuration
public class DefaultRibbonConfig{
@Bean
public IRule ribbonRule(){
return new BestAvailableRule();
}
}
如上,配置IRule的新值,直接可以切换负载均衡策略
给所有的服务指定负载均衡策略:
ribbon: NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
给特定的服务指定负载均衡策略:
<服务名>.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
在服务调用的时候,我们可能不仅仅是简单地进行调用,会涉及到一些接口的校验、权限的校验等。要实现这些,可以实现 ClientHttpRequestInterceptor
接口。
public class LoggingClientHttpRequestInterceptorimplements ClientHttpRequestInterceptor{
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest,byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution)throws IOException {
System.out.println("拦截啦!!!");
System.out.println(httpRequest.getURI());
ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);
System.out.println(response.getHeaders());
return response;
}
}
添加到resttemplate中
@LoadBalanced
@Bean(name = "loadBalanced")
@Primary
RestTemplateloadBalanced(){
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new LoggingClientHttpRequestInterceptor());
return restTemplate;
}
Ribbon的配置如下:
#连接超时时间(ms) ribbon.ConnectTimeout=1000 #业务逻辑超时时间(ms) ribbon.ReadTimeout=6000
#同一台实例最大重试次数,不包括首次调用 ribbon.MaxAutoRetries=1 #重试负载均衡其他的实例最大重试次数,不包括首次调用 ribbon.MaxAutoRetriesNextServer=1 #是否所有操作都重试 ribbon.OkToRetryOnAllOperations=false
使用ribbon重试机制,请求失败后,每个6秒会重新尝试
Feign 是 Netflix 开发的声明式、模板化的 HTTP 客户端,Feign 的使用非常简单,创建一个接口,在接口上加入一些注解,这样就完成了代码开发。
Feign 是一个 Http 请求调用的轻量级框架,可以以 Java 接口注解的方式调用 Http 请求,而不用像 Java 中通过封装 HTTP 请求报文的方式直接调用。通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。Feign 封装 了HTTP 调用流程,面向接口编程
Spring Cloud OpenFeign 通过自动配置并绑定到Spring Environment和其他Spring编程模型习惯用法,为Spring Boot应用程序提供OpenFeign集成。
官方文档: Spring Cloud OpenFeign
使用 Feign 必须引入 spring-cloud-starter-openfeign
。在启动类 Application
上 @EnableFeignClients
注解。
@RequestMapping
public interface HelloApi{
@GetMapping(value = "/v0.1/greeting")
Stringgreeting();
}
@RestController
public class HelloControllerimplements HelloApi{
@Override
public String greeting(){
return "hello world";
}
}
简单使用Feign不需要代码耦合,但是需要硬编码接口的信息。如下:
@FeignClient(value = "producer-service")
public interface HelloClient{
@GetMapping(value = "/v0.1/greeting")
Stringgreeting();
}
@Service
public class TestService{
@Autowired
HelloClient helloClient;
public String test(){
helloClient.greeting();
}
}
@FeignClient
也可以脱离Eureka使用,如: @FeignClient(name = "xxx",url="")
这个url就是接口的地址。
此方法需要引入 服务提供者 提供的接口jar包。
@FeignClient(value = "producer-service")
public interface HelloServiceextends HelloApi{
}
@Service
public class TestService{
@Autowired
HelloService helloService;
public String test(){
helloService.greeting();
}
}
以上代码中, @FeignClient(value = "producer-service")
指定了使用哪一个服务。
feign的默认配置类是:org.springframework.cloud.openfeign.FeignClientsConfiguration。默认定义了feign使用的编码器,解码器等。
允许使用@FeignClient的configuration的属性自定义Feign配置。自定义的配置优先级高于上面的FeignClientsConfiguration。
@Configuration
@Slf4j
@EnableConfigurationProperties(FeignSecurityProperties.class)
@ConditionalOnClass(value = {RequestInterceptor.class, Decoder.class, Encoder.class})
public class FeignAutoConfig{
@Autowired
private FeignSecurityProperties feignSecurityProperties;
/**
* 内部微服务请求,加上timestamp,并且按字典序进行签名,附上sig
*
* @return RequestInterceptor 请求拦截器
*/
@Bean
@ConditionalOnMissingBean(RequestInterceptor.class)
public RequestInterceptor requestTokenBearerInterceptor(){
String apiSecretKey = feignSecurityProperties.getApiSignature().getInternalApiKey();
return requestTemplate -> ApiSigUtil.sig(apiSecretKey, requestTemplate);
}
@Bean
public Decoder feignDecoder(){
HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
}
@Bean
public Encoder feignEncoder(){
HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
return new SpringEncoder(objectFactory);
}
public ObjectMapper customObjectMapper(){
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
return objectMapper;
}
@Bean
@ConditionalOnMissingBean(Retryer.class)
public Retryer feignRetryer(){
return Retryer.NEVER_RETRY;
}
@Bean
@ConditionalOnMissingBean(Request.Options.class)
Request.OptionsfeignOptions(){
return new Request.Options(30 * 1000, 30 * 1000);
}
}
如上面所示, 在配置类上加上@Configuration注解,且该类在@ComponentScan所扫描的包中,那么该类中的配置信息就会被所有的@FeignClient共享。
如果想要对某些的@FeignClient添加指定的配置,则:不指定@Configuration注解(或者指定configuration,用注解忽略),而是手动使用: @FeignClient(name = "service-valuation",configuration = FeignAuthConfiguration.class)
上面代码中:
@Bean
@ConditionalOnMissingBean(RequestInterceptor.class)
public RequestInterceptor requestTokenBearerInterceptor(){
String apiSecretKey = feignSecurityProperties.getApiSignature().getInternalApiKey();
return requestTemplate -> ApiSigUtil.sig(apiSecretKey, requestTemplate);
}
RequestInterceptor
是一个请求拦截器,我们可以继承它做很多事情,如:
import feign.RequestInterceptor;
import feign.RequestTemplate;
public class MyBasicAuthRequestInterceptorimplements RequestInterceptor{
@Override
public void apply(RequestTemplate template){
template.header("Authorization", "Basic cm9vdDpyb290");
}
}
如果是BasicAuth认证,可以重写BasicAuthRequestInterceptor。
然后,在配置文件中添加:
feign: client: config: service-valuation: request-interceptors: - cn.webfuse.passenger.feign.interceptor.MyBasicAuthRequestInterceptor
指定服务名配置:
feign: client: config: service-valuation: connect-timeout: 5000 read-timeout: 5000 logger-level: full
通用配置:
feign: client: config: default: connect-timeout: 5000 read-timeout: 5000 logger-level: full
属性配置比Java代码优先级高。也可通过配置设置java代码优先级高:
feign: client: default-to-properties: false
feign: compression: # 开启请求与响应的GZIP压缩 request: enabled: true min-request-size: 10000 # 单位是B response: enabled: true
Feign默认支持Ribbon;Ribbon的重试机制和Feign的重试机制有冲突,所以源码中默认关闭Feign的重试机制,使用Ribbon的重试机制。
当调用服务时,如果服务返回的状态码不是200,就会进入到 Feign
的 ErrorDecoder
中,因此如果我们要解析异常信息,就要重写 ErrorDecoder