我目前正在构建一个使用Azure存储来存储图像的python Tornado Web应用程序,以及用于在图像上存储元数据的DocumentDB。每当上传图像时,它都可以使用运行Tornado Web App的2个可能的Docker容器中的任何一个来异步执行POST方法。我遇到的错误是当我到达我在DocumentDB脚本中的存储过程时。 sproc在两个独立的Docker容器中同时在两个独立的线程中执行。存储过程旨在为每个上传的图像生成一个新的ReceiptID,方法是查询DocDB中的gen_receipt_id
文档,如下所示:
{
"id": "gen_receipt_id",
"counter": 406
}
然后,sproc将counter
属性递增1,并且该新ID用于附加到元数据中的新收据。 sproc看起来像这样:
function receiptIDSproc() {
var collection = getContext().getCollection();
// Query documents and take 1st item.
var isAccepted = collection.queryDocuments(
collection.getSelfLink(),
"SELECT * FROM root r WHERE r.id='gen_receipt_id'",
function(err, feed) {
if (err) throw err;
// Check the feed and if empty, set the body to 'no docs found',
// else take 1st element from feed
if (!feed || !feed.length) getContext().getResponse().setBody('no docs found');
else {
tryUpdate(feed[0]);
}
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
function tryUpdate(document) {
var requestOptions = {"If-Match": document._etag,
etag: document._etag};
document.counter += 1;
// Update the document.
var isAccepted = collection.replaceDocument(document._self, document, requestOptions, function (err, updatedDocument,responseOptions) {
if (err) throw err;
// If we have successfully updated the document - return it in the response body.
getContext().getResponse().setBody(updatedDocument);
});
// If we hit execution bounds - throw an exception.
if (!isAccepted) {
throw new Error("The stored procedure timed out.");
}
}
}
然而,当我同时上传多个图像时,我与异步发生的操作发生冲突: Fine-Uploader upload conflict
控制台中的错误如下所示:
[36mtornado2_1 |[0m ERROR:500 POST /v1.0/groups/1/receipts (172.18.0.4) 1684.98ms
[33mtornado1_1 |[0m 407 //Here I'm printing the ID the Sproc generated
[33mtornado1_1 |[0m 2016/9/13/000000000000407
[36mtornado2_1 |[0m 407 //Here I'm printing the ID the Sproc generated
[36mtornado2_1 |[0m 2016/9/13/000000000000407
[32mnginx_1 |[0m 10.0.75.1 - - [13/Sep/2016:16:49:47 +0000] "POST /v1.0/groups/1/receipts HTTP/1.1" 200 17 "http://local.zenxpense.com/upload" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36"
[33mtornado1_1 |[0m INFO:200 POST /v1.0/groups/1/receipts (172.18.0.4) 1132.49ms
[36mtornado2_1 |[0m WARNING:500 POST /v1.0/groups/1/receipts (172.18.0.4): An error occured while uploading to Azure Storage: HTTP 500: Internal Server Error (An error occured while creating DocumentDB record: Status code: 409
[36mtornado2_1 |[0m {"code":"Conflict","message":"Message: {\"Errors\":[\"Resource with specified id or name already exists\"]}\r\nActivityId: b226be91-f193-4c1b-9cc2-bcd8293bd36b, Request URI: /apps/8ae2ad5a-d261-42ac-aaa1-9ec0fd662d12/services/cc7fdf37-5f62-41db-a9d6-37626da67815/partitions/8063ad6c-33ad-4148-a60f-91c3acbfae6f/replicas/131171655602617741p"})
正如您从错误中看到的那样,Sproc正在两个不同的Docker容器407
上执行并生成相同的ReceiptID,因此,由于我正在尝试创建两个文档,因此存在冲突错误相同的ID。我需要做的是阻止Sproc在两个单独的容器上生成相同的ID。我尝试在Sproc中使用Etags和“If-Match”标头,但它仍然会发生,因为每个容器在文档上都有相同的Etag,所以它没有看到错误。
答案 0 :(得分:0)
这个常见问题的典型NoSQL解决方案是使用GUID而不是顺序ID。
但是,由于DocumentDB sprocs为您提供了ACID约束,因此应该可以使用乐观并发方法和重试来执行您想要的操作。
因此,如果您在同一时间从两个不同的位置运行此sproc两次,您将获得相同的ID(在此示例中为407)。其中一个sprocs应该能够写入该ID,另一个将失败。这里的关键是重试任何失败。 sproc将重新运行并获取下一个ID(408)。由于同时请求应该是罕见的,因此对中位响应时间的影响可以忽略不计。