redis
redis是单线程执行的IO多路复用模型
具体实现是redis基于Reactor 开发了文件事件处理器,
文件事件处理器:
Redis6.0 引入多线程主要是为了提高网络 IO 读写性能,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。
redis是怎么知道哪个数据过期了的,redis保存了一个map,key是redis中的某个键,value是过期时间。
数据过期了怎么淘汰:
惰性删除 :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
定期删除 : 每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响
但是,仅仅依靠这两个的话,还是有oom的风险的,
所以还提供了LRU算法
内存淘汰机制:
-
allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
-
volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
-
还有一种直接拒绝新写入的数据
持久化机制:
RDP:快照机制,快照的话就是某个时间点下的数据副本
默认配置的话,是你相对时间内改变数据很频繁的话,他就很快的执行快照命令BGSAVE
AOF:
三种模式:
每当你改变数据的话,他就把命令写入aof文件中,
一秒钟同步一次,
让操作系统去决定
AOF重写机制
- AOF重写
aof 文件记录的是每一条redis命令,有时候我们会对某一个key进行多次set,中间会产生很多条命令,但是结果只有一个。
set name 1
...
set name 12
set name 123
...
set name 1234
set name 12345
例如我们执行了上述命令,aof 文件会记录着每一条命令。在redis重启时,会逐条执行上述的命令。但是其实可以精简为set name 12345
,其余的几条命令其实没有意义。AOF重写就是实现了这个功能。
相关配置:
auto-aof-rewrite-percentage 100 # 指当前aof文件比上次重写的增长比例大小,达到这个大小就进行 aof 重写
auto-aof-rewrite-min-size 64mb # 最开始aof文件必须要达到这个文件时才触发,后面的每次重写就不会根据这个变量了
以上配置的意思是:
在 aof 文件小于64mb的时候不进行重写,当到达64mb的时候,就重写一次。重写后的 aof 文件可能是10mb。上面配置了auto-aof-rewrite-percentage 100
,即 aof 文件到了20mb的时候,又开始重写一次。以此类推。
AOF 是在后台自动重写(redis会fork一个子进程来进行备份,保证主进程不会阻塞),也可以人为执行命令 bgrewriteaof
重写 AOF。
- 使用子进程来进行AOF重写时会遇到的问题
问题:
子进程在进行AOF重写期间,服务器进程还要继续处理命令请求,而新的命令可能对现有的数据进行修改,这会让当前数据库的数据和重写后的AOF文件中的数据不一致。
解决方案:
要知道redis是怎么处理这个问题的,需要先了解下AOF重写的具体实现:
AOF文件重写过程与RDB快照bgsave工作过程有点相似,都是通过fork子进程,由子进程完成相应的操作,同样的在fork子进程简短的时间内,redis是阻塞的。
(1)开始bgrewriteaof
,判断当前有没有bgsave命令(RDB持久化)/bgrewriteaof
在执行,倘若有,则这些命令执行完成以后在执行。
(2)主进程fork
出子进程,在这一个短暂的时间内,redis是阻塞的。
(3)主进程fork
完子进程继续接受客户端请求。此时,客户端的写请求不仅仅写入aof_buf
缓冲区,还写入aof_rewrite_buf
重写缓冲区。一方面是写入aof_buf
缓冲区并根据appendfsync策略同步到磁盘,保证原有AOF文件完整和正确。另一方面写入aof_rewrite_buf
重写缓冲区,保存fork之后的客户端的写请求,防止新AOF文件生成期间丢失这部分数据。
(4.1)子进程写完新的AOF文件后,向主进程发信号,父进程更新统计信息。
(4.2)主进程把aof_rewrite_buf
中的数据写入到新的AOF文件。
(5.)使用新的AOF文件覆盖旧的AOF文件,标志AOF重写完成。
redis4.0还有一个混合机制;
- Redis 4.0 混合持久化
重启 Redis 时,我们很少使用 rdb 来恢复内存状态,因为会丢失大量数据。
如果使用 AOF 日志重放,性能则相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动的时候需要花费很长的时间。
Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。
混合持久化同样也是通过bgrewriteaof
完成的,不同的是当开启混合持久化时,fork出的子进程先将共享的内存副本全量的以RDB方式写入aof文件,然后在将aof_rewrite_buf
重写缓冲区的增量命令以AOF方式写入到文件,写入完成后通知主进程更新统计信息,并将新的含有RDB格式和AOF格式的AOF文件替换旧的的AOF文件。简单的说:新的AOF文件前半段是RDB格式的全量数据后半段是AOF格式的增量数据,如下图:
在redis重启的时候,加载 aof 文件进行恢复数据:先加载 rdb 内容再加载剩余的 aof。
混合持久化配置:
aof-use-rdb-preamble yes # yes:开启,no:关闭
缓存一致性
缓存穿透
- 对查询结果为空的情况也进行缓存
- 布隆过滤器
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
如何避免?
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
缓存击穿
redis分布式锁
通过lua脚本去封装逻辑,保证原子性.
先通过setnx命令 expire命令指定过期时间
redis事务
Redis 可以通过 MULTI
,EXEC
,DISCARD
和 WATCH
等命令来实现事务(transaction)功能。
redis的事务和数据库事务是不一样的,他是没有回滚能力的,所以不满足事务的原子性,持久性其实也是不满足的,所以他的一致性也不满足
使用 MULTI
命令后可以输入多个命令。Redis 不会立即执行这些命令,而是将它们放到队列,当调用了EXEC
命令将执行所有命令。
这个过程是这样的:
- 开始事务(
MULTI
)。 - 命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行)。
- 执行事务(
EXEC
)。
你也可以通过 DISCARD
命令取消一个事务,它会清空事务队列中保存的所有命令。
WATCH
命令用于监听指定的键,当调用 EXEC
命令执行事务时,如果一个被 WATCH
命令监视的键被修改的话,整个事务都不会执行,直接返回失败。
sinterstore 求set集合交集命令
redis消息队列
通过 rpush/lpop
实现队列:
rpush 向 list 的头部(右边)添加元素 lpop 将 list的尾部(最左边)元素取出
通过 rpush/rpop
实现栈:
redis集群
单机
主从复制 无法保证高可用 无法解决写的压力
哨兵模式 无法解决写的压力 主从模式,切换需要时间丢数据
**集群(proxy 型):**代理型, 缺点:增加了新的 proxy,需要维护其高可用。
redis-cluster集群
1、资源隔离性较差,容易出现相互影响的情况。
2、数据通过异步复制,不保证数据的强一致性
redis集群
单机
主从复制 无法保证高可用 无法解决写的压力
哨兵模式 无法解决写的压力 主从模式,切换需要时间丢数据
**集群(proxy 型):**代理型, 缺点:增加了新的 proxy,需要维护其高可用。
redis-cluster集群
1、资源隔离性较差,容易出现相互影响的情况。
2、数据通过异步复制,不保证数据的强一致性