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

10 篇文章 0 订阅
10 篇文章 1 订阅

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 com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
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.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Protocol;
import redis.clients.util.SafeEncoder;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.concurrent.Semaphore;

/**
 * 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 {
                semaphore.acquire();
                return redisTemplate;
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }

        public static void release() {
            semaphore.release();
        }

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

    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) RedisTemplateHolder.execute(new Statement() {
            @Override
            public Object prepare(RedisTemplate redisTemplate) {
                return redisTemplate.execute(new RedisCallback<Boolean>() {
                    @Override
                    public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                        Object obj = connection.execute("set", serialize(key), serialize(value), SafeEncoder.encode("NX"), SafeEncoder.encode("EX"), Protocol.toByteArray(expire));
                        return obj != null;
                    }
                });
            }
        }, redisTemplate);

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

        return result;
    }

    public void delete(final String key) {
        RedisTemplateHolder.execute(new Statement() {
            public Object prepare(RedisTemplate redisTemplate) {
                redisTemplate.delete(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转实体忽略未知属性
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 实体转json忽略null
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        serializer.setObjectMapper(objectMapper);
        return serializer;
    }

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

    private byte[] serialize(Object object, SerializeFormat sf) {
        if (object == null) {
            return new byte[0];
        }
        if (sf == SerializeFormat.BLOB) {
            return blobSerializer.serialize(object);
        }
        if (object instanceof String || CacheKeyGenerator.isPrimitive(object.getClass())) {
            return stringSerializer.serialize(String.valueOf(object));
        } else {
            return configuredJackson2JsonRedisSerializer(object.getClass()).serialize(object);
        }
    }
}

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

/**
 * Redis序列化形式
 **/
public enum SerializeFormat {
	// 字符串序列化形式,基本类型(包装类型)、字符串和可JSON化的数据类型才能选用
	STRING,
	// 二进制对象序列化形式,所有可序列化java对象类型
	BLOB,
	;
}
  • 31
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值