Redis分布式锁/Redis的setnx命令如何设置key的失效时间(同时操作setnx和expire)
Redis的setnx命令是当key不存在时设置key,但setnx不能同时完成expire设置失效时长,即不能保证setnx和expire的原子性。我们可以使用set命令完成setnx和expire的操作,并且这种操作是原子操作。下面是set命令的可选项:set key value [EX seconds] [PX milliseconds] [NX|XX]EX seconds:设置失效时...
·
Redis
的setnx
命令是当key
不存在时设置key
,但setnx
不能同时完成expire
设置失效时长,不能保证setnx
和expire
的原子性。我们可以使用set
命令完成setnx
和expire
的操作,并且这种操作是原子操作。
下面是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
是单线程的,因此上面的操作可以看成setnx
和expire
的结合体,是原子性的。
在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,
;
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
已为社区贡献5条内容
所有评论(0)