hystrix入门-原生API使用
熟悉spring-cloud的同学都知道,hystrix是用来做服务熔断降级用的,在Springloud中使用hystrix很简单,后面的文章中我们再演示,今天首先来看下原生的hystrix api是如何来使用的。
hystrix入门demo
<!--添加下这个依赖--><dependency><groupId>com.netflix.hystrix</groupId><artifactId>hystrix-core</artifactId><version>1.5.18</version></dependency>
写个demo:
public class CommandHelloWorld extends HystrixCommand<String> {private final String name;public CommandHelloWorld(String name) {super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));this.name = name;}protected String run() {// a real example would do work like a network call herereturn "Hello " + name + "!";}public static void main(String[] args) throws Exception{System.out.println("------------------同步执行------------------");CommandHelloWorld command = new CommandHelloWorld("Joshua");System.out.println(command.execute());System.out.println("------------------异步执行------------------");command = new CommandHelloWorld("Joshua");Future<String> future = command.queue();System.out.println(future.get());System.out.println("------------------响应式 阻塞执行------------------");command = new CommandHelloWorld("Joshua");System.out.println(command.observe().toBlocking().single());System.out.println("------------------响应式 非阻塞执行------------------");command = new CommandHelloWorld("Joshua");command.observe().subscribe(new Observer<String>() {public void onCompleted() {// nothing needed hereSystem.out.println("onCompleted");}public void onError(Throwable e) {e.printStackTrace();}public void onNext(String v) {System.out.println("onNext: " + v);}});}}
(1)首先继承HystrixCommand,当然也可以继承HystrixObservableCommand(暂不考虑)
(2)调用父类构造,传递groupKey
(3)重写run()方法,做业务逻辑
(4)客户端调用execute()
它的背后使用了rxjava里面的东西,如果不熟悉,自行百度之。
如何执行异常的降级fallback?
public class CommandHelloFailure extends HystrixCommand<String> {private final String name;public CommandHelloFailure(String name) {super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));this.name = name;}protected String run() {throw new RuntimeException("this command always fails");}//只需要重写getFallback()这个方法即可protected String getFallback() {return "Hello Failure " + name + "!";}public static void main(String[] args) {System.out.println("------------fallback--------------");CommandHelloFailure command = new CommandHelloFailure("TEST");//这里会输出Hello Failure TEST!,并不会抛出异常System.out.println(command.execute());}}
只需要重写getFallback()这个方法即可。
但是这里有个问题:getFallback()中并不能拿到异常的原因。
如何将异常传播出来?
run()方法中抛出的异常,除了HystrixBadRequestException之外,都会触发fallback和断路器的逻辑。因此,你可以把想要在run()里面抛出的异常包装到HystrixBadRequestException里面,然后就可以用getCause()获取出来了。设计HystrixBadRequestException的目的就是为了在诸如参数校验失败或者是非系统类异常等情况不需要触发fallback逻辑,比如:
public class CommandHelloErrorPropagation extends HystrixCommand<String> {private final String name;public CommandHelloErrorPropagation(String name) {super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));this.name = name;}protected String run() {//注意这里抛出的异常类型throw new HystrixBadRequestException("参数校验异常,用户名不能为空", new BizException());}protected String getFallback() {return "Hello Failure " + name + "!";}public static void main(String[] args) {System.out.println("------------error propagation--------------");CommandHelloErrorPropagation command = new CommandHelloErrorPropagation("TEST");try{System.out.println(command.execute());//异常会直接传播出来}catch(HystrixBadRequestException e){//这里会拿到原始的异常信息if(e.getCause().getClass() == BizException.class){System.out.println(e.getMessage());}}}public static class BizException extends RuntimeException{}}
Command 和 Command分组
command的默认名字是它的类名,也就是getClass().getSimpleName();当然可以在构造函数中手动明确来设置,比如:
public CommandHelloWorld() {super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")).andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")));}
HystrixCommandKey本身是一个接口,因此可以用枚举或者类来实现它,同时它还提供了工厂方法:
HystrixCommandKey.Factory.asKey("HelloWorld")
hystrix使用分组的key来把command进行分组,方便进行汇总报表展示等,默认情况下,hystrix是给分组创建线池,如果没有明确指定的话。HystrixCommandGroupKey跟HystrixCommandKey类似。
Command的线程池
thread-pool key代表了一个单独的超线池,HystrixCommand可以用HystrixThreadPoolKey关联到单独的线程池上,如果没有设置,那就默认使用HystrixCommandGroupKey关联的那个线程池。比如:
public CommandHelloWorld() {super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")).andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")).andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")));}
这样就做到了线程的隔离,不会因为一个command慢而把整个线程池全部占满不释放。
请求缓存
如果请求参数相同,hystrix可以把相应结果缓存起来,下次就不用执行run()而是从缓存中直接取响应,比如:
public class CommandUsingRequestCache extends HystrixCommand<Boolean> {private final int value;protected CommandUsingRequestCache(int value) {super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));this.value = value;}protected Boolean run() {System.out.println("-----------execute run()---------------");return value == 0 || value % 2 == 0;}//1.定义缓存的keyprotected String getCacheKey() {return String.valueOf(value);}public static void main(String[] args) {//2.初始化缓存的contextHystrixRequestContext context = HystrixRequestContext.initializeContext();try {//执行同样的command就会走缓存了CommandUsingRequestCache command2a = new CommandUsingRequestCache(2);CommandUsingRequestCache command2b = new CommandUsingRequestCache(2);//第一次,缓存不会命中System.out.println(command2a.execute());//输出falseSystem.out.println(command2a.isResponseFromCache());//第二次,缓存命中System.out.println(command2b.execute());//输出trueSystem.out.println(command2b.isResponseFromCache());} finally {context.shutdown();}// 开启了一个新的context,缓存被重置context = HystrixRequestContext.initializeContext();try {CommandUsingRequestCache command3b = new CommandUsingRequestCache(2);System.out.println(command3b.execute());//输出falseSystem.out.println(command3b.isResponseFromCache());} finally {context.shutdown();}}}
只需要重写getCacheKey()定义缓存的key,然后调用的时候开启HystrixRequestContext即可,上面的run方法只会执行2次。
请求Collapsing
就是把多个请求放在一个command中批量去执行,批量可以是request级别的,也可以是全局级别的,request级别是一个HystrixRequestContext一批,全局可以跨多个HystrixRequestContext。request级别非常有用,尤其是在并发执行多个仅仅是参数不同的请求的时候。举个例子:
public class CollapsingBatchCommand extends HystrixCollapser<List<String>, String, Integer> {private final Integer key;public CollapsingBatchCommand(Integer key) {this.key = key;}public Integer getRequestArgument() {return key;}protected HystrixCommand<List<String>> createCommand(final Collection<CollapsedRequest<String, Integer>> requests) {return new BatchCommand(requests);}protected void mapResponseToRequests(List<String> batchResponse, Collection<CollapsedRequest<String, Integer>> requests) {int count = 0;for (CollapsedRequest<String, Integer> request : requests) {//响应结果回填回去request.setResponse(batchResponse.get(count++));}}private static final class BatchCommand extends HystrixCommand<List<String>> {private final Collection<CollapsedRequest<String, Integer>> requests;private BatchCommand(Collection<CollapsedRequest<String, Integer>> requests) {super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")).andCommandKey(HystrixCommandKey.Factory.asKey("GetValueForKey")));this.requests = requests;}protected List<String> run() {ArrayList<String> response = new ArrayList<String>();for (CollapsedRequest<String, Integer> request : requests) {//根据不同的请求参数,做不同的响应response.add("ValueForKey: " + request.getArgument());}return response;}}public static void main(String[] args)throws Exception {HystrixRequestContext context = HystrixRequestContext.initializeContext();try {Future<String> f1 = new CollapsingBatchCommand(1).queue();Future<String> f2 = new CollapsingBatchCommand(2).queue();Future<String> f3 = new CollapsingBatchCommand(3).queue();Future<String> f4 = new CollapsingBatchCommand(4).queue();System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());//command的执行次数:1次System.out.println(HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());HystrixCommand<?> command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().toArray(new HystrixCommand<?>[1])[0];//command的名字:GetValueForKeySystem.out.println(command.getCommandKey().name());//trueSystem.out.println(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED));// trueSystem.out.println(command.getExecutionEvents().contains(HystrixEventType.SUCCESS));} finally {context.shutdown();}}}
hystrix其他的应用场景:
(1)访问网络失败,则访问本地缓存。
(2)主备切换,用一个command,里面根据配置来决定走主command还是备command。
参考代码下载:https://github.com/xjs1919/enumdemo下面的hystrix-demo/hystrix。
