是否有一种方法可以在不依赖数据库的情况下确保字段的唯一性



如果不依赖数据库,是否有办法确保字段(比如用户的emailAddress)是唯一的。

一些常见的失败尝试:

  1. 首先检查emailAddress是否存在(通过查询数据库),如果不存在,则创建用户。现在很明显,在检查窗口中,其他线程可以创建具有相同电子邮件的用户。因此,这种解决方案是不好的
  2. 对负责创建用户的方法应用语言级别的锁定。这个解决方案失败了,因为出于性能原因,我们需要服务的冗余,并且锁定在单个JVM上
  3. 使用事件存储(如Akka参与者的邮箱),事件是AddUser消息,但由于参与者的行为是异步的,因此无法通知请求者(发件人)使用唯一电子邮件成功创建用户。此外,两个请求(使用相同的电子邮件)如何知道它们包含一个唯一的电子邮件?这可能会变得复杂

数据库是每个线程和每个服务实例将写入的单个数据源,因此在这里实现unique constraint是有意义的。但对于关系型数据库来说,情况也是如此。那么NoSql数据库呢?有些确实允许一个唯一的约束,但这不是他们的本地行为,或者可能是。

但是,不使用数据库来实现字段的唯一性的问题是,有什么选择?

我认为你的问题更一般——"我如何确保数据库写入操作成功,以及如何处理没有成功的情况&";。唯一性只是一种失败模式——您可能试图插入一个太大、数据类型错误或与外键约束不匹配的值。

关系数据库通过与ACID兼容来解决这个问题,并在事务失败时抛出错误供客户端处理。

您想要ACID在没有关系数据库的情况下的(一些)好处。这是一个相当大的话题。解决这一问题的明显方法是引入";交易";在您的应用程序层中。例如,在您的情况下,您可能会发送一个";创建帐户(emailAddress,name,…)";消息,并让应用程序侦听"消息";accountCreated";或";accountCreationFailed;回答该消息的接收者负责写入数据库;你有几个选择。一种是锁定该线程(因此任何时候只有一个进程可以写入数据库);这不是超级可扩展的。我使用的另一种机制是引入状态标志——你用一个";草稿";标志,然后检查您的约束(包括唯一性),并设置";草稿";标志为";"已验证";如果满足约束(即没有具有相同电子邮件地址的其他记录);失败";如果不是的话。

要检查uniquness,您需要存储"状态";程序的。为了安全起见,您需要能够以事务方式对状态进行更改。

  • 您可以使用数据库事务。一些NoSQL数据库也支持事务,例如redis和MongoDB。您必须分别检查每个供应商,以了解他们如何支持交易。在这个设置中,每个客户端都将连接到数据库,它将为您处理所有细节。同样,根据您的用例,您应该小心隔离级别的配置
  • 如果不考虑持久性,那么可以使用支持事务的内存中数据库

选择哪个状态存储,它应该支持事务。有几种方法可以实现事务并实现一致性。许多关系数据库(如PostgresSQL)通过实现MVCC算法来实现这一点。在分布式环境中,您必须查找分布式事务,如2PC、Paxos等。

通常每个人都依赖可用的数据存储解决方案,除非对项目有奇怪或特定的要求。

最后要注意的是,这里的通信模式与根本问题无关。例如,在您提到的Actor案例中,在一天结束时,每个Actor都必须查询状态,以确定是否存在电子邮件。如果您的状态存储支持Serializability,那么就没有问题,也不会发生冲突(将错误传达给客户端是另一个问题)。假设您使用的是PostgreSQL。当发出插入/更新查询时,它被包裹在事务中,底层的MVCC算法将处理所有事务。在高级和分布式环境中,您可以使用支持分布式事务的数据存储,如CockratchDB。

如果你想深入研究,你可以研究这些关键词:ACID,隔离级别,原子性,可串行性,CAP定理,2PC,MVCC,分布式交易,分布式锁。。。

NoSQL数据库提供了与关系数据库不同的、较弱的保证。一般来说,代价是放弃ACID保证,以换取在对应用程序重要的维度上增加可伸缩性。

可以提供某种独特性保证,但需要进行某些权衡。有了NoSQL,总会有一些折衷。

如果你的NoSQL存储支持乐观并发控制,也许这种方法会奏效:

在NoSQL表中的所有文档中存储一个单独的文档,该文档包含所有emailAddress值的集合。这是该文档在给定时间的一个实例。

每次要保存包含emailAddress的文档时,首先确认电子邮件地址的唯一性:

在乐观锁定的保护下执行以下操作。如果这是由于并发更新,您可以在后端:

  1. 阅读此";所有电子邮件";文件
  2. 确认电子邮件不存在
  3. 如果不存在,则将电子邮件地址添加到";所有电子邮件文档">
  4. 保存它

您现在交易了一个问题。。。缺乏独特的约束,另一方面。。。无法在您的原始文档和这个新的"文档"之间同步更新;所有电子邮件";文件这可能是可接受的,也可能是不可接受的。这取决于您的应用程序需要提供的保证。

例如,也许你可以接受一封电子邮件可能被添加到";所有电子邮件";,将相关文档保存到您的另一个";表";随后失败,并且该电子邮件地址现在无法使用。你可以用一个批处理工作来清理它。不确定。

电子邮件的索引可以存储在一些其他服务中(例如,持久缓存)。同样的问题也存在,您需要以某种方式保持索引和文档存储的同步。

没有简单的解决办法。有关相关概念的详细概述,我推荐Martin Kleppmann的Designing Data Intensive Applications。

最新更新