8.1 รหัสธุรกรรม
กำหนดเป็น XID หรือ TxID (หากมีความแตกต่าง โปรดบอกฉัน) การประทับเวลาสามารถใช้เป็น TxID ซึ่งสามารถเล่นได้หากเราต้องการคืนค่าการกระทำทั้งหมดในช่วงเวลาหนึ่ง ปัญหาอาจเกิดขึ้นได้หากการประทับเวลาไม่ละเอียดพอ - การทำธุรกรรมจะได้รับ ID เดียวกัน
ดังนั้น ตัวเลือกที่น่าเชื่อถือที่สุดคือการสร้าง UUID prod ID ที่ไม่ซ้ำใคร ใน Python มันง่ายมาก:
>>> import uuid
>>> str(uuid.uuid4())
'f50ec0b7-f960-400d-91f0-c42a6d44e3d0'
>>> str(uuid.uuid4())
'd15bed89-c0a5-4a72-98d9-5507ea7bc0ba'
นอกจากนี้ยังมีตัวเลือกในการแฮชชุดข้อมูลที่กำหนดธุรกรรมและใช้แฮชนี้เป็น TxID
8.2 ลองใหม่
หากเรารู้ว่าฟังก์ชันหรือโปรแกรมบางอย่างเป็น idempotent นั่นหมายความว่าเราสามารถและควรลองเรียกใช้ซ้ำในกรณีที่เกิดข้อผิดพลาด และเราต้องเตรียมพร้อมสำหรับข้อเท็จจริงที่ว่าการดำเนินการบางอย่างจะทำให้เกิดข้อผิดพลาด เนื่องจากแอปพลิเคชันสมัยใหม่มีการแจกจ่ายผ่านเครือข่ายและฮาร์ดแวร์ ข้อผิดพลาดไม่ควรถือเป็นข้อยกเว้น แต่เป็นบรรทัดฐาน ข้อผิดพลาดอาจเกิดขึ้นเนื่องจากเซิร์ฟเวอร์ขัดข้อง ข้อผิดพลาดของเครือข่าย ความแออัดของแอปพลิเคชันระยะไกล แอปพลิเคชันของเราควรทำงานอย่างไร ถูกต้องลองดำเนินการซ้ำ
เนื่องจากโค้ดหนึ่งชิ้นสามารถพูดได้มากกว่าคำทั้งหน้า ลองใช้ตัวอย่างหนึ่งเพื่อทำความเข้าใจว่ากลไกการลองซ้ำแบบไร้เดียงสาควรทำงานอย่างไร ฉันจะสาธิตสิ่งนี้โดยใช้ไลบรารี Tenacity (มันออกแบบมาอย่างดี แม้ว่าคุณจะไม่ได้วางแผนที่จะใช้ก็ตาม ตัวอย่างควรแสดงให้คุณเห็นว่าคุณสามารถออกแบบกลไกการเกิดซ้ำได้อย่างไร):
import logging
import random
import sys
from tenacity import retry, stop_after_attempt, stop_after_delay, wait_exponential, retry_if_exception_type, before_log
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
logger = logging.getLogger(__name__)
@retry(
stop=(stop_after_delay(10) | stop_after_attempt(5)),
wait=wait_exponential(multiplier=1, min=4, max=10),
retry=retry_if_exception_type(IOError),
before=before_log(logger, logging.DEBUG)
)
def do_something_unreliable():
if random.randint(0, 10) > 1:
raise IOError("Broken sauce, everything is hosed!!!111one")
else:
return "Awesome sauce!"
print(do_something_unreliable.retry.statistics)
> ในกรณีที่ฉันจะพูดว่า: \@retry(...) เป็นไวยากรณ์ Python พิเศษที่เรียกว่า "มัณฑนากร" เป็นเพียงฟังก์ชัน retry(...) ที่ล้อมรอบฟังก์ชันอื่นและทำบางอย่างก่อนหรือหลังดำเนินการ
อย่างที่เราเห็น การลองใหม่สามารถออกแบบได้อย่างสร้างสรรค์:
- คุณสามารถจำกัดความพยายามตามเวลา (10 วินาที) หรือจำนวนครั้งที่พยายาม (5)
- สามารถเป็นเลขชี้กำลังได้ (นั่นคือ 2 ** จำนวนที่เพิ่มขึ้น n ) หรือด้วยวิธีอื่น (เช่น แก้ไข) เพื่อเพิ่มเวลาระหว่างความพยายามที่แยกกัน ตัวแปรเลขชี้กำลังเรียกว่า "ความแออัดยุบ"
- คุณสามารถลองใหม่ได้เฉพาะข้อผิดพลาดบางประเภทเท่านั้น (IOError)
- ความพยายามในการลองใหม่สามารถดำเนินการก่อนหน้าหรือเสร็จสิ้นโดยรายการพิเศษบางรายการในบันทึก
ตอนนี้เราได้จบหลักสูตรนักสู้รุ่นเยาว์และรู้ถึงองค์ประกอบพื้นฐานที่เราต้องใช้ในการทำงานกับธุรกรรมในฝั่งแอปพลิเคชันแล้ว เรามาทำความรู้จักกับสองวิธีที่ช่วยให้เราทำธุรกรรมในระบบกระจายได้
8.3 เครื่องมือขั้นสูงสำหรับผู้ชื่นชอบการทำธุรกรรม
ฉันจะให้คำจำกัดความที่ค่อนข้างกว้างเท่านั้นเนื่องจากหัวข้อนี้มีค่าควรแก่บทความขนาดใหญ่แยกต่างหาก
การกระทำสองเฟส (2pc) . 2pc มีสองขั้นตอน: ขั้นตอนการเตรียมการและขั้นตอนการส่ง ในระหว่างขั้นตอนเตรียมการ ไมโครเซอร์วิสทั้งหมดจะถูกขอให้เตรียมพร้อมสำหรับการเปลี่ยนแปลงข้อมูลบางอย่างที่สามารถทำได้ในระดับปรมาณู เมื่อพร้อมแล้ว ขั้นตอนการคอมมิตจะทำการเปลี่ยนแปลงจริง ในการประสานงานกระบวนการนั้นจำเป็นต้องมีผู้ประสานงานส่วนกลางซึ่งจะล็อคออบเจ็กต์ที่จำเป็น - นั่นคือไม่สามารถเข้าถึงการเปลี่ยนแปลงได้จนกว่าผู้ประสานงานจะปลดล็อค หากไมโครเซอร์วิสใดไม่พร้อมสำหรับการเปลี่ยนแปลง (เช่น ไม่ตอบสนอง) ผู้ประสานงานจะยกเลิกธุรกรรมและเริ่มกระบวนการย้อนกลับ
ทำไมโปรโตคอลนี้ถึงดี? มันให้ปรมาณู นอกจากนี้ยังรับประกันความโดดเดี่ยวเมื่อเขียนและอ่าน ซึ่งหมายความว่าการเปลี่ยนแปลงในธุรกรรมหนึ่งจะไม่ปรากฏแก่ผู้อื่นจนกว่าผู้ประสานงานจะยอมรับการเปลี่ยนแปลง แต่คุณสมบัติเหล่านี้ก็มีข้อเสียเช่นกัน เนื่องจากโปรโตคอลนี้เป็นแบบซิงโครนัส (การบล็อก) จึงทำให้ระบบช้าลง (แม้ว่าการเรียก RPC จะค่อนข้างช้าก็ตาม) และอีกครั้งมีอันตรายจากการปิดกั้นซึ่งกันและกัน
ซากะ ในรูปแบบนี้ ธุรกรรมแบบกระจายจะดำเนินการโดยธุรกรรมแบบโลคัลแบบอะซิงโครนัสในไมโครเซอร์วิสที่เกี่ยวข้องทั้งหมด Microservices สื่อสารระหว่างกันผ่านบัสเหตุการณ์ หากไมโครเซอร์วิสใดทำธุรกรรมภายในเครื่องไม่สำเร็จ ไมโครเซอร์วิสอื่น ๆ จะทำธุรกรรมชดเชยเพื่อย้อนกลับการเปลี่ยนแปลง
ข้อได้เปรียบของ Saga คือไม่มีสิ่งกีดขวาง แต่แน่นอนว่ามีข้อเสีย
Saga ยากที่จะดีบัก โดยเฉพาะอย่างยิ่งเมื่อมีไมโครเซอร์วิสจำนวนมากที่เกี่ยวข้อง ข้อเสียอีกประการของรูปแบบ Saga คือขาดการแยกการอ่าน นั่นคือหากคุณสมบัติที่ระบุใน ACID มีความสำคัญสำหรับเรา Saga ก็ไม่เหมาะกับเรามากนัก
เราเห็นอะไรจากคำอธิบายของเทคนิคทั้งสองนี้ ข้อเท็จจริงที่ว่าในระบบแบบกระจาย ความรับผิดชอบสำหรับความเป็นปรมาณูและการแยกตัวขึ้นอยู่กับแอปพลิเคชัน สิ่งเดียวกันนี้เกิดขึ้นเมื่อใช้ฐานข้อมูลที่ไม่รับประกันกรด นั่นคือ การแก้ไขความขัดแย้ง การย้อนกลับ การคอมมิต และการเพิ่มพื้นที่ว่างจะตกอยู่บนบ่าของนักพัฒนา
8.4 ฉันจะรู้ได้อย่างไรว่าเมื่อใดที่ฉันต้องการการรับประกันกรด
เมื่อมีความเป็นไปได้สูงที่ผู้ใช้หรือกระบวนการบางกลุ่มจะทำงานพร้อมกันบนข้อมูลเดียวกัน
ขออภัยในความซ้ำซากจำเจ แต่ตัวอย่างทั่วไปคือการทำธุรกรรมทางการเงิน
เมื่อลำดับการทำธุรกรรมมีความสำคัญ
ลองจินตนาการว่าบริษัทของคุณกำลังจะเปลี่ยนจาก Messenger ของ FunnyYellowChat เป็น Messenger ของ FunnyRedChat เนื่องจาก FunnyRedChat ให้คุณส่ง gif ได้ แต่ FunnyYellowChat ไม่สามารถทำได้ แต่คุณไม่เพียงแค่เปลี่ยนผู้ส่งสาร - คุณกำลังย้ายข้อมูลการติดต่อของบริษัทของคุณจากผู้ส่งสารคนหนึ่งไปยังอีกที่หนึ่ง คุณทำเช่นนี้เพราะโปรแกรมเมอร์ของคุณขี้เกียจเกินไปที่จะจัดทำเอกสารโปรแกรมและกระบวนการไว้ที่ส่วนกลาง และแทนที่จะเผยแพร่ทุกอย่างในช่องต่างๆ ใน Messenger ใช่ และพนักงานขายของคุณเผยแพร่รายละเอียดของการเจรจาและข้อตกลงในที่เดียวกัน กล่าวโดยสรุปคือ ทั้งชีวิตในบริษัทของคุณอยู่ที่นั่น และเนื่องจากไม่มีใครมีเวลาที่จะถ่ายโอนสิ่งทั้งหมดไปยังบริการสำหรับเอกสาร และการค้นหาโปรแกรมส่งข้อความโต้ตอบแบบทันทีได้ผลดี คุณตัดสินใจแทนที่จะล้างเศษขยะเพื่อคัดลอกทั้งหมด ข้อความไปยังตำแหน่งใหม่ ลำดับของข้อความมีความสำคัญ
โดยวิธีการสำหรับการโต้ตอบใน Messenger ลำดับโดยทั่วไปมีความสำคัญ แต่เมื่อคนสองคนเขียนบางสิ่งในการแชทเดียวกันในเวลาเดียวกัน โดยทั่วไปแล้วข้อความของใครจะปรากฏก่อนก็ไม่สำคัญ ดังนั้นสำหรับสถานการณ์เฉพาะนี้ กรดไม่จำเป็น
อีกตัวอย่างที่เป็นไปได้คือชีวสารสนเทศ ฉันไม่เข้าใจสิ่งนี้เลย แต่ฉันคิดว่าคำสั่งนั้นมีความสำคัญเมื่อถอดรหัสจีโนมมนุษย์ อย่างไรก็ตาม ฉันได้ยินมาว่าโดยทั่วไปแล้วนักชีวสารสนเทศศาสตร์ใช้เครื่องมือบางอย่างสำหรับทุกสิ่ง - บางทีพวกเขาอาจมีฐานข้อมูลของตัวเอง
เมื่อคุณไม่สามารถให้ผู้ใช้หรือประมวลผลข้อมูลเก่า
และอีกครั้ง - ธุรกรรมทางการเงิน พูดตามตรง ฉันไม่สามารถนึกถึงตัวอย่างอื่นได้
เมื่อธุรกรรมที่รอดำเนินการเกี่ยวข้องกับต้นทุนที่สำคัญ ลองนึกภาพปัญหาที่อาจเกิดขึ้นเมื่อแพทย์และพยาบาลอัปเดตบันทึกผู้ป่วยและลบการเปลี่ยนแปลงของกันและกันในเวลาเดียวกัน เนื่องจากฐานข้อมูลไม่สามารถแยกธุรกรรมได้ ระบบการรักษาพยาบาลเป็นอีกพื้นที่หนึ่ง นอกเหนือจากการเงิน ซึ่งการรับประกันกรดมักจะมีความสำคัญ
8.5 เมื่อใดที่ฉันไม่ต้องการกรด
เมื่อผู้ใช้อัปเดตข้อมูลส่วนตัวเพียงบางส่วนเท่านั้น
ตัวอย่างเช่น ผู้ใช้แสดงความคิดเห็นหรือกระดาษโน้ตบนหน้าเว็บ หรือแก้ไขข้อมูลส่วนตัวในบัญชีส่วนตัวกับผู้ให้บริการใดๆ
เมื่อผู้ใช้ไม่อัปเดตข้อมูลเลย แต่เสริมด้วยข้อมูลใหม่เท่านั้น (ต่อท้าย)
ตัวอย่างเช่น แอปพลิเคชันการวิ่งที่บันทึกข้อมูลการวิ่งของคุณ: คุณวิ่งไปเท่าไร เวลาใด เส้นทาง ฯลฯ การเรียกใช้ใหม่แต่ละครั้งเป็นข้อมูลใหม่ และรายการเก่าจะไม่ถูกแก้ไขเลย บางที อิงจากข้อมูล คุณจะได้รับการวิเคราะห์ - และฐานข้อมูล NoSQL เท่านั้นที่ดีสำหรับสถานการณ์นี้
เมื่อตรรกะทางธุรกิจไม่ได้กำหนดความต้องการสำหรับคำสั่งบางอย่างในการทำธุรกรรม
อาจเป็นไปได้ว่าสำหรับบล็อกเกอร์ Youtube ที่รวบรวมเงินบริจาคสำหรับการผลิตเนื้อหาใหม่ระหว่างการถ่ายทอดสดครั้งต่อไปไม่สำคัญว่าใครจะโยนเงินให้เขาเมื่อใดและในลำดับใด
เมื่อผู้ใช้จะอยู่บนหน้าเว็บหรือหน้าต่างแอปพลิเคชันเดียวกันเป็นเวลาหลายวินาทีหรือแม้แต่นาที ดังนั้นพวกเขาจะเห็นข้อมูลเก่า
ตามทฤษฎีแล้วสิ่งเหล่านี้คือสื่อข่าวออนไลน์หรือ Youtube เดียวกัน หรือ "ฮับ" เมื่อไม่สำคัญสำหรับคุณว่าธุรกรรมที่ไม่สมบูรณ์สามารถถูกเก็บไว้ในระบบชั่วคราว คุณสามารถเพิกเฉยได้โดยไม่มีความเสียหายใดๆ
หากคุณกำลังรวบรวมข้อมูลจากหลายแหล่งและข้อมูลที่อัปเดตด้วยความถี่สูง ตัวอย่างเช่น ข้อมูลจำนวนที่จอดรถในเมืองที่เปลี่ยนแปลงอย่างน้อยทุกๆ 5 นาที ในทางทฤษฎีแล้ว มันจะไม่ใช่ปัญหาใหญ่ สำหรับคุณหาก ณ จุดหนึ่งการทำธุรกรรมสำหรับหนึ่งในที่จอดรถจะไม่ผ่าน แม้ว่าจะขึ้นอยู่กับว่าคุณต้องการทำอะไรกับข้อมูลนี้