分布式系列教程(01) -Ehcache缓存架构

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 分布式系列教程(01) -Ehcache缓存架构

代码已上传到Github,有兴趣的同学可以下载来看看:https://github.com/ylw-github/SpringBoot-Cache-Demo.git

在前面的《 互联网并发与安全系列教程》中,我们了解到了缓存,知道缓存最终的目的是为减轻服务端压力,减少网络传输请求。

实际的开发中,缓存可以分为几类:

  • 客户端缓存页面缓存浏览器缓存APP客户端缓存
  • 网络缓存代理缓存CDN缓存
  • 服务器缓存数据库缓存平台缓存级缓存

Ehcache 读音(A Cache);

本文将简单的基于Map集合实现本地缓存 以及 详细讲解“本地缓存Ehcache”框架

本文目录结构:

l____1.基于Map集合实现本地缓存

l____2.本地缓存Ehcachs

l____3.Ehcache基本用法

l________3.1 参数相关配置

l____4.Ehcache集群模式

l________4.1 常用集群模式

l________4.2 RMi集群模式

l____5.Ehcache使用场景

l____总结

1.基于Map集合实现本地缓存

代码很简单,直接上代码:

1.定义Map缓存工具类:

@Component
public class MapEhcaChe<K, V> {
  private Map<K, V> ehcaCheMap = new ConcurrentHashMap<K, V>();
  public void put(K k, V value) {
    ehcaCheMap.put(k, value);
  }
  public V get(K k) {
    return ehcaCheMap.get(k);
  }
  public void remove(K k) {
    ehcaCheMap.remove(k);
  }
}

2.使用案例:

@RequestMapping("/ehcaChePut")
public String put(String key, String value) {
  mapEhcaChe.put(key, value);
  return "success";
}
@RequestMapping("/get")
public String get(String key) {
  String value = mapEhcaChe.get(key);
  return value;
}

2.本地缓存Ehcache

Ehcache 是纯java的开源缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。它主要面向通用缓存、Java EE和轻量级容器,具有内存和磁盘存储、缓存加载器、缓存扩展、缓存异常处理程序。

Ehcache最初由Greg Luck于2003年开始开发。2009年,该项目被Terracotta购买。软件仍然开源,但一些新的主要功能(例如,快速可重启性之间的一致性的)只能在商业产品中使用。

Ehcache 被广泛用于在Hibernate、Spring、Cocoon等其他开源系统。

Ehcache的主要特性:

  1. 快速;
  2. 简单;
  3. 多种缓存策略;
  4. 缓存数据有两级:内存和磁盘,因此无需担心容量问题;
  5. 缓存数据会在虚拟机重启的过程中写入磁盘;
  6. 可以通过 RMI、可插入 API 等方式进行分布式缓存;
  7. 具有缓存和缓存管理器的侦听接口;
  8. 支持多缓存管理器实例,以及一个实例的多个缓存区域;
  9. 提供 Hibernate 的缓存实现;

Ehcache是用来管理缓存的一个工具,其缓存的数据可以是存放在内存里面的,也可以是存放在硬盘上的。其核心是CacheManager,一切Ehcache的应用都是从CacheManager开始的,它是用来管理Cache(缓存)的,一个应用可以有多个CacheManager,而一个CacheManager下又可以有多个Cache。Cache内部保存的是一个个的Element,而一个Element中保存的是一个key和value的配对,相当于Map里面的一个Entry。


Ehcache缓存过期策略:

当缓存需要被清理时(比如空间占用已经接近临界值了),需要使用某种淘汰算法来决定清理掉哪些数据。常用的淘汰算法有下面几种:

  • FIFOFirst In First Out,先进先出。判断被存储的时间,离目前最远的数据优先被淘汰。
  • LRULeast Recently Used,最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。
  • LFULeast Frequently Used,最不经常使用。在一段时间内,数据被使用次数最少的,优先被淘汰。

3.Ehcache基本用法

下面讲解下使用SpringBoot2.0整合Ehcache框架

1.创建Maven项目

2.添加maven依赖:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.0.RELEASE</version>
  </parent>
  <dependencies>
    <!-- SpringBoot 对lombok 支持 -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>
    <!-- SpringBoot web 核心组件 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
    </dependency>
    <!-- SpringBoot 外部tomcat支持 -->
    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
    </dependency>
    <!-- springboot-log4j -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-log4j</artifactId>
      <version>1.3.8.RELEASE</version>
    </dependency>
    <!-- springboot-aop 技术 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.6</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.47</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
    </dependency>
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>
    <!--开启 cache 缓存 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <!-- ehcache缓存 -->
    <dependency>
      <groupId>net.sf.ehcache</groupId>
      <artifactId>ehcache</artifactId>
      <version>2.9.1</version><!--$NO-MVN-MAN-VER$ -->
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.1.1</version>
    </dependency>
    <!-- mysql 依赖 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
  </dependencies>

3.YML配置文件信息

###端口号配置
server:
  port: 8081
###数据库配置  
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    test-while-idle: true
    test-on-borrow: true
    validation-query: SELECT 1 FROM DUAL
    time-between-eviction-runs-millis: 300000
    min-evictable-idle-time-millis: 1800000
  # 缓存配置读取
  cache:
    type: ehcache
    ehcache:
      config: classpath:app2_ehcache.xml

4.App启动方式(@EnableCaching 开启ehcache缓存模式)

@MapperScan(basePackages = { "com.xxx.mapper" })
@EnableCaching
@SpringBootApplication
public class App {
  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }
}

5.项目使用(@Cacheable 加了该注解的方法表示可以缓存,@CacheConfig 表示创建缓存配置,Key为userCache)。

@CacheConfig(cacheNames = "userCache")
public interface UserMapper {
  @Select("SELECT ID ,NAME,AGE FROM users where id=#{id}")
  @Cacheable
  List<Users> getUser(@Param("id") Long id);
}

6.ehcache配置(app1_ehcache和app2_ehcache)

app1_ehcache:

<?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.tmpdir/ehcache-rmi-4000" />
  <!-- 多台机器配置 rmiUrls=//192.168.8.32:400002/demoCache|//192.168.5.231:400003/demoCache -->
  <cacheManagerPeerProviderFactory
    class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
    properties="peerDiscovery=manual,rmiUrls=//127.0.0.1:5000/userCache">
  </cacheManagerPeerProviderFactory>
  <!-- 配置 rmi 集群模式 -->
  <cacheManagerPeerListenerFactory
    class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
    properties="hostName=127.0.0.1,port=4000,socketTimeoutMillis=120000" />
  <!-- 多播方式配置 搜索某个网段上的缓存 timeToLive 0是限制在同一个服务器 1是限制在同一个子网 32是限制在同一个网站 64是限制在同一个region 
    128是限制在同一个大洲 255是不限制 <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" 
    properties="peerDiscovery=automatic, multicastGroupAddress=224.1.1.1, multicastGroupPort=40000, 
    timeToLive=32" /> -->
  <!-- 默认缓存 -->
  <defaultCache maxElementsInMemory="1000" eternal="true"
    timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true"
    diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000"
    diskPersistent="true" diskExpiryThreadIntervalSeconds="120"
    memoryStoreEvictionPolicy="LRU">
  </defaultCache>
  <!-- demo缓存 -->
  <cache name="userCache" maxElementsInMemory="1000" eternal="false"
    timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true"
    diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000"
    diskPersistent="false" diskExpiryThreadIntervalSeconds="120"
    memoryStoreEvictionPolicy="LRU">
    <cacheEventListenerFactory
      class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" />
    <!-- 用于在初始化缓存,以及自动设置 -->
    <bootstrapCacheLoaderFactory
      class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" />
  </cache>
</ehcache>

app2_ehcache:

<?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.tmpdir/ehcache-rmi-5000" />
  <!-- 多台机器配置 -->
  <cacheManagerPeerProviderFactory
    class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
    properties="peerDiscovery=manual,rmiUrls=//127.0.0.1:4000/userCache">
  </cacheManagerPeerProviderFactory>
  <cacheManagerPeerListenerFactory
    class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
    properties="hostName=127.0.0.1,port=5000,socketTimeoutMillis=120000" />
  <!-- 多播方式配置 搜索某个网段上的缓存 timeToLive 0是限制在同一个服务器 1是限制在同一个子网 32是限制在同一个网站 64是限制在同一个region 
    128是限制在同一个大洲 255是不限制 <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" 
    properties="peerDiscovery=automatic, multicastGroupAddress=224.1.1.1, multicastGroupPort=40000, 
    timeToLive=32" /> -->
  <!-- 默认缓存 -->
  <defaultCache maxElementsInMemory="1000" eternal="true"
    timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true"
    diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000"
    diskPersistent="true" diskExpiryThreadIntervalSeconds="120"
    memoryStoreEvictionPolicy="LRU">
  </defaultCache>
  <!-- demo缓存 -->
  <cache name="userCache" maxElementsInMemory="1000" eternal="false"
    timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true"
    diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000"
    diskPersistent="false" diskExpiryThreadIntervalSeconds="120"
    memoryStoreEvictionPolicy="LRU">
    <cacheEventListenerFactory
      class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" />
    <!-- 用于在初始化缓存,以及自动设置 -->
    <bootstrapCacheLoaderFactory
      class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" />
  </cache>
</ehcache>

7.清除缓存

@Autowired
private CacheManager cacheManager;
@RequestMapping("/remoKey")
public void remoKey() {
  cacheManager.getCache("userCache").clear();
}
3.1 参数相关配置

diskStore :指定数据(.data and .index)存储位置,可指定磁盘中的文件夹位置期 The diskStore element is optional. It must be configured if you have overflowToDisk or diskPersistent enabled for any cache. If it is not configured, a warning will be issues and java.io.tmpdir will be used.

defaultCache : 默认的管理策略


Ehcache 使用Map集合实现的 element 其实就是 key 和value

以下属性是必须的:

  • name: Cache的名称,必须是唯一的(ehcache会把这个cache放到HashMap里)。
  • maxElementsInMemory:在内存中缓存的element的最大数目。
  • maxElementsOnDisk:在磁盘上缓存的element的最大数目,默认值为0,表示不限制。
  • eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断。
  • overflowToDisk: 如果内存中数据超过内存限制,是否要缓存到磁盘上。

以下属性是可选的:

  • timeToIdleSeconds: 对象空闲时间,指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问。
  • timeToLiveSeconds: 对象存活时间,指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问。
  • diskPersistent: 是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false。
  • diskExpiryThreadIntervalSeconds: 对象检测线程运行时间间隔。标识对象状态的线程多长时间运行一次。
  • diskSpoolBufferSizeMB: DiskStore使用的磁盘大小,默认值30MB。每个cache使用各自的DiskStore。
  • memoryStoreEvictionPolicy: 如果内存中数据超过内存限制,向磁盘缓存时的策略。默认值LRU,可选FIFO、LFU。

4.Ehcache集群模式

由于 EhCache 是进程中的缓存系统,一旦将应用部署在集群环境中,每一个节点维护各自的缓存数据,当某个节点对缓存数据进行更新,这些更新的数据无法在其它节点中共享,这不仅会降低节点运行的效率,而且会导致数据不同步的情况发生。例如某个网站采用 A、B 两个节点作为集群部署,当 A 节点的缓存更新后,而 B 节点缓存尚未更新就可能出现用户在浏览页面的时候,一会是更新后的数据,一会是尚未更新的数据,尽管我们也可以通过 Session Sticky 技术来将用户锁定在某个节点上,但对于一些交互性比较强或者是非 Web 方式的系统来说,Session Sticky 显然不太适合。

4.1 常用集群模式

EhCache从1.7版本开始,支持五种集群方案,分别是:

Terracotta、RMI、JMS、JGroups、EhCache Server

4.2 RMi集群模式

我们或许会有这样的疑问:

  • 你如何知道集群环境中的其他缓存?
  • 分布式传送的消息是什么形式?
  • 什么情况需要进行复制?增加(Puts),更新(Updates)或是失效(Expiries)?
  • 采用什么方式进行复制?同步还是异步方式?

方案:

  1. 正确的元素类型:只有可序列化的元素可以进行复制。一些操作,比如移除,只需要元素的键值而不用整个元素;在这样的操作中即使元素不是可序列化的但键值是可序列化的也可以被复制。
  2. 成员发现(Peer Discovery):Ehcache进行集群的时候有一个cache组的概念。每个cache都是其他cache的一个peer,没有主cache的存在。成员发现(Peer Discovery)正是用来解决 “你如何知道集群环境中的其他缓存?” 这个问题的。Ehcache提供了两种机制用来进行成员发现,即:自动成员发现和手动成员发现。要使用一个内置的成员发现机制要在ehcache的配置文件中指定cacheManagerPeerProviderFactory元素的class属性为
    net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory

5.Ehcache的使用场景

Ehcache可以这样使用:

  • 使用纯java的ehcache作为本地缓存
  • Reids 作为远程分布式缓存
  • 解决redis缓存压力过大,提高缓存速度,以及缓存性能。

Redis和Ehcache缓存的区别:

  • 如果是单个应用或者对缓存访问要求很高的应用,用ehcache。
  • 如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。

实际工作中:

  • 我们在项目中使用集中式缓存(Redis或者式Memcached等),通常都是检查缓存中是否存在期望值的数据,如果存在直接返回,如果不存在就查询数据库让后在将数据库缓存,这个时候如果缓存系统因为某写原因宕机,造成服务无法访问,那么大的量请求直接穿透到数据库,最数据库压力非常大。这时候我们让ehcache作为二级缓存,当redis服务器宕机后,可以查询ehcache缓存。这样能够有效的扛住服务器请求压力。

总结

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2月前
|
前端开发 测试技术 数据处理
Kotlin教程笔记 - MVP与MVVM架构设计的对比
Kotlin教程笔记 - MVP与MVVM架构设计的对比
67 4
|
2月前
|
前端开发 JavaScript 测试技术
Kotlin教程笔记 - 适合构建中大型项目的架构模式全面对比
Kotlin教程笔记 - 适合构建中大型项目的架构模式全面对比
39 3
|
2月前
|
存储 前端开发 Java
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
33 2
|
10天前
|
存储 Prometheus Cloud Native
分布式系统架构6:链路追踪
本文深入探讨了分布式系统中的链路追踪理论,涵盖追踪与跨度的概念、追踪系统的模块划分及数据收集的三种方式。链路追踪旨在解决复杂分布式系统中请求流转路径不清晰的问题,帮助快速定位故障和性能瓶颈。文中介绍了基于日志、服务探针和边车代理的数据收集方法,并简述了OpenTracing、OpenCensus和OpenTelemetry等链路追踪协议的发展历程及其特点。通过理解这些概念,可以更好地掌握开源链路追踪框架的使用。
65 41
|
20天前
|
设计模式 存储 算法
分布式系统架构5:限流设计模式
本文是小卷关于分布式系统架构学习的第5篇,重点介绍限流器及4种常见的限流设计模式:流量计数器、滑动窗口、漏桶和令牌桶。限流旨在保护系统免受超额流量冲击,确保资源合理分配。流量计数器简单但存在边界问题;滑动窗口更精细地控制流量;漏桶平滑流量但配置复杂;令牌桶允许突发流量。此外,还简要介绍了分布式限流的概念及实现方式,强调了限流的代价与收益权衡。
60 11
|
22天前
|
设计模式 监控 Java
分布式系统架构4:容错设计模式
这是小卷对分布式系统架构学习的第4篇文章,重点介绍了三种常见的容错设计模式:断路器模式、舱壁隔离模式和重试模式。断路器模式防止服务故障蔓延,舱壁隔离模式通过资源隔离避免全局影响,重试模式提升短期故障下的调用成功率。文章还对比了这些模式的优缺点及适用场景,并解释了服务熔断与服务降级的区别。尽管技术文章阅读量不高,但小卷坚持每日更新以促进个人成长。
45 11
|
23天前
|
消息中间件 存储 安全
分布式系统架构3:服务容错
分布式系统因其复杂性,故障几乎是必然的。那么如何让系统在不可避免的故障中依然保持稳定?本文详细介绍了分布式架构中7种核心的服务容错策略,包括故障转移、快速失败、安全失败等,以及它们在实际业务场景中的应用。无论是支付场景的快速失败,还是日志采集的安全失败,每种策略都有自己的适用领域和优缺点。此外,文章还为技术面试提供了解题思路,助你在关键时刻脱颖而出。掌握这些策略,不仅能提升系统健壮性,还能让你的技术栈更上一层楼!快来深入学习,走向架构师之路吧!
57 11
|
2月前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
1月前
|
存储 算法 安全
分布式系统架构1:共识算法Paxos
本文介绍了分布式系统中实现数据一致性的重要算法——Paxos及其改进版Multi Paxos。Paxos算法由Leslie Lamport提出,旨在解决分布式环境下的共识问题,通过提案节点、决策节点和记录节点的协作,确保数据在多台机器间的一致性和可用性。Multi Paxos通过引入主节点选举机制,优化了基本Paxos的效率,减少了网络通信次数,提高了系统的性能和可靠性。文中还简要讨论了数据复制的安全性和一致性保障措施。
46 1
|
2月前
|
人工智能 运维 算法
引领企业未来数字基础架构浪潮,中国铁塔探索超大规模分布式算力
引领企业未来数字基础架构浪潮,中国铁塔探索超大规模分布式算力