基础知识
首次分布式服务系统面临的问题
复杂的分布式体系结构中的应用程序有数十个依赖关系,每个依赖有时候难免发生问题,这个时候可能引发连锁反应,导致整个系统雪崩。
所以就有了Hystrix:
官网地址
Hystrix的主要作用:
服务降级:服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示,fallback。发生的场景程序运行异常,超时,服务熔断触发服务降级,线程池/信号池打满也会导致服务降级。
服务熔断:类比保险丝,达到最大服务访问后直接拒绝访问,然后调用服务降级的方法。
服务限流:秒杀高并发操作,严禁一窝蜂的过来拥挤,大家排队,有序进行。
Hystrix使用
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 简单的引入案例
我们首先写一个简单的应用进行压测,从而引出Hystrix
@Service
public class PamentService {
public String info(Integer id) {
return "线程池" + Thread.currentThread().getName() + "info_ok";
}
public String info_false(Integer id) {
int time = 3;
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池" + Thread.currentThread().getName() + "timeoutInfo 耗时 : " + time;
}
}
@RestController
public class PaymentController {
@Resource
PamentService pamentService;
@Value("${server.port}")
private String port;
@GetMapping("/pament/info/{id}")
public String getInfo(@PathVariable("id") Integer id) {
return pamentService.info(id);
}
@GetMapping("/pament/info_false/{id}")
public String info_false(@PathVariable("id") Integer id) {
return pamentService.info_false(id);
}
}
接下来我们使用Jmeter进行压测
新建200个线程,每个线程循环100次
发送请求:
原本程序启动访问info的时候基本是0延迟,由于我们发送大量的请求到info_false,占用大量的资源造成拥堵,我们发现访问info的时候出现了卡顿,而接下来我们就要Hystrix来解决这个问题。
同时我们在消费端,去调用这两个服(用openfeign去调用提供端服务,可以参考springcloud服务接口调用OpenFeign这篇文章,这里不再重复),同样开启压测,结果当然一样也会拥堵。下面就来看看Hystrix的具体使用。
服务降级
我们在服务逻辑处配置fallback
@Service
public class PamentService {
public String info(Integer id) {
return "线程池" + Thread.currentThread().getName() + "info_ok";
}
@HystrixCommand(fallbackMethod = "timeOutHandler", commandProperties = {
// 这个线程池的超时时间是3秒,超过3秒就出错
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String info_false(Integer id) {
int time = 5;
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池" + Thread.currentThread().getName() + "timeoutInfo 耗时 : " + time;
}
public String timeOutHandler(Integer id) {
return "serveice bussy please wait";
}
同时在主启动类添加注解:@EnableCircuitBreaker
运行程序,我们休眠5秒才返回结果。但是我们配置了服务3秒之后就超时,所以就会服务降级,结果如下:
注意:timeOutHandler这个方法相当于finally,用来兜底。它所接受的参数应该和服务方法的参数一致,否则会报错。比如这里我们给 info_false配置了服务降级,但是如果我们timeOutHandler不写参数就找不到fallback这个方法。
接下我们再测试一下,发生异常的情况:
public String info_false(Integer id) {
int time = 5;
int a = 100 / 0; // 人为的制造异常,用于测试异常发生,服务降级
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池" + Thread.currentThread().getName() + "timeoutInfo 耗时 : " + time;
}
毫无疑问它同样会降级
上面演示的是服务端的服务降级。
通常我们客户端可能也有一些限制,比如说3秒还没有返回结果就认为超时。这个时候需要对客户端进行配置。
因为我们是通过feign来调用,所以我们需要开启配置
feign:
hystrix:
enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。
@RestController
public class TestController {
@Resource
Servic servic;
@GetMapping("consumer/getinfo/{id}")
public String getInfo(@PathVariable("id") Integer id) {
return servic.getInfo(id);
}
@GetMapping("consumer/getfalse_info/{id}")
public String getFalseInfo(@PathVariable("id") Integer id) {
return servic.info_false(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
// 如果自己报错,也有兜底的方法
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500") //3秒钟以内就是正常的业务逻辑
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = servic.info_false(id);
System.out.println("服务熔断了");
return result;
}
//兜底方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
}
}
主启动类:@EnableHystrix注解
这个是个复合注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableCircuitBreaker
public @interface EnableHystrix {
}
我们提供服务的地方配置的是5s,这边超时配置的是1.5s很明显是超时,所以会发生服务降级。
按照上面的配置,我们每一个方法都要配置一个fallback,这样好像不太合理,有些fallback的逻辑是相同的。需要把它抽取出来,定义成为全局的fallback。
@RestController
@DefaultProperties(defaultFallback = "paymentTimeOutFallbackMethodGlobal")
public class TestController {
@Resource
Servic servic;
// 添加这个注解,没有指定fallback那么就会直接使用默认的fallback
@GetMapping("consumer/getinfo/{id}")
@HystrixCommand
public String getInfo(@PathVariable("id") Integer id) {
System.out.println("jfdksdjfjdsjff");
int a = 100/ 0;
return servic.getInfo(id);
}
@GetMapping("consumer/getfalse_info/{id}")
public String getFalseInfo(@PathVariable("id") Integer id) {
return servic.info_false(id);
}
// 这边指定了fallback,那就用自己的fallback
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500") //3秒钟以内就是正常的业务逻辑
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = servic.info_false(id);
System.out.println("服务熔断了");
return result;
}
//兜底方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
}
// 全局的兜底方法
//兜底方法
public String paymentTimeOutFallbackMethodGlobal(){
return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查
自己,(┬_┬)";
}
}
```
特别注意:在上面我们配置的时候我提醒了参数一定要一致,但是在配置全局fallback的时候它是无参,因为我们并不知道那个方法发生了异常,所以我们不知道参数类型。
我们发现我们上面的业务逻辑和熔断是混合在一起的,那么我们怎么才能解耦呢?
```java
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = ServiceImpl.class)
public interface Service {
@GetMapping("/pament/info/{id}")
String getInfo(@PathVariable("id") Integer id);
@GetMapping("/pament/info_false/{id}")
String info_false(@PathVariable("id") Integer id);
}
```
```java
@Component
public class ServiceImpl implements Service {
@Override
public String getInfo(Integer id) {
return "服务异常!";
}
@Override
public String info_false(Integer id) {
return "服务异常!";
}
}
```
这个时候比如我们把服务的提供方直接关掉,这个时候它是异常的,就会走实现类的fallback。
![在这里插入图片描述](https://img-blog.csdnimg.cn/f8035405eb3643fc92eaac9e80cbcbb8.png)
这样的话我们可以我们就实现了fallback与业务的解耦。
但是这里需要特别注意:这种方式是不会处理controller里面的异常的,如下面的代码它不会走服务降级的逻辑,这种方式专注的是某个服务的服务降级。
```java
@Resource
Service servic;
@GetMapping("consumer/getinfo/{id}")
public String getInfo(@PathVariable("id") Integer id) {
int a = 100 / 0;
return servic.getInfo(id);
}
```
#### **服务降熔断**
服务熔断是应对雪崩效应的一种服务链路保护机制。当某个服务出现问题会进行服务降级,等到此服务响应正常后,恢复调佣链路。
我们在服务的实现类上添加方法,配置这个服务熔断的相关配置。
```java
// 服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), //时间范围
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
if (id < 0){
throw new RuntimeException("*****id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
}
// 服务降级fallback
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
return "id 不能负数,请稍候再试,(┬_┬)/~~ id: " +id;
}
```
controller
```java
@GetMapping("/pament/fuse/{id}")
public String fuse(@PathVariable("id") Integer id) {
return pamentService.paymentCircuitBreaker(id);
}
```
当我们携带负数ID进行请求的时候,就会不断的抛出异常,这个时候失败率超过60%。此时服务就会降级,这个时候我们正常的请求也会走fallback,过一定的时间后再请求会恢复正常。
![在这里插入图片描述](https://img-blog.csdnimg.cn/8b02147c970146798c4d84138f39b3ce.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/35a2cbcaba1341968574a1d041c17296.png)
更多相关的配置(以下图片都来自于尚硅谷):
![在这里插入图片描述](https://img-blog.csdnimg.cn/bb594475a34d45ee920d4e4ab87385ee.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/56b8c33deb4149ce8fe578929a66e538.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/0d527d3808994adb86fe555860a6b690.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/ae753ae528c14a4cb3138f42f97e57a4.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/72d12a398e0845fb8494e395734fc869.png)
### Hystrix图形界面(监控界面)
创建一个新的springboot项目,添加依赖:
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
```
主启动类添加:@EnableHystrixDashboard
然后访问:http://localhost:9001/hystrix就可以看到如下界面
![在这里插入图片描述](https://img-blog.csdnimg.cn/33b4ef1aeca4403c8408ebbce0bed2f2.png)
在被监控的服务主启动类添加配置:
```java
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
```
在界面填写监控地址:
http://localhost:8001/hystrix.stream
可以看到相关的信息:
![在这里插入图片描述](https://img-blog.csdnimg.cn/edfd194bc51a4ac68edc7474146770c2.png)
特别感谢:以上部分图片和代码来源于尚硅谷视频教学。