Trong quá trình làm việc, gần như chắc chắn các bạn sẽ gặp những trường hợp cần sử dụng đến một distributed lock (hay còn gọi là khóa phân tán) để làm khóa và xử lý những usecase đặc biệt đáp ứng nghiệp vụ của dự án.

Mục đích của distributed lock để làm gì

Bản chất việc sử dụng distributed lock (khóa phân tán) để đảm bảo rằng khi nhiều instances của application thực hiện cùng một loại công việc nhất định, thì tại một thời điểm chỉ có một instances thực sự thực hiện công việc đó.

Một số trường hợp phổ biến mà chúng ta có thể sử dụng distributed lock như là:

  • Ghi dữ liệu xuống DB, hoặc một nơi lưu trữ dữ liệu chung một cách tuần tự, có thực hiện xử lý một vài tính toán logic
  • Application có nhiều instances, nhiều process, thread cùng sử dụng chung một nguồn tài nguyên để xử lý
  • Sử dụng để generate ra những mã code mang tính unique như : mã tổ chức, mã đơn hàng, mã giao dịch…..
  • Sử dụng để tạo các job Schedule bất đồng bộ định kì (điều này mình sẽ nói ở một bài khác, vì bản chất có thể coi là distributed lock, tuy nhiên nó có thiên hướng dùng database để làm lock nhiều hơn nên mình chỉ liệt kê thêm vào đây thôi)

Sử dụng distributed lock để đáp ứng bài toán tới mức nào?

Khi tính toán tới việc sử dụng một bên thứ ba để làm distributed lock, thì chúng ta cần biết rõ lý do sử dụng khóa phân tán nó sẽ đem lại điều gì:

  • Hiệu quả: việc sử dụng khóa giúp bạn không phải thực hiện một công việc hai lần một cách dư thừa, ví dụ một số aggregate vào database hay truy vấn API sang bên thứ ba , hay đơn cử như trường hợp người dùng nhận được 4 cái notification nhận tiền trong khi chỉ được cộng tiền có 1 lần.
  • Tính chính xác: đối với việc sử dụng cho mục đích đảm bảo tính chính xác thì khóa sẽ ngăn các tiến trình đồng thời tranh giành tài nguyên trên cùng một phần dữ liệu và tránh được việc rối loạn trạng thái hệ thống. Nếu lấy khóa không thành công và hai node hoạt động đồng thời trên cùng một dữ liệu, hậu quả gây ra là sai lệch dữ liệu.

Vậy câu hỏi đặt ra là tại sao phải xác định rõ mức độ sử dụng distributed lock ?

Thử hình dung chúng ta chỉ cần dùng lock để đem lại sự hiệu quả thì trong khi chỉ cần 2 node Hazelcast là đủ cho nhu cầu thì ta lại xây dựng một cụm cluster 5 node (trên 5 server khác nhau), gây ra việc thừa thãi tài nguyên, lãng phí, tốn dung lượng, tốn chi phí.

Trường hợp ta chỉ xây dựng 1 hoặc 2 node Hazelcast, nhưng yêu cầu bài toán lại cần đảm bảo dữ liệu về tiền nong, giao dịch,…. luôn luôn phải toàn vẹn và không được phép sai sót, khi đó việc xây dựng khóa để đảm bảo tính chính xác phải được đặt lên hàng đầu.

Nguyên nhân tại sao có trường hợp distributed lock không giải quyết được vấn đề

Xem xét hình ảnh bên dưới

unsafe-lock
  • Trước tiên Client 1 gọi tới Lock service để lấy khóa phân tán -> ok đoạn này đã lấy được khóa
  • Sau đó tiến trình xử lý cho Client 1 bị dừng một khoảng thời gian nhất định do GC (bộ thu thập rác) xảy ra
  • Lúc này khóa phân tán của Client 1 bị hết hạn trong khi GC vẫn chưa thực hiện xong việc thu thập rác
  • Client 2 thực hiện gọi tới Lock service để lấy một khóa phân tán -> ok lúc này lấy được khóa vì khóa cho Client 1 đã hết hạn (ở đây chúng ta đang xét tới việc lấy cùng một key)
  • Client 2 thực hiện write data tới Storage thành công, một action gì đó ví dụ như upload ảnh, hoặc xử lý logic đồng bộ dữ liệu,…
  • Lúc này GC xử lý cho Client 1 đã thực hiện xong, và nó thực hiện write data tới Storage và lúc này xảy ra việc sai dữ liệu, hoặc một hành động nào đó làm sai lệch data.

FencedLock được sinh ra như thế nào

Để xử lý vấn đề phía trên thì có một loại khóa được sinh ra, Fenced Lock (gọi là Khóa Hàng Rào), và bên dưới là cách nó triển khai:

Fenced Lock

Về quy trình thì vẫn giống như ở phía trên, tuy nhiên chỉ khác biệt ở chỗ là mỗi lần nhận khóa thì sẽ nhận kèm cả một token (số luôn tăng dần), và server sẽ dựa vào giá trị của token để quyết định xem có xử lý logic tiếp không hay sẽ từ chối.

  • Client 1 gọi tới Lock service để lấy khóa phân tán -> ok đoạn này đã lấy được khóa và nhận kèm token: 33
  • Sau đó tiến trình xử lý cho Client 1 bị dừng một khoảng thời gian nhất định do GC (bộ thu thập rác) xảy ra
  • Lúc này khóa phân tán của Client 1 bị hết hạn trong khi GC vẫn chưa thực hiện xong việc thu thập rác
  • Client 2 thực hiện gọi tới Lock service để lấy một khóa phân tán -> ok lúc này lấy được khóa và nhận kèm một token mới là: 34
  • Client 2 thực hiện write data tới Storage thành công, Server ghi nhận đã xử lý thành công với token: 34
  • Lúc này GC xử lý cho Client 1 đã thực hiện xong, và nó thực hiện write data tới Storage, tuy nhiên nó bị server reject do server đã xử lý logic cho token 34, nên không xử lý cho Client 1 nữa (token 33)

Lúc này server phải đảm bảo được việc tích cực kiểm tra token, và không thực hiện xử lý các token có giá trị bị ngược.

Lúc này có một vấn đề nảy sinh đó là làm sao mà chắc chắn được mỗi Client đều tạo ra một token tăng dần và không đụng chạm nhau được? chúng ta sẽ đi tiếp xuống phần tiếp theo.

Cách Hazelcast triển khai FencedLock ra sao

fenced lock hazelcast

Với Hazelcast thì khi có chủ sở hữu mới nắm giữ khóa, thì mã fencing token cho chủ sở hữu đó sẽ được tăng dần. Mã fencing token sau đó được chuyển đến các service hoặc tài nguyên bên ngoài để đảm bảo được việc thực hiện tuần tự và tránh các tác dụng phụ cho chủ sở hữu khóa thực hiện.

Đoạn này các bạn đã có cảm giác hơi giống giống versions khi sử dụng ElasticSearch chưa?

Sau đây là những đặc điểm của FencedLock với Hazelcast:

  • FencedLock là 1 triển khai của interface java.util.concurrent.locks.Lock, có thể tuyến tính hóa với ngữ nghĩa thực thi và lỗi được xác định rõ.
  • FencedLock sao chép trạng thái của nó trên một nhóm thành viên Hazelcast thông qua thuật toán đồng thuận Raft.
  • FencedLock theo dõi hoạt động của những người nắm giữ khóa thông qua cơ chế phiên hoạt động thống nhất cho cả máy chủ và máy khách.
  • FencedLock cho phép các hệ thống của bên thứ 3 tham gia vào giao thức khóa và loại trừ lẫn nhau đối với các tác dụng phụ được thực hiện trên chúng.

Tạm thời chúng ta sẽ dừng ở việc tìm hiểu lý thuyết, cách chi tiết thực hành sử dụng FencedLock Hazelcast trong code mình sẽ update tiếp.

Xem thêm các bài viết liên quan ở bên dưới: