知识卡片

RPC读写队列分离:避免一个大Scan请求阻塞排在它后面的Get请求

普通读书笔记卡

内容

HBase早期实现里,RegionServer上所有类型的操作(Put、Scan、Get)共享同一个RPC调用队列(Call queue),这意味着不同性质、不同耗时的操作会互相干扰——一个耗时较长的大范围Scan请求如果恰好排在某个Get请求前面,Get请求就必须等这个Scan完全处理完才能被执行,即使Get本身是一次代价很低、理应快速返回的操作,也会因为排在它前面的Scan而被迫承受一段不必要的额外延迟,这是一种典型的”队头阻塞”问题——先进先出的队列本身没有区分不同请求的紧迫程度和代价差异,导致轻量请求被重量请求拖累。HBase后来的实现引入了多个Call queue,把不同类型的操作分别分配到独立的队列里,通过hbase.ipc.server.callqueue.handler.factorhbase.ipc.server.callqueue.read.ratiohbase.ipc.server.callqueue.scan.ratio这几个参数来控制各类操作队列的资源配比,从而实现Put、Scan、Get之间的相互隔离——一个大Scan请求排队再久,也不会连累到本该快速完成的Get请求,因为它们走的根本是不同的队列、有各自独立的处理资源。这个案例揭示了一条应对”队头阻塞”这类问题的通用解法:当共享一个队列的多种请求之间存在明显的耗时和优先级差异时,简单地依赖先进先出的排队规则,会让轻量、紧迫的请求被重量、非紧迫的请求随机地拖慢(拖慢的程度取决于运气——恰好排在了谁的后面),这种不确定性本身就是一种性能隐患;解法是把这些性质不同的请求按类型拆分到独立的队列/资源池里,让它们各自拥有独立的处理能力,互不干扰——这个思路不局限于数据库的读写请求,任何”多种优先级或耗时特征差异明显的任务共享同一处理资源”的场景(比如线程池、消息队列消费者),都可以借鉴这种按类型拆分独立队列的方式来消除队头阻塞问题。

参考来源

- 位置:《高可用架构(第1卷)》第6章《大数据与数据库》"6.4 Hadoop、HBase年度回顾"节,"6.4.2 HBase2015年技术发展"(源文件:_epub-src/OEBPS/Text/Chapter6_4_3.xhtml) - 结论依据:原文说明"在之前的实现中,RegionServer上所有操作共享队列,各种操作互相影响。比如Scan和Get,在RPC Call queue中,如果一个大的Scan请求排列在Get之前,那么Get就要等之前的Scan完成才可以执行,延迟较大。在目前的实现中,RPC可以具有多个Call queue,同时将它们分配给不同的操作使用,从而实现各种Put、Scan和Get等操作的隔离",直接支撑本卡片结论。 - 原始内容:在之前的实现中,RegionServer上所有操作共享队列,各种操作互相影响。比如Scan和Get,在RPC Call queue中,如果一个大的Scan请求排列在Get之前,那么Get就要等之前的Scan完成才可以执行,延迟较大。在目前的实现中,RPC可以具有多个Call queue。