之前服务消费者调用服务提供者的时候我们使用的是RestTemplate技术,调用方式如下:
这种调用方式的缺点是拼接url和调用restTmplate.getForObJect
这两个地方都比较模板化,而且手动拼写url比较容易出错还比较低级。
Feign简介
Feign是Netflix开发的一个轻量级RESTful的HTTP服务客户端(用它来发起请求,远程调用)。Feign是以Java接口注解的方式调用HTTP请求,而不像Java中通过封装HTTP请求报文的方式直接调用,被广泛应用与SpringCloud的解决方案中。
类似于Dubbo,服务消费者拿到服务提供者的接口,然后想调用本地方法一样去调用,实际上发出的是远程的请求。Feign的优点如下:
- Feign可帮助我们更加便捷,优雅的调用HTTP API,而不需要我们手动去拼接url然后调用RestTemplate的API。在SpringCloud中,使用Feign非常简单,在消费者端创建一个接口,并在接口上添加一些注解,代码就完成了
- SpringCloud对Feign进行了增强,使Feign支持了SpringMVC的注解
Feign配置应用
在 服务消费者 服务中引入Feign依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
改造启动类如下:
package com.rubin.scn.service.autodeliver;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableFeignClients
public class ScnServiceAutoDeliverBootstrap {
public static void main(String[] args) {
SpringApplication.run(ScnServiceAutoDeliverBootstrap.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
创建Feign接口如下:
package com.rubin.scn.service.autodeliver.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(value = "scn-service-resume", path = "resume")
public interface IResumeClient {
@GetMapping("feign")
String feign();
}
这里需要注意以下几点:
@FeignClient
注解的name
属性用于指定要调用的服务提供者名称,和服务提供者配置文件中的spring.application.name
要保持一致- 接口中的接口方法,就好比是远程服务提供者
Controller
中的方法,在参数绑定时使用@PathVariable
、@RequestParam
、@RequestHeader
等SpringMVC的注解,并且一定要指定value
属性
使用方式很简单,直接注入使用即可:
package com.rubin.scn.service.autodeliver.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.rubin.scn.common.entity.RResume;
import com.rubin.scn.service.autodeliver.feign.IResumeClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("auto-deliver")
public class AutoDeliverController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private IResumeClient iResumeClient;
@GetMapping("service/{id}")
public List<ServiceInstance> getServiceInfos(@PathVariable("id") String id) {
return discoveryClient.getInstances(id);
}
@GetMapping("service/ip")
public String getIp() {
OAuth2AuthenticationDetails oAuth2AuthenticationDetails = (OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails();
Map<String, Object> decodedDetails = (Map<String, Object>) oAuth2AuthenticationDetails.getDecodedDetails();
return decodedDetails.get("clientIp").toString();
}
@GetMapping("resume/{userId}")
public RResume getResumeByUserId(@PathVariable("userId") Long userId) {
List<ServiceInstance> instances = getServiceInfos("scn-service-resume");
ServiceInstance instance = instances.get(0);
String host = instance.getHost();
Integer port = instance.getPort();
String url = new StringBuffer("http://")
.append(host)
.append(":")
.append(port)
.append("/resume/detail?userId=")
.append(userId)
.toString();
RResume rResume = restTemplate.getForObject(url, RResume.class);
return rResume;
}
@GetMapping("resumes")
public List getResumes() {
List result = restTemplate.getForObject("http://scn-service-resume/resume/list", List.class);
return result;
}
@GetMapping("hello")
public String hello() throws InterruptedException {
return restTemplate.getForObject("http://scn-service-resume/resume/hello", String.class);
}
// 使用@HystrixCommand注解进行熔断控制
@HystrixCommand(
// 线程池标识,要保持唯一,不唯一的话就公用了
threadPoolKey = "timeout",
// 线程池细节属性配置
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "1"), // 线程数
@HystrixProperty(name = "maxQueueSize", value = "20") // 等待队列长度
},
// commandProperties熔断的⼀些细节属性配置
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
// 统计时间窗口长度
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "8000"),
// 统计时间窗口内的最小请求数
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "2"),
// 统计时间窗口内的最内的错误数量百分比阀值
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
// 自我修复时的活动窗口长度
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "3000")
},
fallbackMethod = "rubinFallback"
)
@GetMapping("timeout")
public String timeout() {
return restTemplate.getForObject("http://scn-service-resume/resume/timeout", String.class);
}
public String rubinFallback() {
return "rubin fallback.";
}
@GetMapping("feign")
public String feign() {
return iResumeClient.feign();
}
}
Feign对负载均衡的支持
Feign本身已经集成了Ribbon的依赖和自动配置,因此不用我们额外引入依赖,可以直接通过ribbon.xxx
来进行全局配置,也可以根据服务名.ribbon.xxx
来进行针对服务的细节配置。
Feign默认的请求处理超时时间是1s。有时候我们的业务确实需要执行一定的时间,在这个时候,我们就需要调整请求处理超时时长。Feign有自己的超时设置,但是如果配置了Ribbon的超时,则会以Ribbon的配置为准。我们一般支配之Ribbon的超时配置即可,配置示例如下:
scn-service-resume:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
#请求连接超时时间 超时时间为连接时间+请求处理时间 如果配制了ribbon的超时时间和重试 hystrix的超时时间要大于 ribbon的超时时间*重试次数
#ConnectTimeout: 2000
#请求处理超时时间
#ReadTimeout: 5000
#对所有操作都进行重试
OkToRetryOnAllOperations: true
# 根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由MaxAutoRetries配置),
# 如果不行,就换一个实例进行访问,如果还不行,再换一次实例访问(更换次数由MaxAutoRetriesNextServer配置),
# 如果依然不行,返回失败信息。
MaxAutoRetries: 0 #对当前选中实例重试次数,不包括第一次调用
MaxAutoRetriesNextServer: 0 #切换实例的重试次数
Feign对熔断器的支持
在Feign客户端工程陪u知文件中添加入下配置来开启Feign对熔断器的支持(默认是关闭的):
feign:
hystrix:
enabled: true
关于Hystrix的配置,我们可以参考博文SpringCloud Netflix之Hystrix熔断器。但是需要注意的一点是:熔断器的超时时间 >= (ribbon.ConnectTimeout
+ ribbon. ReadTimeout
) * 重试次数。
对于自定义降级逻辑的处理,我们可以定义自定义的Fallback处理类,实现对应的Feign接口并使用@Component
注解将其驻入容器,示例如下:
package com.rubin.scn.service.autodeliver.feign.fallback;
import com.rubin.scn.service.autodeliver.feign.IResumeClient;
import org.springframework.stereotype.Component;
@Component
public class ResumeClientFallback implements IResumeClient {
@Override
public String feign() {
return "feign fallback.";
}
}
在Feign接口中指定改熔断回退处理类:
package com.rubin.scn.service.autodeliver.feign;
import com.rubin.scn.service.autodeliver.feign.fallback.ResumeClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(value = "scn-service-resume", path = "resume", fallback = ResumeClientFallback.class)
public interface IResumeClient {
@GetMapping("feign")
String feign();
}
Feign对请求压缩和响应压缩的支持
Feign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的配置参数即可开启请求与响应的压缩功能:
feign:
# 请求响应启用压缩
compression:
response:
enabled: true
request:
enabled: true
mime-types: text/html,application/xml,application/json
min-request-size: 2048
Feign的日志级别配置
Feign是HTTP请求客户端,类似于浏览器,但是其日志功能默认是关闭的。如果我们在开发环境下,一般调试时想要查看一下比较详细的调用日志(响应头、状态码等等)我们就需要对Feign的日志级别做一下配置调整。
开启Feign的日志级别我们需要定义一个Feign日志级别的配置类如下:
package com.rubin.scn.service.autodeliver.feign.logger;
import feign.Logger;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
@SpringBootConfiguration
public class FeignLoggerConfig {
// Feign的日志级别(Feign请求过程信息)
// NONE:默认的,不显示任何日志----性能最好
// BASIC:仅记录请求方法、URL、响应状态码以及执行时间----生产问题追踪
// HEADERS:在BASIC级别的基础上,记录请求和响应的header
// FULL:记录请求和响应的header、body和元数据----适用于开发及测试环境定位问题
@Bean
Logger.Level feignLevel() {
return Logger.Level.FULL;
}
}
再在配制中指定该Feign接口的日志级别为DEBUG:
# 开启对应Feign客户端的日志级别 适配Feign日志级别的打印 Feign日志只会对日志级别为DEBUG的做出响应
logging:
level:
com.rubin.scn.service.autodeliver.feign.IResumeClient: DEBUG
最后,将该配置类配置在Feign接口类中:
package com.rubin.scn.service.autodeliver.feign;
import com.rubin.scn.service.autodeliver.feign.fallback.ResumeClientFallback;
import com.rubin.scn.service.autodeliver.feign.logger.FeignLoggerConfig;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(value = "scn-service-resume", path = "resume", fallback = ResumeClientFallback.class, configuration = FeignLoggerConfig.class)
public interface IResumeClient {
@GetMapping("feign")
String feign();
}
以上就是关于Feign的内容了,欢迎小伙伴们积极留言交流~~~
文章评论