每日八股——Redis(1)

Redis是什么?为什么快?

Redis是什么

Redis是一个开源的基于内存的、支持多种数据结构的Key-Value存储系统,适用于用来缓存、计数器(点赞、访问量)、排行榜(ZSet)、分布式锁、消息队列/延迟队列场景。
关键特性:

  1. 高性能:读写速度极快
  2. 丰富的数据结构:支持string、hash、list、ZSet、Bitmap等多种数据结构
  3. 持久化:支持RDB(快照)和AOF(日志)两种方式,重启后数据不丢失(先从AOF恢复,没有再用RDB,都没有就空库启动)
  4. 功能丰富:支持事务、发布订阅、Lua脚本、主从复制等

Redis为什么这么快

基于内存操作(根本原因)

Redis的所有数据都存在内存中,没有磁盘IO开销,这是它快的基础。

单线程模型(避免了上下文切换)

"单线程"是指 Redis 的网络 I/O 和键值对读写是由一个主线程完成的。(Redis 6.0 之前)Redis的瓶颈通常不在CPU,而在于内存和网络带宽,既然CPU不是瓶颈,那么多线程带来的锁竞争和上下文切换反而会成为累赘, 但是 6.0 之后 Redis 为了进一步提升 IO 的性能,引入了多线程机制,减少 Redis 由于网络 IO 等待造成的影响。

I/O多路复用

Redis是单线程的,利用了OS的epoll(Linux)或kqueue( BSD )机制,采用Reactor模式,有一个文件事件分派器,通过epoll监听多个Socket,哪个Socket有数据了就交给主线程处理,实现非阻塞IO

高效的协议和数据结构(底层优化)

Redis 使用简单的二进制协议,减少了网络传输的开销。同时,其内部使用了高效的数据结构(如哈希表、跳表等),进一步提升了性能。

既然Redis是单线程的,为什么6.0后要引入多线程?

  1. Redis6.0依旧是默认单线程处理命令,执行GET、SET这些操作的时候,依然由主线程串行执行
  2. 引入多线程是为了解决网络IO瓶颈:网络数据读写(Read/Write系统调用)和协议解析占用主线程太多时间,Redis6.0引入多线程专门处理网络数据读写和协议解析,但命令执行依然是主线程排队做。
  3. Redis6.0的多线程是多线程I/O,单线程执行

DB缓存一致性问题

缓存一致性指的是缓存里(Redis)的数据,和数据库(MySQL)里的数据保持一致。有这个问题是因为Rdis与DB是两个系统,更新不是原子的,会遇到网络延迟、并发请求、进程崩溃、顺序错乱的问题,我们要明确,没有100%的强一致Redis+DB架构,我们只能基于业务权衡。

旁路缓存模式(Cache Aside Pattern)------主流

在大多数业务场景下,我们推荐使用'Cache Aside Pattern(旁路缓存模式)',即:先更新数据库,再删除缓存。

为什么是"删除缓存"而不是"更新缓存"?

  • 并发竞争问题:
    • 如果有两个线程同时更新数据库(A改为1,B改为2)。
    • 如果这个时候选择更新缓存,由于网络延迟,可能B先到达Redis,A后到达,结果数据库是2,而缓存里却是1,造成数据不一致。
  • 性能浪费
    • 有些缓存的值是经过复杂计算的
    • 如果每次修改数据库都计算一遍写入缓存,但这个缓存没人读,就会造成计算资源浪费
  • 删除缓存相当于懒加载,只有下次有人读的时候,才去回填,效率更高。

为什么不可以更新缓存再更新数据库?

先更新缓存后更新数据库这个方案,只要任何一个出现错误都会导致数据不一致问题。如果更新缓存失败而更新数据库成功,那没有后续补救措施,缓存里一直是旧数据;如果更新缓存成功而更新数据库失败,对于用户来说,拿到的缓存一直是虚假数据,一旦缓存清空,重新从数据库读数据的时候,就与之前拿到的数据不一致了。而且在并发环境下,很容易会造成数据不一致问题。

"更新数据库"和"删除缓存"谁先谁后?

先删除缓存,再更新数据库:
  • 流程:
    • 线程A删除缓存
    • 线程A更新数据库
  • 致命问题(并发场景):
    • 线程A删除了缓存,还没来得及去更新数据库(比如卡顿)
    • 线程B来了,发现缓存为空,去数据库读旧值
    • 线程B读了旧值后又写入缓存
    • A把新值写入数据库
  • 后果:数据库是新值,而缓存里仍然是旧值,造成数据不一致。
补救方法:延时双删
  • 做法:
    • 先删除缓存
    • 更新数据库
    • 休眠一小会
    • 再次删除缓存
  • 原理:休眠是为了让线程B把旧数据写入缓存这一步完成,线程A睡醒后再删除一次
  • 缺点:吞吐量低(因为要sleep),而且无法知道到底要sleep多久
先更新数据库,再删除缓存(标准做法)
  • 流程:
    • 线程A更新数据库
    • 线程A删除缓存
  • 潜在问题:
    • 理论上仍有不一致问题(概率很低):缓存刚好失效------>A线程读旧库------>线程B更新数据库------>线程B删除缓存------>线程A写入旧值
    • 由于数据库的"写操作"通常比"读操作"慢得多。线程 A(读)要在线程 B(写+删)这整个漫长过程结束之后,还能把旧数据塞进去,这需要极其巧合的时间差。
    • 但还是有一个致命问题------网络抖动:如果数据库改完了,但Redis挂了或网络抖动,缓存没删掉,这时数据库新值而缓存是旧值。
保障方法:消息队列重试机制
  • 针对问题:解决删除缓存失败的问题
  • 做法:
    • 更新数据库
    • 删除缓存
    • 如果删除缓存失败,将要删除的Key发送到消息队列(MQ)
    • 如果有一个消费者不断从MQ取Key,不断重试删除操作,直到成功。

订阅Binlog+MQ(Canal方案)

核心思想:把"缓存删除"这个动作从业务代码里剥离出来,彻底解耦。

架构:

  • 业务代码只管更新数据库,不操作Redis
  • 使用Canal(阿里开源中间件)伪装成MySQL的Slave,监听数据库的Binlog
  • 一旦数据库发生变化,Canal解析Binlog,把变动的Key发送到MQ
  • 消费者服务从MQ取Key,然后去执行Redis删除操作
相关推荐
Remember_9935 分钟前
Spring 核心原理深度解析:Bean 作用域、生命周期与 Spring Boot 自动配置
java·前端·spring boot·后端·spring·面试
笨蛋不要掉眼泪8 分钟前
Redis持久化解析:RDB和AOF的对比
前端·javascript·redis
渡我白衣10 分钟前
【MySQL基础】(2):数据库基础概念
数据库·人工智能·深度学习·神经网络·mysql·机器学习·自然语言处理
alien爱吃蛋挞16 分钟前
【JavaEE】万字详解Mybatis(上)
数据库·java-ee·mybatis
写代码的【黑咖啡】17 分钟前
HiveSQL 语法详解与常用 SQL 写法实战
数据库·sql
kiss strong21 分钟前
springboot替换word模板&加入二维码&加水印&转为pdf
spring boot·后端·pdf
黄筱筱筱筱筱筱筱21 分钟前
7.适合新手小白学习Python的异常处理(Exception)
java·前端·数据库·python
Stecurry_3023 分钟前
Springboot整合SpringMVC --从0到1
java·spring boot·后端
怣5025 分钟前
MySQL WHERE子句完全指南:精准过滤数据的艺术
数据库·mysql
大鳥29 分钟前
第一章 - 数据仓库是什么
大数据·数据库·hive