spring boot RedisTemplate 实现 setnx exptime (扩展 redisTemplate.setIfAbsent)

之前用 redisTemplate 实现setnx exptime 时 是分两步的

  1. redisTemplate.setIfAbsent
  2. redisTemplate.expire

这样的不是原子性的 可能在第一步与第二步之间 重新发布了或者服务器重启了 这个key就永远不会消失了 可以采用以下的方法

在spiring boot 1.5 版本 可以使用以下扩展

public static boolean setIfAbsent(final String key, final Serializable value, final long exptime) {
    Boolean result = (Boolean) redisTemplate.execute(new RedisCallback() {
        @Override
        public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
            RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
            RedisSerializer keySerializer = redisTemplate.getKeySerializer();
            Object obj = connection.execute("set", keySerializer.serialize(key), 
                                valueSerializer.serialize(value),
                                "NX".getBytes(StandardCharsets.UTF_8),
                                "EX".getBytes(StandardCharsets.UTF_8),
                                String.valueOf(exptime).getBytes(StandardCharsets.UTF_8));
            return obj != null;
        }
    });
    return result;
}

在spiring boot 2 可以直接使用 redisTemplate的

Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit);

public boolean setIfAbsent(String key, String value, long expireTime) {

        Boolean result = redisTemplate.opsForValue().setIfAbsent(key,value,expireTime, TimeUnit.SECONDS);

        return result;
}

单机RedisLock的正确姿势 (集群用redisson):
加锁:

 1. 通过setnx 向特定的key写入一个随机数,并设置失效时间,写入成功即加锁成功
 2. 注意点:
    - 必须给锁设置一个失效时间            ----->    避免死锁
    - 加锁时,每个节点产生一个随机字符串    ----->    避免锁误删
    - 写入随机数与设置失效时间必须是同时    ----->    保证加锁的原子性
 使用:
   SET key value NX PX 3000

解锁:

解锁:
	匹配随机数,删除redis上的特定的key数据,
	要保证获取数据,判断一致以及删除数据三个操作是原子性
	执行如下lua脚本:
		if redis.call('get', KEYS[1]) == ARGV[1] then
			return redis.call('del', KEYS[1])
		else
			return 0
		end

伪代码示例:使用的是Jedis客户端

		
			redis.clients
			jedis
			2.9.0
		
package com.redis.mq.util;

import redis.clients.jedis.Jedis;

import java.util.Collections;
import java.util.UUID;

/**
 * @author qinjp
 * @date 2019-05-31
 **/
public class RedisLock {
    /**
     * RedisLock的正确姿势
     * 加锁:
     * 通过setnx 向特定的key写入一个随机数,并设置失效时间,写入成功即加锁成功
     * 注意点:
     *  必须给锁设置一个失效时间            ----->    避免死锁
     *  加锁时,每个节点产生一个随机字符串    ----->    避免锁误删
     *  写入随机数与设置失效时间必须是同时    ----->    保证加锁的原子性
     *  使用:
     *      SET key value NX PX 3000
     *
     * 解锁:
     *  匹配随机数,删除redis上的特定的key数据,
     *  要保证获取数据,判断一致以及删除数据三个操作是原子性
     *  执行如下lua脚本:
     *      if redis.call('get', KEYS[1]) == ARGV[1] then
     *          return redis.call('del', KEYS[1])
     *      else
     *          return 0
     *      end
     *
     */
    // 使用jedis 客户端的
    /**SET key value NX PX 3000 成功返回值*/
    private static final String LOCK_SUCCESS = "OK";
    /**表示 NX 模式*/
    private static final String SET_IF_NOT_EXIST = "NX";
    /**单位 毫秒**/
    private static final String SET_WITH_EXPIRE_TIME_PX = "PX";
    /**lua脚本**/
    private static final String SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    /**存储随机数**/
    private static final ThreadLocal local = new ThreadLocal<>();
    /**
     * 加锁
     */
    public static boolean lock(Jedis jedis, String key, int expireTime) {
        // 产生随机数
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");

        String result = jedis.set(key, uuid, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME_PX, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            // 随机数绑定线程
            local.set(uuid);
            return true;
        }
        return false;

    }

   /**
     * 释放分布式锁
     */
    public static boolean unLock(Jedis jedis, String key) {

        String uuid = local.get();
	    //当前线程没有绑定uuid
        //直接返回
        if (uuid == null || "".equals(uuid)) {
            return false;
        }
        
        Object result = jedis.eval(SCRIPT, Collections.singletonList(key), Collections.singletonList(uuid));

        if (Long.valueOf(1).equals(result)) {
            // 解除绑定线程的随机数
            local.remove();
            return true;
        }
        return false;
    }


    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        jedis.auth("373616885");
        jedis.select(0);
        final String LOCK_KEY = "LOCK_KEY";
        
        RedisLock.lock(jedis,LOCK_KEY,5000);

        RedisLock.unLock(jedis,LOCK_KEY);

    }

}


你可能感兴趣的:(spring boot RedisTemplate 实现 setnx exptime (扩展 redisTemplate.setIfAbsent))