#全局超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
hystrix.command.default.metrics.rollingStats.timeInMilliseconds=4000
#hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds=2000
#hystrix.command.<commandKey>作为前缀,默认是采用Feign的客户端的方法名字作为标识
hystrix.command.saveStudent.execution.isolation.thread.timeoutInMilliseconds=6000
hystrix.command.queryContents.circuitBreaker.sleepWindowInMilliseconds=20000
hystrix.command.errorMessage.execution.isolation.thread.timeoutInMilliseconds=100000000000
#ribbon.restclient.enabled=true
ribbon.client.name=micro-order
#点对点直连测试配置
# 关闭ribbon访问注册中心Eureka Server发现服务,但是服务依旧会注册。
#true使用eureka false不使用
ribbon.eureka.enabled=true
spring.cloud.loadbalancer.retry.enabled=true
#指定调用的节点
#micro-order.ribbon.listOfServers=localhost:8001
#单位ms ,请求连接超时时间
ribbon.ConnectTimeout=1000
#单位ms ,请求处理的超时时间
ribbon.ReadTimeout=3000
在前面我们调用其他服务的接口时采用的是RestTemplate的方式,其是Spring提供的访问Rest服务的客户端,操作起来也非常的方便。
常用的http调用方式还有HttpClient、OkHttp、HttpURLConnetion等。
这里要介绍的是声明式的REST客户端:Feign组件。
Feign是SpringCloud的一个非常重要的组件,SpringCloud对其进行了封装,且可以与Eureka、Hystrix、Ribbon整合一起使用。通过Feign我们可以非常方便的通过RestAPI的形式调用其他服务的接口。
下面看下如何使用:
一、引入依赖
目前已更名为openfeign:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
二、启动类开启注解
@EnableFeignClients(clients = {StudentService.class})
这里可以指明接口的类,或是使用basePackages指明要扫描的接口所在包:
@EnableFeignClients(basePackages = "com.wml.feign")
三、使用
我们在order-service服务中定义一些要调用的RestAPI:
@Slf4j
@RestController
public class StudentController{
@RequestMapping("/feign/student/findByName/{name}")
public String findByName(@PathVariable("name") String name) {
return name;
}
@RequestMapping("/feign/student/getAllStudent")
@Override
public String getAllStudent() {
List<Student>students=new ArrayList<>();
Student student;
for (int i = 0; i < 5; i++) {
student=new Student();
student.setId(i);
student.setName("哈哈"+i);
students.add(student);
}
return JSON.toJSONString(students);
}
@RequestMapping("/feign/student/getStudentById")
@Override
public String queryStudentById(@RequestParam("id") Integer id) {
return "id:"+id;
}
@RequestMapping("/feign/student/saveStudent")
@Override
public String saveStudent(@RequestBody Student student) {
return JSON.toJSONString(student);
}
}
方法很简单,但涉及传实体、占位符传参、传统的?传参几种方式。
接着在另一个web服务中声明Feign客户端:
@FeignClient(name = "ORDER-SERVICE",path = "/feign"
,fallbackFactory = StudentServiceFallbackFactory.class)
public interface StudentService {
@GetMapping("/student/getAllStudent")
String getAllStudent();
@RequestMapping("/student/findByName/{name}")
public String findByName(@PathVariable("name") String name);
@PostMapping("/student/saveStudent")
String saveStudent(@RequestBody Student student);
@GetMapping("/student/getStudentById")
String getStudentById(@RequestParam("id") Integer id);
}
这里作几点说明:
- @FeignClient注解表明当前接口是一个Feign客户端接口
- name:name属性为要调用的服务名称
- path:为一个公共前缀,因为我们要调用的客户端接口都有一个feign前缀,所以可以在这里标明一下,下面的路径中就可以省略掉该部分
- fallbackFactory:像前面使用Hystrix一样,Feign的每个接口都相当于添加了一个HystrixCommand注解,fallbackFactory为降级的处理类,该类实现了
FallbackFactory<StudentService>
接口,可以获取具体异常信息。 - 当前Feign客户端直接将要调用的服务的接口定义复制过来即可。
除了使用fallbackFactory,也可以使用fallback = StudentServiceFallback.class
的方式,该Fallback类实现了当前接口,但不推荐该方式,因为该方式无法获取具体的异常信息。
Feign降级处理
以下是fallbackFactory的处理类,Feign接口出现异常时就会进行如下的对应方法的回调中:
@Slf4j
@Component
public class StudentServiceFallbackFactory implements FallbackFactory<StudentService> {
@Override
public StudentService create(Throwable throwable) {
if(throwable == null) {
return null;
}
final String msg = throwable.getMessage();
log.info("exception:" + msg);
return new StudentService() {
@Override
public String getAllStudent() {
log.info("exception=====getAllStudent==========" + msg);
return msg;
}
@Override
public String findByName(String name) {
log.info("exception=====findByName==========" + msg);
return msg;
}
@Override
public String saveStudent(Student student) {
log.info("exception=====saveStudent==========" + msg);
return msg;
}
@Override
public String getStudentById(Integer id) {
log.info("exception=====getStudentById==========" + msg);
return msg;
}
@Override
public String errorMessage(Integer id) {
log.info("exception=====errorMessage==========" + msg);
return msg;
}
@Override
public String queryStudentTimeout(int millis) {
log.info("exception=====queryStudentTimeout==========" + msg);
return msg;
}
};
}
}
接着我们就可以在当前服务通过Feign客户端进行调用了:
@Slf4j
@RestController
@RequestMapping("/student")
public class StudentController {
@Autowired
private StudentService studentService;
@RequestMapping("/getAllStudent")
public String getAllStudent() {
return studentService.getAllStudent();
}
@RequestMapping("/findByName/{name}")
public String findByName(@PathVariable("name") String name) {
return studentService.findByName(name);
}
@RequestMapping("/getStudentById")
public String getStudentById(Integer id) {
return studentService.getStudentById(id);
}
@PostMapping("/saveStudent")
public String saveStudent(@RequestBody Student student) {
return studentService.saveStudent(student);
}
}
其他几个接口也是可以正常调用的,就不一一展示了。
四、其他配置
4.0 开启熔断器
#feign开启熔断器
feign.hystrix.enabled=true
4.1 压缩
在feign中,我们可以开启GZIP压缩来节约网络资源:
#开启feign的压缩功能
feign.compression.request.enabled=true
feign.compression.response.enabled=true
配置压缩类型和最小压缩值:
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
但是压缩配置只有在非使用okHttp时才生效,因为okHttp本身使用GZIP压缩减少传输数据量。
4.2使用OKHTTP
Feign默认使用的HC是JDK 原生的URLConnection
如果要使用okHttp,只要引入其依赖:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
开启okhttp并关闭httpClient:
feign.httpclient.enabled=false
feign.okhttp.enabled=true
添加配置:
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignOkHttpConfig {
@Bean
public okhttp3.OkHttpClient okHttpClient(){
return new okhttp3.OkHttpClient.Builder()
//设置连接超时
.connectTimeout(60, TimeUnit.SECONDS)
//设置读超时
.readTimeout(60, TimeUnit.SECONDS)
//设置写超时
.writeTimeout(60,TimeUnit.SECONDS)
//是否自动重连
.retryOnConnectionFailure(true)
.connectionPool(new ConnectionPool())
//构建OkHttpClient对象
.build();
}
}
4.3 使用Apache HttpClient
依赖:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>8.17.0</version>
</dependency>
配置:
feign.httpclient.enabled=true
4.4 自定义异常过滤器
该过滤器可以将Feign的异常信息封装成系统的通用异常对象
@Configuration
public class FeignMessageFilter {
@Bean
public ErrorDecoder errorDecoder() {
return new FeignErrorDecoder();
}
/** 当调用服务时,如果服务返回的状态码不是200,就会进入到Feign的ErrorDecoder中
* {"timestamp":"2020-02-17T14:01:18.080+0000","status":500,"error":"Internal Server Error","message":"/ by zero","path":"/feign/student/errorMessage"}
* 只有这种方式才能获取所有的被feign包装过的异常信息
*
* 这里如果创建的Exception是HystrixBadRequestException
* 则不会走熔断逻辑,不记入熔断统计
**/
class FeignErrorDecoder implements ErrorDecoder {
private Logger logger = LoggerFactory.getLogger(FeignErrorDecoder.class);
@Override
public Exception decode(String s, Response response) {
RuntimeException runtimeException = null;
try {
String retMsg = Util.toString(response.body().asReader());
logger.info(retMsg);
runtimeException = new RuntimeException(retMsg);
} catch (IOException e) {
e.printStackTrace();
}
return runtimeException;
}
}
}
超时配置
#全局超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
hystrix.command.default.metrics.rollingStats.timeInMilliseconds=4000
#hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds=2000
#hystrix.command.<commandKey>作为前缀,默认是采用Feign的客户端的方法名字作为标识
hystrix.command.saveStudent.execution.isolation.thread.timeoutInMilliseconds=6000
hystrix.command.queryContents.circuitBreaker.sleepWindowInMilliseconds=20000
hystrix.command.errorMessage.execution.isolation.thread.timeoutInMilliseconds=100000000000
ribbon.eureka.enabled=true
#单位ms ,请求连接超时时间
ribbon.ConnectTimeout=1000
#单位ms ,请求处理的超时时间
ribbon.ReadTimeout=3000
这里配置了ribbon处理请求的超时为3000,hystrix超时为5000。
如果 hystrix 超时时间大于 ribbon 超时时间,那么就以 hystrix 超时时间为准。
在实际开发中,使用Fiegn跨服务调用可能会出现请求头丢失的情况,这种情况的解决可参考周立老师的方案: