Yêu cầu nguy hiểm không phải lần đầu. Đó là cùng yêu cầu đến lần nữa, vì bên gửi không nghe câu trả lời đầu, hoặc nghe rồi vẫn thử lại. Nhà cung cấp thanh toán làm vậy. Hàng đợi làm vậy. Trình duyệt làm vậy khi người ta bấm nút hai lần. Nếu hệ thống coi bản thứ hai là việc mới, bạn trừ tiền hai lần, gửi hai lần, hoặc viết hai dòng sổ cho một lần đặt.
Coi như bản trùng sẽ tới
Tôi thiết kế bộ xử lý để lần giao thứ hai là một thành công không làm gì. Lần đầu làm việc và lưu một khóa: id sự kiện của nhà cung cấp, hoặc khóa máy khách gửi, duy nhất trong cơ sở dữ liệu. Lần hai tìm thấy khóa và trả cùng kết quả. Không trừ tiền lần hai. Không thư lần hai. Không dòng thứ hai.
Ràng buộc duy nhất là phần tôi tin. Một lần kiểm trong mã ứng dụng, ngay trước insert, thua cuộc đua khi hai worker cùng thức. Cơ sở dữ liệu không thua cuộc đua đó. Bên thua nhận một xung đột và biến nó thành “đã xong”.
| Lần đến đầu | Lần đến thứ hai | |
|---|---|---|
| Tra cứu | Khóa là mới | Khóa đã được lưu |
| Viết | Một dòng, một tác dụng phụ | Không có dòng mới |
| Trả lời | Đã nhận | Cùng một cái, đã nhận |
| Nếu tiến trình chết giữa chừng | Hộp gửi vẫn còn nợ tin đó | Nó không nợ cái thứ hai |
Tin đi cùng lần ghi, hoặc không đi
Gửi thư, hoặc tin hàng đợi, trong yêu cầu và trước commit là cách bạn có một tin cho giao dịch đã hoàn tác. Tôi ghi tin đi ra trong cùng giao dịch với dòng nghiệp vụ, và một worker riêng phát nó. Nếu tiến trình chết, dòng và tin hoặc cả hai còn, hoặc cả hai không. Worker được thử phát lại. Bộ xử lý không được thử trừ tiền lại.
Hủy là một bút toán mới, không phải sửa
Khi phải hoàn tác, tôi không ghi đè dòng gốc. Một bút toán đối ứng nói điều gì đã đổi và để dòng đầu vẫn đọc được. Một tuần sau bạn vẫn trả lời được lúc 14:02 người ta tin gì. Sửa dòng chỉ trả lời người ta tin gì bây giờ, và không có người chứng.
Nếu thử lại có thể khiến thao tác xảy ra hai lần, thao tác đó chưa xong.
Đặt tên khóa trước khi viết handler
Khóa không phải “một hash của cả yêu cầu” trừ khi bạn đã xem bên gửi đổi gì giữa các lần thử lại. Nhà cung cấp thanh toán sẽ gửi lại cùng id sự kiện với một dấu thời gian giao mới. Nếu dấu đó nằm trong khóa, lần giao thứ hai trông như mới, và bạn đã dựng đúng lỗi mình muốn chặn. Tôi dùng id mà bên gửi hứa là ổn định. Nếu không hứa, tôi bắt bên gọi gửi một cái, và từ chối lần gọi đến mà không có nó.
Rồi tôi ghi thất bại vào cùng chỗ với thành công. Xung đột không phải trang lỗi cho bên gửi. Đó là câu trả lời đúng cho một lần thử lại. Bên gửi phải dừng được. Nếu tôi trả 500, họ sẽ thử lần ba, lần bốn, và sự cố thành một hàng việc giống hệt.
Tôi không coi là xong chỉ vì đường thuận trả về 200. Bài thử gửi lại cùng tải và khẳng định một hiệu ứng phụ. Rồi nó giết worker giữa commit và lúc phát, và khẳng định tin vẫn ra, một lần. Hai bài thử đó ngắn hơn sự cố chúng thay. Tôi bắt đầu từ đó.
