java中如何实现本地缓存?

在高性能服务架构设计中 , 缓存是不可或缺的环节 。在实际项目中,我们通常会将一些热点数据存储在redis或Memcached等缓存中间件中,只有在缓存访问未命中时才查询数据库 。
在提高访问速度的同时,还可以减轻数据库的压力 。
为什么要使用本地缓存?随着不断的发展,这个架构也得到了完善 。在某些场景下,仅仅使用Redis类的远程缓存可能还不够 。需要进一步与本地缓存配合使用,比如Guava或者Caffeine,从而再次提高程序的响应速度和服务性能 。
由此,形成了以本地缓存作为一级缓存、远程缓存作为二级缓存的二级缓存架构 。
总结:

  1. 本地缓存基于本地环境的内存,访问速度非常快 。对于一些变化频率不高、实时性要求不高的数据 , 可以放在本地缓存中 , 以提高访问速度 。
  2. 使用本地缓存可以减少与Redis类的远程缓存的数据交互 , 减少网络I/O开销,减少这个过程中网络通信的耗时 。
本地存储的基本功能
  • 它可以存储、读取和写入 。
  • 原子操作(线程安全),例如ConcurrentHashMap 。
  • 可以设置缓存的最大限制 。
  • 超过最大限制有相应的淘汰策略,如LRU、LFU 。
  • 统计监控 。
方案选择
1.使用ConcurrentHashMap 。缓存的本质是KV存储在内存中的数据结构,对应JDK中的线程安全ConcurrentHashMap,但是要实现缓存,需要考虑消除、最大限制、消除缓存过期时间等功能 。
优点ConcurrentHashMap是实现简单,不需要引入第三方包 , 所以比较适合一些简单的业务场景 。
缺点是如果需要更多的功能 , 需要定制开发,成本会比较高,稳定性和可靠性难以保证 。
对于更复杂的场景,建议使用相对稳定的开源工具 。
2. 使用Guava缓存Guava是google团队开源的一个JAVA核心增强库 。它包括集合、并发原语、缓存、IO、反射和其他工具箱 。性能和稳定性有保证,应用广泛 。
  • Guava Cache 支持许多功能:
  • 支持最大容量限制 。
  • 支持两种过期删除策略 。
  • 支持简单的统计功能 。它是基于LRU算法实现的 。
示例代码如下
<dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>31.1-jre</version></dependency>@Slf4jpublic class GuavaCacheTest {public static void mAIn(String[] args) throws ExecutionException {Cache<String, String> cache = CacheBuilder.newBuilder().initialCapacity(5)// 初始容量.maximumSize(10)// 缓存的最大数量,超过该数量将被淘汰.expireAfterWrite(60, TimeUnit.SECONDS) // 过期时间.build();String orderId = String.valueOf(123456789);String orderInfo = cache.get(orderId, () -> getInfo(orderId));log.info("orderInfo = {}", orderInfo);}private static String getInfo(String orderId) {String info = "";// 首先查redislog.info("get data from redis");// redis不存在 查dblog.info("get data from MySQL");info = String.format("{orderId=%s}", orderId);return info;}}3. 使用EncacheEncache是?一个纯Java进程内缓存框架,快速且精简 。它是 Hibernate 中默认的 CacheProvider 。与Caffeine和Guava Cache相比 , Encache功能更丰富 , 可扩展性更强 。支持LRU、LFU、FIFO等多种缓存淘汰算法 。
缓存支持三种类型:堆内存储、堆外存储、磁盘存储(支持持久化) 。
支持多种集群方案,解决数据共享问题 。
使用方法如下:
<dependency><dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId><version>3.9.7</version></dependency>@Slf4jpublic class EhcacheTest {private static final String ORDER_CACHE = "orderCache";public static void main(String[] args) {CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()// 创建实例.withCache(ORDER_CACHE, CacheConfigurationBuilder// 声明一个容量为20的堆内缓存.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(20))).build(true);// 得到缓存实例Cache<String, String> cache = cacheManager.getCache(ORDER_CACHE, String.class, String.class);String orderId = String.valueOf(123456789);String orderInfo = cache.get(orderId);if (StrUtil.isBlank(orderInfo)) {orderInfo = getInfo(orderId);cache.put(orderId, orderInfo);}log.info("orderInfo = {}", orderInfo);}private static String getInfo(String orderId) {String info = "";// 首先从redis查log.info("get data from redis");// 不存在 查dblog.info("get data from mysql");info = String.format("{orderId=%s}", orderId);return info;}}


推荐阅读