採用 redis 設計分佈式鎖 (二)

上一篇我們介紹了一種redis 分佈式鎖,介紹了其應用場景和一些存在的問題,而今天我們介紹 redis 第二種分佈式鎖的使用;

這種鎖同時採用lua腳本保證原子性能很好解決以上問題。實例如下:

1、代碼如下:

package com.jy.utils;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCommands;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * RedisTemplate工具類
 */
@Component
public class RedisUtil implements ApplicationContextAware {

    private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);

    private static RedisTemplate<Object, Object> redisTemplate;

    private static ThreadLocal<String> lockFlag = new ThreadLocal<String>();

    public static final String UNLOCK_LUA;

    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call('get',KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call('del',KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }

    //加鎖核心方法
    public static boolean setRedisLock(String key, long expire) {
        try {
            String result = redisTemplate.execute(new RedisCallback<String>() {
                @Override
                public String doInRedis(RedisConnection connection) throws DataAccessException {
                    JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                    String uuid = UUID.randomUUID().toString();
                    lockFlag.set(uuid);//給該線程設置唯一標識,釋放所得時候會用到
                    return commands.set(key, uuid, "NX", "PX", expire);//鎖和超時時間同時設置,保證改鎖的原子性
                }
            });
            logger.info("獲取鎖成功[{}]結果:[{}]",key,result);
            return StringUtils.isNotEmpty(result);
        } catch (Exception e) {
            logger.error("set redis occured an exception", e);
        }
        return false;
    }

     //釋放鎖的核心方法
    public static boolean releaseRedisLock(String key) {
        /** 釋放鎖的時候,有可能因爲持鎖之後方法執行時間大於鎖的有效期,
         *此時有可能已經被另外一個線程持有鎖,所以不能直接刪除,需要判斷該線程的value 值(加鎖時的uuid 值),
         */
        try {
            List<String> keys = new ArrayList<String>();
            keys.add(key);
            List<String> args = new ArrayList<String>();
            args.add(lockFlag.get());//設置需要刪除的線程的標識value值(每個線程精準刪除自己設置的鎖)

            /** 使用lua腳本刪除redis中匹配value的key,
               * 可以避免由於方法執行時間過長而redis鎖自動過期失效的時候誤刪其他線程的鎖
               * 從而避免鎖的錯亂
             */
            Long result = redisTemplate.execute(new RedisCallback<Long>() {
                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    Object nativeConnection = connection.getNativeConnection();
                    /** 單機模式 */
                    return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
                }
            });
            return result != null && result > 0;
        } catch (Exception e) {
            logger.error("release lock occured an exception", e);
        }
        return false;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        redisTemplate = applicationContext.getBean(RedisTemplate.class);
    }

}

2、這種redis 分佈式鎖,支持多線程、安全性更好,可以試一下。下篇我們再介紹一種redis 分佈式鎖,對比一下,根據不同場景選擇合適的分佈式鎖,敬請期待!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章