7.1 Apa perlu
Kita wis ngrembug kanthi rinci kabeh sifat ACID, tujuane lan kasus panggunaan. Kaya sing sampeyan ngerteni, ora kabeh database menehi jaminan ACID, ngorbanake kanggo kinerja sing luwih apik. Mulane, iku uga kelakon sing database sing ora kurban ACID dipilih ing project, lan sampeyan bisa uga kudu ngleksanakake sawetara saka fungsi ACID perlu ing sisih aplikasi. Lan yen sistem sampeyan dirancang minangka layanan mikro, utawa sawetara jinis aplikasi sing disebarake, apa sing bakal dadi transaksi lokal normal ing siji layanan saiki bakal dadi transaksi sing disebarake - lan, mesthi, bakal kelangan sifat ACID, sanajan database saben microservice individu bakal ACID.
Aku ora pengin menehi sampeyan pandhuan lengkap babagan carane nggawe manajer transaksi, mung amarga amba banget lan rumit, lan aku mung pengin nutupi sawetara teknik dhasar. Yen kita ora ngomong babagan aplikasi sing disebarake, mula aku ora weruh alesan kanggo nyoba ngetrapake ACID ing sisih aplikasi yen sampeyan butuh jaminan ACID - sawise kabeh, bakal luwih gampang lan luwih murah kanggo njupuk solusi sing wis siap ( yaiku, database karo ACID).
Nanging aku kaya kanggo nuduhake sawetara Techniques sing bakal mbantu ing nggawe transaksi ing sisih aplikasi. Sawise kabeh, ngerti teknik kasebut bisa mbantu sampeyan ing macem-macem skenario, malah sing ora kudu melu transaksi, lan nggawe sampeyan pangembang sing luwih apik (Muga-muga).
7.2 Piranti dhasar kanggo penyayang transaksi
Pamblokiran optimistis lan pesimis. Iki rong jinis kunci ing sawetara data sing bisa diakses bebarengan.
Optimisnganggep yen kemungkinan akses bebarengan ora dadi gedhe, lan mulane nindakake ing ngisor iki: maca baris sing dikarepake, ngelingi nomer versi (utawa timestamp, utawa checksum / hash - yen sampeyan ora bisa ngganti skema data lan nambah kolom kanggo versi). utawa timestamp), lan sadurunge nulis owah-owahan ing database kanggo data iki, mriksa yen versi data iki wis diganti. Yen versi wis diganti, sampeyan kudu piye wae mutusake masalah konflik digawe lan nganyari data ("komit"), utawa muter maneh transaksi ("rollback"). Kerugian metode iki yaiku nggawe kahanan sing cocog kanggo bug kanthi jeneng sing dawa "wektu-saka-mriksa kanggo wektu-nggunakake", dicekak TOCTOU: negara bisa ngganti ing periode wektu antarane mriksa lan nulis. Aku ora duwe pengalaman ngunci optimistis,
Minangka conto, aku nemokake siji teknologi saka urip saben pangembang sing nggunakake kaya kunci optimistis - iki protokol HTTP. Tanggepan kanggo panjalukan HTTP GET awal MUNGKIN kalebu header ETag kanggo panjalukan PUT sakteruse saka klien, sing bisa digunakake klien ing header If-Match. Kanggo metode GET lan HEAD, server bakal ngirim maneh sumber daya sing dijaluk mung yen cocog karo salah sawijining ETags sing dingerteni. Kanggo PUT lan cara ora aman liyane, mung bakal mbukak sumber daya ing kasus iki uga. Yen sampeyan ora ngerti cara kerja ETag, iki conto sing apik nggunakake perpustakaan "feedparser" (sing mbantu ngurai RSS lan feed liyane).
>>> import feedparser
>>> d = feedparser.parse('http://feedparser.org/docs/examples/atom10.xml')
>>> d.etag
'"6c132-941-ad7e3080"'
>>> d2 = feedparser.parse('http://feedparser.org/docs/examples/atom10.xml', etag=d.etag)
>>> d2.feed
{}
>>> d2.debug_message
'The feed has not changed since you last checked, so the server sent no data. This is a feature, not a bug!'
Pesimis, ing tangan liyane, neruske saka kasunyatan sing transaksi bakal kerep "ketemu" ing data padha, lan kanggo nyederhanakake urip lan ngindhari kahanan balapan sing ora perlu, dheweke mung ngalangi data sing dibutuhake. Kanggo ngleksanakake mekanisme ngunci, sampeyan kudu njaga sambungan basis data kanggo sesi sampeyan (tinimbang narik sambungan saka blumbang - ing kasus iki, sampeyan bakal kudu nggarap kunci optimistis), utawa nggunakake ID kanggo transaksi. , sing bisa digunakake preduli saka sambungan. Kerugian ngunci pesimis yaiku panggunaane nyuda proses transaksi ing umum, nanging sampeyan bisa tenang babagan data lan entuk isolasi nyata.
Bebaya tambahan, Nanging, lurks ing deadlock bisa, kang sawetara pangolahan ngenteni sumber dikunci dening saben liyane. Contone, transaksi mbutuhake sumber daya A lan B. Proses 1 wis dikuwasani sumber daya A, lan proses 2 wis dikuwasani sumber B. Ora ana loro pangolahan bisa nerusake eksekusi. Ana macem-macem cara kanggo ngatasi masalah iki - Aku ora pengin mbukak rincian saiki, mula maca Wikipedia dhisik, nanging ing cendhak, ana kemungkinan nggawe hirarki kunci. Yen sampeyan pengin ngerti konsep iki kanthi luwih rinci, sampeyan bakal diundang kanggo mikir babagan "Masalah Filsuf Dining" ("Masalah Filsuf Dining").
Iki minangka conto sing apik babagan carane loro kunci bakal tumindak ing skenario sing padha.
Babagan implementasi kunci. Aku ora pengin pindhah menyang rincian, nanging ana Managers kunci kanggo sistem mbagekke, contone: ZooKeeper, Redis, etcd, Konsul.
7.3 Idempotensi saka operasi
Kode idempotent umume laku apik, lan iki persis nalika iku bakal apik kanggo pangembang bisa nindakake iki, preduli saka apa kang nggunakake transaksi utawa ora. Idempotensi minangka properti saka operasi kanggo ngasilake asil sing padha nalika operasi kasebut ditrapake maneh ing obyek. Fungsi kasebut diarani - menehi asil. Ditelpon maneh sawise detik utawa lima - menehi asil sing padha. Mesthi, yen data ing database wis diganti, asil bakal beda. Data ing sistem katelu bisa uga ora gumantung ing fungsi, nanging apa wae sing ditindakake kudu bisa ditebak.
Ana sawetara manifestasi saka idempotensi. Salah sijine mung menehi saran babagan carane nulis kode sampeyan. Apa sampeyan ngelingi yen fungsi sing paling apik yaiku sing nindakake siji perkara? Lan apa sing bakal dadi apik kanggo nulis tes unit kanggo fungsi iki? Yen sampeyan netepi rong aturan kasebut, sampeyan wis nambah kemungkinan fungsi sampeyan bakal dadi idempoten. Kanggo ngindhari kebingungan, aku bakal njlentrehake manawa fungsi idempoten ora kudu "murni" (ing arti "fungsi kemurnian"). Fungsi murni yaiku fungsi sing mung beroperasi ing data sing ditampa ing input, tanpa ngganti kanthi cara apa wae lan ngasilake asil sing diproses. Iki minangka fungsi sing ngidini sampeyan nggedhekake aplikasi kanthi nggunakake teknik pemrograman fungsional. Amarga kita ngomong babagan sawetara data umum lan database, fungsi kita ora bisa murni,
Iki minangka fungsi murni:
def square(num: int) -> int:
return num * num
Nanging fungsi iki ora murni, nanging idempoten (aja nggawe kesimpulan babagan cara nulis kode saka potongan kasebut):
def insert_data(insert_query: str, db_connection: DbConnectionType) -> int:
db_connection.execute(insert_query)
return True
Tinimbang akeh tembung, aku mung bisa ngomong babagan carane aku dipeksa sinau carane nulis program idempoten. Aku nindakake akeh karya karo AWS, kaya sing sampeyan deleng saiki, lan ana layanan sing diarani AWS Lambda. Lambda ngidini sampeyan ora ngurus server, nanging mung mbukak kode sing bakal ditindakake kanggo nanggepi sawetara acara utawa miturut jadwal. Acara bisa dadi pesen sing dikirim dening broker pesen. Ing AWS, broker iki yaiku AWS SNS. Aku mikir sing iki kudu cetha malah kanggo wong-wong sing ora bisa karo AWS: kita duwe makelar sing ngirim pesen liwat saluran ("topik"), lan microservices sing langganan saluran iki nampa pesen lan piye wae nanggepi.
Masalahe yaiku SNS ngirim pesen "paling ora sapisan" ("paling ora-sapisan kiriman"). Iki artine apa? Sing cepet utawa mengko kode Lambda bakal disebut kaping pindho. Lan pancen kedadeyan. Ana sawetara skenario ing ngendi fungsi sampeyan kudu idempoten: contone, nalika dhuwit ditarik saka akun, kita bisa nyana wong mbatalake jumlah sing padha kaping pindho, nanging kita kudu mesthekake yen iki pancen 2 kaping independen - ing tembung liyane, iki 2 transaksi beda, lan ora Ambalan siji.
Kanggo ngganti, aku bakal menehi conto liyane - matesi frekuensi panjalukan kanggo API ("rate matesi"). Lambda kita nampa acara karo user_id tartamtu sing kudu dipriksa manawa pangguna sing nganggo ID kasebut wis kesel sawetara panjaluk sing bisa ditindakake menyang sawetara API kita. Kita bisa nyimpen ing DynamoDB saka AWS nilai panggilan sing digawe, lan nambah saben telpon menyang fungsi kita kanthi 1.
Nanging kepiye yen fungsi Lambda iki diarani acara sing padha kaping pindho? Miturut cara, apa sampeyan mbayar manungsa waé kanggo bantahan saka lambda_handler () fungsi. Argumentasi kapindho, konteks ing AWS Lambda diwenehake kanthi standar lan ngemot macem-macem metadata, kalebu request_id sing digawe kanggo saben telpon unik. Iki tegese saiki, tinimbang nyimpen nomer telpon sing digawe ing tabel, kita bisa nyimpen dhaptar request_id lan ing saben telpon, Lambda bakal mriksa yen panjalukan sing diwenehake wis diproses:
import json
import os
from typing import Any, Dict
from aws_lambda_powertools.utilities.typing import LambdaContext # needed only for argument type annotation
import boto3
limit = os.getenv('LIMIT')
def handler_name(event: Dict[str: Any], context: LambdaContext):
request_id = context.aws_request_id
# We find user_id in incoming event
user_id = event["user_id"]
# Our table for DynamoDB
table = boto3.resource('dynamodb').Table('my_table')
# Doing update
table.update_item(
Key={'pkey': user_id},
UpdateExpression='ADD requests :request_id',
ConditionExpression='attribute_not_exists (requests) OR (size(requests) < :limit AND NOT contains(requests, :request_id))',
ExpressionAttributeValues={
':request_id': {'S': request_id},
':requests': {'SS': [request_id]},
':limit': {'N': limit}
}
)
# TODO: write further logic
return {
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": json.dumps({
"status ": "success"
})
}
Wiwit contoku bener-bener dijupuk saka Internet, aku bakal ninggalake link menyang sumber asli, utamane amarga menehi informasi luwih akeh.
Elinga carane aku kasebut sadurunge yen kaya ID transaksi unik bisa digunakake kanggo ngunci data sing dienggo bareng? Saiki kita wis sinau manawa uga bisa digunakake kanggo nggawe operasi idempoten. Ayo goleki carane sampeyan bisa nggawe ID kasebut dhewe.