SpringCloud-Hystrix

问题根源

服务解耦

开发结构逐步发展,随便梳理一下

  • 集中式

单机版,全封闭,黑盒子。

主体的表现就是单机黑盒,显著的表现就是兄弟义气

兄弟义气:有福同享,有难同当,个体即集体,集体及个体,我为人人,人人为我

反正出现些鸡毛蒜皮,想要修改,就不得不停服务。

更关键的,服务只有一个。

  • 模块化

模块化之后,分工确定了,容易管理,也更容易排错。

这算是开发管理,但是根本问题还是没有解决,服务仍然是单例。

即使容易定位问题,修改时还是不得不停掉服务。

  • 服务化

模块服务化,把细小的模块进行放大,以后修改时,可以停掉某个服务,不影响客户的体验。

好比用RabbitMq进行了分隔的两个模块服务。

当任务处理服务停掉时,仍然能够接收,创建任务。

创建任务服务不行了,但是已经接收的任务依旧会进行处理。

而且,这还带来了两个好处

  1. 专精

拥有模块化的优点:专精

同时,也更加容易进行维护,且不影响用户体验。

提升服务效率的同时还不影响服务体验。

  1. 容灾

数据已经在队列里面了,即使处理服务坏掉了,来不及修的情况下,我们可以启动同服务的新实例。

也就是分布式中的多实例,或者负载均衡来容灾也可以。

  • 分布式

分布式的话,可以说把问题进一步放大了。

目光不再局限于单机,转向了多机器的综合稳定上面了。

回顾一下发展

  • 功能拆分

单功能转向功能模块,容易组合,容易维护。

  • 服务拆分

单进程多进程,服务专精,提升效率,提升体验,提高容灾。

  • 集群综合

单机分布式,不局限于服务的多机器部署,防止单一机器故障。

而是多服务,多实例的共同维护,比如redis哨兵的集群。

保证了服务的可用性

依赖加重

之前也说了,虽然粒度越拆越细,但是管理是越来越高。

  • 扇出

原来都是单机服务自己干,虽然不容易管理,但是牵扯真的很少。

一个人做很多,哪怕很累,但是都了然于心;人多了,你就不得不去依赖别人。

然后被别人阻塞。

服务始终是整体的,而不是片面的。

拆分之后,从功能上单一了,独立了,但是从服务上却不得不说更依赖其他模块进行补充了。

当任务接入进来的时候,依赖于其他模块的执行。

把两种模式进行对比的话

A作为入口,Z作为出口

  • 单机

A -> Z

  • 分布

A -> B -> C -> D -> … -> X -> Y -> Z

虽然流程都是相同的,但是服务化模块独立了,服务周期明显拉长了。

可谓失之毫厘,谬以千里,随着调用的加深,影响逐渐的扩大、加深、拓宽,这就是扇出

  • 雪崩

扇出是针对服务周期而言的,接下来说一下专精的破坏力。

假设我的中间队列大小是500,接收速度是200, 处理速度是200。

服务化,分布式以后,更加专精了。

接收速度翻倍成400,处理速度翻倍成400。

或者1000-1000,很好很强大。

但是处理模块坏掉的话,留给我们的反应时间是

500/200 = 2.5

500/400 = 1.25

500/1000 = 0.5

简而言之,留给我的反应时间越来越少了。

由于扇出,对后面的服务依赖加重。

加上专精,如果后面的服务出现问题,服务就会层级的崩溃。

正如扇出的向后依赖加重,雪崩是向前影响加深。

解决办法

人有力尽时,办法虽然很多,问题却总不能够全部排尽。

即使多实例,即使分布式,我们也只能高可用,却做不到必可用

回顾最初的根源,我们只能尽力的维护快速应答这一逻辑联系。

于此,我们转换思路,变成快速失败

当服务异常的时候,为了防止扇出引起的依赖加重由于异常引起阻塞进而导致的雪崩

我们需要维护快速应答,即使处理方式是错误的,但是只要能够维持正确的应答方式,

就能够为我们争取到足够的时间进行补救。

也不一定是快速失败,主要是不能够阻塞,必须尽力维护正常通信

也就是说,我们必须设置一个服务失败时的备用处理方案,进而防止雪崩

正如一个链接,你能够等待多久。

如果需要半小时,而且事先不知道的情况下,你会继续等下去么。

即使结果出来了,体验不好,也没啥好心情了。

更别说后续还有好多人也等你等完然后来访问,你等的越久,后面的人等的越久。

排队的时间可能比服务的时间更久。

但是如果超过五秒就返回500,至少心情没那么坏,服务器压力也没那么大,更没有阻塞

尽快的修复服务,也就没啥大问题了,哪怕是个500

hystrix

客户端

@RestController
public class MyController {
    @GetMapping("/div")
    public String div(@RequestParam("a") String a, @RequestParam("b") String b){
        return String.valueOf(Integer.valueOf(a) / Integer.valueOf(b));
    }
}

拿这个作为开头,如果我这样访问会怎样

curl http://8080/div?a=10&b=0

作为微服务,需要等待你的返回结果,但是异常了,阻塞了。

前面的不断积压,后面的闲的要死,该当何罪。

hystrix改动一下。

  • pom
<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-hystrix</artifactId>
		<version>1.4.6.RELEASE</version>
	</dependency>
  • controller
@RestController
public class MyController {
    @GetMapping("/div")
    @HystrixCommand(fallbackMethod = "error")
    public String div(@RequestParam("a") String a, @RequestParam("b") String b){
        return String.valueOf(Integer.valueOf(a) / Integer.valueOf(b));
    }
    public String error(String a, String b){
        return "error";
    }
}

HystrixCommand:断路器

fallbackMethod:指定失败时的处理方法

error:经过试验,需同参同返回

  • main
@EnableEurekaClient
@SpringBootApplication
@EnableHystrix
public class ProviderApplication {
	public static void main(String[] args) {
		SpringApplication.run(ProviderApplication.class, args);
	}
}
  • 结果

然后,即使是这样

curl http://8080/div?a=10&b=0

给我的虽然是error,但是服务确实正常的,正常通信得以维护。

服务端

  • 盲点

有一个盲点,不知道注意到没有。

客户端使用hystrix的确有效果,但是针对的是服务异常

但是分布式中,焦点从逻辑转移到了服务

也就是说,如果坏掉的不是程序,不是异常情况,而是服务器宕机了怎么办。

如果服务本身就挂掉了,怎么还能够给你备选方案的响应呢。

最终,这种方案,还能没能够维护正常通信,保证快速交互

不说了,服务端来一发。

  • pom
<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-hystrix</artifactId>
		<version>1.4.6.RELEASE</version>
	</dependency>
  • service
@FeignClient(value = "PROVIDER")
public interface Service {
    @RequestMapping(value = "/div")
    public String calc(@RequestParam("a") String a, @RequestParam("b") String b);
}

Feignrest接口,没忘记吧

  • controller
@RestController
public class MyController {
    Service service;
    @GetMapping("/calc")
    @HystrixCommand(fallbackMethod = "error")
    public String calc(@RequestParam("a") String a, @RequestParam("b") String b){
        String result = service.calc(a, b);
        return result;
    }
    public String error(String a, String b){
        return "godme";
    }
}

客户端的记得去掉,要不实验结果不准

  • main
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class ConsumerApplication {
	public static void main(String[] args) {
		SpringApplication.run(ConsumerApplication.class, args);
	}
}

然后访问一下

curl http://localhost:8802/calc?a=10&b=0

不报错了,给了个godme

不过,注意到了么,概念又转换了。

  • 阻塞

最开始出现问题,就会阻塞服务,这是根源问题。

  • 异常应答

然后异常发源地自己管理,异常时候维护正常通信,避免阻塞

  • 异常处理

不过provider即使具备异常排除的功能,也不能够一定的维护正常通信

因为它本身作为服务,就可能会宕掉,不是不信任你,这已经超出了它自身的能力范围。

这种情况下,调用方自己处理,不再依赖于provider信息反馈

就和访问连接一样,服务异常的时候,它自动提示500,这种办法是有好处的。

但是,它全部都挂掉了,不给我500,我还在等着呢,等到啥时候啊。

所以设置好超时以后,规定时间范围内,等它返回。

超过一定时间,我就自己处理了,不干等。

我们使用浏览器访问服务时就是这样,找不到的超时就报错,绝不傻傻的等。

先斩后奏,事急从权,顾客是上帝,不等你了,就是这样。

汇总处理

如果对于controller中的每个接口都@HystrixCommand,也很繁琐啊,介绍一个,不,两个好方法。

  • fallback
@Component
public class MyFallback implements Service {
    @Override
    public String calc(String a, String b) {
        return "godme";
    }
}

恩,实现Service接口,里面的就是错误时候的备选方案。

记得Component,要不行不通的哦

@FeignClient(name = "PROVIDER", fallback = MyFallback.class)
public interface Service {
    @RequestMapping(value = "/div")
    public String calc(@RequestParam("a") String a, @RequestParam("b") String b);
}

@FeignClient(name = "PROVIDER", fallback = MyFallback.class)

指定微服务名称时候直接指定错误时回调的Service即可,会自动找到对应接口进行处理的。

feign:
  hystrix:
    enabled: true

这个一定要配置上,我会连续说三遍的

  • fallbackFactory
@Component
public class MyFallback implements FallbackFactory<Service> {
    @Override
    public Service create(Throwable cause) {
        return new Service() {
            @Override
            public String calc(String a, String b) {
                return "godme";
            }
        };
    }
}

FallbackFactory:泛型,记得是自己rest接口

相比较于fallback,这个会吧cause传进来,信息更丰富一些

@FeignClient(name = "PROVIDER", fallbackFactory = MyFallback.class)
public interface Service {
    @RequestMapping(value = "/div")
    public String calc(@RequestParam("a") String a, @RequestParam("b") String b);
}

@FeignClient(name = "PROVIDER", fallbackFactory = MyFallback.class)

不用怀疑,不一样的。

fallback变成了fallbaclfactory

feign:
  hystrix:
    enabled: true

配置文件,第二遍了

因为是feign中配置的hystrix,默认是关闭的,一定要开启啊

  • yaml
feign:
  hystrix:
    enabled: true

第三遍了,为什么要说三遍呢,不仅是因为它很重要。

更因为有个傻瓜干调了三个小时,翻阅了好多文档、博客、github

结果是自己二逼手写enabled自信写成了enable

feign的配置也没有个自动提示,也就这样过去了,然后就是hystrix死活不生效。

配置文件的字段都是圣经啊,理解可以不同,写出来一个字母都不能有偏差啊。

dashboard

  • pom
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
            <version>2.0.2.RELEASE</version>
        </dependency>

这个东西很重要,为什么呢,看看这个

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>

你可能会遇见java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplicationBuilder这种错误,大部分是版本不匹配。

不过放心好了,还可能是包倒错了,一个单词之差。

  • enable
@SpringBootApplication
@EnableHystrixDashboard
public class DashboardApplication {
	public static void main(String[] args) {
		SpringApplication.run(DashboardApplication.class, args);
	}
}

@EnableHystrixDashboard:使能,我会告诉你导错包的话除了启动会报错,其他完全正确么。

  • yml
server:
  port: 9001

给个端口就行,其他没啥。

  • 小差异
  • 无代码
  • 不注册

因为就是一个监控服务,起来就行,连接的时候再指定即可,无需其他操作,不用注册。

http://localhost:port/hystrix

不过,对需要监察的服务有要求,必须开启actuator,否则状态是看不到的。

actuator

  • pom
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
			<version>2.1.0.RELEASE</version>
		</dependency>
  • yml
management:
  endpoints:
    web:
      exposure:
        include: "*"
  server:
    port: 54301
    servlet:
      context-path: /
    ssl:
      enabled: false
  endpoint:
    health:
      show-details: always

然后就没有问题了.

  • 检查
  1. 状态
http://localhost:port/actuator
# 先访问自身确定是否开启访问
  1. 流访问
http://localhost:port/actuator/hystrix.stream
# 流访问,会不停的 ping 去获取信息
  1. 统计页面
http://localhost:port/actuator/hystrix.stream
# 别问为什么和上面一样,因为就是上面那个啊
# 不过它会自身进行统计并绘图,那就好看很多

在这里插入图片描述在这里插入图片描述

  • 小总结

恩,先说一说先决条件

  • actuator
  • hystrix

如果其中一个没配置,就免谈。

换句话说,如果我们忽视hystrix,不为了断路,单纯为了监控,也必须把它配上。

所以hystrix这个东西的两面啊,断路可能只是装饰,如果我们指向要分析的话。

然后说一下配置

  • pom
  • enable

使能是家常了,关键是pom啊,太像了,太麻糊人了。

总结

  • 为什么雪崩
  • 为什么断路
  • 断路的级别
  • 怎么去断路
  • 怎么去监控

问题都是一层层的递进的,不管是雪崩的渊源,还是客户端或者服务端的断路,仔细思考总能抓住。

很有意思,也很重要。

不过最后的dashboard一出来,我想我可能只用来监控了,哈哈哈。

也不算是玩闹,因为hystrix可以算作是服务管理的一个组件,所谓雪崩或者其他,都是为了保障服务运行。

客户端转移到服务端的断路,单机变分布,都是管理上的考量。

不仅专注于服务的功能,目光转向稳定,然后还要能够监控

实现固然是好的,但是不能沉迷半途,把服务监控提升一下,不要只是一个能实现服务监控的程序员。

如何监控,指标,描述,标准,这些东西同样有价值,或许更高。