Redis 实现分布式锁

2021-06-23  本文已影响0人  小玉1991
工作中需要分布式锁。现在总结一下实现过程。

1、添加配置,引入依赖

1、添加配置

#redis相关配置
spring.redis.host=10.9.198.84
spring.redis.port=6380
spring.redis.database=0
spring.redis.jedis.pool.max-idle=200
spring.redis.jedis.pool.max-wait=5000

2、添加pom依赖
    <properties>
        <jedis.version>3.1.0</jedis.version>
        <spring-data-redis.version>1.8.0.RELEASE</spring-data-redis.version>
    </properties>

 <!-- redis begin -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>${jedis.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>${spring-data-redis.version}</version>
        </dependency>
 <!-- redis end -->

2、创建lua脚本,并构建相关工具类

release_distributed_lock.lua

local lockKey = KEYS[1]
local value = KEYS[2]
if redis.call('get', lockKey) == value then
    return redis.call('del', KEYS[1])
else
    return 0
end

try_get_distributed_lock.lua

local lockKey = KEYS[1]
local value = KEYS[2]
local expireTime = KEYS[3]
return redis.call("set", lockKey, value, "px", expireTime, 'NX');

request_rate_limiter.lua

local tokens_key = KEYS[1]
local timestamp_key = KEYS[2]
--redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key)

local rate = tonumber(KEYS[3])
local capacity = tonumber(KEYS[4])
local now = tonumber(KEYS[5])
local requested = tonumber(KEYS[6])

local fill_time = capacity/rate
local ttl = math.floor(fill_time*2)

--redis.log(redis.LOG_WARNING, "rate " .. ARGV[1])
--redis.log(redis.LOG_WARNING, "capacity " .. ARGV[2])
--redis.log(redis.LOG_WARNING, "now " .. ARGV[3])
--redis.log(redis.LOG_WARNING, "requested " .. ARGV[4])
--redis.log(redis.LOG_WARNING, "filltime " .. fill_time)
--redis.log(redis.LOG_WARNING, "ttl " .. ttl)

local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
    last_tokens = capacity
end
--redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens)

local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
    last_refreshed = 0
end
--redis.log(redis.LOG_WARNING, "last_refreshed " .. last_refreshed)

local delta = math.max(0, now-last_refreshed)
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
local allowed_num = 0
if allowed then
    new_tokens = filled_tokens - requested
    allowed_num = 1
end

--redis.log(redis.LOG_WARNING, "delta " .. delta)
--redis.log(redis.LOG_WARNING, "filled_tokens " .. filled_tokens)
--redis.log(redis.LOG_WARNING, "allowed_num " .. allowed_num)
--redis.log(redis.LOG_WARNING, "new_tokens " .. new_tokens)

redis.call("setex", tokens_key, ttl, new_tokens)
redis.call("setex", timestamp_key, ttl, now)

return { allowed_num, new_tokens }

工具类:

LuaHelper:

@Slf4j
public class LuaHelper {

    public final static String DISTRIBUTE_LOCK_GET = "get distributed lock";
    public final static String DISTRIBUTE_LOCK_RELEASE = "release distributed lock";
    public final static String REQUEST_RATE_LIMITER = "request rate limiter";

    public class LuaScript {
        public String script;
        public String sha1;
    }

    Map<String, LuaScript> map;


    private static LuaHelper instance = new LuaHelper();

    public static LuaHelper getInstance() {
        return instance;
    }

    private LuaHelper() {
        map = new HashMap<>();
        init();
    }

    void init() {
        log.info("LuaHelper init");
        // 分布式锁
        addScriptFromFile(DISTRIBUTE_LOCK_GET, "lua/try_get_distributed_lock.lua");
        addScriptFromFile(DISTRIBUTE_LOCK_RELEASE, "lua/release_distributed_lock.lua");
        addScriptFromFile(REQUEST_RATE_LIMITER, "lua/request_rate_limiter.lua");
    }



    void addScript(String key, String script) {
        LuaScript script1 = new LuaScript();
        script1.script = script;
        script1.sha1 = DigestUtils.sha1Hex(script);
        map.put(key, script1);
    }


    void addScriptFromFile(String key, String file_path) {
        ClassPathResource res = new ClassPathResource(file_path);
        try {
            byte[] bytes = FileCopyUtils.copyToByteArray(res.getInputStream());
            String data = new String(bytes, StandardCharsets.UTF_8);
            addScript(key, data);
        } catch (IOException e) {
            e.printStackTrace();
            log.error(" load script error " + file_path);
        }
    }

    public LuaScript getScript(String key) {
        if (!map.containsKey(key)) {
            return null;
        }
        return map.get(key);
    }

    public static Object runScript(RedisTemplate<String, String> redisTemplate, final String scriptKey, final  String... params){
        long start = System.currentTimeMillis();
        Object object = redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                LuaScript script = LuaHelper.getInstance().getScript(scriptKey);
                assert script != null;
                Jedis jedis = (Jedis) redisConnection.getNativeConnection();

                try {
                    return jedis.evalsha(script.sha1, params.length, params);
                } catch (Exception ex) {
                    log.info("params:" + params.length);
                    for(int i = 0; i < params.length; i++){
                        log.info(i + " " + params[i]);
                    }
                    return jedis.eval(script.script, params.length, params);
                }
            }
        });
//        log.info( "lua script: " +  scriptKey  + " "+ (System.currentTimeMillis() - start));
        return object;
    }

}

RedisService:

@Service
public class RedisService {
    @Autowired
    RedisTemplate<String, String> redisTemplate;

    /**
     * 尝试获取分布式锁
     *
     * @param lockKey
     * @param value
     * @param expireTime 过期时间 毫秒
     * @return
     */
    public boolean getDistributedLock(String lockKey, String value, Integer expireTime) {
        String key = getLockKey(lockKey);
        Object obj = LuaHelper.runScript(redisTemplate, LuaHelper.DISTRIBUTE_LOCK_GET, key, value, expireTime + "");
        return RedisCons.LOCK_SUCCESS.equals(obj);
    }

    private String getLockKey(String lockKey) {
        return RedisCons.KEY_PREFIX + RedisCons.LOCK_KEY_PREFIX + lockKey;
    }

    private String getCommonKey(String lockKey) {
        return RedisCons.KEY_PREFIX + lockKey;
    }

    /**
     * 尝试释放分布式锁
     *
     * @param lockKey
     * @param value
     * @return
     */
    public boolean releaseDistributedLock(String lockKey, String value) {
        String key = getLockKey(lockKey);
        Object obj = LuaHelper.runScript(redisTemplate, LuaHelper.DISTRIBUTE_LOCK_RELEASE, key, value);
        return RedisCons.RELEASE_SUCCESS.equals(obj);
    }


    public Long incrMachineId() {
        String key = RedisCons.KEY_PREFIX + RedisCons.KEY_INCR_MACHINE_ID;
        Long increment = redisTemplate.opsForValue().increment(key, 1);
        if (increment >= Integer.MAX_VALUE) {
            redisTemplate.opsForValue().set(key, "0");
        }
        return increment;
    }
}

配置多个redis源

#lock redis setting
spring.redis.lock.host=10.9.188.145
spring.redis.lock.port=6380
spring.redis.lock.database=0
spring.redis.lock.jedis.pool.max-total=500
spring.redis.lock.jedis.pool.max-idle=200
spring.redis.lock.jedis.pool.max-wait=5000


======================================================================


@Configuration
public class RedisLockConfig {
    @Value("${spring.redis.lock.host}")
    private String host;

    @Value("${spring.redis.lock.port}")
    private Integer port;

//    @Value("${spring.redis.lock.pwd}")
    private String password;

    @Value("${spring.redis.lock.jedis.pool.max-idle}")
    private Integer maxIdle;

    @Value("${spring.redis.lock.jedis.pool.max-total}")
    private Integer maxTotal;

    @Value("${spring.redis.lock.jedis.pool.max-wait}")
    private Integer maxWaitMillis;

    @Value("${spring.redis.lock.database}")
    private Integer dataBase;

    //配置工厂
    public RedisConnectionFactory connectionFactory(String host, int port, String password, int maxIdle,
                                                    int maxTotal, long maxWaitMillis, int index) {
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
        jedisConnectionFactory.setHostName(host);
        jedisConnectionFactory.setPort(port);

        if (!StringUtils.isEmpty(password)) {
            jedisConnectionFactory.setPassword(password);
        }

        if (index != 0) {
            jedisConnectionFactory.setDatabase(index);
        }

        jedisConnectionFactory.setPoolConfig(poolConfig(maxIdle, maxTotal, maxWaitMillis, false));
        jedisConnectionFactory.afterPropertiesSet();
        return jedisConnectionFactory;
    }

    //连接池配置
    public JedisPoolConfig poolConfig(int maxIdle, int maxTotal, long maxWaitMillis, boolean testOnBorrow) {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMaxTotal(maxTotal);
        poolConfig.setMaxWaitMillis(maxWaitMillis);
        poolConfig.setTestOnBorrow(testOnBorrow);
        return poolConfig;
    }

    @Bean(name = "lockRedisTemplate")
    public StringRedisTemplate lockRedisTemplate() {
        StringRedisTemplate redisTemplate = new StringRedisTemplate();
        redisTemplate.setConnectionFactory(
                connectionFactory(host, port, password, maxIdle, maxTotal, maxWaitMillis, dataBase));

        //设置序列化Key的实例化对象
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置序列化Value的实例化对象
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        return redisTemplate;
    }
}

其他代码,打包上传。

Redis分布式锁Demo下载

上一篇 下一篇

猜你喜欢

热点阅读