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

这篇具有很好参考价值的文章主要介绍了极客时间-读多写少型缓存设计。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景

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

总览内容如下:
极客时间-读多写少型缓存设计,极客时间,缓存,java,redis

缓存性价比

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

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

临时缓存

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

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

总结

极客时间-读多写少型缓存设计,极客时间,缓存,java,redis文章来源地址https://www.toymoban.com/news/detail-791008.html

到了这里,关于极客时间-读多写少型缓存设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java架构师缓存通用设计方案

    想学习架构师构建流程请跳转:Java架构师系统架构设计 在实际应用中需要考虑的实际问题。首先,前端页面可以做缓存,虽然图上没有显示,但在现实应用中这是提高性能的一个重要方面。前端页面缓存可以将静态内容

    2024年02月07日
    浏览(39)
  • Java架构师缓存架构设计解决方案

    想学习架构师构建流程请跳转:Java架构师系统架构设计

    2024年02月07日
    浏览(40)
  • 基于java的时间管理系统的设计与实现

    要设计一个时间管理系统,通过这个系统能够满足时间管理的管理功能。系统的主要功能包括首页,个人中心,系统公告管理,用户管理,时间分类管理,事件数据管理,目标数据管理,用户日记管理等功能。 管理员可以根据系统给定的账号进行登录,登录后可以进入时间管

    2024年02月06日
    浏览(37)
  • 缓存的放置时间和删除时间

    在Java中,缓存的放置时间和删除时间是与缓存项相关的重要概念。它们用于控制缓存项在缓存中的生命周期以及何时将其从缓存中删除。以下是对这两个概念的详细介绍: 缓存的放置时间(Cache Put Time) : 含义 :缓存的放置时间是指当您将数据项放置(或存储)到缓存中时

    2024年02月09日
    浏览(29)
  • Spring Batch之读数据—读多文件(三十三)

            前面的所有文件的读取基本上是对单文件执行的,在实际应用中,我们经常操作批量的文件。         Spring Batch框架提供了现有的组件MultiResourceItemReader支持对多文件的读取,通过MultiResourceItemReader读取批量文件非常简单。MultiResourceItemReader通过代理的ItemReade

    2024年02月16日
    浏览(35)
  • “多写多读集群”被攻克,中国数据库产业“越过山丘”

    2020年全国两会期间,合肥工业大学应用数学研究所所长檀结庆在媒体采访中提到:“国产数据库只占据不到7%的市场份额,尤其在数据库最核心的交易业务中,鲜有能跟甲骨文同台竞争并实现替换的产品。” 彼时“去IOE”浪潮已经兴起十多年的时间,但囿于性能、稳定性等客

    2024年02月04日
    浏览(32)
  • 70 # 协商缓存的配置:通过修改时间

    比较一下再去决定是用缓存还是重新获取数据,这样会减少网络请求,提高性能。 客户端第一次请求服务器的时候,服务器会把数据进行缓存,同时会生成一个缓存标识符,这个缓存标识符会被发送到客户端,客户端第二次请求服务器的时候,会把缓存标识符发送到服务器,

    2024年02月12日
    浏览(33)
  • SpringBoot缓存注解@Cacheable之自定义key策略及缓存失效时间指定

    1. 项目依赖 本项目借助 SpringBoot 2.2.1.RELEASE  +  maven 3.5.3  +  IDEA  +  redis5.0 进行开发 开一个 web 服务用于测试 1. key 生成策略 对于 @Cacheable 注解,有两个参数用于组装缓存的 key cacheNames/value: 类似于缓存前缀 key: SpEL 表达式,通常根据传参来生成最终的缓存 key 默认的 redisK

    2024年02月19日
    浏览(39)
  • 翻译: Streamlit从入门到精通七 缓存Cache控制缓存大小和持续时间

    Streamlit从入门到精通 系列: 翻译: Streamlit从入门到精通 基础控件 一 翻译: Streamlit从入门到精通 显示图表Graphs 地图Map 主题Themes 二 翻译: Streamlit从入门到精通 构建一个机器学习应用程序 三 翻译: Streamlit从入门到精通 部署一个机器学习应用程序 四 翻译: Streamlit从入门到精通

    2024年01月20日
    浏览(32)
  • 前端接口请求支持内容缓存和过期时间

    支持用户自定义缓存时间,在规则时间内读取缓存内容,超出时间后重新请求接口 首先封装一下 axios,这一步可做可不做。但是在实际开发场景中都会对 axios 做二次封装,我们在二次封装的 axios 基础上继续封装,增加支持缓存功能 request.js 新建 catchAjax.js ,当我们想用接口

    2024年02月06日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包