当上下游的流操作处于不同的线程时,如果上游弹射数据的速度快于下游接收处理数据的速度,对于那些没来得及处理的数据就会造成积压,这些数据既不会丢失,又不会被垃圾回收机制回收,而是存放在一个异步缓存池中,如果缓存池中的数据一直得不到处理,越积越多,最后就会造成内存溢出,这便是响应式编程中的背压问题。
在请求访问、消息处理中,通常有两种模式,一种是Pull拉模式,一种是Push推模式。两者各有优缺点,为此响应式编程Reactive Streams提出了结合两者各自优点的Pull-Push模式,即所谓的背压(Back-Pressure)处理。
背压,有时也叫反压、回压,这个词对于大多数人都很陌生,笔者也不例外。原因是这个词主要是来自对英文Back-Pressure的翻译,该词在维基百科的解释:
Back pressure (or backpressure) is a resistance or force opposing the desired flow of fluid through pipes, leading to friction loss and pressure drop. The term back pressure is a misnomer, as pressure is a scalar quantity, so it has a magnitude but no direction. The fluid is what is directed, tending to flow away from high-pressure regions and toward low-pressure regions. If the low-pressure space is more high-pressure than intended (e.g. due to obstructions or tight bends in an exhaust pipe) or the high-pressure space is more low-pressure than intended, this opposes the desired flow and reduces the discharge. Similarly, bending or other operations on a pipe (such as a stock car exhaust system with a particularly high number of twists and bends[1]) can reduce flow rate.[2]
以水管与水龙头举例,上游开始放水,当水龙头的出水速度小于上游放水速度时,水管就会慢慢被水充满,直至在上游源头上外溢,这种反向流动其实就是背压。通过这种反向压力,上游就能知道下游的处理速度以及是否产生了阻塞,从而进一步从源头上进行流量控制,所以背压是实现流控的一种方式。
那为什么back-pressure要翻译成背压呢?因为back有后背的意思,可以较为形象的理解就是,当人群通过一个入口时,大家不断地推动前面人的后背往前走,当推不动时,就会从后背传来压力,从而告知后面的人不要再往前推了。
如上图,系统中存在三方:生产者(Producer)产生数据,通过管道(Pipeline)传输给消费者(Consumer)。

Producer Consumer 此时生产的速率(100/s)大于消费的速率(75/s),多余的流量无处可去。于是自然地衍生出三种策略:
控制(Control)。降低生产速率,从源头减少流量缓冲(Buffer)。管道将多余的流量存储起来丢弃(Drop)。消费者将无暇处理的流量丢弃
缓冲不应该是无限的(unbounded)。一方面如果生产者的速率长期大于消费者的速率,那么多余的流量将无限增加,即使流量可以用某种方式存储,这些流量预期被消费的时间也无限增加,满足不了业务需求。另一方面事实上无法实现真正的“无限”缓冲,它们最终都将受限于物理资源(内存、硬盘等),资源耗尽时,就不仅仅是流量丢失的问题了。
如果是有限的缓冲,则当缓冲满了以后,又回到了背压和丢弃策略了。而丢弃可不可行通常得看业务需求,于是早晚我们又得实现背压策略。
在详细介绍Pull与Push的各自细节前,先介绍下两者在编程方面的使用差别,准确的说是编程范式的差别。