RxJava可能有些小伙伴没有听过是什么东西,可能是因为大家平时在做业务需求的时候对异步编程了解得比较少,而RxJava就是这么一个响应式编程框架,RxJava在安卓上面用得非常多,做安卓的朋友肯定对它很熟悉。那我这里为什么要讲这个呢?因为spring cloud中服务治理框架Hystrix中大量用到了RxJava的响应式编程,为了便于理解,这里也简单给大家介绍一下。这里介绍的版本是RxJava 1.X版本的, 而在去年的早些时候,官方便宣布,将在一段时间后不再对 RxJava 1.x 进行维护,推出了RxJava2.X版本,既然有新的,为什么不介绍新的呢?因为目前最新的Hystrix版本1.5.12中使用的RxJava是1.2版本的,而2.X版本的api改动还是比较大的,所以为了大家能更加简单的理解Hystrix,所以这里是对1.X版本的介绍。
响应式编程是一种基于异步数据流概念的编程模式,有点类似于JAVA里面的lambda表达式,相信大家都很熟悉lambda吧。数据流,stream,大家肯定不陌生,我们可以对stream有很多操作,filter、map、reduce 等常见操作。然后响应式中的核心就是响应二字,响应什么呢?响应的是事件,event 。 而流就是一个按照时间进行排序的事件序列。RxJava里面的事件是基于观察者模式,事件流将从上往下,从订阅源传递到观察者。
RxJava 有四个基本概念:Observable
(可观察者,即被观察者)、 Observer
(观察者)、 Subscriber
(订阅,是Observer
的抽象实现类,本质上使用是一样的)、事件。Observable
和 Observer
通过 subscribe()
方法实现订阅关系,从而 Observable
可以在需要的时候发出事件来通知 Observer
。Observable
就像是一个生产者,在不断的生产消息,而Subscriber
和 Observer
就像是一个消费者,在不断的消费消息
另外, RxJava 的事件回调方法还定义了两个特殊的事件,在Hystrix中用得也非常多:onCompleted()
和 onError()
。
onCompleted()
: 事件队列完结。RxJava 不仅把每个事件单独处理,还会把它们看做一个队列。RxJava 规定,当不会再有新的 onNext()
发出时,需要触发 onCompleted()
方法作为标志。onError()
: 事件队列异常。在事件处理过程中出异常时,onError()
会被触发,同时队列自动终止,不允许再有事件发出。说了这么多概念,估计大家都是一头雾水,我们直接来些实际的,加深大家的印象理解。用多的自然而然就会了,就懂了,这里说得可能不是最全的,但是说的都是Hystrix中用得很多的一些操作符,加深大家对Hystrix的理解,看源码就会容易一些。
Observable producer = Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super String> subscriber) {
subscriber.onNext("apple");
subscriber.onNext("orange");
subscriber.onCompleted();
}
});
Subscriber consumer = new Subscriber() {
@Override
public void onNext(String s) {
LOG.info("我收到的水果有 = {}" , s);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
};
producer.subscribe(consumer);
先来一个简单的例子给大家直观的介绍下Observable
和 Subscriber
做了些什么,Observable
使用了onNext方法生产了2个水果,Apple和orange ,然后调用了onCompleted方法结束了这次生产, 消费者用onnext方法收到了2个水果,所以消费者就将收到的水果打印出来了,没有做任何处理
执行结果如下:
2018-04-27 10:21:11.440 INFO [#][#] <main> com.dzy.learn.other.NormalTest :我收到的水果有 = apple
2018-04-27 10:21:11.440 INFO [#][#] <main> com.dzy.learn.other.NormalTest :我收到的水果有 = orange
然后再给大家介绍一下Hystrix中用得非常多的操作符
Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super String> subscriber) {
subscriber.onNext("item1");
subscriber.onNext("item2");
subscriber.onCompleted();
}
});
//在上面的例子中已经跟大家讲过了,create就是创建一个Observable,来生产消息
List fruitList = Arrays.asList("apple","orange");
Observable.from(fruitList).subscribe(new Action1() {
@Override
public void call(String fruit) {
LOG.info("fruit = {}" , fruit);
}
});
上面订阅者的代码被我简化了,直接new 一个Action1, 是subscribe支持的一种订阅方式,跟Subscriber是一样的道理,只是更加简化。然后我们再用lambda表达式简化一下就是这样的了
List fruitList = Arrays.asList("apple","orange");
Observable.from(fruitList).subscribe(fruit -> LOG.info("fruit = {}" , fruit));
执行结果如下:
2018-04-27 10:30:59.030 INFO [#][#] <main> com.dzy.learn.other.NormalTest :fruid = apple
2018-04-27 10:30:59.030 INFO [#][#] <main> com.dzy.learn.other.NormalTest :fruid = orange
只有当订阅者订阅才创建Observable,为每个订阅创建一个新的Observable。内部通过OnSubscribeDefer
在订阅时调用Func0创建Observable
List fruitList = Arrays.asList("apple","orange");
Observable.defer(new Func0>() {
@Override
public Observable call() {
return Observable.from(fruitList);
}
}).subscribe(new Action1() {
@Override
public void call(String fruit) {
LOG.info("defer fruit = {}" , fruit);
}
});
不知道大家理解了没有,每次生产消息都会生产一个新的消息生产者
执行结果如下:
2018-04-27 10:37:26.209 INFO [#][#] <main> com.dzy.learn.other.NormalTest :defer fruit = apple
2018-04-27 10:37:26.209 INFO [#][#] <main> com.dzy.learn.other.NormalTest :defer fruit = orange
在生产的第一个消息前加上一个或者一些消息,看例子比较直观
List fruitList = Arrays.asList("apple","orange");
Observable.from(fruitList)
.startWith("before apple","before apple2")
.subscribe(fruit->LOG.info("fruit = {}" , fruit));
执行结果如下:
2018-04-27 10:40:52.221 INFO [#][#] <main> com.dzy.learn.other.NormalTest :fruit = before apple
2018-04-27 10:40:52.221 INFO [#][#] <main> com.dzy.learn.other.NormalTest :fruit = before apple2
2018-04-27 10:40:52.222 INFO [#][#] <main> com.dzy.learn.other.NormalTest :fruit = apple
2018-04-27 10:40:52.222 INFO [#][#] <main> com.dzy.learn.other.NormalTest :fruit = orange
跟lambda里面的filter很像,也是用来筛选数据的,filter接收的Func1第二个参数是Boolean,定死的
List list = Arrays.asList(10, 5, 3, 2, 1, 0);
Observable.from(list)
.filter(new Func1() {
@Override
public Boolean call(Integer integer) {
return integer>4;
}
})
.subscribe(num->LOG.info("比4大的num = {}" , num));
Map是将需要生产的数据经过Func1进行变换之后,然后在发送给消费者。第二个参数是Object类型的,可以转化成一个Object对象
List list = Arrays.asList(10, 5, 3, 2, 1, 0);
Observable.from(list)
.map(new Func1() {
@Override
public Object call(Integer integer) {
return integer+"变成str";
}
})
.subscribe(s -> LOG.info("s = {}" , s));
输出如下:
2018-04-27 11:08:02.743 INFO [#][#] <main> com.dzy.learn.other.NormalTest :s = 10变成str
2018-04-27 11:08:02.747 INFO [#][#] <main> com.dzy.learn.other.NormalTest :s = 5变成str
2018-04-27 11:08:02.747 INFO [#][#] <main> com.dzy.learn.other.NormalTest :s = 3变成str
2018-04-27 11:08:02.748 INFO [#][#] <main> com.dzy.learn.other.NormalTest :s = 2变成str
2018-04-27 11:08:02.748 INFO [#][#] <main> com.dzy.learn.other.NormalTest :s = 1变成str
2018-04-27 11:08:02.748 INFO [#][#] <main> com.dzy.learn.other.NormalTest :s = 0变成str
跟map有些类似,但是也有很大的区别,map是一个对象变成另外一个对象,而flatMap可以把一个对象转化为多个对象 , 其实FlatMap是将一个对象转成了一个Observable
对象,转完之后,最开始的生产者并不发送这个 Observable
, 而是将它激活,于是它开始发送事件,每一个创建出来的 Observable
发送的事件,都被汇入同一个 Observable
,而这个 Observable
负责将这些事件统一交给 Subscriber
的回调方法,这样也就产生了一对多的概念,就像是把对象铺平了一样,flat
我会跟lambda的flatMap做一个对比,还是很像的
List list = Arrays.asList(10, 5, 3, 2, 1, 0);
List
打印的结果是一开始的每个元素10 ,5等都被加上了10 100 1000 然后输出了,相当于平铺了
2018-04-27 11:50:21.186 INFO [#][#] <main> com.dzy.learn.other.NormalTest :complex integer = 10 // 10本来数字
2018-04-27 11:50:21.191 INFO [#][#] <main> com.dzy.learn.other.NormalTest :complex integer = 20 //加上10之后
2018-04-27 11:50:21.191 INFO [#][#] <main> com.dzy.learn.other.NormalTest :complex integer = 110 //加上100之后
2018-04-27 11:50:21.191 INFO [#][#] <main> com.dzy.learn.other.NormalTest :complex integer = 1010 //加上1000之后
2018-04-27 11:50:21.191 INFO [#][#] <main> com.dzy.learn.other.NormalTest :complex integer = 5
2018-04-27 11:50:21.191 INFO [#][#] <main> com.dzy.learn.other.NormalTest :complex integer = 15
2018-04-27 11:50:21.191 INFO [#][#] <main> com.dzy.learn.other.NormalTest :complex integer = 105
2018-04-27 11:50:21.191 INFO [#][#] <main> com.dzy.learn.other.NormalTest :complex integer = 1005
//限于篇幅,后面的略,都是 源数 加上10 100 1000后输出的
RxJava里面的flatMap, 我乘以10,乘以100然后加下|
转成一个String
List list = Arrays.asList(10, 5, 3, 2, 1, 0);
Observable.from(list).flatMap(new Func1>() {
@Override
public Observable> call(Integer num) {
List strings = Arrays.asList("|" + num + "|", "|" + num * 10 + "|", "|" + num * 100 + "|");
return Observable.from(strings);
}
}).subscribe(new Action1
输出如下:
2018-04-27 11:57:13.316 INFO [#][#] <main> com.dzy.learn.other.NormalTest :新转化后的字符串是 = |10|
2018-04-27 11:57:13.320 INFO [#][#] <main> com.dzy.learn.other.NormalTest :新转化后的字符串是 = |100|
2018-04-27 11:57:13.320 INFO [#][#] <main> com.dzy.learn.other.NormalTest :新转化后的字符串是 = |1000|
2018-04-27 11:57:13.320 INFO [#][#] <main> com.dzy.learn.other.NormalTest :新转化后的字符串是 = |5|
2018-04-27 11:57:13.320 INFO [#][#] <main> com.dzy.learn.other.NormalTest :新转化后的字符串是 = |50|
2018-04-27 11:57:13.320 INFO [#][#] <main> com.dzy.learn.other.NormalTest :新转化后的字符串是 = |500|
2018-04-27 11:57:13.320 INFO [#][#] <main> com.dzy.learn.other.NormalTest :新转化后的字符串是 = |3|
2018-04-27 11:57:13.321 INFO [#][#] <main> com.dzy.learn.other.NormalTest :新转化后的字符串是 = |30|
2018-04-27 11:57:13.321 INFO [#][#] <main> com.dzy.learn.other.NormalTest :新转化后的字符串是 = |300|
reduce是一个聚合函数,接收上次运算的结果,放在下次的参数中,然后输出最后的结果,结果只输出一次。有点类似于递归。跟scan操作符很像,但是有区别,大家看下scan的输出就知道是什么区别了。
RxJava里面的reduce
Observable.from(list).reduce(new Func2() {
@Override
public Integer call(Integer result, Integer num) {
LOG.info("开始前: result {}, num = {}" , result,num);
result+=num;
return result;
}
}).subscribe(new Action1() {
@Override
public void call(Integer result) {
LOG.info("result = {}" , result);
}
});
打印结果如下:
2018-04-27 13:47:29.237 INFO [#][#] <main> com.dzy.learn.other.NormalTest :开始前: result 10, num = 5
2018-04-27 13:47:29.241 INFO [#][#] <main> com.dzy.learn.other.NormalTest :开始前: result 15, num = 3
2018-04-27 13:47:29.241 INFO [#][#] <main> com.dzy.learn.other.NormalTest :开始前: result 18, num = 2
2018-04-27 13:47:29.241 INFO [#][#] <main> com.dzy.learn.other.NormalTest :开始前: result 20, num = 1
2018-04-27 13:47:29.241 INFO [#][#] <main> com.dzy.learn.other.NormalTest :开始前: result 21, num = 0
2018-04-27 13:47:29.241 INFO [#][#] <main> com.dzy.learn.other.NormalTest :result = 21
lambda里面的reduce
List list = Arrays.asList(10, 5, 3, 2, 1, 0);
Integer sum = list.stream().reduce((result, sum1) -> {
result += sum1;
return result;
}).get();
LOG.info("sum = {}" , sum);
//sum是21,就是累加起来
scan和reduce都是把上一次操作的结果做为第二次的参数传递给第二次Observable使用,但是scan每次操作之后先把数据输出,然后在调用scan的回调函数进行第二次操作,看例子
Observable.from(list)
.scan(new Func2() {
@Override
public Integer call(Integer result, Integer num) {
LOG.info("开始前: result {}, num = {}" , result,num);
result+=num;
return result;
}
}).subscribe(new Action1() {
@Override
public void call(Integer result) {
LOG.info("result = {}" , result);
}
});
2018-04-27 13:47:29.245 INFO [#][#] <main> com.dzy.learn.other.NormalTest :result = 10
2018-04-27 13:47:29.245 INFO [#][#] <main> com.dzy.learn.other.NormalTest :开始前: result 10, num = 5
2018-04-27 13:47:29.245 INFO [#][#] <main> com.dzy.learn.other.NormalTest :result = 15
2018-04-27 13:47:29.245 INFO [#][#] <main> com.dzy.learn.other.NormalTest :开始前: result 15, num = 3
2018-04-27 13:47:29.245 INFO [#][#] <main> com.dzy.learn.other.NormalTest :result = 18
2018-04-27 13:47:29.245 INFO [#][#] <main> com.dzy.learn.other.NormalTest :开始前: result 18, num = 2
2018-04-27 13:47:29.245 INFO [#][#] <main> com.dzy.learn.other.NormalTest :result = 20
2018-04-27 13:47:29.245 INFO [#][#] <main> com.dzy.learn.other.NormalTest :开始前: result 20, num = 1
2018-04-27 13:47:29.245 INFO [#][#] <main> com.dzy.learn.other.NormalTest :result = 21
2018-04-27 13:47:29.245 INFO [#][#] <main> com.dzy.learn.other.NormalTest :开始前: result 21, num = 0
2018-04-27 13:47:29.245 INFO [#][#] <main> com.dzy.learn.other.NormalTest :result = 21
每次运算后都会调用订阅者
Hystrix 滑动窗口的核心用的就是window操作符,那么window有什么作用呢?他能将Observable的数据分拆成一些Observable窗口,然后把Observable窗口推送给订阅者,而不是一个数据,是一个Observable。来点例子更加直白
List list = Arrays.asList(10, 5, 3, 2, 1, 0);
Observable.from(list).window(2, 2).subscribe(new Action1>() {
@Override
public void call(Observable integerObservable) {
integerObservable.reduce((sum, num) -> sum+=num).subscribe(new Action1() {
@Override
public void call(Integer integer) {
LOG.info("我被2个打印一次 = {}" , integer);
}
});
}
});
window里面有2个参数,第一个参数2 表示 选取2个事件,比如说10,5 5,3 等,第二个参数是skip,表示跳跃2个事件,来选取,所以是3组窗口,里面是 (10,5)(3,2)(1,0)
输出:
2018-04-27 14:06:08.703 INFO [#][#] <main> com.dzy.learn.other.NormalTest :我被2个打印一次 = 15
2018-04-27 14:06:08.707 INFO [#][#] <main> com.dzy.learn.other.NormalTest :我被2个打印一次 = 5
2018-04-27 14:06:08.707 INFO [#][#] <main> com.dzy.learn.other.NormalTest :我被2个打印一次 = 1
CountDownLatch countDownLatch = new CountDownLatch(1);
Observable inputEventStream = Observable.create(new Observable.OnSubscribe
输出:
2018-04-27 14:26:18.721 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :我会18就被唤醒触发...
2018-04-27 14:26:19.722 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :我会19就被唤醒触发...
2018-04-27 14:26:20.721 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :我会20就被唤醒触发...
2018-04-27 14:26:21.721 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :我会21就被唤醒触发...
2018-04-27 14:26:22.722 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :我会22就被唤醒触发...
2018-04-27 14:26:23.721 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :我会23就被唤醒触发...
2018-04-27 14:26:24.721 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :我会24就被唤醒触发...
2018-04-27 14:26:25.721 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :我会25就被唤醒触发...
.....
...
..
这里用CountDownLatch 阻塞了主线程的关闭,如果不用锁,那么主线程关闭了之后,你就看不到定时输出了。
第二个demo,用到了window另外的一个函数,第一个参数是缓存在这个window的间隔时间,第二个参数是时间单位 , 1s内收到的所有的生产消息都会缓存到window里面,然后统一发出给订阅者。就好像一个时间轴上面,有个窗子在收集数据,1s钟之后收集好了之后,就发送出去,然后到了第二个窗子,这就是Hystrix滑动窗口的精髓所在。
/**
* 两个数字相加,reduce,scan用
*/
public static final Func2 PUBLIC_SUM =
(integer, integer2) -> integer + integer2;
public static final Func1, Observable> WINDOW_SUM =
//跳过第一个数据,因为给了scan一个默认值0,这个值需要跳过,如果不设置就不需要跳过
window -> window.scan(0, PUBLIC_SUM).skip(1);
public static final Func1, Observable> INNER_BUCKET_SUM =
integerObservable -> integerObservable.reduce(0, PUBLIC_SUM);
@Test
public void testWindowSlide() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
BehaviorSubject behaviorSubject = BehaviorSubject.create();
behaviorSubject
// 1秒作为一个基本块,横向移动
.window(1000, TimeUnit.MILLISECONDS)
//将flatMap汇总平铺成一个事件,然后累加成一个Observable对象,比如说1s内有10个对象,被累加起来
.flatMap(INNER_BUCKET_SUM)
//对这个对象2个发送,步长为1
.window(2,1)
//对窗口里面的进行求和,用的scan, 每次累加都会打印出来
.flatMap(WINDOW_SUM)
.subscribe((Integer integer) ->
// 输出统计数据到日志
LOG.info("[{}] call ...... {}",
Thread.currentThread().getName(), integer));
for (int i = 0; i < 1000; i++) {
//200ms生产一个数据,
behaviorSubject.onNext(i);
LOG.info("i = {}" ,i);
Thread.sleep(200);
}
countDownLatch.await();
}
输出:
2018-04-27 15:46:06.547 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 0
2018-04-27 15:46:06.756 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 1
2018-04-27 15:46:07.010 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 2
2018-04-27 15:46:07.211 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 3
2018-04-27 15:46:07.411 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 4
2018-04-27 15:46:07.517 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :[RxComputationScheduler-1] call ...... 10
2018-04-27 15:46:07.517 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :[RxComputationScheduler-1] call ...... 10
2018-04-27 15:46:07.611 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 5
2018-04-27 15:46:07.811 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 6
2018-04-27 15:46:08.011 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 7
2018-04-27 15:46:08.211 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 8
2018-04-27 15:46:08.411 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 9
2018-04-27 15:46:08.517 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :[RxComputationScheduler-1] call ...... 45
2018-04-27 15:46:08.517 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :[RxComputationScheduler-1] call ...... 35
2018-04-27 15:46:08.611 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 10
2018-04-27 15:46:08.811 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 11
2018-04-27 15:46:09.011 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 12
2018-04-27 15:46:09.211 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 13
2018-04-27 15:46:09.411 INFO [#][#] <main> com.dzy.learn.other.NormalTest :i = 14
2018-04-27 15:46:09.517 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :[RxComputationScheduler-1] call ...... 95
2018-04-27 15:46:09.517 INFO [#][#] <RxComputationScheduler-1> com.dzy.learn.other.NormalTest :[RxComputationScheduler-1] call ...... 60
解析,第一个window产生了一个滑动窗口,每秒钟就会把生产者生产的消息累加起来,第二个window是积累2个对象,然后进行发送,每次跳一个数字,第二个window是建立在第一个windows累加之后的基础上的,可能有点难理解,我们来看第一个window产生的序列如下:
0 10 35 60 85 ......
有的同学可能会问,你怎么知道,我看的log日志,打印出来的序列是 10 10 、 45 35 、95 60 、 145 85 、因为这里用的scan,每次累加之后都会把源数打印一遍,所以是0 10 35 60 85 。第二个window就在这个基础上进行累加 0+10 10+35 35+60 60+85,这样就完成了一个滑动窗口的监控过程
这里总结的也许不是最全的,也许不是最新的版本,但是是Hystrix中用到的,结合Hystrix进行针对性讲解,对Hystrix的理解更加深刻,如有错误,望加以斧正,谢谢。