本章是整理知识内容,为强化知识长期更新。
spring-redis-string
Spring-data-redis 有两个客户端,本章节主要使用lettuce来作为代码演示。尽可能面向redis编程。
- 参考文献redis-string
代码实现
- 接口
package cn.z201.learning.redis.operating.service;
import java.util.HashMap;
import java.util.List;
/**
* @author z201.coding@gmail.com
* @see cn.z201.learning.redis.operating.service.impl.RedisStringImpl
**/
public interface RedisStringI {
/**
* 将字符串值 value 关联到 key 。
* 如果 key 已经持有其他值, SET 就覆写旧值, 无视类型。
* 当 SET 命令对一个带有生存时间(TTL)的键进行设置之后, 该键原有的 TTL 将被清除。
* {@link = http://redisdoc.com/string/set.html }
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @param value
* @return
*/
Boolean set(String key, String value);
/**
* 只在键 key 不存在的情况下, 将键 key 的值设置为 value 。
* 若键 key 已经存在, 则 SETNX 命令不做任何动作。
* SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
* {@link = http://redisdoc.com/string/setnx.html }
*
* @param key
* @param value
* @return
*/
Boolean setNx(String key, String value);
/**
* 将键 key 的值设置为 value , 并将键 key 的生存时间设置为 seconds 秒钟。
* 如果键 key 已经存在, 那么 SETEX 命令将覆盖已有的值。
* <p>
* {@link = http://redisdoc.com/string/setex.html}
* 可用版本: >= 2.0.0
* 时间复杂度: O(1)
*
* @param key
* @param timeout seconds 秒
* @param value
* @return
*/
Boolean setEx(String key, Long timeout, String value);
/**
* 这个命令和 SETEX 命令相似, 但它以毫秒为单位设置 key 的生存时间, 而不是像 SETEX 命令那样以秒为单位进行设置。
* <p>
* {@link = http://redisdoc.com/string/psetex.html}
* 可用版本: >= 2.6.0
* 时间复杂度: O(1)
*
* @param key
* @param timeout milliseconds
* @param value
* @return
*/
Boolean pSetEx(String key, Long timeout, String value);
/**
* 返回与键 key 相关联的字符串值。
* {@link = http://redisdoc.com/string/get.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @return
*/
Object get(String key);
/**
* 获取key设置的value,并返回之前设置的值。
* {@link = http://redisdoc.com/string/getset.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @param value
* @return
*/
Object getSet(String key, String value);
/**
* 返回key对应value的长度
* {@link = http://redisdoc.com/string/strlen.html}
* 可用版本: >= 2.2.0
* 复杂度: O(1)
*
* @param key
* @return
*/
Long strLen(String key);
/**
* 如果键 key 已经存在并且它的值是一个字符串, APPEND 命令将把 value 追加到键 key 现有值的末尾。
* 如果 key 不存在, APPEND 就简单地将键 key 的值设为 value , 就像执行 SET key value 一样。
* {@link = http://redisdoc.com/string/append.html}
* 可用版本: >= 2.0.0
* 时间复杂度: 平摊O(1)
*
* @param key
* @param value
* @return
*/
Long appEnd(String key, String value);
/**
* 从偏移量 offset 开始, 用 value 参数覆写(overwrite)键 key 储存的字符串值。
* 不存在的键 key 当作空白字符串处理。
* SETRANGE 命令会确保字符串足够长以便将 value 设置到指定的偏移量上,
* 如果键 key 原来储存的字符串长度比偏移量小(比如字符串只有 5 个字符长,
* 但你设置的 offset 是 10 ), 那么原字符和偏移量之间的空白将用零字节(zerobytes, "\x00" )进行填充。
* <p>
* {@link = http://redisdoc.com/string/setrange.html}
* 可用版本: >= 2.2.0
* 时间复杂度:对于长度较短的字符串,命令的平摊复杂度O(1);对于长度较大的字符串,命令的复杂度为 O(M) ,其中 M 为 value 的长度。
*
* @param key
* @param offset
* @param value
* @return
*/
Boolean setRange(String key, Long offset, String value);
/**
* 返回键 key 储存的字符串值的指定部分, 字符串的截取范围由 start 和 end 两个偏移量决定 (包括 start 和 end 在内)。
* 负数偏移量表示从字符串的末尾开始计数, -1 表示最后一个字符, -2 表示倒数第二个字符, 以此类推。
* GETRANGE 通过保证子字符串的值域(range)不超过实际字符串的值域来处理超出范围的值域请求。
* <p>
* {@link = http://redisdoc.com/string/setrange.html}
* 可用版本: >= 2.4.0
* 时间复杂度: O(N),其中 N 为被返回的字符串的长度。
*
* @param key
* @param start
* @param end
* @return
*/
Object getRange(String key, Long start, Long end);
/**
* 为键 key 储存的数字值加上一。
* 如果键 key 不存在, 那么它的值会先被初始化为 0 , 然后再执行 INCR 命令。
* 如果键 key 储存的值不能被解释为数字, 那么 INCR 命令将返回一个错误。
* 本操作的值限制在 64 位(bit)有符号数字表示之内。
* <p>
* {@link = http://redisdoc.com/string/incr.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @return
*/
Long incr(String key);
/**
* 为键 key 储存的数字值加上增量 increment 。
* 如果键 key 不存在, 那么键 key 的值会先被初始化为 0 , 然后再执行 INCRBY 命令。
* 如果键 key 储存的值不能被解释为数字, 那么 INCRBY 命令将返回一个错误。
* 本操作的值限制在 64 位(bit)有符号数字表示之内。
* <p>
* {@link = http://redisdoc.com/string/incrby.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @param sum
* @return
*/
Long incrBy(String key, Long sum);
/**
* 为键 key 储存的值加上浮点数增量 increment 。
* 如果键 key 不存在, 那么 INCRBYFLOAT 会先将键 key 的值设为 0 , 然后再执行加法操作。
* 如果命令执行成功, 那么键 key 的值会被更新为执行加法计算之后的新值, 并且新值会以字符串的形式返回给调用者。
* 无论是键 key 的值还是增量 increment , 都可以使用像 2.0e7 、 3e5 、 90e-2 那样的指数符号(exponential notation)来表示, 但是,
* 执行 INCRBYFLOAT 命令之后的值总是以同样的形式储存, 也即是, 它们总是由一个数字,
* 一个(可选的)小数点和一个任意长度的小数部分组成(比如 3.14 、 69.768 ,诸如此类), 小数部分尾随的 0 会被移除,
* 如果可能的话, 命令还会将浮点数转换为整数(比如 3.0 会被保存成 3 )。
* 此外, 无论加法计算所得的浮点数的实际精度有多长, INCRBYFLOAT 命令的计算结果最多只保留小数点的后十七位。
* 当以下任意一个条件发生时, 命令返回一个错误:
* 键 key 的值不是字符串类型(因为 Redis 中的数字和浮点数都以字符串的形式保存,所以它们都属于字符串类型);
* 键 key 当前的值或者给定的增量 increment 不能被解释(parse)为双精度浮点数。
* <p>
* {@link = http://redisdoc.com/string/incrbyfloat.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @param sum
* @return
*/
Float incrByFloat(String key, Float sum);
/**
* 为键 key 储存的数字值减去一。
* 如果键 key 不存在, 那么键 key 的值会先被初始化为 0 , 然后再执行 DECR 操作。
* 如果键 key 储存的值不能被解释为数字, 那么 DECR 命令将返回一个错误。
* 本操作的值限制在 64 位(bit)有符号数字表示之内。
* <p>
* {@link = http://redisdoc.com/string/decr.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @return
*/
Long decr(String key);
/**
* 将键 key 储存的整数值减去减量 decrement 。
* 如果键 key 不存在, 那么键 key 的值会先被初始化为 0 , 然后再执行 DECRBY 命令。
* 如果键 key 储存的值不能被解释为数字, 那么 DECRBY 命令将返回一个错误。
* 本操作的值限制在 64 位(bit)有符号数字表示之内。
* <p>
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @param sum
* @return
*/
Long decrBy(String key, Long sum);
/**
* 同时为多个键设置值。
* 如果某个给定键已经存在, 那么 MSET 将使用新值去覆盖旧值,
* 如果这不是你所希望的效果, 请考虑使用 MSETNX 命令, 这个命令只会在所有给定键都不存在的情况下进行设置。
* MSET 是一个原子性(atomic)操作, 所有给定键都会在同一时间内被设置, 不会出现某些键被设置了但是另一些键没有被设置的情况。
* {@link = http://redisdoc.com/string/mset.html}
* 可用版本: >= 1.0.1
* 时间复杂度: O(N),其中 N 为被设置的键数量。
*
* @param map
* @return
*/
Boolean mSet(HashMap<String, Object> map);
/**
* 当且仅当所有给定键都不存在时, 为所有给定键设置值。
* 即使只有一个给定键已经存在, MSETNX 命令也会拒绝执行对所有键的设置操作。
* MSETNX 是一个原子性(atomic)操作, 所有给定键要么就全部都被设置, 要么就全部都不设置, 不可能出现第三种状态。
* {@link = http://redisdoc.com/string/msetnx.html}
* 可用版本: >= 1.0.1
* 时间复杂度: O(N), 其中 N 为被设置的键数量。
*
* @param map
* @return
*/
Boolean mSetNx(HashMap<String, Object> map);
/**
* 返回给定的一个或多个字符串键的值。
* 如果给定的字符串键里面, 有某个键不存在, 那么这个键的值将以特殊值 nil 表示。
* {@link = http://redisdoc.com/string/mget.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(N) ,其中 N 为给定键的数量。
*
* @param key
* @return
*/
List<Object> mGet(String[] key);
}
- 代码实现
package cn.z201.learning.redis.operating.service.impl;
import cn.z201.learning.redis.operating.Tools;
import cn.z201.learning.redis.operating.service.RedisStringExpansionI;
import cn.z201.learning.redis.operating.service.RedisStringI;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* @author z201.coding@gmail.com
**/
@Slf4j
@Service
public class RedisStringImpl implements RedisStringI {
@Autowired
RedisTemplate redisTemplate;
@Override
public Boolean set(String key, String value) {
Tools.notNull(key, value);
return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
return connection.set(stringRedisSerializer.serialize(key),
stringRedisSerializer.serialize(value));
});
}
@Override
public Boolean setNx(String key, String value) {
Tools.notNull(key, value);
return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
return connection.setNX(stringRedisSerializer.serialize(key),
stringRedisSerializer.serialize(value));
});
}
@Override
public Boolean setEx(String key, Long timeout, String value) {
Tools.notNull(key, value);
Tools.notNull(timeout);
return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
return connection.setEx(stringRedisSerializer.serialize(key),
timeout,
stringRedisSerializer.serialize(value));
});
}
@Override
public Boolean pSetEx(String key, Long timeout, String value) {
Tools.notNull(key, value);
Tools.notNull(timeout);
return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
return connection.pSetEx(stringRedisSerializer.serialize(key),
timeout,
stringRedisSerializer.serialize(value));
});
}
@Override
public Object get(String key) {
Tools.notNull(key);
return redisTemplate.execute((RedisCallback<Object>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
byte[] bytes = connection.get(stringRedisSerializer.serialize(key));
if (null != bytes || bytes.length != 0) {
return stringRedisSerializer.deserialize(bytes);
}
return null;
});
}
@Override
public Object getSet(String key, String value) {
Tools.notNull(key, value);
return redisTemplate.execute((RedisCallback<Object>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
byte[] bytes = connection.getSet(stringRedisSerializer.serialize(key)
, stringRedisSerializer.serialize(value));
if (null != bytes || bytes.length != 0) {
return stringRedisSerializer.deserialize(bytes);
}
return null;
});
}
@Override
public Long strLen(String key) {
Tools.notNull(key);
return (Long) redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
return connection.strLen(stringRedisSerializer.serialize(key));
});
}
@Override
public Long appEnd(String key, String value) {
Tools.notNull(key, value);
return (Long) redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
return connection.append(stringRedisSerializer.serialize(key),
stringRedisSerializer.serialize(value));
});
}
@Override
public Boolean setRange(String key, Long offset, String value) {
Tools.notNull(key, value);
Tools.notNull(offset);
return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
connection.setRange(stringRedisSerializer.serialize(key),
stringRedisSerializer.serialize(value),
offset);
return true;
});
}
@Override
public Object getRange(String key, Long start, Long end) {
Tools.notNull(key);
Tools.notNull(start);
Tools.notNull(end);
return redisTemplate.execute((RedisCallback<Object>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
byte[] bytes = connection.getRange(stringRedisSerializer.serialize(key),
start,
end);
return stringRedisSerializer.deserialize(bytes);
});
}
@Override
public Long incr(String key) {
Tools.notNull(key);
return (Long) redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
return connection.incr(stringRedisSerializer.serialize(key));
});
}
@Override
public Boolean batchIncr(String[] key) {
Tools.notNull(key);
return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
connection.openPipeline();
Integer length = key.length;
for (int i = 0; i < length; i++) {
connection.incr(stringRedisSerializer.serialize(key[i]));
}
List<Object> result = connection.closePipeline();
if (null == result || result.isEmpty() || result.contains(false)) {
return false;
}
return true;
});
}
@Override
public Long incrBy(String key, Long sum) {
Tools.notNull(key);
if (null == sum || sum < 0) {
throw new RuntimeException();
}
return (Long) redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
return connection.incrBy(stringRedisSerializer.serialize(key), sum);
});
}
@Override
public Double incrByFloat(String key, Double sum) {
Tools.notNull(key);
if (null == sum || sum < 0) {
throw new RuntimeException();
}
return (Double) redisTemplate.execute((RedisCallback<Double>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
return connection.incrBy(stringRedisSerializer.serialize(key), sum.doubleValue());
});
}
@Override
public Long decr(String key) {
Tools.notNull(key);
return (Long) redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
return connection.decr(stringRedisSerializer.serialize(key));
});
}
@Override
public Long decrBy(String key, Long sum) {
Tools.notNull(key);
return (Long) redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
return connection.decrBy(stringRedisSerializer.serialize(key), sum);
});
}
@Override
public Boolean mSet(HashMap<String, Object> map) {
return mSetNx(map, false);
}
@Override
public Boolean mSetNx(HashMap<String, Object> map) {
return mSetNx(map, true);
}
@Override
public List<Object> mGet(String[] key) {
Tools.notNull(key);
return (List<Object>) redisTemplate.execute((RedisCallback<List<Object>>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
Integer length = key.length;
byte[][] bytes = new byte[length][];
for (int i = 0; i < length; i++) {
bytes[i] = stringRedisSerializer.serialize(key[i]);
}
List<byte[]> list = connection.mGet(bytes);
if (null != list || list.size() == 0) {
List<Object> result = new ArrayList<>();
length = list.size();
for (int i = 0; i < length; i++) {
Object object = stringRedisSerializer.deserialize(list.get(i));
result.add(String.valueOf(object));
}
return result;
}
return null;
});
}
/**
* 重构之后的代码
* @param map
* @param nx
* @return
*/
private Boolean mSetNx(HashMap<String, Object> map, Boolean nx) {
Assert.notNull(map, "map is null");
Assert.notEmpty(map, "map is empty");
redisTemplate.execute((RedisCallback) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
connection.openPipeline();
Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
String key;
Object value;
Map<byte[], byte[]> tuple = new HashMap<>();
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
key = entry.getKey();
value = entry.getValue();
Tools.notNull(key, value.toString());
tuple.put(stringRedisSerializer.serialize(key), stringRedisSerializer.serialize(value));
}
if (nx) {
if (!tuple.isEmpty()) {
return connection.mSetNX(tuple);
}
} else {
if (!tuple.isEmpty()) {
return connection.mSet(tuple);
}
}
return false;
});
return true;
}
}
- 单元测试
@Test
public void testRedisString() {
Assert.assertEquals(true, redisString.set("key", "value"));
Assert.assertEquals("value", redisString.get("key"));
HashMap map = new HashMap<String, Object>();
map.put("key1", "value1");
map.put("key2", "value2");
Assert.assertEquals(true, redisString.mSet(map));
Assert.assertEquals(true, redisString.set("key3", "1"));
Assert.assertEquals(true, redisString.set("key4", "1"));
log.info("key3 key4 {}", redisString.mGet(new String[]{"key3", "key4"}));
log.info("key4 incr {} ", redisString.incr("key4"));
log.info("key4 3 incrBy {} ", redisString.incrBy("key4", 3L));
log.info("key4 decr {} ", redisString.decr("key4"));
log.info("key4 2 incrBy {} ", redisString.incrBy("key4", 2L));
Set<String> keys = redisTool.keys("key*");
keys.forEach(item -> log.info(" keys -> key {} : value {} ", item, redisString.get(item)));
redisString.setEx("key1111", 10L, "value");
}
- 日志输出
[main] INFO c.z.l.r.o.RedisOperatingApplicationTests - key4 incr 2
[main] INFO c.z.l.r.o.RedisOperatingApplicationTests - key3 key4 [1, 2]
[main] INFO c.z.l.r.o.RedisOperatingApplicationTests - key4 decr 1
[main] INFO c.z.l.r.o.RedisOperatingApplicationTests - key4 incrBy 4
[main] INFO c.z.l.r.o.RedisOperatingApplicationTests - keys -> key key4 : value 4
[main] INFO c.z.l.r.o.RedisOperatingApplicationTests - keys -> key key1 : value value1
[main] INFO c.z.l.r.o.RedisOperatingApplicationTests - keys -> key key3 : value 1
[main] INFO c.z.l.r.o.RedisOperatingApplicationTests - keys -> key key : value value
[main] INFO c.z.l.r.o.RedisOperatingApplicationTests - keys -> key key2 : value value2