एक ठोस उदाहरण प्रदान करने के लिए (सही उदाहरण के एक आश्चर्य की बात कमी ऑनलाइन न होने के कारण): यहाँ कैसे लागू करने के लिए एक "atomic bank balance transfer" CouchDB (मोटे तौर पर एक ही विषय पर अपने ब्लॉग पोस्ट से नकल: http://blog.codekills.net/2014/03/13/atomic-bank-balance-transfer-with-couchdb/) में
सबसे पहले, समस्या का संक्षिप्त विवरण: बैंकिंग सिस्टम कैसे खातों को खातों के बीच स्थानांतरित करने की अनुमति देता है ताकि डिज़ाइन किया जा सके ताकि ऐसी स्थितियां न हों जो अमान्य या गैरकानूनी शेष छोड़ सकें?
पहले:: लेन-देन लॉग
इस समस्या का कुछ भागों रहे हैं। एक खाते की शेष राशि को रिकॉर्ड या दस्तावेज़ में संग्रहीत करने के बजाय - {"account": "Dave", "balance": 100}
- खाते का शेष राशि उस खाते में सभी क्रेडिट और डेबिट को सारांशित करके गणना की जाती है। ये क्रेडिट और डेबिट एक सौदे लॉग, जो कुछ ऐसी दिखाई दे सकती में जमा हो जाती:
{"from": "Dave", "to": "Alex", "amount": 50}
{"from": "Alex", "to": "Jane", "amount": 25}
और CouchDB नक्शा को कम कार्यों शेष राशि की गणना कुछ ऐसा दिखाई दे सकता:
POST /transactions/balances
{
"map": function(txn) {
emit(txn.from, txn.amount * -1);
emit(txn.to, txn.amount);
},
"reduce": function(keys, values) {
return sum(values);
}
}
GET /transactions/balances
{
"rows": [
{
"key" : "Alex",
"value" : 25
},
{
"key" : "Dave",
"value" : -50
},
{
"key" : "Jane",
"value" : 25
}
],
...
}
लेकिन इस छुट्टी:
पूर्णता के लिए, यहाँ की शेष राशि की सूची है स्पष्ट प्रश्न है: त्रुटियों को कैसे नियंत्रित किया जाता है? क्या होता है यदि कोई अपनी शेष राशि से अधिक स्थानांतरण करने का प्रयास करता है?
कॉच डीबी (और इसी तरह के डेटाबेस) के साथ इस प्रकार के व्यावसायिक तर्क और त्रुटि हैंडलिंग आवेदन स्तर पर लागू की जानी चाहिए। भोलेपन से, इस तरह के एक समारोह इस प्रकार दिखाई देंगे:
def transfer(from_acct, to_acct, amount):
txn_id = db.post("transactions", {"from": from_acct, "to": to_acct, "amount": amount})
if db.get("transactions/balances") < 0:
db.delete("transactions/" + txn_id)
raise InsufficientFunds()
लेकिन लगता है कि लेन-देन डालने और अद्यतन शेष राशि डेटाबेस एक असंगत राज्य में छोड़ दिया जाएगा जाँच के बीच अनुप्रयोग क्रैश हो जाता है, तो: इस किया जा सकता है
// Initial balances: Alex: 25, Jane: 25
db.post("transactions", {"from": "Alex", "To": "Jane", "amount": 50}
// Current balances: Alex: -25, Jane: 75
यह कैसे तय किया जा सकता है: एक नकारात्मक शेष है, और पैसा है कि पहले नहीं था मौजूद साथ प्राप्तकर्ता के साथ छोड़ दिया?
समय लेनदेन बनाया गया था (यह सुनिश्चित करने के है कि वहाँ एक strict total ordering:
यकीन है कि प्रणाली एक असंगत स्थिति में नहीं है बनाने के लिए, जानकारी के दो टुकड़े प्रत्येक लेन-देन के लिए जोड़ा जा करने की जरूरत है लेनदेन का), और
एक स्थिति - लेनदेन सफल हुआ या नहीं।
वहाँ भी दो बार देखा होने की आवश्यकता होगी - एक जो किसी खाते का उपलब्ध संतुलन (यानी, सब "सफल" लेन-देन का योग) देता है, और एक अन्य जो रिटर्न सबसे पुराने "लंबित" लेन-देन:
POST /transactions/balance-available
{
"map": function(txn) {
if (txn.status == "successful") {
emit(txn.from, txn.amount * -1);
emit(txn.to, txn.amount);
}
},
"reduce": function(keys, values) {
return sum(values);
}
}
POST /transactions/oldest-pending
{
"map": function(txn) {
if (txn.status == "pending") {
emit(txn._id, txn);
}
},
"reduce": function(keys, values) {
var oldest = values[0];
values.forEach(function(txn) {
if (txn.timestamp < oldest) {
oldest = txn;
}
});
return oldest;
}
}
स्थानान्तरण की सूची अब कुछ इस तरह दिख सकता है:
{"from": "Alex", "to": "Dave", "amount": 100, "timestamp": 50, "status": "successful"}
{"from": "Dave", "to": "Jane", "amount": 200, "timestamp": 60, "status": "pending"}
इसके बाद, आवेदन की आवश्यकता होगी सत्यापित करने के लिए एक समारोह जो क्रम में प्रत्येक लंबित लेनदेन की जाँच करके लेनदेन हल कर सकते हैं कि यह मान्य है, तो इसकी स्थिति को अपडेट करने से "लंबित" या तो "सफल" या करने के लिए "को अस्वीकार कर दिया":
def resolve_transactions(target_timestamp):
""" Resolves all transactions up to and including the transaction
with timestamp `target_timestamp`. """
while True:
# Get the oldest transaction which is still pending
txn = db.get("transactions/oldest-pending")
if txn.timestamp > target_timestamp:
# Stop once all of the transactions up until the one we're
# interested in have been resolved.
break
# Then check to see if that transaction is valid
if db.get("transactions/available-balance", id=txn.from) >= txn.amount:
status = "successful"
else:
status = "rejected"
# Then update the status of that transaction. Note that CouchDB
# will check the "_rev" field, only performing the update if the
# transaction hasn't already been updated.
txn.status = status
couch.put(txn)
अंत में, के लिए सही ढंग से आवेदन कोड एक हस्तांतरण प्रदर्शन:
def transfer(from_acct, to_acct, amount):
timestamp = time.time()
txn = db.post("transactions", {
"from": from_acct,
"to": to_acct,
"amount": amount,
"status": "pending",
"timestamp": timestamp,
})
resolve_transactions(timestamp)
txn = couch.get("transactions/" + txn._id)
if txn_status == "rejected":
raise InsufficientFunds()
कुछ नोट:
ब्रेवटी के लिए, यह विशिष्ट कार्यान्वयन कुछ हद तक कोचडीबी के मानचित्र-कम करने में परमाणुता मानता है। कोड अपडेट कर रहा है, इसलिए यह पर निर्भर नहीं है कि धारणा पाठक के लिए एक अभ्यास के रूप में छोड़ी गई है।
मास्टर/मास्टर प्रतिकृति या कॉच डीबी के दस्तावेज़ सिंक को विचार में नहीं लिया गया है। मास्टर/मास्टर प्रतिकृति और सिंक इस समस्या को को और अधिक कठिन बनाते हैं।
वास्तविक प्रणाली में, time()
का उपयोग करके टकराव हो सकता है, इसलिए का उपयोग थोड़ा और एन्ट्रॉपी के साथ कुछ अच्छा विचार हो सकता है; शायद "%s-%s" %(time(), uuid())
, या ऑर्डरिंग में दस्तावेज़ के _id
का उपयोग कर। समय सहित सख्ती से जरूरी नहीं है, लेकिन यह एक लॉजिकल को बनाए रखने में मदद करता है यदि एकाधिक अनुरोध एक ही समय में आते हैं।
CouchDB, की-वैल्यू नहीं है, यह एक दस्तावेज की दुकान है। – OrangeDog