首页 > 编程语言 >缓存一梭子, 程序员的快乐就是如此简单

缓存一梭子, 程序员的快乐就是如此简单

时间:2024-02-22 14:14:38浏览次数:26  
标签:缓存 过期 cache Cache 程序员 梭子 数据 内存

缓存也是一把梭项目的标配,从业多年,有事无事set/getCache来一梭子。

夜深人静的时候,头脑里冷不丁会出现一些问题,我竟一时无法自圆其说。

  1. 已经有cpu多级缓存、操作系统page cache,那为什么还需要定义应用缓存?
  2. 应用的多个副本缓存了同一份数据库数据, 怎么保证这些多副本的缓存一致性?

  1. 缓存在计算机体系中的地位

  2. 缓存和缓冲的区别

  3. 使用缓存时的业务考量?

  • 数据一致性要求不那么严格的场景
  • 设计模式: 惰性、直写
  1. 使用缓存时的技术考量?
  • 缓存与数据库一致性
  • 过期策略
  • 驱逐策略

https://course.ccs.neu.edu/cs5600/paging-caching.html

缓存在计算机体系中的定位

内存层次结构

  1. 寄存器
  2. CPU Cache(L1,L2,L3)
  3. RAM (内存/主存)
  4. Disk or SSD(外存/辅助存储)

静态体系架构

  • 低级的缓存包含更多的存储空间,高级的缓存有更好的存取速度(低延迟)。
  • RAM Page cache作为磁盘缓存,CPU cache是RAM的缓存, 寄存器数据来自CPU Cache。

动态逻辑流程

  • 在搜索内存数据, 自上向下搜素(CPU、内存、磁盘),当在某层找到数据时,将在该层的较高层级保存一份副本, 以便将来可以迅速找到该内容。

如果在CPU Cache中没找到数据,我们叫Cache miss;
如果在RAM内存中没找到数据,我们叫Page miss/Page fault

  • 根据数据传输的时间/空间局部性原理,一次传输整个块(而不是单个字节或内存字)时的数据传输效率更高 。
    根据这个设计:
    • 硬盘一次只能传输一个磁盘块,而不是单个字节, 页通常是磁盘块的软件视图,磁盘块、页通常是4KB
    • CPU缓存的数据块通常只有32B

Q:已经有cpu cache, page cache, 为什么我们还需要自定应用义程序cache。

A:不管是cpu级别的cpu cache,还是操作系统维护的page cache,都是对于最近访问数据的缓存, 不针对特定的应用程序,机制对于程序员是透明的。

程序员日常工作的背景是快速灵活的利用内存数据,而不是cpu cache/page cache仅缓存最近访问的数据块(临近32B/4KB),应用程序需要缓存的数据底层存储可能是分散的或访问频次不固定,故应用程序需要做自定义的业务缓存。


作为应用程序员,我们普遍说的Cache指的是应用程序Cache。

2. 缓存 vs 缓冲

缓存是对数据一致性要求不那么严格的一种存取技术,利用内存访问比外设访问速度快的特性, 最终目的是加快入站请求的处理速度。

缓冲是提供一块内存区域,用于入站请求频繁地写操作,之后一次性写入到底层的外设,最终目的是减轻对外设的频繁访问。

3. 使用缓存的业务考量

亚马逊的缓存最佳实践

  • 使用缓存值是否安全。同一段数据在不同的上下文中可能具有不同的一致性要求。例如,在线结账期间,您需要物品的确切价格,因此不适合使用缓存,但在其他页面上,价格晚几分钟更新不会给用户带来负面影响。
  • 对该数据而言,维持缓存是否高效? 某些应用程序会生成不适合缓存的访问模式。 例如,查询频繁变化的大型数据集的键空间,在这种情况下,保持缓存更新可能会抵消缓存带来的优势。
  • 数据结构是否适合缓存? 结构有无schema? or 聚合信息/独立信息?

缓存的产生的方式:

  • 惰性缓存: 仅在应用程序实际请求对象时才填充缓存
  • 直写: 缓存在数据库更新时实时更新,由特定应用程序或者后台程序更新,避免了缓存未命中,可帮助应用程序更好、更快捷地运行

对于第二个问题, 如何维护多个副本的缓存一致?
知乎经典回答:遇事先问“要不要”,而不是直接问”怎么做“,如果定位为缓存,那么本来就有可能是过期的数据; 使用直写来尽快保持一致, 要求更严格就不是缓存了,那就是分布式一致性(CAP理论,共识算法)。

4. 使用缓存的技术考量

4.1 缓存和数据库一致性

  1. cache-aside :(旁路缓存) 强调应用程序App与数据库交互, Cache组件作为旁路。
  • 如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户。
  • 更新: 先更新数据库中的数据,再删除缓存中的数据。
  1. read-through/write-through: (读穿/写穿) 强调App与Cache组件交互
  • 先查询缓存中数据是否存在,如果存在则直接返回,如果不存在,则由缓存组件负责从数据库查询数据,并将结果写入到缓存组件,最后缓存组件将数据返回给应用。
  • 更新: 如果缓存中数据已经存在,则更新缓存中的数据,并且由缓存组件同步更新到数据库中,然后缓存组件告知应用程序更新完成。

旁路缓存与读/写穿缓存的差异在于 :谁来填充Database:App还是cache组件。 1,2中的写缓存和写数据库的行为是贯序同步的。

  1. write back:(写回)在更新数据时,只更新Cache,标记Cache是脏数据,然后立马返回;对于数据库的更新会通过批量异步更新的方式。

写回策略一般用在计算机系统中(上面的CPU Cache和文件系统的Pache Cahce)。


首先要知道缓存过期和缓存驱逐是不同的关注点, 我们以redis为例。

redis 作为内存键值对数据库, 所有的KV都是以全局字典来实现,带有过期时间的kv键值对也是维护在一个独立的字典中。

4.2 redis缓存过期

redis带有过期时间的KV项,并不是到期就被立即清除的, 考虑:

  • 到期删除: 每一个设置了过期时间的kv项附带一个计时器, 不消费内存,但是消耗cpu资源。
  • 惰性删除: 每次访问时,先去名单中判断该k是否需要清理,不占用cpu, 但是有可能kv项始终没人访问,造成过期的kv项内存堆积。
  • 定期删除: 每隔一段时间从带过期时间的kv字典中清理一部分。

redis以惰性删除+定期删除(默认100ms)来实现清理缓存过期KV项, 以实现cpu和mem的平衡使用。

(当然这2种策略还是会有一部分过期的kv值未能删除)

4.3 redis缓存驱逐(内存淘汰)

redis是内存键值对数据库,存储受限于内存。考虑

  • 驱逐目的: 避免无限制占用内存
  • 驱逐时机: maxmemory : 接近或者达到这个值会触发缓存驱逐。
  • 驱逐策略:maxmemory-policy,8种策略(4维度和2种范围的叉积的集合),默认是noeviction

https://cloud.tencent.com/developer/article/2045330

# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key among the ones with an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.

分享一个与缓存有关的OOM案例:

应用定时(1min)滚动设置缓存KV项(1h),一开始使用golang bluele/gcache

2.5k star 内存缓存库, 支持多种驱逐策略(LFU, LRU and ARC)。表现的像是一个固定长度的map。

func main() {
  gc := gcache.New(1000).   // 缓存项容量, 驱逐时机
    LRU().                  // 驱逐策略
    Build()
  gc.Set("key", "value")
}

注意看gcache的驱逐时机是基于缓存项容量,与内存无关
一开始缓存项容量设置的比较大,导致不容易触发gcache的KV项驱逐,实际上这个时候gcache占据的内存在滚动增长,最终应用OOM。

  • 案例可以通过设置较小的 缓存项容量来解决。
  • 案例也可以切换到patrickmn/go-cache缓存库来解决

7.7k star 这个缓存的表现就类似redis

利用go-cache的缓存过期策略: 定期删除过期项(purges expired items every 10 minutes),
搭配合适的过期时间和定时清理过期项的周期。

// Create a cache with a default expiration time of 5 minutes, and which
	// purges expired items every 10 minutes
	c := cache.New(5*time.Minute, 10*time.Minute)

	// Set the value of the key "foo" to "bar", with the default expiration time
	c.Set("foo", "bar", cache.DefaultExpiration)

	// Set the value of the key "baz" to 42, with no expiration time
	// (the item won't be removed until it is re-set, or removed using
	// c.Delete("baz")
	c.Set("baz", 42, cache.NoExpiration)

所以,除了SetCache(k,v)一把梭外,开发者应有更多前置心路历程:

缓存在计算机系统中的地位、缓存的适用性、缓存的使用方式、 缓存作为内存存储的过期、驱逐实践......

本文抛砖引玉,望构建更完整的缓存知识体系, 自勉。

标签:缓存,过期,cache,Cache,程序员,梭子,数据,内存
From: https://www.cnblogs.com/JulianHuang/p/18027192

相关文章

  • 若依+vue3配置菜单后设置缓存但实际上切换页签重复请求接口
    刚接触ruoyi,配置菜单时发现一个问题,配置好了,也设置了缓存,但是切换tab页签还是会重复请求接口,配置如图:仅是举例,如上图,系统管理->角色管理列表配置,路由地址是role,缓存也勾选了,但实际上第一次打开角色管理页签第一次请求了数据,再跳转其他页面,回到角色管理页签时,又一次请求了数据,实......
  • AIGC程序员效能提升之道
    得益于IT产业近几年的繁荣,老杨所在公司的业务也出奇的兴隆,每天干不完的工作背后,也意味着健康的消耗和体重的不断增加。曾记否,刚毕业的老杨体重刚刚堪堪破百,同事们经常调侃他说是一阵风就能吹走,经过了十年的拼搏,他的体重终于达到了130斤。而自从来到这家公司之后,短短两年时间,他的......
  • 《程序员的修炼之道》读后感
    本周阅读了《程序员的修炼之道》,这本书一共8章。针对编程的实效进行一系列的阐述,详细的介绍项目开发中程序员担当的角色,读完本书丰富了自己对一名程序员工作的认知,也让我认识到自己以前的认识知误区,本书从多角度来讲述编程之道,介绍从程序员甚至到项目管理者在一个项目开发的过程中......
  • Antd的ProTable高级表格缓存列设置
    1、目的:要将ProTable组件的列设置缓存到localStorage中,你可以使用浏览器的localStorageAPI。通过监听onColumnsStateChange事件,你可以在每次列的显示和隐藏状态发生变化时,将最新的列设置保存到localStorage中。然后,在组件初始化时,从localStorage中读取之前保存的列设......
  • JetCacheUtil 删除本地及远端缓存
    publicclassJetCacheUtil{privateJetCacheUtil(){}/***删除缓存**/publicstaticbooleanremoveCache(CacheLocatecacheLocate){Assert.isTrue(StringUtils.hasText(cacheLocate.getCacheKey())&&StringUtils.hasT......
  • “薪”的一年程序员裁员潮技术变革情况下 程序员就业机会在哪里?
    引言:一对来自中国的工程师夫妻在美国的不幸身亡,疑似与谷歌的裁员有关,这一事件再次引发了人们对技术变革下裁员对程序员影响的关注。一、针对裁员潮的一些看法在我看来,技术变革对程序员的影响是双面的。一方面,技术变革意味着程序员需要不断学习新技术,提升自己的技能,以适应市场需......
  • 面试官:如何实现多级缓存?
    对于高并发系统来说,有三个重要的机制来保障其高效运行,它们分别是:缓存、限流和熔断。而缓存是排在最前面也是高并发系统之所以高效运行的关键手段,那么问题来了:缓存只使用Redis就够了吗?1.冗余设计理念当然不是,不要把所有鸡蛋放到一个篮子里,成熟的系统在关键功能实现时一定会考......
  • 认识Redis:不只是缓存,还有这些厉害的功能!
    在当今数据驱动的世界中,快速存取信息成为了技术发展的关键。而在众多存储解决方案中,Redis以其独特的魅力和强大的功能,成为了开发者们的宠儿。今天,就让我们一起来认识一下Redis。一、Redis是什么,可以用来干什么?Redis,英文全称是RemoteDictionaryServer(远程字典服务),是一个开源......
  • 2024程序员能有什么新的出路?
    前言前两天和一个前端同学聊天,他说不准备再做前端了,准备去考公。不过难度也很大。从20152016年那会儿开始互联网行业爆发,到现在有7、8年了,当年20多岁的小伙子们,现在也都30+了大量的人面临这个问题:大龄程序员就业竞争力差,未来该如何安身立命?先说我个人的看法:除非你......
  • 对于程序员来说CPU是什么
    通过第一章的学习,我了解到的CPU是计算机的核心组件,它是计算机执行计算和逻辑运算的部分,负责解释和执行指令,控制计算机的各个部分协同工作。CPU按照程序的指令执行相应的操作。CPU是寄存器的集合体(在代码清单中,exa和ebp表示的都是寄存器)。CPU的控制器就会参照程序计数器的数值,从内......