一、简介
REmote DIctionary Server(Redis)是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库。
Redis 就是一款 NoSQL,而 NoSQL 就是指非关系型数据库,主要分为四种:
- 键值型:Redis
- 文档型:ElasticSearch、Mongdb
- 面向列:Hbase
- 图形化:Neo4j
二、Redis基础
2.1 Redis安装
- 通过 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
|
- 进入容器内部测试 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 对应一个有序的集合
2.2.1 string常用命令
- 设置值
- 取值
- 批量操作
1 2
| mset key1 value1 key2 value2 ... mget key1 key2 ...
|
- 自增
- 自减
- 自增自减指定数量
1 2
| incrby key number decrby key number
|
- 设置值的同时指定生存时间
- 设置值,如果当前 key 不存在如同 set,如果存在则说明都不做
- 在 key 对应的 value 后追加内容
- 查看 value 字符串长度
2.2.2 hash常用命令
- 存储数据
- 获取数据
- 批量操作
1 2
| hmset key1 field1 value1 field2 value2 ... hmget key1 firle1 field2 ...
|
- 指定自增
1
| hincrby key field number
|
- 设置值,如果当前 key 不存在如同 set,如果存在则说明都不做
- 检查 field 是否存在
- 删除某个 field
1
| hdel key field1 field2 ...
|
- 获取当前 hash 结构中的全部 field 和 value
- 获取当前 hash 结构中的全部 field
- 获取当前 hash 结构中的全部 value
- 获取当前 hash 中 field 的数量
2.2.3 list常用命令
- 存储数据
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 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 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 2
| # value不允许重复,且数据无序排列 sadd key value1 value2 ...
|
- 获取数据
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
| srem key value1 value2 ...
|
2.2.5 zset常用命令
- 存储数据
1 2 3 4 5
| # score必须是数值,value不允许重复 zadd key score1 value1 score2 value2 ...
# 修改score,如果value存在则增加分数,如果不存在则相当于zadd zincrby key number value
|
- 获取数据
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
| zrem key value1 value2 ...
|
2.2.6 key常用命令
- 查看所有key
- 查看某个key是否存在
- 删除key
- 设置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 2 3 4 5
| # redis默认有16个库 select 0~15
# 移动key到另一个库中 move key db
|
2.2.7 库的常用命令
- 清空当前所在数据库
- 清空所有数据库
- 查看当前库有多少key
- 查看最后一次操作的时间
- 实时监控redis接收到的命令
三、Redis配置
3.1 Redis的AUTH
- 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"]
|
- 设置 Redis 连接密码
1 2
| vim redis.conf requirepass toortoor
|
- 进入 Redis 之后都需要输入密码才可以创建 key
3.2 Redis的事务
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中
一个事务从开始到执行会经历以下几个阶段:
- 开始事务:multi
- 命令入队:……
- 执行事务:exec
- 取消事务:discard
- 监听:在开启事务之前,先通过 watch 监听事务中要操作的 key,如果在事务过程中有其他的客户端修改了 key,那么事务将会被取消
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
- 监听
- 开始事务
- 命令入队
1 2 3
| set name cqm set age 22 set gander male
|
- 执行事务/取消事务
3.3 Redis的持久化
3.3.1 RDB持久化
Redis 的配置文件位于 Redis 安装目录, ROB 是默认的持久化机制。
- 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 command: ["redis-server", "/usr/local/redis/redis.conf"]
|
- redis.conf
1 2 3 4 5 6 7 8 9
| vim redis.conf
save 900 1 save 300 10 save 60 10000
rdbchecksum yes
dbfilename dump.rdb
|
- 测试持久化
1 2 3 4
| set name cqm set age 22 # 关闭redis并保存 shutdown save
|
- 可以看到 data 目录下多了个 rdb 文件,即使 redis 容器重启也不会造成 key 的丢失
3.3.2 AOF持久化
AOF 比起 RDB 有更高的数据安全性,如果同时开启了 AOF 和 RDB,那么前者比后者的优先级更高,且如果先开启了 RDB 在开启 AOF,那么 RDB 中的内容会被 AOF 的内容覆盖。
- 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
|
- 重启 docker-compose 测试
1 2 3
| set gander male # 不RDB持久化 shutdown nosave
|
- 可以看到 data 目录下多了个 aof 文件,即 AOF 持久化生成的文件
3.4 Redis主从架构
Redis 的主从架构是指 Master 节点负责写操作,而其余的 Slave 节点负责读操作。
- 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"] 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
|
- 从节点 redis.conf
1 2
| # slaveof <主节点地址> <端口> slaveof master 6379
|
- 启动后进入容器内部通过
info
可看到节点信息
3.5 Redis哨兵模式
Redis 的主从架构有一个很明显的问题,就是当 Master 节点出现问题宕机后,那么 Redis 集群就没有可以进行写操作的 Redis 了,而哨兵就可以解决该问题。
在每个节点中都会有个哨兵与 Redis 进行连接,且哨兵与哨兵之间也会进行连接,如果 Master 节点出现故障宕机了,那么哨兵们就会选出一个 Slave 来作为新的 Master 来提供写的操作。
- 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
|
- 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
|
- Slave 节点 sentinel.conf
1 2 3
| daemonize yes sentinel monitor master master 6379 2 sentinel down-after-milliseconds master 10000
|
- 进入容器启动哨兵,当 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 槽
- 每个主节点都要跟一个从节点,但这里的从节点只管备份,不管查询
- 集群中半数的节点宕机后,那么集群就瘫痪
- 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"]
|
- redis{1..6}.conf,x 为 {1..6}
1 2 3 4 5 6 7 8 9 10 11 12
| port 700x
cluster-enabled yes
cluster-config-file nodes-700x.conf
cluster-announce-ip 192.168.88.135
cluster-announce-port 700x
cluster-announce-bus-port 1700x
|
- 进入任意 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
|
- 由于每个主节点都被分配了不同的 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
来设置,参数如下
volatile-lru:当内存不足时,会删除一个设置了生存时间且最近最少使用的 key
allkeys-lru:当内存不足时,会删除一个设置了最近最少使用的 key
volatile-lfu:当内存不足时,会删除一个设置了生存时间且最近使用频率最低的 key
allkeys-lfu:当内存不足时,会删除一个设置了最近使用频率最低的 key
volatile-random:当内存不足时,会随机删除一个设置了生存时间的 key
allkeys-random:当内存不足时,会随机删除一个 key
volatile-ttl:当内存不足时,会删除一个生存时间最少的 key
noeviction:内存不足时,直接报错
4.3 缓存问题
缓存穿透
当客户查询的数据 Redis 中没有,数据库中也没有,且请求量特别大时,就会导致数据库的压力过大,解决方法如下
- 根据 id 查询时,如果 id 是自增的,那么可以将最大的 id 放到 Reids 中,当查询数据时直接对比 id 即可
- 如果 id 不是 int 型,那么可以将全部的 id 放入 set 中,用户查询之前可以先到 set 查看是否有该 id
- 获取用户的 ip 地址,对该地址进行访问限制
缓存击穿
当用户查询的是热点数据时,那么并发量肯定是很高的,当 Redis 中的热点数据过期了,那么数据库的压力就会很大,甚至宕机,解决方法如下
- 在访问热点数据时,缓存中没有的时候,可以添加一把锁,让几个请求去访问数据库,避免数据库宕机
- 把热点数据的生存时间去掉
缓存雪崩
当大量缓存同时到期时,导致请求都去到了数据库,也很容易导致数据库宕机,解决方法如下
- 对缓存中的数据设置一个随机的生存时间,避免同时过期
缓存倾斜
如果将热点数据放在集群中的某一 Redis 节点上时,那么大量的数据都会去到该 Redis 节点,导致节点宕机,解决方法如下
- 主从架构,准备大量的从节点
- 在 Tomcat 中做 JVM 缓存,在查询 Redis 前先查询 JVM 缓存