• Home
  • 归档
文章检索 作品与友邻 关于我
暴君乔博客
Home 归档

Redis技术要点

发布于: 2024-05-19
更新于: 2024-05-19
文章目录
  1. 缓存读写模式
    1. 旁路缓存 CacheAside
    2. 读写穿透
    3. 异步缓存写入
  2. 技术实现
    1. 技术架构
    2. redis 单线程模型
    3. redis 内存实现
    4. 删除机制实现
    5. 事务
    6. 基础数据
    7. 批量操作
    8. BIGKey 优化
    9. hotKEY 问题
    10. 慢查询
    11. 内存碎片
    12. 持久化实现
    13. 宕机恢复
  3. 场景题
    1. 购物车怎么存储
    2. 排行榜怎么做
    3. 统计活跃用户怎么实现
    4. 缓存穿透
    5. 缓存击穿
    6. 缓存雪崩
    7. 统计亿级 keys 场景
    8. 如何解决数据倾斜
    9. 如何保证秒杀场景
  4. 参考文档

Redis 高效的内存数据库

  • 为什么 redis 那么快
    • 纯内存模型
    • 基于 Reactor 模式设计了时间处理,依赖了单线程事件循环和 IO 多路复用实现
    • 内置了大量优化的数据结构实现
  • 支持丰富的数据结构
  • 有灾备机制
  • 有集群模式
  • 有发布订阅模式,能做消息订阅,但没必要
  • Lua 脚本、事务功能
  • 支持惰性删除以及定期删除

缓存读写模式

旁路缓存 CacheAside

image.png

  • 对于这个情况,如果要更新数据怎么实现【注,这里场景是指没有分布式锁】
    • 可以先删除 cache 再更新 DB 吗?
      • 不行,因为如果先删除 cache,那么正在写数据的事务,还没有更新到 DB 上,但别的读事务读的数据还是老的
    • 那么反过来呢?先更新 DB 再删除 cache 呢?
      • 其实也会有问题,只是因为短暂时间内 cache 会比 DB 块,所以最好的实现方式还是加分布式锁,把写的动作解耦开
      • 并且提前对热点 key 进行缓存

读写穿透

  • 都是操作 cache 先,如果不存在,则查询 DB 里的数据进行回写
    image.png

异步缓存写入

  • 只操作缓存数据,DB 异步写入
  • 适合数据经常变化,但一致性要求较低的,比如点赞数、游览数场景

技术实现

技术架构

redis 单线程模型

  • 基于 Reactor 模式开发事件处理机制,Netty 也是 - 简单来说就是为了解决 IO 阻塞等待调用,以及占用资源的问题 - 通过一个服务分发中间层,将 client 的请求转发给服务端 - 大白话解释就是 - 餐厅有多个客人,前台点菜的服务员是一个,而后面做饭的厨师有多个【当然随着业务复杂,这里面的服务员和厨师可以是 1,也可以是 n】
    image.png

  • Redis 中基于 file event handler 实现,而这个是单线程实现

    • 基于 IO 多路复用,监听多个套接字 socket,并根据目前执行的任务,关联不同的任务处理器
    • 多个 socket 链接
    • 文件事件分发机制
    • IO 多路复用程序
    • 事件处理器【连接应答、命令请求、命令回复】
  • 好处 - 不需要创建多于的线程监控大量的连接,降低损耗 - 处理上是单线程,也避免了线程的上下文切换损耗 - 备注 6.0 后引入了“多线程”,主要是解决 IO 返回数据,增加数据解析协议以及返回数据的内容,但核心处理数据线程依旧是单线程的
    image.png

  • 除了这项还有什么线程在跑的

    • 比如 AOF、RDB
    • 惰性删除
    • fsync 刷盘等
  • 需要操作内容 - IO 处理 - 键值读写 - 网络 iO 传输
    image.png

redis 内存实现

  • 缓存时间
    • 避免内存打爆了,及时清理不需要使用的 cache
  • 怎么识别是否过期呢
    • redis 维护了一个过期字典,可以理解为 hash 表,保存数据的过期时间,UNIX 时间戳
typedef struct redisDb {
    ...

    dict *dict;     //数据库键空间,保存着数据库中所有键值对
    dict *expires   // 过期字典,保存着键的过期时间
    ...
} redisDb;

image.png

删除机制实现

  • 惰性删除
    • 获取 key 的时候判断是否过期
  • 定期删除
    • 定期删除,定期扫描 key 实现
  • 怎么保证能及时删除不需要使用的内存,通过内存淘汰机制
    • lru 最少最近使用
    • ttl 挑选要过期淘汰
    • random 从已过期的数据删除
    • lru 从最近最少用的 key 删除,最常用
    • random 随机
    • no-eciction OOM 时报错,禁止写入,几乎没人用

事务

MULTI
     SET xx xxx
EXEC
DISCARD
WATCH
  • redis 的事务不支持回滚,也就是不满足 ACID 中的 A 原子性
  • 认为没必要保证这个内容
  • 事务是支持备份的,rdb 与 aof 都支持
  • 通过 lua 脚本实现原子性功能
    • 通过 lua 脚本将多条命令一次传输,减小网络开销
    • 脚本可以作为一条命令执行,保证不会被其他命令插队
    • 同时,如果脚本中间执行报错了,也是不会实现回滚的,所以并不能完全实现原子性操作

基础数据

image.png

  • 数据结构
    • String key-val
      • 实现场景:库存,微博数量,信息存储
    • hash
      • hashmap 的实现
      • 可以用来做有主体的数据缓存,比如用户信息,商品信息
    • list
      • 消息列表,关注列表,公告栏列表
      • 可以通过 lrange 实现分页查询功能
    • set
      • 实现去重的 list 功能,做一些共同关注,共同粉丝等等
      • 或者说做一些防重的 key
    • sortedset
      • 增加 score 打分机制,可以实现礼物排行榜,弹幕等功能
  • 底层实现
    • sds simple dynamic String 除了用作字符串实现外,还作为 aof 缓冲区,用户输入缓冲区
      • 记录 len
      • free 未使用字节
      • char 数组 buf 保存字符串
      • o1 获取长度,通过 free 以及长度可以更好减少内存重分配次数
    • 链表 除了 list 结构外还用于发布,订阅,监视器,客户端状态等等
      • 双向,无环
      • 表头指针,表尾指针
      • 链表长度计数器
      • 支持多态
    • 字典
      • 哈希键值底层实现之一,以及是数据库的增伤改查的实现
      • 哈希表
      • 表大小,used 已用节点

批量操作

  • 通过批量操作优化网络开销
  • MGET 批量获取数据
  • MSET 批量设置数据
  • lua 脚本

BIGKey 优化

  • 定义
    • string 超过 10kb
    • 复合元素超过 5000 个
  • 危害
    • 网络、内存占用大
    • 可能造成阻塞
  • 扫描
    • 通过 bigkeys 命令查询,scan 所有的 key 进行查询
  • 解决方案
    • 拆分大 key,打散存储
    • 手动清理
    • lazy-free 机制

hotKEY 问题

  • 如果访问过高,可能导致节点压力过大,阻塞 redis
  • 通过 hotkeys 查询
  • 提前预估是否存在热点 key
  • 解决方案
    • 二级缓存,JVM 本地
    • 读写分离,从节点读取
    • 打散数据
    • redis cluster 实现
    • cluster 搭配 65535 个槽再打散分配

慢查询

  • On 的命令
    • KEYS 可能导致阻塞
    • HGETALL 量大
    • LRANGE
    • SMEMBERS

内存碎片

  • 存储数据时申请的大于实际大小
  • 频繁修改数据时,或者删除数据,也会导致碎片,因为这部分的内存也不会返回给操作系统
  • 重启节点可恢复、或者通过自动内存碎片整理实现
    Redis 内存碎片详解 | JavaGuide

持久化实现

  • AOF append only file
  • RDB redis database snapshot 快照
  • RDB 与 AOF 混用
  • RDB
    • 通过 save、bgsave 的形式创建快照,获取相关数据
    • 压缩的二进制文件,保存所有的数据文件,类似于 binlog
    • RDB 大数据恢复时会比 AOF 快
  • AOF - 开启 AOF 后,每一条命令都会写入到 AOF 缓冲区,再写到 AOF 文件中,根据 fsync 刷盘策略决定是否同步磁盘 - 同时是在执行完命令后进行重写 - 1. 避免额外检查开销,aof 的日志不会检查语法 - 2.命令执行完再记录,就不会阻塞命令的执行 - 流程 - append 写命令到 AOF 缓冲区 - write 缓冲到文件 - fsync 持久化硬盘 - rewrite 重写文件压缩命令 - load 重启宕机回复
    image.png
  • AOF 重写 - AOF 重写是子进程中执行 - 还会有 AOF 重写缓冲区,在创建新的 AOF 文件期间,记录所有的写命令 - 当子进程完成创建新的 AOF 文件创建后,将重写缓冲区的文件追加到新的 AOF 文件后面 - 然后再用新的 AOF 文件替代旧的,保证与数据库的状态一致
    image.png

宕机恢复

AOF 校验机制

  • redis 启动时,校验 aof 文件,确认是否完整,通过 checksum 校验和的机制实现
  • 如果发现文件不完整,则拒绝启动并报错;RDB 也有类似的机制
  • 这块的原因是因为写入时,没有校验是否完整,是弱依赖的实现

场景题

购物车怎么存储

  • 比如可以用 hash 结构,用户 ID 是它的 key,map 就是商品 id 和加购的商品数量

排行榜怎么做

  • 通过 sorted set 实现
  • 通过 zrange、zrevrange 实现正序和倒序排序,以及 zrevrank 指定元素排名

统计活跃用户怎么实现

  • 通过 bitmap 实现,布谷鸟逻辑
  • bitap 存储的是一个连续庞大的 01 二进制 bit 位
  • key 为日期,value 为当天统计的人数
  • 查询多天的数据
    • bitop and desk1 20240518 20240520
    • bitcount desk1 查询出来总人数

缓存穿透

  • redis 没有数据,将 DB 打爆了
  • 解决方案 - 提前缓存无效 key,直接返回 - 布隆过滤器 - 先判断用户在不在范围内,再决定要不要往下走
    image.png

缓存击穿

  • 缓存过期了,打爆了 DB
  • 解决方案
    • 热点 key 不失效
    • 提前预热热点 key 数据
    • 查询数据库的操作增加互斥锁,减少流量访问压力

缓存雪崩

  • 缓存大量失效
  • 解决方案
    • key 失效时间随机化
    • 查询增加互斥锁
    • 二级缓存机制

统计亿级 keys 场景

统计页面 UV

  • 可以考虑使用 hyperLogLog
  • 每个 HyperLogLog 只花费 12KB 内存,就能存储 2^64 元素
  • PFADD page1:uv user1 user2 user3
  • PFCOUNT page1:uv

如何解决数据倾斜

  • 原因
    • bigKey
    • Slot 分配不均
    • Hash tag 倾斜
      • 是指 key 里面的花括号{},计算花括号里面的 crc16 来算 key
  • 解决方案
    • 打散 key
    • 可以手动迁移实例上的 slot 到其他节点上 cluster setslot xx importing 实例 id

如何保证秒杀场景

  • 支持高并发查询
    • 先通过 CRC 算法计算不同 key 对应的 slot,然后将这些打散到均匀的 key 上
    • 保存库存校验以及扣减的原子性执行
#获取商品库存信息
local counts = redis.call("HMGET", KEYS[1], "total", "ordered");
#将总库存转换为数值
local total = tonumber(counts[1])
#将已被秒杀的库存转换为数值
local ordered = tonumber(counts[2])
#如果当前请求的库存量加上已被秒杀的库存量仍然小于总库存量,就可以更新库存
if ordered + k <= total then
    #更新已秒杀的库存量
    redis.call("HINCRBY",KEYS[1],"ordered",k)                              return k;
end
return 0

参考文档

  • Reactor 模式 - 知乎
  • Redis 常见面试题总结(上)
  • 10 第 1 ~ 9 讲课后思考题答案及常见问题答疑
Redis技术要点
/archives/ea18a0c8/
作者
tyrantqiao
发布于
2024-05-19
更新于
2024-05-19
许可协议
CC BY-NC-SA 4.0
赏

蟹蟹大佬的打赏,大家一起进步

支付宝
微信
  • 面经
  • 技术
  • Redis

扫一扫,分享到微信

微信分享二维码
jvm技术要点
RocketMQ技术要点
© 2026 tyrantqiao 本站总访问量次 本站访客数人次 载入天数...载入时分秒...
  • 文章检索
  • 作品与友邻
  • 关于我

展开标签

  • 随笔
  • 旅行
  • Java
  • 面经
  • 源码
  • 架构
  • Hadoop
  • 计算机网络
  • 技术
  • IDEA
  • maven
  • JDK
  • Linux
  • MySQL
  • 性能
  • 数据库
  • Redis
  • MQ
  • Spring
  • SquirrelFin
  • 记账
  • Android
  • 个人财务
  • TIDB
  • unity
  • chatgpt
  • 前端
  • Vue
  • Git
  • shadowsocks
  • Hexo
  • 问题排查
  • 业务
  • JVM
  • 算法
  • nginx
  • ssh
  • Python
  • 爬虫
  • test
  • vim
  • 影视剧
  • 中间件
  • 事务
  • 表达
  • 运动
  • mybatis
  • 域名
  • AI
  • 产品
  • 家装
  • 土巴兔
  • github
  • 软件
  • 个人项目
  • Leaves
  • 幽默
  • 穿搭
  • 画画
  • 缓存
  • 摄影
  • 设计模式
  • c
  • 课程设计
  • 调酒
  • 数学

Collection

作品与友邻

我做的小工具、朋友的站点和一些长期值得回访的灵感来源。

  • 土 土巴兔 AI 导购交互演示 可点击体验用户导购、平台运营、装修公司、材料商及 AI 话术知识库的多端增长流程。 /demos/tubatu-ai-guide/
  • L Leaves 行程记录 我做的行程记录页面小工具,用来沉淀旅行路线、日程安排和途中的灵感。 https://leaves.tyrantqiao.com/
  • 本 本网站主题yilia设计者的主页 http://litten.me/
tyrantqiao的头像

tyrantqiao

github rss qq mail facebook linkedin

About

关于我

代码、海浪和持续折腾,都是生活的一部分。

<div class="aboutme-card"> <p class="aboutme-eyebrow">tyrantqiao</p> <h3>Java 全栈开发</h3> <p>常年和 Java、后端服务、前端工程、数据与部署打交道,喜欢把复杂问题拆开、写清楚,也把踩坑过程整理成文章留给后来的人。</p> <div class="aboutme-tags"> <span>Java</span> <span>Full Stack</span> <span>Backend</span> <span>Frontend</span> <span>Extreme Sports</span> </div> <p>工作之外偏爱冲浪等极限运动,享受在浪上找平衡,也享受在代码里找边界。</p> <div class="aboutme-contact"> <a href="mailto:[email protected]">Gmail</a> <a href="mailto:[email protected]">QQ Mail</a> </div> </div>