Redis

一、简介

REmote DIctionary Server(Redis)是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库。

redis_logo

Redis 就是一款 NoSQL,而 NoSQL 就是指非关系型数据库,主要分为四种:

  1. 键值型:Redis
  2. 文档型:ElasticSearch、Mongdb
  3. 面向列:Hbase
  4. 图形化:Neo4j

二、Redis基础

2.1 Redis安装

  1. 通过 docker-compose 安装 redis
1
2
3
4
5
6
7
8
9
10
version: '3.8'
services:
redis:
image: daocloud.io/library/redis:5.0.9
restart: always
container_name: redis
environment:
- TZ=Asia/Shanghai
ports:
- 6379:6379
  1. 进入容器内部测试 redis
1
2
3
4
5
6
# 连接redis
redis-cli
# set新建键值对
set key value
# get获取键值
get key

2.2 Redis常用命令

redis 的数据存储结构有以下几种:

  • key-string(字符串):一个 key 对应一个值
  • key-hash(哈希):一个 key 对应一个 map
  • key-list(列表):一个 key 对应一个列表
  • key-set(集合):一个 key 对应一个集合
  • key-zset(有序集合):一个 key 对应一个有序的集合

redis一

2.2.1 string常用命令

  1. 设置值
1
set key value
  1. 取值
1
get key
  1. 批量操作
1
2
mset key1 value1 key2 value2 ...
mget key1 key2 ...
  1. 自增
1
incr key
  1. 自减
1
decr key
  1. 自增自减指定数量
1
2
incrby key number
decrby key number
  1. 设置值的同时指定生存时间
1
setex key seconds value
  1. 设置值,如果当前 key 不存在如同 set,如果存在则说明都不做
1
setex key value
  1. 在 key 对应的 value 后追加内容
1
append key value
  1. 查看 value 字符串长度
1
strlen key

2.2.2 hash常用命令

  1. 存储数据
1
hset key field value
  1. 获取数据
1
hget key field
  1. 批量操作
1
2
hmset key1 field1 value1 field2 value2 ...
hmget key1 firle1 field2 ...
  1. 指定自增
1
hincrby key field number
  1. 设置值,如果当前 key 不存在如同 set,如果存在则说明都不做
1
hsetnx key field value
  1. 检查 field 是否存在
1
hexists key field
  1. 删除某个 field
1
hdel key field1 field2 ...
  1. 获取当前 hash 结构中的全部 field 和 value
1
hgetall key
  1. 获取当前 hash 结构中的全部 field
1
hkeys key
  1. 获取当前 hash 结构中的全部 value
1
hvals key
  1. 获取当前 hash 中 field 的数量
1
hlen key

2.2.3 list常用命令

  1. 存储数据
1
2
3
4
5
6
7
8
9
10
11
# 从左侧插入数据
lpush key value1 value2 ...
# 从右侧插入数据
rpush key value1 value2 ...

# 如果key不存在,什么都不做,如果key存在但不是list结构,也什么都不做
lpushx key value1 value2 ...
rpushx key value1 value2 ...

# 通过索引位置添加value
lset key index value
  1. 获取数据
1
2
3
4
5
6
7
8
9
10
11
12
13
# 左侧弹出数据并移除
lpop key
# 右侧弹出数据并移除
rpop key

# 获取一定范围的数据,start从0开始,stop为-1时为最后一个value,-2时为倒数第二个value
lrange key start stop

# 根据索引位置获取value
lindex key index

# 获取整个list的长度
llen key
  1. 删除数据
1
2
3
4
5
6
7
8
# 删除list中count个value的值,当count>0,从左向右删除,但count<0,从右向左删除,但count==0,全部删除
lrem key count value

# 保留列表中指定范围内的数据,超出这个范围的都会被移除
ltrim start stop

# 将list1中的最后一个数据弹出,插入到list2中的第一个位置
rpoplpush key1 key2

2.2.4 set常用命令

  1. 存储数据
1
2
# value不允许重复,且数据无序排列
sadd key value1 value2 ...
  1. 获取数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 获取全部数据
smembers key

# 随机获取数据,并移除,可加弹出数量
spop key number

# 取多个set的交集
sinter key1 key2 ...

# 取多个set的并集
sunion key1 key2 ...

# 取多个set的差集
sdiff key1 key2 ...

# 查看当前set是否包含某个值
sismember key value
  1. 删除数据
1
srem key value1 value2 ...

2.2.5 zset常用命令

  1. 存储数据
1
2
3
4
5
# score必须是数值,value不允许重复
zadd key score1 value1 score2 value2 ...

# 修改score,如果value存在则增加分数,如果不存在则相当于zadd
zincrby key number value
  1. 获取数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查看指定value的分数
zscore key value

# 获取value数量
zcard key

# 根据score范围查询value数量
zcount key min max

# 根据分数从小到大排序,获取指定范围内的数据,添加了withscores参数会返回value的具体score
zrange key start stop withscores

# 从大到小
zrevrange key start stop withscores

# 根据分数的范围获取数据,如果不希望包括min和max的值可以用(min max)的方式,最大最小值用±inf表示
zrangebyscore key min max withscores [limit,offset,count]
zrevrangebyscore key max min withscores [limit,offset,count]
  1. 删除数据
1
zrem key value1 value2 ...

2.2.6 key常用命令

  1. 查看所有key
1
keys *
  1. 查看某个key是否存在
1
exists key
  1. 删除key
1
del key
  1. 设置key的生存时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 单位为s
expire key seconds

# 单位为ms
pexpire key milliseconds

# 指定生存到某个时间点
expireat key timestamp
pexpireat key millseconds

# 查看key的剩余生存时间,返回-2则key不存在,-1则没设置生存时间
ttl key
pttl key

# 移除生存时间
persist key
  1. 选择操作的库
1
2
3
4
5
# redis默认有16个库
select 0~15

# 移动key到另一个库中
move key db

2.2.7 库的常用命令

  1. 清空当前所在数据库
1
flushdb
  1. 清空所有数据库
1
flushdball
  1. 查看当前库有多少key
1
dbsize
  1. 查看最后一次操作的时间
1
lastsave
  1. 实时监控redis接收到的命令
1
monitor

三、Redis配置

3.1 Redis的AUTH

  1. docker-compose.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3.8'
services:
redis:
image: daocloud.io/library/redis:5.0.9
container_name: redis
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 6379:6379
volumes:
- ./redis.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
  1. 设置 Redis 连接密码
1
2
vim redis.conf
requirepass toortoor
1
docker-compose up -d
  1. 进入 Redis 之后都需要输入密码才可以创建 key
1
2
redis-cli
auth toortoor

3.2 Redis的事务

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中

一个事务从开始到执行会经历以下几个阶段:

  • 开始事务:multi
  • 命令入队:……
  • 执行事务:exec
  • 取消事务:discard
  • 监听:在开启事务之前,先通过 watch 监听事务中要操作的 key,如果在事务过程中有其他的客户端修改了 key,那么事务将会被取消

事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

  1. 监听
1
watch name age gander
  1. 开始事务
1
multi
  1. 命令入队
1
2
3
set name cqm
set age 22
set gander male
  1. 执行事务/取消事务
1
exec/discard

3.3 Redis的持久化

3.3.1 RDB持久化

Redis 的配置文件位于 Redis 安装目录, ROB 是默认的持久化机制。

  1. docker-compose.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.8'
services:
redis:
image: daocloud.io/library/redis:5.0.9
container_name: redis
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 6379:6379
volumes:
- ./redis.conf:/usr/local/redis/redis.conf
- ./data:/data
# 加载redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
  1. redis.conf
1
2
3
4
5
6
7
8
9
vim redis.conf
# 在900秒内有1一个 key 发生了变化,就执行 RDB 持久化
save 900 1
save 300 10
save 60 10000
# 开启RDB持久化
rdbchecksum yes
# RDB持久化名称
dbfilename dump.rdb
  1. 测试持久化
1
2
3
4
set name cqm
set age 22
# 关闭redis并保存
shutdown save
  1. 可以看到 data 目录下多了个 rdb 文件,即使 redis 容器重启也不会造成 key 的丢失

redis二

3.3.2 AOF持久化

AOF 比起 RDB 有更高的数据安全性,如果同时开启了 AOF 和 RDB,那么前者比后者的优先级更高,且如果先开启了 RDB 在开启 AOF,那么 RDB 中的内容会被 AOF 的内容覆盖。

  1. redis.conf
1
2
3
4
5
6
7
8
9
# 开启AOF持久化
appendonly yes
# AOF文件名
appendfilename appendonly.aof
# AOF持久化执行策略
# always:每次执行写操作都调用fsync
# everysec:最多每秒调用一次fsync
# no:根据环境的不同在不确定的时间调用fsync
appendfsync always|everysec|no
  1. 重启 docker-compose 测试
1
2
3
set gander male
# 不RDB持久化
shutdown nosave
  1. 可以看到 data 目录下多了个 aof 文件,即 AOF 持久化生成的文件

redis三

3.4 Redis主从架构

Redis 的主从架构是指 Master 节点负责写操作,而其余的 Slave 节点负责读操作。

redis四

  1. docker-compose.yaml
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
version: '3.8'
services:
redis-master:
container_name: redis-master
image: daocloud.io/library/redis:5.0.9
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 7001:6379
volumes:
- ./redis1.conf:/usr/local/redis/redis.conf
- ./data1:/data
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis-slave1:
container_name: redis-slave1
image: daocloud.io/library/redis:5.0.9
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 7002:6379
volumes:
- ./redis2.conf:/usr/local/redis/redis.conf
- ./data2:/data
command: ["redis-server", "/usr/local/redis/redis.conf"]
# 连接redis-master容器,并将该容器ip地址映射为master
links:
- redis-master:master
redis-slave2:
container_name: redis-slave2
image: daocloud.io/library/redis:5.0.9
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 7003:6379
volumes:
- ./redis3.conf:/usr/local/redis/redis.conf
- ./data3:/data
command: ["redis-server", "/usr/local/redis/redis.conf"]
links:
- redis-master:master
  1. 从节点 redis.conf
1
2
# slaveof <主节点地址> <端口>
slaveof master 6379
  1. 启动后进入容器内部通过 info 可看到节点信息

redis五

3.5 Redis哨兵模式

Redis 的主从架构有一个很明显的问题,就是当 Master 节点出现问题宕机后,那么 Redis 集群就没有可以进行写操作的 Redis 了,而哨兵就可以解决该问题。

在每个节点中都会有个哨兵与 Redis 进行连接,且哨兵与哨兵之间也会进行连接,如果 Master 节点出现故障宕机了,那么哨兵们就会选出一个 Slave 来作为新的 Master 来提供写的操作。

redis六

  1. docker-compose.yaml
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
version: '3.8'
services:
redis-master:
container_name: redis-master
image: daocloud.io/library/redis:5.0.9
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 7001:6379
volumes:
- ./redis1.conf:/usr/local/redis/redis.conf
- ./data1:/data
- ./sentinel1.conf:/data/sentinel.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis-slave1:
container_name: redis-slave1
image: daocloud.io/library/redis:5.0.9
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 7002:6379
volumes:
- ./redis2.conf:/usr/local/redis/redis.conf
- ./data2:/data
- ./sentinel2.conf:/data/sentinel.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
links:
- redis-master:master
redis-slave2:
container_name: redis-slave2
image: daocloud.io/library/redis:5.0.9
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 7003:6379
volumes:
- ./redis3.conf:/usr/local/redis/redis.conf
- ./data3:/data
- ./sentinel3.conf:/data/sentinel.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
links:
- redis-master:master
  1. Master 节点 sentinel.conf
1
2
3
4
5
6
# 以守护进程的方式运行redis
daemonize yes
# 指定Master节点,sentinel monitor <名称> <ip> <端口> <Slave个数>
sentinel monitor master localhost 6379 2
# 指定哨兵每隔多久检测一次redis主从架构
sentinel down-after-milliseconds master 10000
  1. Slave 节点 sentinel.conf
1
2
3
daemonize yes
sentinel monitor master master 6379 2
sentinel down-after-milliseconds master 10000
  1. 进入容器启动哨兵,当 Master 节点出现问题后,就会在两个 Slave 中选出一个作为新的 Master,而旧的 Master 启动后就会变为新的 Slave
1
redis-sentinel /data/sentinel.conf

3.6 Redis集群

Redis 集群在保证主从和哨兵的基本功能之外,还能提高 Redis 存储数据的能力,主要的特点如下

  • Redis 集群是无中心的
  • Redis 集群有ping-pang的机制
  • 投票机制,集群节点的数量必须是 2n+1
  • 分配了 16484 个 hash 槽,在存储数据时,会对 key 进行 crc16 的算法,并对 16384 进行取余,通过结果分配到对应的节点上,每个节点都有自己维护的 hash 槽
  • 每个主节点都要跟一个从节点,但这里的从节点只管备份,不管查询
  • 集群中半数的节点宕机后,那么集群就瘫痪

redis七

  1. docker-compose.yaml
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
version: '3.8'
services:
redis1:
container_name: redis1
image: daocloud.io/library/redis:5.0.9
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 7001:7001
- 17001:17001
volumes:
- ./conf.d/redis1.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis2:
container_name: redis2
image: daocloud.io/library/redis:5.0.9
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 7002:7002
- 17002:17002
volumes:
- ./conf.d/redis2.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis3:
container_name: redis3
image: daocloud.io/library/redis:5.0.9
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 7003:7003
- 17003:17003
volumes:
- ./conf.d/redis3.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis4:
container_name: redis4
image: daocloud.io/library/redis:5.0.9
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 7004:7004
- 17004:17004
volumes:
- ./conf.d/redis4.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis5:
container_name: redis5
image: daocloud.io/library/redis:5.0.9
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 7005:7005
- 17005:17005
volumes:
- ./conf.d/redis5.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis6:
container_name: redis6
image: daocloud.io/library/redis:5.0.9
restart: always
environment:
- TZ=Asia/Shanghai
ports:
- 7006:7006
- 17006:17006
volumes:
- ./conf.d/redis6.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
  1. redis{1..6}.conf,x 为 {1..6}
1
2
3
4
5
6
7
8
9
10
11
12
# 指定redis端口
port 700x
# 开启集群
cluster-enabled yes
# 集群信息文件
cluster-config-file nodes-700x.conf
# 集群对外ip
cluster-announce-ip 192.168.88.135
# 集群对外端口
cluster-announce-port 700x
# 集群总线端口
cluster-announce-bus-port 1700x
  1. 进入任意 reids 容器创建集群
1
2
# --cluster-replicas:每个主节点分配的从节点个数
redis-cli --cluster create 192.168.88.135:7001 192.168.88.135:7002 192.168.88.135:7003 192.168.88.135:7004 192.168.88.135:7005 192.168.88.135:7006 --cluster-replicas 1
  1. 由于每个主节点都被分配了不同的 hash 槽,所以要在容器内任意切换不同的 redis 节点需要加参数 -c
1
redis-cli -h 192.168.88.135 -p 7001 -c

四、Redis常见问题

4.1 Redis的删除策略

当 key 的生存时间到了,Redis 并不会立即删除该 key,而是遵守以下删除策略来进行删除

  • 定期删除:Redis 每隔一段时间就回去查看设置了生存时间的 key,默认是 100ms 查看 3 个 key
  • 惰性删除:当用户去查询已经超过了生存时间的 key,Redis 会先查看该 key 是否已经超过了生存时间,如果超过,那么 Redis 会将该 key 删除并给用户返回一个空值

4.2 Redis的淘汰机制

当 Redis 内存满的时候添加了一个新的数据,那么就会执行 Redis 的淘汰机制,通过 maxmemory-policy 来设置,参数如下

  1. volatile-lru:当内存不足时,会删除一个设置了生存时间且最近最少使用的 key

  2. allkeys-lru:当内存不足时,会删除一个设置了最近最少使用的 key

  3. volatile-lfu:当内存不足时,会删除一个设置了生存时间且最近使用频率最低的 key

  4. allkeys-lfu:当内存不足时,会删除一个设置了最近使用频率最低的 key

  5. volatile-random:当内存不足时,会随机删除一个设置了生存时间的 key

  6. allkeys-random:当内存不足时,会随机删除一个 key

  7. volatile-ttl:当内存不足时,会删除一个生存时间最少的 key

  8. noeviction:内存不足时,直接报错

4.3 缓存问题

缓存穿透

当客户查询的数据 Redis 中没有,数据库中也没有,且请求量特别大时,就会导致数据库的压力过大,解决方法如下

  • 根据 id 查询时,如果 id 是自增的,那么可以将最大的 id 放到 Reids 中,当查询数据时直接对比 id 即可
  • 如果 id 不是 int 型,那么可以将全部的 id 放入 set 中,用户查询之前可以先到 set 查看是否有该 id
  • 获取用户的 ip 地址,对该地址进行访问限制

缓存击穿

当用户查询的是热点数据时,那么并发量肯定是很高的,当 Redis 中的热点数据过期了,那么数据库的压力就会很大,甚至宕机,解决方法如下

  • 在访问热点数据时,缓存中没有的时候,可以添加一把锁,让几个请求去访问数据库,避免数据库宕机
  • 把热点数据的生存时间去掉

缓存雪崩

当大量缓存同时到期时,导致请求都去到了数据库,也很容易导致数据库宕机,解决方法如下

  • 对缓存中的数据设置一个随机的生存时间,避免同时过期

缓存倾斜

如果将热点数据放在集群中的某一 Redis 节点上时,那么大量的数据都会去到该 Redis 节点,导致节点宕机,解决方法如下

  • 主从架构,准备大量的从节点
  • 在 Tomcat 中做 JVM 缓存,在查询 Redis 前先查询 JVM 缓存