一. 前言
上一部分添加了Ribbon实现了负载均衡,并且使用了Feign实现声明式的REST调用。微服务架构中还有很多其他组件,让我们继续学习其他有用的微服务架构的组件。
二. 使用熔断器Hystrix
微服务架构是由多个微服务组成的系统。虽然架构更加清晰,分工更加明确,但也存在相应的问题:出现问题难以排查(到底是哪个服务出现问题?)。而且还会存在微服务所特有的问题:雪崩效应。当其中一个微服务出现故障时,由于微服务之间会存在依赖关系,所以其他服务也会渐渐地受到影响,最后影响到整个系统。如果这时候放任不管,那么请求就会堆积,导致系统彻底瘫痪。(有理论分析,即使每个系统的正常运行时间是99.9%,仍然会因为雪崩效应导致很严重的效果)所以我们需要一个组件来管理微服务,监控微服务,当组件发现(认为)服务出现故障,会把系统调到降级模式,拒绝继续接受请求,避免请求堆积。等到一定时间之后再申请一个请求,如果请求通过,解除降级模式,否则,继续拒绝。这个就是Hystrix,熔断器。逻辑也很简单,其实就相当于电路里的断路器。
正常情况下,断路器关闭,可正常请求依赖的服务。
当一段时间内,请求失败率达到一定阈值(例如错误率达到50%,或100次/分钟等),断路器就会打开。此时,不会再去请求依赖的服务。
使用步骤:
①导入依赖包spring-cloud-starter-netflix-hystrix
②启动类添加注解:@EnableHystrix, @EnableCircuitBreaker
③给Controller里的RequestMapping方法添加一个@HystrixCommand(fallback = "xxx")
的注解,xxx就是一个fallback方法名。当降级模式触发的时候,停止请求,改为调用fallback方法。
(HystrixCommand上可以进行很多配置)
一两次失败的request并不会导致断路器打开,而多次之后可以看到断路器确实已经开启:
Hystrix的隔离策略:线程隔离 和 信号量隔离。
一般推荐HystrixCommand使用线程隔离,而HystrixObservableCommand使用信号量隔离。
HystrixCommand和HystrixObservableCommand的区别:
①HystrixCommand用在依赖服务返回单个操作结果的时候。有两种执行方式
-execute():同步执行。从依赖的服务返回一个单一的结果对象,或是在发生错误的时候抛出异常。
-queue();异步执行。直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果对象。
②HystrixObservableCommand 用在依赖服务返回多个操作结果的时候。它也实现了两种执行方式
-observe():返回Obervable对象,他代表了操作的多个结果,他是一个HotObservable
-toObservable():同样返回Observable对象,也代表了操作多个结果,但它返回的是一个Cold Observable。
参考链接:https://www.cnblogs.com/happyflyingpig/p/8079308.html
线程隔离和信号量隔离的区别:
THREAD —— it executes on a separate thread and concurrent requests are limited by the number of threads in the thread-pool
SEMAPHORE—— it executes on the calling thread and concurrent requests are limited by the semaphore count.
简单地说,就是线程隔离就是HystrixCommand会在单独的线程上执行(new一个线程),受到线程池的线程数量限制。
而信号量隔离,仍然在调用的线程上执行,隔离等级由线程缩小为信号量。
显然,线程更安全,开销也更高,而信号量可以更高效,开销较低,但限制也较大(信号量个数限制
Feign使用Hystrix:
前面使用Hystrix是手动定义一个fallback方法,如果需要为Feign整合Hystrix,那么也很简单,为自定义的FeignClient接口提供一个实现类,然后接口的定义@FeignClient增加一个fallback属性即可。
看起来是没有任何问题的,但却出错了,而且一直解决不了,差点直接弃坑。
当中可能存在的问题:
①没有配置Hystrix?
网上一堆互联网垃圾互抄都是说这个问题,但并不是,我第一时间就在yml里设置好了
②Feign已经包含了Hystrix,所以把启动类的@EnableHystrix去掉?
难得看到一个不是提①的帖子,表示了是这个问题,但我去掉了还是存在500的问题
③CustomizeFeignClientImpl的问题?路径?另外两个方法?
都无关
④@EnableFeignClients注解的问题?@EnableHystrix的影响?
没有影响。
⑤primary = false ? @primary等等的原因?
都不是关键,如果是注入出错的时候,Spring启动的时候就会抛出注入失败的错误。显式给fallback类使用@primary其实就是直接调用它的方法了,那样虽然可以fallback但已经失去了意义。
⑥上一章节Feign的decoder,encoder,contract的问题?
有可能,换了一个全新的Controller(简化了各种东西),并且把自定义FeignConfiguration也去掉了,RequestLine也换回了RequestMapping。但这时报错无法Autowire,因为它不知道要注入的是CustomizeFeignClient,还是CustomizeFeignClientImpl(注入类型是CustomizeFeignClient,所以两个都是可行的)。这时候我不懂为什么之前没有这个问题,就因为之前有自定义FeignConfiguration?
但是这时候我给CustomizeFeignClient设置了primary = false(官方文档里的解法),还是不行。只能给Impl添加@Primary,可这时候又有问题了,因为优先是Impl,所以无论如何都是fallback,那么就失去了原本的意义。而如果是给接口设置@Primary,那么就是继续500错误。而且之前把Configuration去掉,那么Feign会直接失效的,也可能是这个问题。关于这部分,只好先用着Hystrix,而不是Feign整合Hystrix,具体还得看文档才能解决。
Feign使用Hystrix:(解决篇
问题原因:
①Feign依赖包已经包括了Ribbon包,所以需要把Ribbon包去掉。
1 | <!-- |
(Feign整合了很多内容,包括Eureka,Ribbon,Hystrix,但这里只有Ribbon起到了冲突,具体原因不明。其他的诸如spring-cloud-starter-netflix-hystrix等包,不去掉也没有影响。
②上一节在创建FeignClient实例的时候,用到了Feign.builder( ),然后认证内容都直接写在里面了,导致后面fallback是没有认证的。从而会导致401错误,最后变成500错误。
1 | userUserFeignClient = HystrixFeign.builder().client(client).decoder(decoder). |
改成:
1 |
|
(无须decoder,encoder等等,也不需要那版本烦人的xxx.FeignClientsConfiguration.class了。
如何确定是这里的关键问题?如何看到401错误?
把上述代码改了之后,访问URL,会返回401错误,因为这时候其实已经可以访问到fallback了,但因为是最后的findById的时候没有认证,所以导致出错。之前写Feign刚好有一个搁置的配置文件,用于对Feign的认证,把它加上,问题彻底解决:
1 | // when we need to add Interceptor for Feign, we need it to add the Http Basic auth. |
1 | "microservice-simple-provider-user", (name = |
使用fallbackFactory而不是fallback类,使得可以打印错误日志(fallback类实现FallbackFactory<xx>接口,实现create方法即可。
1 |
|
这样当前消费者service也能通过printStackTrace查看提供者service的错误信息(值得注意的是直接LOGGER.info(throwable)并没什么用,不会打印出任何东西。。至此,问题解决。
对某个Feign禁用Hystrix:
(先编写一个配置类,然后在@FeignClient中导入即可)
1 |
|
想要禁用Hystrix的@FeginClient引用该配置类即可,例如:
1 | "user", configuration = FeignDisableHystrixConfiguration.class) (name = |
也可以直接全局禁用Hystrix,在yml里,一般不会使用。
三. 使用Hystrix的监控
Hystrix除了实现容错,还有强大的实时监控功能。
步骤:
①首先,给Consumer服务添加Hystrix监控节点。
并不需要再导入依赖包了,因为spring-cloud-starter-netflix-hystrix
就已经足够。另外,书上的例子是根据@HystrixCommand
的继续的,它直接就拥有了hystrix.stream节点,不清楚为何。但我觉得可能是版本的原因,也可能是@HystrixCommand和Feign的区别,总而言之,上网查了一下,发现最可靠的办法还是:自己亲手创建者一个节点。这个过程很简单,而且也能对节点有更具体的认识,而不是像之前的,为什么会有info节点,health节点,到现在的hystrix.stream节点?都是系统自带的么?最好还是自己创建。
参考:https://blog.csdn.net/dangshushu/article/details/80416042
当然,这里添加的是``/actuator/hystrix.stream`,自己改一下就好,很简单。
这里当时遇到过问题,无法打开这个节点,忘了是什么原因了。一开始以为是@EnableHystrix或者@EnableCircuitBreaker跟@EnableHystrixDashboard冲突,因为删掉前面两个就可以了。但其实并不是,主要原因是,我没有导入Hystrix依赖包!可能是一开始以为Hystrix依赖包跟Hystrix-dashboard依赖包冲突,然后去掉了Hystrix依赖包。所以当时的原因是:没有加Hystrix依赖包,导致前两个注解出现问题而已,这同属一个包的注解如果不兼容,那一般不可能。
然后无论是打开浏览器查看hystrix.stream节点,还是直接查看hystrix查看dashboard界面,都可行了。这里又有一个问题,hystrix.stream一直ping,但没有内容。因为需要先在服务里执行至少一次操作,这里才会有信息,所以使用服务一次,比如xxx:8010/user/1,然后再看hystrix.stream节点就好。
②关于可视化监控Dashboard
首先需要导入依赖的:
1 | <dependency> |
只需要在启动类加上@EnableHystrixDashboard
即可。
对了,这里没有把Dashboard注册到Eureka上,一开始想着就在本身上监控,所以不注册也OK。
③Dashboard可以监控多个服务实例,但当存在很多个实例的时候,只能一次一次地在hystrix节点上修改url,比较麻烦,而且不能对比。这时候就需要Turbine来聚合监控数据,其实也就是一次显示所有的/hystrix.stream节点,更方便,而且可以横向对比了。
然后这时候发现前面的错了,我的架构搞错了,因为之前无论是Ribbon还是Feign等都直接在Consumer上添加,所以我也就直接都加到Consumer上了。原来Consumer上只需要添加一个Hystrix.stream节点即可(就是那个只有文字的Hystrix监控信息),然后对于dashboard跟Turbine,最好另外分别起两个modules,所以现在的项目架构应该是这样的:
这时就遇到了一个问题,太久没有创建子module,都忘了哪些是必须要添加的了,大概如下:
1 | <dependencies> |
PS:我觉得<dependencies>这部分应该是要放到父项目的,但现在懒得搞,后面重构再说。但h2数据库为什么也要导入我也不是很清楚。而且<dependencyManagement>也是必须的。还有启动类记得不能放在默认的package,不然无法scan。
然后Dashboard项目还好,其实跟上面说的是一样的,只是我们把consumer的那些内容都去掉了而已。然后刚刚又不小心解除了技能,之前在project左方找文件的时候,尤其是External Libraries,一堆文件找得头疼,各种快捷键也没有。刚刚才发现,无须快捷键,指到那个区域,直接输入便是搜索,无语了,一下就能把什么jackson,commonxxx揪出来了。
整个Dashboard项目结构:
Dashboard启动类前除了@SpringBootApplication,还要增加一个@EnableHystrixDashboard
:
1 |
|
yml只需要配置一个端口号即可:
1 | server: |
Turbine项目的结构同理,只是除了上面Dashboard要导入的资源,还有:
1 | <dependency> |
yml:(这个Turbine需要注册到Eureka,所以还要配置一下Eureka相关属性。但Eureka的依赖包前面是直接放在父项目了,所以是无须在这个module特别导入的)
1 | server: |
这里的app-config就是指定要监控哪些服务,可以指定多个,逗号分隔开即可,甚至同一个实例的不同端口号启动也可以一起监控。由于我没有编写多个consumer,后面就直接拿双端口号实例来测试了。
而且,因为Turbine需要注册到Eureka,所以启动类应该是这样的:(exclude也是必须的,否则它会去yml里找,找不到就又生成了默认密钥了! PS:去掉Turbine的security就不需要exclude了)
1 | (exclude {org.springframework.boot.autoconfigure.security. |
填坑
然后启动类加上@EnableTurbine,启动,理论上应该就OK了,但果然还是踩坑了,然后下面开始讲述填坑的过程:
①首先必须清晰这几个项目之间是如何工作的,不能只知道它这样写work,而不清楚为何这样写,不求甚解的结果都懂的,后面稍微出现了点状况你也不知道是哪里出现问题,错误得不到正确的定位,就会花费极多的事件去做无用功(这里又要吐槽一次前面的Feign使用Hystrix,定位错误两天,解决一小时)
首先,给Consumer添加了Hystrix.stream
节点。这个节点就是可被后面的Dashboard跟Turbine检测的,而且我们看一下配置节点的代码:
1 |
|
即使不清楚它的源码,但看了也大概知道是啥了,addUrlMappings就是添加节点的URL路径,然后设置了一个BeanName。而且这个Bean也不是瞎创建的,使用了HystrixMetricsStreamServlet来创建,所以很自然地,我们在8010/hystrix.stream里就可以看到一些关于Hystrix监控的信息了,尽管现在还不清楚到底是啥。
然后,Dashboard是一个单独的结点,不需要配置Eureka信息,它也确实无须注册到Eureka,毕竟它是在主页里手动输入url来获取Hystrix信息的(如localhost:8010/hystrix.stream)。PS:启动的时候可能会报错,说无法注册到服务器,不需要管,因为本来就不打算注册到Eureka,不影响。
最后,Turbine是需要注册到Eureka的,因为它不是单独地输入一个URL,然后就检测这一个,它是检测Eureka里所有具备了某个结点的服务(一开始以为默认就是hystrix.stream结点),然后Turbine本身又是一个结点,/turbine.stream,最后只需要把这个结点放到Dashboard里查询,那么就可以在Dashboard里显示所有的Turbine检测到的Hystrix.stream结点。总体逻辑就是这样。
②Dashboard can not connect xxx:
输入了某个hystrix.stream的URL之后,发现无法connect。这时候看一下日志就知道:401,未授权。所以这里也直接很简单粗暴了,URL前面加上xxx:yyy@即可 (这个好像叫OAuth协议,后面再说)。前面最开始为什么没有这个问题?因为当时Dashboard直接就在Consumer服务里,而Consumer服务是有配置这个认证的代码的,所以自然它也自动认证了。
③在Dashboard里输入turbine.stream的时候,一直loading。
一开始直接打开turbine.stream,发现一直在打印:”reportingHostsLast10Seconds”:0 ……
很显然,它没有发现到任何的Host,自然也没有信息。这时候再回看turbine的yml配置:
1 | turbine: |
我当时consumer偷懒,一直没有给它加名字,所以consumer服务在Eureka里是叫UNKNOWN的。于是我把这里改成了UNKNOWN,确实就找到了。然后我给consumer加回这个正常的名字:
1 | spring: |
发现是发现到了,但仍然是loading,这时候日志就看到错误原因了:401
仍然是未授权。这时候应该怎么解决?我前面是直接在Dashboard里的URL添加了认证信息,但这里显然是不行的,为什么?因为Dashboard里的URL直接就是我要监控的那个URL的地址,它具体地调用了某个Provider,然后这个Provider需要认证信息,所以我直接在URL里添加认证信息就可以了。但这里我在Dashboard里输入的URL是turbine.stream的URL,如果我在这个URL里加入了认证信息,那么只是传递给了Turbine。但后续Turbine还要自己去监控各种的服务,显然因为Turbine是连接多个服务的,如果不同的服务认证信息不一致,那么这种在URL前缀加认证信息其实就没必要,所以Turbine也很聪明地直接就不会把这个参数传递到服务(尽管我这里用的是同一个服务实例,如果它传是可以通过认证的,但显然它直接不这么尝试,我认为这个设计是合理的)
最标准的解法应该是,给Turbine增加一个配置类,然后通过认证,但我前面的认证都是针对注册到Eureka 的,而Consumer里的认证是使用Feign的,不可能给Turbine为了认证也加这么一个不需要的东西吧。不清楚如何解决,最后直接在源头:在Provider上配置例外,这样Turbine就不需要认证了:
1 |
|
结果还是不行,一开始说找不到/actuator/hystrix.stream。然后加上了这个ignoring也不行。其实当时就猜到了,默认路径应该是/actuator/hystrix.stream,而不是/hystrix.stream(可能是2.x改的)
所以正确的做法,给yml手动配置Turbine要监控的结点(不知道默认是什么,最好就直接自己配置):
1 | turbine: |
这里的turbine:instanceUrlSuffix: 就是配置了要监控的后缀。果然401问题解决了,就全部都解决了。
效果图:
①Eureka:一共启动了6个项目。其中一个Provider,两个Consumer(通过不同端口启动的),一个Turbine,剩下的就是Eureka,还有没注册到Eureka的Dashboard。
②Turbine.stream可以监控到数据:
③Dashboard的界面:
④给Dashboard输入我们要访问的URL,随便起一个Title(URL可以是单个的hystrix.stream,也可以是监控多个的turbine.stream,这里直接看turbine.stream)
可以看到监控了两个Host,说明成功了。如果是多个不同的实例,会有多个图: