持久化
Redis的持久化机制
Redis可以利用快照RDB,和只追加文件AOF进行持久化存储到磁盘中
AOF日志
AOF是只追加文件,每次处理完请求命令后,将该命令追加到aof文件末尾。
AOF是写后日志,先执行命令,把数据写入内存,然后再记录日志
AOF为什么要先执行命令再记日志
- 记录日志时,是不对命令进行语法检查的。
- 如果先记录日志的话,日志中可能记录了错误的命令,导致恢复数据时可能会出错
AOF的好处
- 先执行命令,再记录日志,可以避免记录错误命令的情况
- 由于在记录执行后才记录日志,不会阻塞当前的操作
AOF的缺陷
- 如果执行完命令后,没有来得及写入日志就宕机了,就有数据丢失的风险
- AOF虽然不会阻塞当前的操作,但是可能会阻塞下一条的操作。AOF日志是在主线程中执行的,如果日志文件写入磁盘时太慢,就会导致后序的操作无法执行
AOF三种写回策略
- Always:每个命令执行完,立马同步将日志写回磁盘
- Everysec:每秒写回:每个写命令执行完,只是把日志写道AOF文件的内存缓冲区中,每隔一秒把缓存区的内容写入磁盘。这种如果发生宕机,可能会丢失1s的数据
- NO:操作系统控制写回磁盘,如果宕机,那么丢失的数据就较多
AOF的重写机制
- AOF重写机制解决日志文件过大的问题
- 重写的时候,会根据该键值的当前最新状态,为它生成对应的写入命令。而对中间的修改之类的操作全部忽略
AOF重写会阻塞吗
和AOF日志不同,重写是由后台线程完成的。
AOF重写的具体过程
一个拷贝,两处日志
- 每次执行重写时,主进程会fork一个子进程,由子进程进行重写,这个将不会阻塞主线程
- 第一部分的日志是正在使用的AOF日志,第二处是新的AOF重写日志。当新的操作来临时,Redis会将其写入到主线程AOF缓冲区和新的AOF缓冲区。
- 当拷贝数据的所有操作重写记录完成后,重写日志的记录了也会写入到新的AOF文件。
- 然后使用新的AOF文件替代旧的文件
RDB快照
RDB类似于一种照片记录效果的方式,把某一时刻的状态写入到文件中
RDB是Redis的默认持久化方案
给那些内存数据做快照
会把内存的所有数据记录到磁盘中,就是全量快照
- save:主线程中执行,导致主线程阻塞
- bgsave:创建一个新的子进程,专门写入RDB文件,不会导致阻塞,默认。
bgsave指令工作管理
- 执行bgsave命令,然后fork一个bgsave子线程
- bgsave子进程运行后,读取内存数据,写入RDB文件
- 如果主线程都是读操作,那么主线程和bgsave子线程互不影响
- 如果主线程要修改一块数据,那么就会这块数据就会复制一份,生成该数据的副本。然后bgsave会把副本数据写入到RDB文件。在这个过程中主线程仍然可以修改原来的数据
RDB缺点
- fork创建本身会阻塞线程,而且主线程的内存越大,阻塞时间越长
- 每隔一段时间执行快照,若间隔时间宕机会导致数据丢失
RDB优点
- RDB是一种压缩的二进制文件,存储效率高
- RDB存储的时时间点的数据快照,适合数据备份,全量复制等
- RDB恢复数据的速度AOF快很多。
应用场景
- 数据不能丢失,可以两者混用
- 允许分钟级别的丢失,kyRDB
- aof推荐使用everysec的配置选项。可以从可靠性和性能做一个平衡
主从复制
什么是Redis的主从复制
- 将一台Redis服务器的数据,复制到其他的Redis服务器。前者为主节点(master)后者为从节点(slave)
- 数据的复制是单向的,只能由主节点到从节点
主从复制的好处
- 读写分离:master写,slave读,提高服务器的负载的能力
- 可以实现负载均衡:slave分担master的分离,并且根据需求的变化,改变slave的数量,可以分担数据的读取负载,提高Redis服务器的整体并发量和数据吞吐量
- 故障恢复:当master出现问题时,可以由slave提供服务
- 高可用的基石:基于主从复制,可以构建哨兵模式和集群,实现Reids的高可用方案
读写分离
- 主库和从库采用读写分离的方式
- 读操作:主库从库都可以接受
- 写操作:首先到主库执行,然后主库将写操作同步到从库
为什么要采用读写分离呢
- 如果每个节点都可以接受写请求:那么会导致数据不一致的问题,如果要保证一致性,需要加锁,节点之间的协商,这样就会造成额外的开销
- 读写分离可以保证数据的一致性,所有的修改只会在主库,然后将数据同步到从库,效率高
从库和主库如何第一次同步
主要分为三个阶段
- 主从库之间建立连接后,从库向主库发送psync命令,表示要开始进行数据同步。
- 主库收到psync的命令后,使用fullresync响应命令,表示主库第一次复制采用全量复制。
- 主库执行bgsave命令,生成rdb文件,发给从库
- 从库接收到rdb文件后,先清空当前的数据库,然后加载rdb文件
- 在发送RDB过程时会产生新的数据,因此主库会把第二阶段执行过程中新的写命令在发送到从库中
主从级联模式下分担全量复制时的主库压力
- 通过主-从-从的方式将主库生成RDB和传输RDB的压力,以级联的方式分散到从库上。
- 具体如下
- 选择一个从库,用来级联其他的从库。
增量复制
如果主从网络断了之后,主从库会采用增量复制的方式继续同步。
- 增量复制,只会把主从库网络断连期间主库收到的命令,同步给从库。
主从库之间的复制为什么不使用AOF呢
- AOF虽然比RDB操作命令更全,丢失的数据更小,但是AOF的文件比RDB文件大,网络传输更加耗时
- 初始化数据时,RDB文件执行比AOF快很多
哨兵机制
什么是哨兵,作用是什么
哨兵实际上就是一个Redis进程,主要负责自动在主从库下的:监控、选主和通知。
- 监控:监控主库运行状态,并判断主库是否客观下线
- 在从库客观下线后,选择新主库
- 选出新主库后,通知从库和客户端
哨兵如何判断主库的下线状态
哨兵对主库的下线判断有主观下线和客观下线两种
主观下线
哨兵进程通过ping命令来检测主库和从库的网络连接情况。
- 如果检测到从库ping命令超时,那么哨兵就会标记为主观下线
- 如果检测到主库,为了防止误判,需要通过哨兵集群,少数服从多数判断为客观下线才能判断为下线状态
客观下线
使用奇数个哨兵集群,当有一半以上的哨兵判断主库为客观下线后,就可以判断为客观下线。
如何选定新的主库
- 哨兵根据在线状态、网络状态筛选出不符合要求的从库,然后根据优先级,复制进度,ID号大小对从库进行打分。得分最高的,就选为新主库
哨兵集群
哨兵实例在运行时发生了故障,主从库还能正常切换吗
一般情况下,哨兵集群是有多个实例组成的,即使有哨兵实例出现故障挂掉,其他的哨兵还可以举行顶上
基于pub/sub机制的哨兵集群的组成
- 哨兵实例的相互发现,是归功于发布订阅机制,哨兵只要和主库发生了连接,就可以在主库上发布消息了,例如自己的IP和端口。同时也可以再主库订阅消息,获得其他哨兵发布的信息
哨兵如何和从库建立连接
哨兵的监控任务中,需要对主从库进行心跳判断,并且主从库切换完成后,需要通知从库,让它们和新主库进行同步。
- 哨兵是通过向主库发送INFO命令,主库收到这个命令后,把库列表发送给哨兵,哨兵根据列表中的信息和每个从库建立连接。
哨兵是否需要和客户端建立连接
- 需要,哨兵是负责主从切换的,如果发送了主从切换,客户端需要和哨兵进行连接得到相关的信息。
切片集群
为什么要集群
因为如果有大量的数据来到,需要进行扩展
- 纵向扩展:升级单个Redis实例的资源配置,例如内存
- 横向扩展:增加Redis的实例个数
切片集群
- 多个Redis实例组成一个集群,然后按照一定的规则,把收到的数据分成多份,每一份用一个实例来保存。
- 但是这样就需要解决两个问题
- 数据切片后,多个实例之间如何分布
- 客户端怎么确定访问的数据在那个实例上
数据切片和实例的对应分布关系
- 切片集群是一种保存大量数据的通用机制,官方提供了Redis Cluster的方案。
- Redis Cluster采用了哈希槽。来处理数据和实例的关系。每个键值对根据key,来映射到哈希槽中
- 客户端定位数据时,在创建连接后,客户端和集群实例连接后,Redis实例会把自己哈希槽信息发送给它相连的其他实例,完成哈希槽分配信息的扩散。这样客户端就可以得到这些哈希槽的信息。
缓存
缓存穿透
缓存穿透是指缓存和数据库中都没有数据,用户不断的发起请求。例如请求id为-1的数据。用户可能是攻击者
接口校验
例如商品的ID是正整数,直接对非整数的内容进行过滤。
缓存空指
如果缓存和DB都没有查询到值,可以将空值写入到缓存中。设置一定的过期时间。
布隆过滤器
- 使用布隆过滤器可以快速判断数据是否存在,如果不存在则不需要进入到数据库中进行查询
- 可以将常用的数据请求内容放入到布隆过滤器中
缓存击穿
缓存击穿是指缓存的热点数据时间到期,大量的请求一直打到数据库中。造成数据库压力激增
加互斥锁
并发请求中,第一个请求的线程可以拿到锁并执行数据库的查询操作,其他的线程等待第一个线程将数据写入缓存后,直接走缓存
热点数据不过期
直接设置缓存不过期,然后由定时任务异步加载数据,更新缓存。
缓存雪崩
缓存雪崩就是缓存中数据大批量的过期时间,导致查询请求都到了数据库
- 过期时间打散:给缓存的过期时间加上一个随机值,让每个key的过期时间分开来
- 热点数据不过期和加互斥锁。
缓存预热
- 系统上线后,提前将相关的缓存数据加载到缓存系统。
- 如果不进行预热,系统上线初期,面对高并发的流量,都会达到数据库中
解决方案
- 数据量不大时,可以在工程启动时进行加载
- 数据量大,就设置一个定时任务脚本,进行缓存的刷新
- 若太大,则优先保证热点数据进入缓存
缓存降级
缓存降级是,缓存失效或者缓存服务器挂掉的情况下,不去访问数据库,而是直接返回默认数据。
缓存一致性
- 缓存数据的一致性
- 缓存中有数据,那么数据需要和数据库中的值相同
- 缓存中没有数据,那么数据库的值必须是最新值
非并发下,数据不一致的发生和解决
主要为更新数据库和删除缓存值。中间有一个失败了,就导致客户端读取到旧值
先删除缓存,再更新数据库的值
若删除删除成功,而数据库更新失败。请求访问缓存时,发现缓存丢失,再访问数据库得到旧值
先更新数据库,后删除缓存
若更新数据库成功,但是缓存删除失效,导致请求访问缓存时,发现缓存命中,从而从缓存中读取旧值
解决方法:重试机制
可以把要删除的缓存值,或者要更新的数据库的值,暂存到消息队列中(例如Kafka消息队列),如果没有更新成功,那么就从消息队列中重新读取该值,然后再次尝试。
并发状态下,数据不一致如何解决。
先删除缓存值,后更新数据库的值
缓存删除后,尚未更新数据库,此时另外一个并发请求,从数据库读取旧值,然后更新到缓存,导致后续请求得到的都是旧值
- 延时双删。第一个线程更新完数据库值后,sleep一段时间,再进行一次缓存删除操作
先更新数据库,后删除缓存值
优先使用这个方案,因为延迟双删的时间不好估算。 数据库更新成功后,还没有删除缓存,此时有并发请求,从缓存中读取旧值
- 等待删除缓存期间,会有不一致数据短暂存在
Redis缓存延迟的淘汰策略
- 不进行数据淘汰策略:noeviction
- 会进行淘汰的策略
- 设置过期时间的数据集中选择性删除
- volatile-ttl:根据过期时间进行删除,越早过期删除
- volatile-random:随机删除
- volatile-lru:使用lru算法进行筛选
- volatile-lfu:使用lfu算法进行筛选
- 从全局数据集中选择性删除
- allkeys-random:所有的键值对中随机删除
- 如果没有冷热数据,可以使用这个
- allkeys-lru:使用lru在所有数据中进行筛选
- 优先使用,这个,因为把最近最长访问的数据留在内存中
- allkeys-lfu:使用lfu在所有数据中进行筛选
- allkeys-random:所有的键值对中随机删除
- 设置过期时间的数据集中选择性删除
过期键删除策略
Redis使用惰性删除和定期删除
- 惰性删除:当使用key时,会检查是否过期,如果过期则删除。
- 对cpu友好,但是对内存不友好
- 定时删除:设置好一个过期时间的时候,创建一个定时器,让定时器在该过期时间到来时,立刻执行删除操作
- 对内存友好,对cpu不友好
- 定期删除:每隔一段时间都对key进行检查,删除key
- 到可能会造成业务错误
并发
如何保证并发的正确性
redis提供了加锁和原子操作
- 加锁:读取数据钱,需要先获得锁,更新完成后,在释放
- 但是加锁的操作多的时候,会降低系统的并发性能
- redis客户端要加锁的时候,可能使用分布式锁,比较复杂
- 原子操作:执行过程中保证了原子性
- 可以保证并发,不需要进行加锁
- 多个操作在Redis是实同一个个操作
- 多个操作卸载一个Lua脚本中,以原子的方式执行Lua脚本
- 可以保证并发,不需要进行加锁
事务
Redis能实现ACID的属性吗
- Redis只能保证一致性和隔离性,对持久性无法保证,因为Redis本身时内存数据库
- 原子性需要分类,如果事务的执行没有错误,是可以进行保证的,如果事务中的语句执行错误了,是无法保证原子性的。
Redis如何实现事务
- multi显示的开启一个事务
- 添加相应的操作,redis实例把这些命令放到一个队列,并不会立刻执行
- 客户端向服务端发送提交事务的exec命令,执行第二步的操作
Redis的事务可以保证原子性吗
- 命令入队时如果报错了,放弃事务的执行,就可以保证原子性
- 如果没有报错,实际执行时报错,那么就不保证原子性
- 如果执行的时候实例故障,开启了aof日志,可以恢复,那么就可以保证原子性
Redis的事务可以保证一致性吗
- 在命令执行错误或者Redis发送故障的情况下,Redis事务机制对一致性属性是由保证的
Redis的事务可以保证隔离性吗
- 事务的隔离性保证,会和事务的执行的并发操作有关
- 并发操作在EXEC命令执行之前保证,此时隔离性需要开启WATCH机制来实现,否则隔离性无法保证
- WATCH机制可以检查监控的键是否被其他的客户端修改了,如果修改了,那么直接报错
- 并发操作在EXEC命令执行之后,隔离性可以保证
- 并发操作在EXEC命令执行之前保证,此时隔离性需要开启WATCH机制来实现,否则隔离性无法保证
Redis的食物可以保证持久性吗
- Redis不管采用持久化形式,数据的持久性得不到保障。
- RDB快照,在一个事务执行完后,下一次的RDB快照还没有执行,此时宕机。
- AOF,no,evverysec和always都会发生数据丢失的情况
Redis为什么不支持事务回滚
- 因为Redis的错误,只会因为语法而失败,应该在开发过程中发现