+1

Distributed Lock trong Microservices: Tại sao đơn giản nhưng rất dễ làm sai?

Khi Microservices cần một cái Lock: Sự phức tạp ẩn trong Distributed Systems

Khi hệ thống còn là monolith, việc đảm bảo chỉ có một process thực hiện một đoạn logic nào đó khá đơn giản.

Chúng ta có thể dùng:

  • mutex
  • synchronized
  • hoặc chỉ đơn giản là một biến lock trong memory

Nhưng mọi thứ thay đổi khi hệ thống chuyển sang distributed architecture.

Lúc này, cùng một service có thể chạy trên nhiều instance ở nhiều machine khác nhau. Và khi đó, in-memory lock hoàn toàn không còn tác dụng.

Một tình huống rất quen thuộc

Giả sử bạn có một job xử lý:

  • refresh cache
  • xử lý payment
  • update inventory
  • hoặc chạy cron job

Service của bạn được deploy với nhiều replicas để scale.

Điều gì có thể xảy ra?

Nhiều instance có thể cùng lúc thực hiện một logic đáng lẽ chỉ nên chạy một lần.

Ví dụ:

  • Cron job chạy 5 lần thay vì 1 lần
  • Payment bị trừ tiền hai lần
  • Inventory bị trừ stock nhiều lần

Đây là một vấn đề concurrency rất phổ biến trong microservices.

Distributed Lock ra đời để giải quyết vấn đề này

Distributed lock giúp đảm bảo rằng chỉ một service instance được phép thao tác trên một resource tại một thời điểm.

Một cách phổ biến để implement distributed lock là sử dụng Redis.

Ví dụ:

SET lock_key token NX PX ttl

Trong đó:

  • NX đảm bảo key chỉ được set khi chưa tồn tại
  • PX đặt TTL để tránh deadlock nếu process crash

Nếu instance set key thành công → acquire lock.

Các instance khác có thể:

  • retry
  • wait
  • hoặc fail fast

Redis thường được dùng cho mục đích này vì:

  • chạy in-memory nên rất nhanh
  • có các atomic operations
  • dễ tích hợp trong hệ thống distributed

Nhưng distributed lock không đơn giản như vẻ ngoài

Trong môi trường production, distributed lock có khá nhiều edge cases.

Process bị pause

Giả sử một service acquire lock với TTL là 10 giây.

Sau đó xảy ra:

  • GC pause
  • container bị CPU throttling
  • hoặc OS scheduling delay

Process bị pause 15 giây.

Trong thời gian đó:

  • lock đã expire
  • instance khác acquire lock

Khi process ban đầu resume:

Hai instance đều nghĩ rằng mình đang giữ lock. Logic quan trọng có thể chạy hai lần.

Network latency hoặc network partition

Distributed systems luôn phải đối mặt với:

  • network delay
  • packet loss
  • partial failure

Những tình huống này đôi khi khiến nhiều node cùng nghĩ rằng mình đang giữ lock.

Redis failover

Nếu Redis master crash trước khi lock được replicate sang replica:

  • replica có thể được promote thành master
  • lock có thể bị mất
  • Một client khác có thể acquire lại lock.

Mutual exclusion có thể bị phá vỡ.

Những thứ dev thường phải xử lý khi tự implement distributed lock

Trong thực tế, một distributed lock đúng nghĩa thường cần thêm nhiều thứ:

  • retry khi acquire lock
  • TTL để tránh deadlock
  • đảm bảo chỉ owner mới được release lock
  • extend lock nếu job chạy lâu
  • xử lý crash hoặc network delay

Chỉ riêng việc release lock an toàn cũng cần dùng Lua script để đảm bảo atomic check.

Vì vậy mình đã viết một thư viện nhỏ

Sau khi gặp pattern này nhiều lần khi làm việc với microservices, mình đã viết một thư viện nhỏ cho Node.js:

simple-distributed-lock

Mục tiêu rất đơn giản: một distributed lock nhẹ, dễ dùng, dựa trên Redis.

Thư viện cung cấp:

  • Redis-based distributed lock
  • token-based safe release bằng Lua script
  • retry mechanism khi acquire lock
  • auto-extend lock cho các job chạy lâu
  • helper using() để chạy critical section một cách an toàn

Ví dụ:

await lock.using("order:123", async () => {
  await processOrder()
})

Hoặc cách chi tiết hơn:

const token = await lock.acquire("order:123", {
  ttl: 5000,
  retryCount: 10,
  retryDelay: 200
})

if (!token) {
  throw new Error("Failed to acquire lock")
}

try {
  await processOrder()
} finally {
  await lock.release("order:123", token)
}

Điều này đảm bảo rằng chỉ một instance của service xử lý order tại một thời điểm.

Nếu bạn đang làm việc với Node.js microservices và cần một giải pháp distributed lock đơn giản, bạn có thể thử:

👉 https://www.npmjs.com/package/simple-distributed-lock

Mọi feedback hoặc góp ý đều rất welcome.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí