springboot的原生cache_一个缓存使用案例:Spring Cache VS Caffeine 原生 API

最近在学习本地缓存发现,在 Spring 技术栈的开发中,既可以使用 Spring Cache 的注解形式操作缓存,也可用各种缓存方案的原生 API。那么是否 Spring 官方提供的就是最合适的方案呢?那么本文将通过一个案例来为你揭晓。

Spring Cache

Since version 3.1, the Spring Framework provides support for transparently adding caching to an existing Spring application. The caching abstraction allows consistent use of various caching solutions with minimal impact on the code.

Spring Cache 和 slf4j、jdbc 类似,是由 Spring Framwork 提供的一个缓存抽象层,可以接入各种缓存解决方案来进行使用,通过 Spring Cache 的集成,我们只需要通过一组注解来操作缓存就可以了。目前支持的有 Generic、JCache (JSR-107) 、EhCache 2.x、Hazelcast、Infinispan、Couchbase、Redis、Caffeine、Simple,几乎包含了主流的本地缓存方案。

其主要的原理就是向 Spring Context 中注入 Cache 和 CacheManager 这两个 bean,再通过 Spring Boot 的自动装配技术,会根据项目中的配置文件自动注入合适的 Cache 和 CacheManager 实现。

本地缓存方案

Java 技术栈中成熟的本地缓存方案已经有很多了,有大而全的 ehcache,也有后起之秀 Google Guava Cache。下面是常用的三大本地缓存方案的对比,引用自博客 如何优雅的设计和使用缓存?

项目

Ehcache

Guava Cache

Caffeine

读写性能

好,需要做淘汰操作

很好

淘汰算法

支持多种淘汰算法, LRU,LFU,FIFO

LRU,一般

W-TinyLFU, 很好

功能丰富程度

功能很丰富

功能很丰富,支持刷新和虚引用等

功能和 Guava Cache 类似

工具大小

很大,最新版本 1.4MB

是 Guava 工具类中的一个小部分,较小

一般,最新版本 644KB

是否持久化

是否支持集群

目前比较推荐的是 Caffeine,淘汰算法比较先进,并且得到 Spring Cache 的支持(新版的 Spring Cache 不再支持 Guava Cache)。下文的代码也是使用 Caffeine 的原生 API 的。

案例

使用过 Spring Cache 的人应该会发现,通过几个注解就能够轻松实现缓存的 CRUD 操作,并且替换其他的缓存方案不需要对代码进行改动吗,同时也不需要写例如下文的样板代码:

{

// 缓存命中

if(cache.getIfPresent(key) != null){

// todo

}else{

// 缓存未命中,IO 获取数据,结果存入缓存

Object value = repo.getFromDB(key);

cache.put(key,value);

}

}

那学到这里,我就产生了疑惑,既然 Spring 出了缓存的注解化开发,并且大量的博客也都在往 Spring Cache 上引,那还是否需要用原生 API 呢?毕竟在 Spring Data JPA 出现后,我们的确很少关注后端 ORM 框架,也不再直接使用 Hibernate 了。

当我实现了项目中的一个需求,这个问题好像就豁然开朗了。

其实需求很简单,原本在本地 HashMap 中维护的一个映射表,由于后期需要频繁改动而放到了数据库中。但由于数据量并不大且不配置映射表时,数据保持不变,因此既然在学习缓存,就想把它加进去。那么现在需要做的就是:

一个读取映射表全表的方法 aliasMap()。并缓存数据到 Caffeine。

一个支持映射记录 CRUD 操作的页面,且修改映射表时,更新缓存。

@Cacheable(value = "default", key = "#root.methodName")

@Override

public Map aliasMap() {

return getMapFromDB();

}

由于 Spring Cache 的注解一般是添加在类或者方法上的,换而言之,缓存的是方法返回的对象。显然,通过某个方法来触发另一个缓存中的对象的更新是行不通的。这样是否意味着 Spring Cache 无法实现了呢?仔细去看一下 Spring Cache 的原理,其实还是可行的。

Spring Cache 会向 Spring Context 中注入 Cache 和 CacheManager 这两个 bean,再通过 Spring Boot 的自动装配技术,根据项目中的配置文件自动注入合适的 Cache 和 CacheManager 实现。再看到 CaffeineCacheManager 的源码:

public class CaffeineCacheManager implements CacheManager {

private final ConcurrentMap cacheMap = new ConcurrentHashMap(16);

private boolean dynamic = true;

private Caffeine cacheBuilder = Caffeine.newBuilder();

@Nullable

private CacheLoader cacheLoader;

private boolean allowNullValues = true;

}

显然,缓存是存在 cacheMap 这样一个 ConcurrentHashMap 中,那只要我们能够手动去获取到这个 bean 的实例去操作它,那么这个需求就可以实现了,代码如下:

@Autowired

private CacheManager cacheManager;

@Cacheable(value = "default", key = "#root.methodName")

@Override

public Map aliasMap() {

return getMapFromDB();

}

private Map getMapFromDB() {

Map map = new HashMap<>();

List list = repository.findAll();

list.forEach(x -> map.put(x.getAlias(), x.getName()));

return map;

}

@Override

public PartAlias saveOrUpdateWithCache(PartAlias obj) {

PartAlias partAlias = repository.saveAndFlush(obj);

Cache cache = cacheManager.getCache("default");

cache.clear();

cache.put("aliasMap", getMapFromDB());

return partAlias;

}

经过测试,上面的代码是可行的。显然,遇到一些稍微复杂的需求,仅仅依靠 Spring Cache 的注解是远远不够的,我们需要自己去操作 cache 对象。如果使用原生 API 就非常简单了,能应对不同的需求。

What's More

上面的需求,Spring Cache 尚且还是能够处理的,但是如果要实现数据的自动加载和刷新呢?现在 Spring Cache 并不能够很好的支持。

spring:

cache:

type: caffeine

caffeine:

spec: maximumSize=1024

cache-names: cache1,cache2

上面的代码是用来配置 cache 的,结合上文 CaffeineCacheManager 的源码,我们可以知道,Spring Cache 的配置是全局的,也就是说例如最大条数、过期时间等参数是为全体缓存进行设置的,无法单独为某个缓存设置。而在 Caffeine 中用于数据加载和刷新的 CacheLoader 也是 CaffeineCacheManager 这个 bean 共有的,因此也就失去存在的意义,毕竟每个缓存的加载和数据刷新的方式是不可能相同的。

因此,在遇到复杂场景下, 还是得上原生 API 的,Spring Cache 就显得心有余而力不足了。笔者也写个一个工具类,可以全局使用缓存。

@Component

public class CaffeineCacheManager {

private final ConcurrentMap cacheMap = new ConcurrentHashMap<>(16);

/**

* 缓存创建

*

* @param cacheName

* @param cache

*/

public void createCache(String cacheName, Cache cache) {

cacheMap.put(cacheName, cache);

}

/**

* 缓存获取

*

* @param name

* @return

*/

public synchronized Cache getCache(String name) {

Cache cache = this.cacheMap.get(name);

if (cache == null) {

throw new IllegalArgumentException("No this cache.");

}

return cache;

}

@Autowired

private static CaffeineCacheManager manager;

public static void main(String[] args) {

manager.createCache("default", Caffeine.newBuilder()

.maximumSize(1024)

.build());

Cache cache = manager.getCache("default");

// TODO

}

}

当然,再来提一提,既然是 Spring 的套路,总是会给开发者留一条后路的,如果愿意折腾的,可以阅读 CacheManager 的代码,再根据自己需求重新实现,从而管理自己的 cache 实例。

总结

本文不是一篇介绍 Spring Cache 和 Caffeine 用法的文章(有需要可以阅读参考文献),而是在探讨 Spring Cache 和 Caffeine 的原生 API 的使用场景。显然,Spring 全家桶有时未必是最优的解决方案(有能力重写的另当别论了)!所以也希望网上有更多的博客可以 focus on 框架本身的使用,而不是千篇一律的各种集成到 Spring xxx。

附录

yaml 配置

initialCapacity: # 初始的缓存空间大小

maximumSize: # 缓存的最大条数

maximumWeight: # 缓存的最大权重

expireAfterAccess: # 最后一次写入或访问后经过固定时间过期

expireAfterWrite: # 最后一次写入后经过固定时间过期

refreshAfterWrite: # 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存

weakKeys: # 打开 key 的弱引用

weakValues: # 打开 value 的弱引用

softValues: # 打开 value 的软引用

recordStats: # 开发统计功能

原理篇

合理使用缓存

缓存的目的主要是为了降低主主数据库的压力,服务可以直接从缓存中获取数据,从而提高响应速度,让原本有限的资源可以服务更多的用户。

从工程的角度来说,缓存的引入并不是盲目的,如果主数据库压力不大的情况,并不需要添加缓存。多添加一个数据中间件显然也会增加维护的成本,而且在实际使用过程中还会存在一些,例如缓存击穿、缓存雪崩等问题。

基本概念

命中率。 返回正确结果数 / 请求缓存次数, 命中率越高,表明缓存的使用率越高。

最大元素。缓存中可以存放元素的最大数目, 一旦超过,会通过合适的策略进行清空操作。

清空策略:FIFO、LFU、LRU

缓存类型

缓存根据存储的方式可以分成本地缓存和分布式缓存。

本地缓存:本地缓存一般指的是缓存在应用进程内部的缓存。以 Java 技术栈为例,可是自己实现一个 HashMap 作为数据缓存,也可以直接使用现成的缓存方案,例如 ehcache、caffeine 等。

分布式缓存:缓存和应用环境分离,会单独存放在自己的服务器或集群里,且多个应用可直接的共享缓存。 常见的缓存方案有 MemCache 和 Redis 等。

这一节主要是让大家对缓存有一个基本的认识,缓存不是一种具体的技术,而是一种通用的技术方案,如何选择合适的缓存方案集成到自己的项目中去并且如何解决引入缓存后产生的一些经典问题,不是本文讨论的重点。有关于缓存的详细介绍和选型可以参考:

参考文献

Spring JMSTemplate 与 JMS 原生API比较

博客分类: JMS Spring 2.x   JMSUtil与Spring JmsTemplate的对比 Author:信仰 Date:2012-4-20 未完待续,截止日期2012-4-20 从以下 ...

Spring MVC 支持的原生API参数

HttpServletRequest HttpServletResponse HttpSession java.security.Principal Local InputStream OutputS ...

Spring MVC 使用Servlet原生API作为参数

具体看代码: @RequestMapping("/testServletAPI") public void testServletAPI(HttpServletRequest re ...

以Spring Cache扩展为例介绍如何进行高效的源码的阅读

摘要 日常开发中,需要用到各种各样的框架来实现API.系统的构建.作为程序员,除了会使用框架还必须要了解框架工作的原理.这样可以便于我们排查问题,和自定义的扩展.那么如何去学习框架呢.通常我们通过阅读 ...

如何进行高效的源码阅读:以Spring Cache扩展为例带你搞清楚

摘要 日常开发中,需要用到各种各样的框架来实现API.系统的构建.作为程序员,除了会使用框架还必须要了解框架工作的原理.这样可以便于我们排查问题,和自定义的扩展.那么如何去学习框架呢.通常我们通过阅读 ...

springboot整合spring @Cache和Redis

转载请注明出处:https://www.cnblogs.com/wenjunwei/p/10779450.html spring基于注解的缓存 对于缓存声明,spring的缓存提供了一组java注解: ...

Spring Cache缓存技术的介绍

缓存用于提升系统的性能,特别适用于一些对资源需求比较高的操作.本文介绍如何基于spring boot cache技术,使用caffeine作为具体的缓存实现,对操作的结果进行缓存. demo场景 本d ...

Java缓存相关memcached、redis、guava、Spring Cache的使用

随笔分类 - Java缓存相关 主要记录memcached.redis.guava.Spring Cache的使用 第十二章 redis-cluster搭建(redis-3.2.5) 摘要: redi ...

随机推荐

Ext.Net常用方法

1.js(Ext)操作 Ext.Msg.alert('系统提示', '未连接血站,该功能暂时不能使用.'); Ext.getCmp("id").getValue();Ext.get ...

大话数据结构&ndash;1.基础知识+2.算法

2.算法: 算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每个指令表现为一个或多个操作. 特性:输入.输出.有穷性.确定性.可行性.   2.9.1.算法时间复杂度: 语句 ...

orientation和gravity的区别

orientation:决定UI组件是按行还是列显示 gravity:指定文字对齐方式.

mac os x安装ngigx+php fastcgi+mysql+memcache详细流程

Part 1: MacPorts Mac上装软件常用的是MacPorts和homebrew,这个软件会很方便地提供软件的安装.装这些前先得装Xcode,Xcode在appstore上有,一个多G,下载 ...

【转载】Apache kafka原理与特性(0.8V)

http://blog.csdn.net/xiaolang85/article/details/37821209 前言: kafka是一个轻量级的/分布式的/具备replication能力的日志采集组 ...

Android 百度地图API 定位 导航

看看这个利用百度地图定位并实现目的地导航的Demo. 首先看实现效果:                          进 入后首先会得到当前位置,在地图上显示出来.在输入框中输入目的地后,就会在地 ...

[转]如何查询SQL Server连接数

1.获取SQL Server允许同时用户连接的最大数 SELECT @@MAX_CONNECTIONS 2.获取当前指定数据库的连接信息 SELECT * FROM master.dbo.syspro ...

nyoj 2 括号配对问题 栈

nyoj 2 括号配对问题 题目链接: http://acm.nyist.net/JudgeOnline/problem.php?pid=2 思路: 栈:'(' '['入栈,遇到 ']' ')'出栈 ...

javascript中的事件Event

一.事件流 1.事件流:描述的是从页面中接受事件的顺序 IE的事件流是事件冒泡流,Netscape的事件流是事件捕获流. 2.事件冒泡 IE的事件流叫做事件冒泡(event bubbing),即事件开 ...

使用selenium操作ant design前端的页面,感觉页面没加载完

因需要收集页面数据,遂准备使用selenium爬取瓦斯阅读页面, 瓦斯网站使用的是ant design,元素定位非常困难,页面元素都没有ID,现在还只是能做到操作登录,不能自动打开订阅,查询某公众号, ...

你可能感兴趣的:(springboot的原生cache_一个缓存使用案例:Spring Cache VS Caffeine 原生 API)