Pegasus,HBASE,Redis比较

Pegasus典型场景

  • 数据模型简单, 可以接受Key-Value模型存储

  • 数据存储量大, 例如100GB~10TB的存储规模, 使用Redis等内存存储价格高昂, 希望通过相对更低廉的磁盘(SSD)来访问

  • 数据有强一致, 持久化的需求. 例如存储用户消息, 用户访问历史等等, 不希望数据丢失

  • 数据访问需要较低的延迟, Pegasus 通常能够提供P99在10ms级别的稳定读写延迟 (注: 3KB的单条数据读写)

不适用场景

  • 存储>=100KB的单条数据. 通常该长度的数据建议使用对象存储(如FDS)进行读写, 受Pegasus的系统设计所限, 无法保证此类型数据读写的延迟和稳定性

  • 数据量小(<=10GB), 不需要持久化, 且有大量读写的数据(例如缓存数据), 通常更适合使用Redis存储

  • 在线SQL查询. Pegasus仅支持通过pegasus-spark进行离线数据分析, 不支持SQL接口

与HBase比较

Pegasus系统的最初目的就是弥补HBase的不足,而且上面也详细阐述了理由和设计思想。这里再从用户使用角度比较一下两者的区别:

  • 数据模型:HBase是表格模型,采用Range分片;Pegasus是Key-Value模型,采用Hash分片。
  • Range 分片(HBase)

    • HBase 根据 RowKey 的"范围"进行分片(shard),也就是一个 Region 管理一段连续的 RowKey 范围。

    • 优点:支持范围查询(range scan),比如获取某个用户 ID 范围内的数据。

    • 缺点:如果 RowKey 分布不均,容易导致热点(部分 Region 压力过大)。

  • Pegasus 是 Key-Value 模型:更像 Redis,是以 key-value 键值对的方式存储数据,没有复杂的表结构。Key 是唯一标识,Value 是存储的内容。

  • Hash 分片(Pegasus)

    • 根据 Key 的哈希值分配到不同的分区。

    • 优点:负载均衡更好,数据分布更均匀。

    • 缺点:不支持范围查询,只能根据特定 key 查询。

  • 接口:HBase的API接口功能虽然很丰富,但是使用也更复杂;Pegasus的接口简单,对用户更友好。

  • 可用度:由于架构和实现的原因,HBase的可用度通常达到99.95%就不错了;Pegasus的可用度可以到达99.99%。

  • 性能:由于分层架构,HBase的读写性能不是太好,P99通常在几十甚至几百毫秒级别,而且GC问题会带来毛刺问题;Pegasus的P99可以在几毫秒,满足低延迟的在线业务需求。

HBASE查询一条数据的过程

  1. 客户端发起请求

客户端提供 RowKey,想读取对应的数据。


  1. 查 Meta 信息(定位 Region)

HBase 的数据被拆成了很多个 Region(即分片),每个 Region 管理一段连续的 RowKey 范围。

为了知道 某个 RowKey 属于哪个 Region ,客户端需要通过 Meta 表(特殊的系统表)查找:

  • Meta 表也就是一个索引,它记录了:

    RowKey 范围 → Region → 所在 RegionServer

🧠 所以这个阶段,客户端可能要:

  • 向 ZooKeeper 获取 Root/META 表的位置。

  • 查询 META 表,获得 Region 的位置。

  • 找到对应的 RegionServer。


  1. 请求 RegionServer

一旦知道了数据在哪个 RegionServer 上,就向那个服务器发起数据请求。


  1. RegionServer 内部查询数据

这个阶段包括几个层次:

✅ MemStore(内存缓存)优先查找

  • 如果数据在 MemStore(写缓存)中,直接返回。

✅ BlockCache(读缓存)尝试命中

  • 如果曾经访问过,可能在 block cache 中(LRU 缓存),可以快速读取。

❌ 如果缓存都没命中,才去 HFile 文件中查找:

  • HFile 是 HBase 存储在 HDFS 上的实际文件。

  • 通常使用 Bloom Filter + Block Index 做定位。

  • 但本质上还是要读取 HDFS 上的文件块。


  1. 读取 HDFS 上的文件(慢的根源之一)
  • HBase 使用 HDFS 存储数据,HDFS 的 IO 模型是为大文件顺序读 优化的,不擅长小文件随机读

  • 虽然 HFile 有 index,但:

    • 每次读可能涉及多个文件。

    • 如果数据冷热不均或缓存未命中,就需要走磁盘 + 网络 IO。

🐢查询慢的原因总结

❶ 分层架构带来的多跳访问

  • 查询数据不是"直接查磁盘",而是:

    1. 先找 Meta 表(可能访问 ZooKeeper)

    2. 找 RegionServer

    3. 走缓存或落盘

      => 请求路径很长,层级多,容易产生额外延迟。


❷ 数据在 HDFS 上,IO 机制不适合随机读

  • HDFS 是为吞吐量设计的,适合大文件批量读写。

  • 但在线业务通常需要低延迟、随机读,这就很吃亏。

  • 特别是数据不在缓存里时,访问延迟会显著增加(几十~几百 ms 是常态)。


❸ GC 问题

  • RegionServer 是 Java 服务,使用 JVM。

  • 当内存中写缓存(MemStore)、BlockCache 增长到一定程度,会频繁触发 GC(垃圾回收)。

  • GC 会暂停应用线程,造成 请求延迟毛刺,即突然变慢。


❹ Region 分裂 & 移动

  • Region 是动态分裂、迁移的,尤其是在负载变化、扩容、Compaction(合并)时。

  • 这个过程依赖 ZooKeeper 协调,频繁变动会导致 元信息不一致、缓存失效、访问失败重试

与HBASE比较总结

特性 HBase Pegasus
数据结构 半结构化表格模型 简单的 Key-Value
是否有行列概念 有(RowKey、ColumnFamily、Column) 无,只有 Key 和 Value
列是否固定 否(每行可以有不同列) 不涉及列
Schema 严格性 较松散(弱 schema) 无 schema
支持复杂查询 支持范围查询(Range Scan) 不支持,只支持精确查询
用途 批处理、海量数据存储,偶尔在线使用 高并发、低延迟的在线服务

与Redis比较

其实Pegasus是不适合与Redis比较的,Redis是基于内存的缓存系统,与之比较性能肯定被吊打。但是我们发现,业务往往需要的不仅仅是性能,可能还有可用性、伸缩性等,所以比拼综合素质,Pegasus也是有其自身的优势的。

我们在与业务的沟通中发现,他们很多时候对数据的性能和可用性要求都很高。在系统选型时遇到这些问题:

  • HBase虽然可用性高也易伸缩,但是性能不够好。

  • Redis虽然性能好,但是需要大量内存,如果数据量太大,一台机器搞不定;如果采用分布式方案,譬如Redis Cluster或者Codis,在机器宕机故障情况下的可用性又不够,并且使用内存的成本也比较高。

  • 一些用户想出了HBase+Redis的方案,即使用HBase做底层存储,使用Redis做上层缓存,写数据的时候同时更新HBase和Redis,读数据的时候先从Redis中读,如果读不到再从HBase中读;但是这样的缺点是:因为涉及两个系统,用户的读写逻辑会比较复杂,且同时写两个系统时容易出现一致性问题;一份数据要存储在HBase和Redis中,成本比较高;Redis机器宕机后造成部分缓存丢失,还是要从HBase读取,性能明显降低,服务质量下降甚至降级。

Pegasus可以看做是HBase和Redis的结合体,它即保证高的可用度,又具有好的伸缩性,还具有相对不错的性能。如果业务对性能的要求不是太变态(譬如P99要求在1毫秒以内),那么可以考虑直接使用Pegasus。与Redis进行比较区别如下:

  • 数据模型:两者都是Key-Value模型,但是Pegasus支持(HashKey + SortKey)的二级键。

  • 接口:Redis的接口更丰富,支持List、Set、Map等容器特性;Pegasus的接口相对简单,功能更单一。

  • 性能:Redis性能比Pegasus好不少,Redis是在几十或者几百微妙级别,Pegasus是在毫秒级别。

  • 伸缩性:Pegasus伸缩性更好,可以很方便地增减机器节点,并支持自动的负载均衡;Redis的分布式方案在增减机器的时候比较麻烦,需要较多的运维介入。

  • 可用性:Pegasus数据总是持久化的,系统架构保证其较高的可用性;Redis在机器宕机后需要较长时间恢复,可用性不够好,还可能丢掉最后一段时间的数据。

  • 成本:Pegasus使用SSD,Redis主要使用内存,从成本上考虑,Pegasus显然更划算。

与Redis数据模型对比

虽然不像Redis一样支持丰富的list/set/map等数据Schema,用户同样可以使用Pegasus实现类似的语义。

譬如用户可以将 HashKey 等同于 Redis 的 key,将 SortKey 作为 map 的 key,实现 Redis 中 map 或者 set 的语义。list 语义的支持稍微困难些,但是也可以基于 Key-Value 进行封装,譬如360开源的 Pika 就做过 类似的事情。另一种解决方案就是,将 map/set/list 数据结构通过某种方法(protobuf/thrift/json)序列化为一个字节串,然后直接作为一个整体存储在 value 中,其缺点是用户需要在客户端增加序列化/反序列化开销,并且每次数据更新都需要对整个 value 读写一次。