上一篇我們介紹了在Spring Boot中整合EhCache的方法。既然用了ehcache,我們自然要說說它的一些高級(jí)功能,不然我們用默認(rèn)的ConcurrentHashMap
就好了。本篇不具體介紹EhCache緩存如何落文件、如何配置各種過期參數(shù)等常規(guī)細(xì)節(jié)配置,不熟悉的朋友可以看看這里的官方文檔。
那么我們今天具體講什么呢?先思考一個(gè)場(chǎng)景,當(dāng)我們使用了EhCache,在緩存過期之前可以有效的減少對(duì)數(shù)據(jù)庫的訪問,但是通常我們將應(yīng)用部署在生產(chǎn)環(huán)境的時(shí)候,為了實(shí)現(xiàn)應(yīng)用的高可用(有一臺(tái)機(jī)器掛了,應(yīng)用還需要可用),肯定是會(huì)部署多個(gè)不同的進(jìn)程去運(yùn)行的,那么這種情況下,當(dāng)有數(shù)據(jù)更新的時(shí)候,每個(gè)進(jìn)程中的緩存都是獨(dú)立維護(hù)的,如果這些進(jìn)程緩存同步機(jī)制,那么就存在因緩存沒有更新,而一直都用已經(jīng)失效的緩存返回給用戶,這樣的邏輯顯然是會(huì)有問題的。所以,本文就來說說當(dāng)使用EhCache的時(shí)候,如果來組建進(jìn)程內(nèi)緩存EnCache的集群以及配置配置他們的同步策略。
由于下面是組建集群的過程,務(wù)必采用多機(jī)的方式調(diào)試,避免不必要的錯(cuò)誤發(fā)生。
動(dòng)手試試
本篇的實(shí)現(xiàn)將基于上一篇的基礎(chǔ)工程來進(jìn)行。先來回顧下上一篇中的程序要素:
User實(shí)體的定義
@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實(shí)體的數(shù)據(jù)訪問實(shí)現(xiàn)(涵蓋了緩存注解)
@CacheConfig(cacheNames = "users")
public interface UserRepository extends JpaRepository<User, Long> {
@Cacheable
User findByName(String name);
}
下面開始改造這個(gè)項(xiàng)目:
第一步:為需要同步的緩存對(duì)象實(shí)現(xiàn)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;
}
}
注意:如果沒有做這一步,后續(xù)緩存集群通過過程中,因?yàn)橐獋鬏擴(kuò)ser對(duì)象,會(huì)導(dǎo)致序列化與反序列化相關(guān)的異常
第二步:重新組織ehcache的配置文件。我們嘗試手工組建集群的方式,不同實(shí)例在網(wǎng)絡(luò)相關(guān)配置上會(huì)產(chǎn)生不同的配置信息,所以我們建立不同的配置文件給不同的實(shí)例使用。比如下面這樣:
實(shí)例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>
實(shí)例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
標(biāo)簽中定義名為users的緩存,這里我們?cè)黾恿艘粋€(gè)子標(biāo)簽定義cacheEventListenerFactory
,這個(gè)標(biāo)簽主要用來定義緩存事件監(jiān)聽的處理策略,它有以下這些參數(shù)用來設(shè)置緩存的同步策略:- replicatePuts:當(dāng)一個(gè)新元素增加到緩存中的時(shí)候是否要復(fù)制到其他的peers。默認(rèn)是true。
- replicateUpdates:當(dāng)一個(gè)已經(jīng)在緩存中存在的元素被覆蓋時(shí)是否要進(jìn)行復(fù)制。默認(rèn)是true。
- replicateRemovals:當(dāng)元素移除的時(shí)候是否進(jìn)行復(fù)制。默認(rèn)是true。
- replicateAsynchronously:復(fù)制方式是異步的指定為true時(shí),還是同步的,指定為false時(shí)。默認(rèn)是true。
- replicatePutsViaCopy:當(dāng)一個(gè)新增元素被拷貝到其他的cache中時(shí)是否進(jìn)行復(fù)制指定為true時(shí)為復(fù)制,默認(rèn)是true。
- replicateUpdatesViaCopy:當(dāng)一個(gè)元素被拷貝到其他的cache中時(shí)是否進(jìn)行復(fù)制指定為true時(shí)為復(fù)制,默認(rèn)是true。
- 新增了一個(gè)
cacheManagerPeerProviderFactory
標(biāo)簽的配置,用來指定組建的集群信息和要同步的緩存信息,其中: - hostName:是當(dāng)前實(shí)例的主機(jī)名
- port:當(dāng)前實(shí)例用來同步緩存的端口號(hào)
- socketTimeoutMillis:同步緩存的Socket超時(shí)時(shí)間
- peerDiscovery:集群節(jié)點(diǎn)的發(fā)現(xiàn)模式,有手工與自動(dòng)兩種,這里采用了手工指定的方式
- rmiUrls:當(dāng)peerDiscovery設(shè)置為manual的時(shí)候,用來指定需要同步的緩存節(jié)點(diǎn),如果存在多個(gè)用
|
連接
第三步:打包部署與啟動(dòng)。打包沒啥大問題,主要緩存配置內(nèi)容存在一定差異,所以在指定節(jié)點(diǎn)的模式下,需要單獨(dú)拿出來,然后使用啟動(dòng)參數(shù)來控制讀取不同的配置文件。比如這樣:
-Dspring.cache.ehcache.config=classpath:ehcache-1.xml
-Dspring.cache.ehcache.config=classpath:ehcache-2.xml
第四步:實(shí)現(xiàn)幾個(gè)接口用來驗(yàn)證緩存的同步效果
@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;
}
}
驗(yàn)證邏輯:
- 啟動(dòng)通過第三步說的命令參數(shù),啟動(dòng)兩個(gè)實(shí)例
- 調(diào)用實(shí)例1的
/create
接口,創(chuàng)建一條數(shù)據(jù) - 調(diào)用實(shí)例1的
/find
接口,實(shí)例1緩存User,同時(shí)同步緩存信息給實(shí)例2,在實(shí)例1中會(huì)存在SQL查詢語句 - 調(diào)用實(shí)例2的
/find
接口,由于緩存集群同步了User的信息,所以在實(shí)例2中的這次查詢也不會(huì)出現(xiàn)SQL語句
進(jìn)一步思考
有的朋友會(huì)問數(shù)據(jù)更新之后怎么辦?
其實(shí)當(dāng)構(gòu)建了緩存集群之后,就比較好辦了。比如這里的例子,需要做兩件事:
save
操作增加@CachePut
注解,讓更新操作完成之后將結(jié)果再put到緩存中- 保證緩存事件監(jiān)聽的replicateUpdates=true,這樣數(shù)據(jù)在更新之后可以保證復(fù)制到其他節(jié)點(diǎn)
這樣就可以防止緩存的臟數(shù)據(jù)了,但是這種方法還并不是很好,因?yàn)榫彺婕旱耐揭廊恍枰獣r(shí)間,會(huì)存在短暫的不一致。同時(shí)進(jìn)程內(nèi)的緩存要在每個(gè)實(shí)例上都占用,如果大量存儲(chǔ)的話始終不那么經(jīng)濟(jì)。所以,很多時(shí)候進(jìn)程內(nèi)緩存不會(huì)作為主要的緩存手段。
注:本文轉(zhuǎn)載自“程序猿DD”,如有侵權(quán),請(qǐng)聯(lián)系刪除!