嘉兴市南湖区建设街道网站,电商网站的制作,网站备案 换空间,互联网推广方法目录
摘要
1. Java缓存技术概述
1.1定义
1.2 优势
1.3 应用场景
2. Java中的内置缓存实现
2.1 通过通过HashMap和ConcurrentHashMap实现缓存
3. Java缓存框架
3.1 Redis
3.1.1 redis的简介
3.1.4 Redis的工作原理
3.1.5 总结
3.2 Ehcache
3.2.1 Eh…目录
摘要
1. Java缓存技术概述
1.1定义
1.2 优势
1.3 应用场景
2. Java中的内置缓存实现
2.1 通过通过HashMap和ConcurrentHashMap实现缓存
3. Java缓存框架
3.1 Redis
3.1.1 redis的简介
3.1.4 Redis的工作原理
3.1.5 总结
3.2 Ehcache
3.2.1 Ehcache的简介
3.2.2 Ehcache的简单使用
3.2.3 数据的持久化
3.2.4 总结
3.3 Caffeine
3.3.1 Caffeine的简介
3.3.2 Caffeine的简单使用
3.3.3 caffeine总结
4. 报告总结 摘要
缓存作为提升系统性能的关键策略在Java编程环境中扮演着重要角色。本报告聚焦于Java缓存技术深入探讨了其基本概念、应用场景及多样化实现。
报告首先明确了缓存的定义并强调了其在读操作频繁、数据计算复杂及数据更新不频繁等场景下的显著优势。在Java中缓存的实现方式多样包括利用本地数据结构实现的本地缓存、借助Java标准库提供的缓存功能。
此外报告还介绍了几个广受欢迎的Java缓存框架如Redis高性能分布式内存缓存、Ehcache开源Java缓存框架支持分布式缓存以及Caffeine高性能Java内存缓存库专注于快速响应和高效内存利用。这些框架各具特色为开发者提供了丰富的选择空间。
通过本报告的阐述读者将全面理解Java缓存技术的核心概念、应用场景及实现方式为优化系统性能提供有力的理论与实践支持。 Java缓存技术概述
1.1定义
缓存是数据访问的加速器它为数据提供了一个快速的临时栖息地以减少数据检索的时间消耗。在Java编程中缓存技术是提升数据处理速度和系统性能的关键工具。它利用内存这一高速存储介质保存数据的副本以便快速访问避免了对较慢存储设备如硬盘的频繁访问。
对于需要频繁访问相同数据的应用Java缓存技术显得尤为重要。它像一座桥梁连接了对快速数据的需求和较慢的数据存储减少了对数据源的直接访问提高了数据处理的效率。
简而言之Java中的缓存技术是一种高效的数据处理策略它利用内存的高速访问特性为应用程序提供了快速的数据检索服务从而提高了系统的响应速度和用户体验。这种技术不仅减轻了系统的资源负担还为数据的快速处理和有效利用提供了新的途径。
1.2 优势
1. 提高响应速度通过在快速的存储介质中保存数据副本缓存减少了数据检索时间从而加快了应用程序的响应速度。
2. 减轻后端负载缓存减少了对数据库或其他数据源的访问次数从而减轻了后端系统的负担。
3. 提升用户体验更快的数据访问速度和更流畅的交互显著提升了用户的体验。
4. 降低成本缓存减少了对昂贵资源如数据库查询的依赖有助于降低运营成本。
1.3 应用场景
1. 数据库缓存
在高并发访问的系统中数据库压力可能非常大。为了缓解数据库压力可以使用缓存来存储常用的查询结果。当再次访问这些数据时可以直接从缓存中读取而无需查询数据库从而显著提高系统的响应速度。缓存还可以用于存储数据库中的临时数据如会话信息、用户登录状态等以避免频繁访问数据库。
Web应用缓存
在Web应用中缓存可以用于存储静态资源如图片、CSS、JavaScript等和动态内容如网页、API响应等。通过缓存静态资源可以减少对服务器的请求次数降低服务器负载提高网页加载速度。对于动态内容可以使用缓存来存储重复的查询结果或计算结果以减少数据库查询和计算的时间。 分布式系统缓存
在分布式系统中缓存可以用于实现分布式锁、分布式会话共享等功能。通过使用分布式锁可以确保多个进程或线程在访问共享资源时的同步性。分布式会话共享则允许多个服务器共享用户的会话信息从而提供一致的用户体验。
4. CDN缓存
内容分发网络CDN中的缓存用于存储和分发静态内容如图片、视频、音频等。通过将内容缓存在CDN节点上可以缩短用户访问内容的距离和时间提高内容的加载速度和可用性。
5. 应用层缓存
在应用层缓存可以用于存储应用程序的临时数据、计算结果或中间状态。这有助于减少应用程序对后端服务的请求次数提高应用程序的响应速度和性能。
硬件缓存
在计算机硬件中缓存如CPU缓存、硬盘缓存等用于加速数据的访问速度。通过将常用数据存储在离处理器更近的缓存中可以减少对慢速存储设备的访问次数提高系统的整体性能。 Java中的内置缓存实现
2.1 通过通过HashMap和ConcurrentHashMap实现缓存
Java 提供了多种基础数据结构其中 HashMap 和 ConcurrentHashMap 特别适合用于构建内存缓存。HashMap 是一个高效的哈希表实现而 ConcurrentHashMap 则在此基础上进一步优化专为多线程环境设计能够提供卓越的并发访问性能。
然而这些数据结构有一个共同的局限性它们不支持数据持久化。因此当应用程序重启时所有存储在其中的缓存数据都会丢失。
在特定场景下如果需要对某些操作进行更精细的控制以确保其原子性那么 ConcurrentHashMap 可能无法完全满足需求。此时开发者可能需要考虑使用更复杂的原子操作或者将 ConcurrentHashMap 与其他并发控制工具如锁机制结合使用以实现所需的数据一致性和完整性。
以下是简单的代码实现
public class ConcurrentMyCache {private MapString, Object cache new ConcurrentHashMap();public void put(String key, Object value) {cache.put(key, value);}public Object get(String key) {return cache.get(key);}public void remove(String key) {cache.remove(key);}public void clear() {cache.clear();}public int size() {return cache.size();}
}public class MyCache {private MapString, Object cache new HashMap();// 向缓存中放入键值对public void put(String key, Object value) {cache.put(key, value);}// 从缓存中获取值public Object get(String key) {return cache.get(key);}// 从缓存中移除键值对public void remove(String key) {cache.remove(key);}// 清空缓存public void clear() {cache.clear();}// 获取缓存大小public int size() {return cache.size();}}SpringBootTestclass CacheTest {Testvoid testCache(){ConcurrentMyCache cachenew ConcurrentMyCache();cache.put(key1,zhangsan);cache.put(key2,lisi);System.out.println(key1: cache.get(key1));System.out.println(key2: cache.get(key2));MyCache cache1new MyCache();cache1.put(key3,wangwu);cache1.put(key4,lht);System.out.println(key3: cache1.get(key3));System.out.println(key4: cache1.get(key4));}} Java缓存框架
3.1 Redis
3.1.1 redis的简介
Redis是一个完全开源免费的高性能NOSQL的key-value数据库。它遵守BSD协议使用ANSI C语言编写并支持网络和持久化。Redis拥有极高的性能每秒可以进行11万次的读取操作和8.1万次的写入操作。它支持丰富的数据类型包括String、Hash、List、Set和Ordered Set并且所有的操作都是原子性的。此外Redis还提供了多种特性如发布/订阅、通知、key过期等。Redis采用自己实现的分离器来实现高速的读写操作效率非常高。Redis是一个简单、高效、分布式、基于内存的缓存工具通过网络连接提供Key-Value式的缓存服务。
Redis可以通过配置文件设置密码参数这样客户端连接到Redis服务就需要密码验证从而提高Redis服务的安全性。
3.1.2 redis的简单使用
Datapublic class User implements Serializable {private Integer id;private String username;private String password;}
通过自动装配redis里面的RedisTemplate这个类里面的相关配置并且封装方法方便后期使用。
/*** spring redis 工具类***/SuppressWarnings(value { unchecked, rawtypes })Componentpublic class RedisCache{Autowiredpublic RedisTemplate redisTemplate;/*** 缓存基本的对象Integer、String、实体类等** param key 缓存的键值* param value 缓存的值*/public T void setCacheObject(final String key, final T value){redisTemplate.opsForValue().set(key, value);}/*** 获得缓存的基本对象。** param key 缓存键值* return 缓存键值对应的数据*/public T T getCacheObject(final String key){ValueOperationsString, T operation redisTemplate.opsForValue();return operation.get(key);}} 3.1.3 redis的主要特征
- 键值key-value型value支持多种不同数据结构功能丰富
- 单线程每个命令具备原子性
- 低延迟速度快基于内存、IO多路复用、良好的编码。
- 支持数据持久化
- 支持主从集群、分片集群,
- 支持多语言客户端 3.1.4 Redis的工作原理
1. 内存存储数据完全存储在内存中提供快速的读写访问时间复杂度接近O(1)。
单线程架构采用单线程处理请求避免了多线程带来的上下文切换和锁竞争简化了并发控制提高了性能。非阻塞IO使用多路复用IO模型能够非阻塞地处理多个客户端请求提高了并发处理能力。Lua脚本执行支持在Lua脚本中执行命令允许用户执行复杂的逻辑和操作增加了操作的灵活性。 3.1.5 总结
Redis是一种高效的内存键值存储系统广泛用于缓存管理、会话保持及实时数据处理等多种场景。它的优势体现在极快的读写速度、对数据结构的多样化支持、具备持久化功能包括RDB快照和AOF日志、支持分布式部署以及提供强大的原子操作特性。 3.2 Ehcache
3.2.1 Ehcache的简介
EhCache是一个高效的纯Java进程内缓存框架支持单机和分布式缓存适用于需要快速数据访问的场景。它具备简单易用、快速访问、多种缓存策略如堆缓存、磁盘缓存、集群缓存等优点。EhCache的缓存数据有两级一级是内存二级是磁盘当内存不足时数据可以自动溢出到磁盘从而解决了容量问题。此外EhCache还支持缓存数据在虚拟机重启时写入磁盘以及通过RMI、可插入API等方式进行分布式缓存。
在Spring Boot中EhCache可以通过配置文件和Bean注入来使用提供了灵活的缓存策略配置如缓存对象的最大数量、对象是否永不过期、空闲时间和存活时间等。同时EhCache还提供了缓存和缓存管理器的侦听接口支持多缓存管理器实例以及一个实例的多个缓存区域。
虽然EhCache在非集群环境下可能导致敏感数据更新延迟但它非常适合高QPS场景和小量数据缓存需求。使用EhCache时建议设置较短的过期时间以保证数据的及时更新。 3.2.2 Ehcache的简单使用
首先需要初始化一个缓存管理器CacheManager利用它来创建新的缓存或者访问已有的缓存。之后可以在这些缓存中存储数据或者从缓存中检索数据。
Testpublic void test() {// 初始化 CacheManagerCacheManager cacheManager CacheManagerBuilder.newCacheManagerBuilder()// 一个CacheManager可以管理多个Cache.withCache(ehcacheDemo,CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class,String.class,// heap相当于设置数据在堆内存中存储的 个数 或者 大小ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10, MemoryUnit.MB).build()).build()).build(true);// 如果 CacheManagerBuilder.build(); 如果没有传参数,需要手动调用init()// cacheManager.init();// 基于 CacheManager 获取 Cache对象CacheString, String ehCache cacheManager.getCache(ehcacheDemo, String.class, String.class);// 放去缓存ehCache.put(ehcache, hello ehcache);// 取System.out.println(ehCache.get(ehcache));} EhCache 提供了非常灵活和强大的配置选项这使得它能够适应各种不同的缓存需求。
config xmlnshttp://www.ehcache.org/v3xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd!--定义缓存--cache aliassquaredNumberuses-templatemyTemplate!--缓存使用的缓存模板的名称--key-typejava.lang.Integer/key-typevalue-typejava.lang.Integer/value-typeheap unitentries10/heap/cache!--定义缓存模板--cache-template namemyTemplateexpiryttl unitseconds60/ttl!--缓存项的过期策略60秒过期--/expiry/cache-template
/config
3.2.3 数据的持久化
Ehcache还可以将数据落地本地磁盘这样的话当服务重启后依然会从磁盘反序列化数据到内存中实现数据的持久化代码如下
Test
public void test1() {// 声明存储位置String path D:\\ehcache;// 初始化 CacheManagerCacheManager cacheManager CacheManagerBuilder.newCacheManagerBuilder()// 设置存储位置.with(CacheManagerBuilder.persistence(path))// 一个CacheManager可以管理多个Cache.withCache(ehcacheDemo,CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class,String.class,// heap相当于设置数据在堆内存中存储的 个数 或者 大小ResourcePoolsBuilder.newResourcePoolsBuilder()// 堆内内存.heap(10, MemoryUnit.MB)// 堆外内存// off-heap大小必须 大于 heap 设置的内存大小.offheap(15,MemoryUnit.MB)// 磁盘存储,记得添加true,才能正常持久化,并且序列号以及反序列化// disk大小必须 大于 off-heap 设置的内存.disk(20,MemoryUnit.MB,true)).build()).build(true);// 如果 CacheManagerBuilder.build(); 如果没有传参数,需要手动调用init()// cacheManager.init();// 基于 CacheManager 获取 Cache对象CacheString, String ehCache cacheManager.getCache(ehcacheDemo, String.class, String.class);// 放去缓存ehCache.put(ehcache, hello ehcache);// 取System.out.println(ehCache.get(ehcache));// 保证数据正常持久化不丢失,记得 close()cacheManager.close();
}
3.2.4 总结
EhCache 是一个纯Java的进程内缓存框架具有快速、精干等特点。EhCache支持单机缓存和分布式缓存分布式可以理解为缓存数据的共享这就导致内存缓存数据量偏小。ehcache缓存非常存储和读取非常快。 3.3 Caffeine
3.3.1 Caffeine的简介
Caffeine是一个高性能的Java缓存库它通过精细的数据结构和高效的内存管理确保了在高并发环境下的快速访问。它提供了丰富的配置选项包括缓存的最大容量、数据失效策略和自动刷新等以满足不同应用程序的需求。Caffeine支持同步和异步两种数据加载方式同步加载会阻塞主线程直到数据加载完成而异步加载则允许主线程在数据加载时继续执行从而提高系统的并发性。此外Caffeine还支持多种过期策略如基于时间或访问频率以及注册监听器来监控缓存的变更事件为应用程序提供了更细粒度的控制和监视。总的来说Caffeine是一个功能强大、易于使用的缓存解决方案非常适合需要高性能缓存的Java应用程序。 3.3.2 Caffeine的简单使用
缓存加载策略
Cache手动创建
最普通的一种缓存无需指定加载方式需要手动调用put()进行加载。需要注意的是put()方法对于已存在的key将进行覆盖这点和Map的表现是一致的。在获取缓存值时如果想要在缓存值不存在时原子地将值写入缓存则可以调用get(key, k - value)方法该方法将避免写入竞争。调用invalidate()方法将手动移除缓存。
在多线程情况下当使用get(key, k - value)时如果有另一个线程同时调用本方法进行竞争则后一线程会被阻塞直到前一线程更新缓存完成而若另一线程调用getIfPresent()方法则会立即返回null不会被阻塞。 Testpublic void test1() {CacheObject, Object cache Caffeine.newBuilder()//初始数量.initialCapacity(10)//最大条数.maximumSize(10)//expireAfterWrite和expireAfterAccess同时存在时以expireAfterWrite为准//最后一次写操作后经过指定时间过期.expireAfterWrite(1, TimeUnit.SECONDS)//最后一次读或写操作后经过指定时间过期.expireAfterAccess(1, TimeUnit.SECONDS)//监听缓存被移除.removalListener((key, val, removalCause) - { })//记录命中.recordStats().build();cache.put(1,张三);//张三System.out.println(cache.getIfPresent(1));//存储的是默认值System.out.println(cache.get(2,o - 默认值));}
Loading Cache自动创建
LoadingCache是一种自动加载的缓存。其和普通缓存不同的地方在于当缓存不存在/缓存已过期时若调用get()方法则会自动调用CacheLoader.load()方法加载最新值。调用getAll()方法将遍历所有的key调用get()除非实现了CacheLoader.loadAll()方法。使用LoadingCache时需要指定CacheLoader并实现其中的load()方法供缓存缺失时自动加载。
在多线程情况下当两个线程同时调用get()则后一线程将被阻塞直至前一线程更新缓存完成。 //Loading Cache自动创建Testpublic void test2() {LoadingCacheString, String loadingCache Caffeine.newBuilder()//创建缓存或者最近一次更新缓存后经过指定时间间隔刷新缓存refreshAfterWrite仅支持LoadingCache.refreshAfterWrite(10, TimeUnit.SECONDS).expireAfterWrite(10, TimeUnit.SECONDS).expireAfterAccess(10, TimeUnit.SECONDS).maximumSize(10)//根据key查询数据库里面的值这里是个lamba表达式.build(key - new Date().toString());loadingCache.put(1,张三);//张三System.out.println(loadingCache.getIfPresent(1));//存储的是默认值System.out.println(loadingCache.get(2,o - 默认值));}
Async Cache异步获取
AsyncCache是Cache的一个变体其响应结果均为CompletableFuture通过这种方式AsyncCache对异步编程模式进行了适配。默认情况下缓存计算使用ForkJoinPool.commonPool()作为线程池如果想要指定线程池则可以覆盖并实现Caffeine.executor(Executor)方法。synchronous()提供了阻塞直到异步缓存生成完毕的能力它将以Cache进行返回。
在多线程情况下当两个线程同时调用get(key, k - value)则会返回同一个CompletableFuture对象。由于返回结果本身不进行阻塞可以根据业务设计自行选择阻塞等待或者非阻塞。 //Async Cache异步获取Testpublic void test3() {AsyncLoadingCacheString, String asyncLoadingCache Caffeine.newBuilder().refreshAfterWrite(1, TimeUnit.SECONDS).expireAfterWrite(1, TimeUnit.SECONDS).expireAfterAccess(1, TimeUnit.SECONDS).maximumSize(10).buildAsync(key - {try {// 模拟数据库查询延迟Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}// 模拟从数据库获取的数据return new Date().toString();});// 获取缓存中的值CompletableFutureString future asyncLoadingCache.get(1);// 当获取完成时打印结果future.thenAccept(System.out::println);// 等待一段时间确保异步加载完成try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}} 驱逐策略
驱逐策略在创建缓存的时候进行指定。常用的有基于容量的驱逐和基于时间的驱逐。基于容量的驱逐需要指定缓存容量的最大值当缓存容量达到最大时Caffeine将使用LRU策略对缓存进行淘汰基于时间的驱逐策略如字面意思可以设置在最后访问/写入一个缓存经过指定时间后自动进行淘汰。
基于容量的驱逐LRU
基于容量的驱逐是指缓存在达到一定的容量后会按照最近最少使用Least Recently Used的策略自动淘汰掉一些缓存项。这通常用于限制缓存占用的内存空间。 //基于容量驱逐Testpublic void maximumSizeTest() throws InterruptedException {CacheInteger, Integer cache Caffeine.newBuilder()//超过10个后会使用W-TinyLFU算法进行淘汰.maximumSize(10).evictionListener((key, val, removalCause) - {System.out.println(淘汰缓存key: key val: val);}).build();for (int i 1; i 20; i) {cache.put(i, i);}Thread.sleep(500);//缓存淘汰是异步的// 打印还没被淘汰的缓存System.out.println(cache.asMap());}
基于时间的驱逐
基于时间的驱逐是指缓存项在一定时间后自动被淘汰。可以基于最后一次写入时间或者最后一次访问时间来淘汰缓存项。 /*** 访问后到期每次访问都会重置时间也就是说如果一直被访问就不会被淘汰*/Testpublic void expireAfterAccessTest() throws InterruptedException {CacheInteger, Integer cache Caffeine.newBuilder().expireAfterAccess(1, TimeUnit.SECONDS)//可以指定调度程序来及时删除过期缓存项而不是等待Caffeine触发定期维护//若不设置scheduler则缓存会在下一次调用get的时候才会被动删除.scheduler(Scheduler.systemScheduler()).evictionListener((key, val, removalCause) - {log.info(淘汰缓存key:{} val:{}, key, val);}).build();cache.put(1, 2);System.out.println(cache.getIfPresent(1));Thread.sleep(3000);System.out.println(cache.getIfPresent(1));//null
刷新机制
Caffeine 缓存库提供了灵活的刷新机制可以在缓存项即将过期时自动刷新数据以确保缓存数据的时效性。refreshAfterWrite()表示x秒后自动刷新缓存的策略可以配合淘汰策略使用注意的是刷新机制只支持LoadingCache和AsyncLoadingCache。
//刷新机制private static int NUM 0;Testpublic void refreshAfterWriteTest() throws InterruptedException {LoadingCacheInteger, Integer cache Caffeine.newBuilder().refreshAfterWrite(1, TimeUnit.SECONDS)//模拟获取数据每次获取就自增1.build(integer - NUM);//获取ID1的值由于缓存里还没有所以会自动放入缓存System.out.println(cache.get(1));// 1// 延迟2秒后理论上自动刷新缓存后取到的值是2// 但其实不是值还是1因为refreshAfterWrite并不是设置了n秒后重新获取就会自动刷新// 而是x秒后第二次调用getIfPresent的时候才会被动刷新Thread.sleep(2000);System.out.println(cache.getIfPresent(1));// 1//此时才会刷新缓存而第一次拿到的还是旧值System.out.println(cache.getIfPresent(1));// 2}
统计
Caffeine 缓存库提供了内置的统计功能可以帮助开发者监控和调优缓存性能。通过启用统计功能你可以收集关于缓存操作的详细信息例如命中率、未命中率、加载次数、加载时间等。 //统计Testpublic void requestCount(){LoadingCacheString, String cache Caffeine.newBuilder()//创建缓存或者最近一次更新缓存后经过指定时间间隔刷新缓存refreshAfterWrite仅支持LoadingCache.refreshAfterWrite(1, TimeUnit.SECONDS).expireAfterWrite(1, TimeUnit.SECONDS).expireAfterAccess(1, TimeUnit.SECONDS).maximumSize(10)//开启记录缓存命中率等信息.recordStats()//根据key查询数据库里面的值.build(key - {Thread.sleep(1000);return new Date().toString();});cache.put(1, shawn);cache.get(1);/** hitCount :命中的次数* missCount:未命中次数* requestCount:请求次数* hitRate:命中率* missRate:丢失率* loadSuccessCount:成功加载新值的次数* loadExceptionCount:失败加载新值的次数* totalLoadCount:总条数* loadExceptionRate:失败加载新值的比率* totalLoadTime:全部加载时间* evictionCount:丢失的条数*/System.out.println(cache.stats());
3.3.3 caffeine总结
Caffeine是一个高性能的Java缓存库它提供了丰富的配置选项和强大的缓存策略包括最近最少使用(LRU)、最近最不常用(LFU)、先进先出(FIFO)等。它支持自动刷新、定时失效、大小限制和异步加载确保了数据的时效性和缓存的高效性。Caffeine还内置了统计监控功能帮助开发者了解缓存性能并进行调优。其线程安全、易于集成和使用是提升Java应用性能的理想选择。 4. 报告总结
本报告深入探讨了Java缓存技术包括其定义、优势、应用场景及实现方式。缓存通过在内存中存储数据副本减少了对慢速存储设备的访问从而加速了数据检索提高了系统性能。Java提供了多种缓存实现如利用HashMap和ConcurrentHashMap构建本地缓存以及使用Redis、Ehcache和Caffeine等缓存框架。这些框架支持不同的缓存策略如堆缓存、磁盘缓存、集群缓存以及自动刷新和过期策略。Caffeine特别受到关注它是一个高性能的Java缓存库提供了丰富的配置选项包括定时失效、大小限制、同步和异步加载以及多种过期策略。Caffeine还内置了统计监控功能帮助开发者优化缓存性能。报告通过介绍这些技术和框架为读者提供了理论与实践相结合的缓存解决方案以优化系统性能。