基于Spring的Web缓存

Posted 天码营

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Spring的Web缓存相关的知识,希望对你有一定的参考价值。

基于Spring的Web存缓存 由Cliff发表在天码营 

缓存的基本思想其实是以空间换时间。我们知道,IO的读写速度相对内存来说是非常比较慢的,通常一个web应用的瓶颈就出现在磁盘IO的读写上。那么,如果我们在内存中建立一个存储区,将数据缓存起来,当浏览器端由请求到达的时候,直接从内存中获取相应的数据,这样一来可以降低服务器的压力,二来,可以提高请求的响应速度,提升用户体验。

缓存的分类

  • 数据库数据缓存

一般来说,web应用业务逻辑业务逻辑比较复杂,数据库繁多,要获取某个完整的数据,往往要多次读取数据库,或者使用极其复杂效率较低的SQL查询语句。为了提高查询的性能,将查询后的数据放到内存中进行缓存,下次查询时,直接从内存缓存直接返回,提高响应效率。

  • 应用层缓存

应用层缓存主要针对某个业务方法进行缓存,有些业务对象逻辑比较复杂,,可能涉及到多次数据库读写或者其他消耗较高的操作,应用层缓存可以将复杂的业务逻辑解放出来,降低服务器压力。

  • 页面缓存

除了IO外,web应用的另一大瓶颈就是页面模板的渲染。每次请求都需要从业务逻辑层获取相应的model,并将其渲染成对应的html。一般来说,web应用读取数据的需求比更新数据的需求大很多,大多数情况下,某个请求返回的HTML是一样的,因此直接将HTML缓存起来也是缓存的一个主流做法。

  • 代理服务器缓存

代理服务器是浏览器和源服务器之间的中间服务器,浏览器先向这个中间服务器发起Web请求,经过处理后(比如权限验证,缓存匹配等),再将请求转发到源服务器。代理服务器缓存的运作原理跟浏览器的运作原理差不多,只是规模更大。可以把它理解为一个共享缓存,不只为一个用户服务,一般为大量用户提供服务,因此在减少相应时间和带宽使用方面很有效,同一个副本会被重用多次。

  • CDN缓存

CDN( Content delivery networks )缓存,也叫网关缓存、反向代理缓存。浏览器先向CDN网关发起Web请求,网关服务器后面对应着一台或多台负载均衡源服务器,会根据它们的负载请求,动态将请求转发到合适的源服务器上。虽然这种架构负载均衡源服务器之间的缓存没法共享,但却拥有更好的处扩展性。

基于spring的缓存

spring作为一个成熟的java web 框架,自身有一套完善的缓存机制,同时,spring还未其他缓存的实现提供了扩展。接下来,让我们在一个简单的学生管理系统中尝试spring的数据库缓存、应用层缓存、页面缓存的实现。

基于spring的Web缓存

源程序简介

本节课我们来看看一个简单的学生管理系统,改系统使用了Spring+JPA+EhCache的架构对数据库进行了缓存。大家可以直接下载源码进行学习。

数据库准备

测试程序使用了mysql作为数据库,安装好mysql后,建立一个空白的 数据库,例如cache

建好数据库后,修改src/main/resources/application.properties的数据库配置

spring.datasource.url=jdbc:mysql://localhost/cache?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=

利用maven启动程序

该系统利用maven作为构建工具,如果对maven没有了解的同学可以自行了解一下,我们会利用maven进行整个项目的构建以及运行。因此需要大家下载安装maven。

安装完成后,打开命令行,进入程序所在目录,输入以下命令:

mvn spring-boot:run

打开浏览器,访问以下http://localhost:8111/blogs即可看到最初的博客列表页面

直接运行 com.tmy.App.java

如果你成功的将项目作为一个maven项目导入进eclipse,直接运行com.tmy.App.java也可以将项目启动起来。

注意,如果希望将项目导入进eclipse,需要为eclipse添加maven插件,否则会出现依赖的类找不到的问题。

页面列表

以下是程序所提供的所有页面以及相关说明:

http://localhost:8111/blogs //没有加缓存的博客列表页面

http://localhost:8111/blogs/dao //添加了数据层缓存

http://localhost:8111/blogs/service?test=test //添加了服务层缓存
http://localhost:8111/blogs/service/update?test=test //更新服务层缓存
http://localhost:8111/blogs/service/evict?test=test //删除服务层缓存
http://localhost:8111/blogs/service/test?test=test //删除服务层缓存的同时更新缓存

http://localhost:8111/blogs/page //添加了页面缓存
http://localhost:8111/blogs/page/update //清空页面缓存
http://localhost:8111/blogs/page/delete //清空页面缓存

涉及到的技术

  • maven

maven是目前主流java的构建工具之一,如果对maven没有了解的同学可以自行了解一下,接下来我们会利用maven进行整个项目的构建以及运行。

  • spring boot

spring boot是spring的一个子项目,其目的是spring应用的初始搭建以及开发过程,如果你想自己搭建一个基于spring的应用,强烈建议学习一下在《java web 全栈开发》这门课程,教你如何从对spring零基础到搭建好一个完整的spring web应用。这里,我们只需知道mvn spring-boot:run命令可以将系统run起来即可。

  • Spring

Spring作为目前主流的java web框架,大家应该都很了解,这里不做过多介绍。

  • JPA

JPA全称Java Persistence API,JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。本门课程主要讲基于spring的数据库缓存,对于JPA的内容不做过多的涉及。

  • EhCache

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点。我们的学生管理系统将利用EhCache对数据库层进行缓存。

配置EhCache

对EhCache的依赖

上一节我们讲到很多技术,这里我们主要的依赖是指对EhCache的依赖,需要在Spring项目中引入EhCache,在pom.xml中加入以下代码即可:

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-ehcache</artifactId>
    </dependency>

配置CacheManager

添加ehcache配置文件

src/main/resources下添加文件ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         name="CM1"
         updateCheck="false"
         maxBytesLocalHeap="16M">

    <diskStore path="/data/app/cache/ehcache"/>

    <defaultCache
            eternal="false"
            overflowToDisk="false"
            maxElementsInMemory="10000"
            timeToIdleSeconds="3600"
            timeToLiveSeconds="36000"
            />
</ehcache>

encache可以对以下参数进行配置:

  • name

缓存名称

  • maxElementsInMemory

内存中最大缓存对象数

  • maxElementsOnDisk

硬盘中最大缓存对象数,若是0表示无穷大

  • eternal

true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false

  • overflowToDisk

true表示当内存缓存的对象数目达到了maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。

  • diskSpoolBufferSizeMB

磁盘缓存区大小,默认为30MB。每个Cache都应该有自己的一个缓存区。

  • diskPersistent

是否缓存虚拟机重启期数据

  • diskExpiryThreadIntervalSeconds

磁盘失效线程运行时间间隔,默认为120秒

  • timeToIdleSeconds

设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地处于空闲状态

  • timeToLiveSeconds

设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清除。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有意义

  • memoryStoreEvictionPolicy

当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。

添加cacheManager

首先,我们要通过@EnableCaching标注将Spring通过标注进行缓存管理的功能打开,以方便我们之后通过标注添加数据库缓存。

然后,为CacheConfiguration添加@Configuration标注,打开CacheConfiguration内@Bean的功能。

生成一个CacheManager的实例。

最后,在web app销毁的时候销毁cacheManager。

@Configuration
@EnableCaching
public class CacheConfiguration 

    private net.sf.ehcache.CacheManager cacheManager;

    @PreDestroy
    public void destroy() 
        cacheManager.shutdown();
    

    @Bean
    public CacheManager cacheManager() 
        cacheManager = net.sf.ehcache.CacheManager.create();
        EhCacheCacheManager ehCacheManager = new EhCacheCacheManager();
        ehCacheManager.setCacheManager(cacheManager);
        return ehCacheManager;
    


数据层缓存实现

添加ehcache设置

首先,我们需要在EhCache中设置一块区域来存放缓存,在src/main/resources/ehcache.xml中添加如下配置:

<cache name="com.tmy.model.User"></cache>
<cache name="com.tmy.model.Blog"></cache>

Hibernate的一级缓存和二级缓存

Hibernate提供了两级缓存,第一级是Session的缓存。由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存。第一级缓存是必需的,hibernate会默认提供好。

第二级缓存是一个可插拔的的缓存插件,它是由SessionFactory负责管理。由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此第二级缓存是进程范围或者集群范围的缓存。这个缓存中存放的对象的松散数据第二级缓存是可选的,可以在每个类或每个集合的粒度上配置第二级缓存。

打开二级缓存

我们可以通过为entry对象添加标注的方式打开二级缓存:

@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)

二级缓存一共有以下5种策略:

  • CacheConcurrencyStrategy.NONE

不使用缓存,默认的缓存策略

  • CacheConcurrencyStrategy.READ_ONLY

只读模式,在此模式下,如果对数据进行更新操作,会有异常

  • CacheConcurrencyStrategy.READ_WRITE

读写模式在更新缓存的时候会把缓存里面的数据换成一个锁,其它事务如果去取相应的缓存数据,发现被锁了,直接就去数据库查询

  • CacheConcurrencyStrategy.NONSTRICT_READ_WRITE

不严格的读写模式则不会的缓存数据加锁

  • CacheConcurrencyStrategy.TRANSACTIONAL

事务模式指缓存支持事务,当事务回滚时,缓存也能回滚

指定cache region factory

然后,在src/main/resources/application.properties中为cache指定一个factory:

spring.jpa.properties. =org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory

性能对比

第一次访问

第一次访问http://localhost:8111/blogs时,waiting也就是服务器响应的时间为2.82秒,耗时较多。

注意:这里消耗2.82秒的原因是:在Blog对象中添加了对成员creator添加了@ManyToOne的标注,因此,当通过JPA获取blog对象后,JPA还会请求一次SQL查询,去user表中获取user信息,将user填充进来,而为了效果更加明显,系统在添加测试数据时为每个blog都添加了不同的user,导致sql请求大大增加,处理时间也大大增加

多次访问未缓存页面

多次访问http://localhost:8111/blogs后,服务器响应时间大大减少,基本保持在700毫秒左右:

这是因为mysql实际上帮我们做了缓存的工作,因此,多次访问后,服务器响应时间会大大减少。如果大家有兴趣,可以自行搜索mysql缓存相关的内容。

多次访问已缓存页面

那么,在多次访问http://localhost:8111/blogs/dao后,访问时间基本保持在100多毫秒,比没有缓存的页面效率高了5倍左右,比第一次访问效率高了20倍以上。

服务层缓存实现

Spring缓存的相关标注

Spring 提供了一套标注来保住我们快速的实现缓存系统:

  • @Cacheable 触发添加缓存的方法
  • @CacheEvict 触发删除缓存的方法
  • @CachePut 在不干涉方法执行的情况下更新缓存
  • @Caching 组织多个缓存标注的标注
  • @CacheConfig 在class的层次共享缓存的设置

接下来我们来看缓存的具体实现。

添加ehcache设置

和数据层缓存一样,需要在内存中设置一块区域来存放service的缓存,在src/main/resources/ehcache.xml中添加如下配置:

Spring cache支持多种类型缓存(事务敏感缓存)

spring 方法级缓存多种实现

第16章 使用Squid部署代理缓存服务

基于Spring Boot技术栈 博客系统企业级前后端实战 渐进式讲解+Thymeleaf+Elasticsearch+多种数据库

Spring Cache的基本使用与分析

Spring Cache的基本使用与分析