极客时间-读多写少型缓存设计

背景

内容是极客时间-徐长龙老师的高并发系统实战课的个人学习笔记,欢迎大家学习!https://time.geekbang.org/column/article/596644

总览内容如下:
在这里插入图片描述

缓存性价比

一般来说,只有热点数据放到缓存才更有价值

  • 数据量
  • 查询频率
  • 命中率

临时缓存

把目标放到会被高频查询的信息,也就是用户信息,在用户信息第一次被使用的时候,同时将数据放到缓存中,短期内如果再次有类似的查询酒可以快速从缓存中获取,伪代码如下。

userInfo, err := Redis.Get("user_info_9527")
if err != nil {return nil, err
}if userInfo != nil {return userInfo, nil
}userInfo, err := userInfoModel.GetUserInfoById(9527)
if err != nil {//这里的err是数据库链接时的错误,期望是通知获取方系统错误return nil, err
}if userInfo != nil {Redis.set("user_info_9527", userinfo, 60)return userInfo, nil
}//可以未找到,放一个空数据进入,短期内不再访问数据库
Redis.set("user_info_9527", "")
return nil, nil

缓存更新不及时问题

临时缓存有TTL,如果60秒内修改了用户的昵称,缓存不会马上更新

单条实体数据缓存刷新

  1. 先更新数据库
  2. 然后清理缓存,让下次读取时刷新缓存,防止并发修改导致临时数据进入缓存

可以给队列发更新消息让子系统更新,还可以开发中间件把数据操作发给子系统,自行决定更新的数据范围

  1. 中间件可以失败重试,保证其可以更新成功
  2. 队列形式可以保证并发的执行顺序

问题:但条件批量更新的操作无法知道具体有多少个ID可能有修改(更新操作是基于条件进行的,因此在更新之前无法确定有多少个ID可能已经被修改)

解法:先用同样的条件把所有涉及的ID都取出来,然后update,这时用所有相关ID更新具体缓存即可。

关系型和统计型数据缓存刷新

这类数据缓存刷新存在一定难度,核心在于统计是由多条数据计算而成的。很难识别出需要刷新哪些关联缓存

人工维护缓存方式

刷新缓存很多,那么缓存更新会比较慢,并且存在延迟

订阅数据库来找到ID数据变化

maxwell 或 canal,对MySQL的更新进行监控

缺点:复杂的关联关系刷新,仍旧需要通过人工写逻辑来实现

版本号缓存设计

一旦有任何更新,整个表内所有数据缓存一起过期

user_info表设置一个key,更新这个表数据时,直接对key+1,在缓存中也保留version的值。

当业务要读取user_info某个用户的信息的时候,业务会同时获取当前表的version。如果发现缓存数据内的版本和当前表的版本不一致,那么就会更新这条数据。但如果 version 更新很频繁,就会严重降低缓存命中率,所以这种方案适合更新很少的表

当然,我们还可以对这个表做一个范围拆分,比如按 ID 范围分块拆分出多个 version,通过这样的方式来减少缓存刷新的范围和频率。

识别主要实体ID来刷新缓存

这要保证其他缓存保存的key也是主要实体ID

异步脚本遍历数据库刷新所有相关缓存

这个方式适用于两个系统之间同步数据,能够减少系统间的接口交互;缺点是删除数据后,还需要人工删除对应的缓存,所以更新会有延迟。但如果能配合订阅更新消息广播的话,可以做到准同步

长期热数据缓存

长期缓存要求业务几乎不走数据库,并且服务运转期间所需的数据都要能在缓存中找到,同时还要保证使用期间缓存不丢。

下面伪代码使用了singleflight方式预防临时缓存被大量请求穿透

singleflight实现可以参考我的另一个博客https://editor.csdn.net/md/?articleId=135174867

// 尝试从缓存中直接获取用户信息
userinfo, err := Redis.Get("user_info_9527")
if err != nil {return nil, err
}//缓存命中找到,直接返回用户信息
if userinfo != nil {return userinfo, nil
}//set 检测当前是否是热数据
//之所以没有使用Bloom Filter是因为有概率碰撞不准
//如果key数量超过千个,建议还是用Bloom Filter
//这个判断也可以放在业务逻辑代码中,用配置同步做
isHotKey, err := Redis.SISMEMBER("hot_key", "user_info_9527")
if err != nil {return nil, err
}//如果是热key
if isHotKey {//没有找到就认为数据不存在//可能是被删除了return "", nil
}//没有命中缓存,并且没被标注是热点,被认为是临时缓存,那么从数据库中获取
//设置更新锁set user_info_9527_lock nx ex 5
//防止多个线程同时并发查询数据库导致数据库压力过大
lock, err := Redis.Set("user_info_9527_lock", "1", "nx", 5)
if !lock {//没抢到锁的直接等待1秒 然后再拿一次结果,类似singleflight实现//行业常见缓存服务,读并发能力很强,但写并发能力并不好//过高的并行刷新会刷沉缓存time.sleep( time.second)//等1秒后拿数据,这个数据是抢到锁的请求填入的//通过这个方式降低数据库压力userinfo, err := Redis.Get("user_info_9527")if err != nil {return nil, err}return userinfo,nil
}//拿到锁的查数据库,然后填入缓存
userinfo, err := userInfoModel.GetUserInfoById(9527)
if err != nil {return nil, err
}//查找到用户信息
if userinfo != nil {//将用户信息缓存,并设置TTL超时时间让其60秒后失效Redis.Set("user_info_9527", userinfo, 60)return userinfo, nil
}// 没有找到,放一个空数据进去,短期内不再问数据库
Redis.Set("user_info_9527", "", 30)
return nil, nil

查询某个用户信息时:

如果缓存中没有数据,长期缓存会直接返回没有找到,临时缓存则直接走更新流程。

如果数据属于热点key,并且在缓存中找不到的话,就直接返回不存在。

这些热缓存 key,来自于统计一段时间内数据访问流量,计算得出的热点数据。

TTL过期刷新

那长期缓存的更新会异步脚本去定期扫描热缓存列表,通过这个方式来主动推送缓存,同时把 TTL 设置成更长的时间,来保证新的热数据缓存不会过期,同时需要将热度过的key从当前set移除。

在每个业务服务器上部署一个小容量的Redis来保存热点缓存数据

小容量Redis查不到,再去集群中查

问题

使用 Bloom Filter 识别热点 key 时,有时会识别失误,进而导致数据没有找到,那么如何避免这种情况呢?

使用 Bloom Filter 只能添加新 key,不能删除某一个 key,如果想更好地更新维护,有什么其他方式吗?

https://github.com/MGunlogson/CuckooFilter4J

总结

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/613982.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣289. 生命游戏

模拟 染色 思路: 可以复制一个表格,然后根据规则两层循环模拟出结果,但是空间复杂度太高;可以复用原有数组,对其进行染色标记; 最终状态是活的标记值 > 1,还原标记值时可以使用规则 val &g…

MongoDB聚合:$bucketAuto

按照指定的表达式对输入文档进行分类后放入指定数字的桶中&#xff0c;跟$bucket不太一样&#xff0c;$bucketAuto可以指定分组的数量&#xff08;颗粒度&#xff09;&#xff0c;$bucketAuto会根据groupBy的值和颗粒度自动生成桶的边界。 语法 {$bucketAuto: {groupBy: <…

java基础之异常练习题

异常 1.Java 中所有的错误/异常都继承自 Throwable类&#xff1b;在该类的子类中&#xff0c; Error 类表示严重的底层错误&#xff0c; 对于这类错误一般处理的方式是 直接报告并终止程序 &#xff1b; Exception 类表示异常。 2.查阅API&#xff0c;完成以下填空&#xff1a;…

leetcode动态规划(零钱兑换II、组合总和 Ⅳ)

518.零钱兑换II 给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。 示例 1: 输入: amount 5, coins [1, 2, 5] 输出: 4 解释: 有四种方式可以凑成总金额: 55 5221 52111 511111 示例 2: 输入: amount 3, coi…

【江科大STM32单片机】day1点亮LED灯流水灯蜂鸣器

知识点 推挽模式&#xff1a;高-》低、低-》高电平都能驱动 开漏模式&#xff1a;只能低-》高电平能驱动&#xff0c;高电平相当于高阻态 GPIO_WriteBit 操作单个 GPIO_ResetBits 操作同组 3-2 led闪烁 配置相关驱动 USE_STDPERIPH_DRIVER 配置输出文件格式debug配置slink勾选…

华为认证 | HCIE自学通过率有多高?

01 什么是HCIE认证&#xff1f; HCIE&#xff08;Huawei Certified ICT Expert 华为认证 ICT 专家&#xff09;是华为认证体系中最高级别的 ICT 技术认证&#xff0c;表示通过认证的人具有ICT 领域专业知识和丰富实践经验。 02 HCIE考试可以自学吗&#xff1f; HCIE考试可以自…

Leetcode242.有效的字母异位词

文章目录 原题链接思路1&#xff08;字符串排序后比较&#xff09;代码1思路2&#xff08;哈希表&#xff09;代码2 原题链接 Leetcode242.有效的字母异位词 思路1&#xff08;字符串排序后比较&#xff09; t 是 s 的字母异位词 等价于 将 t 和 s 排序后&#xff0c;两个字符串…

基于Springboot的课程答疑系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的课程答疑系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

Python知识点(史上最全)

Python期末考试知识点&#xff08;史上最全&#xff09; python简介 Python是一种解释型语言 Python使用缩进对齐组织代码执行&#xff0c;所以没有缩进的代码&#xff0c;都会在载入时自动执行 数据类型&#xff1a;整形 int 无限大 浮点型 float…

小程序基础学习(组件化)

&#xff08;一&#xff09;创建 找到components文件夹下面创建新的文件夹 然后再文件夹内创建component格式的文件 创建后这样 我创建的是my-info的文件夹以及my-info的components文件&#xff0c;跟着普通的页面一样 &#xff08;二&#xff09; 注册组件 找到你需要使用组…

Android Debug Bridge(ADB)常用指令记录

ADB简介 Android Debug Bridge&#xff08;ADB&#xff09;是用于在计算机和 Android 设备之间进行通信和调试的命令行工具。它提供了一组命令&#xff0c;可以帮助执行各种与 Android 设备相关的操作&#xff0c;如安装应用程序、调试应用程序、访问设备的文件系统等。 下载…

392. 判定子序列

给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde"的一个子序列&#…

轴承故障诊断系统的需求说明,仅供参考使用

项目名称&#xff1a;轴承故障诊断系统 项目目标 开发一个自动化系统&#xff0c;用于测试和诊断工业轴承的潜在故障。系统将通过分析从轴承收集的振动数据来检测异常模式&#xff0c;以预测故障并提供维护建议。 硬件需求 传感器&#xff1a;高精度振动传感器&#xff0c;…

微信小程序-----宿主环境(组件介绍和代码编写)

目录 前言 宿主环境简介 1. 什么是宿主环境 ​编辑 2.小程序的宿主环境 3. 小程序宿主环境包含的内容 一、通信模型 1. 通信的主体 2. 小程序的通信模型 二、运行机制 1.小程序启动的过程 2.页面渲染的过程 三、组件 常用的视图容器类组件 1.view 组件 2.scroll-…

RK3399平台入门到精通系列讲解(驱动篇)eventpoll结构体详解

🚀返回总目录 文章目录 一、eventpoll 结构体二 、epitem 结构体三、eppoll_entry 结构体eventpoll 结构体:eventpoll 结构体是 epoll 在内核中的核心结构epitem 结构体:epitem 结构体用于表示 epoll 实例中的事件项eppoll_entry 结构体:它的作用就是关联Socket等待队列中…

OpenXP(Windows Server 2003 RTM,NT 5.2.3790.0)构建指南

OpenXP&#xff08;Windows Server 2003 RTM&#xff0c;NT 5.2.3790.0&#xff09;构建指南 版本 11&#xff0c;最后更新于 2023 年 10 月 1 日 Discord |信使极客 注意 OpenXP&#xff08;Windows Server 2003 RTM&#xff0c;NT 5.2.3790.0&#xff09;构建指南 (rentry…

Leetcode16-有多少小于当前数字的数字(1365)

1、题目 给你一个数组 nums&#xff0c;对于其中每个元素 nums[i]&#xff0c;请你统计数组中比它小的所有数字的数目。 换而言之&#xff0c;对于每个 nums[i] 你必须计算出有效的 j 的数量&#xff0c;其中 j 满足 j ! i 且 nums[j] < nums[i] 。 以数组形式返回答案。…

MES数据采集在制造业的应用

MES设备数据采集的流程包括以下几个步骤&#xff1a; 1. 设备接入&#xff1a;将设备接入MES系统&#xff0c;建立设备与MES系统之间的连接。 2. 数据采集&#xff1a;通过传感器和采集器等设备&#xff0c;采集设备运行数据和状态信息。 3. 数据存储&#xff1a;将采集到的设…

【JaveWeb教程】(19) MySQL数据库开发之 MySQL数据库操作-DML 详细代码示例讲解

目录 3. 数据库操作-DML3.1 增加(insert)3.2 修改(update)3.3 删除(delete)3.4 总结 3. 数据库操作-DML DML英文全称是Data Manipulation Language(数据操作语言)&#xff0c;用来对数据库中表的数据记录进行增、删、改操作。 添加数据&#xff08;INSERT&#xff09;修改数据…

中央处理器CPU(1)----指令周期和微程序

前言&#xff1a;由于期末复习计算机组成效率太慢所以抽时间写一下文章总结一下思路&#xff0c;理解不是很深&#xff0c;欢迎各位不吝赐教。 由于时间不是很充分&#xff0c;所以有些考点由于我们不考试&#xff0c;一笔带过了。 我这是期末复习总结&#xff0c;不是考研知识…