Bài viết trước mình có đề cập đến Lock và các loại Lock để giải quyết những vấn đề liên quan đến Concurrency. Sử dụng lock là một phương pháp đơn giản nhưng rất hữu ích khi mà các tiến trình khác nhau trong nhiều môi trường phải vận hành và chia sẻ các tài nguyên theo cách đồng bộ. Tuy nhiên, quản lý lock như thế nào lại là một bài toán không đơn giản vậy nên có khá là nhiều bên thứ ba nhảy vào để giải quyết cũng như ăn miếng bánh béo bở này từ những vấn đề của mọi hệ thống dữ liệu lớn.
Việc quản lý này được gọi là Distributed Lock Manager
Trình quản lý khóa phân tán (distributed lock manager – viết tắt DLM) là một thành phần có thể tích hợp để giúp bạn quản lý lock do bên thứ ba cung cấp để hỗ trợ nền tảng (platform) hệ thống của bạn. Trình quản lý khóa phân tán duy trì một danh sách các tài nguyên hệ thống (system resource) và cung cấp các cơ chế lock để kiểm soát việc phân bổ và sửa đổi các tài nguyên.
Trình quản lý khóa phân tán không kiểm soát quyền truy cập vào bảng (table) hoặc bất kỳ thứ gì trong chính cơ sở dữ liệu (database).
Mọi tiến trình (process) quan tâm đến tài nguyên cơ sở dữ liệu được bảo vệ bởi trình quản lý khóa phân tán phải mở một khóa trên tài nguyên.
Nói đơn giản nó sinh ra để quản lý chiếc bánh mang tên tài nguyên cho rất nhiều miếng ăn tranh nhau. Nó không đặc thù cho riêng tài nguyên cụ thể nào như database hay file mà nó chỉ hỗ trợ cơ chế giúp bạn dễ dàng sử dụng và quản lý lock mà thôi.
Có rất nhiều bên hỗ trợ distributed lock manager như Oracle, Red Hat, Redis… Bài viết này mình cũng không có mục đích so sánh cách bên cung cấp.
Mình chỉ muốn sử dụng một nhà cung cấp để làm ví dụ cho giải pháp quản lý lock mà thôi. Với vốn kiến thức ít ỏi của mình thì mình mới làm việc qua với Redis, và mình sẽ chọn nó làm demo cho bài viết này.
Redis có đầy đủ các tính năng mà ta có thể sử dụng như một công cụ quản lý lock trong hệ thống phân tán và Redis cũng đưa ra một giải thuật mà họ gọi là Redlock để quản lý lock khi sử dụng Redis.
Redis đưa ra ba tiêu chí tối thiểu để sử dụng lock phân tán một cách hiệu quả
– Safety property: Loại trừ lẫn nhau. Tại bất kì thời điểm nào thì chỉ có duy nhất một client được phép giữ lock.
– Liveness property A: Giải phóng deadlock. Một tài nguyên không được phép bị lock mãi mãi, ngay cả khi một client đã lock tài nguyên đó nhưng bị treo hoặc gặp sự cố.
– Liveness property B: Tính chịu lỗi: Nếu phần lớn các node Redis vẫn đang hoạt động thì client vẫn có thể nhận và giải phóng lock.
Bạn có thể đọc thêm tại document của redis nhé. (https://redis.io/docs/reference/patterns/distributed-locks/)
Với một hệ thống bình thường nếu nhận nhiều request tới cùng một lúc thì nó sẽ thực hiện như hình vẽ sau:
Vấn đề phát sinh khi nếu bạn mở rộng nhiều hơn một instances hay nhiều tác vụ xử lý mà không quản lý nó được với hàng đợi thông thường.
Khi này Redis sẽ có cơ chế lock một tài nguyên đó là tạo ra một key. Key này thường được tạo có giới hạn thời gian tồn tại bằng cách sử dụng tính năng expire của Redis, vì vậy nó sẽ đảm bảo lock luôn luôn được giải phóng. Khi một máy client cần giải phóng tài nguyên thì chỉ cần xóa bỏ key đã tạo.
Và Redlock ra đời để Distributed Lock Manager
Một cơ chế khác của Redis chính là chia tách nhiệm vụ làm việc thành Redis Master và Redis Slave. Đây là một cơ chế đảm bảo tính High Availability, hiểu đơn giản là giúp cho hệ thống có thể đảm bảo duy trì sự liên tục, những slave được xem là những anh lính dự bị nhằm đảm bảo master luôn trong trạng thái sẵn sàng và những anh lính này được chuyển giao thay phiên nhau bằng Redis Replication.
Vậy khi trong quá trình lock mà Redis master bị ngừng hoạt động thì sao? Bạn sẽ nghĩ rằng nó chuyển sang sử dụng slave đúng không nào.
Nghe có vẻ hết sức hợp lý nhưng việc này lại không thể thực hiện được. Chính điều này dẫn đến tiêu chí Loại trừ lẫn nhau (Safety property) không được triển khai vì Redis không đồng bộ trong quá trình sử dụng Lock.
Tại sao vậy?
Ví dụ mình có một request A tới và nhận lock ở Redis master. Rồi Redis master bị treo trước khi đồng bộ key sang Redis slave. Khi này Redis slave bây giờ được chuyển thành Redis master. Và những request B, C, D khác vẫn nhận lock trên cùng tài nguyên với request A đang giữ lock. Điều này khiến cho việc lock như không lock vậy.
Vậy RedLock xử lý thế nào trong trường hợp này?
Giả định là ta sẽ có N=5 Redis master và Redis slave được đặt trên các máy khác nhau hoàn toàn độc lập.
Để nhận lock, một client thực hiện các thao tác sau:
– Đầu tiên RedLock sẽ xác định thời gian hiện tại theo đơn vị mili giây.
– Sau đó nó cố gắng tạo lock tuần tự trong tất cả N instance, sử dụng cùng một tên key và giá trị ngẫu nhiên cho tất cả instance. Trong khoảng thời gian này khi nó đặt lock trong mỗi instance, client sử dụng một timeout nhỏ hơn so với tổng thời gian tự động giải phóng lock để tạo lock. Ví dụ nếu thời gian giải phóng lock tự động là 10 giây thì timeout có thể nằm trong khoảng 10 mili giây nhằm cố gắng kết nối với một Redis node bị treo trong một thời gian dài theo kiểu health check.
Để đảm bảo một instance không sẵn sàng để sử dụng nó sẽ thử kết nối với instance tiếp theo càng sớm càng tốt.
– Tiếp đó client sẽ phải tính toán xem bao nhiêu thời gian đã trôi qua để nhận được lock bằng cách sử dụng thời gian được thiết lập được từ bước đầu tiên. Nếu client nhận được lock trong đa số các instances (ít nhất là 3 trên 5 instances) và tổng thời gian trôi qua để có được lock ít hơn thời gian hiệu lực của lock thì lock đó sẽ được nhận được với những client sau đó.
Nói dễ hiểu hơn thì bạn tưởng tượng như khóa phòng giam được chia thành nhiều chìa khóa và chia trên từng quản tù vậy. Lock có hiệu lực khi đa số trên thiểu số đồng thuận.
– Khi này sẽ xảy ra hai trường hợp
+ Nếu lock đã được nhận và thời gian hiệu lực thực sự của nó chính là thời gian hiệu lực khởi tạo ban đầu trừ đi thời gian trôi qua trong lúc tạo lock như đã được tính trước ở trên.
+ Nếu không thể nhận được lock nó sẽ cố gắng unlock trên tất cả instances ngay cả những instances mà nó cho rằng vẫn chưa tạo được lock để chắc chắn rằng không bao giờ bị deadlock.
– Cuối cùng là quá trình giải phóng lock toàn cục được thực hiện giải phóng lock trong tất cả các instance cho dù client không biết là lock đó có được tạo trên một instance cụ thể nào hay không.
Nghe có vẻ giải quyết được vấn đề rồi nhỉ. Đó là những gì mình tìm hiểu được, có thể thời gian sử dụng sau này có những vấn đề phát sinh khiến cộng đồng phải xem xét lại, nhưng đối với hiện tại trong quá trình sử dụng thì mình chưa thấy vấn đề nào khác phát sinh.
Nếu anh em nào đã từng dùng Redlock, cho mình xin ý kiến về nó nhé.
Trên đây là bài viết về cách xử lý vấn đề quản lý lock với Distributed Lock Manager, hi vọng bài viết có thể giúp bạn hiểu được phần nào cơ chế quản lý của DLM nói chung và Redlock nói riêng.
Cuối cùng là source code demo và diagram của mình trên github với code C#.
Bạn có thể tham khảo nhé!
https://github.com/ntechdevelopers/ntech.distributedlocks.redlock
Bài viết gốc được đăng tải tại ntechdevelopers.com
Xem thêm các bài viết liên quan:
- Top 10 khái niệm System Design mà mọi lập trình viên nên biết
- Làm việc với Redis sử dụng Redisson
- Làm việc với Redux trong ứng dụng lớn
Xem thêm Việc làm IT hấp dẫn trên TopDev