缓存技术

2022-03-26 21:19:20 浏览数 (1)

缓存技术

1 为什么要使用缓存

在上一次课redis入门的过程中我们讲过随着访问量的上升,几乎大部分使用MySQL架构的网站在数据库 上都出现了性能问题,web程序不再仅仅关注在功能上,同时也开始追求性能,Memcached(缓存)自然 成为一个非常时尚的技术产品。 缓存的实质是替数据库挡了一层。主要是减轻对数据库高频率读的压力。频繁被访问的数据可以被放 置于缓存当中,以供频繁访问。

2 缓存的原理

(1)将数据写入/读取速度更快的存储(设备);

(2)将数据缓存到离应用最近的位置;

(3)将数据缓存到离用户最近的位置。

3 缓存分类

在分布式系统中,缓存的应用非常广泛,从部署角度有以下几个方面的缓存应用。

(1)CDN缓存;

(2)反向代理缓存;

(3)分布式缓存;

(4)本地应用缓存;

4 Ehcache本地缓存

Ehcache是一个用Java实现的使用简单,高速,实现线程安全的缓存管理类库,ehcache提供了用内存, 磁盘文件存储,以及分布式存储方式等多种灵活的cache管理方案,同时具有快速,简单,低消耗,依赖性 小,扩展性强,支持对象或序列化缓存,支持缓存或元素的失效,提供 LRU、LFU 和 FIFO 缓存策略,支 持内存缓存和磁盘缓存,分布式缓存机制等等特点。

1.1 导入依赖

代码语言:javascript复制
      <!-- Ehcache 坐标-->
        <!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache -->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.6</version>
        </dependency>

1.2 配置yaml文件

代码语言:javascript复制
spring:
  thymeleaf:
    cache: false
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test1?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
  cache:
    ehcache:
    #获取encache.xml文件的路径
      config: classpath:ehcache.xml
​
logging:
  level:
    root: info
    com.xwd.springbootredis.mapper: trace
​
mybatis-plus:
  mapper-locations: classpath:com/xwd/springbootredis/*.xml;
  type-aliases-package: com.xwd.springbootredis.pojo
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true
​

1.3 编写ehcache.xml文件

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    <diskStore path="java.io.File.TempDirectory"/>
​
    <!--defaultCache:echcache 的默认缓存策略-->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxElementsOnDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
    </defaultCache>
​
    <!-- 自定义缓存策略-->
    <cache name="news"
           maxElementsInMemory="10000"
           eternal="false"
           timeToIdleSeconds="120"
           timeToLiveSeconds="120"
           maxElementsOnDisk="10000000"
           diskExpiryThreadIntervalSeconds="120"
           memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
    </cache>
​
</ehcache>
代码语言:javascript复制
name : 缓存的名称,可以通过指定名称获取指定的某个Cache对象
maxElementsInMemory :内存中允许存储的最大的元素个数,0代表无限个
clearOnFlush:内存数量最大时是否清除。
eternal :设置缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。根据存储数据的不
同,例如一些静态不变的数据如省市区等可以设置为永不过时
timeToIdleSeconds : 设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是
永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds :缓存数据的 生存时间(TTL),也就是一个元素从构建到消亡的最大时间间隔
值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。(和上面的两者取
最小值)
overflowToDisk:内存不足时,是否启用磁盘缓存。
maxEntriesLocalDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘
中。
maxElementsOnDisk:硬盘最大缓存个数。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个
Cache都应该有自己的一个缓冲区。
diskPersistent:是否在VM重启时存储硬盘的缓存数据。默认值是false。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去
清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。这里
比较遗憾,Ehcache并没有提供一个用户定制策略的接口,仅仅支持三种指定策略,感觉做的不够理想。

1.4 开启缓存和写service

(1) 给主入口添加@EnableCaching注解

(2) 给service方法上加

代码语言:javascript复制
    @Override
    @Cacheable(cacheNames = "news")
    public List<News> findNewsByTitle(String title) {
        LambdaQueryWrapper<News> like = new QueryWrapper<News>().lambda()
                .like(StringUtils.hasText(title), News::getTitle, title);
        return this.list(like);
    }

5 缓存常用注解

1.1 @Cacheable

@Cacheable 作用:把方法的返回值添加到Ehcache 中做缓存.

cacheNames 属性:cacheNames属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应 Cache的名称。值可以是一个Cache名称也可以是多个Cache的名称,当需要指定多个Cache时其是一个 数组。

Key 属性:key属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持SpringEL表达 式。当我们没有指定该属性时,Spring将使用默认策略生成key, 使用方法参数时我们可以直接使用”#参 数名”(#empno)或者”#p参数下标”(#p0)

代码语言:javascript复制
@Cacheable(value="news",key="#title")
public List<News> findNewsByList(String title) {
System.out.println("进入service 执行按title查询");
QueryWrapper wrapper= new QueryWrapper();
wrapper.like("title",title);
return newsMapper.selectList(wrapper);
}

如果key需要拼接则拼接语法为: T(String).valueOf(值).concat(值)

代码语言:javascript复制
@Cacheable(value = "emp", key ="T(String).valueOf('news-').concat('#title')",
unless = "#result eq null")
public List<News> findNewsByList(String title) {
System.out.println("进入service 执行按title查询");
QueryWrapper wrapper= new QueryWrapper();
wrapper.like("title",title);
return newsMapper.selectList(wrapper);
}

condition属性:指定发生的条件, condition属性默认为空,表示将缓存所有的调用情形。其值是通过 SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调 用该方法时该方法都会执行一次,

如:只有当news的title不为空时才会进行缓存条件写法为:condition="#title eq null"。

unless属性:unless= "#result eq null" ,返回结果是null值不缓存

allEntries属性:allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表 示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一 下清除所有的元素,这比一个一个清除元素更有效率。

beforeInvocation属性:清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而 未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指 定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

代码语言:javascript复制
@CacheEvict(value="news",allEntries=true, beforeInvocation=true)
public void removeNewsById(Integer id);
1.2 @CachePut

每次都需要执行方法并将对象缓存到缓存空间,@Cacheable 的key 要和 @CachePut 的key 一致,类 似于更新缓存 将重新查询到的数据 缓存到redis 替换原有的同key的缓存。

代码语言:javascript复制
@CachePut(value="news", key="T(String).valueOf('news-').concat(#news.title)")
News saveNews(News news);
1.3 JSON注解

@JsonIgnore是用来标注在需要忽略的属性上。因为我们前面在RedisConfig配置类中配置了使用 Jackson的序列化对象,将对象转换为JSON保存在Redis中。那么在将对象转换为JSON时,有些属性需 要忽略,特别是对象之间有关联关系时,需要使用@JsonIgnore忽略关联对象,避免转换时出现死循 环。

在News.java实体中忽略type属性:

代码语言:javascript复制
@JsonIgnore 
private Type type;

6.7 分布式缓存

1.1 为什么要使用分布式缓存

一致性 本地缓存只有在应用程序被部署到单一的应用服务器上的时候才有意义, 如果它被部署到了多台应用服 务器上的话, 那么本地缓存一点意义都没有, 问题出在过期数据. 集群缓存通过复制和让缓存数据失效来解 决这个问题的.

可伸缩性 集群缓存和数据网格的区别就在于可伸缩性. 数据网格是可伸缩的. 缓存数据是通过动态的分区被分发 的. 结果就是, 增加一个缓存节点即提高了吞吐量也提高了容量.

独立性 如果把一个数据网格集成进应用程序里面的话, 那么它就和应用程序耦合在一起了, 也就是, 当扩展这 个内置的数据网格的时候, 同事也需要扩展应用程序, 结果, 扩展网格的同时, 增加了与之关联的应用程序 (和应用服务器)的管理成本.

0 人点赞