Redis分布式锁/Redis的setnx命令如何设置key的失效时间(同时操作setnx和expire)

时间:2025-04-26 11:56:37

Redissetnx命令是当key不存在时设置key,但setnx不能同时完成expire设置失效时长,不能保证setnxexpire的原子性。我们可以使用set命令完成setnxexpire的操作,并且这种操作是原子操作。
下面是set命令的可选项:

set key value [EX seconds] [PX milliseconds] [NX|XX]
EX seconds:设置失效时长,单位秒
PX milliseconds:设置失效时长,单位毫秒
NX:key不存在时设置value,成功返回OK,失败返回(nil)
XX:key存在时设置value,成功返回OK,失败返回(nil)

案例:设置name=p7+,失效时长100s,不存在时设置
1.1.1.1:6379> set name p7+ ex 100 nx
OK
1.1.1.1:6379> get name
"p7+"
1.1.1.1:6379> ttl name
(integer) 94

从上面可以看出,多个命令放在同一个redis连接中并且redis是单线程的,因此上面的操作可以看成setnxexpire的结合体,是原子性的。

Java中,如何使用RedisTemplate封装上述操作呢?

import ;
import ;
import ;
import ;
import ;
import ;
import ;
import .Jackson2JsonRedisSerializer;
import ;
import ;
import ;
import ;
import ;
import ;

import ;
import ;
import ;

/**
 * Redis拓展set为setnx
 **/
@Component
public class RedisStringOps {

    /**
     * RedisTemplate 装饰器
     * @date 2019/6/11 14:45
     **/
    private static class RedisTemplateHolder {

        /**
         * 最大有20个redis连接被使用,其他的连接要等待令牌释放
         * 令牌数量自己定义,这个令牌是为了避免高并发下,获取redis连接数时,抛出的异常
         * 在压力测试下,性能也很可观
         */
        private static Semaphore semaphore = new Semaphore(20);

        private RedisTemplateHolder() {
        }

        public static RedisTemplate getRedisTemplate(RedisTemplate redisTemplate) {
            try {
                ();
                return redisTemplate;
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }

        public static void release() {
            ();
        }

        public static Object execute(Statement statement, RedisTemplate<String, Object> redisTemplate) {
            try {
                return (getRedisTemplate(redisTemplate));
            } finally {
                ();
            }
        }
    }

    private interface Statement {
        Object prepare(final RedisTemplate redisTemplate);
    }

    @Resource
    private RedisTemplate redisTemplate;

    private static RedisSerializer<String> stringSerializer = new StringRedisSerializer();
    private static RedisSerializer<Object> blobSerializer = new JdkSerializationRedisSerializer();

    /**
     * 如果key不存在,set key and expire key
     *
     * @param key
     * @param value
     * @param expire
     * @return
     */
    public boolean setAndExpireIfAbsent(final String key, final Serializable value, final long expire) {

        Boolean result = (Boolean) (new Statement() {
            @Override
            public Object prepare(RedisTemplate redisTemplate) {
                return (new RedisCallback<Boolean>() {
                    @Override
                    public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                        Object obj = ("set", serialize(key), serialize(value), ("NX"), ("EX"), (expire));
                        return obj != null;
                    }
                });
            }
        }, redisTemplate);

        return result;
    }
    
	public boolean setIfAbsent(final String key, final Serializable value) {
        Boolean result = (Boolean) (new Statement() {
            @Override
            public Object prepare(RedisTemplate redisTemplate) {
                return (new RedisCallback<Boolean>() {
                    @Override
                    public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                        Object obj = ("set", serialize(key), serialize(value), ("NX"));
                        return obj != null;
                    }
                });
            }
        }, redisTemplate);

        return result;
    }

    public void delete(final String key) {
        (new Statement() {
            public Object prepare(RedisTemplate redisTemplate) {
                (serialize(key));
                return null;
            }
        }, redisTemplate);
    }

    private <T> Jackson2JsonRedisSerializer<T> configuredJackson2JsonRedisSerializer(Class<T> clazz) {
        Jackson2JsonRedisSerializer<T> serializer = new Jackson2JsonRedisSerializer<T>(clazz);
        ObjectMapper objectMapper = new ObjectMapper();
        // json转实体忽略未知属性
        (DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 实体转json忽略null
        (.NON_NULL);
        (objectMapper);
        return serializer;
    }

    private byte[] serialize(Object object) {
        return serialize(object, );
    }

    private byte[] serialize(Object object, SerializeFormat sf) {
        if (object == null) {
            return new byte[0];
        }
        if (sf == ) {
            return (object);
        }
        if (object instanceof String || (())) {
            return ((object));
        } else {
            return configuredJackson2JsonRedisSerializer(()).serialize(object);
        }
    }
}

/**
 * 工具方法
 * 判定指定的 Class 对象是否表示一个基本类型或者包装器类型
 * @param clazz
 * @return
 */
@SuppressWarnings("rawtypes")
public static boolean isPrimitive(Class clazz){
	if(()){
		return true;
	} else
		try {
			if(("TYPE") !=null && 
					((Class)(("TYPE").get(null))).isPrimitive()){
				return true;
			}
		} catch (Exception e) {
		}
	return false;
}

/**
 * Redis序列化形式
 **/
public enum SerializeFormat {
	// 字符串序列化形式,基本类型(包装类型)、字符串和可JSON化的数据类型才能选用
	STRING,
	// 二进制对象序列化形式,所有可序列化java对象类型
	BLOB,
	;
}