实践-spring-redis-string

本章是整理知识内容,为强化知识长期更新。

spring-redis-string

Spring-data-redis 有两个客户端,本章节主要使用lettuce来作为代码演示。尽可能面向redis编程。

代码实现

  • 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
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);

}
  • 代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
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();
Long result = connection.incr(stringRedisSerializer.serialize(key));
return result;
});
}


@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();
Long result = connection.incrBy(stringRedisSerializer.serialize(key), sum);
return result;
});
}

@Override
public Float incrByFloat(String key, Float sum) {
throw new RuntimeException("没有找到实现接口");
}

@Override
public Long decr(String key) {
Tools.notNull(key);
return (Long) redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
Long result = connection.decr(stringRedisSerializer.serialize(key));
return result;
});
}

@Override
public Long decrBy(String key, Long sum) {
Tools.notNull(key);
return (Long) redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
Long result = connection.decrBy(stringRedisSerializer.serialize(key), sum);
return result;
});
}

@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;
});
}


private Boolean mSetNx(HashMap<String, Object> map , Boolean nx){
if (null == map || map.isEmpty()) {
throw new RuntimeException();
}
return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) 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;
});
}
}
  • 单元测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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");
}
  • 日志输出
1
2
3
4
5
6
7
8
9
[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

END