วิธีป้องกันสภาวะการแข่งขันในเว็บแอปพลิเคชัน


31

พิจารณาเว็บไซต์อีคอมเมิร์ซที่ทั้ง Alice และ Bob กำลังแก้ไขรายการผลิตภัณฑ์ อลิซกำลังปรับปรุงคำอธิบายขณะที่ Bob กำลังอัพเดทราคา พวกเขาเริ่มแก้ไข Acme Wonder Widget ในเวลาเดียวกัน บ๊อบเสร็จสิ้นก่อนและบันทึกผลิตภัณฑ์ด้วยราคาใหม่ อลิซใช้เวลาในการอัปเดตคำอธิบายนานขึ้นและเมื่อเธอทำเสร็จเธอจะบันทึกผลิตภัณฑ์ด้วยคำอธิบายใหม่ของเธอ น่าเสียดายที่เธอเขียนทับราคาด้วยราคาเดิมซึ่งไม่ได้มีเจตนา

จากประสบการณ์ของฉันปัญหาเหล่านี้พบได้ทั่วไปในเว็บแอป ซอฟต์แวร์บางตัว (เช่นซอฟต์แวร์ wiki) มีการป้องกันสิ่งนี้ - โดยปกติการบันทึกครั้งที่สองจะล้มเหลวด้วย "หน้าได้รับการปรับปรุงในขณะที่คุณกำลังแก้ไข" แต่เว็บไซต์ส่วนใหญ่ไม่มีการป้องกันนี้

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

เราจะป้องกันสภาพการแข่งขันดังกล่าวได้อย่างไร? โดยเฉพาะอย่างยิ่งฉันต้องการทราบ:

  • เทคนิคใดบ้างที่สามารถใช้ได้ เช่นการติดตามเวลาของการเปลี่ยนแปลงครั้งล่าสุด อะไรคือข้อดีข้อเสียของแต่ละคน
  • ประสบการณ์การใช้งานที่เป็นประโยชน์คืออะไร?
  • กรอบการป้องกันนี้มีอะไรในตัว?

คุณได้รับคำตอบแล้ว: โดยการติดตามวันที่ของการเปลี่ยนแปลงวัตถุและเปรียบเทียบกับอายุของข้อมูลที่การเปลี่ยนแปลงอื่น ๆ พยายามอัปเดต คุณต้องการที่จะรู้อย่างอื่นเช่นวิธีการทำอย่างมีประสิทธิภาพ?
Kilian Foth

@KilianFoth - ฉันได้เพิ่มข้อมูลบางอย่างเกี่ยวกับสิ่งที่ฉันอยากรู้เป็นพิเศษ
paj28

1
คำถามของคุณไม่มีทางพิเศษสำหรับเว็บแอปพลิเคชันแอปพลิเคชันเดสก์ท็อปอาจมีปัญหาเดียวกันทั้งหมด กลยุทธ์การแก้ปัญหาทั่วไปมีการอธิบายไว้ที่นี่: stackoverflow.com/questions/129329/…
Doc Brown

2
FYI รูปแบบของการล็อคที่คุณพูดถึงในคำถามของคุณเรียกว่า " การควบคุมภาวะพร้อมกันในแง่ดี "
TehShrike

การสนทนาบางส่วนเกี่ยวข้องกับ Django ที่นี่
paj28

คำตอบ:


17

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


ดูเหมือนว่าวิธีปฏิบัติที่เป็นไปได้ที่สุด คุณรู้กรอบที่ใช้สิ่งนี้หรือไม่? ฉันคิดว่าปัญหาที่ใหญ่ที่สุดของโครงร่างนี้คือข้อความ "แก้ไขข้อขัดแย้ง" ที่เรียบง่ายจะทำให้ผู้ใช้ผิดหวัง แต่การพยายามรวมการแก้ไข (ด้วยตนเองหรือโดยอัตโนมัติ) เป็นเรื่องยาก
paj28

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

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

1
Dataflex มีฟังก์ชันที่เรียกว่า "reread ()" ซึ่งทำในสิ่งที่คุณอธิบาย ในรุ่นที่ใหม่กว่ามันปลอดภัยในสภาพแวดล้อมแบบผู้ใช้หลายคน และแน่นอนว่ามันเป็นวิธีเดียวที่จะได้รับการปรับปรุงแบบอินเตอร์ลีฟเพื่อใช้งานได้

คุณสามารถยกตัวอย่างวิธีการทำเช่นนี้กับเซิร์ฟเวอร์ sql ได้หรือไม่ \
l

10

ฉันได้เห็น 2 วิธีหลัก:

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

    • pro: ผู้ใช้หลายคนสามารถแก้ไขส่วนต่าง ๆ ของหน้า หน้าข้อผิดพลาดสามารถนำไปสู่หน้าต่างที่ผู้ใช้คนที่สองสามารถรวมการเปลี่ยนแปลงของเขาในหน้าใหม่

    • คอนดิชั่น: บางครั้งความพยายามส่วนใหญ่ได้สูญเปล่าระหว่างการแก้ไขพร้อมกันขนาดใหญ่

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

    • pro: ความพยายามแก้ไขไม่สูญเปล่า

    • คอนดิชั่น: ผู้ใช้ที่ไร้ยางอายสามารถล็อคเพจได้อย่างไม่มีกำหนด หน้าเว็บที่มีการล็อคที่หมดอายุอาจยังสามารถกระทำได้เว้นแต่จะได้รับการจัดการ (โดยใช้เทคนิค 1)


7

ใช้มองโลกในแง่ควบคุมภาวะพร้อมกัน

เพิ่มคอลัมน์ versionNumber หรือ versionTimestamp ให้กับตารางในคำถาม (จำนวนเต็มปลอดภัยที่สุด)

ผู้ใช้ 1 อ่านบันทึก:

{id:1, status:unimportant, version:5}

ผู้ใช้ 2 อ่านบันทึก:

{id:1, status:unimportant, version:5}

ผู้ใช้ 1 บันทึกการบันทึกการเพิ่มรุ่นนี้:

save {id:1, status:important, version:5}
new value {id:1, status:important, version:6}

ผู้ใช้ 2 พยายามบันทึกบันทึกที่อ่าน:

save {id:1, status:unimportant, version:5}
ERROR

Hibernate / JPA สามารถทำสิ่งนี้โดยอัตโนมัติด้วย@Versionคำอธิบายประกอบ

คุณจำเป็นต้องรักษาสถานะของเร็กคอร์ดการอ่านไว้ที่ใดที่หนึ่งโดยทั่วไปในเซสชัน (ซึ่งปลอดภัยกว่าในตัวแปรรูปแบบที่ซ่อนอยู่)


ขอบคุณ ... มีประโยชน์อย่างยิ่งที่จะรู้เกี่ยวกับ @Version คำถามหนึ่ง: ทำไมจึงปลอดภัยที่จะเก็บสถานะในเซสชั่น? ในกรณีนี้ฉันกังวลว่าการใช้ปุ่มย้อนกลับอาจทำให้เกิดความสับสน
paj28

เซสชันปลอดภัยกว่าองค์ประกอบแบบฟอร์มที่ซ่อนอยู่เนื่องจากผู้ใช้จะไม่สามารถเปลี่ยนค่ารุ่นได้ หากนั่นไม่ใช่สิ่งที่น่ากังวลให้เพิกเฉยส่วนที่เกี่ยวกับเซสชัน
Neil McGuigan

เทคนิคนี้เรียกว่าการล็อคออฟไลน์ในแง่ดีและอยู่ใน SQLAlchemy เช่นกัน
paj28

@ paj28 - ลิงก์SQLAlchemyนั้นไม่ได้ชี้ไปที่อะไรเลยเกี่ยวกับการล็อคออฟไลน์ในแง่ดีและฉันไม่สามารถหาได้ในเอกสาร คุณมีลิงค์ที่เป็นประโยชน์มากกว่าหรือเพียงแค่ชี้คนไปที่ SQLAlchemy โดยทั่วไป?
dwanderson

@dwanderson ฉันหมายถึงตัวนับเวอร์ชันของลิงก์นั้น
paj28

1

ระบบ Object Relational Mapping (ORM) บางระบบจะตรวจจับว่าฟิลด์ใดของวัตถุที่ถูกเปลี่ยนแปลงนับตั้งแต่ถูกโหลดจากฐานข้อมูลและจะสร้างคำสั่ง SQL update เพื่อตั้งค่าเหล่านั้นเท่านั้น ActiveRecord สำหรับ Ruby on Rails เป็นหนึ่ง ORM เช่นนั้น

ผลกระทบสุทธิคือฟิลด์ที่ผู้ใช้ไม่ได้เปลี่ยนจะไม่รวมอยู่ในคำสั่ง UPDATE ที่ส่งไปยังฐานข้อมูล ผู้ที่อัปเดตเขตข้อมูลต่าง ๆ ในเวลาเดียวกันจะไม่เขียนทับการเปลี่ยนแปลงของกันและกัน

ขึ้นอยู่กับภาษาการเขียนโปรแกรมที่คุณใช้งานวิจัย ORM ใดที่พร้อมใช้งานและดูว่ามีรายการใดบ้างที่จะอัปเดตคอลัมน์ในฐานข้อมูลที่ระบุว่า "สกปรก" ในแอปพลิเคชันของคุณ


สวัสดีเกร็ก น่าเสียดายที่สิ่งนี้ไม่ได้ช่วยแก้ไขเงื่อนไขการแข่งขันประเภทนี้ หากคุณพิจารณาตัวอย่างดั้งเดิมของฉันเมื่ออลิซบันทึก ORM จะเห็นคอลัมน์ราคาสกปรกและอัปเดตแม้ว่าจะไม่ต้องการอัปเดตก็ตาม
paj28

1
@ paj28 จุดสำคัญในคำตอบของ Greg คือ " เขตข้อมูลที่ผู้ใช้ไม่เปลี่ยนแปลง " อลิซไม่ได้เปลี่ยนแปลงราคาดังนั้น ORM จะไม่พยายามบันทึกค่า "ราคา" ลงในฐานข้อมูล
Ross Patterson

@RossPatterson - ORM จะทราบความแตกต่างระหว่างฟิลด์ที่ผู้ใช้เปลี่ยนไปและข้อมูลเก่าจากเบราว์เซอร์ได้อย่างไร อย่างน้อยก็ไม่ต้องมีการติดตามเพิ่มเติม หากคุณต้องการแก้ไขคำตอบของ Greg เพื่อรวมการติดตามดังกล่าวหรือส่งคำตอบอื่นนั่นจะเป็นประโยชน์
paj28

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

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