[译]Uber是如何使用MySQL设计可扩展性数据存储的?(一)

  Schemaless触发器

  Schemaless的一个重要特性是触发器,提供了在Schemaless实例变更时可获得通知的能力。由于cell是不可变的,以及新的版本是追加的,所以每个cell都代表了一个修改或者一个版本,这允许一个实例的值变更可以像日志一样查看变化。对于一个给定实例,可以监听这些变化以及触发基于这些变化的函数,非常像类似Kafka这种事件总线系统。Schemaless的触发器使Schemaless成为一个完美的真实来源的数据存储,因为除了随机访问数据,下游的依赖可以运用触发器功能来监听并触发任何应用侧特定的代码(与LinkedIn’s的 DataBus类似),进而实现数据创建与数据处理的解耦。

  在其它用例中,Uber在BASE列写入Mezzanine实例后,使用Schemaless的触发器来进行结账操作。针对上面的例子,当trip_uuid1的BASE列被写入后,我们的支付服务被BASE列的触发器触发,获取这个cell,然后尝试用信用卡支付该行程。无论成功与否,信用卡支付的结果都会回写如Mezzanine的STATUS列。通过这种方式实现了支付服务于行程创建的解耦,Schemaless扮演了一个异步事件总线的角色。

  索引的易用性

  最后,Schemaless支持在JSON blob中的字段上定义索引。当这些预定义的用于找到cell的字段与查询的参数相匹配时,就会用到索引。索引查询效率很高,因为索引查询只需要访问一个单一的分片来找到需要返回的cell的集合。事实上,查询还可以更深度的优化,因为Schemaless允许非标准化的cell数据直接加入索引中。索引中含有非标准化数据意味着索引查询在查询和取信息操作一起只需要查询一个分片。事实上,我们通常推荐Schemaless用户在可能需要的地方都把非标准数据加到索引当中,除非只需要直接用row key查询并取回cell。在某种意义上,这样一来我们用存储交换来了快速查询的性能。

  作为Mezzanine的一个例子,我们定义个了一个副索引来查询指定司机的所有行程。我们将行程的创建时间以及行程发生的城市非标准话加入到索引中。这样就可以查询一个司机在一个城市中一段时间中的所有行程。下面我们给出了driver_partner_index YAML 格式的定义,这是行程数据存储的一部分,定义在BASE列上 (这个例子用标准#符号添加了注释)。

  1

  2

  3

  4

  5

  6

  7

  8

  9

  10

  11

  12

  13

  14

  15

  table: driver_partner_index # Name of the index.

  datastore: trips # Name of the associated datastore

  column_defs:

  – column_key: BASE # From which column to fetch from.

  fields: # The fields in the cell to denormalize

  – { field: driver_partner_uuid, type: UUID}

  – { field: city_uuid, type: UUID}

  – { field: trip_created_at, type: datetime}

  使用这个索引,通过筛选city_uuid或者trip_created_at,我们能够找出指定driver_partner_uuid的行程。在这个例子中我们只用到BASE列的中的字段,但是Schemaless支持从多个列中非标准化数据,相当于上面column_def列表中的多个实例。

  像上文提到的Schemaless高效的索引得益于基于分片字段将索引分片。因此一个索引的唯一需求是索引中有一个字段是分片字段(例如上例中最先给出的driver_partner_uuid)。该分片字段决定了索引实体应该在哪个分片写入或者读取。原因是我们在查询索引的时候需要提供分片字段。这意味着在查询时,我们只需要查询一个分片来获取索引实体。关于分片字段有一点需要注意的是要选一个分布好的字段。UUID最佳,其次是city ids,不要选状态字段(枚举值)。

  除分片字段外,Schemaless还支持相等、不等以及范围查询的过滤器,同时支持只查询索引字段的一个子集以及根据索引实体指向的row key获取特定列或所有列。现在分片字段必须是不可修改的,因此Schemaless只需跟一个分片交互,但是我们正在探寻如何在没有太大性能开销的情况下让他成为可变的。

  索引具备最终一致性,无论何时我们写入一个cell,我也更新这个索引实体,但是这不发生在同一个事务中。Cell与索引实体通常不在同一个分片上,因此如果我们想要提供一致的索引,就需要在写入操作中引入 2PC ,这会明显加大开销。通过最终一致性的索引,我们避免了这项开销,但是Schemaless的用户可能会看到过期的数据。多数情况下cell变化与相关索引变化之间的延迟能控制在20ms之内。