我在我的应用程序中使用C#和DynamoDb。
我的设计假设只写-读。强烈禁止更新项目。仅插入新项目或阅读现有项目。
假设我的付款项目为
{
"PaymentInvoice":"001", //PK
"Status":"2019-07-10T00:00:00#Approved" //SK
}
现在有2个并发请求来自不同的客户:第一个尝试尝试Cancel
付款,第二个尝试Settle
(确认)付款。
2个插入是:
{
"PaymentInvoice":"001", //PK
"Status":"2019-07-10T00:01:00#Cancel" //SK
}
和
{
"PaymentInvoice":"001", //PK
"Status":"2019-07-10T00:01:00#Settle" //SK
}
这是比赛条件。
-如果付款被取消,您将无法结清
-如果已经结算,您将无法取消付款
明显的解决方案是:
1)创建交易
2)首先进行查询,然后检查由于业务规则是否可以插入
3)插入新项目
所以问题是: 1)是否可以锁定整个分区以防止从其他客户端插入新项目? 2)是否有诸如条件更新之类的内置选项,但对于插入项
答案 0 :(得分:1)
在开始之前请先澄清一下。我假设是一种分布式的,面向服务的体系结构,并且我假定对此DynamoDB表的所有读取和写入仅通过一项服务进行。我将使用“应用程序”来指代您正在构建的用于访问表的软件,使用“客户端”来指代所有属于您的应用程序的客户端。
是否有诸如条件更新之类的内置选项,但可用于插入项目?
简短的答案是“是”,使用基于optimistic locking的version numbers。
首先需要将排序键更改为顺序的事件编号。这将是用于对项目进行版本控制的属性,该属性使用条件更新,并且在解决您的问题的任何解决方案中所产生的额外开销最少。
让我们从查看所提议模式的一些样本数据开始。我可以自由添加更多状态类型。
invoiceId | eventNo | eventStatus | datetime
----------|---------|-------------|---------------------
111 | 0 | created | 2019-07-11T00:01:00
111 | 1 | approved | 2019-07-11T00:02:00
111 | 2 | modified | 2019-07-12T00:03:00
111 | 3 | approved | 2019-07-12T00:04:00
111 | 4 | settled | 2019-07-13T00:05:00
乐观锁定的一般想法是,您先读取当前状态,然后通过插入新记录来更新状态,该记录的增量为eventNo
(与AWS文档中的version
相同),条件是该eventNo
尚未出现invoiceId
。之所以起作用,是因为当您读取现有状态时,您总是知道下一个eventNo
应该是什么(不同于使用时间戳作为排序键)。
为了更具体一点,在2019-07-13
上,当客户发送结算发票的请求时,您的应用程序读取最新状态,看到eventNo
为3,而{{1} }是“已批准”的,因此它向DynamoDB提交了一个UpdateItem请求(翻译成普通英语),
仅当
status
和invoiceId=111
尚无其他状态更新时,才使用eventNo=4
和invoiceId=111
插入状态更新
如果两个客户端尝试同时更新状态,则只有一个UpdateItem请求将成功,而另一个将返回ConditionalCheckFailedException。
好的,那我该怎么编码?
十多年来我一直没有使用C#,因此请原谅可能出现的任何语法或格式错误。
eventNo=4
这是我提供的代码示例的一些相关文档。
是否可以锁定整个分区以防止从其他客户端插入新项目?
是的,但这不是数据库中实现的锁。锁定必须使用单独的锁定库在应用程序中进行,这会产生额外的开销,因此,除非没有其他选择,否则不应使用此方法。对于阅读了无法更改其表架构的问题的任何人,您可以将您的应用程序设置为使用DynamoDB lock client来锁定分区键,读取当前状态,执行写入(如果允许),然后释放锁。