Cách đảm bảo dữ liệu không bị trùng lặp trong các tình huống đồng thời cao?

Nội dung bài viết

Video học lập trình mỗi ngày

Mô phỏng

Vừa bước vào sảnh công ty, ku INTERN đã thấy lòng bàn tay ướt đẫm mồ hôi. Ngồi trên ghế chờ, cậu nhìn xung quanh, những nhân viên ở đây đều lịch lãm, chuyên nghiệp. Điều này càng làm cậu cảm thấy mình nhỏ bé.

Sau vài phút, người phỏng vấn đeo bảng tên SENIOR xuất hiện và mời INTERN vào phòng. Bước vào căn phòng sáng sủa, với ánh sáng từ cửa sổ lớn chiếu vào, cậu cảm thấy trái tim mình như muốn nhảy ra khỏi lồng ngực. Cầm CV trên tay..

  • SENIOR: "Ồ, có thực sự bạn đã làm việc trong hệ thống đồng thời cao hay không?"

  • INTERN: "Đúng vậy thưa Anh." (Hãy nói những gì cần nói, không cần nói thêm...)

  • SENIOR: "Như vậy thì tôi sẽ không cần cậu giới thiệu về bản thân nữa mà hãy làm cách nào để đảm bảo dữ liệu không bị trùng lặp trong các tình huống đồng thời cao?"

  • INTERN: (Cười... Rồi trúng tủ rồi... Hit một hơi thật sâu..) Nếu nói về đảm bảo dữ liệu không bị trùng lặp trong các tình huống đồng thời cao? Thì trên thực tế rất có nhiều kịch bản, ông có thể nêu một kịch bản được không?

  • SEINOR: "Tất nhiên rồi, kịch bản như thế này..."

Dậy thôi nào, đó chỉ là giấc mơ mà thôi...

Trước tiên hãy xem câu hỏi và sau đó mỗi chúng ta tự hỏi: "Mình đã từng gặp phải những tình huống này chưa? Hoặc có thể tương lai sẽ gặp thì mình sẽ thực hiện như thế nào?"

Câu hỏi này được đưa vào Series: Intern đánh bại người phỏng vấn Senior như thế nào?

Câu hỏi khác nâng cao hơn:

Phân tích câu hỏi?

Vì câu hỏi chưa đưa ra kịch bản cụ thể, vì trên thực tế có nhiều kịch bản xảy ra, ví dụ đăng ký tài khoản trùng email, đặt hàng trên shopdev bị duplicate, chuyển tiền một lần nhưng bị trừ nhiều lần và còn có nhiều kịch bản khác trong các ứng dụng hiện nay... Hoặc lớn hơn nữa là do sử dụng Message Queue(MQ) và đôi khi dữ liệu khi đưa vào producer bị trùng lặp. Nếu chúng không được xử lý tốt, dữ liệu trùng lặp cũng sẽ được tạo ở consumer và sau đó là database.

Mỗi kịch bản đều mang một bản sắc và cách xử lý tình huống riêng, nếu như ở kịch bản trước khi user đăng ký hệ thống thì phải kiểm tra user có thực sự tồn tại hay không trong dữ liệu có hơn 100 triệu developer như github thì chúng ta đã có một số phương án điển hình là

"Cách hiệu quả làm sao để biết user có tồn tại trong 100 triệu user như GITHUB, có thể tốt nhất không?"

ở đó tôi và anh chị cũng đã có câu trả lời của riêng mình.

Giải pháp mà ai cũng nghĩ trong đầu đó chính là select or findOne sẽ thường được sử dụng. Tuy nhiên, giải pháp này không phù hợp với các tình huống đồng thời cao, do đó nó phải được sử dụng kết hợp với các giải pháp khác, nếu không dữ liệu trùng lặp cũng sẽ được tạo ra. Và điều này hẳn ai cũng rõ.

Ngoài ra anh em cũng đã trình bày một cách cự kỳ cool đó là sử dụng khoá phân tán, nếu anh chị chưa biết các sử dụng trong tình huống đồng thời cao thì có thể đón xe vào khu vực: Mysql vẫn sống tốt cho dù khách hàng đặt vé 25.000 req/s

Chính vì vậy chúng ta sẽ có nhiều cách để khắc phục theo các kịch bản ví dụ như lock(), Optimistic vs. Pessimistic locking, Message Queue Broker. Hoặc có thể đơn giản là dưới database thì sẽ tạo ra một key unique hay nói khác khác là tạo ra một ràng buộc unique được sử dụng để đảm bảo tính unique của thứ tự của bảng dữ liệu trong cơ sở dữ liệu.

Sau khi thêm ràng buộc unique, giả sử rằng việc chèn user01 thành công, thì khi một action khác của user01 xảy ra đồng thời thì sẽ có một lỗi được đưa ra.

Ví dụ trong java thì

try {
    // Chèn dữ liệu vào MySQL
    orderRepository.insert(order);
} catch (DuplicateKeyException | MySQLIntegrityConstraintViolationException e) {
    // Bắt lỗi vi phạm chỉ mục duy nhất
    System.out.println("Dữ liệu đã tồn tại, trả về thành công!");
    return "Success";
}

Còn nếu trong mysql thì:


-- Lần đầu: Thêm thành công
INSERT INTO `order` (`code`, `amount`) VALUES ('002', 100);

-- Lần thứ hai: Thêm trùng -> Lỗi
INSERT INTO `order` (`code`, `amount`) VALUES ('002', 150);

Lỗi xảy ra

Duplicate entry '002' for key 'order.un_code'

Thêm một cách đơn giản nữa đó là sử dụng select ... for update. Nếu bạn chưa có sử dụng câu lệnh này bao giờ thì tôi có thể giải thích thêm một chút..

Kịch bản chuyền tiền trong Banking

Tôi lấy một ví dụ điển hình ở đây, tất nhiên vì là ví dụ cho nên không thể nói hết tường tận của môn phái tài chính võ lâm được cho nên hãy xem nhẹ vấn đề về chuyên môn. Hãy bắt đầu...

Có hai thằng JAVA và GO: Đó là hai trong 3 môn phái võ công mà anh em ta đang tìm hiều nâng cao trong danh sách: Tôi đang đi trên con đường lập trình viên

Trong tình huống thanh toán, số dư tài khoản của người dùng JAVA là 150 USD và JAVA muốn chuyển 50 USD qua cho anh bạn GO. Trong trường hợp tuyệt vời và hoàn hảo nhất thì sau khi gửi lệnh đi thì hoàn thành và kết quả có JAVA = 100 USD và GO = 50 USD.

Nhưng đời không như mơ, và ở đây cũng vậy nếu cùng một requet xảy ra nhiều lần thì điều đó có thể khiến số dư của thắng JAVA và thằng GO không phải như trên mà có thể là số âm. Thì để giải quyết trường hợp này, họ có thể sử dụng khoá bi quan và lạc quan có đề cập ở trên - Optimistic vs. Pessimistic locking.

Đơn giản là hãy đi từ từ, không chen lấn xô đẩy....

Tôi thích kết thúc ngắn củn như vậy, vì cuộc đời không như mơ...

Có thể bạn đã bị missing