Spring Boot 2.x基础教程:使用EhCache缓存集群

上一篇我们介绍了在Spring Boot中整合EhCache的方法。既然用了ehcache,我们自然要说说它的一些高级功能,不然我们用默认的ConcurrentHashMap就好了。本篇不具体介绍EhCache缓存如何落文件、如何配置各种过期参数等常规细节配置,这部分内容留给读者自己学习,如果您不知道如何搞,可以看看这里的官方文档

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

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

动手试试

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

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<User, Long> {

    @Cacheable
    User findByName(String name);

}

下面开始改造这个项目:

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

@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对象,会导致序列化与反序列化相关的异常

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

实例1,使用ehcache-1.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd">

    <cache name="users"
           maxEntriesLocalHeap="200"
           timeToLiveSeconds="600">
        <cacheEventListenerFactory
                class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
                properties="replicateAsynchronously=true,
            replicatePuts=true,
            replicateUpdates=true,
            replicateUpdatesViaCopy=false,
            replicateRemovals=true "/>
    </cache>

    <cacheManagerPeerProviderFactory
            class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
            properties="hostName=10.10.0.100,
                        port=40001,
                        socketTimeoutMillis=2000,
                        peerDiscovery=manual,
                        rmiUrls=//10.10.0.101:40001/users" />

</ehcache>

实例2,使用ehcache-2.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd">

    <cache name="users"
           maxEntriesLocalHeap="200"
           timeToLiveSeconds="600">
        <cacheEventListenerFactory
                class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
                properties="replicateAsynchronously=true,
            replicatePuts=true,
            replicateUpdates=true,
            replicateUpdatesViaCopy=false,
            replicateRemovals=true "/>
    </cache>

    <cacheManagerPeerProviderFactory
            class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
            properties="hostName=10.10.0.101,
                        port=40001,
                        socketTimeoutMillis=2000,
                        peerDiscovery=manual,
                        rmiUrls=//10.10.0.100:40001/users" />

</ehcache>

配置说明:

  • cache标签中定义名为users的缓存,这里我们增加了一个子标签定义cacheEventListenerFactory,这个标签主要用来定义缓存事件监听的处理策略,它有以下这些参数用来设置缓存的同步策略:
    • replicatePuts:当一个新元素增加到缓存中的时候是否要复制到其他的peers。默认是true。
    • replicateUpdates:当一个已经在缓存中存在的元素被覆盖时是否要进行复制。默认是true。
    • replicateRemovals:当元素移除的时候是否进行复制。默认是true。
    • replicateAsynchronously:复制方式是异步的指定为true时,还是同步的,指定为false时。默认是true。
    • replicatePutsViaCopy:当一个新增元素被拷贝到其他的cache中时是否进行复制指定为true时为复制,默认是true。
    • replicateUpdatesViaCopy:当一个元素被拷贝到其他的cache中时是否进行复制指定为true时为复制,默认是true。
  • 新增了一个cacheManagerPeerProviderFactory标签的配置,用来指定组建的集群信息和要同步的缓存信息,其中:
    • hostName:是当前实例的主机名
    • port:当前实例用来同步缓存的端口号
    • socketTimeoutMillis:同步缓存的Socket超时时间
    • peerDiscovery:集群节点的发现模式,有手工与自动两种,这里采用了手工指定的方式
    • rmiUrls:当peerDiscovery设置为manual的时候,用来指定需要同步的缓存节点,如果存在多个用|连接

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

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

进一步思考

上一篇发布的时候,公众号上有网友留言问,数据更新之后怎么办?

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

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

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

欢迎关注本系列教程:《Spring Boot 2.x基础教程》

参考资料

本文首发:Spring Boot 2.x基础教程:使用EhCache缓存集群,转载请注明出处。
欢迎关注我的公众号:程序猿DD,获得独家整理的学习资源和日常干货推送。点击直达本系列教程目录

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342