知识卡片
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.factor、hbase.ipc.server.callqueue.read.ratio、hbase.ipc.server.callqueue.scan.ratio这几个参数来控制各类操作队列的资源配比,从而实现Put、Scan、Get之间的相互隔离——一个大Scan请求排队再久,也不会连累到本该快速完成的Get请求,因为它们走的根本是不同的队列、有各自独立的处理资源。这个案例揭示了一条应对”队头阻塞”这类问题的通用解法:当共享一个队列的多种请求之间存在明显的耗时和优先级差异时,简单地依赖先进先出的排队规则,会让轻量、紧迫的请求被重量、非紧迫的请求随机地拖慢(拖慢的程度取决于运气——恰好排在了谁的后面),这种不确定性本身就是一种性能隐患;解法是把这些性质不同的请求按类型拆分到独立的队列/资源池里,让它们各自拥有独立的处理能力,互不干扰——这个思路不局限于数据库的读写请求,任何”多种优先级或耗时特征差异明显的任务共享同一处理资源”的场景(比如线程池、消息队列消费者),都可以借鉴这种按类型拆分独立队列的方式来消除队头阻塞问题。