Schemaless的主要功能是什么

36次阅读
没有评论

本篇内容介绍了“Schemaless 的主要功能是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让丸趣 TV 小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

Schemaless 的主要功能是什么

Schemaless trigger 是一项具有可扩展性、容错性和无损性的技术,监听 Schemaless 实例中的变更。在行程(trip)流程中起到引擎的作用,从司机按下“结束行程”并向系统提交费用,直到相应数据进入数据库等待分析。在 Schemaless 系列的最后一篇中,我们将深入讲解 Schemaless trigger 的功能,以及如何开发出这个可扩展的容错系统。

简单来说,在 Schemaless 数据的基本单位被命名为单元(cell)。它是不可变的,一旦写入,便无法被覆盖。(在特殊情况下,我们可以删除旧记录);单元可以被行键(row key)、列名(column name)和引用键(ref key)来引用;单元内容通过编写引用键更高的新版来执行更新,但行键和列名保持不变。Schemaless 不对其中存储的数据执行任何操作(故而命名 schemaless)。从 Schemaless 的观点来看,它只负责存储 JSON 对象。

Schemaless Trigger 案例

我们来看一下实践中 Schemaless trigger 的运作方式。下面的代码是简化版的异步计费方式(大写标注 Schemaless 的列名)。案例 Python 代码:

#我们实例化一个客户端,以便与 Schemaless 实例通讯  schemaless_client = SchemalessClient(datastore=’mezzanine’) #为 BASE 列注册一个 bill_rider 功能  @trigger(column=’BASE’) def bill_rider(row_key): # row_key 是行程的 UUID status = schemaless_client.get_cell_latest(row_key, ‘STATUS’) if status.is_completed: #也就是说我们已经提交了乘客的账单  return #否则就尝试提交账单  #我们从 BASE 列拿到了基本行程信息  trip_info = schemaless_client.get_cell_latest(row_key, ‘BASE’) #提交乘客账单  result = call_to_credit_card_processor_for_billing_trip(trip_info) if result != ‘SUCCESS’: #提交例外,让 Schemaless trigger 稍后重试。 raise CouldNotBillRider() # 成功提交乘客账单,写入 Mezzanine schemaless_client.put(row_key, status, body={‘is_completed’: True, ‘result’: result})

在 Schemaless 实例中,我们在函数中通过添加 decorator@trigger 来定义 trigger,并指定列。如果指定列的单元中有内容,通知 Schemaless trigger 框架调用函数——本例是 bill_rider。这里通过 BASE 中的一个新单元表明行程结束。触发 trigger,然后通过函数来发送行键——本例是行程 UUID。如果需要更多数据,必须从 Schemaless 实例——本例是从行程存储 Mezzanine 中获取真实数据。

bill_rider trigger 函数的信息流见下表(这里是乘客结账)。箭头方向指明调用方与被调方,旁边的数字指明流程的顺序:

Schemaless 的主要功能是什么

首先将行程输入 Mezzanine,Schemaless Trigger 框架调用 bill_rider。在调用时,函数向行程存储请求 STATUS 列的最新信息。本例中 is_completed 字段不存在,也就是说乘客尚未结账。然后获得 BASE 列的行程信息,通过函数调用信用卡 provider 来结账。在本例中,我们成功用信用卡付费,并返回成功信息到 Mezzanine,然后设置 STATUS 列的 is_completed 为 True。

Trigger 框架确保在每个 Schemaless 实例中的每个单元至少调用 bill_rider 一次。一般来说只触发 trigger 函数一次,不过在出错的情况下(无论是 trigger 功能还是其他功能短暂出错),都可能需要多次调用该函数。也就是说 trigger 函数是幂等的,在本例中要检查单元是否处理完毕。如果答案为是,则返回函数。

在查看下文中 Schemaless 如何在流程中提供支持时,要记得这个案例。我们将会解释 Schemaless 如何被看作变更日志,并讨论与 Schemaless 相关的 API,分享让流程支持可扩展和可容错的技术。

将 Schemaless 视为日志

Schemaless 包含所有单元,也就是说包含指定行键、列 keypair 的所有版本。由于包含单元的所有历史版本,除了随机访问 key-value 存储外,Schemaless 还可作为变更日志。事实上它就是一个分区日志,每个分片都是自己的日志,如下图:

Schemaless 的主要功能是什么

根据行键(也就是 UUID)将每个单元写入特定的分片。分片中的所有单元都有唯一标识符,称为添加 ID。添加 ID 是一个自动递增的字段,代表着单元的插入顺序(越新的单元,添加 ID 的数字越大)。除了添加 ID 之外,每个单元都有单元写入的时间(datetime)。在所有分片备份中,单元的添加 ID 是唯一的,这点对于故障时转移非常重要。

Schemaless 的 API 支持随机访问和日志类访问。随机访问 API 是针对单独的单元,均由 row_key、column_key 和 ref_key 一同定义。

Schemaless 还包含这些 API 端点的批处理版本,这里省略。之前说过的 trigger 函数 bill_rider 就使用这些函数来获取并操纵单个单元。

对于日志类访问 API,我们关心单元的分片数字与时间戳以及添加 ID(合称位置 location):

Schemaless 的主要功能是什么

与随机访问 API 类似,日志访问 API 有更多可用的 knob,实时从多个分片中抓取单元,不过上面的端点更为重要。位置可以是 timestamp 或 added_id。调用 get_cells_for_shard,除了单元之外,还返回下一个添加 ID。例如,如果调用位置 1000 的 get_cells_for_shards,请求 10 个单元,返回的下一个位置偏移是 1010。

追踪日志

通过日志类访问 API,可以追踪 Schemaless 实例,就像可以在系统中追踪文件一样(比如 tail -f),或者类似最新变更轮询的事件队列(比如 Kafka)。然后,客户端持续追踪偏移,并将其用在轮询中。要想引导追踪程序,需要从第一条开始(比如位置 0),或从任何时间,或偏移后。
Schemaless trigger 通过使用日志类访问 API 完成相同的追踪,并保持追踪偏移。轮询 API 的好处直接表现在,通过 Schemaless trigger 让这个过程具有可扩展性与容错性。通过配置从哪个 Schemaless 实例、哪一列开始轮询数据,将客户端程序与 Schemaless trigger 框架链接。使用的函数或回调与框架中的数据流相关,在新单元格插入实例时通过 Schemaless trigger 或调用或触发。反过来,通过框架在程序所运行的主集群中找到要找的工作进程。框架将工作分到可用进程中,然后通过将分到故障进程的工作分配给其他可用进程,巧妙地解决出现故障的进程。work 分配代表着程序员只用编写处理程序(比如 trigger 函数),并确保它是幂等的。剩下的交给 Schemaless trigger 来处理。

架构

在这部分中,我们会讨论 Schemaless trigger 如何扩展,如何将故障影响最小化。下图从较高角度展示了其架构,取自之前的账单结算服务:

Schemaless 的主要功能是什么

账单结算服务使用了运行在三台不同主机上的 Schemaless trigger,我们(简单起见)假设每个主机只有一个工作进程。Schemaless trigger 框架区将分片按工作进程区分开,因此每个工作进程只负责处理一个特定的分片。注意:工作进程 1 从分片 1 拉取数据,工作进程 2 从分片 2 和分片 5 拉取数据,工作进程 3 从分片 3 和分片 4 拉取数据。一个工作进程只处理指定分片的单元,抓取新单元、为这些分片调用注册的回调函数。一个工作进程就是指定的 leader,负责向工作进程分派片区。如果进程挂起,leader 将为故障进程分配的片区重新分配给其他进程。

在一个分片中,单元都是以写入顺序来触发。也就是说如果特定单元的 trigger 总是由于程序错误而出现故障,就会阻碍该片区的单元处理。为了避免延迟,可以配置 Schemaless trigger 来标记多次出错的单元,并将它们放在单独的队列中。之后,Schemaless trigger 就会继续下一个单元的处理。如果标记单元的数字超过了特定阈值,trigger 就会停止。通常代表着系统错误,需要人工修复。

通过存储每个片区中最近一次成功触发单元的添加 ID,Schemaless trigge 继续保持追踪。该框架将这些偏移保存到共享存储中,比如 Zookeeper 或 Schemaless 实例自身,也就是说如果程序重启,trigger 就会继续从存储片区的存储偏移开始执行。共享存储也用在 meta-info 中,比如协调选出 leader,探知添加或移除的工作进程。

可扩展性与容错性

Schemaless trigger 是为可扩展而设计的。在被追踪的 Schemaless 实例中,对于任意客户端程序,我们能够添加最多与片区数量一致的工作进程(通常是 4096)。此外,我们能够在线添加或移除 worker,来独立处理 Schemaless 实例中其他 trigger 客户端的变动负载。通过在框架中追踪进度,我们可以为要发送数据的 Schemaless 实例添加尽可能多的客户端。在服务器端并没有逻辑来持续追踪客户端或者将状态推送过去。

Schemaless trigger 也是容错的任何进程故障都可以不影响系统。

如果一个客户端 worker 的进程出错,leader 会将这个 work 重新分配,确保所有片区都有进程。

如果 Schemaless trigger 节点上的一个 leader 出错,会有新的节点被选成 leader。在 leader 选举期间,可以继续处理单元,不过 work 不能执行重分配工作,也无法移除和添加进程。

如果分片存储(比如 ZooKeeper)出错,单元进程持续进行。不过就像在 leader 选举期间一样,work 无法执行重分配工作,而在分片存储出错时进程也无法变更。

最后,在 Schemaless 实例中,Schemaless trigger 框架是不可能出现故障的。任何数据库节点出错都没关系,因为 Schemaless trigger 可以从备份读取。

“Schemaless 的主要功能是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注丸趣 TV 网站,丸趣 TV 小编将为大家输出更多高质量的实用文章!