Redis
一. 简介
Redis是一个基于内存存储 NoSQL(Not Only SQL) 数据库,正式因为它数据是存储到内存中的,所以其操作效率非常高,它的数据存储形式是Key-Value对,它具备以下几个优点:
- 效率高,每秒8万次数据读取,每秒11万次的数据写入。
- Redis所有操作都是原子性,不用考虑线程安全问题。
- Redis支持丰富的数据类型。
二. Redis的安装
第一步,先下载 Redis, 下载地址:https://redis.io/download>
第二步,解压,然后进入到解压目录,执行如下命令:
make
如果执行 make令报错,那么执行如下命令,安装 c 语言的编译器
yum install -y cpp binutils glibc glibc-kernheaders glibc-common glibc-devel gcc
执行完如上命令,再试着执行 make 命令,如果还是报错,再执行如下命令:
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
scl enable devtoolset-9 bash
执行完如上的命令,再执行 make 命令,还是报错,就执行如下命令:
make MALLOC=libc
解决如上的问题的方式参照网址:https://blog.youkuaiyun.com/realize_dream/article/details/106483499
第三步,启动Redis, 进入到src目录下,执行如下命令:
./redis-server
# 如果想让日志的打印再后台进行运行,执行如下命令
./redis-server &
# 指定配置配置文件的方式启动redis
src/redis-server ./redis.conf
第四步,通过客户端连接 redis
./redis-cli
三. Redis支持的数据类型
Redis总共有10种数据类型:string、lists、sets、hash、sorted set、stream、geo、bitmap、bitfileds、webloglog。
四. Redis5种数据类型操作
4.1 通用命令
通用命令就是不局限于某种数据类型的命令,所有的数据类型都可以通用的命令。
# 查看redis种所有的key
keys *
# 删除key1和key2两个key
del key1 key2
# 表示让key的有效期是20S
expire key 20
# 查询key还有多久过期, ttl => time to live
ttl key
# 判断某个key是否存在
exists key
4.2 strings
# 将num的值设置为1
set num 1
# 设置name的值为lisi, 并且name的有效期是60S
set name lisi EX 60
# 获取num的值
get num
# 将num的值+1
incr num
# 将num的值11
decr num
# 将num的值+5
incrby num 5
# 将num的值 -3
decrby num 3
# setnx(set if not exist, 如果不存在就设置值),如果不存在key为num的数据,就将num值设置为20
setnx num 20
4.3 lists
# 往address种依次添加 wuhan hangzhan changsha 这三个
lpush address wuhan hangzhou changsha
# 查看address的元素的个数
llen address
# 查看address中索引从0到2的元素
lrange address 0 2
# 查看address中索引从0到倒数第一个元素,也就是全部,用的时候慎重
lrange address 0 -1
# 从顶部删除一个元素,并返回该元素
lpop address
# 将address中索引为1的值替换为 beijing
lset address 1 beijing
# 在 beijing 的前面插入shenzhen
linsert address before beijing shenzhen
# 往底部添加 tianjing这个元素
rpush address tianjing
# 从底部移除一个元素,并返回该元素
rpop address
4.4 sets
# 往address中添加多个元素,但是wuhan只会添加一次,因为set中元素是不能重复
sadd address wuhan beijing tianjing hangzhou wuhan
# 返回 address 中元素的个数
scard address
# 返回address 中所有的元素,用的时候慎重
smembers address
# 查看wuhan在不在 address 中
sismember address wuhan
# 将武汉这个元素移除掉
srem address wuhan
# 随机移除一个元素
spop address
4.5 hash
# 将home的值设置为wuhan ,将work的值设置为 shanghai
hset address home wuhan work shanghai
# 获取 homde 值,返回wuhan
hget address home
# 获取到address中所有的数据
hgetall address
# 将work 的数据删除掉
hdel address work
# 返回address中的key
hkeys address
# address 中所有的value
hval address
# 获取address的长度
hlen address
# 判断 work是否存在
hexists address work
# 将 num 的值+2
hincrby address num 2
4.6 sorted set
sorted set它还是一个集合,元素是不能重复,它是通过给每个元素指定一个分数,通过这个分数来控制元素的顺序。
# zhangsan的分数是50;lisi的分数是51
zadd rank 50 zhangsan 51 lisi
# 统计 rank的元素的个数
zcard rank
# 展示所有的元素
zrange rank 0 -1
# 展示所有的元素以及对应的分数
zrange rank 0 -1 withscores
# 统计分数在 [20, 50] 到元素的个数
zcount rank 20 50
# 给lisi这个元素的分数 +15
zincrby rank 15 lisi
# 按照分数从大到小的顺序进行排序
zrevrange rank 0 -1
# 按照分数从大到小的顺序进行排序, 并且将分数也带出来
zrevrange rank 0 -1 withscores
五. 安全配置
所谓的安全配置就是给redis设置访问的ip和认证的密码
#表示所有的ip可以访问
bind 0.0.0.0
# 给redis设置密码
requirepass admin
六. redis整合springboot
- 引入如下依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.1</version>
</dependency>
- 新建一个配置文件
redisson-standalone.yml
,内容如下
singleServerConfig:
# redis的连接地址
address: "redis://192.168.24.129:6379"
connectionMinimumIdleSize: 16
password: admin
nettyThreads: 4
# 通信方式是通过 NIO(Non Blocking IO或者 New IO)方式 (Netty)
transportMode: NIO
- 在主配置文件中,引入如下的配置
spring:
redis:
redisson:
file: classpath:redisson-standalone.yml
- 在代码中注入
StringRedisTemplate
Bean
七. 主从备份
主从备份,只需要在从节点中加入如下配置:
# 指定要复制数据的主节点的ip和端口
replicaof 192.168.24.129 6379
# 指定主节点的密码
masterauth admin
八. redis的持久化策略
8.1 RDB
RDB的全称是 Redis Database,是redis默认开启的,在指定的时间内,如果有对应条数的数据发生改变,会自动写入到磁盘;RDB存储的是数据,恢复数据的时候直接从磁盘加载数据,所以它的数据恢复性能比AOF好,如果只是让redis作为一个缓存来使用,可以只用开启 RDB,如果使用 Redis作为数据库来使用,需要开启 AOF。
8.2 AOF
AOF的全称是 append only file,需要手动开启,它是为了弥补RDB的不足,它默认是每秒将数据异步将数据写入到磁盘上,也可以实时写入数据,AOF中记录的是操作,它恢复数据的方式是将操作重放,所以性能相较于RDB要低,如果要将Redis作为数据库来使用,需要开启AOF.
九. Redis的哨兵模式
9.1 哨兵模式搭建
Redis节点主机信息:
IP地址 | 角色 |
---|---|
192.168.24.129 | 主节点 |
192.168.24.130 | 从节点 |
192.168.24.131 | 从节点 |
主节点配置:
# 允许所有的ip可以访问
bind 0.0.0.0
# 配置密码
requirepass admin
# 因为它虽然刚启动的时候是主节点,但是未来可能变为一个从节点
masterauth admin
从节点配置:
# 允许所有的ip可以访问
bind 0.0.0.0
# 配置密码
requirepass admin
# 因为它虽然刚启动的时候是主节点,但是未来可能变为一个从节点
masterauth admin
# 刚开始需要配置哪个节点作为主节点
replicaof 192.168.24.129 6379
哨兵节点:
IP的地址 |
---|
192.168.24.129 |
192.168.24.130 |
192.168.24.131 |
三个哨兵的配置:
# sentinel monitor 这个是固定值
# mymaster 是给主节点取了个名字
# 192.168.24.129 6379 主节点的ip和端口
# 2表示至少得有两个哨兵认为某个节点是主节点
sentinel monitor mymaster 192.168.24.129 6379 2
# sentinel auth-pass 固定写法
# mymaster 就是上面给主节点取的一个别名
# admin 是主节点的密码
sentinel auth-pass mymaster admin
哨兵启动的命令:
src/redis-sentinel ./sentinel.conf &
9.2 查看redis集群和哨兵的状态
# 查看集群的状态
info repliaction
# 查看哨兵的状态
info sentinel
9.3 脑裂问题以及解决方式
#redis从节点至少两个,至少有几个从节点才可以写入数据
min-replicas-to-write 1
十. Redis的集群模式
11. 1 redis集群模式搭建
集群规划:
Host | 主节点端口 | 从节点信息 |
---|---|---|
192.168.24.129 | 192.168.24.129:6379 | 6380是 192.168.24.131:6379 从节点 |
192.168.24.130 | 192.168.24.130:6379 | 6380是 192.168.24.129:6379 从节点 |
192.168.24.131 | 192.168.24.131:6379 | 6380是 192.168.24.130:6379 从节点 |
创建redis集群,在任何一台机器中都可以操作:
# redis-cli --cluster create 创建集群的命令
# 192.168.24.129:6379 192.168.24.130:6379 192.168.24.131:6379 这三台分别是主节点
# 192.168.24.130:6380 192.168.24.131:6380 192.168.24.129:6380 这三台分别是从节点
src/redis-cli -a admin --cluster create 192.168.24.129:6379 192.168.24.130:6379 192.168.24.131:6379 192.168.24.130:6380 192.168.24.131:6380 192.168.24.129:6380 --cluster-replicas 1
查看集群状态:cluster nodes
集群设置数据:
集群做法是将redis的key做以一个hash运算,然后对 16384 取模操作(HASH_SLOT = CRC16(key) mod 16384),结果在 [0, 16383] 区间返回内,集群对每个节点划分都有槽位(slot),如果值落到某个区间,然后就将该值设置到对应的主节点中。
11.2 集群模式的问题
集群模式带来的问题是数据倾斜,数据倾斜分为两种情况:
- 写倾斜
- 读倾斜;
写倾斜:在设置redis的key的时候,让底层通过 CRC16 hash运算的时候,不要对整个key做hash,而是对数据唯一的标识(一般是数字)做hash运算:
# 这种情况下,在hash运算的时候,是对 CRC16("product:1001")
set product:1001 商品信息
# 这种情况下,在hash运算的时候,是对 CRC16("1001")
set product:{1001} 商品信息
读倾斜:人工干预,将倾斜的数据分散到不同的节点,在访问时候,让访问开始分散。
十一. 缓存击穿、穿透、雪崩
缓存穿透、击穿、雪崩在大量的请求情况下,会带来很多问题。
缓存击穿,表示要查询的数据,缓存中没有,数据中存在,在并发场景,会导致所有请求都抵达数据库,导致数据库压力很大。
解决方式:控制并发即可。
缓存穿透,表示要查询的数据,缓存中没有,数据中也没有,恶意用户就一直查询这个不存在的数据,SQL会一直数据库中执行,导致结果是数据压力很大。
解决方式:将这种数据在缓存中存储一个 “null”. 但是它会诱发另外一个问题,缓存中可能存在这大量的 “null” 问题。解决大量的 “null” 字符串的问题,使用布隆过滤器。
缓存雪崩:大量的用户查询大量不同的数据,但是这些数据在redis都失效了,导致所有的查询请求都打到数据库中了。
解决方式:将不同的key让其失效时间尽量随机,而且随机的区间要很大。
十二. Redis的内存淘汰策略
redis会将所有设置了过期淘汰的键存储到一个集合中,对于这种需要到点删除的key,redis对这些过期键的删除有两种策略: 1. 定时删除;2. 惰性删除;
定时删除就是redis底层会通过定时每100ms扫描这个集合,执行流程如下:
1. 每次随机性取20条,然后判断是否已经过期,如果过期就删除; 2. 如果从随机取的20条数据中,有超过1/4的键过期; 3. 再重复第一步; 定时删除,使用随机的方式是因为,如果每次进行一个全部key的判断,会导致CPU性能损耗过大。 因为定时删除肯定存在着有些key过期没有被删除掉,于是就有了惰性删除,所谓的惰性删除就是当用户使用的时候再去判断。 但是定时删除和惰性删除这两种配合的情况下,可能会导致有些键过期还是没有被删除;于是就有了内存的淘汰策略,总共有八种:
八种内存淘汰策略的前提是在redis配置中设置了最大内存: maxmemory
1、no-eviction: 添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,就返回error,然后啥也不干
2、allkeys-lru:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,就会扫描所有的key,淘汰一些最近未使用的key
3、volatile-lru:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,扫描那些设置里过期时间的key,淘汰一些最近未使用的key
4、allkeys-random:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,就会扫描所有的key,随机淘汰一些key
5、volatile-random:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,扫描那些设置里过期时间的key,随机淘汰一些key
6、volatile-ttl:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,扫描那些设置里过期时间的key,淘汰一些即将过期的key
7、volatile-lfu:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,就会淘汰一些设置了过期时间的,使用LFU算法淘汰一些key(有些key虽然最近没有被使用,在接下来可能会被使用,就不会删除).
8、allkeys-lfu:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,就会扫描所有的key,使用 LFU这种算法淘汰一些key(有些key虽然最近没有被使用,在接下来可能会被使用,就不会删除).
面试回答:默认的是当新的添加数据会导致超出redis最大内存限制,不会删除任何key, 直接报错;还有就是在所有的设置过期时间的key的集合中,淘汰那些快过期了、很少使用的key或者随机淘汰一些key;还有就是从所有的key中淘汰掉很少使用的key、或者随机淘汰一些key; redis4.0 中增加两种,以前看过,使用的算法不同,具体的没有使用了。
十三. Redis的事务(了解)
Redis的事务操作主要有三个命令:
- multi -> begin
- exec -> commit
- discard -> rollback
十四. Redis的分布式锁
十五. Java对象在Redis中的存储
15.1 string存储
将对象序列化为一个JSON数据(json就是字符串),直接使用string进行存储;
public class Person {
private Integer id;
private String name;
// setter and getter
}
// 生成对象
Person p = new Person(1, "张三");
// 对象对应的json数据
{"id": 1, "name": "张三"}
// 存储到redis中的时候,如何存储:
person:1 => {"id": 1, "name": "张三"}
弊端在于:如果我们就像获取对象中某个属性的时候,必须将数据拿到然后反序列化转为对象,然后取属性。
15.2 hash存储
将对象转换为hash,然后hash的Key是对象的属性名,hash的value是属性的值。
78109)]