Triển Khóa Phân Tán Dựa Trên Redis

Phương Pháp 1: Sử Dụng Thư Viện Redisson

1. Cài Đặt Redis

Để sử dụng dịch vụ khóa của Redisson, phiên bản Redis của bạn phải cao hơn 2.6. Tham khảo hướng dẫn cài đặt Redis để biết thêm chi tiết.

2. Thêm Thư Viện Redisson

Thêm dependency Redisson vào file pom.xml:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>2.1.0</version>
</dependency>

3. Ví Dụ Thử Nghiệm

RedisConfiguration config = new RedisConfiguration();
config.setServerAddress("ipaddress:6379");
RedisClient redisClient = RedisClient.create(config);

for(int i=0; i<5; i++) {
    DistributedLock lock = redisClient.acquireLock("sampleLock");
    lock.acquire(10, TimeUnit.SECONDS); // Thời gian chờ 10 giây
    logger.info("ID của client đang giữ khóa là client{}", i);
    Thread.sleep(1000);
    logger.info("Client{} giải phóng khóa", i);
    lock.release();
}

4. Tài Tham Khảo

Redisson

Phương Pháp 2: Triển Khóa Tự Định Nghĩa

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;

/**
 * Triển khai khóa phân tán sử dụng Redis
 */
public final class DistributedLockManager {

    private static final String LOCK_KEY_TEMPLATE = "distributed:lock:%s";
    private static final int LOCK_EXPIRE_SECONDS = 20 * 60; // 20 phút
    private static final Logger logger = LoggerFactory.getLogger(DistributedLockManager.class);

    private DistributedLockManager() {
    }

    public static DistributedLockManager getInstance() {
        return new DistributedLockManager();
    }

    /**
     * Giành lấy khóa, thoát nếu hết thời gian chờ
     * @param lockId ID của khóa
     * @param timeout Thời gian chờ(ms)
     * @return true nếu giành được khóa, false nếu hết thời gian chờ
     */
    public boolean acquireLock(String lockId, int timeout) {
        // Jedis thực tế là một proxy qua JedisCallback và JedisFactoryBean
        Jedis jedisConnector = JedisConnectorFactory.getJedisInstance();

        long lockResult = 0;
        long startTime = System.currentTimeMillis();

        while (lockResult != 1) {
            long currentTime = System.currentTimeMillis();
            // Kiểm tra hết thời gian chờ
            if (timeout > 0 && currentTime > startTime + timeout) {
                return false;
            }
            
            long lockExpireTime = currentTime + LOCK_EXPIRE_SECONDS * 1000 + 1;

            try {
                String key = String.format(LOCK_KEY_TEMPLATE, lockId);
                lockResult = jedisConnector.setnx(key, String.valueOf(lockExpireTime));
                
                if (lockResult == 1) {
                    logger.info("Thiết lập khóa Redis thành công, key=" + key);
                    jedisConnector.expire(key, LOCK_EXPIRE_SECONDS);
                    logger.info("Thiết lập thời gian hết hạn cho khóa Redis thành công, key=" + key);
                } else {
                    String lockValue = jedisConnector.get(key);
                    Long previousLockTime = Long.parseLong(lockValue);

                    // Nếu khóa đã được dịch vụ khác nắm giữ, trả về false
                    if (jedisConnector.ttl(key) != -1 && previousLockTime > System.currentTimeMillis()) {
                        logger.info("Khóa Redis đã được dịch vụ khác nắm giữ, key=" + key);
                        return false;
                    }
                    
                    /**
                     * Nếu thời gian khóa trước đã hết hạn, xóa khóa để tác vụ định kỳ có thể bắt đầu
                     * Nguyên nhân không giải phóng khóa có thể do jedis.expire(key, EXPIRE) thất bại
                     * hoặc do lỗi chương trình/mạng khiến unlock không được gọi thành công
                     */
                    if (jedisConnector.ttl(key) == -1 || previousLockTime < System.currentTimeMillis()) {
                        jedisConnector.del(key);
                        continue;
                    }
                    
                    Thread.sleep(100);
                }
            } catch (Exception e) {
                logger.error("Lỗi khi lấy khóa từ Redis cho tác vụ định kỳ, key="+String.format(LOCK_KEY_TEMPLATE, lockId), e);
                return false;
            }
        }
        return true;
    }

    /**
     * Giải phóng khóa
     * @param lockId ID của khóa
     */
    public void releaseLock(String lockId) {
        // Jedis thực tế là một proxy qua JedisCallback và JedisFactoryBean
        Jedis jedisConnector = JedisConnectorFactory.getJedisInstance();
        String key = String.format(LOCK_KEY_TEMPLATE, lockId);
        jedisConnector.del(key);
    }
}

Lớp JedisConnectorFactory

/**
 * Factory tạo Jedis connector
 */
@Component
public class JedisConnectorFactory implements ApplicationContextAware {

    private static volatile ApplicationContext applicationContext;

    /**
     * Tạo instance Jedis
     *
     * @return Jedis instance
     */
    public static Jedis getJedisInstance() {
        return applicationContext.getBean(Jedis.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext appContext) {
        if (applicationContext != null) {
            return;
        }
        synchronized (JedisConnectorFactory.class) {
            if (applicationContext != null) {
                return;
            }
            applicationContext = appContext;
        }
    }
}

Thẻ: Redis Khóa Phân Tán Redisson Jedis phân tán hệ thống

Đăng vào ngày 7 tháng 6 lúc 19:29