【问题标题】:Designing HBase schema to best support specific queries设计 HBase 架构以最好地支持特定查询
【发布时间】:2012-01-24 07:45:40
【问题描述】:

我有一个与 HBase 架构设计相关的问题。问题相当简单——我将“通知”存储在 hbase 中,每个通知都有一个状态(“新”、“已见”和“已读”)。以下是我需要提供的 API:

  • 获取用户的所有通知
  • 获取用户的所有“新”通知
  • 获取用户所有“新”通知的计数
  • 更新通知状态
  • 更新所有用户通知的状态
  • 获取数据库中的所有“新”通知
  • 通知应该可以按时间倒序扫描并允许分页。

我有一些想法,我想看看其中一个显然是最好的,或者我是否完全错过了一个好的策略。对于这三者来说,我认为每个通知都有一行,并且在 rowkey 中有用户 ID 是要走的路。为了获得分页的时间顺序,我也需要有一个反向时间戳。我想将所有通知保存在一个表中(因此我不必为“获取用户的所有通知”调用合并排序)并且不想为二级索引表编写批处理作业(因为更新到计数和状态应该是实时的)。

最简单的方法是(1)行键是“userId_reverseTimestamp”并在客户端过滤状态。这似乎很幼稚,因为我们将通过网络发送大量不必要的数据。

下一种可能性是 (2) 将状态也编码到行键中,因此要么“userId_reverseTimestamp_status”,然后对扫描进行行键正则表达式过滤。我看到的第一个问题是需要在状态更改时删除一行并将通知数据复制到新行(大概每个通知应该恰好发生两次)。此外,由于状态是行键的最后一部分,因此对于每个用户,我们将扫描许多额外的行。这对性能有很大影响吗?最后,为了更改状态,我需要知道之前的状态是什么(构建行键),否则我需要进行另一次扫描。

我的最后一个想法是 (3) 有两个列族,一个用于静态通知数据,一个作为状态标志,即“s:read”或“s:new”和“s”作为 cf 和 status 作为限定符。每行只有一个,我可以针对该 cf 执行 MultipleColumnPrefixFilter 或 SkipFilter w/ ColumnPrefixFilter。在这里,我也必须在状态更改时删除和创建列,但它应该比复制整行更轻量级。我唯一担心的是 HBase 书中的警告,即 HBase 不能很好地处理“超过 2 或 3 个列族”——也许如果系统需要扩展更多查询功能,multi-cf 策略将无法扩展.

所以 (1) 似乎有太多的网络开销。 (2) 似乎会浪费复制数据的成本,并且 (3) 可能会导致太多家庭出现问题。在 (2) 和 (3) 之间,哪种类型的过滤器应该提供更好的性能?在这两种情况下,扫描都会查看用户的每一行,这可能主要是读取通知 - 这将具有更好的性能。我想我倾向于 (3) - 还有其他我错过的选项(或调整)吗?

【问题讨论】:

  • 通知状态是否为“新”和“已读”,只有一个可能的从新到已读的转换?这些通知的数量是多少?

标签: java hadoop nosql hbase


【解决方案1】:

您对此考虑了很多,我认为这三个都是合理的!

您希望主键是与时间戳连接的用户名,因为您的大多数查询都是“按用户”。这将有助于通过扫描轻松进行分页,并且可以非常快速地获取用户信息。

我认为你的问题的症结在于这个不断变化的状态部分。一般来说,像“读取”->“删除”->“重写”这样的操作会引入各种并发问题。如果您的任务在两者之间失败会发生什么?您是否有处于无效状态的数据?你会打破纪录吗?

我建议您改为将表格视为“仅附加”。基本上,按照您对#3 的建议进行操作,但不要删除标志,而是将其保留在那里。如果某个东西已经被读取,它可以有三个“s:seen”、“s:read”(如果它是新的,我们可以假设它是空的)。您也可以花哨并在三个中的每一个中放置一个时间戳,以显示该事件何时得到满足。这样做不会对性能造成太大影响,而且您不必担心并发性,因为所有操作都是只写的和原子的。

我希望这会有所帮助。我不确定我是否回答了所有问题,因为您的问题非常广泛。请跟进其他问题,我很乐意详细说明或讨论其他问题。

【讨论】:

  • Gppd 关于使其只写的观点。不必破解原子更新使其变得不那么复杂 - 我的过滤器将只是“只要没有未读状态”。有人建议的另一个选择是每行有多个列,其中一行是用户的所有通知。据推测,列的排序类似于行。我的问题是,这会给我们带来什么吗?他们还建议只在通知上做一个 ValueFilter (因此状态存在于数据本身中,需要更新,而不是单独的 CF)。我的猜测是这会有更差的表现。想法?
【解决方案2】:

我的解决办法是:

不要在 hbase 中为每个通知保存通知状态(已查看、新)。对于通知,请使用简单模式。键:userid_timestamp - 列:notification_message。

一旦客户端询问 API“获取所有新通知”,保存时间戳(所有新通知已推送)。键:userid - colmn:All_new_notifications_pushed_time

每个带有时间戳的通知都低于假定“已看到”的“推送的所有新通知”,如果更大则假定为“新”

要获取所有新通知: 首先通过用户 ID 获取 All_new_notifications_pushed_time 的值(时间戳) 然后按键对 notification_message 列执行范围扫描:从 current_timestamp 到 All_new_notifications_pushed_time。

这将显着限制受影响的列,并且大部分应该在 memstore 中。

计算客户端上的新通知。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-04
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多