0. 引言

随着redis的普及,更多的同学对redis分布式缓存更加熟悉,但在一些实际场景中,其实并不需要用到redis,使用更加简单的本地缓存即可实现我们的缓存需求。

今天,我们一起来看看本地缓存组件ehcache

1. ehcache简介

1.1 简介

ehcache是基于java开发的本地缓存组件,无需单独安装部署,只要引入jar包就可利用它来实现缓存。

所谓本地缓存,就是指存储在JVM堆内存中的临时缓存数据,当然ehcache本身也支持Off-Heap Store机制来使用堆外内存,本地缓存相较于redis性能和响应速度更高。

Ehcache的本地缓存还支持过期时间、最大容量、持久化等特性,使得它可以适用于各种不同的缓存场景。

官方文档地址:https://www.ehcache.org/documentation/

1.2 本地缓存与redis的区别

本地缓存与redis的区别在于:

  • 架构:

    本地缓存基于单机架构,即数据仅本机可用,无法共享给其他服务。除非使用服务调用来获取。而redis本身基于分布式架构,支持跨服务调取。
    所以当数据需要分布式调用时,则适用于redis,如果数据只需要本地获取,则可考虑本地缓存

  • 性能:

    本地缓存本身基于本机内存,没有网络IO消耗,所以性能上大大高于redis,但是如果数据量较大,则还是要考虑使用redis,本地缓存仅适用于数据量小、结构简单的数据场景,不适合复杂的业务数据

  • 功能拓展:

    redis支持持久化、订阅模式、集群、主从模式等,而ehcache更倾向于简单的缓存功能场景,虽然也支持持久化,但是本身并不建议用它来做大型或复杂场景的缓存。如果场景比较简单轻量,对延迟有较高要求,则可选择本地缓存

2. ehcache使用

1、创建一个springboot项目,这里我的springboot版本为2.6.13

2、引入ehcahe组件依赖

这里需要注意的是net.sf.ehcache是ehcache2.X 与 org.ehcache是echcache3.X,两个版本配置有区别

        <dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId><version>2.10.9.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId><version>2.6.13</version></dependency>

3、在启动类上添加@EnableCaching注解,开启缓存

@SpringBootApplication
@EnableCaching
public class LocalCacheDemoApplication {public static void main(String[] args) {SpringApplication.run(LocalCacheDemoApplication.class, args);}}

4、在配置文件application.yml中添加配置

spring:profiles: active: devcache:type: ehcacheehcache:config: classpath:ehcache.xml

5、在resources文件夹下创建配置文件ehcache.xml,注意这里单独创建了一个name为user的缓存,用于后续保存用户信息缓存。如果有不同的缓存需要使用不同的name的,需要单独创建cache标签

标签介绍:

defaultCache: 默认缓存配置标签
cache 指定缓存标签,name表示缓存名称
diskStore 数据存储磁盘路径

属性介绍:

eternal: 缓存是否永久有效,如果为 true 则忽略timeToIdleSeconds 和 timeToLiveSeconds
maxElementsInMemory:最多缓存多少个key
overflowToDisk: 缓存超限时是否写入磁盘,默认为true
overflowToOffHeap: 堆内存超限时是否使用堆外内存,企业版功能,收费
diskPersistent:缓存是否持久化
timeToLiveSeconds:缓存多久过期
timeToIdleSeconds:缓存多久没有被访问就过期
diskExpiryThreadIntervalSeconds:磁盘缓存过期检查线程运行时间间隔
memoryStoreEvictionPolicy:缓存淘汰策略, LFU:最近最少使用的元素先移出; FIFO:最先进入的元素被移出; LRU:使用越少的元素被移出
maxBytesLocalHeap:缓存最大占用JVM堆内存,0表示不限制,单位支持K、M或G
maxBytesLocalOffHeap: 缓存最大占用堆外内存,0表示不限制,单位支持K、M或G,企业版功能,收费
maxBytesLocalDisk:缓存最大占用磁盘,0表示不限制,单位支持K、M或G

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"updateCheck="false"><defaultCacheeternal="false"maxElementsInMemory="10000"overflowToDisk="false"diskPersistent="false"timeToLiveSeconds="3600"timeToIdleSeconds="0"diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU"/><cachename="user"eternal="false"maxElementsInMemory="10000"overflowToDisk="false"diskPersistent="false"timeToLiveSeconds="3600"timeToIdleSeconds="0"diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU"/><!--    存储到磁盘时的路径--><diskStore path="/Users/wuhanxue/Downloads/ehcache" /></ehcache>

6、缓存使用,在获取方法中使用@Cacheable注解,在更新方法中使用@CachePut注解。
我这里模拟就没有访问数据库查询数据了,大家在实际书写的时候可以连接上数据源测试

@RestController
@RequestMapping("user")
public class UserController {@GetMapping("get")@Cacheable(cacheNames = "user", key = "#id")public User getById(Integer id) {System.out.println("get第一次获取,不走缓存");User user = new User();user.setId(id);user.setAge(18);user.setName("benjamin_"+id);user.setSex(true);return user;}@PostMapping("update")@CachePut(cacheNames = "user", key = "#search.id")public User update(@RequestBody User search) {System.out.println("update更新缓存");User user = new User();Integer id = search.getId();user.setId(id);user.setAge(search.getAge() != null ? search.getAge()+1 : 0);user.setName("update_benjamin_"+id);user.setSex(true);return user;}}

3. 测试

1、调用查询接口:localhost:8080/user/get?id=1

2、第一次调用,打印"get第一次获取,不走缓存"。再调用一次发现没有打印了,但是数据正常查询,说明走了缓存

3、调用更新接口

4、再调用查询接口,查询到的就是更新的数据,说明缓存更新成功

4. 注意事项

谨慎使用maxElementsInMemory

maxElementsInMemory表示的是最大缓存多少个key,这个配置项谨慎使用,一般我们应该根据占用多少内存空间来控制,而不是占用多少个key,如果出现某些key的数据量特别大时,就会导致key数量没超过,但内存占用超过导致的OOM了

这个我们通过一个生成大数据量的接口来模拟,其中generateMemoryString方法可以在文末的源码仓库中

1、书写接口

@GetMapping("build")@Cacheable(cacheNames = "user", key = "#id")public User build(Integer id) {System.out.println("get第一次获取,不走缓存");User user = new User();user.setId(id);user.setAge(18);// 生成指定大小的字符串user.setName(generateMemoryString(id));user.setSex(true);return user;}

2、限制项目JVM内存为100m,方便更快模拟出报错

3、调用接口localhost:8080/user/build?id=100,因为该接口会生成大数据,占用本地缓存,而JVM缓存又给的100M,所以调用会报错堆内存溢出,如图所示

4、因此该配置项要谨慎使用,可以通过maxBytesLocalHeap,maxBytesLocalDisk设置占用多少内存、磁盘来替代

<cachename="user"eternal="false"maxBytesLocalHeap="50M"maxBytesLocalDisk="200M"overflowToDisk="false"diskPersistent="false"timeToLiveSeconds="3600"timeToIdleSeconds="0"diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU"/>

如果maxBytesLocalHeapmaxElementsInMemory都配置了的,谁先达到配置的值,就触发

如果单个key值太大,仍然会导致OOM

虽然我们上面配置了maxBytesLocalHeap来限制最大使用的内存,比如我们限制了该值为100M,则如果我们有4个30M的数据进来,那么就会根据配置的淘汰策略去淘汰之前的key,以腾出空间来装新的数据

但如果新进来的数据很大,比如超过100M了,那么就会一下子装满内存,甚至淘汰之前的key也不行,所以这种情况下还是会导致OOM的

遇到这种情况,两种处理办法,一种是保证不会有大于这个阈值的数据产生,这个可以通过业务代码控制,二是设置一个全局错误捕捉,捕捉产生的OOM报错,然后返回一个兜底或者其他的状态码,以此标识

演示源码

https://gitee.com/wuhanxue/wu_study/tree/master/demo/local_cache_demo

springboot:缓存不止redis,学会使用本地缓存ehcache相关推荐

  1. Springboot:整合redis对接口数据进行缓存

    文章目录 Springboot:整合redis对接口数据进行缓存 一.注解及其属性介绍 @Cacheable @CacheEvict @CachePut @CacheConfig 二.代码实现 1.创 ...

  2. java本地缓存_java缓存——(五)LocalCache本地缓存分享

    LocalCache本地缓存分享 前言 一.本地缓存应用场景 二.java本地缓存标准 三.java开源缓存框架 四.LocalCache实现 结束语 前言 本次分享探讨java平台的本地缓存,是指占 ...

  3. ehcache 清除缓存_如何设计一个本地缓存

    作者:ksfzhaohuihttp://my.oschina.net/OutOfMemory/blog/3133013 前言 最近在看Mybatis的源码,刚好看到缓存这一块,Mybatis提供了一级 ...

  4. 本地缓存:为什么要用本地缓存?用它会有什么问题?

    背景 在高性能的服务架构设计中,缓存是一个不可或缺的环节.在实际的项目中,我们通常会将一些热点数据存储到Redis或Memcached 这类缓存中间件中,只有当缓存的访问没有命中时再查询数据库.在提升 ...

  5. SpringBoot同时集成Redis和Guava作为缓存组件--进一步分析代码

    2019独角兽企业重金招聘Python工程师标准>>> 请先看 https://my.oschina.net/u/3866531/blog/1840386 CompositeCach ...

  6. SpringBoot 05 集成redis实现对象序列化缓存

    1.为什么用Spring Data Spring Data 的目标是为数据访问提供一个基于Spring的普遍常用的编程模型,同时仍然保留底层数据存储的特殊特性, 它让使用数据访问技术.关系和非关系数据 ...

  7. html缓存ios,iOS-H5离线包本地缓存CCCandyWebCache

    一.离线包缓存的简介离线包缓存含几个包的概念:预装包:指通过脚本文件在程序编译时期下载的包. 增量包:指通过包的文件比对,生成的diff文件(差量包) 全量包:指完整的包文件CCCandyWebCac ...

  8. lodop打印html有缓存,Lodop打印维护PRINT_SETUP本地缓存ini文件

    针对千差万别的客户端,Lodop提供了打印维护(PRINT_SETUP),可以针对某个客户端微调,调整结果保存在客户端本地,不会影响其他访问网站的用户的使用. 打印维护使用方法: 1.PRINT_IN ...

  9. SpringBoot 本地缓存的使用

    目录 缓存概述 springboot整合caffeine spring缓存注解的使用 springboot整合ehcache 缓存概述 对于访问频率高.一段时间内变动频率低的数据,可以加缓存,以缓解服 ...

最新文章

  1. 计算机设计思想 —— 代理(proxy)
  2. vue 使用fs_node.js中常用的fs文件系统
  3. python signal模块作用_如何理解python中信号Signal?
  4. SQL Server 查询性能优化——创建索引原则(一)
  5. NYOJ 289 苹果(01背包)
  6. LeetCode 537. 复数乘法
  7. 作者:王小兵,男,农业部市场与经济信息司副司长。
  8. linux(Centos5.1)升级至2.6.19内核+L7实现-- 续限P2P软件
  9. Java微信公众平台开发(十五)--微信JSSDK的使用
  10. RS485通信原理图及程序实例详解
  11. Oracle中的索引类型
  12. vue实现手机验证码登录
  13. Materials - 剑网3地形材质若干
  14. 手机banner图片自适应手机宽高定位
  15. 基于 Electron 实现 uTools 的超级面板
  16. 第八届蓝桥杯个人赛赛后总结
  17. 数据研究必备:国内40个免费数据源大放送!
  18. htons、htonl、ntohl、ntohs的区别与使用
  19. Linux下常用的系统监控软件
  20. linux 渗透工具_适用于Linux的十大最佳渗透测试工具

热门文章

  1. 内网安全学习(1)---信息收集
  2. java倒序输出数字的方法
  3. 从音箱入门到高手必看知识(二)—— 音箱由哪几部分组成
  4. 微信防屏蔽域名防封 爆红域名如何在微信打开
  5. 二、Vue2.0项目结构内容及配置解析
  6. DirectX11 顶点缓存
  7. linux的crontab绝对路径咋填,RHCE培训视频Crontab详解(创想云教育)
  8. 进程地址空间 - 地址空间布局
  9. 射频集成电路芯片原理与应用电路设计
  10. 基于C++(QT)的车牌识别实现——二值化处理