一、Redis速度快的原因
1
、纯内存操作
2
、单线程操作,可以避免频繁的上下文切换和资源争用问题,多线程需要占用更多的
CPU
资源
3
、采用了非阻塞
I/O
多路复用机制
4
、提供了非常高效有用的数据结构,例如双向链表、压缩页表和跳跃表等,可以根据现实中的数据类型选择合理的数据编码
Redis
是以内存的操作为基础,
CPU
一般不会是
Redis 遇到的困难
,
Redis
的难处最有可能是机器内存的大小或者网络宽带。既然单线程很容易实现,而且 CPU
不会成为问题的话,那么采用单线程的方案来解决!!!
注意:
本质上
Redis
不是单纯的单线程服务模型,一些辅助工作比如持续刷盘、习惯性的删除等任务是由
BIO线程来完成的,单线程主要是与客户端交互完成命令请求和回复的工作线程。
重点:
执行命令的最主要的模块是单线程的。新的命令不会立即被执行,而是统一放到了队列,一条一条的执行。
二、Redis 和 Memcached
1
、存储方式:
Memcache
会把数据全部存在内存里,断电后会自动挂掉的,数据不能超过内存大小。
Redis
部分数据存储在硬盘上,保证了数据的持久性。
2
、数据支持类型:
Memcache
对数据类型的支持简单,只支持简单
key-value
,而
Redis
最基本都要支持五种数据类型。
3
、底层模型不同:它们之间底层实现方式以及与客户端之间通信的应用协议不一样。
Redis
直接构建了 VM
机制,因为一般的系统调用系统函数的话,需要花费一定的时间去移动和请求。
4
、值大小:
Redis
可以达到
1GB
,而
Memcache
只有
1MB
。
2.1 Redis 缓存使用场景
1
、降低后端负载
2
、加速请求响应
3
、大量写合并为批量写
2.2 Redis 缓存策略
1、LRU、LFU、FIFO
2
、超时删除
3
、主动更新
三、内存淘汰策略
1、volatile-lru 从已经设置过期时间的数据挑选最近最少使⽤的数据淘汰
2、volatile-random 从已设置过期时间的数据集中任意选择数据淘汰
3
、
allkeys-lru
当内存不⾜以容纳新写⼊数据时,在键空间中,移除最近最少使⽤的
key
,常用
4
、
allkeys-random
从数据集中任意选择数据淘汰
5
、
volatile-ttl
从已设置过期时间的数据集中挑选将要过期的数据淘汰
6
、
no-eviction
禁止驱逐数据,也就是说当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错
OOM
。
4.0 版本后增加两种:
7
、
volatile-lfu
从已设置过期时间的数据集中挑选最不经常使⽤的数据淘汰
8
、
allkeys-lfu
当内存不⾜以容纳新写⼊数据时,在键空间中,移除最不经常使⽤的
key
。
常见选择
:
- allkeys-lru 用于对缓存的访问符合幂律分布,存在相对热点数据,或者不太清楚应用的缓存访问分布状况,可以选择 allkeys-lru 策略。
- allkeys-random 对于缓存 key 的访问概率相等,可以使用这个策略。volatile-ttl 策略使得可以向 Redis 提示哪些 key 更适合被删除!!!
四、Redis 删除策略
redis
开发了一个空间来放值的地址和过期时间,删除策略是为了在内存和
cpu
之间找到一个平衡,过期数据通常是在 cpu
空余的时候被删除的。
Redis
中的过期数据删除情况:
redis
服务器有很多的操作需要被执行,执行会导致
CPU
的工作大大的增加,当内存的空间还足够时,被删除的数据的内存空间并未直接释放,对客户端的指令先执行,redis 中的数据删除策略包括定时删除、惰性删除、定期删除。
![](https://img-blog.csdnimg.cn/9304cc743655410eaca9afce088f6e3e.png)
五、Redis 缓存问题
1、缓存穿透:大量请求缓存中数据库并不存在的数据。解决方案:1、布隆过滤器。2、缓存空对象
2
、缓存击穿:大量请求缓存中同时访问一个过期数据。解决方案:
1
、设置
key
永不过期和随机时间失效。
2
、 互斥锁使访问有序
3
、缓存雪崩:大量请求缓存中大面积失效的缓存数据。解决方案:
1
、缓存数据设置随机的过期时间,防止同一时间大量数据集合失效。2
、集群,将数据分布在不同的缓存数据库中。
3
、限流,通过加锁或队列来控制读数据库写缓存的线程数量。
六、Redis编程客户端
Lettuce
支持同步、异步通信的方式
API
调用,也支持响应式编程
API
,包括发布
/
订阅消息、高可用性服务部署架构。
Jedis
是
Redis
的
Java
实现客户端,提供了比较全面的
Redis
命令的支持。
优点
:提供了比较全面的
Redis
操作特性的
API
;
API
基本与
Redis
的指令一一对应,使用简单易理解。
缺点
:同步阻塞
IO
、不支持异步、线程不安全
六、使用 Jedis 和 lettuce 总结
- 调大连接池大小可以提高 jedis 的吞吐量,但是不能避免出现超时错误和很长的时间等待。jedis 连接方式最大连接数和最小、最大空闲连接数设置为一样有利于减少上下文切换时间,提升效率。
- lettuce 调大连接池大小反而会影响性能,最佳个数=CPU 核数+1,lettuce 整体稳定性和性能优于 jedis 方式。
常见
value
的数据类型
![](https://img-blog.csdnimg.cn/d8112862800142e7a386d7380998a968.png)
有一个典型的工具类代码程序如下:
@Slf4j
@Component
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
public boolean set(final String key, Object value){ 写入缓存
boolean res=false;
try{
this.set(key,value,new Random().nextInt(60)+10); 10 到 70 秒的随机数
res=true;
} catch (Exception e){
log.debug("问题:"+e.getMessage()); }
return res;
}
public boolean set(String key,Object value,int timeout){ 写入缓存设置时效时间
boolean res=false;
try{
redisTemplate.opsForValue().set(key,value);
redisTemplate.expire(key, Duration.ofSeconds(timeout));
res=true;
} catch (Exception e){
log.debug("问题:"+e.getMessage()); }
return res;
}
public Object get(String key){ 读取缓存
Object res=null;
try {
res = redisTemplate.opsForValue().get(key);
} catch (Exception e){
log.debug("问题:"+e.getMessage()); }
return res;
}
public void remove(String key){ 删除 key,也删除对应的 value
if(exists(key)) redisTemplate.delete(key);
}
public boolean exists(String key){ 判断缓存中是否有对应的 value
return redisTemplate.hasKey(key); }
public long getExpire(String key) { 根据 key 获取过期时间,单位为秒
return redisTemplate.getExpire(key, TimeUnit.SECONDS); }
public boolean expire(String key, long time) { 指定缓存失效时间
try {
if (time > 0) redisTemplate.expire(key, time, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
log.debug("问题:"+e.getMessage());
return false; }
}
public Set<String> keys(String keyPattern){ 获取指定对应 pattern 模板的所有 key
return redisTemplate.keys(keyPattern); }
}
七、Redis 序列化器
针对数据的序列化
/
反序列化提供了许多种可以选择策略
RedisSerializer
- JdkSerializationRedisSerializer 用来作为 POJO 对象的存取场景,使用 JDK 本身序列化机制,将 pojo 类通过ObjectInputStream/ObjectOutputStream 进行序列化操作,最终 redis-server 中将存储字节序列。是目前为止最常用的序列化策略。
- StringRedisSerializer用于Key和value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是 new String(bytes, charset)和 string.getBytes(charset)的直接封装,是最轻量级和高效的策略。
- JacksonJsonRedisSerializer 是 jackson-json 工具提供的 javabean 与 json 之间的转换能力,可以将 pojo 实例序列化成 json 格式存储在 redis 中,也可以将 json 格式的数据转换成 pojo 实例。因为 jackson 工具在序列化和反序列化时,需要明确指定 Class 类型,因此此策略封装起来稍微复杂。
7.1 缓存与数据库双写不一致
缓存可以提升性能、缓解数据库压力,但是使用缓存也会导致数据不太一致的问题。一般写数据操作使用缓存有三种经典的缓存模式 Cache-Aside Pattern
、
Read-Through/Write through
和
Write behind
,这三种常见的更新策略本质上都是保证数据的最终一致性的方法,可以总结为:
- 先更新数据库再更新缓存,后续线程会读取旧数据
- 先删除缓存再新数据库,并发线程读取旧数据并写到缓存
- 先更新数据库再删除缓存,后续线程会读取旧数据
常见解决方案是延时双删,但是延时的时长不好控制。
八、Redis 持久化
Redis
为了保证效率,数据缓存在了内存中,但是会每次把更新的数据写入磁盘或者把修改操作写入追加的记录文件中,以保证数据的持久化。总的目的把数据保存到硬盘,有 RDB
和
AOF
两种。RDB 持久化方案
: RDB
是一次的全量备份,快照形式是周期性直接把内存中的数据保存到一个
dump
文件中,定时保存。是 redis
默认的存储方式,我很喜欢使用这个!!
优点
:
性能消耗的比较小,速度快
缺点
:
容易丢失数据
AOF
持久化方案:把所有的对
Redis
的服务器进行修改的命令都存到一个文件里,是命令的集合。
优点
:
不容易丢失数据
缺点
:
性能差,给客户的体验度不好
当
Redis
重启时会优先使用
AOF
文件来还原数据集,因为
AOF
文件保存的数据集通常比
RDB
文件所保存的数据集更完整。甚至可以关闭持久化功能,让数据只在服务器运行时存储
九、RDB 持久化
RDB
持久化方案是最推荐使用的。
Redis
生成二进制压缩的快照文件
xxx.rdb
保存到磁盘。
RDB
工作原理:当
Redis
需要做持久化时,
Redis
会
fork
一个子进程,子进程将数据写到磁盘上一个临时
RDB 文件中。当子进程完成写临时文件后,将原来的 RDB
替换掉,这样的好处是可以
copy-on-write
。定时生成 RDB
快照非常便于进行数据库备份,并且
RDB
恢复数据集的速度也要比
AOF
恢复的速度快。
Redis4.0支持同时开启 RDB
和
AOF
,系统重启后,
Redis
会优先使用
AOF
来恢复数据,这样丢失的数据会最少。
这个肯定是需要查看配置文件,满足三个条件中一个触发生成快照
rdb
文件。可以配置符合快照触发条件,默认的是
1
分钟内改动 1
万次,或者
5
分钟改动
10
次,或者是
15
分钟改动一次
save 900 1
save 300 10
save 60 10000
启动服务器的时候需要通过命令行启动,进入
reids
的安装目录
redis-server.exe redis.conf
save
命令:
save
时只管保存,其他不管,全部阻塞
bgsave
命令:
redis
会在后台进行快照操作,快照操作的同时还可以响应客户端的请求,可以通过
lastsave
命令获取最后一次成功执行快照的时间。
十、AOF 持久化
AOF
日志存储的是
redis
服务器的顺序指令序列,即对内存中数据进行修改的指令记录。当
redis
收到客户端修改指令后,先进行参数校验,如果校验通过,把该指令存储到 AOF
日志文件中,也就是先存到磁盘,然后再执行该修改指令。当 redis
宕机后重启后,可以读取该
AOF
文件中的指令,进行数据恢复,恢复的过程就是把记录的指令再顺序执行一次,这样就可以恢复到宕机之前的状态。
打开
AOF
配置
redis.conf
中的
appendonly yes
就可以打开
AOF
功能,例如
appendfsync no
当设置
appendfsync为 no
的时候,
Redis
不会主动调用
fsync
去将
AOF
日志内容同步到磁盘,所以这一切就完全依赖于操作系统的调试了。对大多数 Linux
操作系统,是每
30
秒进行一次
fsync
,将缓冲区中的数据写到磁盘上。
appendfsync everysec
当设置
appendfsync
为
everysec
的时候,
Redis
会默认每隔一秒进行一次
fsync
调用,将缓冲区中的数据写到磁盘。但是当这一次的 fsync
调用时长超过
1
秒时。
Redis
采取延迟
fsync
的策略再等一秒钟。也就是在两秒后再进行 fsync,这一次的 fsync 就不管会执行多长时间都会进行。这时候由于在
fsync 时文件描述符会被阻塞,所以当前的写操作就会阻塞。
在绝大多数情况下,
Redis
会每隔一秒进行一次
fsync
。在最坏的情况下,两秒钟会进行一次
fsync
操作。这一 操作在大多数数据库系统中被称为 group commit
,就是组合多次写操作的数据,一次性将日志写到磁盘
appendfsync always
每一次写操作都会调用一次
fsync
,这时数据是最安全的,当然由于每次都会执行
fsync,
所以其性能也会受到影响
10.1 AOF 重写
因为
AOF
持久化是通过保存被执行的写命令来记录数据库状态的,那么就会涉及到很多无用的命令,如
set a b 和
set a c
以及
set a d
,其实就最后一条有意义。
Redis
会
fork
一个进程来读取现在
redis
生成的
AOF
文件,然后在内存中去除冗余命令,在此过程中不会影响原来 AOF
文件的继续写入,如果有新的命令,会缓存在重写缓冲中,当重写完全结束后会替换掉原来的
AOF文件。
重写触发条件:手动命令
BGREWRITEAOF
和配置自动调用。
10.2 RDB 与 AOF 的选择
宕机后会优先加载 AOF 文件。RDB 保存的数据,AOF 保存的命令,RDB 文件比 AOF 小。恢复速度 RDB 小, 更快。RDB 一次写入的数据较多,时间间隔会比 AOF 长,出现宕机丢失的数据会更多各有优劣,如果能综合就好了,所幸的是在 redis4.0 后,通过配置 aof-use-rdb-preamble 就可以开启两者混合持久化,取长补短。
10.3 单机性能建议
因为 RDB 文件只用作后备用途,只要 15 分钟备份一次就够了,只保留 save 900 1 这条规则。 如果 Enable AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只 load 自己的 AOF 文件就可以了。 代价一是带来了持续的 IO,二是 AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少 AOF rewrite 的频率,AOF 重写的基础大小默认值 64M 太小了,可以设到 5G 以上。 默认超过原大小 100%大小时重写可以改到适当的数值。
10.4 混合持久化
Redis4.0 后大部分的使用场景都不会单独使用 RDB 或者 AOF 来做持久化机制,而是兼顾二者的优势混合使用其原因是 RDB 虽然快,但是会丢失比较多的数据,不能保证数据完整性;AOF 虽然能尽可能保证数据完整性,但是性能确实是一个诟病,比如重放恢复数据
混合持久化通过
aof-use-rdb-preamble yes
开启,
Redis 4.0
以上版本默认开启推荐是 AOF
和
RDB
两者均开启如果对数据不敏感,可以选单独用 RDB
不建议单独用 AOF
,因为可能会出现
Bug 如果只是做纯内存缓存,可以都不用。
十一、Redis 事务
事务是指一个完整的动作,要么全部执行,要么什么也没有做。
redis
事务相关的四个
redis
指令,即
multi
、exec、
discard
、
watch
。这四个指令构成了
redis
事务处理的基础。
1
、
multi
用来组装一个事务;
2
、
exec
用来执行一个事务;
3
、
discard
用来取消一个事务;
4
、
watch
用来监视一些
key
,一旦这些
key
在事务执行之前被改变,则取消事务的执行。
redis> multi
redis> INCR id
redis> INCR id
redis> exec
用
multi
组装事务时,每一个命令都会进入内存中缓存起来,
QUEUED
表示缓存成功,在
exec
时这些被
QUEUED的命令都会被组装成一个事务来执行。
对于事务的执行来说,如果
redis
开启了
AOF
持久化的话,那么一旦事务被成功执行,事务中的命令就会通过 write 命令一次性写到磁盘中去,如果在向磁盘中写的过程中恰好出现断电、硬件故障等问题,那么就可能出现只有部分命令进行了 AOF
持久化,这时
AOF
文件就会出现不完整的情况,这时可以使用
redis-check-aof
工具来修复这一问题,这个工具会将 AOF
文件中不完整的信息移除,确保
AOF
文件完整可用。
十二、Redis 集群
Redis
单节点存在单点故障问题,为了解决单点问题,一般都需要对
Redis
配置从节点,然后使用哨兵来监听主节点的存活状态,如果主节点挂掉,从节点能继续提供缓存功能,主从配置结合哨兵模式能解决单点故障问题,提高 Redis
可用性。从节点仅提供读操作,主节点提供写操作。对于读多写少的状况,可给主节点配置多个从节点,从而提高响应效率。
与 mysql 相同,redis 也提供了主从的功能,更加非常简单。Mysql 的一主多从不能达到主高可用,只能提高并
发,高可用不能直接依赖于 mysql
主从复制,需要依赖于集群;
redis
的主从通过哨兵机制可以达到高可用;
高并发:单位时间内可以接收的请求数量
高可用:服务器的服务质量
12.1.1 应用场景
1、在生产中难以避免单台
redis
出现故障,保证高可用可以用主从
2、单台
redis
官网说能抗住
11w
并发量,超过了
10w
高并发,就可以主从
3、
QPS 瓶颈。其中 QPS 每秒处理的查询次数、TPS 每秒处理的事务数。从请求到获得 数 据 为一个完整事务过程
注意:一个
master
可以有多个
slave
,一个
slave
只能有一个
master
,数据流向是单向的
master
到
slave
12.1.2 主从复制的作用
1、读写分离:master
写,
slave
读,提高服务器的读写负载能力
2、负载均衡:基于主从架构,配合读写分离,由 slave
分担
master
负载,并根据需求 的变化,改变
slave
的 数量,通过多个从节点分担数据读取负载,大大提高 redis
服务 器并发量和数据吞吐量
3、故障恢复:当 master
出现问题时,由
slave
提供服务,实现快速的故障恢复
4、数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
5、高可用基石:基于主从复制,构建哨兵模式与集群,实现 redis 的高可用方案。
12.1.2 主从复制实现步骤
1
、复制
redis
为
2
个,对应的端口号分别为
6379
和
6380
,通过启动两个进程以模拟两个节点的情形
2
、设置主从关系,例如
6379
为
master
,可以通过
info replication
查看节点角色
3
、
slaveof localhost 6379
将一个节点转换为从节点。打开从节点配置文件
redis.conf
文件,找到
#slaveof <masterip> <masterport>这个地方,添加
slaveof master
的
ip
地址
master
的端口
4
、从节点配置文件
slave-read-only
为
yes
则从节点只能读。注意如果配置从节点可以写的化主节点不会获数据
5
、可以在
redis-cli -p 6379
的客户端使用
shutdown
关闭主节点,这时候从节点不会自动转换为主节点,重启主节点后它仍旧是 master
十三、哨兵模式 Sentinel
Redis Sentinel
是
Redis
的高级别的可用实现方案,可以实现对
redis
的监控、通知和自动故障转移功能,当
redis master挂掉之后,可以自动拉起 slave
提供业务,最终实现了
redis
的高可用。为了避免
Sentinel
本身出现单点故障,Sentinel 自己也可以采用集群模式!!!
13.1 哨兵模式的原理
我认为 Sentinel 是一种很特殊的 redis 节点,每个 sentinel 节点会维护与其他 redis 节点,包括 master、slave、sentinel的心跳。
1
、当一个
sentinel
节点与
master
节点的心跳丢失时,这个
sentinel
节点就会认为
master
节点出现了故障,处于不可用的状态,这种判定叫作主观下线,即 sentinel
节点自己主观认为
master
下线了。
2
、之后,这个
sentinel
节点会与其他
sentinel
节点交换信息,如果发现认为主节点发生故障的
sentinel
节点的个数超过了某个阈值(通常为 sentinel
节点总数的
1/2+1
,即超过半数),则
sentinel
会认为
master
节点已经处于客观下线的状态,即大家都认为 master
故障不可用了。
3
、之后,
sentinel
节点中会选举处一个
sentinel leader
来执行
redis
主节点的故障转移。
13.2 进行故障转移的具体步骤
1、在从节点列表中选出一个节点作为新的主节点过滤不健康或者不满足要求的节点选择 slave-priority 优先级最高的从节点,如果存在则返回,不存在则继续选择复制偏移量最大的从节点,如果存在则返回,不存在则继续选择 runid 最小的从节点
2、Sentinel 领导者节点会对选出来的从节点执行 slaveof no one 命令让其成为主节点
3
、
Sentinel
领导者节点会向剩余的从节点发送命令,让他们从新的主节点上复制数据
4
、
Sentinel 领导者会将原来的主节点更新为从节点, 并对其进行监控, 当其恢复后命令它去复制新的主节点。
13.3 集群架构
Redis 集群使用数据分片 sharding 而非一致性哈希 consistency hashing 来实现
1
、一个
Redis
集群包含
16384
个哈希槽
hash slot
,数据库中的每个键都属于这
16384
个哈希槽的其中一个
2
、集群使用公式
CRC16(key) % 16384
来计算键
key
属于哪个槽, 其中
CRC16(key)
语句用于计算键
key的 CRC16
校验和
3
、集群中的每个节点负责处理一部分哈希槽