Spring Boot 2.x基礎教程:使用EhCache緩存集羣

上一篇咱們介紹了在Spring Boot中整合EhCache的方法。既然用了ehcache,咱們天然要說說它的一些高級功能,否則咱們用默認的ConcurrentHashMap就行了。本篇不具體介紹EhCache緩存如何落文件、如何配置各類過時參數等常規細節配置,這部份內容留給讀者本身學習,若是您不知道如何搞,能夠看看這裏的官方文檔html

那麼咱們今天具體講什麼呢?先思考一個場景,當咱們使用了EhCache,在緩存過時以前能夠有效的減小對數據庫的訪問,可是一般咱們將應用部署在生產環境的時候,爲了實現應用的高可用(有一臺機器掛了,應用還須要可用),確定是會部署多個不一樣的進程去運行的,那麼這種狀況下,當有數據更新的時候,每一個進程中的緩存都是獨立維護的,若是這些進程緩存同步機制,那麼就存在因緩存沒有更新,而一直都用已經失效的緩存返回給用戶,這樣的邏輯顯然是會有問題的。因此,本文就來講說當使用EhCache的時候,若是來組建進程內緩存EnCache的集羣以及配置配置他們的同步策略。java

因爲下面是組建集羣的過程,務必採用多機的方式調試,避免沒必要要的錯誤發生。spring

動手試試

本篇的實現將基於上一篇的基礎工程來進行。先來回顧下上一篇中的程序要素:數據庫

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實體的數據訪問實現(涵蓋了緩存註解)bash

@CacheConfig(cacheNames = "users")
public interface UserRepository extends JpaRepository<User, Long> {

    @Cacheable
    User findByName(String name);

}

下面開始改造這個項目:網絡

第一步:爲須要同步的緩存對象實現Serializable接口app

@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.xmlsocket

<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,得到獨家整理的學習資源和平常乾貨推送。 點擊直達本系列教程目錄
相關文章
相關標籤/搜索