【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
前言
Rxjava
,由于其基于事件流的链式调用、逻辑简洁 & 使用简单的特点,深受各大Android
开发者的欢迎。
- 本文主要讲解的是
RxJava
中的 背压控制策略,希望你们会喜欢。
目录
1. 引言
1.1 背景
- 观察者 & 被观察者 之间存在2种订阅关系:同步 & 异步。具体如下:
- 对于异步订阅关系,存在 被观察者发送事件速度 与观察者接收事件速度 不匹配的情况
1.2 问题
- 被观察者 发送事件速度太快,而观察者 来不及接收所有事件,从而导致观察者无法及时响应 / 处理所有发送过来事件的问题,最终导致缓存区溢出、事件丢失 & OOM
下面再举个例子:
- 被观察者的发送事件速度 = 10ms / 个
- 观察者的接收事件速度 = 5s / 个
即出现发送 & 接收事件严重不匹配的问题
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 结果
由于被观察者发送事件速度 > 观察者接收事件速度,所以出现流速不匹配问题,从而导致OOM
1.3 解决方案
采用 背压策略。
下面,我将开始介绍背压策略。
2. 背压策略简介
2.1 定义
一种 控制事件流速 的策略
2.2 作用
在 异步订阅关系 中,控制事件发送 & 接收的速度
2.3 解决的问题
解决了 因被观察者发送事件速度 与 观察者接收事件速度 不匹配(一般是前者 快于 后者),从而导致观察者无法及时响应 / 处理所有 被观察者发送事件 的问题
2.4 应用场景
- 被观察者发送事件速度 与 观察者接收事件速度 不匹配的场景
- 具体场景就取决于 该事件的类型,如:网络请求,那么具体场景:有很多网络请求需要执行,但执行者的执行速度没那么快,此时就需要使用背压策略来进行控制。
3. 背压策略的原理
- 那么,RxJava实现背压策略(
Backpressure
)的原理是什么呢? - 解决方案 & 思想主要如下:
- 示意图如下
- 与
RxJava1.0
中被观察者的旧实现Observable
对比
- 好了,那么上图中在
RxJava 2.0
观察者模型中,Flowable
到底是什么呢?它其实是RxJava 2.0
中被观察者的一种新实现,同时也是背压策略实现的承载者 - 请继续看下一节的介绍:背压策略的具体实现 -
Flowable
4. 背压策略的具体实现:Flowable
在 RxJava2.0
中,采用 Flowable
实现 背压策略
4.1 Flowable 介绍
- 定义:在
RxJava2.0
中,被观察者(Observable
)的一种新实现 - 作用:实现 非阻塞式背压 策略
4.2 Flowable 特点
Flowable
的特点 具体如下
- 下面再贴出一张
RxJava2.0
与RxJava1.0
的观察者模型的对比图
4.3 与 RxJava1.0 中被观察者的旧实现 Observable 的关系
- 具体如下图
- 那么,为什么要采用新实现
Flowable
实现背压,而不采用旧的Observable
呢? - 主要原因:旧实现
Observable
无法很好解决背压问题。
4.4 Flowable的基础使用
Flowable
的基础使用非常类似于Observable
- 具体如下
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 更加优雅的链式调用
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 至此,
Flowable
的基础使用讲解完 - 关于更深层次的使用会结合 背压策略的实现 来讲解
5. 背压策略的使用
- 在本节中,我将结合 背压策略的原理 & Flowable的使用,为大家介绍在RxJava 2.0 中该如何使用Flowable来实现背压策略功能,即背压策略的使用
Flowable
与Observable
在功能上的区别主要是 多了背压的功能- 下面,我将顺着第3节中讲解背压策略实现原理 & 解决方案(如下图),来讲解
Flowable
在背压策略功能上的使用
5.1 控制 观察者接收事件 的速度
5.1.1 异步订阅情况
- 简介
- 具体原理图
- 具体使用
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 效果图
- 有2个结论是需要大家注意的
下图 = 当缓存区存满时(128个事件)溢出报错的原理图
- 代码演示1:观察者不接收事件的情况下,被观察者继续发送事件 & 存放到缓存区;再按需取出
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 代码演示2:观察者不接收事件的情况下,被观察者继续发送事件至超出缓存区大小(128)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
5.1.2 同步订阅情况
同步订阅 & 异步订阅 的区别在于:
- 同步订阅中,被观察者 & 观察者工作于同1线程
- 同步订阅关系中没有缓存区
- 被观察者在发送1个事件后,必须等待观察者接收后,才能继续发下1个事件
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
所以,实际上并不会出现被观察者发送事件速度 > 观察者接收事件速度的情况。可是,却会出现被观察者发送事件数量 > 观察者接收事件数量的问题。
- 如:观察者只能接受3个事件,但被观察者却发送了4个事件,所以出现了不匹配情况
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
所以,对于没有缓存区概念的同步订阅关系来说,单纯采用控制观察者的接收事件数量(响应式拉取)实际上就等于 “单相思”,虽然观察者控制了要接收3个事件,但假设被观察者需要发送4个事件,还是会出现问题。
- 有1个特殊情况需要注意
- 代码演示
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
在被观察者发送第1个事件后, 就抛出MissingBackpressureException
异常 & 观察者没有收到任何事件
5.2 控制 被观察者发送事件 的速度
- 简介
FlowableEmitter
类的requested()
介绍
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
-
每个线程中的
requested()
的返回值 = 该线程中的request(a)
的a值 -
对应于同步 & 异步订阅情况 的原理图
为了方便大家理解该策略中的requested()
使用,该节会先讲解同步订阅情况,再讲解异步订阅情况
5.2.1 同步订阅情况
- 原理说明
即在同步订阅情况中,被观察者 通过 FlowableEmitter.requested()
获得了观察者自身接收事件能力,从而根据该信息控制事件发送速度,从而达到了观察者反向控制被观察者的效果
- 具体使用
下面的例子 = 被观察者根据观察者自身接收事件能力(10个事件),从而仅发送10个事件
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 特别注意
在同步订阅情况中使用FlowableEmitter.requested()
时,有以下几种使用特性需要注意的:
情况1:可叠加性
- 即:观察者可连续要求接收事件,被观察者会进行叠加并一起发送
- 1
- 2
- 3
- 4
- 代码演示
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
情况2:实时更新性
- 即,每次发送事件后,emitter.requested()会实时更新观察者能接受的事件
- 1
- 2
- 3
- 4
- 5
- 6
- 代码演示
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
情况3:异常
- 当
FlowableEmitter.requested()
减到0时,则代表观察者已经不可接收事件 - 此时被观察者若继续发送事件,则会抛出
MissingBackpressureException
异常
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
![示意图](http://upload-images.jianshu.io/upload_images/944365-acbf17d1cf200c0c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
额外
- 若观察者没有设置可接收事件数量,即无调用
Subscription.request()
- 那么被观察者默认观察者可接收事件数量 = 0,即
FlowableEmitter.requested()
的返回值 = 0
5.2.2 异步订阅情况
- 原理说明
从上面可以看出,由于二者处于不同线程,所以被观察者 无法通过 FlowableEmitter.requested()
知道观察者自身接收事件能力,即 被观察者不能根据 观察者自身接收事件的能力 控制发送事件的速度。具体请看下面例子
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
而在异步订阅关系中,反向控制的原理是:通过RxJava
内部固定调用被观察者线程中的request(n)
从而 反向控制被观察者的发送事件速度
那么该什么时候调用被观察者线程中的request(n)
& n
的值该是多少呢?请继续往下看。
- 具体使用
关于RxJava
内部调用request(n)(n = 128、96、0)
的逻辑如下:
- 代码演示
下面我将用一个例子来演示该原理的逻辑
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
整个流程 & 测试结果 请看下图
5.3 采用背压策略模式:BackpressureStrategy
5.3.1 背压模式介绍
在Flowable的使用中,会被要求传入背压模式参数
- 面向对象:针对缓存区
- 作用:当缓存区大小存满、被观察者仍然继续发送下1个事件时,该如何处理的策略方式
5.3.2 背压模式类型
![示意图](http://upload-images.jianshu.io/upload_images/944365-47b55edec299faea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 下面我将对每种模式逐一说明。 **模式1:BackpressureStrategy.ERROR** - 问题:发送事件速度 > 接收事件 速度,即流速不匹配
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
![示意图](http://upload-images.jianshu.io/upload_images/944365-0c56eb0868106c41.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) **模式2:BackpressureStrategy.MISSING** - 问题:发送事件速度 > 接收事件 速度,即流速不匹配
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
![示意图](http://upload-images.jianshu.io/upload_images/944365-8f27f7fe6258bea6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) **模式3:BackpressureStrategy.BUFFER**
- 问题:发送事件速度 > 接收事件 速度,即流速不匹配
- 处理方式:将缓存区大小设置成无限大
1. 即 被观察者可无限发送事件 观察者,但实际上是存放在缓存区
2. 但要注意内存情况,防止出现OOM
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
可以接收超过原先缓存区大小(128)的事件数量了 ![示意图](http://upload-images.jianshu.io/upload_images/944365-f1fffce5c7925567.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) **模式4: BackpressureStrategy.DROP**
- 问题:发送事件速度 > 接收事件 速度,即流速不匹配
- 处理方式:超过缓存区大小(128)的事件丢弃
如发送了150个事件,仅保存第1 - 第128个事件,第129 -第150事件将被丢弃
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
被观察者一下子发送了150个事件,点击按钮接收时观察者接收了128个事件;再次点击接收时却无法接受事件,这说明超过缓存区大小的事件被丢弃了。 ![示意图](http://upload-images.jianshu.io/upload_images/944365-6b601cfdcaa0eb29.gif?imageMogr2/auto-orient/strip) **模式5:BackpressureStrategy.LATEST**
- 问题:发送事件速度 > 接收事件 速度,即流速不匹配
- 处理方式:只保存最新(最后)事件,超过缓存区大小(128)的事件丢弃
即如果发送了150个事件,缓存区里会保存129个事件(第1-第128 + 第150事件)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 被观察者一下子发送了150个事件,点击按钮接收时观察者接收了128个事件;
- 再次点击接收时却接收到1个事件(第150个事件),这说明超过缓存区大小的事件仅保留最后的事件(第150个事件)
5.3.3 特别注意
在使用背压策略模式的时候,有1种情况是需要注意的:
a. 背景 FLowable
可通过自己创建(如上面例子),或通过其他方式自动创建,如interval操作符
b. 冲突
- 对于自身手动创建FLowable
的情况,可通过传入背压模式参数选择背压策略
(即上面描述的)
- 可是对于自动创建
FLowable
,却无法手动传入传入背压模式参数,那么出现流速不匹配的情况下,该如何选择 背压模式呢?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
c. 解决方案 RxJava 2.0
内部提供 封装了背压策略模式的方法
- onBackpressureBuffer()
- onBackpressureDrop()
- onBackpressureLatest()
具体使用如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
从而很好地解决了发送事件 & 接收事件 速度不匹配的问题。
其余方法的作用类似于上面的说背压模式参数,此处不作过多描述。
背压策略模式小结
- 至此,对
RxJava 2.0
的背压模式终于讲解完毕 - 所有代码Demo均存放在Carson_Ho的Github地址
6. 总结
-
本文主要对
Rxjava
的背压模式知识进行讲解 -
接下来的时间,我将持续推出
Android
中Rxjava 2.0
的一系列文章,包括原理、操作符、应用场景、背压等等 ,有兴趣可以继续关注Carson_Ho的安卓开发笔记!!
来源:oschina
链接:https://my.oschina.net/u/1177694/blog/1935035