Avatar
2
Hihi Teacher
Hihi Teacher
Hỏi về đồng bộ trong Java
Hi xin chào mọi người,

Em có 1 ví dụ với ứng dụng banking trong Spring Boot như sau :

Account A hiện đang có 1000$ ( lưu ở DB )

Có 2 request yêu cầu nạp tiền cùng 1 thời điểm tại account A với số tiền lần lượt là 200$ và 400$

=> Nếu đúng thì accout A sẽ có 1600$

Tuy nhiên trong lúc yêu cầu đầu tiên chưa update DB lại tổng tiền mới (1000$ +200$ = 1200$) thì yêu cầu thứ 2 đang chạy song song lấy tổng tiền cũ từ DB nên cũng update dựa trên tổng tiền cũ ( 1000$ + 400$ = 1400$ )

=> Tổng tiền mới sau 2 luồng chạy  xong là 1400$ thay vì 1600$. 200$ của luồng 1 do update trước nên bị mất.

Em nghĩ trong Spring Boot nếu chúng ta không lock 1 account khi account đó trong quá trình xử lí thì sẽ gây ra hậu quả như trên.

Em nghĩ trường hợp như trên trong thực tế cũng có thể xảy ra ạ.Giải pháp xử lí tình trạng trên trong thực tế với Spring Boot là như thế nào ạ ?

Em có suy nghĩ đến synchronize nhưng nếu dùng synchronize thì có vẻ ko đúng cho lắm vì có thể nó sẽ lock luôn các luồng của account khác.Em muốn chỉ lock đúng accout mà đang xử lí thôi còn account khác thì cho chạy bình thường.
  • Answer
java spring
Remain: 5
1 Answer
Avatar
monkey Teacher
monkey Teacher
Đúng vậy, bởi vì tính chất Isolation - "Một giao dịch đang thực thi và chưa được xác nhận phải bảo đảm tách biệt khỏi các giao dịch khác" vậy nên khi em dùng đa luồng thì với những dạng hàm:

lấy ra
update
lưu lại

Sẽ bị xảy ra tình trạng như trên, dữ liệu bị ghi đè.

Có nhiều cách để giải quyết bài toán này:

  1. Đơn giản nhất là như em nói sử dụng lock toàn bộ account, tuy nhiên em sẽ chỉ được phép sử dụng 1 server để update account balance, nếu có 2 server thì việc lock trên từng server sẽ là vô nghĩa vì vẫn sẽ xảy ra tình trạng concurrent update.

  1. Sử dụng versioning dữ liệu, nếu dữ liệu bị version thấp hơn nó sẽ bị từ chối và em sẽ yêu cầu user cộng tiền lại hoặc tự động retry lại nếu cộng tiền tự động

  1. Đóng gói việc update vào trong 1 câu lệnh: UPDATE balance SET amount = amount + :value
  • 0
  • Reply
Cảm ơn anh vì câu trả lời

Đối với các câu trả lời của anh thì em có thắc mắc lần lượt là

  1. Cách này thì ko ứng dụng thực tế được đúng ko a ? Ví dụ trong các app của bank có số lượng lớn account khác nhau nạp tiền cùng lúc thì do lock nên yêu cầu của account càng trễ thì xử lý càng lâu do đợi từng thread một trước nó xử lý.
  2. Anh có thể cho em xin ví dụ về cách này ko ạ ?
  3. Em vẫn chưa hiểu ý tưởng này của anh có thể thực hiện như thế nào trong Spring Boot lắm ạ.Làm thế nào mà các request khác nhau lại đóng gói (dùng chung) bằng 1 câu lệnh Update được ạ ? Nếu được mong anh giải thích và cho ví dụ về cách 3 này ạ.
 –  Hihi 1665012403000
  1. Ừ, cách này có vẻ như sẽ làm giảm khả năng phục vụ của hệ thống nếu số lượng giao dịch tại 1 thời điểm quá lớn (khoảng vài nghìn giao dịch / giây)
  2. Nếu anh nhớ không nhầm thì với mysql khi em tạo bảng, em chỉ cần tạo trường tên là version, thì mỗi khi em update nó sẽ tăng cái trường này lên 1 đơn vị, nếu em truyền 1 record vào với giá trị nhỏ hơn version hiện tại nó sẽ báo lỗi. Ví dụ:

Server 1: lấy ra Balance (1000, version = 1), + 200, save DB Balance (1200, version = 2): OK

Server 2: lấy ra Balance (1000, version = 1), + 400, save DB Balance (1400, version = 2): Lỗi vì lúc này version ở db đã tăng lên 2 rồi

  1. Em chỉ cần sử dụng @Query (UPDATE balance SET amount = amount + :value) với hàm update balance của em ở tầng Repository là được em ạ

balance, amount: là tên bảng và trường anh ví dụ, em cần thay bằng của em nhé
 –  monkey 1665013169000