ฉันควรทำอย่างไรเมื่อการล็อคในแง่ดีไม่ทำงาน


11

ฉันมีสถานการณ์ต่อไปนี้:

  1. ผู้ใช้ทำให้GETคำขอไป/projects/1และได้รับการETag
  2. ผู้ใช้ทำการร้องขอPUT/projects/1กับ ETag จากขั้นตอน # 1
  3. ผู้ใช้ทำการร้องขอ PUT อื่น/projects/1โดยใช้ ETag จากขั้นตอนที่ # 1

โดยปกติคำขอ PUT ที่สองจะได้รับการตอบสนอง 412 เนื่องจาก ETag ตอนนี้ค้าง - คำขอ PUT แรกแก้ไขทรัพยากรดังนั้น ETag จึงไม่ตรงกันอีกต่อไป

แต่จะเกิดอะไรขึ้นถ้าคำขอ PUT ทั้งสองนั้นถูกส่งในเวลาเดียวกัน คำขอ PUT แรกไม่มีเวลาในการประมวลผลและอัปเดตทรัพยากรก่อน PUT # 2 มาถึงซึ่งทำให้ PUT # 2 เขียนทับ PUT # 1 จุดรวมของการล็อคในแง่ดีคือการที่จะไม่เกิดขึ้น ...


3
ทำให้การดำเนินงานของคุณเป็นละอองในธุรกรรมระดับธุรกิจดังที่ Esben อธิบายไว้ด้านล่าง
Robert Harvey

จะเกิดอะไรขึ้นหากฉันทำให้การปฏิบัติการของฉันเป็นละอองโดยใช้ธุรกรรม PUT # 2 จะไม่ถูกประมวลผลจนกว่า PUT # 1 จะถูกประมวลผลอย่างสมบูรณ์หรือไม่
maximedupre

7
กลายเป็นคนมองโลกในแง่ร้าย?
jpmc26

นี่คือสิ่งที่ล็อคไว้สำหรับ
Fattie

ถูกต้องแน่นอนว่า # 2 ไม่ควรถูกประมวลผลซึ่งควรจะไม่ซ้ำกัน
Fattie

คำตอบ:


21

กลไก ETag ระบุเฉพาะโปรโตคอลการสื่อสารสำหรับการล็อคในแง่ดีที่สุด มันเป็นความรับผิดชอบของบริการแอปพลิเคชันที่จะใช้กลไกในการตรวจสอบการปรับปรุงพร้อมกันเพื่อบังคับใช้การล็อคในแง่ดี

ในแอปพลิเคชันทั่วไปที่ใช้ฐานข้อมูลคุณมักจะทำสิ่งนี้โดยเปิดธุรกรรมเมื่อประมวลผลคำขอ PUT ตามปกติคุณจะอ่านสถานะที่มีอยู่ของฐานข้อมูลภายในธุรกรรมนั้น (เพื่อให้ได้การล็อกการอ่าน) ตรวจสอบความถูกต้องของ Etag ของคุณและเขียนทับข้อมูล (ในลักษณะที่จะทำให้เกิดข้อขัดแย้งในการเขียนเมื่อมีธุรกรรมที่เกิดขึ้นพร้อมกัน จากนั้นกระทำ หากคุณตั้งค่าการทำธุรกรรมอย่างถูกต้องแล้วหนึ่งในข้อผูกพันควรล้มเหลวเพราะทั้งคู่พยายามอัปเดตข้อมูลเดียวกันพร้อมกัน จากนั้นคุณจะสามารถใช้ธุรกรรมนี้ล้มเหลวในการส่งคืน 412 หรือลองส่งคำขออีกครั้งหากเหมาะสมสำหรับแอปพลิเคชัน


วิธีที่เซิร์ฟเวอร์ใช้กลไกในการตรวจหาการอัพเดทพร้อมกันคือการเปรียบเทียบแฮชของทรัพยากร เซิร์ฟเวอร์ใช้ธุรกรรมสำหรับการดำเนินการทั้งหมด แต่ฉันไม่ได้รับการล็อกใด ๆ ซึ่งอาจเป็นสาเหตุของปัญหา อย่างไรก็ตามในตัวอย่างของคุณจะมีข้อผิดพลาดในข้อผูกพันข้อใดข้อหนึ่งได้อย่างไรหากธุรกรรมกำลังใช้การล็อก? ธุรกรรมที่สองควรจะค้างอยู่เมื่ออ่านสถานะจนกว่าธุรกรรมแรกจะได้รับการแก้ไข
maximedupre

1
@maximedupre: หากคุณใช้ทรานแซคชันคุณมีการล็อคบางประเภทแม้ว่ามันอาจจะเป็นการล็อคแบบนัย (การล็อคจะได้รับโดยอัตโนมัติเมื่อคุณอ่าน / อัพเดทฟิลด์แทนที่จะร้องขออย่างชัดเจน) กลไกที่ฉันอธิบายไว้ข้างต้นสามารถดำเนินการได้โดยใช้เพียงการล็อคโดยปริยาย ตามคำถามอื่น ๆ ของคุณขึ้นอยู่กับฐานข้อมูลที่คุณใช้ แต่ฐานข้อมูลที่ทันสมัยหลายแห่งใช้ MVCC (การควบคุมหลายรุ่นพร้อมกัน) เพื่อให้ผู้อ่านและนักเขียนหลายคนทำงานในสาขาเดียวกันโดยไม่จำเป็นต้องปิดกั้นกันและกัน
Lie Ryan

1
คำเตือน: ในหลาย DBMS (PostgreSQL, Oracle, SQL Server ฯลฯ ) ระดับการแยกธุรกรรมเริ่มต้นคือ "read มุ่งมั่น" ซึ่งวิธีการของคุณไม่เพียงพอที่จะป้องกันสภาวะการแข่งขันของ OP ใน DMBSes ดังกล่าวคุณสามารถแก้ไขได้โดยรวมAND ETag = ...ไว้ในส่วนUPDATEคำสั่งของคุณWHEREและตรวจสอบการนับแถวที่อัปเดตหลังจากนั้น (หรือโดยใช้ระดับการแยกธุรกรรมที่เข้มงวดขึ้น แต่ฉันไม่แนะนำให้ทำเช่นนั้น)
ruakh

1
@ruakh: ขึ้นอยู่กับวิธีที่คุณเขียนแบบสอบถามใช่ระดับการแยกเริ่มต้นไม่ได้มีพฤติกรรมดังกล่าวโดยอัตโนมัติสำหรับการค้นหาทั้งหมด แต่บ่อยครั้งที่เป็นไปได้ที่จะจัดโครงสร้างธุรกรรมของคุณในลักษณะที่เพียงพอที่จะใช้การล็อคในแง่ดี ในกรณีส่วนใหญ่หากความสอดคล้องของการทำธุรกรรมมีความสำคัญในแอปพลิเคชันฉันขอแนะนำให้อ่านซ้ำเป็นระดับการแยกเริ่มต้นอย่างไรก็ตาม ในฐานข้อมูลที่ใช้ MVCC ค่าใช้จ่ายในการอ่านซ้ำ ๆ นั้นค่อนข้างน้อยและทำให้แอปพลิเคชันง่ายขึ้นอย่างมาก
โกหกไรอัน

1
@ruakh: ข้อเสียเปรียบหลักของการอ่านที่ทำซ้ำได้คือคุณจะต้องเตรียมพร้อมเพื่อลองใหม่หรือล้มเหลวหากมีการทำธุรกรรมพร้อมกัน นี่เป็นปัญหาปกติ แต่แอปพลิเคชันที่ให้การล็อก Optimistic เป็นกลยุทธ์การทำงานพร้อมกันจะต้องมีการจัดการนี้อยู่แล้วดังนั้นแผนที่ความล้มเหลวในการอ่านที่ทำซ้ำได้ซ้ำตามธรรมชาติเพื่อความล้มเหลวในการล็อคในแง่ดีและจะไม่เพิ่มข้อเสียใหม่
โกหกไรอัน

13

คุณต้องดำเนินการคู่ต่อไปนี้แบบอะตอม:

  • การตรวจสอบความถูกต้องของแท็ก (เช่นล่าสุด)
  • อัปเดตทรัพยากร (ซึ่งรวมถึงการอัปเดตแท็กของมัน)

คนอื่น ๆ เรียกสิ่งนี้ว่าการทำธุรกรรม - แต่โดยพื้นฐานแล้วการดำเนินการปรมาณูของการดำเนินการทั้งสองนี้เป็นสิ่งที่ป้องกันไม่ให้เกิดการเขียนทับอีกอันหนึ่งโดยบังเอิญเมื่อถึงเวลา หากปราศจากสิ่งนี้คุณก็จะมีสภาพการแข่งขันเหมือนที่คุณสังเกต

นี่ยังถือว่าเป็นการล็อกในแง่ดีหากคุณดูภาพรวม: ทรัพยากรนั้นไม่ได้ถูกล็อคโดยการอ่านครั้งแรก (GET) โดยผู้ใช้หรือผู้ใช้ใด ๆ ที่กำลังดูข้อมูลไม่ว่าจะมีเจตนาที่จะอัพเดทหรือไม่

พฤติกรรมแบบอะตอมมิกบางอย่างมีความจำเป็น แต่สิ่งนี้เกิดขึ้นภายในคำขอเดียว (PUT) แทนที่จะพยายามล็อกการเชื่อมต่อเครือข่ายหลายครั้ง นี่คือการล็อคในแง่ดี: วัตถุไม่ได้ถูกล็อคโดย GET แต่ยังคงสามารถปรับปรุงได้อย่างปลอดภัยโดย PUT

นอกจากนี้ยังมีหลายวิธีในการบรรลุการเรียกใช้งานอะตอมมิกของการดำเนินการทั้งสองนี้ - การล็อกทรัพยากรไม่ใช่ตัวเลือกเดียว ตัวอย่างเช่นเธรดที่มีน้ำหนักเบาหรือล็อควัตถุอาจพอเพียงและขึ้นอยู่กับสถาปัตยกรรมของแอปพลิเคชันและบริบทการดำเนินการของคุณ


4
+1 สำหรับการสังเกตว่ามันเป็นอะตอมที่สำคัญ สามารถทำได้โดยไม่ต้องทำธุรกรรมหรือล็อค ตัวอย่างเช่นการเปรียบเทียบและแลกเปลี่ยนอะตอมของทรัพยากรในหน่วยความจำหรือการจัดหาเหตุการณ์ของข้อมูลที่เก็บไว้
Aaron M. Eshbach

@ AaronM.Eshbach เห็นด้วยและขอบคุณสำหรับการโทรออกเหล่านั้น
Erik Eidt

1

เป็นผู้พัฒนาแอปพลิเคชันเพื่อตรวจสอบ E-Tag และระบุตรรกะนั้น ไม่ใช่เรื่องวิเศษที่เว็บเซิร์ฟเวอร์ทำเพื่อคุณเพราะรู้เพียงวิธีการคำนวณE-Tagส่วนหัวสำหรับเนื้อหาคงที่ ดังนั้นเราจะนำสถานการณ์ของคุณข้างต้นมาพิจารณาว่าการโต้ตอบควรเกิดขึ้นได้อย่างไร

GET /projects/1

เซิร์ฟเวอร์ได้รับการร้องขอกำหนด E-Tag สำหรับบันทึกรุ่นนี้ส่งคืนพร้อมเนื้อหาจริง

200 - OK
E-Tag: "412"
Content-Type: application/json
{modified: false}

เนื่องจากไคลเอ็นต์มีค่า E-Tag ในขณะนี้จึงสามารถรวมไว้ในPUTคำขอได้:

PUT /projects/1
If-Match: "412"
Content-Type: application/json
{modified: true}

ณ จุดนี้แอปพลิเคชันของคุณจะต้องทำสิ่งต่อไปนี้:

  • ตรวจสอบว่า E-Tag ยังคงถูกต้อง: "412" == "412"?
  • ถ้าเป็นเช่นนั้นทำการอัปเดตและคำนวณ E-Tag ใหม่

ส่งคำตอบสำเร็จ

204 No Content
E-Tag: "543"

หากมีคำขออื่นมาและพยายามดำเนินการPUTคล้ายกับคำขอด้านบนครั้งที่สองที่รหัสเซิร์ฟเวอร์ของคุณทำการประเมินคุณจะต้องรับผิดชอบต่อข้อความแสดงข้อผิดพลาด

  • ตรวจสอบว่า E-Tag ยังคงถูกต้อง: "412"! = "543"

เมื่อล้มเหลวส่งการตอบสนองความล้มเหลว

412 Precondition Failed

นี่คือรหัสที่คุณต้องเขียน อันที่จริง E-Tag สามารถเป็นข้อความใด ๆ (ภายในขีด จำกัด ที่กำหนดไว้ในข้อมูลจำเพาะ HTTP) ไม่จำเป็นต้องเป็นตัวเลข มันสามารถเป็นค่าแฮชได้เช่นกัน


นี่ไม่ใช่สัญกรณ์ HTTP มาตรฐานที่คุณใช้อยู่ที่นี่ ใน HTTP ที่เป็นไปตามมาตรฐานคุณใช้ ETag ในส่วนหัวการตอบกลับเท่านั้น คุณไม่เคยส่ง ETag ในส่วนหัวของคำขอ แต่ให้ใช้ค่า ETag ที่ได้รับมาก่อนหน้านี้ในส่วนหัว If-Match หรือ If-None-Match ในส่วนหัวของคำขอ
Lie Ryan

-2

ในฐานะที่เป็นส่วนเติมเต็มให้กับคำตอบอื่น ๆ ฉันจะโพสต์คำพูดที่ดีที่สุดในเอกสาร ZeroMQที่อธิบายถึงปัญหาพื้นฐานอย่างซื่อสัตย์:

เพื่อให้โปรแกรม MT สมบูรณ์แบบที่สุด (และฉันหมายถึงอย่างนั้น) เราไม่จำเป็นต้องปิดการใช้งาน mutexes ล็อคหรือรูปแบบอื่น ๆ ของการสื่อสารระหว่างเธรดยกเว้นข้อความที่ส่งผ่านซ็อกเก็ต ZeroMQ

โดย "โปรแกรม MT ที่สมบูรณ์แบบ" ฉันหมายถึงรหัสที่ง่ายต่อการเขียนและเข้าใจซึ่งทำงานด้วยวิธีการออกแบบเดียวกันในภาษาการเขียนโปรแกรมใด ๆ และในระบบปฏิบัติการใด ๆ และปรับขนาดของซีพียูจำนวนมากโดยไม่ต้องรอศูนย์เลย ของผลตอบแทนลดลง

หากคุณใช้เวลาหลายปีในการเรียนรู้เทคนิคเพื่อให้รหัส MT ของคุณทำงานได้อย่างรวดเร็วปล่อยให้อยู่คนเดียวอย่างรวดเร็วด้วยการล็อกและเซมาฟอเรสและส่วนที่สำคัญคุณจะเบื่อหน่ายเมื่อคุณรู้ว่ามันไม่มีค่าอะไรเลย หากมีหนึ่งบทเรียนที่เราได้เรียนรู้จากการเขียนโปรแกรมพร้อมกันมากกว่า 30 ปีนั่นก็คือ: อย่าแชร์รัฐ มันเหมือนคนขี้เมาสองคนพยายามจะแบ่งปันเบียร์ ไม่สำคัญว่าพวกเขาเป็นเพื่อนที่ดีหรือไม่ ไม่ช้าก็เร็วพวกเขาจะเข้าสู่การต่อสู้ และยิ่งขี้เมายิ่งเพิ่มลงในตารางยิ่งพวกเขาต่อสู้กันบนเบียร์มากขึ้นเท่านั้น แอพพลิเคชั่น MT ที่น่าเศร้าส่วนใหญ่ดูเหมือนจะเป็นการต่อสู้ที่เมาเหล้า

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.