1.BlueStore源码分析之Cache
2.linux内核源码:内存管理——内存分配和释放关键函数分析&ZGC垃圾回收
3.对TP5数据库缓存cache的一些思考
4.④优雅的缓存框架:SpringCache之多级缓存
5.Spark RDDä¸cacheåpersistçåºå«
6.Spring Cache 中的表达式求值(及 Spring Cache 小结)
BlueStore源码分析之Cache
BlueStore通过DIO和Libaio直接操作裸设备,放弃了PageCache,为优化读取性能,它自定义了Cache管理。核心内容包括元数据和数据的Cache,以及两种Cache策略,c 调用ocx源码即LRU和2Q,2Q是默认选择。
2Q算法在BlueStore中主要负责缓存元数据(Onode)和数据(Buffer),为提高性能,Cache被进一步划分为多个片,HDD默认5片,SSD则默认8片。
BlueStore的元数据管理复杂,主要分为Collection和Onode两种类型。Collection存储在内存中,Onode则对应对象,便于对PG的操作。启动时,会初始化Collection,将其信息持久化到RocksDB,并为PG分配Cache。
由于每个BlueStore承载的Collection数量有限(Ceph建议每个OSD为个PG),Collection结构设计为常驻内存,而海量的Onode则仅尽可能地缓存在内存中。
对象的数据通过BufferSpace进行管理,写入和读取完成后,会根据特定标记决定是否缓存。同时,内存池机制监控和管理元数据和数据,一旦内存使用超出限制,会执行trim操作,丢弃部分缓存。
深入了解BlueStore的Cache机制,可以参考以下资源:
linux内核源码:内存管理——内存分配和释放关键函数分析&ZGC垃圾回收
本文深入剖析了Linux内核源码中的源码提取教程内存管理机制,重点关注内存分配与释放的关键函数,通过分析4.9版本的源码,详细介绍了slab算法及其核心代码实现。在内存管理中,slab算法通过kmem_cache结构体进行管理,利用数组的形式统一处理所有的kmem_cache实例,通过size_index数组实现对象大小与kmem_cache结构体之间的映射,从而实现高效内存分配。其中,关键的计算方法是通过查找输入参数的最高有效位序号,这与常规的0起始序号不同,从1开始计数。
在找到合适的kmem_cache实例后,下一步是通过数组缓存(array_cache)获取或填充slab对象。若缓存中有可用对象,则直接从缓存分配;若缓存已空,会调用cache_alloc_refill函数从三个slabs(free/partial/full)中查找并填充可用对象至缓存。在对象分配过程中,array_cache结构体发挥了关键作用,它不仅简化了内存管理,还优化了内存使用效率。
对象释放流程与分配流程类似,涉及数组缓存的管理和slab对象的回收。在cache_alloc_refill函数中,关键操作是检查slab_partial和slab_free队列,寻找空闲的对象以供释放。整个过程确保了内存资源的高效利用,避免了资源浪费。
总结内存操作函数概览,栈与堆的区别是显而易见的。栈主要存储函数调用参数、局部变量等,而堆用于存放new出来的对象实例、全局变量、shiro源码系列静态变量等。由于堆的动态分配特性,它无法像栈一样精准预测内存使用情况,导致内存碎片问题。为了应对这一挑战,Linux内核引入了buddy和slab等内存管理算法,以提高内存分配效率和减少碎片。
然而,即便使用了高效的内存管理算法,内存碎片问题仍难以彻底解决。在C/C++中,没有像Java那样的自动垃圾回收机制,导致程序员需要手动管理内存分配与释放。如果忘记释放内存,将导致资源泄漏,影响系统性能。为此,业界开发了如ZGC和Shenandoah等垃圾回收算法,以提高内存管理效率和减少内存碎片。
ZGC算法通过分页策略对内存进行管理,并利用“初始标记”阶段识别GC根节点(如线程栈变量、静态变量等),并查找这些节点引用的直接对象。此阶段采用“stop the world”(STW)策略暂停所有线程,确保标记过程的准确性。接着,通过“并发标记”阶段识别间接引用的对象,并利用多个GC线程与业务线程协作提高效率。在这一过程中,ZGC采用“三色标记”法和“remember set”机制来避免误回收正常引用的对象,确保内存管理的精准性。
接下来,ZGC通过“复制算法”实现内存回收,将正常引用的源码工具web对象复制到新页面,将旧页面的数据擦除,从而实现内存的高效管理。此外,通过“初始转移”和“并发转移”阶段进一步优化内存管理过程。最后,在“对象重定位”阶段,完成引用关系的更新,确保内存管理过程的完整性和一致性。
通过实测,ZGC算法在各个阶段展现出高效的内存管理能力,尤其是标记阶段的效率,使得系统能够在保证性能的同时,有效地管理内存资源。总之,内存管理是系统性能的关键因素,Linux内核通过先进的算法和策略,实现了高效、灵活的内存管理,为现代操作系统提供稳定、可靠的服务。
对TP5数据库缓存cache的一些思考
在优化代码过程中,我偶然想起TP5中的数据库操作cache,发现其在缓存时间内能够显著提高请求速度,但修改数据后可能不能及时更新。本文旨在深入理解cache的工作原理。
然而,官方文档和网络搜索结果大多仅介绍了如何使用cache,对于其原理并未详细阐述,因此我决定阅读源码以获取更多信息。
首先,我疑惑cache与常规缓存的区别。通过实验,我发现其功能与常规缓存类似,均支持设置key值、基础源码论坛有效期及标签。cache方法在设置属性后,真正的操作在select、find、value、column等方法中。
结论显而易见:不论是写入还是读取cache,其过程与常规缓存相同。不同之处在于,若未指定key名,系统会根据操作的数据库名、表名及主键ID自动生成密文key,避免了key重复可能导致的缓存覆盖问题。
其次,我关注数据更新时cache的处理。文档提到两种方法:手动在update等更新操作中添加cache以实现缓存更新;或使用find方法结合主键查询自动清理缓存。新增操作不会触及缓存,而更新操作后缓存将被清除,随后在查询时重新写入。增删改查中,新增操作不涉及缓存。
接着,我探讨了数据更新自动清除缓存的条件。文档提及两种操作均使用主键ID作为条件时,可以实现自动清除缓存,涉及缓存操作时是否使用主键查询条件的差异,共八种组合情况。
尽管还有些未测试的情况,如更新操作的数据是否为缓存数据、查询与更新操作条件是否一致等,我更倾向于深入源码探索答案。以update操作为例,结论是只有当查询与修改操作均使用主键ID作为条件时,才能实现自动清除缓存。
因此,数据库缓存并非随意使用,不当使用可能影响数据时效性和用户体验。若确需使用,建议手动设置缓存名称,并在更新操作时明确清除指定缓存。
本文总结了cache的基本原理、使用方法及注意事项。希望对您有所帮助。如有问题或讨论,欢迎访问我的博客:/blog
④优雅的缓存框架:SpringCache之多级缓存
多级缓存策略能够显著提升系统响应速度并减轻二级缓存压力。本文采用Redis作为二级缓存,Caffeine作为一级缓存,通过多级缓存的设计实现优化。
首先,进行多级缓存业务流程图的声明,并通过LocalCache注解对一级缓存进行管理。具体源码地址如下。
其次,自定义CaffeineRedisCache,进一步优化缓存性能。相关源码地址提供如下。
为了确保缓存机制的正确执行,自定义CacheResolver并将其注册为默认的cacheResolver。具体实现细节可参考以下源码链接。
在实际应用中,通过上述自定义缓存机制,能够有效地提升系统性能和用户体验。为了验证多级缓存优化效果,我们提供实战应用案例和源码。相关实战案例和源码如下链接。
实现多级缓存策略的完整源码如下:
后端代码:<a href="github.com/L1yp/van-tem...
前端代码:<a href="github.com/L1yp/van-tem...
欲加入交流群讨论更多技术内容,点击链接加入群聊: Van交流群
Spark RDDä¸cacheåpersistçåºå«
éè¿è§å¯RDD.scalaæºä»£ç å³å¯ç¥écacheåpersistçåºå«ï¼
def persist(newLevel: StorageLevel): this.type = {
ããif (storageLevel != StorageLevel.NONE && newLevel != storageLevel) {
ããããthrow new UnsupportedOperationException( "Cannot change storage level of an RDD after it was already assigned a level")
ãã}
ããsc.persistRDD(this)
ããsc.cleaner.foreach(_.registerRDDForCleanup(this))
ããstorageLevel = newLevel
ããthis
}
/** Persist this RDD with the default storage level (`MEMORY_ONLY`). */
def persist(): this.type = persist(StorageLevel.MEMORY_ONLY)
/** Persist this RDD with the default storage level (`MEMORY_ONLY`). */
def cache(): this.type = persist()
å¯ç¥ï¼
1ï¼RDDçcache()æ¹æ³å ¶å®è°ç¨çå°±æ¯persistæ¹æ³ï¼ç¼åçç¥å为MEMORY_ONLYï¼
2ï¼å¯ä»¥éè¿persistæ¹æ³æ工设å®StorageLevelæ¥æ»¡è¶³å·¥ç¨éè¦çåå¨çº§å«ï¼
3ï¼cacheæè persist并ä¸æ¯actionï¼
éï¼cacheåpersisté½å¯ä»¥ç¨unpersistæ¥åæ¶
Spring Cache 中的表达式求值(及 Spring Cache 小结)
上一章未完部分,本章重点解析Spring Cache中的表达式求值机制。Cache注解如key、unless、condition等,支持SpEL表达式。
CacheOperationExpressionEvaluator在CacheAspectSupport中实现,其关键在于定义的ExpressionKey,实现Comparable接口,包含element和expression字段,并配备SpelExpressionParser和DefaultParameterNameDiscoverer。
ExpressionKey类在解析过程中扮演关键角色,它从cache获取表达式,若不存在,则使用parser执行parseExpression并缓存结果。此过程需注意表达式的参数名兼容性,分别在Java 8及以下和Java 8以上版本中使用不同的ParameterNameDiscoverer。
CachedExpressionEvaluator的核心在于解析并缓存表达式,而CacheOperationExpressionEvaluator在其中扮演关键角色,它创建CacheEvaluationContext,该上下文在SpEL求值过程中提供方法参数。
CacheOperationExpressionEvaluator中提供了求值关键函数,通过生成CacheEvaluationContext以及调用generateKey等方法,结合SpEL表达式和上下文计算实际值。
总结,Spring Cache的核心源码至此解析完毕。配置由ProxyCachingConfiguration实现,解析注解则由SpringCacheAnnotationParser完成。CacheAspectSupport作为拦截类,通过CacheResolver等进行解析,结合CacheOperationExpressionEvaluator进行表达式求值,最终执行缓存操作。
沉浸式go-cache源码阅读!
大家好,我是豆小匠,这期将带领大家探索go-cache的内部实现,深入理解本地缓存机制,并分享一些阅读源码的实用技巧。
首先,我们从源码入手,Goland中仅需关注cache.go和sharded.go两个文件,总共行代码,是不错的学习资源。通过README.md,可以了解到包的使用方法。
创建缓存实例时,我们注意到它依赖于清理间隔,而非实时过期删除。这引出了一个问题:如何在逻辑上处理过期缓存?我们开始在cache.go中寻找答案。
首先,我们关注Cache结构体,它定义了整个缓存的框架。接下来,重点阅读New函数,这里使用了runtime.SetFinalizer来确保即使对象被设置为nil,清理协程的GC回收也受到影响。
通过源码解析,我们明白,如果清理协程与Cache对象关联,即使对象不再活跃,GC仍无法立即回收。再深入Get方法,你会发现,缓存失效并非通过key是否存在,而是通过item中的过期时间判断,定时清理主要为了释放存储空间。
最后,我们对常用的方法进行挑选,梳理cache类的成员变量和功能,通过创建图示的方式,来帮助我们更好地理解和记忆。值得注意的是,onEvicted是删除key的回调函数,而sharded.go是未公开的分片缓存实验代码。
简单概括Linux内核源码高速缓存原理(图例解析)
高速缓存(cache)概念和原理涉及在处理器附近增加一个小容量快速存储器(cache),基于SRAM,由硬件自动管理。其基本思想为将频繁访问的数据块存储在cache中,CPU首先在cache中查找想访问的数据,而不是直接访问主存,以期数据存放在cache中。
Cache的基本概念包括块(block),CPU从内存中读取数据到Cache的时候是以块(CPU Line)为单位进行的,这一块块的数据被称为CPU Line,是CPU从内存读取数据到Cache的单位。
在访问某个不在cache中的block b时,从内存中取出block b并将block b放置在cache中。放置策略决定block b将被放置在哪里,而替换策略则决定哪个block将被替换。
Cache层次结构中,Intel Core i7提供一个例子。cache包含dCache(数据缓存)和iCache(指令缓存),解决关键问题包括判断数据在cache中的位置,数据查找(Data Identification),地址映射(Address Mapping),替换策略(Placement Policy),以及保证cache与memory一致性的问题,即写入策略(Write Policy)。
主存与Cache的地址映射通过某种方法或规则将主存块定位到cache。映射方法包括直接(mapped)、全相联(fully-associated)、一对多映射等。直接映射优点是地址变换速度快,一对一映射,替换算法简单,但缺点是容易冲突,cache利用率低,命中率低。全相联映射的优点是提高命中率,缺点是硬件开销增加,相应替换算法复杂。组相联映射是一种特例,优点是提高cache利用率,缺点是替换算法复杂。
cache的容量决定了映射方式的选取。小容量cache采用组相联或全相联映射,大容量cache采用直接映射方式,查找速度快,但命中率相对较低。cache的访问速度取决于映射方式,要求高的场合采用直接映射,要求低的场合采用组相联或全相联映射。
Cache伪共享问题发生在多核心CPU中,两个不同线程同时访问和修改同一cache line中的不同变量时,会导致cache失效。解决伪共享的方法是避免数据正好位于同一cache line,或者使用特定宏定义如__cacheline_aligned_in_smp。Java并发框架Disruptor通过字节填充+继承的方式,避免伪共享,RingBuffer类中的RingBufferPad类和RingBufferFields类设计确保了cache line的连续性和稳定性,从而避免了伪共享问题。
Sparkä¸cacheåpersistçåºå«
cache
ããé»è®¤æ¯å°æ°æ®åæ¾å°å åä¸ï¼ææ§è¡
ããdef cache(): this.type = persist()
ããpersist
ããå¯ä»¥æå®æä¹ åç级å«ã
ããæ常ç¨çæ¯MEMORY_ONLYåMEMORY_AND_DISKã
ããâ_2â表示æå¯æ¬æ°ãå°½éé¿å 使ç¨_2åDISK_ONLY级å«
ããcacheåpersistç注æç¹
ãã1.é½æ¯ææ§è¡(æçå«å»¶è¿æ§è¡)ï¼éè¦action触åæ§è¡ï¼æå°åä½æ¯partition
ãã2.对ä¸ä¸ªRDDè¿è¡cacheæè persistä¹åï¼ä¸æ¬¡ç´æ¥ä½¿ç¨è¿ä¸ªåéï¼å°±æ¯ä½¿ç¨æä¹ åçæ°æ®
ãã3.å¦æ使ç¨ç¬¬äºç§æ¹å¼ï¼ä¸è½ç´§è·actionç®å