使用MongoDB模拟Oracle序列

时间:2015-05-27 14:58:07

标签: mongodb

我们的域名模型处理销售发票,每个销售发票都有一个唯一的,自动生成的号码。创建发票时,我们的SalesInvoiceService从SalesInvoiceNumberGenerator检索一个数字,使用此编号和一些其他对象(卖方,买方,发行日期等)创建SalesInvoice,并通过SalesInvoiceRepository存储它。由于我们使用MongoDB作为数据库,因此我们的MongoDbSalesInvoiceNumberGenerator在给定的InvoicePolicies.nextSalesInvoiceNumber上使用带有$ inc 1的findAndModify命令来生成此唯一编号,类似于我们使用Oracle序列的编号。

这在正常情况下有效。但是,当由于业务规则损坏(例如无效的发布日期)而导致发票创建失败时,将引发异常并且我们的InvoicePolicies.nextSalesInvoiceNumber已经增加。显然,由于没有管理此工作单元的事务,因此不会回滚此增量,因此我们最终会丢失发票号。我们确实为用户提供了手动补偿机制,但我们希望首先避免这种情况。

你会如何应对这种情况?不,切换到另一个数据库不是选项:)

谢谢!

2 个答案:

答案 0 :(得分:2)

TL; DR:你想要的是strict serializability,但你可能无法得到它,除非你完全放弃并发(然后你理论上你甚至可以线性化)。无差距很容易,但确保今天的发票数量不比昨天低,这几乎是不可能的。

这很棘手,或者至少非常昂贵。对于任何其他数据存储也是如此,因为您必须将应用程序的并发性限制为保证它。想象一下在办公室里传来的自动增加的邮票,但是一些办公室工作人员丢失了信件。棘手......但你可以降低可能性。

当争用很高时,生成无间隙的序列很难,而在分布式系统中很难。在生成发票的整个时间内保持锁定通常不是一种选择,尽管这很容易。所以,让我们尝试一下:

  

最简单的方法:使用单例后台工作程序,即在单台计算机上运行的单线程进程。是否明确检查当前号码是否确实存在于发票集合中。因为它在一台机器上是单线程的,所以它不具备竞争条件。 完成,通过限制并发。

当允许并发时,事情会变得混乱:

最好使用two-phase commit protocol之类的东西。从本质上讲,使整个发票创建过程成为一个长期运行的交易,并明确存储待处理的交易,即存储尚未使用尚未但尚未保留的所有数字。

然后跟踪每个事务的完成状态。如果某个事务在超时后尚未完成,请再次考虑该号码。它很难将其添加到计数器代码中,但它是可能的(检查是否存在超时事务,否则获得新的计数器值)。

有几种可能的错误,但它们都可以解决。这在链接和网络上有更好的解释。一般来说,实现正确是很难的。

然而,超时会产生问题,因为您需要对生成发票所需的时间进行硬编码。这可能很难接近日/月/年障碍,因为您要避免在2015年创建发票12345和在2014年创建12344。

即使这样也无法在有限的时间间隔内保证无差距数字:如果没有更多的请求可以使用当年的差距数字,那么您就会遇到问题。

答案 1 :(得分:1)

我想知道是否可以使用类似findAndModify和新的Transactions API的组合来实现类似的目标,同时还考虑到如果在事务中进行交易会产生缺口呢?我还没有亲自尝试过它,我的项目还算不上担心帐单系统,但是希望能够对所有内容使用相同的数据库,以使事情更容易操作。

我认为一个问题可能是写瓶颈,但这只需要几毫秒,我可能会像现实生活商店那样对每个辖区或商店使用不同的计数器。然后,收银机号码也可能是其中的一部分,例如,假设您使用微服务,那么我猜数字世界中的收银机号码可能是去往的交易处理服务器,因此您可以在它们之间进行轮流平衡负载。假设它是否使用了每个文档锁-根据我的理解,可能会这样做。

我可能最担心这个瓶颈的唯一主要时间是,如果您有一家非常受欢迎的商店,或者在黑色星期五附近,那里有大量的高峰或正在开定期发票。