Java 业务开发常见错误 100 例
朱晔
贝壳金服资深架构师
52944 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 48 讲
代码篇 (23讲)
Java 业务开发常见错误 100 例
15
15
1.0x
00:00/00:00
登录|注册

23 | 缓存设计:缓存可以锦上添花也可以落井下石

解决大Key对Redis的影响
分散热点Key的压力
推荐的更新策略
更新策略分析
解决方案
问题描述
解决方案
问题描述
解决方案
产生原因
数据淘汰策略
Redis的特点和限制
不将Redis当作数据库使用
缓存的架构设计模式
缓存的作用
思考与讨论
缓存数据同步策略
缓存穿透问题
缓存击穿问题
缓存雪崩问题
Redis作为缓存
缓存概述
缓存设计

该思维导图由 AI 生成,仅供参考

你好,我是朱晔。今天,我从设计的角度,与你聊聊缓存。
通常我们会使用更快的介质(比如内存)作为缓存,来解决较慢介质(比如磁盘)读取数据慢的问题,缓存是用空间换时间,来解决性能问题的一种架构设计模式。更重要的是,磁盘上存储的往往是原始数据,而缓存中保存的可以是面向呈现的数据。这样一来,缓存不仅仅是加快了 IO,还可以减少原始数据的计算工作。
此外,缓存系统一般设计简单,功能相对单一,所以诸如 Redis 这种缓存系统的整体吞吐量,能达到关系型数据库的几倍甚至几十倍,因此缓存特别适用于互联网应用的高并发场景。
使用 Redis 做缓存虽然简单好用,但使用和设计缓存并不是 set 一下这么简单,需要注意缓存的同步、雪崩、并发、穿透等问题。今天,我们就来详细聊聊。

不要把 Redis 当作数据库

通常,我们会使用 Redis 等分布式缓存数据库来缓存数据,但是千万别把 Redis 当做数据库来使用。我就见过许多案例,因为 Redis 中数据消失导致业务逻辑错误,并且因为没有保留原始数据,业务都无法恢复。
Redis 的确具有数据持久化功能,可以实现服务重启后数据不丢失。这一点,很容易让我们误认为 Redis 可以作为高性能的 KV 数据库。
其实,从本质上来看,Redis(免费版)是一个内存数据库,所有数据保存在内存中,并且直接从内存读写数据响应操作,只不过具有数据持久化能力。所以,Redis 的特点是,处理请求很快,但无法保存超过内存大小的数据。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了缓存设计在系统性能优化中的重要性,特别强调了Redis作为缓存系统的注意事项。强调了缓存的作用是用空间换时间,可以加快IO并减少原始数据的计算工作。此外,还介绍了缓存雪崩问题和缓存击穿问题,并提出了解决方案。对于缓存穿透问题,提出了使用特殊值或布隆过滤器进行前置过滤的解决方案。另外,文章还探讨了缓存数据同步策略,强调了先更新数据库再删除缓存的策略是最佳选择。总之,本文为读者提供了系统性能优化方面的重要指导,对于技术人员在实际工作中具有实际的指导意义。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 业务开发常见错误 100 例》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(25)

  • 最新
  • 精选
  • Darren
    第一个问题: vivi童鞋回复的很棒,我的第一想法也是加随机后缀。 分型一个场景:假如在一个非常热点的数据,数据更新不是很频繁,但是查询非常的频繁,要保证基本保证100%的缓存命中率,该怎么处理? 我们的做法是,空间换效率,同一个key保留2份,1个不带后缀,1个带后缀,不带的后缀的有ttl,带后缀的没有,先查询不带后缀的,查询不到,做两件事情:1、后台程序查询DB更新缓存;2查询带后缀返回给调用方。这样可以尽可能的避免缓存击穿而引起的数据库挂了。 第二个问题: 1:单个key存储的value很大 key分为2种类型: 第一:该key需要每次都整存整取 可以尝试将对象分拆成几个key-value, 使用multiGet获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多个redis实例中,降低对单个redis的IO影响; 第二:该对象每次只需要存取部分数据 可以像第一种做法一样,分拆成几个key-value, 也可以将这个存储在一个hash中,每个field代表一个具体的属性,使用hget,hmget来获取部分的value,使用hset,hmset来更新部分属性。 2、一个集群存储了上亿的key 如果key的个数过多会带来更多的内存空间占用, 第一:key本身的占用(每个key 都会有一个Category前缀) 第二:集群模式中,服务端需要建立一些slot2key的映射关系,这其中的指针占用在key多的情况下也是浪费巨大空间 这两个方面在key个数上亿的时候消耗内存十分明显(Redis 3.2及以下版本均存在这个问题,4.0有优化); 所以减少key的个数可以减少内存消耗,可以参考的方案是转Hash结构存储,即原先是直接使用Redis String 的结构存储,现在将多个key存储在一个Hash结构中,具体场景参考如下: 一: key 本身就有很强的相关性,比如多个key 代表一个对象,每个key是对象的一个属性,这种可直接按照特定对象的特征来设置一个新Key——Hash结构, 原先的key则作为这个新Hash 的field。 二: key 本身没有相关性,预估一下总量,预分一个固定的桶数量 比如现在预估key 的总数为 2亿,按照一个hash存储 100个field来算,需要 2亿 / 100 = 200W 个桶 (200W 个key占用的空间很少,2亿可能有将近 20G ) 现在按照200W 固定桶分就是先计算出桶的序号 hash(123456789) % 200W , 这里最好保证这个 hash算法的值是个正数,否则需要调整下模除的规则; 这样算出三个key 的桶分别是 1 , 2, 2。 所以存储的时候调用API hset(key, field, value),读取的时候使用 hget (key, field) 注意两个地方:1,hash 取模对负数的处理; 2,预分桶的时候, 一个hash 中存储的值最好不要超过 512 ,100 左右较为合适

    作者回复: 厉害

    2020-05-07
    12
    100
  • 汝林外史
    先更新数据库再删缓存,如果并发查询发生在删缓存之前更新数据库之后,查到的不都是旧数据吗? 不是应该先删除缓存,向队列中插入一个数据的修改标识,并发查询发现缓存为空把查询数据库的标识也放入队列中,等修改的处理完了再处理查询的请求。

    作者回复: 我们的目标是避免长期出现不一致(读取到了旧值进入缓存属于长期不一致,因为又需要等一个缓存周期了)。先更新数据库再删缓存,如果并发查询发生在删缓存之前更新数据库之后,查到的不都是旧数据吗?是旧数据但是这是非常短暂的,下次查询就是新数据了。 你说的如何实现绝度一致。先删除缓存,向队列中插入一个数据的修改标识,我们如何确保删除缓存后向队列插入数据修改标识之前又有请求过来读取数据了呢?绝对一致或许考虑锁的方案。 不过反过来思考,既然已经缓存了,真的需要绝对一致吗?

    2020-05-08
    5
    25
  • 赵宇浩
    跟用户信息相关的缓存怎么处理穿透,因为用户的量很大,很难全放进布隆过滤器。

    作者回复: 测试一下 https://krisives.github.io/bloom-calculator/ 10亿的数据量,期望千分之一的错误率,推荐10个Hash函数,占用内存空间不到1.8GB

    2020-05-07
    5
    20
  • vivi
    第一个问题,我认为可以给hotkey加上后缀,让这些hotkey打散到不同的redis实例上。

    作者回复: 是的,加随机前缀后缀是一个办法

    2020-05-07
    2
    10
  • wang
    对于一个频繁更新的热点key,有什么好的方案,先更新redis在定时同步到数据库,可能会丢数据

    作者回复: 可以考虑一下Event Sourcing的思想,所有数据都从缓存读取,修改直接记录AppendLog+刷缓存,异步Apply到数据库,如果丟数据重放日志即可

    2020-05-09
    7
  • hellojd
    Mongodb算缓存吗?还是数据库?我们现正在开发的一个功能,将用户会员状态放到缓存中,缓存的过期时间是会员的过期时间,如果缓存没到缓存过期时间就被驱除,那就死翘翘了。

    作者回复: mongodb是数据库,会员状态放缓存显然不合适

    2020-05-11
    5
  • hellojd
    文稿中提到的布隆过滤器,怎么保证加载用户全部数据。用户量太大会不会oom?,如果新用户注册过来,怎么同步更新所有实例的布隆过滤器数据

    作者回复: 布隆过滤器容量预估可以参考我之前的回答,如果数据保存节点内可以定时任务来加载新增数据,不同节点数据略有不同问题不大,或者也可以为redis安装布隆模块,集中保存

    2020-05-11
    4
  • Michael
    老师,你好! 目前我们项目中遇到的问题: 当资讯在后台管理台修改完成审核上架后,需要在客户端实时生效,但是客户端的显示涉及分页和详情查询,分页查询中的缓存key涉及分页参数pageNo,pageSIze,分页和详情缓存key还涉及用户参数,这种情况情况如何处理?

    作者回复: 我不是很理解你的问题,你是说咨询修改缓存怎么办?修改后可以直接把全量分页缓存的数据全部删除,也可以全量更新数据到缓存,当然,如果修改不涉及分页的改动,比如修改单篇文章的标题更新或删除那一页的缓存即可

    2020-06-24
    3
  • 那时刻
    关于redis热key的处理,vivi和Darren给出很好的方案。如果hot key数据量不大的话,服务器本地localcache也是个方法吧?另外想问老师,在工程上如何发现热key呢?使用redis4提供的object freq?不知是否有其他方式?

    作者回复: https://www.infoq.cn/article/3L3zAQ4H8xpNoM2glSyi

    2020-05-08
    3
  • 码哥字节
    还有一种 “延迟双删” 的说法,也就是先删除缓存,再写数据库,延迟 一下再次删除缓存

    作者回复: ok

    2021-03-01
    2
收起评论
显示
设置
留言
25
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部