写字就能赚钱做网站网站的建设与维护实践报告

当前位置: 首页 > news >正文

写字就能赚钱做网站,网站的建设与维护实践报告,怎么开网页游戏平台,网站域名备案与不备案的区别目录 文章目录 目录缓存简介工作原理缓存分类1.按照技术层次分类2.按照应用场景分类3.按照缓存策略分类 应用场景1.硬件缓存2.软件缓存数据库缓存Web开发应用层缓存 3.分布式缓存4.微服务架构5.移动端应用6.大数据处理7.游戏开发 缓存优点缓存带来的问题 常见常用Java缓存技术1…目录 文章目录 目录缓存简介工作原理缓存分类1.按照技术层次分类2.按照应用场景分类3.按照缓存策略分类 应用场景1.硬件缓存2.软件缓存数据库缓存Web开发应用层缓存 3.分布式缓存4.微服务架构5.移动端应用6.大数据处理7.游戏开发 缓存优点缓存带来的问题 常见常用Java缓存技术1.Redis最常用2.MyBatis缓存2.1 MyBatis一级缓存2.2 MyBatis二级缓存 3.Spring Cache4.Ehcache 额外Java缓存解决方案1.Guava Cache2.Caffeine3.OSCache4.Infinispan5.Hazelcast6.ConcurrentHashMap / LRUHashMap7.Memcached8.Apache JCS (Java Caching System)9.Hibernate Second Level Cache 常用Java缓存技术用法Ehcache实际使用1.导入依赖2.yml配置3.ehcache的xml配置4.编写测试代码查看使用效果5.分析结果 实现原理核心组件核心流程 Redis简介核心特性应用场景实际使用1.导入依赖2.对Redis进行配置3.启动类上开启注解4.使用时注入RedisTemplate5.在接口中使用6.效果展示 注意事项实现原理简述SDSSimple Dynamic StringList的实现Set和Zset的实现Hash的实现总结 Spring Cache简介主要特点常用注解实际使用1.导入依赖2.同样需要启动类开启注解3.直接在接口中进行使用4.效果展示 总结实现原理简述核心组件工作流程 MyBatis缓存简介一级缓存本地缓存 / Session 级别缓存一级缓存的工作原理一级缓存的优缺点 二级缓存全局缓存 / SqlSessionFactory 级别缓存二级缓存的工作原理 实际使用实现原理 常用Java缓存技术总结EhcacheRedisSpring CacheMyBatis缓存小结 手写本地缓存DemoDemo主要功能解释 使用实例位置 参考文献 缓存 简介 缓存Cache是一种存储技术用于临时存放从原始数据源如硬盘、数据库或网络获取的数据副本目的是加快数据的访问速度减少不必要的重复处理进而提升系统整体的性能和响应效率。它是计算机科学中“空间换时间”策略的一个典型应用即通过牺牲少量的存储空间来换取数据访问速度的显著提升。 简而言之缓存就是存储数据副本或计算结果的组件以便后续可以更快地访问。 工作原理 一句话概况更快读写的存储介质减少IO减少CPU计算性能优化。 数据存储当系统首次请求数据时数据从原始数据源加载并被复制到缓存中。这一步通常发生在第一次访问或数据更新后重新加载时。快速访问之后当系统再次请求相同的数据时可以直接从缓存中获取而无需再次访问较慢的数据源。由于缓存通常位于更快的存储介质上如RAM数据访问速度远高于直接从硬盘或网络获取。数据更新与同步缓存中的数据并不是永久不变的需要有机制来维护其与数据源之间的一致性。常见的策略包括定时刷新、写穿write-through、写回write-back等。此外为了高效利用有限的缓存空间还会有算法如最近最少使用LRU、最不经常使用LFU等定期淘汰旧数据为新数据腾出空间。 缓存分类 1.按照技术层次分类 硬件缓存 CPU缓存位于CPU和主内存之间用于加速数据和指令的访问速度减少CPU等待时间。GPU缓存类似CPU缓存专为图形处理单元设计提高图形渲染和数据处理速度。 软件缓存 操作系统缓存如文件系统缓存用于加速文件的读写操作。数据库缓存数据库管理系统内部缓存如MySQL的InnoDB缓冲池缓存索引和数据页减少磁盘I/O。Web应用缓存 浏览器缓存存储已访问过的网页资源如图片、样式表、JavaScript文件加速页面加载。代理服务器缓存位于客户端和源服务器之间存储常用网页内容减少带宽消耗和响应时间。
2.按照应用场景分类 数据库查询缓存 ORM工具缓存如Hibernate、MyBatis的二级缓存减少对数据库的重复查询。搜索引擎缓存加速搜索结果的呈现如Elasticsearch的查询缓存。 Web服务与API缓存 RESTful API缓存通过HTTP缓存机制如ETag、Last-Modified减少API响应时间。CDN缓存内容分发网络全球分布的缓存节点存储静态资源提升访问速度和用户体验。 应用数据缓存 Session缓存存储用户的会话状态减轻数据库负担如Web应用中的用户登录状态。计算结果缓存如Spring Cache用于存储昂贵计算的结果避免重复计算。 分布式缓存 Redis/Memcached高性能键值存储系统常用于分布式应用中共享数据缓存提高数据访问速度和应用扩展性。
3.按照缓存策略分类 时间敏感型缓存 LRULeast Recently Used最近最少使用策略移除最近未被访问的数据。TTLTime To Live设置数据在缓存中的有效存活时间到期自动删除。 访问频率敏感型缓存 LFULeast Frequently Used最少访问次数策略移除访问频率最低的数据。LFU变体如LRU2、2Q结合访问时间和频率进行淘汰。 自适应策略 FBRFrequency-Based Replacement、LRUFLeast Recently and Frequently Used First、ALRUF等根据访问模式动态调整淘汰策略。
应用场景 1.硬件缓存 CPU缓存CPU级别的缓存分为L1、L2、L3等用于存储频繁访问的指令和数据减少CPU访问内存的时间。GPU缓存用于存储图形处理过程中频繁访问的数据加速图形渲染和计算任务。 2.软件缓存 数据库缓存 数据库查询缓存例如MySQL的查询缓存存储查询结果减少对数据库的重复查询。 ORM框架缓存如Hibernate二级缓存、MyBatis二级缓存提高数据访问速度。 Web开发 浏览器缓存存储静态资源如图片、CSS、JavaScript以减少网络请求加速页面加载。 Web服务器缓存如Nginx缓存、Apache模块缓存减少服务器对动态内容的重复生成。 CDN缓存内容分发网络全球分布存储网站内容缩短用户访问距离提高速度。
应用层缓存 会话缓存存储用户会话信息减轻数据库压力如Tomcat session复制。计算结果缓存存储计算密集型操作的结果如价格计算、推荐算法结果等。API响应缓存缓存频繁请求的API响应减少后端服务负载。 3.分布式缓存 Redis高可用的键值存储系统用于存储会话信息、计数器、消息队列等。Memcached简单高效的分布式缓存系统适合存储简单的键值数据。Spring Cache提供了一套抽象方便在Spring应用中整合各种缓存技术。 4.微服务架构 服务间通信缓存减少服务间重复的RPC调用提高微服务间通信效率。配置中心缓存存储配置信息确保配置更改时能快速传播到各服务实例。 5.移动端应用 离线缓存为提高用户体验在移动端应用中缓存数据以便在网络不稳定或无网络时仍能访问内容。 6.大数据处理 MapReduce缓存在MapReduce作业中预加载数据到内存减少多次磁盘I/O。Spark缓存将RDD弹性分布式数据集存储在内存中加速迭代计算。 7.游戏开发 游戏资源缓存减少游戏启动和运行时的加载时间提升玩家体验。 通过上述场景可以看出缓存的应用几乎覆盖了所有需要提高数据访问速度和降低系统响应时间的领域是现代软件开发中不可或缺的技术。 缓存优点 提高性能减少了对外部慢速资源的访问提高了数据访问速度。降低负载减少了数据库、网络等资源的压力。提升用户体验页面加载更快应用响应更迅速。 缓存带来的问题 数据一致性确保缓存数据与数据源保持一致避免脏读或数据不一致问题。缓存失效如何高效管理缓存项的生命周期避免缓存雪崩、击穿等问题。资源管理合理分配缓存资源避免过度占用内存。缓存介质带来的不可靠性:一般使用内存做缓存的话若机器故障如何保证缓存的高可用可考虑对缓存进行分布式做成高可用同时需要接受这种不可靠不安全会给数据带来的问题在异常情况下进行补偿处理定期持久化等方式缓存的数据使得更难排查问题:因为缓存命中是随着访问随时变化的缓存的行为难以重现使得出现BUG很难排查。进程内缓存可能会增加GC压力在具有垃圾收集功能的语言中如Java大量长寿命的缓存对象会增加垃圾收集的时间和次数。 使用缓存之前我们需要对数据进行分类对访问行为进行预估思考哪些数据需要缓存缓存时需要采用什么策略这样我们才不被缓存所困扰才能规避这些问题。 常见常用Java缓存技术 1.Redis最常用 特点Redis是一个开源的、基于键值对的数据结构存储系统可用作数据库、缓存和消息代理。它支持多种数据结构如字符串、哈希、列表、集合、有序集合等且数据全部存储在内存中提供了极高的读写速度。Redis支持主从复制和集群模式能够满足高可用和水平扩展的需求。 应用场景适合需要高性能、低延迟的数据缓存和实时分析场景如社交网络的点赞计数、购物车、实时排行榜等。 实际使用在构建实时数据分析平台时使用Redis作为缓存存储热点数据结合发布/订阅功能实现实时数据推送极大提升了数据处理和响应速度。 2.MyBatis缓存 2.1 MyBatis一级缓存 特点 作用域一级缓存是SqlSession级别的缓存即每个SqlSession实例都有自己的缓存空间。生命周期与SqlSession相同当SqlSession关闭时一级缓存也随之失效。自动启用MyBatis默认开启一级缓存无需额外配置。自动管理当执行查询后结果自动存入缓存后续相同查询直接从缓存中读取直到SqlSession中发生增删改操作缓存会被清空。数据隔离不同SqlSession之间缓存不共享适用于单线程操作或短生命周期的SqlSession。 应用场景 适用于单个事务内多次查询同一数据的场景可以减少数据库访问提升性能。适合处理简单的查询操作尤其是在数据不频繁变动且查询频繁的情况下。 实际使用 开发者通常不需要做额外配置即可享受一级缓存带来的性能提升。在需要确保数据最新时可通过手动清除缓存如调用SqlSession的clearCache()方法或关闭当前SqlSession来重新查询数据库。 2.2 MyBatis二级缓存 特点 作用域二级缓存是Mapper级别的跨SqlSession共享由同一个SqlSessionFactory创建的所有SqlSession共享。配置需求需要手动开启且需在对应的Mapper XML文件中使用标签配置。数据一致性二级缓存更加关注数据的一致性问题通常需要序列化和反序列化对象且在数据更新时增删改会清空相关缓存。灵活性提供了更多配置选项如缓存实现类、缓存大小、过期策略等可以自定义缓存策略。 应用场景 适合多线程环境或在多个SqlSession间共享数据的场景。对于不经常改变的共享数据如配置信息、静态数据表等使用二级缓存可以显著提高系统性能。 实际使用 需要在MyBatis配置文件中全局启用二级缓存并在相应的Mapper配置文件中添加标签。考虑到数据一致性使用二级缓存时要确保数据的更新策略能及时刷新缓存避免脏读。实际开发中可能还需要结合第三方缓存工具如Redis进一步增强二级缓存的功能实现更复杂的数据共享和管理。 3.Spring Cache 特点Spring Cache是Spring框架提供的一个抽象缓存支持它不直接提供缓存实现而是提供了一套接口允许开发者灵活选择和配置不同的缓存提供商如Ehcache、Redis、Caffeine等。通过注解的方式开发者可以轻松实现方法级别的缓存。 应用场景适用于基于Spring框架的应用特别是那些需要细粒度控制缓存策略的应用如数据访问层的方法缓存、Web服务的响应缓存等。 实际使用在构建RESTful API时使用Spring Cache注解来缓存频繁请求但不经常变化的API响应减少了服务器的计算和数据库查询负担提高了服务的响应速度和吞吐量。 4.Ehcache 特点Ehcache是一种广泛使用的、轻量级的Java进程内缓存解决方案。它支持内存和磁盘两级缓存提供了丰富的缓存策略如LRU、LFU等并且可以进行分布式缓存的扩展。Ehcache配置简单易于集成到现有的Java应用中支持事务、监听器和统计信息收集等功能。 应用场景适用于单机应用或小型集群环境特别适合需要频繁读取且不经常变更的数据缓存如用户会话、配置信息等。 实际使用在处理高并发用户会话管理时Ehcache可以有效减少数据库的读取压力通过设置合理的过期策略保证数据的新鲜度同时减轻维护负担。 额外Java缓存解决方案 1.Guava Cache 特点Guava库提供的本地缓存实现提供了丰富的缓存过期策略基于时间、基于大小、基于引用等、自动加载、统计信息等功能且易于使用适合单机应用。应用场景适用于需要高性能、轻量级缓存解决方案的Java应用程序特别是在处理大量数据缓存和快速查找场景下。 2.Caffeine 特点Caffeine是一个高性能、近似最近最少使用(LRU)的本地缓存库设计上旨在超越Guava Cache提供了更优秀的性能和灵活性。它通过优化数据结构和算法实现了低延迟和高命中率。应用场景适用于需要高性能缓存且对响应时间敏感的应用如高访问量的Web应用、实时数据分析系统等。 3.OSCache 特点OSCache是一个成熟的开源缓存框架支持缓存JSP页面、Servlet输出、对象图形等提供了事件监听器、缓存刷新机制和灵活的配置选项。应用场景虽然不如Guava和Caffeine现代但在一些遗留系统中仍可见其身影特别适合需要页面缓存和动态内容缓存的Web应用。 4.Infinispan 特点Infinispan是一个高度可扩展的分布式缓存框架不仅支持内存缓存还支持持久化存储提供了事务支持、数据网格功能以及丰富的数据分片和复制策略。应用场景适用于需要分布式缓存解决方案的大规模应用特别是在需要高性能、高可用和数据一致性的场景中。 5.Hazelcast 特点Hazelcast是一个开源的内存数据网格提供了分布式的内存缓存、计算和消息传递解决方案。它支持多节点集群、数据分区、高可用性配置等高级特性。应用场景适合需要高性能、分布式缓存和数据共享的系统如微服务架构、大数据处理、实时分析等。 6.ConcurrentHashMap / LRUHashMap 特点虽然不是专门的缓存框架但通过自定义实现可以基于JDK内置的ConcurrentHashMap或继承LinkedHashMap实现简单的LRU缓存。这种方式灵活但功能相对基础。应用场景适合轻量级缓存需求或者作为临时解决方案特别是在对第三方依赖有严格限制的项目中。 7.Memcached 特点虽然不是纯Java实现但通过客户端库可以在Java应用中使用。Memcached是一个高性能、分布式内存对象缓存系统简单易用广泛应用于缓存数据库查询结果、会话数据等。应用场景适用于跨语言环境特别是需要在不同应用或服务之间共享缓存数据的场景。 8.Apache JCS (Java Caching System) 特点JCS是一个高性能、分布式缓存系统提供了多种缓存算法支持多种缓存后端如内存、磁盘、远程缓存等并支持缓存监听器、事件通知等。应用场景适合需要灵活配置和高级缓存管理功能的应用尤其是在需要多种缓存策略和存储介质的应用中。 9.Hibernate Second Level Cache 特点作为ORM框架Hibernate的一部分第二级缓存用于缓存数据库查询结果以减少数据库的访问次数。它可以与多种第三方缓存实现集成如Ehcache、Infinispan等提供了一种透明的缓存机制开发者无需直接操作缓存。应用场景适用于基于Hibernate的大型应用程序尤其是那些需要频繁访问相同数据库实体的场景通过减少数据库的读取操作提升整体性能。 常用Java缓存技术用法 Ehcache 简介ehcache是现在非常流行的纯java开源框架,配置简单结构清晰,功能强大。 实际使用 1.导入依赖 dependencygroupIdnet.sf.ehcache/groupIdartifactIdehcache/artifactId /dependency2.yml配置 spring:cache:type: ehcacheehcache:config: classpath:ehcache.xml3.ehcache的xml配置 ehcachexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:noNamespaceSchemaLocationhttp://ehcache.org/ehcache.xsdupdateCheckfalse!–缓存路径–diskStore pathD:/project/cache_demo/base_ehcache/!– 默认缓存 –defaultCachemaxEntriesLocalHeap10000eternalfalsetimeToIdleSeconds120timeToLiveSeconds120maxEntriesLocalDisk10000000diskExpiryThreadIntervalSeconds120memoryStoreEvictionPolicyLRU/!– helloworld1缓存 –cache namehelloworld1maxElementsInMemory1eternalfalsetimeToIdleSeconds5timeToLiveSeconds5overflowToDisktruememoryStoreEvictionPolicyLRU/!– helloworld2缓存 –cache namehelloworld2maxElementsInMemory1000eternalfalsetimeToIdleSeconds5timeToLiveSeconds5overflowToDiskfalsememoryStoreEvictionPolicyLRU/ /ehcache!–以下属性是必须的name Cache的名称必须是唯一的(ehcache会把这个cache放到HashMap里)。iskStore 指定数据存储位置可指定磁盘中的文件夹位置defaultCache 默认的管理策略maxElementsInMemory 在内存中缓存的element的最大数目。maxElementsOnDisk 在磁盘上缓存的element的最大数目默认值为0表示不限制。eternal 设定缓存的elements是否永远不过期。如果为true则缓存的数据始终有效如果为false那么还要根据timeToIdleSecondstimeToLiveSeconds判断。overflowToDisk 如果内存中数据超过内存限制是否要缓存到磁盘上。以下属性是可选的timeToIdleSeconds 对象空闲时间指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0表示一直可以访问。timeToLiveSeconds 对象存活时间指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0表示一直可以访问。diskPersistent 是否在磁盘上持久化。指重启jvm后数据是否有效。默认为false。diskExpiryThreadIntervalSeconds 对象检测线程运行时间间隔。标识对象状态的线程多长时间运行一次。diskSpoolBufferSizeMB DiskStore使用的磁盘大小默认值30MB。每个cache使用各自的DiskStore。memoryStoreEvictionPolicy 如果内存中数据超过内存限制向磁盘缓存时的策略。默认值LRU可选FIFO、LFU。缓存的3 种清空策略 FIFO first in first out (先进先出).LFU Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性hit 值最小的将会被清出缓存。LRU Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳当缓存容量满了而又需要腾出地方来缓存新的元素的时候那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。–4.编写测试代码查看使用效果 Test void ehcacheTest() {//通过读取ehcache配置文件来创建缓存管理器即CacheManagerInputStream in CacheDemoApplicationTests.class.getClassLoader().getResourceAsStream(ehcache.xml);CacheManager cacheManager CacheManager.create(in);// 创建一个缓存实例在配置文件中获取一个缓存实例final Cache cache cacheManager.getCache(helloworld1);final String key greeting;final String key1 greeting1;//创建一个数据容器来存放我们所创建的elementfinal Element putGreeting new Element(key, Hello, World!);final Element putGreeting1 new Element(key1, Hello Ehcache);//将数据放入到缓存实例中cache.put(putGreeting);cache.put(putGreeting1);//取值final Cache cache2 cacheManager.getCache(helloworld1);final Element getGreeting cache2.get(key);final Element getGreeting1 cache2.get(key1);// Print the valueSystem.out.println(value//getGreeting.getObjectValue());System.out.println(value1//getGreeting1.getObjectKey()); }5.分析结果 可以从控制台中发现我们成功取到缓存值 实现原理 Ehcache 的实现原理非常复杂涉及多个层级的组件和逻辑但我会尝试基于其核心组成部分和关键流程给出一个简化的概述并尽可能结合源码细节进行解释。请注意由于源码细节可能随版本更新而变化以下内容基于Ehcache的一般设计思路和常见版本特性。 核心组件 CacheManager管理一组缓存实例的工厂类负责创建、获取、销毁缓存。在Ehcache中CacheManager是一个单例对象可以通过CacheManager.getInstance()获取。Cache代表一个具体的缓存包含了一系列配置项如最大内存大小、是否溢写到磁盘等和数据存储结构。每个Cache都有一个唯一的名称并且内部维护了缓存项的集合。Element缓存的基本单位包含一个key-value对以及一些额外的元数据如过期时间、创建时间等。Store数据存储层包括内存存储和磁盘存储。内存存储通常是基于SelfPopulatingCache自生式缓存和MemoryStore实现而磁盘存储则通过DiskStore实现。 核心流程 缓存初始化当通过CacheManager创建或获取一个Cache时首先会检查是否存在配置文件中定义的相应缓存。如果存在则根据配置创建Cache实例。这个过程会初始化存储结构比如创建内存存储和配置磁盘存储策略。 // 简化版示例代码非实际源码 private static CacheManager cacheManager;static {cacheManagerInit(); }/*** EhcacheConstants.CACHE_NAME, cache name* EhcacheConstants.MAX_ELEMENTS_MEMORY, 缓存最大个数* EhcacheConstants.WHETHER_OVERFLOW_TODISK, 内存不足时是否启用磁盘缓存* EhcacheConstants.WHETHER_ETERNAL, 缓存中的对象是否为永久的如果是超过设置将被忽略对象从不过期* EhcacheConstants.timeToLiveSeconds, 缓存数据的生存时间元素从构建到消亡的最大时间间隔值只在元素不是永久保存时生效若该值为0表示该元素可以停顿无穷长的时间* EhcacheConstants.timeToIdleSeconds 对象在失效前的允许闲置时间仅当eternalfalse对象不是永久有效时使用可选属性默认值是0可闲置时间无穷大/ private static CacheManager cacheManagerInit() {if (cacheManager null) {//创建一个缓存管理器cacheManager CacheManager.create();//建立一个缓存实例Cache memoryOnlyCache new Cache(EhcacheConstants.CACHE_NAME,EhcacheConstants.MAX_ELEMENTS_MEMORY,EhcacheConstants.WHETHER_OVERFLOW_TODISK,EhcacheConstants.WHETHER_ETERNAL,EhcacheConstants.timeToLiveSeconds,EhcacheConstants.timeToIdleSeconds);//在内存管理器中添加缓存实例cacheManager.addCache(memoryOnlyCache);return cacheManager;}return cacheManager; }缓存读取当通过key访问缓存时Ehcache首先在内存中查找如果找到了对应的Element则直接返回其value。如果内存中没有找到且配置了磁盘存储和溢写策略则尝试从磁盘加载数据。 public static Object getValue(String ehcacheInstanceName, Object key) {Cache cache cacheManager.getCache(ehcacheInstanceName);Object value cache.get(key).getObjectValue();return value; }逻辑解释 2.1 初始化观测器并检查状态: this.getObserver.begin(); 开启获取操作的观测记录。this.checkStatus(); 检查缓存当前状态是否允许执行get操作。 2.2 处理禁用状态: 如果缓存被标记为disabled则记录观测结果为GetOutcome.MISS_NOT_FOUND未找到并直接返回null表示获取失败。 2.3 尝试从缓存中获取元素: Element element this.compoundStore.get(key); 试图从复合存储器compoundStore中根据提供的键获取元素。 2.4 处理未找到的情况: 如果元素为null说明缓存中没有对应键的值观测记录为GetOutcome.MISS_NOT_FOUND然后返回null。 2.5 检查元素是否过期: if (this.isExpired(element)) { … } 判断获取到的元素是否已经过期。如果过期执行立即删除操作并记录观测结果为GetOutcome.MISS_EXPIRED过期未命中然后返回null。 2.6 更新访问统计和返回元素: 对于未过期的元素如果不需要跳过访问统计更新通过skipUpdateAccessStatistics判断则调用element.updateAccessStatistics(); 更新元素的访问统计信息。记录观测结果为GetOutcome.HIT命中最后返回找到的元素。 缓存写入写入操作首先是写入内存然后根据配置可能还会写入磁盘。Ehcache会检查当前内存使用情况如果超过配置的最大内存限制可能会触发溢写到磁盘的逻辑。 public static void put(String ehcacheInstanceName, String key, Object value) {Cache cache cacheManager.getCache(ehcacheInstanceName);cache.put(new Element(key, value)); }逻辑解释 3.1 初始化观察者和缓存写入器: this.putObserver.begin(); 开始记录缓存插入的观测事件。如果useCacheWriter为真则初始化缓存写入器管理器准备调用外部的缓存写入逻辑。 3.2 状态检查与异常处理: this.checkStatus(); 检查缓存当前的状态确保可以执行put操作。如果缓存被标记为disabled则直接忽略此操作并结束观测。 3.3 处理空元素: 如果传入的element为null根据doNotNotifyCacheReplicators条件输出日志提示可能是因为软引用被垃圾回收然后结束观测不执行任何插入操作。 3.4 验证元素有效性: 检查元素的键是否为null若为null则同样忽略操作并结束观测。 3.5 元素预处理: 重置元素的访问统计信息。若元素未设置生命周期lifespan应用默认配置。如果磁盘空间已满执行退避逻辑可能包括等待、抛出异常等。 3.6 执行缓存写入: 根据useCacheWriter标志决定是否使用缓存写入器进行操作。如果使用调用compoundStore.putWithWriter该方法会先尝试调用外部定义的CacheWriter如果配置了的话然后再执行实际的存储操作。在这个过程中如果遇到StoreUpdateException异常会根据配置决定是否通知监听器并重新抛出异常。如果不使用缓存写入器则直接调用compoundStore.put进行存储。 3.7 更新访问统计与通知: 更新元素的更新统计信息。根据元素是否已存在于缓存中即是否是更新操作而非新增调用notifyPutInternalListeners通知相关的监听器。 3.8 结束观测并返回结果: 根据元素是否已存在记录插入操作的结果ADDED或UPDATED并结束观测。 缓存淘汰策略当内存达到上限时Ehcache会根据配置的淘汰策略如LRU、LFU来决定哪些Element应该被淘汰出内存。这一过程可能涉及复杂的算法和数据结构管理。缓存清理与过期Ehcache支持定时清理过期的缓存项这通常由后台线程周期性执行。同时每次读取或写入操作也会检查元素是否过期。 深入理解Ehcache的实现原理需要查看具体版本的源码特别是net.sf.ehcache.Cache、net.sf.ehcache.store.MemoryStore、net.sf.ehcache.store.DiskStore等类。例如内存存储的管理在MemoryStore中实现其中可能包含一个HashMap或者更复杂的数据结构来维护缓存项。磁盘存储则涉及文件系统的读写操作DiskStore类中会有相关的文件操作逻辑。 Redis 简介 Redis是我们平常开发中最常用到的缓存中间件了Redis是一个开源的、高性能的键值存储系统它以高效的数据结构存储数据并支持多种数据类型包括字符串(Strings)、哈希(Hashes)、列表(Lists)、集合(Sets)、有序集合(Sorted Sets)等。由于它的超高性能使其成为我们在开发中首选的缓存工具 核心特性 高性能Redis将数据存储在内存中使用C语言实现单线程模型减少了线程上下文切换的开销因此能够提供非常高的读写速度。官方数据表明Redis能够支持每秒十万次以上的读写操作。数据持久化Redis提供了两种数据持久化机制——RDB快照和AOF追加文件。RDB通过定时快照的方式将内存中的数据保存到磁盘上适合恢复和备份AOF则记录每次写操作保证数据的高完整性但可能会占用更多磁盘空间。网络IO模型Redis使用单线程模型处理网络请求通过非阻塞IO和多路复用如epoll在Linux上的实现来高效地处理并发连接避免了多线程锁的竞争开销。多种数据结构Redis不仅仅是一个简单的键值存储它支持丰富多样的数据结构这使得它能灵活地应用于多种场景如计数器、排行榜、消息队列等。发布/订阅功能Redis支持发布/订阅模式可以作为消息队列使用实现消息的广播和订阅适用于实时通知、聊天系统等。事务尽管不是严格的ACID事务Redis提供了简单的事务支持允许一系列操作在没有其他客户端请求干扰的情况下连续执行。Lua脚本Redis支持在服务器端执行Lua脚本可以用来实现复杂的逻辑处理减少网络往返提高执行效率。主从复制与高可用Redis支持主从复制可以设置多个从节点提高系统的可用性和数据安全性。在此基础上还可以配置哨兵模式或集群模式实现自动故障转移进一步提高系统的高可用性。模块化扩展Redis支持模块化扩展允许开发者为Redis增加新的数据类型和功能进一步增强了其灵活性和功能性。 应用场景 Redis因其独特的特性和高性能被广泛应用于缓存、会话存储、实时分析、消息队列、排行榜、计数器、社交网络、游戏开发等多个领域。在许多情况下它作为数据库的补充加速数据访问减少数据库压力提高整体系统的响应速度。 实际使用 1.导入依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId /dependency2.对Redis进行配置 Configuration public class RedisConfig extends CachingConfigurerSupport {Beanpublic RedisTemplateObject, Object redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplateObject, Object redisTemplate new RedisTemplate();//默认的Key序列化器为JdkSerializationRedisSerializerredisTemplate.setKeySerializer(new StringRedisSerializer()); // key序列化//redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // value序列化redisTemplate.setConnectionFactory(connectionFactory);return redisTemplate;} }3.启动类上开启注解 4.使用时注入RedisTemplate Resource private RedisTemplate redisTemplate;5.在接口中使用 比如我们在查询数据时先去查询缓存如果查不到再去查询数据库并存入Redis中下一次就可以直接命中缓存 ApiOperation(分段提示展示) GetMapping(/show) public RListScoreNote show(RequestParam(questionListId) String questionListId) {ListScoreNote scoreNoteList null;// 动态构造keyString key scorenote questionListId; // 例:score_note_1397844391040167938// 先从Redis中获取缓存数据scoreNoteList (ListScoreNote) redisTemplate.opsForValue().get(key);if (scoreNoteList ! null) {// 如果存在, 直接返回, 无需查询数据库return R.success(scoreNoteList);}LambdaQueryWrapperScoreNote wrapper new LambdaQueryWrapperScoreNote().eq(ScoreNote::getQuestionListId, questionListId).orderByAsc(ScoreNote::getEndScore);scoreNoteList scoreNoteService.list(wrapper);// 将查询到的fenduan数据缓存到Redis中redisTemplate.opsForValue().set(key, scoreNoteList, 1, TimeUnit.HOURS);return R.success(scoreNoteList); }同时在增删改时要记得删除缓存防止缓存和数据库不一致 ApiOperation(删除分段提示) PostMapping(/delete) public RString delete(RequestBody ScoreNote scoreNote) {if (isNullOrEmpty(scoreNote.getId())) {return R.error(无效参数);}scoreNoteService.removeById(scoreNote.getId());// 清理所有分段提示的缓存数据Set keys redisTemplate.keys(scorenote);redisTemplate.delete(keys);return R.success(删除成功); }6.效果展示 可以看到数据是正常返回的 第二次请求也是直接命中了缓存 再来看我们Redis的存储情况也是成功存储
注意事项 尽管Redis提供了诸多优点但在使用时也需要注意其内存消耗因为所有数据默认存储在内存中。此外对于大规模数据存储和高并发访问需要合理规划内存使用、持久化策略和集群部署以确保系统的稳定性和性能。 实现原理 简述 Redis 实现原理涉及以下几个核心方面 事件驱动模型Redis 核心是一个事件循环(event loop)模型它基于 Reactor 模式。程序围绕一个事件循环进行通过 I/O 多路复用如Linux下的epoll或select来监听和处理多个客户端连接。当有新的连接请求或已有连接上有数据可读写时事件循环会将其加入到待处理事件队列并分配给事件处理器执行相应的操作。这样Redis 能够在一个单线程内高效地处理大量的并发连接。内存数据存储Redis 将所有数据存储在内存中这大大减少了数据访问延迟使得读写操作极为迅速。内存响应时间大约为100纳秒级别这是Redis能够达到每秒百万级甚至更高访问量的基础。数据结构包括字符串(Strings)、列表(Lists)、哈希(Hashes)、集合(Sets)、有序集合(Sorted Sets)、位图(Bitmaps)、超日志(LogLogs)、地理空间索引(GeoSpatial Indexes)等每种数据结构都有优化的内部实现以提高存储和访问效率。单线程模型Redis 采用单线程模型处理客户端请求避免了多线程环境下的上下文切换开销和锁竞争问题。单线程简化了编程模型使得代码更容易理解和维护同时也降低了错误的可能性。尽管单线程可能看似限制了CPU的使用但Redis的瓶颈通常在于网络IO和内存访问速度而非CPU计算能力。持久化机制为了防止数据丢失Redis 提供了两种持久化方式RDB快照和AOF追加文件。RDB是定时将内存中的数据保存到磁盘上的二进制文件适合做备份AOF则是记录每一条写命令持续追加到文件中保证更高的数据安全性但会占用更多磁盘空间。Redis 还支持混合使用这两种方式以平衡数据安全性和性能。集群与分布式Redis Cluster 提供了数据自动分区和故障转移的能力允许数据分布在多个节点上。每个节点都持有数据的一部分并且能够透明地处理节点加入、离开或失败的情况。Redis Sentinel 系统用于监控集群中各个节点的状态当主节点出现问题时Sentinel 可以自动进行故障转移提升系统的可用性。网络通信早期版本的Redis基于自己定制的事件处理逻辑而在新版本中Redis开始支持更多的网络通信库如libuv、asyncio等这些库进一步提升了网络通信的效率和跨平台兼容性。 SDSSimple Dynamic String Redis使用SDS而非C语言标准字符串以提供快速的长度获取O(1)复杂度、自动扩容、空间预分配和惰性释放等优势。SDS设计包括三个核心部分free空闲空间、len内容长度、buf存储内容的字符数组支持二进制数据存储。 List的实现 Redis 3.2前List可使用ziplist或linkedlist实现之后引入了QuickList。ziplist是一种紧凑存储结构适用于元素数量少、元素小的情景优点是内存连续缺点是插入删除操作可能引发元素移动。linkedlist采用双向链表结构提供了良好的插入删除性能。QuickList结合两者优点将多个ziplist通过双向链表相连以适应大数据量存储同时保持了ziplist的内存高效性。 Set和Zset的实现 Set根据条件选择整数集合或字典实现前者用于整数且数量不大于特定阈值的集合。Zset有序集合根据元素数量和大小选择ziplist或SkipList实现ziplist用于数据量小且元素长度受限的场景而SkipList跳跃表提供了对大量数据高效排序和检索的能力平均查找复杂度为O(logN)。 Hash的实现 Hash也采用ziplist或hashtable哈希表实现具体依据是元素个数和大小。当元素数量少、大小符合配置条件时使用ziplist以节省空间反之使用哈希表保证快速访问。 总结 Redis内部几种数据结构的底层实现原理通过巧妙的设计平衡内存使用、访问速度和数据操作效率。从SDS的灵活高效到List、Set、Zset、Hash根据不同条件采用不同存储结构展现了Redis在实现高性能缓存存储方面的技术细节。这些机制共同支撑起Redis作为一个快速、灵活且强大的数据存储解决方案的基础。 Spring Cache 简介 Spring Cache 是 Spring 框架中一个强大的模块它为开发者提供了一套基于注解的缓存抽象。这一抽象层使得开发者能够在不修改太多代码的情况下方便地将缓存逻辑集成到应用程序中从而提升应用的性能和响应速度。Spring Cache 的设计目标是简化缓存的使用同时保持足够的灵活性以支持多种缓存实现。 我们日常开发中经常会将Spring Cache和Redis结合使用达到既开发代码简洁可以直接使用注解开发又可以实现缓存的高性能 主要特点 基于注解的配置Spring Cache 使用注解如 Cacheable, CachePut, CacheEvict, Caching来声明缓存策略这些注解可以直接应用在方法上减少了手动管理缓存的复杂性。缓存提供者无关性Spring Cache 提供了一层抽象使得开发者可以无缝切换不同的缓存实现如 Ehcache、Redis、Caffeine、 Hazelcast 等。只需更改配置无需修改业务代码。AOP 支持Spring Cache 利用了面向切面编程AOP的技术自动拦截标记了缓存注解的方法调用根据注解的指示进行缓存的读取或更新减少了对业务逻辑的侵入。灵活的缓存管理支持多种缓存策略如缓存过期、缓存更新策略如写入时更新、读取时更新、缓存淘汰策略等可以根据应用需求进行细粒度的配置。事务感知Spring Cache 能够与 Spring 的事务管理集成确保缓存操作与数据库事务的一致性。 常用注解 Cacheable标记在方法上表示该方法的返回结果应该被缓存起来下次相同参数的调用可以直接从缓存中获取结果。CachePut同样标记在方法上当方法被执行时其结果将会被放入缓存但它不会影响方法的正常执行即使缓存操作失败也不会抛出异常。CacheEvict用于缓存的清除操作可以配置在方法上当方法被调用时根据配置的规则清除缓存项。Caching当需要在一个方法上应用多个缓存操作时可以使用此注解组合多个缓存操作。 实际使用 1.导入依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-cache/artifactId /dependency2.同样需要启动类开启注解 EnableCaching // 开启Spring Cache注解方式缓存功能3.直接在接口中进行使用 比如我们在新增和删除时可以进行缓存清除直接加注解就可以实现 ApiOperation(添加文章) PostMapping(/add) CacheEvict(value articleCache, allEntries true) // 清除所有缓存 public RString add(RequestBody Article article) {if (isNullOrEmpty(article.getCategoryId(), article.getTitle(), article.getContent())) {return R.error(无效参数);}articleService.save(article);return R.success(添加成功); }ApiOperation(删除文章) PostMapping(/delete) CacheEvict(value articleCache, allEntries true) // 清除所有缓存 public RString delete(RequestBody Article article) {if (isNullOrEmpty(article.getId())) {return R.error(无效参数);}articleService.removeById(article.getId());return R.success(删除成功); }我们在修改时可以进行缓存清除也可以进行重新放入缓存推荐粒度更小下面是两个注解进行实现 ApiOperation(修改文章) PostMapping(/update) // CacheEvict(value articleCache, allEntries true) // 清除所有缓存 CachePut(value articleCache, key #articleReq.id) // 将结果放入缓存 public RString update(RequestBody Article articleReq) {if (isNullOrEmpty(articleReq.getId())) {return R.error(无效参数);}Article article articleService.getById(articleReq.getId());if (article null) {return R.error(文章不存在);}BeanUtils.copyProperties(articleReq, article);articleService.updateById(article);return R.success(修改成功); }查询时是一样的逻辑先查询缓存查不到再去查数据库并放入缓存这里我们通过注解可快速实现 ApiOperation(查询文章列表) GetMapping(/list) Cacheable(value articleCache, key #categoryId) // 把返回的文章数据进行缓存 public RPageArticle list(RequestParam(value pageNum, defaultValue 1) Integer pageNum,RequestParam(value pageSize, defaultValue 10) Integer pageSize,RequestParam(value categoryId) String categoryId,RequestParam(value ss, required false) String ss) {LambdaQueryWrapperArticle queryWrapper new LambdaQueryWrapperArticle().eq(Article::getCategoryId, categoryId).orderByDesc(Article::getCreateTime);if (!isNullOrEmpty(ss)) {queryWrapper.like(Article::getTitle, ss);}PageArticle page new Page(pageNum, pageSize);articleService.page(page, queryWrapper);return R.success(page); }ApiOperation(查询文章详情) GetMapping(/detail) Cacheable(value articleCache, key #id) // 把返回的文章数据进行缓存 public RArticleResp detail(RequestParam(value id) String id) {if (isNullOrEmpty(id)) {return R.error(无效参数);}Article article articleService.getById(id);if (article null) {return R.error(文章不存在);}ArticleResp articleResp new ArticleResp();BeanUtils.copyProperties(article, articleResp);// 设置点赞数int count likeService.count(new LambdaQueryWrapperLike().eq(Like::getEventId, id));articleResp.setLikeNum(count);// 设置评论列表ListCommentResp commentRespList commentService.list(new LambdaQueryWrapperComment().eq(Comment::getEventId, id)).stream().map(comment - {CommentResp commentResp new CommentResp();BeanUtils.copyProperties(comment, commentResp);User user userService.getById(comment.getUserId());if (user ! null) {commentResp.setUser(user);}// 设置评论点赞数commentResp.setLikeNum(likeService.count(new LambdaQueryWrapperLike().eq(Like::getEventId, comment.getId())));// 设置评论回复列表ListComment commentList commentService.list(new LambdaQueryWrapperComment().eq(Comment::getEventId, comment.getId()));if (!commentList.isEmpty()) {ListCommentResp commentARespList commentList.stream().map(commentA - {CommentResp commentAResp new CommentResp();BeanUtils.copyProperties(commentA, commentAResp);User user1 userService.getById(comment.getUserId());if (user1 ! null) {commentAResp.setUser(user1);}// 设置评论点赞数commentAResp.setLikeNum(likeService.count(new LambdaQueryWrapperLike().eq(Like::getEventId, commentA.getId())));return commentAResp;}).collect(Collectors.toList());commentResp.setCommentRespList(commentARespList);}return commentResp;}).collect(Collectors.toList());articleResp.setCommentRespList(commentRespList);return R.success(articleResp); }4.效果展示 小插曲注意在使用Spring Cache时使用的缓存要唯一。比如使用Redis就不要配置Ehcache。同样使用Ehcache就不要配置Redis。否则将会抛出异常Spring容器会找不到你所存储的缓存配置。这里就是因为我两个都进行了配置也是解决了很久 正常返回查询结果 Redis中也进行了正常存储 对本文章进行修改 可以看到本文章也进行了正常缓存
总结 综上所述Spring Cache 提供了一种简洁、高效的方式来集成缓存机制帮助开发者构建高性能的应用程序同时保持了代码的清晰和可维护性。 实现原理 简述 Spring Cache 是Spring框架提供的一种抽象缓存机制它允许开发者在不改变原有业务逻辑的情况下通过简单的注解配置来实现对方法调用结果的缓存从而提高应用的性能。Spring Cache的核心实现原理基于Spring AOP面向切面编程和模板方法模式。 核心组件 Spring AOP (面向切面编程) Spring Cache利用AOP拦截标记了特定注解如Cacheable, CachePut, CacheEvict等的方法调用。当一个方法被调用时Spring的代理基于JDK动态代理或CGLIB会先于目标方法执行之前和之后插入缓存逻辑。 缓存管理器 (Cache Manager) CacheManager是Spring Cache的核心组件负责创建和管理各种缓存实例。它是一个接口Spring提供了多种实现比如ConcurrentMapCacheManager使用Java的ConcurrentHashMap作为缓存存储、EhCacheCacheManager集成Ehcache、RedisCacheManager集成Redis等。开发者可以根据需要选择合适的缓存实现。 缓存注解 Cacheable: 用于方法上表示方法的返回结果应该被缓存起来下次相同的调用直接从缓存中获取而不是执行实际方法。CachePut: 同样用于方法上但与Cacheable不同它在方法执行后无论结果如何都会更新缓存常用于数据更新操作。CacheEvict: 用于方法上用于清除缓存中的某些项可以指定清除条件。Caching: 允许在一个方法上组合多个缓存操作注解。 缓存解析器 (Cache Resolver) 和缓存解析策略 (Cache Resolution Strategy) 这些组件负责确定在执行缓存操作时使用哪个具体的缓存管理器和缓存。Spring提供了默认实现但也可以自定义以适应特定需求。 工作流程 方法调用前AOP代理检测到方法上存在缓存注解如Cacheable。解析缓存键根据注解配置和方法参数使用Spring Expression Language (SpEL) 解析出缓存键。查找缓存通过缓存管理器和解析出的键在缓存中查找结果。缓存命中如果找到直接返回缓存中的结果跳过方法执行。缓存未命中执行方法获取结果并根据Cacheable或CachePut注解的配置将结果放入缓存。方法执行后如果是CacheEvict根据配置清除相关缓存项。 通过这种机制Spring Cache能够无缝集成到Spring应用中提供了声明式缓存策略大大简化了缓存逻辑的实现提高了应用程序的响应速度和性能。 MyBatis缓存 简介 MyBatis缓存我们平常一般不会去特意留意或者去开启但是MyBatis的缓存几乎是我们接触到最多的Java缓存技术MyBatis 提供了两层缓存机制旨在通过减少数据库访问次数来提升查询性能。MyBatis会默认帮我们开启一级缓存 一级缓存本地缓存 / Session 级别缓存 默认开启是线程级别的缓存也称为SqlSession的缓存。在一个SqlSession生命周期中有效当SqlSession关闭时缓存会被清空。在同一个SqlSession中MyBatis会把执行的方法和参数通过算法生成缓存的键值将键值和结果存放在一个Map中。如果后续的键值一样则直接从Map中获取数据。不同的SqlSession之间的缓存是相互隔离的。缓存是以namespace为单位的不同namespace下的操作互不影响。当执行UPDATE、INSERT、DELETE语句时可以通过配置使得在查询前清空缓存通过设置flushCache“true”。 作用域一级缓存是SqlSession级别的意味着它只在一个SqlSession的生命期内有效。当一个SqlSession被创建时MyBatis会为其分配一个缓存空间该缓存仅对该SqlSession可见。同一SqlSession内的多次相同查询第二次及以后的查询会直接从缓存中获取结果避免了重复的数据库访问。 触发条件只要同一个SqlSession执行相同的Mapper方法和参数就会命中一级缓存。 失效情况 当SqlSession执行commit、rollback、close操作时一级缓存会被清空。执行了任何更新操作如insert、update、delete后也会清空缓存因为数据可能已经改变。显式调用clearCache()方法也会清空缓存。 一级缓存的工作原理 缓存存储当在一个SqlSession中执行查询操作时MyBatis会将查询结果存储在内部的一个Map对象中。这个Map的键是查询的SQL语句和参数值是查询结果。缓存命中如果在同一个SqlSession中再次执行相同的查询即SQL语句和参数都相同MyBatis会首先检查一级缓存中是否存在该查询的结果。如果存在则直接从缓存中获取结果而不是再次访问数据库。缓存失效一级缓存的生命周期与SqlSession相同。当SqlSession关闭或提交/回滚事务时一级缓存中的数据将被清空。此外如果在同一个SqlSession中执行了更新操作如INSERT、UPDATE、DELETE那么一级缓存也会被清空以确保数据的一致性。 一级缓存的优缺点 优点由于一级缓存是SqlSession级别的因此它提供了非常快的查询速度。对于频繁执行的相同查询一级缓存可以显著减少数据库的访问次数。缺点一级缓存的缺点是它的生命周期较短且只能在一个SqlSession内部使用。因此如果需要在多个SqlSession之间共享数据就需要使用二级缓存。 二级缓存全局缓存 / SqlSessionFactory 级别缓存 二级缓存则是为了延长查询结果的保存时间从而提高系统性能。它也可以用于共享数据。二级缓存的范围更大它可以跨SqlSession是多个SqlSession共享的。MyBatis允许你在映射文件中配置cache/标签以开启二级缓存。当这个映射文件被加载时会创建一个Cache实例并将其存储在MapperRegistry中。当一个新的SqlSession执行查询时会先检查这个MapperRegistry中是否存在对应的Cache实例如果存在则使用这个Cache实例进行查询。 作用域二级缓存是基于SqlSessionFactory的跨SqlSession共享适用于分布式环境。一旦开启同一个命名空间下的SQL查询结果会被存储在二级缓存中后续的SqlSession如果执行相同的查询语句就可以直接从二级缓存中获取数据。 配置与启用二级缓存需要在MyBatis的配置文件中启用并且在需要缓存的Mapper XML文件中指定cache/元素。同时实体类必须实现序列化接口Serializable以便在不同线程和进程中共享。 失效策略二级缓存可以配置过期时间、内存大小限制等以及使用LRU最近最少使用等策略来管理缓存空间。 注意事项 二级缓存对数据一致性要求较高如果数据更新频繁需要考虑缓存与数据库数据同步的问题。因为二级缓存在多个SqlSession间共享所以对数据的修改需要考虑如何刷新缓存以确保缓存数据的时效性和一致性。二级缓存的粒度通常是Mapper级别的即一个Mapper下的所有查询都可以共享缓存但可以通过cache-ref/元素来引用另一个Mapper的缓存配置实现更细粒度的控制。 二级缓存的工作原理 缓存存储当某个Mapper的查询结果被存储到二级缓存后其他Mapper或SqlSession也可以访问这些数据。与一级缓存类似二级缓存也使用Map来存储数据但Map的键可能更加复杂以区分不同的Mapper和查询条件。缓存命中当其他Mapper或SqlSession执行相同的查询时MyBatis会首先检查二级缓存中是否存在该查询的结果。如果存在则直接从缓存中获取结果而不是再次访问数据库。缓存失效二级缓存的失效机制与一级缓存类似。当执行更新操作时相关的二级缓存会被清空。此外还可以通过配置来设置缓存的过期时间等参数。 实际使用 我们平常开发过程中通过对二级缓存的配置来进行使用 在配置MyBatis的缓存时有两种主要的方法 在全局配置文件中配置缓存在MyBatis的全局配置文件中通过settings元素的子元素setting来配置缓存可以设置缓存的类型和其他相关属性。在注解中配置缓存在使用注解的方式进行SQL映射时可以使用CacheNamespace注解来配置缓存。 以MyBatis-Plus为例 不开启的情况下 可以发现如果我们不开启二级缓存即使是同样的SQL也不会命中缓存 开启二级缓存 在application.yml或application.properties中开启缓存 对于application.yml添加如下配置 mybatis-plus:configuration:cache-enabled: true如果使用application.properties则配置为 mybatis-plus.configuration.cache-enabledtrue在需要使用二级缓存的Mapper接口上添加CacheNamespace注解。如果你希望自定义缓存行为可以在该注解中设置更多属性如指定缓存的实现类等。 再次测试缓存是否命中 可以看到确实是命中了缓存SQL也是只执行了一次
实现原理 MyBatis的二级缓存是建立在SqlSessionFactory级别上的旨在提高应用程序的整体性能通过跨SqlSession重用已经执行过的查询结果。其实现原理概括如下 缓存作用域二级缓存的范围是基于命名空间Namespace的这意味着同一个命名空间下的SQL查询结果会被共享。每个Mapper映射文件都有一个独立的命名空间MyBatis会为每个命名空间创建一个单独的二级缓存。CacheExecutor装饰模式当MyBatis配置开启二级缓存cacheEnabledtrue在创建SqlSession时MyBatis会使用CacheExecutor来装饰原始的Executor执行器。CacheExecutor是一个装饰器模式的应用它会在执行查询之前先检查二级缓存中是否存在所需数据。缓存存储结构二级缓存的数据结构是基于Map的其Key由Mapper的ID、查询条件如偏移量、限制条件、SQL语句以及所有输入参数组成Value是查询结果。这样的设计允许MyBatis根据精确的查询条件快速定位缓存数据。缓存失效策略当执行增删改操作提交事务后MyBatis会清空相关的二级缓存以保证数据一致性。此外还可以通过配置自定义的缓存同步策略例如基于时间或基于某些条件的自动刷新。自定义缓存实现MyBatis允许开发者通过实现org.apache.ibatis.cache.Cache接口来自定义缓存实现。这为集成第三方缓存系统如Redis、Memcached提供了可能。启用条件为了启用二级缓存除了全局配置外还需要在每个Mapper的XML文件中使用cache标签并可以配置缓存的属性如缓存实现类、序列化策略等。缓存交互过程 当执行查询时CacheExecutor首先检查二级缓存中是否存在匹配的键值对。如果存在则直接从缓存中返回结果避免了数据库查询。如果不存在则执行查询并将结果存储到二级缓存中以便后续相同查询能够直接命中缓存。 MyBatis的二级缓存通过在执行器层面添加一层缓存逻辑利用Map存储查询结果并基于命名空间进行隔离实现了跨SqlSession的数据共享有效减少数据库访问次数提升了应用性能。 常用Java缓存技术总结 Ehcache 概述 Ehcache 是一个纯Java编写的、广泛应用于Java平台的进程内缓存框架它提供了内存和磁盘存储、缓存加载器、缓存扩展、缓存异常处理器等特性。Ehcache特别适合于单机应用或者小型集群环境因为它直接运行在JVM中访问速度快效率高。但是对于大规模分布式系统Ehcache在缓存共享和集群部署上的支持不够完善多个节点间的数据同步较为困难。 使用场景 单个应用或小型项目对缓存访问速度要求极高的场合。作为MyBatis等ORM工具的二级缓存提高数据库查询性能。在Shiro等安全框架中存储会话信息提升应用安全性与性能。 Redis 概述 Redis是一个开源的、基于键值对的数据结构存储系统可以用作数据库、缓存和消息中间件。它支持多种数据结构如字符串、哈希、列表、集合、有序集合等并且提供了丰富的API。Redis运行在独立的服务器上客户端通过网络协议访问因此它天然支持分布式缓存非常适合大型分布式系统。 使用场景 大型分布式系统尤其是需要跨服务共享缓存的场景。对数据持久化有要求的缓存应用Redis支持数据持久化到硬盘。实现复杂的缓存策略如排行榜、计数器、发布/订阅等高级特性。高并发环境下的会话缓存、全页缓存、消息队列等。 Spring Cache 概述 Spring Cache是Spring框架提供的一个抽象缓存管理接口它允许开发者在应用中轻松地集成各种缓存技术如Ehcache、Redis等。Spring Cache通过AOP面向切面编程技术对标注了缓存注解如Cacheable, CachePut, CacheEvict的方法进行拦截从而实现缓存逻辑的透明化。 使用场景 任何基于Spring的项目特别是需要缓存策略来提升性能的应用。当需要灵活切换缓存实现时Spring Cache的抽象层提供了良好的兼容性和可替换性。在需要细粒度缓存控制如缓存过期策略、条件缓存、缓存清除等复杂场景中。结合Spring Boot快速搭建带有缓存功能的微服务架构。 MyBatis缓存 概述 MyBatis提供了两层缓存机制一级缓存本地缓存和二级缓存全局缓存。一级缓存是在SqlSession级别生命周期短暂主要用于同一个SqlSession内的查询优化。二级缓存则是跨SqlSession的位于命名空间级别可以配置成跨节点的分布式缓存提高数据库查询的复用率。 使用场景 一级缓存适用于短周期、高频率的查询操作减少同一SqlSession内的重复查询。二级缓存适用于多用户、多会话场景特别是在查询结果相对稳定、不经常变更的数据表上可以大幅度提高应用性能。当应用需要跨服务共享查询结果或在分布式环境下减少数据库负载时配置分布式二级缓存如使用Redis作为二级缓存尤为重要。 小结 总结来说Ehcache和Redis在缓存实现上有明显区别前者更适合单机或小规模环境后者则在分布式和大数据量场景下表现更优。Spring Cache作为Spring生态的一部分提供了统一的缓存抽象使得在Spring应用中集成缓存变得更加便捷。而MyBatis的缓存机制作为ORM框架的内置功能主要关注数据库查询结果的高效复用提升数据访问性能。在实际应用中根据具体需求和场景可以选择合适的缓存策略和技术组合。 对于本次Java缓存技术的收集和报告实实在在让我更加深刻的理解和深入了解了缓存。我也相信一定会对自己的编程生涯产生深远影响。还有很多种Java缓存技术并没有进行更加深入的了解日后我也会结合日常工作或新场景下对其他缓存进行学习争取能够将大部分缓存技术得心应手应用在项目中 手写本地缓存Demo Demo 本DemoLocalCache类设计实现了丰富的功能包括自动过期、容量限制、并发控制以及定期清理机制。 Slf4j public class LocalCacheK, V implements MapK, V {/*** 缓存默认最大容量: 4096/private static final int DEFAULT_MAX_CAPACITY 1 12;/** 定期全面清理过期数据的间隔时间: 1分钟*/private static final int ONE_MINUTE 60 * 1000;/*** 最大容量/private final int maxCapacity;/** 默认过期时间(毫秒), -1 表示不过期./private long defaultTtlTime -1;/** 缓存使用频率/private final LinkedListK cacheList new LinkedList();/** 缓存对象/private final ConcurrentHashMapK, CacheValueV cacheMap;public LocalCache() {this(DEFAULT_MAX_CAPACITY);}/** param maxCapacity 最大容量/public LocalCache(int maxCapacity) {this(maxCapacity, -1L);}/** param maxCapacity 最大容量* param defaultTtlTime 默认过期时间(毫秒), -1 表示不过期./public LocalCache(int maxCapacity, long defaultTtlTime) {this.maxCapacity maxCapacity;if (defaultTtlTime 0) {this.defaultTtlTime defaultTtlTime;}cacheMap new ConcurrentHashMap();Thread thread new Thread(this::cleanUpExpired, clear_thread);thread.setDaemon(true);thread.start();}/** 返回缓存中有效键值的数量/Overridepublic int size() {return entrySet().size();}/** 返回缓存中是否有 有效键值数据/Overridepublic boolean isEmpty() {return entrySet().isEmpty();}/** 返回缓存中是否有 有效的此键/Overridepublic boolean containsKey(Object key) {CacheValueV cacheValue cacheMap.get(key);if (cacheValue ! null) {if (cacheValue.ttlTime -1 || cacheValue.ttlTime System.currentTimeMillis()) {return true;}remove(key);return false;}return false;}/** 返回缓存中是否有 有效的此值/Overridepublic boolean containsValue(Object value) {long now System.currentTimeMillis();for (EntryK, CacheValueV entry : cacheMap.entrySet()) {CacheValueV cacheValue entry.getValue();if (Objects.equals(cacheValue.value, value)) {if (cacheValue.ttlTime now) {return true;}remove(entry.getKey());return false;}}return false;}/** 从缓存中获取有效的值/Overridepublic V get(Object key) {CacheValueV cacheValue cacheMap.get(key);if (cacheValue ! null) {cacheList.remove(key);if (cacheValue.ttlTime System.currentTimeMillis()) {cacheList.addLast((K) key);return cacheValue.value;}cacheMap.remove(key);}return null;}/** 从缓存中获取有效的值, 如果不存在, 则缓存默认值, 并返回** param key 键* param defaultV 默认值/public V get(K key, V defaultV) {return get(key, defaultV, defaultTtlTime);}/** 从缓存中获取有效的值, 如果不存在, 则缓存默认值, 并返回** param key 键* param defaultV 默认值* param ttlTime 缓存默认值的有效时间(毫秒)/public V get(K key, V defaultV, long ttlTime) {V v get(key);if (v ! null) {return v;}put(key, defaultV, ttlTime);return defaultV;}/** 从缓存中获取有效的值, 如果不存在, 则缓存方法的返回值, 并返回** param key 键* param function 提供值的方法/public V get(K key, FunctionK, V function) {return get(key, function, defaultTtlTime);}/** 如果不存在, 则缓存方法的返回值, 并返回** param key 键* param function 提供值的方法* param ttlTime 缓存方法返回值的有效时间(毫秒)/public V get(K key, FunctionK, V function, long ttlTime) {V v get(key);if (v ! null) {return v;}V value function.apply(key);put(key, value, ttlTime);return value;}/** 缓存数据/Overridepublic V put(K key, V value) {return put(key, value, defaultTtlTime);}/** 缓存数据** param key 键* param value 值* param ttlTime 过期时间(毫秒), -1 表示不过期./public V put(K key, V value, long ttlTime) {if (ttlTime 0) {return null;} else if (ttlTime 0) {ttlTime -1L;} else {ttlTime System.currentTimeMillis() ttlTime;}if (cacheMap.containsKey(key)) {remove(key);} else if (cacheList.size() maxCapacity) {cleanUpExpired();if (cacheList.size() maxCapacity) {synchronized (this) {cacheMap.remove(cacheList.removeFirst());}}}CacheValueV cacheValue new CacheValue(value, ttlTime);synchronized (this) {cacheList.addLast(key);cacheMap.put(key, cacheValue);}return value;}/** 设置 key 缓存时间** param key 键* param ttlTime 过期时间(毫秒), -1 表示不过期./public boolean expire(K key, long ttlTime) {long now System.currentTimeMillis();if (ttlTime 0) {ttlTime -1L;} else {ttlTime now ttlTime;}CacheValueV cacheValue cacheMap.get(key);if (cacheValue ! null) {if (cacheValue.ttlTime -1 || cacheValue.ttlTime now) {cacheValue.ttlTime ttlTime;return true;}remove(key);return false;}return false;}/** 删除缓存值/Overridepublic synchronized V remove(Object key) {CacheValueV remove cacheMap.remove(key);if (remove ! null) {cacheList.remove(key);return remove.value;}return null;}/** 全部增加进缓存/Overridepublic synchronized void putAll(Map? extends K, ? extends V m) {if (!m.isEmpty()) {for (Entry? extends K, ? extends V entry : m.entrySet()) {this.put(entry.getKey(), entry.getValue());}}}/** 清空缓存/Overridepublic synchronized void clear() {cacheMap.clear();cacheList.clear();}/** 有效值的键/Overridepublic SetK keySet() {SetK set new HashSet();long now System.currentTimeMillis();if (!cacheMap.isEmpty()) {for (EntryK, CacheValueV entry : cacheMap.entrySet()) {if (entry.getValue().ttlTime now) {set.add(entry.getKey());} else {remove(entry.getKey());}}}return set;}/** 有效值/Overridepublic CollectionV values() {ArrayListV vs new ArrayList();long now System.currentTimeMillis();if (!cacheMap.isEmpty()) {for (EntryK, CacheValueV entry : cacheMap.entrySet()) {CacheValueV value entry.getValue();if (value.ttlTime now) {vs.add(value.value);} else {remove(entry.getKey());}}}return vs;}/** 有效值的键值对/Overridepublic SetEntryK, V entrySet() {SetEntryK, V set new LinkedHashSet();long now System.currentTimeMillis();for (EntryK, CacheValueV entry : cacheMap.entrySet()) {if (entry.getValue().ttlTime now) {EntryK, V cacheEntry new CacheEntry(entry.getKey(), entry.getValue().value);set.add(cacheEntry);} else {remove(entry.getKey());}}return set;}/** 定期全面清理过期数据./private void cleanUpExpired() {long last System.currentTimeMillis();while (true) {long now System.currentTimeMillis();while (now - ONE_MINUTE last) {Thread.yield();now System.currentTimeMillis();}for (EntryK, CacheValueV entry : cacheMap.entrySet()) {CacheValueV value entry.getValue();if (value.ttlTime now) {synchronized (this) {cacheMap.remove(entry.getKey());cacheList.remove(entry.getKey());}}}last now;}}AllArgsConstructorstatic class CacheValueV {/** 缓存对象/private V value;/** 缓存过期时间/private long ttlTime;}AllArgsConstructorstatic class CacheEntryK, V implements Map.EntryK, V {private K k;private V v;Overridepublic K getKey() {return k;}Overridepublic V getValue() {return v;}Overridepublic V setValue(V value) {return this.v value;}}}主要功能解释 获取缓存数据 /** 从缓存中获取指定键对应的值同时进行缓存项的有效性检查和列表维护。* p* 方法执行流程如下* 1. 尝试从缓存映射cacheMap中根据键获取对应的CacheValue对象。* 2. 如果CacheValue不为null说明存在该缓存项* a. 从使用频率列表cacheList中移除该键模拟最近最少使用LRU策略的更新。* b. 检查CacheValue的过期时间ttlTime是否大于当前时间确认缓存项是否已过期。* - 如果未过期将该键重新添加到cacheList的末尾表示最近被访问然后返回缓存的值。* - 如果已过期从缓存映射中移除该过期项。* 3. 如果CacheValue为null说明请求的键在缓存中不存在直接返回null。** param key 缓存项的键。* return 缓存中的值如果键不存在或已过期则返回null。/Overridepublic V get(Object key) {// 获取缓存中的值及过期信息CacheValueV cacheValue cacheMap.get(key);// 检查缓存项是否存在if (cacheValue ! null) {// 移除并重新排序模拟LRU更新cacheList.remove(key);// 检查是否过期if (cacheValue.ttlTime System.currentTimeMillis()) {// 未过期更新使用频率列表并返回值cacheList.addLast((K) key);return cacheValue.value;} // 已过期清理过期缓存else {cacheMap.remove(key);}}// 缓存项不存在或已过期返回nullreturn null;}录入缓存数据 /** 向缓存中添加或更新一个键值对并设定过期时间。* p* 此方法处理了以下逻辑* 1. 验证过期时间ttlTime确保其为有效值。若ttlTime为0则不执行任何操作并直接返回null。* 若ttlTime小于0则将其视为不过期ttlTime设为-1。否则将其转换为绝对过期时间当前时间加上ttlTime。* 2. 检查键是否已存在于缓存中。如果存在则先移除旧的缓存项。* 3. 检查缓存是否已达到最大容量。如果是先调用cleanUpExpired()方法尝试清理过期项。* 如果清理后仍然满载则同步移除最不常用的项cacheList的第一个元素以腾出空间。* 4. 使用提供的键、值和过期时间创建一个新的CacheValue对象。* 5. 在同步块中执行实际的添加操作以确保线程安全* a. 将键添加到使用频率列表cacheList的末尾表示此键刚被使用。* b. 将新的CacheValue对象放入缓存映射cacheMap中。* 6. 最后返回缓存的值。** param key 缓存项的键。* param value 缓存项的值。* param ttlTime 缓存项的过期时间毫秒-1 表示不过期0 表示不执行插入操作。* return 被缓存的值如果ttlTime为0则返回null。*/public V put(K key, V value, long ttlTime) {// 校正过期时间ttlTime确保其为有效值if (ttlTime 0) {return null;} else if (ttlTime 0) {ttlTime -1L;} else {ttlTime System.currentTimeMillis() ttlTime;}// 如果键已存在先移除旧的缓存项if (cacheMap.containsKey(key)) {remove(key);}// 检查并维护缓存的最大容量if (cacheList.size() maxCapacity) {cleanUpExpired(); // 尝试清理过期项if (cacheList.size() maxCapacity) {synchronized (this) {// 如果清理后仍满移除最不常用项cacheMap.remove(cacheList.removeFirst());}}}// 准备新的CacheValue对象CacheValueV cacheValue new CacheValue(value, ttlTime);// 同步添加新缓存项到map和listsynchronized (this) {cacheList.addLast(key);cacheMap.put(key, cacheValue);}// 返回缓存的值return value;}使用实例 位置 心理健康产品模块的心理健康控制器中 引入本地缓存 接口中使用查询心理健康产品列表接口/list 第一次调用本接口可以看到并没有命中缓存查询数据库后存入本地缓存过期时间设置为10000毫秒并进行数据返回 成功返回数据 第二次调用本接口成功命中缓存直接进行数据返回
参考文献 java缓存技术实例_springboot的缓存技术的实现干货|java缓存技术详解JAVA几种缓存技术介绍说明【java缓存、redis缓存、guava缓存】java中实现缓存的几种方式Java 缓存Java本地高性能缓存的几种实现方式Ehcache使用教程一文通透讲解Redis高级特性多线程/持久化/淘汰机制等统统搞定Redis八大特性Spring cache 缓存介绍Spring Cache的介绍以及使用方法、常用注解Spring Cache的基本使用与分析「Mybatis系列」Mybatis缓存MyBatis缓存功能原理及实例解析Mybatis缓存机制详解来5W1H分析法帮你系统掌握缓存缓存是什么 什么是缓存什么是缓存Ehcache注解核心逻辑源码学习Redis详细讲解redis原理redis安装redis配置redis使用redis命令深度剖析Redis九种数据结构实现原理建议收藏Redis 系列九Redis 集合底层实现原理Spring cache原理详解分享给大家【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache抽象详解的核心原理探索MyBatis的二级缓存原理是什么【Mybatis源码分析】SqlSessionFactory二级缓存原理分析Mybatis源码剖析二级缓存原理