Hystrix服务降级和分组
异常处理
当我们在调用服务提供者时有可能会抛异常(注意HystrixBadRequestException异常是不会触发服务降级,原因会在后面进行介绍),默认情况下方法抛了异常会自动触发服务降级,并交给服务降级中的方法去处理。同样由于Hystrix命令存在两种实现方法来调用服务,因此异常处理也需要分为两种情况。
继承类方式
如果开发者使用继承类的方式来实现Hystrix命令,那么我们可以在getFallback()
方法内通过Throwable executionException = getExecutionException();
方法来获取具体的异常信息,然后通过判断以进入不同的处理逻辑。
修改MovieCommand类中的getFallback方法的代码为如下所示:
1 | @Override |
为了更加清楚认识到这一点,笔者修改了MovieCommand类中run方法的逻辑,使其执行过程中抛出异常:
1 | @Override |
然后需要启动以下信息,,注意启动的顺序不能颠倒:
(1)服务名为eureka-server的工程,这是服务注册中心,注意端口为1111,即启动实例名为peer1的服务实例。
(2)服务名为hello-service的工程,这是服务提供者,注意需要启动两个实例,启动的端口分别为8081和8082,即启动实例名为p1和p2的服务实例(注意两个服务提供者均需往服务注册中心注册)。
(3)服务名为ribbon-consumer的工程,这是服务消费者,注意需要启动一个实例,启动的端口为9000,即启动实例名为ribbon-consumer的服务实例。
接着去浏览器地址栏中访问http://localhost:9000/testOne
链接,然后页面显示如下信息:
顺便可以在ribbon-consumer项目的控制台中可以看到有输出/ by zero
等字样,这就说明由于出现异常,自动触发了服务降级。
注解方式
除了前面提到的继承类方式,还可以使用注解方式。注解方式的实现非常简单,只需要在fallback实现方法的参数中增加Throwable e
对象的定义,这样在方法内部就可以获取触发服务降级的具体异常内容。
往MovieService类中新增testFall和testFallError方法,其中的代码如下所示:
1 | //注解方式实现异常 |
既然是新增了两个方法,那么就需要在ConsumerController类中新增一个方法用于调用testFall方法,这里定义一个同名的testFall方法,相应的代码为:
1 | //采用注解实现的异常处理 |
然后需要启动以下信息,,注意启动的顺序不能颠倒:
(1)服务名为eureka-server的工程,这是服务注册中心,注意端口为1111,即启动实例名为peer1的服务实例。
(2)服务名为hello-service的工程,这是服务提供者,注意需要启动两个实例,启动的端口分别为8081和8082,即启动实例名为p1和p2的服务实例(注意两个服务提供者均需往服务注册中心注册)。
(3)服务名为ribbon-consumer的工程,这是服务消费者,注意需要启动一个实例,启动的端口为9000,即启动实例名为ribbon-consumer的服务实例。
接着去浏览器地址栏中访问http://localhost:9000/testFall
链接,然后页面显示如下信息:
顺便可以在ribbon-consumer项目的控制台中可以看到有输出/ by zero等字样,这就说明由于出现异常,自动触发了服务降级。
其实看到这里,大家也就明白了继承类和注解方式的区别,两种是存在非常相似的逻辑,只是逻辑执行方式不同。
在继承类方式中,run方法用来调用服务并抛出异常,而getFallback方法则用来输出异常和执行服务降级逻辑。
在注解方式中,添加有@HystrixCommand
注解的方法(此处是testFall方法)用来调用服务并抛出异常,而fallbackMethod属性指定的方法(此处为testFallError方法)则用来输出异常和执行服务降级逻辑。
异常忽略
在实际开发过程中可能存在这样一种需求,就是假设某个异常被抛出后,我不希望该异常进入到服务降级方法中处理,而是直接将异常抛给用户。在前面我们抛出算数除法为0的异常,它们都进入了服务降级方法,并打印输出了详细的异常,如/ by zero
。这里以注解方式为例进行说,开发者只需要在@HystrixCommand
注解上添加ignoreExceptions属性,注意它的值是异常对象的字节码类型:
1 | //注解方式实现异常 |
然后按照前面的启动顺序启动各个服务后,访问http://localhost:9000/testFall
链接,可以看到页面信息为:
确实实现了我们所要的目的,那么现在问题来了为什么这个异常不会进入到服务降级的逻辑中呢?那是因为在Hystrix中存在一个名为HystrixBadRequestException的异常,它是直接继承至RuntimeException异常,注意该异常不会进入到服务降级方法中。当我们在@HystrixCommand
注解内配置了ignoreExceptions属性,且值为ArithmeticException.class
时,如果抛出了ArithmeticException异常,那么Hystrix会将异常信息封装到HystrixBadRequestException中,然后将该异常抛出,由于这个异常不会进入到服务降级的逻辑中,因此就不会触发服务降级方法。
命令名称、分组以及线程池划分
不知道你是否发现这么一个问题,就是在使用继承方式实现Hystrix命令的时候,我们都在controller的方法中都有定义下面这行代码:
1 | MovieCommand movieCommand = new MovieCommand(HystrixCommand.Setter.withGroupKey( |
我们在MovieCommand类中定义了一个有参的构造方法,其中第一个参数Sette用于保存一些分组信息,第二个参数RestTemplate则用于调用相关服务:
1 | protected MovieCommand(Setter setter,RestTemplate restTemplate) { |
也就说其实下面这行代码就表示分组信息:
1 | HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")) |
但是具体什么含义前面没有介绍,因此这里来对其进行学习。
继承类的方式实现
如果开发者选择采用继承类的方式来实现Hystrix命令,那么它将默认使用类名作为默认的命令名称,但是我们可以在构造方法中通过Setetr静态类来设置。通过查看源码可以知道,当一个使用继承类的方式实现Hystrix命令,那么这个实现类其实就存在5个重载方法:
打开MovieCommand类,在里面新增包含HystrixCommandGroupKey参数的构造方法,如下所示:
1 | protected MovieCommand() { |
从上面可以看出,我们并没有直接设置命令名称,而是先调用了Setter.withGroupKey()
方法来设置命令组名,然后通过调用andCommandKey()
方法来设置命令名称。查看一下Setter类的源码,可以知道它其实是一个内部类:
1 | public abstract class HystrixCommand<R> extends AbstractCommand<R> implements HystrixExecutable<R>, HystrixInvokableInfo<R>, HystrixObservable<R> { |
且只有withGroupKey方法才能返回一个Setter对象,所以GroupKey是每个Setter必需的参数,而CommandKey则是可选参数,这一点可以从前面所说的5个重载方法中得到验证。
通过设置命令组,Hystrix会根据组来组织和统计命令的告警、仪表盘等信息。除此之外,设置命令组不仅能实现统计,且Hystrix命令默认的线程划分也是根据命令分组来实现的。默认情况下,Hystrix会让相同组名的命令使用同一个线程池,所以需要我们在创建Hystrix命令时为其指定命令组名来实现默认的线程池划分。
如果Hystrix的线程池分配仅仅依靠命令组来划分,那么它就显得不够灵活,因此Hystrix还提供了HystrixThreadPoolKey来对线程池进行设置,通过它我们可以实现更细粒度的线程池划分。
打开MovieCommand类,在里面新增包含HystrixThreadPoolKey参数的构造方法,如下所示:
1 | protected MovieCommand() { |
同样在没有指定HystrixThreadPoolKey的情况下,依然会使用命令组的方式来划分线程池。通常情况下,尽量通过HystrixThreadPoolKey的方式来指定线程池的划分,而不是通过组名的默认方式实现划分,因为多个不同的命令可能从业务逻辑上来看属于同一个组,但是往往从实现本身上需要跟其他命令进行隔离。
###注解的方式实现
在前面介绍了如何为通过继承实现的HystrixCommand设置命令名称、分组以及线程池划分,现在介绍另一种基于注解方式实现的Hystrix命令的设置方式。
其实使用注解实现Hystrix命令时,想设置命令名称、分组以及线程池划分非常简单,只需要在@HystrixCommand
注解上设置相应的属性即可,如表示命令名称的属性commandKey,分组的属性groupKey,线程池划分的属性threadPoolKey,这些其实就是@HystrixCommand
注解的属性,查看该注解的源码:
1 | public @interface HystrixCommand { |
因此这里面还有笔者没有介绍到的其他属性,开发者可以根据需要来自行选择使用。
接下来举一个例子来演示如何基于注解来实现命令名称、分组以及线程池划分的设置,如下所示:
1 | //注解方式实现命令名称、分组以及线程池划分 |
这样关于SpringCloud Hystrix的自定义命令、服务降级、异常处理、命令名称、分组和线程池划分的介绍就到此结束,后续开始学习请求缓存、请求合并等内容。