Tôi nên làm gì khi khóa lạc quan không hoạt động?


10

Tôi có kịch bản sau đây:

  1. Một người dùng thực hiện một yêu cầu GET/projects/1 và nhận một ETag .
  2. Người dùng thực hiện yêu cầu PUT/projects/1 với ETag từ bước # 1.
  3. Người dùng thực hiện một yêu cầu PUT khác /projects/1với ETag từ bước # 1.

Thông thường, yêu cầu PUT thứ hai sẽ nhận được phản hồi 412, vì ETag hiện đã cũ - yêu cầu PUT đầu tiên đã sửa đổi tài nguyên, do đó, ETag không khớp nữa.

Nhưng điều gì sẽ xảy ra nếu hai yêu cầu PUT được gửi cùng một lúc (hoặc chính xác là một yêu cầu khác)? Yêu cầu PUT đầu tiên không có thời gian để xử lý và cập nhật tài nguyên trước khi PUT # 2 đến, điều này khiến PUT # 2 ghi đè lên PUT # 1. Toàn bộ quan điểm của khóa lạc quan là để điều đó không xảy ra ...


3
Nguyên tử hóa hoạt động của bạn trong các giao dịch cấp doanh nghiệp, như Esben giải thích bên dưới.
Robert Harvey

Điều gì sẽ xảy ra nếu tôi nguyên tử hóa các hoạt động của mình bằng các giao dịch? PUT # 2 sẽ không được xử lý cho đến khi PUT # 1 được xử lý đầy đủ?
maximedupre

7
Trở thành một người bi quan?
jpmc26

Vâng, đây là những gì khóa là dành cho.
Fattie

Chính xác, tất nhiên không nên xử lý số 2 - chúng được coi là duy nhất.
Fattie

Câu trả lời:


21

Cơ chế ETag chỉ định giao thức truyền thông để khóa tối ưu. Trách nhiệm của dịch vụ ứng dụng là thực hiện cơ chế phát hiện các cập nhật đồng thời để thực thi khóa tối ưu.

Trong một ứng dụng thông thường sử dụng cơ sở dữ liệu, bạn thường làm điều này bằng cách mở giao dịch khi xử lý yêu cầu PUT. Bạn thường đọc trạng thái hiện có của cơ sở dữ liệu trong giao dịch đó (để có được khóa đọc), kiểm tra tính hợp lệ Etag của bạn và ghi đè dữ liệu (theo cách sẽ gây ra xung đột ghi khi có bất kỳ giao dịch đồng thời không tương thích nào), sau đó cam kết. Nếu bạn thiết lập giao dịch chính xác, thì một trong những cam kết sẽ thất bại vì cả hai sẽ cố gắng cập nhật cùng một dữ liệu. Sau đó, bạn sẽ có thể sử dụng lỗi giao dịch này để trả về 412 hoặc thử lại yêu cầu, nếu nó có ý nghĩa đối với ứng dụng.


Cách máy chủ hiện đang thực hiện cơ chế phát hiện các cập nhật đồng thời là bằng cách so sánh các giá trị băm của tài nguyên. Máy chủ cũng sử dụng các giao dịch cho tất cả các hoạt động, nhưng tôi không có được bất kỳ khóa nào, đây có thể là nguyên nhân gây ra sự cố. Tuy nhiên, trong ví dụ của bạn, làm thế nào có thể có lỗi trong một trong các cam kết nếu các giao dịch đang sử dụng khóa? Giao dịch thứ hai nên chờ xử lý khi đọc trạng thái, cho đến khi giao dịch đầu tiên được giải quyết.
maximedupre

1
@maximedupre: nếu bạn đang sử dụng giao dịch, bạn có một số loại khóa, mặc dù đó có thể là khóa ẩn (khóa được lấy tự động khi bạn đọc / cập nhật các trường thay vì yêu cầu rõ ràng). Cơ chế tôi mô tả ở trên có thể được thực hiện chỉ bằng cách sử dụng các khóa ẩn. Như câu hỏi khác của bạn, nó phụ thuộc vào cơ sở dữ liệu mà bạn đang sử dụng, nhưng nhiều cơ sở dữ liệu hiện đại sử dụng MVCC (điều khiển đồng thời nhiều phiên bản) để cho phép nhiều người đọc và người viết làm việc trên cùng một lĩnh vực mà không cần phải chặn lẫn nhau.
Lie Ryan

1
Cảnh báo: trong nhiều DBMS (PostgreSQL, Oracle, SQL Server, v.v.), mức cô lập giao dịch mặc định là "đọc cam kết", trong đó cách tiếp cận của bạn không đủ để ngăn chặn điều kiện cuộc đua của OP. Trong các DMBS như vậy, bạn có thể sửa nó bằng cách đưa AND ETag = ...vào mệnh đề UPDATEcủa câu lệnh WHEREvà kiểm tra số hàng được cập nhật sau đó. (Hoặc bằng cách sử dụng mức cô lập giao dịch chặt chẽ hơn, nhưng tôi thực sự không khuyến nghị điều đó.)
ruakh

1
@ruakh: tùy thuộc vào cách bạn viết truy vấn của mình, vâng, mức cô lập mặc định không tự động cung cấp hành vi đó cho tất cả các truy vấn, nhưng thường có thể cấu trúc giao dịch của bạn theo cách đủ để thực hiện khóa tối ưu. Trong hầu hết các trường hợp, nếu tính nhất quán giao dịch là quan trọng trong ứng dụng, tôi khuyên bạn nên đọc lặp lại làm mức cô lập mặc định; trong các cơ sở dữ liệu sử dụng MVCC, chi phí đọc lặp lại khá tối thiểu và nó đơn giản hóa ứng dụng một cách đáng kể.
Lie Ryan

1
@ruakh: nhược điểm chính của việc đọc lặp lại là bạn sẽ phải chuẩn bị thử lại hoặc thất bại nếu có giao dịch đồng thời. Đây thường là một vấn đề, nhưng các ứng dụng cung cấp khóa Lạc quan như một chiến lược đồng thời sẽ yêu cầu xử lý này, do đó, lỗi đọc lặp lại bản đồ một cách tự nhiên đối với các lỗi khóa lạc quan và điều này thực sự sẽ không thêm nhược điểm mới.
Lie Ryan

13

Bạn phải thực hiện cặp sau đây nguyên tử:

  • kiểm tra tính hợp lệ của thẻ (tức là cập nhật)
  • cập nhật tài nguyên (bao gồm cập nhật thẻ của nó)

Những người khác đang gọi đây là một giao dịch - nhưng về cơ bản, việc thực hiện nguyên tử của hai hoạt động này là điều ngăn cản người này ghi đè lên người kia một cách tình cờ; không có điều này bạn có một điều kiện cuộc đua, như bạn đang chú ý.

Đây vẫn được coi là khóa lạc quan, nếu bạn nhìn vào bức tranh lớn: rằng tài nguyên không bị khóa bởi lần đọc ban đầu (GET) bởi bất kỳ Người dùng hoặc bất kỳ Người dùng nào đang xem dữ liệu, cho dù có ý định cập nhật hay không.

Một số hành vi nguyên tử là cần thiết, nhưng điều này xảy ra trong một yêu cầu (PUT) thay vì cố gắng giữ khóa trên nhiều tương tác mạng; Đây là khóa lạc quan: đối tượng không bị khóa bởi GET nhưng vẫn có thể được cập nhật an toàn bằng PUT.

Cũng có nhiều cách để đạt được sự thực thi nguyên tử của hai hoạt động này - khóa tài nguyên không phải là lựa chọn duy nhất; ví dụ, một luồng nhẹ hoặc khóa đối tượng có thể đủ và phụ thuộc vào kiến ​​trúc và bối cảnh thực thi của ứng dụng của bạn.


4
+1 để lưu ý rằng đó là nguyên tử quan trọng. Tùy thuộc vào tài nguyên cơ bản được cập nhật, điều này có thể được thực hiện mà không cần giao dịch hoặc khóa. Ví dụ, so sánh nguyên tử và trao đổi tài nguyên trong bộ nhớ hoặc tìm nguồn cung cấp dữ liệu liên tục.
Aaron M. Eshbach

@ AaronM.Eshbach, đã đồng ý và cảm ơn vì đã gọi những người đó ra ngoài.
Erik Eidt

1

Nhà phát triển ứng dụng thực sự kiểm tra Thẻ điện tử và cung cấp logic đó. Đó không phải là phép màu mà máy chủ web làm cho bạn vì nó chỉ biết cách tính E-Tagcác tiêu đề cho nội dung tĩnh. Vì vậy, hãy đưa kịch bản của bạn lên trên và phân tích cách tương tác sẽ xảy ra.

GET /projects/1

Máy chủ nhận được yêu cầu, xác định Thẻ điện tử cho phiên bản này của bản ghi, trả lại yêu cầu đó với nội dung thực tế.

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

Vì ứng dụng khách hiện có giá trị Thẻ điện tử, nên nó có thể bao gồm điều đó với PUTyêu cầu:

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

Tại thời điểm này, ứng dụng của bạn phải làm như sau:

  • Xác minh rằng Thẻ điện tử vẫn đúng: "412" == "412"?
  • Nếu vậy, hãy cập nhật và tính toán Thẻ điện tử mới

Gửi phản hồi thành công.

204 No Content
E-Tag: "543"

Nếu một yêu cầu khác xuất hiện và cố gắng thực hiện PUTtương tự như yêu cầu ở trên, lần thứ hai mã máy chủ của bạn đánh giá nó, bạn có trách nhiệm cung cấp thông báo lỗi.

  • Xác minh Thẻ điện tử vẫn đúng: "412"! = "543"

Khi thất bại, gửi phản hồi thất bại.

412 Precondition Failed

Đây là mã bạn thực sự phải viết. Thẻ E trên thực tế có thể là bất kỳ văn bản nào (trong giới hạn được xác định trong thông số HTTP). Nó không phải là một con số. Nó có thể là một giá trị băm là tốt.


Đây không phải là ký hiệu HTTP tiêu chuẩn bạn đang sử dụng ở đây. Trong HTTP tuân thủ tiêu chuẩn, bạn chỉ sử dụng ETag trong tiêu đề phản hồi. Bạn không bao giờ gửi ETag trong tiêu đề yêu cầu, mà thay vào đó sử dụng giá trị ETag đã mua trước đó trong tiêu đề If-Match hoặc If-none-Match trong tiêu đề yêu cầu.
Lie Ryan

-2

Để bổ sung cho các câu trả lời khác, tôi sẽ đăng một trong những trích dẫn hay nhất trong tài liệu ZeroMQ mô tả trung thực vấn đề cơ bản:

Để thực hiện các chương trình MT hoàn hảo (và ý tôi là theo nghĩa đen), chúng tôi không cần mutexes, khóa hoặc bất kỳ hình thức giao tiếp liên luồng nào khác ngoại trừ các tin nhắn được gửi qua các ổ cắm ZeroMQ.

Theo "chương trình MT hoàn hảo", ý tôi là mã dễ viết và dễ hiểu, hoạt động với cùng một cách tiếp cận thiết kế trong bất kỳ ngôn ngữ lập trình nào và trên bất kỳ hệ điều hành nào, và quy mô trên bất kỳ số lượng CPU nào có trạng thái chờ không và không có điểm lợi nhuận giảm dần.

Nếu bạn đã dành nhiều năm để học các thủ thuật để làm cho mã MT của bạn hoạt động hoàn toàn, hãy nhanh chóng, với các khóa và ngữ nghĩa và các phần quan trọng, bạn sẽ chán ghét khi bạn nhận ra tất cả chẳng là gì cả. Nếu có một bài học chúng ta đã học được từ hơn 30 năm lập trình đồng thời, đó là: chỉ cần không chia sẻ trạng thái. Giống như hai người say rượu cố gắng chia sẻ bia. Không thành vấn đề nếu họ là bạn tốt. Sớm muộn gì họ cũng sẽ đánh nhau. Và bạn càng thêm say vào bàn, họ càng đánh nhau với bia. Phần lớn bi thảm của các ứng dụng MT trông giống như những trận đánh ở quán rượu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.