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

背景

内容是极客时间-徐长龙老师的高并发系统实战课的个人学习笔记,欢迎大家学习!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.mfbz.cn/a/311734.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2000-2021年全国各省环境相关指标数据(890+指标)

2000-2021年全国各省环境相关指标数据(890指标) 1、指标时间:2000-2021年 2、范围:31省市 3、来源:2001-2022年环境统计年鉴 4、指标:工业废水排放总量、工业废水排放达标量、工业废水处理量、化学需氧…

golang 生成一年的周数

// GetWeekTimeCycleForGBT74082005 获取星期周期 中华人民共和国国家标准 GB/T 7408-2005 // 参数 year 年份 GB/T 7408-2005 func GetWeekTimeCycleForGBT74082005(year int) (*[]TimeCycle, error) {var yearstart time.Time //当年最开始一天var yearend time.Time //当年…

Python知识点(史上最全)

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

Javaweb之SpringBootWeb案例开发规范的详细解析

1.2 开发规范 了解完需求也完成了环境搭建了,我们下面开始学习开发的一些规范。 开发规范我们主要从以下几方面介绍: 1、开发规范-REST 我们的案例是基于当前最为主流的前后端分离模式进行开发。 在前后端分离的开发模式中,前后端开发人员…

Vue3 父事件覆盖子事件,Vue2 的 v-on=“$listeners“ 的替代方案

在 Vue3 中,$listeners 被删除 子组件代码,需要特别注意的是事件名为 on 开头,例如 onBack。不确定的可以通过给父组件传递 事件或属性,再打印子组件的 attrs useAttrs(),来确定传值 // template v-bind"newA…

Netty-Netty组件了解

EventLoop 和 EventLoopGroup 回想一下我们在 NIO 中是如何处理我们关心的事件的?在一个 while 循环中 select 出事 件,然后依次处理每种事件。我们可以把它称为事件循环,这就是 EventLoop 。 interface io.netty.channel. EventLoo…

【自学笔记】01Java基础-08Java常用API:04包装类

记录Java基础-常用API-有关时间日期的类。 1 包装类 其实就是8种基本数据类型对应的引用类型,因为基本数据类型不能直接参与面向对象编程。具有将基本数据类型转换为对象的功能,并且实现了多种接口,支持集合框架和泛型。 包装类的主要特点和…

记录汇川:H5U与Fctory IO测试8

主程序: 子程序: IO映射 子程序: 出料程序 子程序: 重量程序 子程序: 自动程序 Fctory IO配置: HMI配置 实际动作如下: Fctory IO测试8

【一】创建Python TK GUI窗口,并简单设置窗口

文章目录 背景系统环境开始一个简单GUI启动一个GUI窗口(不完成功能)简单配置GUI窗口(大小、位置、图标) 运行示例 背景 这是一个系列文章。下一篇【【二】为Python Tk GUI窗口添加一些组件和绑定一些组件事件】 使用pyth…

AIGC大模型必备知识——LLM ,你知道它是如何训练的吗?小白必读深度好文

Look!👀我们的大模型商业化落地产品📖更多AI资讯请👉🏾关注Free三天集训营助教在线为您火热答疑👩🏼‍🏫 近年来,人工智能(AI)领域经历了令人瞩目…

FineBI实战项目一(17):热门商品Top10分析开发

点击新建组件,创建热门商品Top10组件。 选择柱状图,拖拽cnt(总数)到横轴,拖拽goodName到纵轴。 选择排序规则。 修改横轴和纵轴的标签名称 切换到仪表板,拖拽组件到仪表板 效果如下:

今天去面一个点工,HR要我会数据库,Linux还有Python,这合理吗

软件测试出路在哪? 业务编程!! 1、软件测试的变化趋势 变化趋势1: 功能测试是核心,但是价值降低 目前测试这个行业,还是有大量的点工。但是行业的进步,技术的创新,导致了企业的需求…

mapper向mapper.xml传参中文时的乱码问题

1.起因: 在idea中进行模糊查询传参时,发现在idea中查中文查不出记录,在navicate中可以查出来。 2.猜测: 1.idea中的编码问题导致的乱码。 2.idea和navicate的编码一致性导致的乱码。 3.mapper向mapper.xml传参后出现乱码。 3.解…

「 典型安全漏洞系列 」02.SQL注入详解

引言:SQL注入是一个老生常谈且又非常重要的漏洞,导致许多热点的数据泄露事件。尽管学习起来相对简单,但它可能用于某些高危漏洞的利用。这使得它成为初学者的兴趣点,甚至对于更有经验的用户来说,SQL注入也是基本知识。…

快速打通 Vue 3(四):标签的 ref 属性与 Vue3 生命周期

很激动进入了 Vue 3 的学习,作为一个已经上线了三年多的框架,很多项目都开始使用 Vue 3 来编写了 这一组文章主要聚焦于 Vue 3 的新技术和新特性 如果想要学习基础的 Vue 语法可以看我专栏中的其他博客 Vue(一):Vue 入…

【数据结构】--二叉树递归题记

最近写了几道关于二叉树的剑指offer题,和小伙伴们分享一下心得。 🌈对称的二叉树 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。 思路分析: 对于二叉树的问题来说肯…

谷达冠楠:抖音开网店创业怎么做

随着互联网的发展,越来越多的人选择在网上创业。而抖音作为目前最火的短视频平台之一,也成为了许多人开网店的首选。那么,如何在抖音上开网店创业呢?下面就来详细介绍一下。 第一步:注册账号 首先,你需要在抖音上注册…

登录模块的实现

一.前期的准备工作 1.页面的布局 (1)表单的校验: 利用element-ui提供的文档绑定rules规则后实现校验 (2)跨域的配置 : 利用proxy代理来解决跨域的问题 (3)axios拦截器的配置 两个点:1. 在请求拦截的成功回调中,如果token,因为调用其它的接口需要token才能调取。 在请…

【排序】对各种排序的总结

文章目录 前言1. 排序算法的复杂度及稳定性分析2. 排序算法的性能测试2.1 重复率较低的随机值排序测试2.2 重复率较高的随机值排序测试 前言 本篇是基于我这几篇博客做的一个总结: 《简单排序》(含:冒泡排序,直接插入排序&#x…

【Docker】概述与安装

🥳🥳Welcome Huihuis Code World ! !🥳🥳 接下来看看由辉辉所写的关于Docker的相关操作吧 目录 🥳🥳Welcome Huihuis Code World ! !🥳🥳 一. Docker的概述 1.Docker为什么出现 2…