# Redis
# 一、简介
Redis是一个基于内存的高性能key-value数据库
- 高性能(支持11万读,3万写)速度非常快
- 丰富的数据类型 (5种基本数据类型
string
hash
list
set
sortedset
,3种其他类型) - 主从同步
- 两种数据持久化方式
RDB
AOP
- 支持事务
- 订阅发布功能(subscribe/publish)
使用场景
- 缓存、数据库、消息中间件
- 分布式锁(setnx)
- 简易订阅通知
- 延时通知(键过期通知)
- 其他
# 二、安装
1. win:
- 下载 https://github.com/MicrosoftArchive/redis/releases
- 解压缩 可以看到
redis-server.exe
redis.windows.conf
- 设置密码(非必须)在
redis.windows.conf
中requirepass 123456
- 启动服务端
redis-server.exe redis.windows.conf
- 客户端连接,可以使用自带的,或者第三方的 有密码需要输入
auth 123456
3. 基础操作
启动Redis服务端
redis-server ./redis.conf
启动客户端
redis-cli -h localhost -p 6379
测试是否连通
ping
查看进程序
ps -ef|grep redis
查看所有的key
keys *
redis自带性能测试工具 100个并发连接 100000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
redis默认有16个数据库,默认使用第0个 用
select 3
切换第三个dbsize
数据库大小flushdb
清空当前库,查询是否清空了keys *
flushall
清空所有数据库操作Redis-Key
set name zhangsan set age 26 key * exists name move name 1 expire name 10 # 10s过期 ttl name # 剩余过期时间 type age # 查看key类型
1
2
3
4
5
6
7
8
# 三、基础数据类型
类型 | 存储的值 | 描述 |
---|---|---|
String | 可以是字符串、整数或者浮点数 | 字符串操作,对象和浮点数执行自增(increment)或者自减(decrement) |
List | 一个链表,链表上的每个节点都包含了一个字符串 | 从链表的两端推入或者弹出元素,可以玩出队列、栈、阻塞队列等特性 |
Hash | 包含键值对的无序散列表, key-map 和String相似 | 包含键值对的无序散列表 |
Set | Set里不能重复,无序 | 添加、获取、移除单个元素;检查一个元素是否存在于某个集合中;计算交集、并集、差集;从集合里卖弄随机获取元素 |
ZSet | 字符串成员(member)和浮点分数(score)之间的有序映射,元素的排列顺序由分值大小决定 | 添加、获取、删除单个元素;根据分值范围(range)或者成员来获取元素 |
# 1. 字符串 String
##########################################################
127.0.0.1:6379> set key1 v1 # 设置值
OK
127.0.0.1:6379> get key1 # 获取值
"v1"
127.0.0.1:6379> keys * # 获取所有的key
1) "key1"
2) "name"
127.0.0.1:6379> exists key1 # 判断key是否存在
(integer) 1
127.0.0.1:6379> append key1 hello # 追加字符串,如果当前key不存在,就相当于set key
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1 # 获取字符串长度
(integer) 7
127.0.0.1:6379> append key1 " ,biubiu"
(integer) 15
127.0.0.1:6379> strlen key1
(integer) 15
127.0.0.1:6379> get key1
"v1hello ,biubiu"
127.0.0.1:6379>
##########################################################
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incr views # 自增1 浏览量+1
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views # 自减1 浏览量-1
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> decr views
(integer) -1
127.0.0.1:6379> incrby views 10 # 可设置步长,指定增量
(integer) 9
127.0.0.1:6379> incrby views 10
(integer) 19
127.0.0.1:6379> decrby views 5
(integer) 14
127.0.0.1:6379>
##########################################################
127.0.0.1:6379> set key1 "hello,biubiu" # 设置key1
OK
127.0.0.1:6379> get key1
"hello,biubiu"
127.0.0.1:6379> getrange key1 0 4 # 截取字符川 [0,4]
"hello"
127.0.0.1:6379> getrange key1 0 -1 # 获取全部字符川 [0,-1] 和get key是一样的
"hello,biubiu"
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> keys *
1) "key1"
2) "key2"
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx # 替换指定位置开始的字符串
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
##########################################################
127.0.0.1:6379> setex key1 10 "hello" # 设置key1的值为 hello 10s后过期
OK
127.0.0.1:6379> ttl key1
(integer) 4
127.0.0.1:6379> get key1
(nil)
127.0.0.1:6379> setnx key1 "redis" # 不存在再设置(分布式锁中常常使用)
(integer) 1
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> setnx key1 "mongodb" # 已存在,设置无效
(integer) 0
127.0.0.1:6379> get key1
"redis"
##########################################################
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> mget k1 k3 # 同时获取多个值
1) "v1"
2) "v3"
127.0.0.1:6379> msetnx k3 v33 k5 v55 # msetnx 是一个原子操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k5
(nil)
##########################################################
# key的巧妙设计 user:{id}{field}
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 20
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "20"
##########################################################
# 先get再set
127.0.0.1:6379> getset db "redis" # 如果不存在,怎返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db "mongodb" # 如果存在,怎返回对应的值 并且设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"
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
# 2. 列表 List
Redis的list可以玩出队列、栈、阻塞队列等特性。
所有的list命令都是L开头的
##########################################################
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> lpush list one # 将一个值或者多个值,插入到列表的头部(左)
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three four five
(integer) 5
127.0.0.1:6379> lrange list 0 -1 # 查看list里所有元素
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> lrange list 0 2 # 通过区间获取具体的值
2) "four"
3) "three"
127.0.0.1:6379> rpush list "-a" # 将一个值或者多个值,插入到列表的尾部(右)
(integer) 6
127.0.0.1:6379> lrange list 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"
6) "-a"
##########################################################
127.0.0.1:6379> lrange list 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"
6) "-a"
127.0.0.1:6379> lpop list # 移除列表第一个元素
"five"
127.0.0.1:6379> rpop list # 移除列表最后一个元素
"-a"
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
##########################################################
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lindex list 1 # 通下标获取 list 中的某一个值
"three"
127.0.0.1:6379> lindex list 0
"four"
##########################################################
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> llen list # 获取列表长度
(integer) 4
##########################################################
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "four"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> lrem list 1 one # 移除value为one的一个元素,精确匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "four"
3) "three"
4) "two"
127.0.0.1:6379> lrem list 2 three # 移除value为three的2个元素
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "two"
##########################################################
127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello2"
3) "hello1"
4) "hello"
127.0.0.1:6379> ltrim list 0 1 # 截取指定的长度,这个list已经被修改了,只剩下截取之后的元素了
OK
127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello2"
##########################################################
127.0.0.1:6379> lrange list 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
127.0.0.1:6379> rpoplpush list list1 # 移除列表的最后一个元素,并且移动到新的列表里
"1"
127.0.0.1:6379> lrange list 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
127.0.0.1:6379> lrange list1 0 -1
1) "1"
##########################################################
127.0.0.1:6379> exists list
(integer) 0
127.0.0.1:6379> lset list 0 item # 将列表中指定下标的值替换为另一个值,更新操作,,如果不存在,更新报错
(error) ERR no such key
127.0.0.1:6379> lpush list item
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "item"
127.0.0.1:6379> lset list 0 item0 # 将列表中指定下标的值替换为另一个值,更新操作
OK
127.0.0.1:6379> lrange list 0 -1
1) "item0"
##########################################################
127.0.0.1:6379> rpush list hello world
(integer) 2
127.0.0.1:6379> linsert list before "world" "biubiu" # 将biubiu插入到list的world的前面
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "biubiu"
3) "world"
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
实际上是一个链表,两端都可以插入值,key不存在,创建新链表,key存在,新增内容,在两边插入或改动值,效率最高!中间元素,效率稍微低一点
- 消息队列 Lpush Rpop 左边插入,右边取值
- 栈 Lpush Lpop 左边插入,左边取值
# 3. 哈希(字典) Hash
key-map 和String相似
##########################################################
127.0.0.1:6379> hset myhash name "tom" # 设置myhash的值 name-"tom"
(integer) 1
127.0.0.1:6379> hset myhash id 1001 age 20 # 设置多个值 多个键值对
(integer) 2
127.0.0.1:6379> hget myhash name # 获取myhash name对应的值
"tom"
127.0.0.1:6379> hmget myhash name id age # 批量获取
1) "tom"
2) "1001"
3) "20"
127.0.0.1:6379> hgetall myhash # 获取全部数据,包含 key
1) "name"
2) "tom"
3) "id"
4) "1001"
5) "age"
6) "20"
127.0.0.1:6379> hdel myhash age # 删除指定的key,对应的value也消失了
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "name"
2) "tom"
3) "id"
4) "1001"
127.0.0.1:6379> hlen myhash # 获取长度
(integer) 2
127.0.0.1:6379> hexists myhash name # 是否存在某个key
(integer) 1
127.0.0.1:6379> hexists myhash name1
(integer) 0
127.0.0.1:6379> hkeys myhash # 获取所有的keys
1) "name"
2) "id"
127.0.0.1:6379> hvals myhash # 获取所有的vals
1) "tom"
2) "1001"
##########################################################
127.0.0.1:6379> hset myhash num 5
(integer) 1
127.0.0.1:6379> hincrby myhash num 3 # 正数3 加法 负数-3 减法
(integer) 8
127.0.0.1:6379> hget myhash num
"8"
127.0.0.1:6379> hsetnx myhash f4 biubiu # 不存在 设置值
(integer) 1
127.0.0.1:6379> hsetnx myhash f4 biubiu # 存在 设置失败
(integer) 0
##########################################################
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
# 4. 集合 Set
set里不能重复,无序
##########################################################
127.0.0.1:6379> sadd key hello hello world # set中丢元素
(integer) 2
127.0.0.1:6379> smembers key # 查看set中的元素
1) "hello"
2) "world"
127.0.0.1:6379> sismember key hello # set中是否存在hello这个元素
(integer) 1
##########################################################
127.0.0.1:6379> scard key # 获取set里元素个数
(integer) 2
127.0.0.1:6379> sadd key biubiu
(integer) 1
127.0.0.1:6379> scard key
(integer) 3
127.0.0.1:6379> smembers key
1) "biubiu"
2) "hello"
3) "world"
127.0.0.1:6379> srem key hello # 移除set里的指定元素
(integer) 1
127.0.0.1:6379> smembers key
1) "biubiu"
2) "world"
##########################################################
127.0.0.1:6379> srandmember key # 随机获取一个元素
"fish"
127.0.0.1:6379> srandmember key
"world"
127.0.0.1:6379> srandmember key
"world"
127.0.0.1:6379> smembers key
1) "fish"
2) "biubiu"
3) "world"
127.0.0.1:6379> spop key # 随机移除一个元素
"world"
127.0.0.1:6379> smembers key
1) "fish"
2) "biubiu"
##########################################################
127.0.0.1:6379> sadd myset a b c d
(integer) 4
127.0.0.1:6379> sadd myset1 aa bb cc
(integer) 3
127.0.0.1:6379> smove myset myset1 a # 移动一个集合中myset的指定元素a到另一个集合中myset1
(integer) 1
127.0.0.1:6379> smembers myset1
1) "cc"
2) "aa"
3) "a"
4) "bb"
127.0.0.1:6379> smembers myset
1) "d"
2) "c"
3) "b"
##########################################################
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sdiff key1 key2 # 取两个集合中的不同的元素
1) "b"
2) "a"
127.0.0.1:6379> sinter key1 key2 # 取两个集合中的共同的元素
1) "c"
127.0.0.1:6379> sunion key1 key2 # 并集
1) "d"
2) "b"
3) "c"
4) "a"
##########################################################
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
# 5. 有序集合 ZSet
数据淘汰策略
Redis单线程
指Redis的网络IO和键值对读写是由一个线程来完成的,这也是Redis对外提供键值存储服务的主要流程。但Redis的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的Redis高可用 redis具备的高可用,其实包含两层含义:一是数据尽量少丢失,二是服务尽量少中断。对于前者redis使用AOF和RDB两种持久化方式保证,对于后者Redis的做法就是增加副本冗余量,将一份数据同时保存在多个实例上
redis的哨兵模式
Redis持久化方式: RDB(内存快照)持久化机制,对redis中的数据执行周期性的持久化
AOF机制对每条写入命令作为日志,以append-only的模式写入一个日志文件中,在redis重启的时候,可以通过回放AOF日志中的写入指令来重新构建整个数据集。
如果我们想要redis仅仅作为纯内存的缓存来用,那么可以禁止RDB和AOF所有的持久化机制。
如果同时使用RDB和AOF两种持久化机制,那么在redis重启的时候,会使用AOF来重新构建数据,因为AOF中的数据更加完整。
# 四、Redis事物
- 开启事务
multi
- 命令入队
其他命令
- 执行事物
exec
- 取消事物
discard
redis 乐观锁 watch
数据库乐观锁:认为不会有线程修改,获取的时候不加锁,更新数据的时候去判断是否有更新 通常用version实现,每次修改的时候version+1,比较是否有变化
Redis发布订阅
1.消息发送者
2.频道
3.消息接收者
# 五、集群模式
- 数据复制是单向的,只能由主节点到从节点,Master以写为主,Slave以读为主
- 单台Redis最大使用内存最好不要超过20Gb
127.0.0.1:6379> info replication # 查看当前库信息
# Replication
role:master # 角色 主库
connected_slaves:0 # 从库 0个
master_failover_state:no-failover
master_replid:484f506441abfb6ee14cad986a01ae071bcd17ae
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
2
3
4
5
6
7
8
9
10
11
12
13
主节点负责写,从节点负责读取,认老大,配从机
方式一
slaveof ip port
ip主机的ip port主机的端口 (从机重启,就变成主机了,需要重新认老大) 方式二 配置文件 replication
Slave启动连接到Master后会发送一个 sync
同步命令,Master接到命令,启动后台存盘进程,同时收集所有接收到的用于修改数据的命令,再后台进程执行完毕后,Master将传送整个数据文件到Slave,并完成一次完全同步。
- 全量复制
- 增量复制
一主二从
复制三个配置文件,修改 端口
pid名字
log文件名字
dump.rdb名字
启动三个Redis服务 查询 ps -ef | grep redis
,默认情况下三个都是主节点。主从配置,我们一般只需要配置从机就行。
层层链路
Master挂了,谋权篡位,自己当老大 slaveof no one
其他节点手动连接到新主节点上
但是上面都是手动操作的,比较麻烦,所以有了 哨兵模式
哨兵模式(自动选举老大)
后台监控主节点是否有故障,有故障根据投票数自动将从库转换为主库。Redis提供了哨兵命令,哨兵是一个独立的进程,独立运行,哨兵通过发送命令,等待Redis服务器响应,从而监控多个Redis实例。主节点挂掉,哨兵投票选举从节点为主节点,若哨兵发现坏掉的主节点恢复了,只能当从节点。
- 配置文件 sentinel.conf
# sentinel monitor 被监控的名称 host port 数字代表主机挂了,从节点投票
sentinel monitor myredis 127.0.0.1 6379 1
2
- 启动哨兵
redis-sentinel ./sentinel.conf
# 六、缓存
- 缓存处理流程 前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果
- 缓存穿透 访问了不存在的数据,缓存里没有,数据库也没有(恶意攻击或者缓存过期),导致大量请求打到数据库,数据库压力激增
- 设置热点数据永不过期
- 缓存空值或缺省值 缓存空值或缺省值。当没有这个数据时,可以缓存一个空值,那么下次请求就会在redis中命中。但是这种方式也有个问题,就是如果后面这个数据在数据库中有值,那么redis中还是空值,所以一般设置空值的过期时间短一点。
- 缓存雪崩 缓存发生故障或者大量key失效,导致请求到达数据库
- 设置key过期时间为随机,反正就是不要同一时间全过期
最简单MyBatis缓存代码,示例Demo (opens new window)
public List<User> queryAll() {
//1. 先从Redis查询用户列表
log.debug("读取Redis缓存");
String userListJson = (String) redisUtils.get("userListCache");
if (userListJson == null) {
log.debug("Redis没有,读取DB");
List<User> users = userService.selectList(null);
//DB有数据,刷新缓存
if (users != null) {
redisUtils.set("userListCache", JSON.toJSONString(users), 300L);
return users;
}
return null;
} else {
log.debug("从redis缓存取值");
return JSONObject.parseArray(userListJson, User.class);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
官方命令手册:http://www.redis.cn/commands.html#string
# 七、Redis分布式锁
String 类型setnx方法,只有不存在时才能添加成功,返回true
public static boolean getLock(String key) {
Long flag = jedis.setnx(key, "1");
if (flag == 1) {
jedis.expire(key, 10);
}
return flag == 1;
}
public static void releaseLock(String key) {
jedis.del(key);
}
2
3
4
5
6
7
8
9
10
11