用MongoDB模拟Oracle序列

用MongoDB模拟Oracle序列,mongodb,Mongodb,我们的域模型处理销售发票,每个发票都有一个唯一的自动生成的编号。创建发票时,我们的SalesInvoiceService从SalesInvoiceNumberGenerator中检索一个编号,使用此编号和一些其他对象(卖方、买方、发行日期等)创建一个SalesInvoice,并通过SalesInvoiceRepository存储该编号。由于我们使用MongoDB作为数据库,因此我们的MongoDbSalesInvoiceNumberGenerator在给定的InvoicePolicys.next

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

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

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

谢谢

TL;DR:你想要的是,但你可能得不到,除非你完全放弃并发(理论上,你甚至可以获得线性化能力)。没有差距是很容易的,但要确保今天的发票不会比昨天少,这几乎是不可能的

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

当竞争激烈时,生成没有间隙的序列是很困难的,并且在分布式系统中非常困难。在生成发票的整个过程中保持锁定通常不是一个选项,尽管这很容易。让我们试试看:

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

允许并发时,事情会变得一团糟:

最好是使用类似于a的东西。本质上,使整个发票创建过程成为一个长期运行的事务,并显式存储挂起的事务,即存储所有尚未使用但保留的编号

然后跟踪每笔交易的完成状态。如果事务在超时后未完成,请考虑再次可用。将其添加到计数器代码很难,但这是可能的(检查是否存在超时事务,否则获取新的计数器值)

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

但是,超时会带来一个问题,因为您需要硬编码生成发票所需时间的假设。接近日/月/年界限可能会很尴尬,因为您希望避免在2015年和2014年分别创建12345和12344发票


即使这样,也不能保证在有限的时间间隔内没有缺口:如果没有更多的请求可以使用当年的缺口编号,您正面临一个问题。

我想知道是否可以使用
find和modify
之类的工具,以及新的组合来实现类似的功能,同时还可以考虑在事务中运行时的差距?我还没有亲自尝试过,我的项目也不远,不用担心计费系统,但我希望能够在所有事情上使用相同的数据库,使事情更易于操作

我认为有一个问题可能是写瓶颈,但我认为这只需要几毫秒,而且您可能会像现实生活中的商店一样,为每个辖区或商店使用不同的计数器。然后收银机号码也可以是其中的一部分,我猜数字世界中的收银机号码可能是它所使用的事务处理服务器,比如说你使用了微服务,所以你可能可以在它们之间进行负载平衡循环。这是假设它使用了每个文档的锁——据我所知,这是可能的


我唯一可能担心这个瓶颈的时候是,如果你有一家非常受欢迎的商店,或者是在黑色星期五前后,那里有一个巨大的峰值,或者是做重复的发票。

在mongodb上模拟交易是徒劳的。您所能做的最好的事情就是成堆脆弱的应用程序逻辑,它们很难理解和维护。Mongodb没有事务不是因为开发人员不喜欢它们或者其他什么。这是因为交易很难做对。另一种解决方案是容忍发票号码丢失。:)FWIW,在Oracle中,在回滚的情况下,序列号不会被拉回。因此,Oracle无法保证序列中没有漏洞。。。就像在MongoDB实现中一样。对容错/回滚的“无漏洞”序列编号的追求似乎是一个乌托邦:例如,如果根据某些法规,您需要删除过去的发票,那么这种行为会如何?是否将以下所有发票重新编号以强制执行连续编号?另一种解决方案是仅在成功保存后设置发票编号。也就是说,插入所有发票数据(不包括编号)。如果写入成功,您将从序列中检索编号并对该发票进行更新。谢谢