Redis线程模型
redis是基于内存运行的高性能k-v数据库,6.x之前是单线程, 对外提供的键值存储服务的主要流程 是单线程,也就是网络 IO 和数据读写是由单个线程来完成,6.x之后引入多线程而键值对读写命 令仍然是单线程处理的,所以 Redis 依然是并发安全的
Redis为什么快
-
完全基于内存操作,避免了传统的磁盘io读取内存这部分的消耗
-
数据结构简单,基于哈希表结构,可以在 O(1)的时间内计算出 hash 值并且找到对应的 entry位置,entry 里面是一个一个 key 指针和 value 指针这也是 redis 之所以性能高的原因之一
-
采用单线程,避免线程切换的事件,不存在竞争条件,也不会出现死锁而造成性能消耗
Redis多线程
1、Redis6.0 之前为什么一直不使用多线程?
Redis使用单线程的可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。
2、Redis6.0 为什么要引入多线程呢?
因为Redis的瓶颈不在内存,而是在网络I/O模块带来CPU的耗时,所以Redis6.0的多线程是用来处理网络I/O这部分,充分利用CPU资源,减少网络I/O阻塞带来的性能损耗。
3、Redis6.0 如何开启多线程?
默认情况下Redis是关闭多线程的,可以在conf文件进行配置开启 4、多线程模式下,是否存在线程并发安全问题?
如图,一次redis请求,要建立连接,然后获取操作的命令,然后执行命令,最后将响应的结果写到socket上。
在redis的多线程模式下,获取、解析命令,以及输出结果着两个过程,可以配置成多线程执行的,因为它毕竟是我们定位到的主要耗时点,但是命令的执行,也就是内存操作,依然是单线程运行的。所以,Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行,也就不存在并发安全问题。
Redis持久化
Redis 是一个内存数据库,数据保存在内存中,但是我们都知道内存的数据变化是很快的,也容易发生丢失。Redis 还为我们提供了持久化的机制,分别是RDB(Redis DataBase)和 AOF(Append Only File)
-
RDB方式
RDB 持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为 dump.rdb。 在我们安装了 redis 之后,所有的配置都是在 redis.conf 文件中,里面保存了 RDB 和 AOF 两种持久化机制的各种配置。当符合一定条件时 Redis 会自动将内存中的数据进行快照并持久化到硬盘。
触发时机
(1)save命令触发:
该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB完成为止,如果数据量大的话会造成长时间的阻塞,所以线上环境一般禁止使用。
(2)bgsave命令触发:
执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体流程如下:
执行bgsave命令时,Redis主进程会fork一个子进程来完成RDB的过程,会先将数据写入到一个临时二进制文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件(可以理解为Copy On Write机制)。Redis主进程阻塞时间只有fork阶段的那一下。相对于save,阻塞时间很短。基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令。
fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
(3)自动触发:
自动触发是可以在redis.conf配置文件中修改,默认达到以下三种条件,就会自动触发持久化,触发后,底层调用的其实还有bgsave命令
-
AOF方式
以日志的形式来记录每个写操作,将 Redis 执行过的所有指令记录下来(读操作不记录),只许追文件但不可以改写文件,redis 启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
Redis双写一致性
延时双删
-
(1)先淘汰缓存
-
(2)再写数据库
-
(3)休眠1秒,再次淘汰缓存
异步队列
增加消息队列,将redis更新操作交给kafka,由消息队列保证可靠性,再搭建一个消费服务,来异步更新redis
订阅binlog
通过订阅binlog来更新redis,把搭建的消费服务作为mysql的一个slave,订阅binlog,解析出更新内容,再更新到redis
Redis事务
Redis 事务本质是一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程的中,会按照顺序执行. 所有的命令在事务中,并没有直接被执行. 只有发起执行 exec 命令的时候才会执行. 事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。但是事务不保证同一事物中多条命令执行的原子性,即使命令有错误也会添加到队列中,执行报错也不影响其他命令执行
、MULTI:
用于标记事务块的开启。MULTI执行之后,Redis会将后续的命令逐个放到一个缓存队列中,当EXEC命令被调用时,所有队列中的命令才会被原子化执行。
2、EXEC:
在一个事务中执行所有先前放入队列的命令,然后恢复正常的连接状态。当使用WATCH命令时,只有当受监控的键没有被修改时,EXEC命令才会执行事务中的命令。
3、DISCARD:
放弃事务,清除事务队列中的命令,然后恢复正常的连接状态。如果使用了UNWATCH命令,那么DISCARD命令就会取消当前连接监控的所有键。
主从复制
主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点
1、Redis为什么需要主从复制? 使用Redis主从复制的原因主要是单台Redis节点存在以下的局限性:
(1)Redis虽然读写的速度都很快,单节点的Redis能够支撑QPS大概在5w左右,如果上千万的用户访问,Redis就承载不了,成为了高并发的瓶颈。
(2)单节点的Redis不能保证高可用,当Redis因为某些原因意外宕机时,会导致缓存不可用
(3)CPU的利用率上,单台Redis实例只能利用单个核心,这单个核心在面临海量数据的存取和管理工作时压力会非常大。
2主从复制的好处: (1)数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
(2)故障恢复:如果master宕掉了,使用哨兵模式,可以提升一个 slave 作为新的 master,进而实现故障转移,实现高可用
(3)负载均衡:可以轻易地实现横向扩展,实现读写分离,一个 master 用于写,多个 slave 用于分摊读的压力,从而实现高并发;
3、主从复制的缺点: 由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave服务器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重
哨兵机制
哨兵模式是一种特殊的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待 Redis 服务器响应,从而监控运行的多个 Redis 实例。
key过期策略
-
惰性删除:惰性删除是指,某个键值过期后,此键值不会马上被删除,而是等 到下次被使用的时候,才会被检查到过期,此时才能得到删除。所以惰性删除的 缺点很明显:浪费内存,还需要维护一个字典记录 key 是否过期。
-
定期删除:每隔一定的时间,会扫描一定数量的数据库字典中一定数量的 key, 并清除其中已过期的 key。通过调整定时扫描的时间间隔和每次扫描的限定耗 时,可以在不同情况下使得 CPU 和内存资源达到最优的平衡效果。
Redis 中同时使用了惰性过期和定期过期两种过期策略
缓存穿透
key对应的数据不存在,例如id=-1 DB和Redis都没有数据
解决:
-
参数校验
-
对空值进行缓存,返回value=null
-
布隆过滤器,使用bitmap先将可以访问到的资源通过映射关系放入过滤器,当请求到达时,先走过滤器
缓存击穿
某个key再数据库存在,但在redis中key过期
解决:
-
提前对key做过期时间设置
-
监控数据
-
使用锁,只有一个请求获得互斥锁,然后db中将数据查询并返回到redis中,之后所有请求从redis中查询
缓存雪崩
redis中大量key集体过期
解决:
-
将失效时间分散,生成随机数key
-
多级架构,nginx缓存+redis缓存
-
记录缓存标记,如果过期会通知另一个线程更新
-
查不到加排他锁
缓存预热
1、什么是缓存预热:
缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。
如果不进行预热,那么Redis初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。
2、缓存预热解决方案:
(1)数据量不大的时候,工程启动的时候进行加载缓存动作;
(2)数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;
(3)数据量太大的时候,优先保证热点数据进行提前加载到缓存
缓存降级
缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。
在项目实战中通常会将部分热点数据缓存到服务的内存中,这样一旦缓存出现异常,可以直接使用服务的内存数据,从而避免数据库遭受巨大压力。