0

TL;博士

如何在具有大量数据的键/值存储中找到“无法访问的键”?

背景

与提供 ACID 保证的关系数据库相比,NoSQL 键/值数据库为处理“大数据”提供的保证较少。例如,它们仅在单个键/值对的上下文中提供原子性,但它们使用分布式哈希表等技术在任意大型机器集群中“分片”数据。

钥匙通常对人类不友好。例如,代表员工的数据块的键可能是 Employee:39045e87-6c00-47a4-a683-7aba4354c44a. 员工可能还具有更人性化的标识符,例如jdoe员工登录系统时使用的用户名。此用户名将存储为单独的键/值对,其中键可能是EmployeeUsername:jdoe. key 的值EmployeeUsername:jdoe通常是包含主键的字符串数组(将其视为二级索引,不一定包含唯一值)或员工 blob 的非规范化版本(可能从其他对象聚合数据以改进查询性能)。

问题

现在,鉴于键/值数据库通常不提供事务保证,当进程插入键 Employee:39045e87-6c00-47a4-a683-7aba4354c44a(以及员工的序列化表示)但在插入 EmployeeUsername:jdoe键之前崩溃时会发生什么?客户不知道员工数据的密钥——他或她只知道用户名jdoe——那么如何找到 Employee:39045e87-6c00-47a4-a683-7aba4354c44a密钥?

我唯一能想到的是枚举键/值存储中的键,一旦找到合适的键,“恢复”索引/非规范化。我非常了解诸如事件溯源之类的技术,其中幂等事件处理程序可以响应事件(例如,EmployeeRegistered)以重新创建用户名到员工 uuid 二级索引,但仍然使用键/值存储上的事件溯源需要枚举键,这可能会降低性能。

比喻

我在 IT 方面的经验越多,我就越能看到在不同场景中解决相同的问题。例如,Linux 文件系统将文件和目录内容都存储在“inode”中。您可以将它们视为键/值对,其中键是整数,值是文件/目录内容。写入新文件时,系统会创建一个 inode 并用数据填充它然后修改父目录以添加“filename-to-inode”映射。如果系统在创建文件之后但在父目录中引用它之前崩溃,则您的文件“存在于磁盘上”但本质上是不可读的。当系统重新上线时,希望它会将此文件放入“lost+found”目录中(我想它是通过扫描整个磁盘来实现的)。

编辑

我发现这篇关于手动二级索引的有趣文章,但它没有“损坏”或“过时”二级索引。

4

2 回答 2

0

如果从事件源实体的角度来看一个问题,那么 EventStore 实体的责任包括保证将事件保存到存储中并发送到总线。从这个角度来看,可以保证事件会被完整地写入,并且作为 append-only 模式的数据库,永远不会出现无效事件的问题。

同时,当然不能保证生成事件的所有命令都会成功执行——可以只保证一个命令并防止重复执行同一命令,但不能保证所有事务。

进一步发生如下 - 传奇截获原始命令,并尝试执行所有事务。如果事务的任何部分因错误而结束,或者例如,在预设时间内没有结束 - 该过程通过生成所谓的补偿事件来回滚。此类事件不能删除实体,但会使系统进入与从未执行过的命令类似的一致状态。

笔记。如果你的事件数据库的具体实现被安排成键值可以保证只记录一对,只需序列化一个事件,标识符和聚合根的版本的组合就可以是一个键。在这种情况下,聚合的版本有点类似于 CAS 操作。

关于并发你可以阅读这篇文章:http ://danielwhittaker.me/2014/09/29/handling-concurrency-issues-cqrs-event-sourced-system/

于 2017-06-23T14:22:39.820 回答
0

我想出的解决方案是使用流程管理器(或“saga”),其密钥包含用户名。这也保证了注册期间员工之间的唯一性。(请注意,我正在使用具有比较和交换(CAS)语义的键/值存储来进行并发控制。)

  1. EmployeeRegistrationProcess用 的键 创建一个EmployeeRegistrationProcess:jdoe

    如果发生并发错误(即注册过程已经存在),那么这是一个重复的用户名。

  2. 启动时,EmployeeRegistrationProcess分配一个员工 UUID。EmployeeRegistrationProcess尝试使用此 UUID 创建对象Employee(例如, Employee:39045e87-6c00-47a4-a683-7aba4354c44a)。

    EmployeeRegistrationProcess如果系统在启动之后但在创建之前崩溃 Employee,我们仍然可以通过用户名“jdoe”来定位“员工”(或者更准确地说是员工注册过程)。然后我们可以恢复“交易”。

    如果存在并发错误(即Employee生成的 UUID 已经存在),则RegistrationProcess可以将自己标记为“错误”或“待审核”或我们认为最好的任何过程。

  3. Employee成功创建后, 创建EmployeeRegistrationProcess二级索引 EmployeeUsernameToUuid:jdoe-> 39045e87-6c00-47a4-a683-7aba4354c44a

    同样,如果失败,我们仍然可以通过用户名“jdoe”找到“员工”并恢复交易。

    同样,如果存在并发错误(即 EmployeeUsernameToUuid:jdoe密钥已经存在),则 EmployeeRegistrationProcess可以采取适当的措施。

  4. 当两个命令都成功(创建员工和创建二级索引)时, EmployeeRegistrationProcess可以删除。

在流程的所有阶段,Employee(或 EmployeeRegistrationProcess)都可以通过它的人性化标识符“jdoe”访问。事件溯源EmployeeRegistrationProcess是可选的。

请注意,使用流程管理器还可以帮助在注册后强制跨用户名的唯一性。也就是说,我们可以使用包含新用户名的键创建一个 EmployeeUsernameChangeProcess 对象。在注册或用户名更改时“强制”唯一性会损害可伸缩性,因此“EmployeeUsernameToUuid:jdoe”标识的值可能是一组员工 UUID。

于 2017-06-20T00:26:13.347 回答