0
0
Lập trình
Flame Kris
Flame Krisbacodekiller

Giải Quyết Anomaly Lost Update Trong Spring Boot Với Redlock

Đăng vào 7 tháng trước

• 5 phút đọc

Giới thiệu

Trong các hệ thống phân tán hiện đại, việc đảm bảo tính nhất quán của dữ liệu dưới tải cao là một trong những thách thức lớn nhất. Mặc dù cơ sở dữ liệu cung cấp các đảm bảo giao dịch, nhưng khi nhiều luồng hoặc dịch vụ tương tác đồng thời, chúng ta thường dựa vào các khóa phân tán để điều phối truy cập vào các tài nguyên chia sẻ.

Một trong những lựa chọn phổ biến là Redlock, một thuật toán khóa phân tán được triển khai trên nền tảng Redis. Nó cung cấp khả năng chịu lỗi và sự công bằng trong các môi trường phân tán. Tuy nhiên, việc sử dụng Redlock một cách chính xác đòi hỏi nhiều hơn là chỉ đơn giản là lấy và giải phóng khóa - nó đòi hỏi sự đồng bộ hóa cẩn thận với các giao dịch cơ sở dữ liệu.

Gần đây, tôi đã gặp một vấn đề thực tế trong một ứng dụng Spring Boot, nơi Redlock vô tình gây ra một anomaly lost update. Dưới đây là câu chuyện về cách tôi gỡ lỗi, hiểu nguyên nhân gốc rễ và triển khai một giải pháp với AOP tùy chỉnh giúp hệ thống trở nên kiên cố hơn.

Vấn đề: Lost Update với Redlock

Hệ thống được thiết kế để xử lý các yêu cầu đồng thời, với Redlock đảm bảo chỉ một luồng có thể sửa đổi một thực thể chia sẻ tại một thời điểm. Trên giấy, điều này nên ngăn chặn các xung đột.

Nhưng dưới tải, tôi nhận thấy điều gì đó kỳ lạ:

  1. Luồng A đã lấy khóa và cập nhật cơ sở dữ liệu.
  2. Trước khi giao dịch của Luồng A hoàn thành, khóa đã được giải phóng.
  3. Luồng B đã lấy khóa và đọc dữ liệu cũ, sau đó áp dụng cập nhật của nó.
  4. Khi giao dịch của Luồng A cuối cùng được thực hiện, cập nhật của Luồng B đã bị ghi đè - một anomaly lost update điển hình.

Nguyên nhân chính của vấn đề là Redlock đã giải phóng khóa sớm hơn so với việc giao dịch cơ sở dữ liệu hoàn thành. Vòng đời của khóa phân tán và vòng đời giao dịch cơ sở dữ liệu không được đồng bộ hóa.

Tại sao điều này lại xảy ra

Các giao dịch cơ sở dữ liệu được quản lý bởi Spring (thông qua @Transactional). Một giao dịch được thực hiện sau khi phương thức được chú thích hoàn tất.

Tuy nhiên, Redlock lại là bên ngoài. Một khi phần thân của phương thức hoàn thành, nhiều triển khai sẽ giải phóng khóa - mà không chờ đợi việc hoàn thành giao dịch DB.

Khoảng thời gian nhỏ này tạo ra một cửa sổ nơi các luồng khác có thể xen vào, gây ra việc ghi không nhất quán.

Đây là một ví dụ điển hình về cách các khóa phân tán và ranh giới giao dịch phải được phối hợp để đạt được tính chính xác.

Giải pháp: AOP Tùy Chỉnh với Đồng Bộ Hóa Giao Dịch

Để khắc phục vấn đề, tôi đã tạo ra một lớp AOP tùy chỉnh liên kết với vòng đời giao dịch.

Ý tưởng rất đơn giản:

  1. Lấy khóa Redis trước khi thực thi phần quan trọng của mã.
  2. Ràng buộc việc giải phóng khóa với giai đoạn hoàn thành giao dịch DB.
  3. Giải phóng khóa chỉ sau khi giao dịch đã hoàn tất.

Trong Spring, điều này có thể đạt được bằng cách sử dụng TransactionSynchronizationManager, cung cấp các callback cho các sự kiện giao dịch.

Phác thảo mã giả:

java Copy
@Around("@annotation(RedisLock)")  
public Object around(ProceedingJoinPoint pjp) throws Throwable {  
    String lockKey = getLockKey(pjp);  
    RLock lock = redissonClient.getLock(lockKey);
    lock.lock(); // Lấy khóa Redis

    try {
        // Đăng ký callback để giải phóng khóa *sau* khi hoàn thành
        TransactionSynchronizationManager.registerSynchronization(
            new TransactionSynchronizationAdapter() {
                @Override
                public void afterCompletion () {
                    lock.unlock();
                }
            }
        );

        return pjp.proceed(); // Tiến hành thực thi phương thức
    } catch (Exception e) {
        lock.unlock(); // Bảo vệ an toàn
        throw e;
    }
}

Lợi ích của Cách Tiếp Cận Này

  • ✅ Không còn mất cập nhật - Khóa gắn liền trực tiếp với vòng đời giao dịch DB.
  • ✅ Độ tin cậy được cải thiện - Các luồng luôn hoạt động trên dữ liệu đã được cam kết, nhất quán.
  • ✅ Tích hợp liền mạch - AOP đảm bảo sự can thiệp tối thiểu vào logic nghiệp vụ.
  • ✅ Có thể mở rộng - Có thể điều chỉnh cho các chiến lược khóa khác hoặc các trình quản lý giao dịch khác.

Bài Học Chính

Kinh nghiệm này đã củng cố một số bài học quan trọng:

  • Một khóa phân tán không đủ - nó phải đồng bộ với khái niệm của cơ sở dữ liệu về “khi nào công việc đã hoàn tất.”
  • Trong các hệ thống có độ đồng thời cao, các điều kiện đua tinh vi thường ẩn nấp trong khoảng trống giữa các công cụ bên ngoài (Redis) và các đảm bảo nội bộ (giao dịch Spring).
  • AOP + đồng bộ hóa giao dịch là một sự kết hợp mạnh mẽ để thực thi tính nhất quán mà không làm rối logic nghiệp vụ.

Kết luận

Các hệ thống phân tán yêu cầu cả tính chính xác và hiệu suất. Các công cụ như Redlock cung cấp các nguyên tắc mạnh mẽ, nhưng chúng phải được tích hợp cẩn thận vào quy trình công việc của ứng dụng. Bằng cách đồng bộ hóa các khóa Redis với các giao dịch cam kết, chúng ta có thể loại bỏ các anomaly như mất cập nhật và xây dựng các hệ thống vừa đáng tin cậy vừa có thể mở rộng.

Nếu bạn đang xây dựng với Spring Boot + Redis, tôi khuyến khích bạn tìm hiểu sâu hơn về cách các khóa của bạn tương tác với các ranh giới giao dịch. Điều này có thể giúp bạn tránh khỏi các lỗi tinh vi trong môi trường sản xuất.

Bạn đã gặp những thách thức tương tự với các khóa phân tán hoặc đồng bộ hóa giao dịch chưa? Tôi rất muốn nghe về các giải pháp và cách tiếp cận của bạn trong phần bình luận!

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào