1. Redis 有多少种数据结构?

主要有 5 种 Redis 对象,分别是 String、List、Hash、Set、Zset,这里的对象都指的是 Value 部分。底层实现依托于 sds、ziplist、skiplist、dict 等更基础的数据结构。
image.png

2. String(字符串)

2.1 简单介绍

字符串类型是 Redis 最基础的数据结构,字符串类型可以是JSONXML甚至是二进制的图片等数据,但是最大值不能超过512MB。在 Redis 中,String 是可以修改的,称为动态字符串(Simple Dynamic String简称SDS),说是字符串但它的内部结构更像是一个ArrayList,内部维护着一个字节数组,并且在其内部预分配了一定的空间,以减少内存的频繁分配。Redis的内存分配机制是这样:

  • 当字符串的长度小于 1MB 时,每次扩容都是加倍现有的空间。
  • 如果字符串长度超过 1MB 时,每次扩容时只会扩展 1MB 的空间。

这样既保证了内存空间够用,还不至于造成内存的浪费,字符串最大长度为**512MB**。分析一下 SDS 的数据结构:

1
2
3
4
5
6
struct SDS {
T capacity; //数组容量
T len; //实际长度
byte flages; //标志位,低三位表示类型
byte[] content; //数组内容
}

capacitylen两个属性都是泛型,为什么不直接用int类型?因为Redis内部有很多优化方案,为更合理的使用内存,不同长度的字符串采用不同的数据类型表示(intembstrraw),且在创建字符串的时候len会和capacity一样大,不产生冗余的空间,所以String值可以是字符串、数字(整数、浮点数) 或者 二进制。

Redis 会根据当前值的类型和长度决定使用哪种内部编码来实现。字符串类型的内部编码有3种:

  1. int:8 个字节的长整型。
  2. embstr:小于等于 39 个字节的字符串。
  3. raw:大于 39 个字节的字符串。

2.2 应用场景

2.2.1 缓存

在 web 服务中,通常使用 MySQL 作为数据库,Redis 作为缓存。由于 Redis 具有支撑高并发的特性,通常能起到加速读写和降低后端压力的作用。web 端的大多数请求都是从 Redis 中获取的数据,如果 Redis 中没有需要的数据,则会从 MySQL 中去获取,并将获取到的数据写入 Redis。

2.2.2 计数

Redis 中有一个字符串相关的命令incr keyincr命令对值做自增操作,返回结果分为以下三种情况:

  • 值不是整数,返回错误
  • 值是整数,返回自增后的结果
  • key 不存在,默认键为0,返回1

比如文章的阅读量,视频的播放量等等都会使用 Redis 来计数,每播放一次,对应的播放量就会加1,同时将这些数据异步存储到数据库中达到持久化的目的。

2.2.3 共享 Session

在分布式系统中,用户的每次请求会访问到不同的服务器,这就会导致 session 不同步的问题,假如一个用来获取用户信息的请求落在 A 服务器上,获取到用户信息后存入 session。下一个请求落在 B 服务器上,想要从 session 中获取用户信息就不能正常获取了,因为用户信息的 session 在服务器 A 上,为了解决这个问题,使用 Redis 集中管理这些 session,将 session 存入 redis,使用的时候直接从 Redis 中获取就可以了。

2.2.4 限速

为了安全考虑,有些网站会对 IP 进行限制,限制同一 IP 在一定时间内访问次数不能超过 n 次。

2.3 String 常用命令

1
2
3
4
5
6
7
8
9
10
11
set [key]  [value]   给指定key设置值(set 可覆盖老的值)
get [key] 获取指定key 的值
del [key] 删除指定key
exists [key] 判断是否存在指定key
mset [key1] [value1] [key2] [value2] ...... 批量存键值对
mget [key1] [key2] ...... 批量取key
expire [key] [time] 给指定key 设置过期时间 单位秒
setex [key] [time] [value] 等价于 set + expire 命令组合
setnx [key] [value] 如果key不存在则set 创建,否则返回0
incr [key] 如果value为整数 可用 incr命令每次自增1
incrby [key] [number] 使用incrby命令对整数值 进行增加 number

3. List(列表)

3.1 简单介绍

Redis 中的ListJava中的LinkedList很像,底层都是一种链表结构,List的插入和删除操作非常快,时间复杂度为 O(1),不像数组结构插入、删除操作需要移动数据。像归像,但是 Redis 中的List底层可不是一个双向链表那么简单。

当数据量较少的时候它的底层存储结构为一块连续内存,称之为ziplist(压缩列表),它将所有的元素紧挨着一起存储,分配的是一块连续的内存;当数据量较多的时候将会变成quicklist(快速链表)结构。

可单纯的链表也是有缺陷的,链表的前后指针prevnext会占用较多的内存,会比较浪费空间,而且会加重内存的碎片化。在 Redis 3.2 之后就都改用ziplist+链表的混合结构,称之为quicklist(快速链表)ziplist的每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值。

3.2 应用场景

由于List是一个按照插入顺序排序的列表,所以应用场景相对还较多的,例如:

3.2.1 消息队列

列表用来存储多个有序的字符串,既然是有序的,那么就满足消息队列的特点。使用lpush+rpop或者rpush+lpop实现消息队列。除此之外,redis 支持阻塞操作,在弹出元素的时候使用阻塞命令来实现阻塞队列。

Redis 虽然支持消息队列的实现,但是并不支持 ack。所以 Redis 实现的消息队列不能保证消息的可靠性,除非自己实现消息确认机制,不过这非常麻烦,所以如果是重要的消息还是推荐使用专门的消息队列去做。

3.2.2 栈

由于列表存储的是有序字符串,满足队列的特点,也就能满足栈先进后出的特点,使用lpush+lpop或者rpush+rpop实现栈。

3.2.3 文章列表

因为列表的元素不但是有序的,而且还支持按照索引范围获取元素。lpush命令和lrange命令能实现最新列表的功能,每次通过lpush命令往列表里插入新的元素,然后通过lrange命令读取最新的元素列表。比如我们可以使用命令lrange key 0 9分页获取文章列表。

3.3 List 常用命令

1
2
3
4
5
6
7
8
rpush [key] [value1] [value2] ......    链表右侧插入
rpop [key] 移除右侧列表头元素,并返回该元素
lpop [key] 移除左侧列表头元素,并返回该元素
llen [key] 返回该列表的元素个数
lrem [key] [count] [value] 删除列表中与value相等的元素,count是删除的个数。 count>0 表示从左侧开始查找,删除count个元素,count<0 表示从右侧开始查找,删除count个相同元素,count=0 表示删除全部相同的元素
lindex [key] [index] 获取list指定下标的元素 (需要遍历,时间复杂度为O(n)) index 代表元素下标,index 可以为负数, index= 表示倒数第一个元素,同理 index=-2 表示倒数第二 个元素。
lrange [key] [start_index] [end_index] 获取list 区间内的所有元素(时间复杂度为 O(n))
ltrim [key] [start_index] [end_index] 保留区间内的元素,其他元素删除(时间复杂度为 O(n))

4. Hash(字典)

4.1 简单介绍

Redis 中的Hash和 Java 的HashMap更加相似,是数组+链表的结构,当发生 hash 碰撞时将会把元素追加到链表上,值得注意的是在RedisHashvalue只能是字符串。

4.2 使用场景

4.2.1 购物车

hset [key] [field] [value]命令, 可以实现以用户Id商品Idfield,商品数量为value,恰好构成了购物车的 3 个要素。

4.2.2 存储对象

hash类型的(key, field, value)的结构与对象的(对象id, 属性, 值)的结构相似,也可以用来存储对象,如:

1
2
3
4
5
6
7
key=JavaUser293847
value={
“id”: 1,
“name”: “SnailClimb”,
“age”: 22,
“location”: “Wuhan, Hubei”
}

4.3 Hash 常用命令

1
2
3
4
5
6
7
8
hset  [key]  [field] [value]    新建字段信息
hget [key] [field] 获取字段信息
hdel [key] [field] 删除字段
hlen [key] 保存的字段个数
hgetall [key] 获取指定key 字典里的所有字段和值 (字段信息过多,会导致慢查询 慎用:亲身经历 曾经用过这个这个指令导致线上服务故障)
hmset [key] [field1] [value1] [field2] [value2] ...... 批量创建
hincr [key] [field] 对字段值自增
hincrby [key] [field] [number] 对字段值增加number

5. Set(集合)

5.1 简单介绍

Redis 中的setJava中的HashSet有些类似,它内部的键值对是无序的、唯一 的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL。当集合中最后一个元素被移除之后,数据结构被自动删除,内存被回收。

5.2 应用场景

  • 比如:在在线讨论社区中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
  • 好友、关注、粉丝、感兴趣的人集合: 1)sinter命令可以获得 A 和 B 两个用户的共同好友; 2)sismember命令可以判断 A 是否是 B 的好友; 3)scard命令可以获取好友数量;
  1. 关注时,smove命令可以将 B 从 A 的粉丝集合转移到 A 的好友集合
  • 首页展示随机:美团首页有很多推荐商家,但是并不能全部展示,set 类型适合存放所有需要展示的内容,而srandmember命令则可以从中随机获取几个。
  • 抽奖功能:用户点击抽奖按钮,参数抽奖,将用户编号放入集合,然后抽奖,分别抽一等奖、二等奖,如果已经抽中一等奖的用户不能参数抽二等奖则使用spop key [count],反之使用srandmember key [count]

5.3 Set 常用命令

1
2
3
4
5
6
sadd [key]  [value]  向指定key的set中添加元素
smembers [key] 获取指定key 集合中的所有元素
sismember [key] [value] 判断集合中是否存在某个value
scard [key] 获取集合的长度
spop [key] 弹出一个元素
srem [key] [value] 删除指定元素

6. Zset(有序集合)

6.1 简单介绍

Zset也叫SortedSet一方面它是个set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个score,代表这个 value 的排序权重。它的内部实现由两种数据结构支持:ziplist 和 skiplist。

6.1.1 ziplist(压缩列表)

当 Zset 使用 ziplist 作为存储结构的时候,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值。

6.1.2 skiplist(跳跃表)

image.png
当 Zset 使用 skiplist 作为存储结构时,使用 skiplist 按序保存元素分值,使用 dict 来保存元素和分值的对应关系。具体实现可参考 https://www.yuque.com/codershenghai/javalearning/qtuoil#edE8j

6.1.3 Zset 为什么同时需要使用字典和跳表来实现?

Zset 是一个有序列表,字典和跳表分别对应两种查询场景,字典用来支持按成员查询数据,跳表则用以实现高效的范围查询,这样两个场景,性能都做到了极致。

6.2 Zset 应用场景

6.2.1 排行榜

list不同的是Zset它能够实现动态的排序。比如用来存储粉丝列表,在线讨论社区项目的关注模块用到了Zset,value 为粉丝的用户 ID,score 为关注时间,这样我们可以对粉丝列表按关注时间进行排序。

Zset还可以用来存储学生的成绩,value值是学生的 ID,score是他的考试成绩。 我们对成绩按分数进行排序就可以得到他的名次。

6.2.2 延迟消息队列

在一个下单系统中,下单后需要在 15 分钟内进行支付,如果 15 分钟未支付则自动取消订单。将下单后的 15 分钟后时间作为 score,订单作为 value 存入 Redis,消费者轮询去消费,如果消费的大于等于这笔记录的 score,则将这笔记录移除队列,取消订单。

6.3 Zset 常用命令

1
2
3
4
5
6
7
8
zadd [key] [score] [value] 向指定key的集合中增加元素
zrange [key] [start_index] [end_index] 获取下标范围内的元素列表,按score 排序输出
zrevrange [key] [start_index] [end_index] 获取范围内的元素列表 ,按score排序 逆序输出
zcard [key] 获取集合列表的元素个数
zrank [key] [value] 获取元素再集合中的排名
zrangebyscore [key] [score1] [score2] 输出score范围内的元素列表
zrem [key] [value] 删除元素
zscore [key] [value] 获取元素的score

7. HyperLogLog

  • 常用于计数,它采用一种基数算法,用于完成独立总数的统计。
  • 占据空间小,无论统计多少个数据,只占12K的内存空间。
  • 是一种不精确的统计算法,标准误差为 0.81%。
  • 比如:在线讨论社区中统计独立访客 UV,使用当前日期作为 key,IP 地址作为 value 存入 HyperLoglog。如果要统计指定日期范围内的 UV,那么整理该日期范围内的 key 到一个列表中,然后对这个列表求一个 union,然后统计 HyperLoglog 的 size 即可。

8. Geo

用于支持存储地理位置信息。

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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
package com.dzgu.myblog.service;

import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;


import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* @description: redis操作接口
* @Author: dzgu
* @Date: 2021/11/7 16:46
*/
public interface RedisService {
/**
* 保存属性
*
* @param key key值
* @param value value值
* @param time 时间戳
*/
void set(String key, Object value, long time);

/**
* 保存属性
*
* @param key key值
* @param value value值
*/
void set(String key, Object value);

/**
* 获取属性
*
* @param key key值
* @return 返回对象
*/
Object get(String key);

/**
* 删除属性
*
* @param key key值
* @return 返回成功
*/
Boolean del(String key);

/**
* 批量删除属性
*
* @param keys key值集合
* @return 返回删除数量
*/
Long del(List<String> keys);

/**
* 设置过期时间
*
* @param key key值
* @param time 时间戳
* @return 返回成功
*/
Boolean expire(String key, long time);

/**
* 获取过期时间
*
* @param key key值
* @return 返回时间戳
*/
Long getExpire(String key);

/**
* 判断key是否存在
*
* @param key key值
* @return 返回
*/
Boolean hasKey(String key);

/**
* 按delta递增
*
* @param key key值
* @param delta delta值
* @return 返回递增后结果
*/
Long incr(String key, long delta);

/**
* 按delta递减
*
* @param key key值
* @param delta delta值
* @return 返回递减后结果
*/
Long decr(String key, long delta);

/**
* 获取Hash结构中的属性
*
* @param key 外部key值
* @param hashKey 内部key值
* @return 返回内部key的value
*/
Object hGet(String key, String hashKey);

/**
* 向Hash结构中放入一个属性
*
* @param key 外部key
* @param hashKey 内部key
* @param value 内部key的value
* @param time 过期时间
* @return 返回是否成功
*/
Boolean hSet(String key, String hashKey, Object value, long time);

/**
* 向Hash结构中放入一个属性
*
* @param key 外部key
* @param hashKey 内部key
* @param value 内部key的value
*/
void hSet(String key, String hashKey, Object value);

/**
* 直接获取整个Hash结构
*
* @param key 外部key值
* @return 返回hashMap
*/
Map<String, Object> hGetAll(String key);

/**
* 直接设置整个Hash结构
*
* @param key 外部key
* @param map hashMap值
* @param time 过期时间
* @return 返回是否成功
*/
Boolean hSetAll(String key, Map<String, Object> map, long time);

/**
* 直接设置整个Hash结构
*
* @param key 外部key
* @param map hashMap值
*/
void hSetAll(String key, Map<String, ?> map);

/**
* 删除Hash结构中的属性
*
* @param key 外部key值
* @param hashKey 内部key值
*/
void hDel(String key, Object... hashKey);

/**
* 判断Hash结构中是否有该属性
*
* @param key 外部key
* @param hashKey 内部key
* @return 返回是否存在
*/
Boolean hHasKey(String key, String hashKey);

/**
* Hash结构中属性递增
*
* @param key 外部key
* @param hashKey 内部key
* @param delta 递增条件
* @return 返回递增后的数据
*/
Long hIncr(String key, String hashKey, Long delta);

/**
* Hash结构中属性递减
*
* @param key 外部key
* @param hashKey 内部key
* @param delta 递增条件
* @return 返回递减后的数据
*/
Long hDecr(String key, String hashKey, Long delta);

/**
* zset添加分数
*
* @param key 关键
* @param value 价值
* @param score 分数
* @return {@link Double}
*/
Double zIncr(String key, Object value, Double score);

/**
* zset减少分数
*
* @param key 关键
* @param value 价值
* @param score 分数
* @return {@link Double}
*/
Double zDecr(String key, Object value, Double score);

/**
* zset根据分数排名获取指定元素信息
*
* @param key 关键
* @param start 开始
* @param end 结束
* @return {@link Map<Object, Double>}
*/
Map<Object, Double> zReverseRangeWithScore(String key, long start, long end);

/**
* 获取zset指定元素分数
*
* @param key 关键
* @param value 价值
* @return {@link Double}
*/
Double zScore(String key, Object value);

/**
* 获取zset所有分数
*
* @param key 关键
* @return {@link Map}
*/
Map<Object, Double> zAllScore(String key);

/**
* 获取Set结构
*
* @param key key
* @return 返回set集合
*/
Set<Object> sMembers(String key);

/**
* 向Set结构中添加属性
*
* @param key key
* @param values value集
* @return 返回增加数量
*/
Long sAdd(String key, Object... values);

/**
* 向Set结构中添加属性
*
* @param key key
* @param time 过期时间
* @param values 值集合
* @return 返回添加的数量
*/
Long sAddExpire(String key, long time, Object... values);

/**
* 是否为Set中的属性
*
* @param key key
* @param value value
* @return 返回是否存在
*/
Boolean sIsMember(String key, Object value);

/**
* 获取Set结构的长度
*
* @param key key
* @return 返回长度
*/
Long sSize(String key);

/**
* 删除Set结构中的属性
*
* @param key key
* @param values value集合
* @return 删除掉的数据量
*/
Long sRemove(String key, Object... values);

/**
* 获取List结构中的属性
*
* @param key key
* @param start 开始
* @param end 结束
* @return 返回查询的集合
*/
List<Object> lRange(String key, long start, long end);

/**
* 获取List结构的长度
*
* @param key key
* @return 长度
*/
Long lSize(String key);

/**
* 根据索引获取List中的属性
*
* @param key key
* @param index 索引
* @return 对象
*/
Object lIndex(String key, long index);

/**
* 向List结构中添加属性
*
* @param key key
* @param value value
* @return 增加后的长度
*/
Long lPush(String key, Object value);

/**
* 向List结构中添加属性
*
* @param key key
* @param value value
* @param time 过期时间
* @return 增加后的长度
*/
Long lPush(String key, Object value, long time);

/**
* 向List结构中批量添加属性
*
* @param key key
* @param values value 集合
* @return 增加后的长度
*/
Long lPushAll(String key, Object... values);

/**
* 向List结构中批量添加属性
*
* @param key key
* @param time 过期时间
* @param values value集合
* @return 增加后的长度
*/
Long lPushAll(String key, Long time, Object... values);

/**
* 从List结构中移除属性
*
* @param key key
* @param count 总量
* @param value value
* @return 返回删除后的长度
*/
Long lRemove(String key, long count, Object value);

/**
* 向bitmap中新增值
*
* @param key key
* @param offset 偏移量
* @param b 状态
* @return 结果
*/
Boolean bitAdd(String key, int offset, boolean b);

/**
* 从bitmap中获取偏移量的值
*
* @param key key
* @param offset 偏移量
* @return 结果
*/
Boolean bitGet(String key, int offset);

/**
* 获取bitmap的key值总和
*
* @param key key
* @return 总和
*/
Long bitCount(String key);

/**
* 获取bitmap范围值
*
* @param key key
* @param limit 范围
* @param offset 开始偏移量
* @return long类型集合
*/
List<Long> bitField(String key, int limit, int offset);

/**
* 获取所有bitmap
*
* @param key key
* @return 以二进制字节数组返回
*/
byte[] bitGetAll(String key);

/**
* 向hyperlog中添加数据
*
* @param key key
* @param value 值
* @return {@link Long}
*/
Long hyperAdd(String key, Object... value);

/**
* 获取hyperlog元素数量
*
* @param key key
* @return {@link Long} 元素数量
*/
Long hyperGet(String... key);

/**
* 删除hyperlog数据
*
* @param key key
*/
void hyperDel(String key);

/**
* 增加坐标
*
* @param key key
* @param x x
* @param y y
* @param name 地点名称
* @return 返回结果
*/
Long geoAdd(String key, Double x, Double y, String name);

/**
* 根据城市名称获取坐标集合
*
* @param key key
* @param place 地点
* @return 坐标集合
*/
List<Point> geoGetPointList(String key, Object... place);

/**
* 计算两个城市之间的距离
*
* @param key key
* @param placeOne 地点1
* @param placeTow 地点2
* @return 返回距离
*/
Distance geoCalculationDistance(String key, String placeOne, String placeTow);

/**
* 获取附该地点附近的其他地点
*
* @param key key
* @param place 地点
* @param distance 附近的范围
* @param limit 查几条
* @param sort 排序规则
* @return 返回附近的地点集合
*/
GeoResults<RedisGeoCommands.GeoLocation<Object>> geoNearByPlace(String key, String place, Distance distance, long limit, Sort.Direction sort);

/**
* 获取地点的hash
*
* @param key key
* @param place 地点
* @return 返回集合
*/
List<String> geoGetHash(String key, String... place);
}

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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
package com.dzgu.myblog.service.impl;

import com.dzgu.myblog.service.RedisService;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
* @description: redis操作
* @Author: dzgu
* @Date: 2021/11/7 17:55
*/
@Service
public class RedisServiceImpl implements RedisService {
@Resource
private RedisTemplate<String, Object> redisTemplate;

@Override
public void set(String key, Object value, long time) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}

@Override
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}

@Override
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}

@Override
public Boolean del(String key) {
return redisTemplate.delete(key);
}

@Override
public Long del(List<String> keys) {
return redisTemplate.delete(keys);
}

@Override
public Boolean expire(String key, long time) {
return redisTemplate.expire(key, time, TimeUnit.SECONDS);
}

@Override
public Long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}

@Override
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}

@Override
public Long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}

@Override
public Long decr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, -delta);
}

@Override
public Object hGet(String key, String hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}

@Override
public Boolean hSet(String key, String hashKey, Object value, long time) {
redisTemplate.opsForHash().put(key, hashKey, value);
return expire(key, time);
}

@Override
public void hSet(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}

@Override
public Map hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}

@Override
public Boolean hSetAll(String key, Map<String, Object> map, long time) {
redisTemplate.opsForHash().putAll(key, map);
return expire(key, time);
}

@Override
public void hSetAll(String key, Map<String, ?> map) {
redisTemplate.opsForHash().putAll(key, map);
}

@Override
public void hDel(String key, Object... hashKey) {
redisTemplate.opsForHash().delete(key, hashKey);
}

@Override
public Boolean hHasKey(String key, String hashKey) {
return redisTemplate.opsForHash().hasKey(key, hashKey);
}

@Override
public Long hIncr(String key, String hashKey, Long delta) {
return redisTemplate.opsForHash().increment(key, hashKey, delta);
}

@Override
public Long hDecr(String key, String hashKey, Long delta) {
return redisTemplate.opsForHash().increment(key, hashKey, -delta);
}

@Override
public Double zIncr(String key, Object value, Double score) {
return redisTemplate.opsForZSet().incrementScore(key, value, score);
}

@Override
public Double zDecr(String key, Object value, Double score) {
return redisTemplate.opsForZSet().incrementScore(key, value, -score);
}

@Override
public Map<Object, Double> zReverseRangeWithScore(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end)
.stream()
.collect(Collectors.toMap(ZSetOperations.TypedTuple::getValue, ZSetOperations.TypedTuple::getScore));
}

@Override
public Double zScore(String key, Object value) {
return redisTemplate.opsForZSet().score(key, value);
}

@Override
public Map<Object, Double> zAllScore(String key) {
return Objects.requireNonNull(redisTemplate.opsForZSet().rangeWithScores(key, 0, -1))
.stream()
.collect(Collectors.toMap(ZSetOperations.TypedTuple::getValue, ZSetOperations.TypedTuple::getScore));
}

@Override
public Set<Object> sMembers(String key) {
return redisTemplate.opsForSet().members(key);
}

@Override
public Long sAdd(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}

@Override
public Long sAddExpire(String key, long time, Object... values) {
Long count = redisTemplate.opsForSet().add(key, values);
expire(key, time);
return count;
}

@Override
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}

@Override
public Long sSize(String key) {
return redisTemplate.opsForSet().size(key);
}

@Override
public Long sRemove(String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}

@Override
public List<Object> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}

@Override
public Long lSize(String key) {
return redisTemplate.opsForList().size(key);
}

@Override
public Object lIndex(String key, long index) {
return redisTemplate.opsForList().index(key, index);
}

@Override
public Long lPush(String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}

@Override
public Long lPush(String key, Object value, long time) {
Long index = redisTemplate.opsForList().rightPush(key, value);
expire(key, time);
return index;
}

@Override
public Long lPushAll(String key, Object... values) {
return redisTemplate.opsForList().rightPushAll(key, values);
}

@Override
public Long lPushAll(String key, Long time, Object... values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
expire(key, time);
return count;
}

@Override
public Long lRemove(String key, long count, Object value) {
return redisTemplate.opsForList().remove(key, count, value);
}

@Override
public Boolean bitAdd(String key, int offset, boolean b) {
return redisTemplate.opsForValue().setBit(key, offset, b);
}

@Override
public Boolean bitGet(String key, int offset) {
return redisTemplate.opsForValue().getBit(key, offset);
}

@Override
public Long bitCount(String key) {
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
}

@Override
public List<Long> bitField(String key, int limit, int offset) {
return redisTemplate.execute((RedisCallback<List<Long>>) con ->
con.bitField(key.getBytes(),
BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(limit)).valueAt(offset)));
}

@Override
public byte[] bitGetAll(String key) {
return redisTemplate.execute((RedisCallback<byte[]>) con -> con.get(key.getBytes()));
}

@Override
public Long hyperAdd(String key, Object... value) {
return redisTemplate.opsForHyperLogLog().add(key, value);
}

@Override
public Long hyperGet(String... key) {
return redisTemplate.opsForHyperLogLog().size(key);
}

@Override
public void hyperDel(String key) {
redisTemplate.opsForHyperLogLog().delete(key);
}

@Override
public Long geoAdd(String key, Double x, Double y, String name) {
return redisTemplate.opsForGeo().add(key, new Point(x, y), name);
}

@Override
public List<Point> geoGetPointList(String key, Object... place) {
return redisTemplate.opsForGeo().position(key, place);
}

@Override
public Distance geoCalculationDistance(String key, String placeOne, String placeTow) {
return redisTemplate.opsForGeo()
.distance(key, placeOne, placeTow, RedisGeoCommands.DistanceUnit.KILOMETERS);
}

@Override
public GeoResults<RedisGeoCommands.GeoLocation<Object>> geoNearByPlace(String key, String place, Distance distance, long limit, Sort.Direction sort) {
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates();
// 判断排序方式
if (Sort.Direction.ASC == sort) {
args.sortAscending();
} else {
args.sortDescending();
}
args.limit(limit);
return redisTemplate.opsForGeo()
.radius(key, place, distance, args);
}

@Override
public List<String> geoGetHash(String key, String... place) {
return redisTemplate.opsForGeo()
.hash(key, place);
}

}

#

参考

  1. 一口气说出 Redis 5 种数据结构及对应使用场景,面试要加分的
  2. redis 五大数据类型使用场景
  3. 我是面试官,Redis 面试攻略第一弹