EhCache多进程同步缓存失效怎么办?实战解决方案

网安智编 厦门萤点网络科技 2026-05-27 00:17 14 0
上一篇我们介绍了在 Boot中整合的方法。既然用了,我们自然要说说它的一些高级功能,不然我们用默认的就好了。本篇不具体介绍缓存如何落文件、如何配置各种过期参数等常规细节配置,不熟悉的朋友可以看看这里的官方文档。 那么我们今天具体讲什么呢?先...

上一篇我们介绍了在 Boot中整合的方法。既然用了,我们自然要说说它的一些高级功能,不然我们用默认的就好了。本篇不具体介绍缓存如何落文件、如何配置各种过期参数等常规细节配置,不熟悉的朋友可以看看这里的官方文档。

那么我们今天具体讲什么呢?先思考一个场景,当我们使用了,在缓存过期之前可以有效的减少对数据库的访问,但是通常我们将应用部署在生产环境的时候,为了实现应用的高可用(有一台机器挂了,应用还需要可用),肯定是会部署多个不同的进程去运行的,那么这种情况下,当有数据更新的时候,每个进程中的缓存都是独立维护的,如果这些进程缓存同步机制,那么就存在因缓存没有更新,而一直都用已经失效的缓存返回给用户,这样的逻辑显然是会有问题的。所以,本文就来说说当使用的时候,如果来组建进程内缓存的集群以及配置配置他们的同步策略。

由于下面是组建集群的过程,务必采用多机的方式调试,避免不必要的错误发生。

动手试试

本篇的实现将基于上一篇的基础工程来进行。先来回顾下上一篇中的程序要素:

User实体的定义

@Entity
@Data
@NoArgsConstructor
public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private Integer age;
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

User实体的数据访问实现(涵盖了缓存注解)

@CacheConfig(cacheNames = "users")
public interface UserRepository extends JpaRepository {
    @Cacheable
    User findByName(String name);
}

下面开始改造这个项目:

第一步:为需要同步的缓存对象实现接口

@Entity
@Data
@NoArgsConstructor
public class User implements Serializable {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private Integer age;
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

注意:如果没有做这一步,后续缓存集群通过过程中,因为要传输User对象,会导致序列化与反序列化相关的异常

第二步:重新组织的配置文件。我们尝试手工组建集群的方式,不同实例在网络相关配置上会产生不同的配置信息,所以我们建立不同的配置文件给不同的实例使用。比如下面这样:

实例1,使用-1.xml


    
        
    
    

实例2,使用-2.xml


    
        
    
    

配置说明:

Spring Boot EhCache集群配置_EhCache缓存同步策略_ehcache使用文档

新增了一个标签的配置,用来指定组建的集群信息和要同步的缓存信息,其中:

第三步:打包部署与启动。打包没啥大问题,主要缓存配置内容存在一定差异,所以在指定节点的模式下,需要单独拿出来,然后使用启动参数来控制读取不同的配置文件。比如这样:

-Dspring.cache.ehcache.config=classpath:ehcache-1.xml
-Dspring.cache.ehcache.config=classpath:ehcache-2.xml

第四步:实现几个接口用来验证缓存的同步效果

@RestController
static class HelloController {
    @Autowired
    private UserRepository userRepository;
    @GetMapping("/create")    
    public void create() {
        userRepository.save(new User("AAA", 10));
    }
    @GetMapping("/find")
    public User find() {
        User u1 = userRepository.findByName("AAA");
        System.out.println("查询AAA用户:" + u1.getAge());
        return u1;
    }
}

验证逻辑:

启动通过第三步说的命令参数,启动两个实例调用实例1的/接口,创建一条数据调用实例1的/find接口,实例1缓存User,同时同步缓存信息给实例2,在实例1中会存在SQL查询语句调用实例2的/find接口,由于缓存集群同步了User的信息,所以在实例2中的这次查询也不会出现SQL语句进一步思考

有的朋友会问数据更新之后怎么办?

其实当构建了缓存集群之后,就比较好办了。比如这里的例子,需要做两件事:

save操作增加@注解,让更新操作完成之后将结果再put到缓存中保证缓存事件监听的=true,这样数据在更新之后可以保证复制到其他节点

这样就可以防止缓存的脏数据了,但是这种方法还并不是很好,因为缓存集群的同步依然需要时间,会存在短暂的不一致。同时进程内的缓存要在每个实例上都占用,如果大量存储的话始终不那么经济。所以,很多时候进程内缓存不会作为主要的缓存手段。