汇总:《精通Spring4.x 企业应用开发实战》持续更新中…
伴随着信息量的爆炸式增长,以及构建的应用系统越来越多样化、复杂化,特别是伴随着近年来企业级应用互联网化的超势,缓存(Cache)对应用程序性能的优化变得越来越重要。将所需服务请求的数据放到缓存中,既可以提高应用程序的访问效率,又可以减少数据库服务器的压力,从面让用户获得更为极致的体验。
Spring 开发者正是因为看到了缓存在应用中的重要地位,从 Spring 3.1 开始,就以一贯的优雅风格提供了一种透明的缓存解决方案,这使得 Spring 可以在后台使用不同的缓存框架( 如 EhCache、 GemFire、 HazelCast 和 Guava)时保持编程的一致性。而从 Spring4.0 开始则全面支持 JSR-107 annotations 和自定义的缓存标签。
缓存作为系统架构中提升性能的一种重要支撑技术,在企业级应用中的地位越来越突显。
用过Maven 的朋友应该知道,在找依赖构件的时候,先从本机仓库找,如果没有再从本地服务器仓库找,最后才到远程服务器仓库找。所以可将缓存定文为一种 存储机制
,它将数据保存在某个地方,并以一种更快的方式提供服务。较为常见的一种情况是在应用中使用缓存机制,以避免方法的多次执行,从而克服性能缺陷,也可减少应用服务器或者数据库的压力。
缓存的策略有很多种,在应用系统中可根据实际情况选择:
静态数据或者变化频率不高的数据放到缓存中
,如配置参数、字典表等。搜索引擎
,如 Solr 或 ElasticSearch.通常在 Web 应用开发中,不同层级对应的缓存要求和缓存策略全然不同。如图15-1所示列举了系统不同层级对应的缓存技术选型:
要了解缓存,必须对其中的一些基本概念有所了解。下面先来了解一下缓存中两个比较重要的基本概念。
即从缓存中读取数据的次数与总读取次数的比率。一般来说,命中率越高越好。
命中率= 从缓存中读取的次数/(总读取次数[从缓存中读取的次数+从慢速设备上读取的次数])
Miss率 = 没有从缓存中读取的次数/(总读取次数[从缓存中读取的次数+从慢速设备上读取的次数)
这是一个非常重要的监控指标,如果要做缓存,就一定要监控这个指标,来看缓存是否工作良好。还是之前举的例子,假设京东的某个分仓库通过监控发现某个仓库的命中率一直很低,经常无法找到待发货的商品,那么这时候就需要调整策略,否则就会造成搏击里面“出拳百次,无一命中”的笑话。
即如果缓存满了,从缓存中移除数据的策略。常见的有 LFU、 LRU、 FIFO:
至此,我们基本了解了缓存的一些基本知识。在 Java 中,一般会对调用方法进行缓存控制。比如调用“findUserByld(String id)”,应该在调用这个方法之前先从缓存中查找有没有符合查询条件的数据,如果没有,则执行该方法从数据库中查找该用户,然后添加到缓存中,下次调用时将会从缓存中获取该数据。
从 Spring 3.1 开始,提供了类似于@Transactional 事务注解的缓存注解,且提供了Cache 层的抽象。此外,JSR-107 也从 Spring 4.0 开始得到全面支持。
Spring提供了一种可以在方法级别
进行缓存的缓存抽象:
需要注意的是,Spring Cache 仅提供了一种抽象而未提供具体实现。在此之前,我们一般会自己使用 AOP 来做一定程度的封装实现。
使用 Spring Cache带来的好处如下:
需要注意的是:
1、Spring Cache 并不针对多进程的应用环境进行专门的处理。也就是说,当应用程序处于分布式
或者集群
环境下时,需要针对具体的缓存进行相应的配置。如 EhCache 可以通过 RMI、JGroups 及 EhCache Server 等方式来配置其多播集群环境。
2、另外,在 Spring Cache 抽象的操作中没有锁的概念, 当多线程并发操作(更新或者删除)同一个缓存项时,将可能得到过期的数据
。有些缓存实现提供了锁的功能,如果需要考虑如上场景,则可以详细了解具体缓存的一些相关特性,如 EhCache 就提供了针对缓存元素 key 的 Read(读)、Write(写)锁。
这里先展示一个自定义的缓存实现,即不通过任何第三方组件来实现对象的内存缓存,然后通过 Spring Cache 来实现缓存操作,以体会 Spring Cache 所带来的优雅和便捷性。
Java 对象的缓存和序列化是息息相关的,一般情况下,需要被缓存的实体类需要实现 Seriaizable
,只有实现了 Serializable 接口的类,JVM 才可以对其对象进行序列化。对于 Redis、 EhCache 等缓存套件来说,被缓存的对象应该是可序列化的,否则在网络传输、硬盘存储时都会抛出序列化异常。
实体类始终实现Serializable 接口是一个好的编程习惯。实现 Serializable 接口的实体类,一般要求声明一个 serialVersionUID 成员变量,以表明该实体类的版本。如果实体类的结构发生变化,则可以修改 serialVersionUID 值以支持反序列化工作。关于对象序列化和反序列化的更多知识,读者可自行查找相关资料学习,在此不再展开。
书中手写缓存代码示例略。
略。
在实际项目中,有时可能需要在使用之前就完成缓存的初始化。最典型的示例是当启动并且运行应用程序时将数据加载到缓存中
。 实现该方法很简单,首先访问缓存管理器,然后将数据手工加载到不同的缓存中(这些缓存根据缓存名称进行区分)。在下面的示例中,当初始化应用程序上下文时,将用户列表加载到缓存区域中。
代码清单15-10 展示了在 Sping Bean 的@PostConstrut
注解方法中初始化缓存 users。
具体代码实现,请看书。
在企业级应用中往往会有更复杂的功能和性能需求,所以在日常开发过程中,大部分情况下会使用第三方的缓存实现
,而不是 SimpleCacheManager 的简单实现。在企业级 Java 领域,Spring缓存提供了与不同缓存框架的集成支持。
书中并没有过多的对下面几种存储方式做过多的介绍。
1.EhCache
2.Guava
3.HazelCast
4.GemFire
5.JSR-107 Cache
Spring Cache使用实战经验,略。
待补充。