Spring Cloud认知学习(二):Feign的使用、熔断器Hystrix
Feign
🔵Feign用于声明式调用服务
🔵在上面的服务调用中,我们始终还是没有摆脱restTemplate,我们调用别的服务始终要使用restTemplate来发起。想想我们以前是怎么开发的(三层架构,controller调用service,service调用dao),controller调用service,feign就是为这种面向接口化编程需求而产生的。为什么说他能面向接口化编程呢?我们下面来演示。
使用示例
代码参考:Feign简单使用实验
考虑到服务可能有多个消费者,所以我们把共有的代码写到spring-cloud-common-data
中,这样所有的消费者都可以通过继承这个依赖包来获取Feign修饰的接口。
1.导入依赖:
在spring-cloud-common-data
中导入依赖:
<dependencies>
<!--增加feign依赖 start-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<!--旧版本的-->
<!--<artifactId>spring-cloud-starter-feign</artifactId>-->
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--增加feign依赖 end-->
</dependencies>
2.新建Feign Interface
在spring-cloud-common-data
创建一个interface:
package com.progor.study.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.Map;
// 由于这种服务的服务消费者可能比较多,放到共有依赖中。
@FeignClient(value = "MESSAGESERIVE")
public interface MessageService {
// 这里使用RequestMapping将服务提供者的方法与本地Service方法建立映射
@RequestMapping(value = "/msg/list", method = RequestMethod.GET)
Map<String, String> list();
}
💡@FeignClient
用来配置信息,可以配置当前interface对应哪个服务。搭配@RequestMapping
的效果就是调用对应服务的对应API接口。上面代码的效果就是从eureka中获取名为MESSAGESERIVE的服务的服务示例,调用服务实例的/msg/list
路由方法。
3.创建服务消费者
3.1 在服务消费者spring-cloud-user-consumer-80
中创建一个MessageController2,注入MessageService:
package com.progor.study.Controller;
import com.progor.study.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
// 这个控制器用来处理使用fegin的情况
@RestController
public class MessageController2 {
// 注入MessageService
@Autowired
private MessageService messageService;
@GetMapping("/msg2/list")
public Map<String, String> list() {
return messageService.list();
}
}
3.2.在spring-cloud-user-consumer-80
启用Feign:
💡由于在spring-cloud-common-data
中导入了fegin依赖,所以这里不需要再导入了。
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "USERSERIVE", configuration = MyConfig.class)
@EnableFeignClients // 使用feign
public class UserConsumer80Application {
public static void main(String[] args) {
SpringApplication.run(UserConsumer80Application.class, args);
}
}
4.测试
4.1:启动spring-cloud-eureka-server-7001
,spring-cloud-message-service-8004
,spring-cloud-message-service-8005
4.2:访问http://localhost/msg2/list
,这个URL调用的是MessageController2的API,而MessageController2里面调用的是被Fegin封装的MessageService的方法。
如果能访问成功,那说明了是我们使用Feign成功了。
从上面可以看出,使用了Feign之后,我们可以像以前一样调用Service来调用业务逻辑了。
补充:
- Feign默认是有负载均衡的,看一下
spring-cloud-starter-openfeign
依赖包,你会发现它有导入依赖spring-cloud-starter-netflix-ribbon
- 更多内容包括其工作原理,将会单章讲解,咕咕咕。
💡上一篇介绍一个用于声明式调用服务的组件Fegin,主要用于解决前面的服务调用与restTemplate耦合紧密的问题。 Spring Cloud认知学习(三):声明式调用Feign的使用
💡这一篇介绍一个新的组件Hystrix,Hystrix是一个熔断器,可以用于解决微服务调用中发送的服务熔断和服务降级问题。
Hystrix
* Hystrix是服务熔断器,用于解决服务熔断和服务降级的情况。
服务熔断和服务降级
- 主要是解决服务熔断和服务降级的问题。
服务熔断主要用了解决服务雪崩,我们先来介绍一下服务雪崩的概念。 - 服务雪崩:假如有很多个服务都需要调用A服务,但A突然卡住了,响应很慢,在高并发的情况下,此时A服务就会持有了过量的资源,而导致其他服务的资源不足,从而影响其他服务的使用,甚至可能传播性地导致整个系统崩溃。
- 服务熔断:熔断的概念是什么呢?就是假如说你家里用了超大的功率的电器,你家的电闸就会为了避免造成危险而帮你跳闸断电。对于服务雪崩也是这样的,他会防止服务占用过量的资源。
原理:用户的请求将不再直接调用服务,而是通过一个线程池来调用服务,当线程池中没有空闲的线程时,就会返回一个简单的执行结果(需要设定,可能是一个提示信息或者一个假数据)。 - 服务降级:当某一个主要的服务端的资源不够的时候,可能此时其他的不太重要服务需要进行关闭来为他腾出空间(就好像与支付功能相比,换头像这个功能就可以暂时为支付功能献身了),这就是服务的降级。虽然降级了,但理论上还应该给消费者一个保底的回应,比如说返回提示说服务已经关闭。由于此时服务的提供者已经关闭了,所以这个判断只能发生在服务的消费者中。
💡:对于上面的这两种情况,如果是服务熔断,既可以部署在消费者也可以部署在生产者,因为只是一个“等太久就不等待”的处理,这个等太久既可以事消费者判断也可以事生产者判断;如果是服务降级,由于此时服务提供者大多都关闭了,所以这时候Hystrix只能部署在消费者端。
💡:由于Hystrix其实就是对于错误情况的处理,服务的过度消耗资源(雪崩)和降级其实都是服务不可用,他们的处理在Hystrix其实都是一样的。所以下面就根据Hystrix的部署位置来演示。
简单使用示例:
下面的代码可以参考:Hystrix简单使用实验
部署在服务提供者
这次我们修改模块spring-cloud-user-service-8003
1.导入依赖:
<!--增加hystrix依赖 start-->
<!--旧版本的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--增加hystrix依赖 end-->
2.修改UserController,给listUser增加一个Hystrix处理方法listUserByHystirx。@HystrixCommand(fallbackMethod = "listUserByHystirx")
代表发生不可用的时候,就会调用listUserByHystirx来返回结果。
package com.progor.study.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.progor.study.entity.User;
import com.progor.study.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
// 由于返回json数据,懒得加注解@ResponseBody了,加个RestController
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/{id}")
public User getUser(@PathVariable Integer id) {
User user = userService.getUser(id);
if (user == null) {
throw new RuntimeException("该ID:" + id + "没有对应的用户信息");
}
return user;
}
private static int count = 0;
@GetMapping("/user/list")
// fallbackMethod时发生错误时调用的方法,
// commandProperties用于配置熔断器,requestVolumeThreshold代表请求多少次就不再尝试调用原方法,直接调用错误处理方法。
@HystrixCommand(fallbackMethod = "listUserByHystirx",commandProperties = {
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"), //请求次数
}) //
public List<User> listUser() throws InterruptedException {
count = count+1; // 假装偶尔发生了bug
System.out.println(count);
// 偶尔发生时
// if (count%2 == 0){
// Thread.sleep(5000);
// }
// 一直发生时:
Thread.sleep(5000);
List<User> users = userService.listUser();
return users;
}
public List<User> listUserByHystirx() {
User user = new User(0,"null","null");
List<User> users = new ArrayList<>();
users.add(user);
return users;
}
}
<br>
3.在主程序类中开启hystrix:
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix // 开启hystrix
public class UserService8003Application {
public static void main(String[] args) {
SpringApplication.run(UserService8003Application.class, args);
}
}
4.启动spring-cloud-user-service-8003
,访问http://localhost:8003/user/list
,发现如果发生错误的时候,会调用listUserByHystirx来返回。
🔵如果你通过服务消费者来调用8003的服务的话,这时候也会一样会在发生错误的时候,调用listUserByHystirx来返回。
🔵注意,我上面的代码是一次执行成功,一次执行失败这样的顺序。
部署在服务消费者
目前我们只有一个服务消费者,所以我们要修改spring-cloud-user-consumer-80
1.在spring-cloud-user-consumer-80
模块导入依赖:
<!--增加hystrix依赖 start-->
<!--旧版本的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--增加hystrix依赖 end-->
2.修改MessageController2,增加@HystrixCommand
// 这个控制器用来处理使用fegin的情况
@RestController
public class MessageController2 {
@Autowired
private MessageService messageService;
@GetMapping("/msg2/list")
// 使用HystrixCommand
@HystrixCommand(fallbackMethod = "listByHystirx",commandProperties = {
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"), //请求次数
}) //如果请求次数达到5次都是失败,那么直接调用listByHystirx
public Map<String, String> list() {
return messageService.list();
}
public Map<String, String> listByHystirx() {
Map<String, String> map = new HashMap<>();
map.put("msg","服务端已停止服务");
return map;
}
}
3.修改主程序类,开始Hystrix,@EnableHystrix
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "USERSERIVE", configuration = MyConfig.class)
@EnableFeignClients // 使用feign
@EnableHystrix
public class UserConsumer80Application {
public static void main(String[] args) {
SpringApplication.run(UserConsumer80Application.class, args);
}
}
4.测试
- 💡启动
spring-cloud-user-consumer-80
和spring-cloud-eureka-server-7001
- 💡访问一次
http://localhost/msg2/list
。此时由于没有服务实例,那么应该会调用listByHystirx来返回结果。- 请注意我们之前创建的MessageService在8004和8005,如果我们没有启动8004或8005,那么此时eureka内部应该没有MessageService服务实例,所以你会发现会调用我们的错误处理方法中的结果来返回给我们,而如果我们调用
http://localhost/msg/list
由于我们没有做hystrix处理,那么就会报错;
- 请注意我们之前创建的MessageService在8004和8005,如果我们没有启动8004或8005,那么此时eureka内部应该没有MessageService服务实例,所以你会发现会调用我们的错误处理方法中的结果来返回给我们,而如果我们调用
- 💡启动
spring-cloud-message-service-8004
,访问http://localhost/msg2/list
,是访问成功的。【但由于我们设置了重试次数为5,如果你启动了8004还是访问失败,那么尝试重启一下80吧,不然要等重新拉取eureka的信息(30s大概)。】 - 💡再停掉
spring-cloud-message-service-8004
,访问http://localhost/msg2/list
,会调用listByHystirx来返回结果。
整合feign
🔵Hystrix还可以与Fegin整合,也是相当于部署在服务消费者。
下面的代码可以参考:Hystrix整合Feign使用实验
1.修改Feign代码
由于我们只在spring-cloud-common-data
模块整合了fegin,所以我们要在spring-cloud-common-data
做实验了。
1.在spring-cloud-common-data
模块导入依赖。
<!--增加hystrix依赖 start-->
<!--旧版本的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--增加hystrix依赖 end-->
2.修改MessageService
,在FeignClient注解中增加fallback
// 由于这种服务的服务消费者可能比较多,放到共有依赖中。
// 使用fallback指定一个类,这个类实现了MessageService,发生服务不可用的时候就会调用这个类中方法
@FeignClient(value = "MESSAGESERIVE",fallback = MessageServiceHystrix.class)
public interface MessageService {
// 这里使用RequestMapping将服务提供者的方法与本地Service方法建立映射
@RequestMapping(value = "/msg/list", method = RequestMethod.GET)
Map<String, String> list();
}
3.创建MessageServiceHystrix:
package com.progor.study.service;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
// 注意,要使用@Component
@Component
public class MessageServiceHystrix implements MessageService {
@Override
public Map<String, String> list() {
Map<String, String> map = new HashMap<>();
map.put("msg","服务端已停止服务");
return map;
}
}
2.修改消费者
1.由于我们上面在MessageController2中使用了Hystrix,我们新建一个MessageController3:
@RestController
public class MessageController3 {
@Autowired
private MessageService messageService;
@GetMapping("/msg3/list")
public Map<String, String> list() {
return messageService.list();
}
}
2.修改spring-cloud-user-consumer-80
中的application.yml增加如下内容:
feign:
hystrix:
enabled: true # 用来开启fegin中的hystrix
🔴注意,此时主程序类中的@EnableHystrix可以没有
3.测试:
💡启动spring-cloud-user-consumer-80
和spring-cloud-eureka-server-7001
,访问http://localhost/msg2/list
- 💡访问一次
http://localhost/msg3/list
。此时由于没有服务实例,那么应该会调用listByHystirx来返回结果。- 请注意我们之前创建的MessageService在8004和8005,如果我们没有启动8004或8005,那么此时eureka内部应该没有MessageService服务实例,所以你会发现会调用我们的错误处理方法中的结果来返回给我们,而如果我们调用
http://localhost/msg/list
由于我们没有做hystrix处理,那么就会报错;
- 请注意我们之前创建的MessageService在8004和8005,如果我们没有启动8004或8005,那么此时eureka内部应该没有MessageService服务实例,所以你会发现会调用我们的错误处理方法中的结果来返回给我们,而如果我们调用
- 💡启动
spring-cloud-message-service-8004
,访问http://localhost/msg2/list
,是访问成功的。【如果你启动了8004还是访问失败,那么尝试重启一下80吧,不然要等重新拉取eureka的信息(30s大概)。】 - 💡再停掉
spring-cloud-message-service-8004
,访问http://localhost/msg2/list
,会调用listByHystirx来返回结果。
Hystrix Dashboard
💡Hystrix Dashboard是一个监控Hystrix熔断器状况的组件,有图形化的数据统计界面,你可以通过查看熔断器的统计数据来判断服务的状况。
💡对于restTemplate和fegin整合Hystrix Dashboard的方式都是一样的。
下面来演示整合,代码可以参考:Hystrix Dashboard整合使用实验,PS,这里的commit的注释上一个版本提交错了一个代码,请以这个版本的为准
有点小问题,我以为我提交了一个错误的版本,没想到我revert成功了,参考这个版本的代码即可。上一个版本Hystrix整合Feign使用实验
不存在错误。
1.配置启动Hystrix Dashboard
0.创建模块spring-cloud-hystrix-dashboard-9001
1.导入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
2.主程序类添加注解@EnableHystrixDashboard:
@SpringBootApplication
@EnableHystrixDashboard
public class SpringCloudHystrixDashboard9001Application {
public static void main(String[] args) {
SpringApplication.run(SpringCloudHystrixDashboard9001Application.class, args);
}
}
3.访问http://localhost:9001/hystrix
,如果能正常看到页面,那么就启动成功了。
💡上面启动了Hystrix Dashboard服务,但Hystrix Dashboard是一个接收服务的数据的组件,服务不开放数据,它也接收不了,下面会进行配置。
2.修改服务提供者
1.在服务提供者spring-cloud-user-service-8003中增加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.在spring-cloud-user-service-8003的主程序类中增加ServletRegistrationBean的Bean:【这个是在Finchley的时候增加的好像,以前可以不配这个Bean】
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix // 开启hystrix
public class UserService8003Application {
public static void main(String[] args) {
SpringApplication.run(UserService8003Application.class, args);
}
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.addUrlMappings("/hystrix.stream");//路径
return registrationBean;
}
}
3.修改8003的熔断器处理代码:
因为Hystrix dashboard只能检测发生被熔断器处理的方法。没有熔断处理的方法无法被监控。这里解开注释,用于测试时好时坏时,对于Hystrix dashboard的成图效果。
3.测试:
💡启动spring-cloud-user-service-8003
,spring-cloud-eureka-server-7001
💡在谷歌浏览器中查看localhost:8003/hystrix.stream
【如果是在火狐,可能会进行下载。】,这是8003开放的请求流,Hystrix dashboard实际上就是对这个流来分析的。
当你访问http://localhost:8003/user/list
的时候因为我们的代码问题,它偶尔会发生问题,此时上图的ping才会显示出不同的内容。
💡访问http://localhost:9001/hystrix
,是一个如下的页面:
在中间输入http://localhost:8003/hystrix.stream
,让Hystrix Dashboard监控这个流,然后点击下面的按钮,你就进入一个这样的图:
Delay是监控的刷新时间。
7色:中间的7种颜色的,要参考右上角的颜色问题。
- Success:代表请求成功
- Short-Circuited:代表熔断数
- Bad Request:代表抛出HystrixBadRequestException的次数
- Timeout:代表请求超时的次数
- Rejected:线程池拒绝数
- Failure:抛出异常的请求
- Error:最近10秒的错误比例
1圈:这个圈会随着七种数字的总和而变大,圈越大,访问越多;圈的颜色是其中颜色的混搭,比如成功为主的时候,偏于绿色,如果是偏于红色,那么说明请求失败很多。【绿、黄、橙、红】
1线:线是请求次数的高低,没有请求的时候就是一条直线,有请求的时候就是一张折线图。
想了解图中更多的内容可以参考:图内数据官方参考文档