EhCache 基礎知識
吐嘈
- clusteredShared的size會直接占用內存,而不是像redis那用動態的增大
- clusteredDedicated能夠動態增大,但是還是必須指定每個cache的大小,不但造成浪費,還增加開發難度
Getting Start
- http://www.ehcache.org/documentation/3.5
- EHCache支持兩種創建方式:代碼創建,XML配置文件創建
- EHCache支持分布式的Cache集群,Cache的服務由Terracotta提供
- Maven dependency
- org.ehcache : ehcache
- ehcache.3.5.2.jar
- org.ehcache : ehcache-clustered
- ehcache-clustered.3.5.2.jar
- org.ehcache : ehcache
EHCache的分層架構
- heap : 最快最小
- offheap : 大而快
- disk :磁盤持久化,當CacheManager在close()之后,會把所有數據持久化,下次重啟后會恢復
Tiering 分層架構
heap
- 堆內存,無需序列化所以最快,但是容量有限
- 可以指定Entry個數,或者內存大小,默認為Entry個數
- heap層在多層Cache架構中是必須的,分層架構的Cache大小應該是金字塔式的
- 只有heap能在運行時改變大小,通過ResourcePools的updateResourcePools()
offheap
- 堆外內存,需要序列化和反序列化,容量大但是比較慢
- 通過DirectByteBuffer的native方法分配堆外內存,通過c來分配,對外內存不受JVM管理,無法垃圾回收
- 必須指定資源池大小,當資源池滿了之后會觸發淘汰策略
- 大緩存,命中率低的緩存都可以放在off-heap
- -XX:MaxDirectMemorySize,設置JVM堆外內存大小,和offheap大小對應
- 使用offheap可以減小JVM垃圾回收壓力,提高性能
disk
- 可以設置是否Persistence ,Persistence 意思是重啟JVM時cache會被還原
- 一個disk/persistence directory只能被一個cache managers使用
- 當CacheManager在close()之后,會把所有數據持久化,JVM down機不管用
- 持久化的過程是一個分段的並發操作,默認16個線程並發,可以通過減少段落來減少並發
Clustered
- Terracotta服務提供遠程分布式Cache服務
- Clustered和disk無法共存
- PersistentCacheManager的destroy()可以刪除所有disk或者clustered的cache,這個方法必須在cache manager關閉close()之后
- destroyCache(String cacheName)清除指定cache,必須保證沒有其他cache manager在使用此manager
- put cache首先put到底層tier,上層通過get獲取底層cache,get cache則相反,所以底層越慢,put越慢
附加設置
- withSizeOfMaxObjectSize:設置單個Entry的大小限制
- withSizeOfMaxObjectGraph
- withDefaultSizeOfMaxObjectSize/withDefaultSizeOfMaxObjectGraph:在CacheManager 層設置而不一定要每個Cache都指定
疑問
-
Manage the CacheManager/Cahce/Entity
-
load balance
-
Thread Pools線程池
-
在不指定的時候server resource的時候,是否從defaultServerResource分配
-
get cache的thread pool
-
cache sync的時間間隔
-
重新啟動是否還有pre-active的信息
-
ClentReconnectWindow 到時間后 exp...()是否連接的上
-
在reconnect期間clinet是否重復發送請求,直到連接成功
-
SimpleKey/String key /Key Gernorator規則
-
EhCache和Terrcotta官網剩余部分
EhCache高級配置
Cluster server配置
- CacheManager通過terracotta://168.72.230.65:9411/app-name連接cluster server
- 創建CacheManager實例
- clusteredDedicated的大小必須比heap大,但是clusteredShared沒有這種檢查,但是也需要遵守金字塔原則
- getCache必須要有withCache或者createCache將cluster server的cache同步到本地CacheManager實例
- withCache或者createCache的ClusteredResourcePoolBuilder可以用.clustered()來繼承cluster server已有的cache的配置
- 連接同名CacheManager實例
- 一個cluster server只有一個同名(app-name名字相同)的CacheManager實例與之連接,其他的同名CacheManager必須連接已有的這個CacheManager
- with的ClusteringServiceConfigurationBuilder的配置必須相同
- withCache的ClusteredResourcePoolBuilder的配置必須相同,但其他與cluster無關的配置不需要相同
- withCache的ClusteredResourcePoolBuilder寫defaultServerResource與不寫是不同的配置,不能連接
- withCache有新增的cache,或者是有新的createCache時,無法鏈接,必須用autoCreate,在原來的CacheManager實例里創建新的cache並且連接
- withCache不需要with所有cluster的cache
- 不申明autoCreate或者expecting的時候,可以省略ClusteringServiceConfigurationBuilder的配置,cache 消費者使用這種方式
- 使用autoCreate或者expecting的時候,配置必須保持一致,維護麻煩
- 可以用單例模式全局變量等方式配置ClusteringServiceConfigurationBuilder
- 不同的項目可以用common.jar,但是麻煩
- 約定,但是維護麻煩
Cache配置的繼承
- clustered() : cache會繼承server端已有的cache的配置
- 第一個client的CacheManager用autoCreate,並且必須有Dedicated或者Shared pool配置
- 第二個client的CacheManager用expecting,並且用clustered不申明pool的配置
- createCache名字和第一個相同
- 好處:
- 簡化cache配置
- 減少分配pool的錯誤
- sizing計算只需要在一個client端進行
- 不必擔心配置匹配
EhCache的數據刷新機制采用Expiry機制
- timeToLiveExpiration:從創建cache entry開始計算,一段時間后過期
- timeToIdleExpiration : 從最后一次訪問cache entry開始計算,只有heap里的cache entry生效,訪問clustered cache不會被認為是對cache的一次access,這絕對是個bug
- noExpiration : 永不過期
- 還可以實現自定義的過期機制
- 過期的Entry會被從當前cache刪除,同時刪除clustered的Entry,過期並不會刪除cache
Consistency 一致性
- Eventual一致性模式下,任何對cluster的cache的修改,並不一定會及時反映到client端
- Strong一致性模式下,任何對cluster的cache的修改,都會及時的反應到client端,由於需要操作所有client端,所以put等操作可能會比較耗時
淘汰策略
Cache模式
- Cache-aside
- 先從cache拿,如果沒有,從SoR(比如數據庫)拿,然后存Cache,然后返回結果
- 寫入SoR的時候,可以同時寫入Cache
- Cache-as-SoR
- Read-through : 先從cache拿,如果沒有,cache調用loader從SoR拿,然后存cache,然后返回cache
- Write-through : 寫入cache請求來的時候,cache調用writer存SoR,同時存cache
- Write-behind : 與Write-through的區別在於寫入SoR是一個異步的操作,先寫cache,然后結束,令一個線程去寫SoR,writer-behind不支持retry,需要自己實現
- Cluster不支持Cache-as-SoR
- 需要實現接口CacheLoaderWriter
- 好處
- application端代碼簡單,可維護性高
- 使得消費者與SoR隔離
- 提高性能
- 壞處
- cache端代碼復雜
- 不同的CacheLoaderWriter實行需要創建不同的cache
事務管理
Thread Pools線程池
- OnDemandExecutionService : 在任務不指定線程池時使用的默認方式,每次都會創建一個新的線程池來執行任務,新的線程池對不同任務有對應的默認線程個數
- PooledExecutionService :自定義線程池,用PooledExecutionServiceConfiguration 來配置
- 自定義線程池用於惡劣環境下提高性能
EhCache類型詳解
CacheManager
CacheManagerBuilder
- 用於創建CacheManager
- with(Builder[CacheManagerConfiguration]) : 用於配置CacheManager,比如disk的路徑或者cluster的URL等
- using(ServiceCreationConfiguration)
- withCache("cahceName", Builder[CacheConfiguration]) : 定義/配置一個cache
- build(boolean init) : 創建一個初始化/未初始化的CacheManager實例
- withDefaultDiskStoreThreadPool(alias) : 默認磁盤化線程池
- withDefaultWriteBehindThreadPool(alias) :默認寫Cache線程池
- withDefaultEventListenersThreadPool(alias) :默認事件監聽線程池
ClusteringServiceConfigurationBuilder : with
- 用於配置CacheManager,從哪個cluster的哪些resource分配多少pool給CacheManager
- cluster(URI) : 創建cluster服務連接,設置CacheManager實例名字
- autoCreate() : 如果不存在一個cluster層的同名CacheManager實例,則創建一個,如果存在並且配置相同,便會建立連接,如果配置不同則CacheManager init會失敗
- expecting() : 如果存在並且配置相同,那么便會建立連接,否則CacheManager init會失敗
- autoCreate/expecting不申明,如果存在,不管配置相同不相同都會建立連接,否則CacheManager init會失敗
- defaultServerResource("server-resource-name") : 設置默認的tc offheap resource,供CacheManager使用
- resourcePool("pool-a", 28, MemoryUnit.MB, "secondary-server-resource") : 從tc的名字為secondary-server-resource的resource分配一個28MB的pool,如果不指定resource,從默認resource創建
PooledExecutionServiceConfigurationBuilder : using
- defaultPool(alias, minSize, maxSize) : 不指定thread pool時使用
- pool(alias, minSize, maxSize) : 添加一個thread pool,供任務使用
CacheManager
- init()
- close()
- destroy() : PersistentCacheManager的destroy()可以刪除所有disk或者clustered的cache,這個方法必須在所有連接此配置的cache manager close()之后
- createCache("cache-name",Builder[CacheConfiguration]) : 與CacheManagerBuilder的withCache一樣
- getCache("cache-name", String.class, String.class)
- removeCache("cache-name") : 刪除本地CacheManager里的cache對象,不會銷毀clustered的cache entry
- destroyCache("cache-name") : 刪除本地CacheManager里的cache對象,同時銷毀clustered的cache entry,但是不能有其他cache manager在使用這個cache
Cache
CacheConfigurationBuilder : withCache/crateCache
- 用於配置Cache
- newCacheConfigurationBuilder(Long.class, String.class, Builder[ResourcePools])
- withSizeOfMaxObjectSize(1, MemoryUnit.MB) : 設置單個Entry的最大size,超過大小的將無法存到cache
- withDefaultSizeOfMaxObjectGraph(2000) : ?
- add(Builder[ServiceConfiguration])
- add(new OffHeapDiskStoreConfiguration(2)) : disk的持久化的過程是一個分段的並發操作,默認16個線程並發,可以通過減少段落來減少並發
- withExpiry(ExpiryPolicy) : 過期策略
- withDiskStoreThreadPool(threadPoolAlias, concurrency) : 使用指定線程池和指定並發數來寫磁盤,與add(new OffHeapDiskStoreConfiguration(2)) 功能相似
- withEventListenersThreadPool(threadPoolAlias) :使用指定線程池給事件監聽任務,等同於add(new DefaultCacheEventDispatcherConfiguration(threadPoolAlias))
- withLoaderWriter(CacheLoaderWriter) : 實現CacheLoaderWriter來實現Cache-throught模式
ResourcePoolsBuilder : CacheConfigurationBuilder
- heap(10, EntryUnit.ENTRIES) : 最多10個entry
- heap(10, MemoryUnit.MB) : 最多10MB
- offheap(10, MemoryUnit.MB) :只能是MemoryUnit
- disk(20, MemoryUnit.MB, true) :只能是MemoryUnit,Persistence=true 意思是重啟JVM時cache會被還原,默認false
- with(ResourcePool) : 添加一個ResourcePool,與ClusteredResourcePoolBuilder合用,定義cluster層
ClusteredResourcePoolBuilder : with
- 所有方法都是返回一個ResourcePool,而不是返回Builder
- clusteredDedicated("primary-server-resource", 128, MemoryUnit.MB) : 為Cache從指定server resource分配一個專用的resource pool
- clusteredShared("pool-b") : 為Cache指定一個可分享的resource pool,這個resource pool能被其他Cache使用,當一個Cache從一個shared pool中分配了一定內存空間后,這些空間不會被釋放也無法分配給其他Cache
- clustered() : cache會繼承server端已有的cache的配置
- 第一個client的CacheManager用autoCreate,並且必須有Dedicated或者Shared pool配置
- 第二個client的CacheManager用expecting,並且用clustered不申明pool的配置
- createCache名字和第一個相同
- 好處: 簡化cache配置,減少分配pool的錯誤,sizing計算只需要在一個client端進行,不必擔心配置匹配
ClusteredStoreConfigurationBuilder : add
- 與CacheConfigurationBuilder 的add合用
- withConsistency(Consistency.EVENTUAL) : Eventual一致性模式下,任何對cluster的cache的修改,並不一定會及時反映到client端
- withConsistency(Consistency.STRONG) : Strong一致性模式下,任何對cluster的cache的修改,都會及時的反應到client端,由於需要操作所有client端,所以put等操作可能會比較耗時
ExpiryPolicyBuilder : withExpiry
- timeToLiveExpiration(Duration) :從創建cache entry開始計算,一段時間后過期
- timeToIdleExpiration(Duration) : 從最后一次訪問cache entry開始計算
- noExpiration() : 永不過期,默認設置
WriteBehindConfigurationBuilder :add
- newBatchedWriteBehindConfiguration(2, TimeUnit.SECONDS, 5) :批處理滿5個后執行,最多延遲2秒,2秒后批處理不滿5個也執行
- queueSize(3) :單個隊列最多積壓3個write-behind的bathcing操作,否則無法進行其他cache操作
- concurrencyLevel(2) :最多2個並發,並且有兩個隊列, 所以最多積壓bathcing操作數=concurrencyLevelqueueSize=23=6,最多積壓writhe數=concurrencyLevelqueueSizebatchSize=235=30
- enableCoalescing() : 開啟合並操作,使得對單個cache entry的update在一次批處理中只執行一次,只有最晚的一次會發給CacheLoaderWriter
- write-behind提高write cache和write SoR的性能
CacheEventListenerConfigurationBuilder : add
Ehcache
- cache的key,value必須implements Serializable
- forEach()/iterator()方法沒有被實現,也就是無法遍歷cache里的所有Entry
Terracotta Server
Getting Start
- https://documentation.softwareag.com/onlinehelp/Rohan/tc-ehcache_10-2/webhelp/index.html#page/tc-ehcache-webhelp/co-srv_cluster_architecture.html
- Clustered Cache由Terracotta提供Cache服務
- Terracotta Server通過off-heap實現Cache存儲
Terracotta Server Array (TSA)的三種拓撲模式
- Single-server cluster
- High-availability cluster
- 只有一個stripe包含至少一個active和多個possive服務器
- 把所有server配到servers
- server name 不能相同
- 把所有server啟動
- Multi-stripe cluster
- 多個相互獨立的stripe,通過增加stripes可增加存儲容量
- stripe與stripe之間沒有聯系,緩存不同步,無法實現load balance
Active and Passive Servers
- Active server的職責
- Active 負責直接與client交互,當沒有active的時候client默認無限等待,沒有默認的time-out
- Active 負責傳遞數據給Passive用於同步數據
- Active 負責同步請加入的server的狀態,使其能STANDBY,表示數據完全同步
- Active的選舉規則
- 第一個啟動的為Active,其他只是Passive
- 當Active下線,會通過以下情況打分決定
- 只有State[ PASSIVE-STANDBY ]的server有資格成為active
- server處理的交易量
- server啟動時間
- 隨機選擇一個,如果兩個server達成平局
- Client如何連接到Active server
- 除非出現網絡異常等情況,所有server之間都相互保持着溝通的通道
- 連接stripe的任意一台server1的時候,client會通過這些通道遍歷的嘗試連接所有的server,直到找到會給出回應的active server
- 隨后client會保持連接這台server,直到server下線,並且client會保存這台active server的信息
- 當active server下線,client會繼續通過這些通道尋找下一台active server,並且保存這台active server的信息
- 如果client最開始連接的server1出現問題,無法通過它的通道尋找active server的時候,client會通過所有保存過的pre-active server的通道去尋找
- 這樣的機制就決定了client最好使用最后啟動的passive作為嘗試連接的server,決不要用active的server,當active下線的時候,最好能馬上充啟
- 多個stripe之間不能共用Passive
Failover Tuning故障轉移策略(參考CAP定理)
- Client Reconnect Window
- 當發生故障, client需要重新連接到新的active server,轉換成功之前,client拿不到連接
- 在全部client重新連接或者Reconnect Window time-out之前,新的active server會暫停所有響應,即使一個用戶發生問題,所以用戶都需要等待至Reconnect Window time-out
- Client Reconnect Window默認120s, 之后會清除所有沒有重連的client
- 在Client Reconnect Window之后連接的client,都會以新用戶的方式重新創建一個新的連接
- tc-config.xml配置servers:client-reconnect-window
- 在重新連接之后,active server第一次會重新發送client上次請求的數據
- availability / consistency
- 當stripe的server由於網絡等異常被分割成兩個set,選擇availability 或者consistency之一會產生不一樣的策略
- availability
- 沒有active的部分,會選舉一台passive成為active,這時候兩個set獨立開來,都可能為client提供服務,造成數據不一致
- 由於連接任意set服務器的client都能找到active,所以不會阻塞client的請求,但是不保證數據一致性
- consistency
- 擁有更多數的server的set會選舉一台active,其他set都變成passive
- 這樣會造成連接passive的set的client無法找到active,某些client請求無法保證
- 但是這樣能保證數據的一致性
- 只有兩台的strape的TSA沒有任何一台會被認為是多數的,所以不會有active,建議consistency使用奇數服務器
- 也可以使用第三方vote開完成active選舉
- 可以自己實現循環所有server來尋找active,保證可用性
啟動Terracotta Server
- config/tc-config.xml
- 同一個stripe的server共用同一個tc-config.xml,不同的stripe需要不同的tc-config.xml
- offheap-resources
- tsa-port : 默認9410
- tsa-group-port : 默認9430,如果不指定,會根據tsa-port自動設置,比如tsa-port=9411,那么tsa-group-port=9431
- ./bin/start-tc-server.sh -n server-name -f ../config/tc-config.xml
Terracotta 實現load balance
Terracotta Management and Monitoring
JCache using Ehache as Provider
Getting Start
- Maven Dependency
- javax.cache : cache-api
- classpath 下有JCache和EhCache jar包
- classpath 下其他JCache Provider需要清除
- Caching.getCachingProvider();會自動去classpath 尋找Provider
- 如果還有其他JCache Provider,可以使用Caching.getCachingProvider(org.ehcache.jsr107.EhcacheCachingProvider)來指定
- 當使用EhCache Cluster的時候,無法使用EhCache的Cache對象鏈接JCache的Cache對象
Sample Code
//JCache CachingProvider and EhcacheCachingProvider
CachingProvider cachingProvider = Caching.getCachingProvider();
EhcacheCachingProvider ehcacheProvider = (EhcacheCachingProvider) cachingProvider;
//EhCache ServiceCreationConfiguration setting
ServiceCreationConfiguration<ClusteringService> clusteringServiceConfiguration = ClusteringServiceConfigurationBuilder
.cluster(URI.create("terracotta://168.72.230.65:9412/rates-ehcache"))
.build();
//EhCache CacheConfigurationsetting
CacheConfiguration<SimpleKey, String> cacheConfiguration = CacheConfigurationBuilder
.newCacheConfigurationBuilder(
SimpleKey.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(64, MemoryUnit.MB)
.with(ClusteredResourcePoolBuilder.clustered())
)
.build();
//the same as withCache in EhCache
Map<String, CacheConfiguration<?, ?>> caches = new HashMap<String, CacheConfiguration<?, ?>>();
caches.put("cache-bond", cacheConfiguration);
//Use EhCache Config to create JCache CacheManager
DefaultConfiguration configuration = new DefaultConfiguration(caches, ehcacheProvider.getDefaultClassLoader(), clusteringServiceConfiguration);
CacheManager cacheManager = ehcacheProvider.getCacheManager(ehcacheProvider.getDefaultURI(), configuration);
// User EhCache Cache config to create cache by JCache CacheManager
Configuration<String, String> conf = Eh107Configuration.fromEhcacheCacheConfiguration(cacheConfiguration);
cacheManager.createCache("cache-test", conf);
Differences
- heap-only 的cache存儲默認by-reference or by-value
- JCache默認by-value,只能使用Serializable 的key和value
- Ehcache默認by-reference
- Cache-through and compare-and-swap operations
-
putIfAbsent(K, V)等compare-and-swap操作下,JCache默認在put成功后才會
-
EhCache默認總是調用CacheLoaderWriter 去更新SoR,可以使用以下配置設置EhCache使用JCache一樣的設置
<service> <jsr107:defaults jsr-107-compliant-atomics="true"/> </service>
-
Spring Cache整合EhCache by JCache
Getting Start
- https://spring.io/guides/gs/caching/
- Maven Dependency
- org.springframework.boot : spring-boot-starter-cache
- CacheManager
- 默認使用ConcurrentHashMap
- @EnableCaching,@Cacheable, @CachePut and @CacheEvict
- 建議不要將Spring Cache annotation和JCache annotation混合使用
Spring Cache整合EhCache原理
- SpringCache按以下順序在classpath尋找CacheManager提供者
- Generic
- JCache (JSR-107)
- EhCache 2.x
- Hazelcast
- Infinispan
- Redis
- Guava
- Simple
- 除了按順序尋找,也可以用spring.cache.type指定
- 用JCache作為Spring Cache的CacheManager Provider
- 如果有javax.cache.CacheManager的Bean被定義,將被自動封裝在一個默認的org.springframework.cache.CacheManager的實現里面
- 這個功能由org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration的cacheManager方法實現
- 使用@Bean定義javax.cache.CacheManager, bean名字不能使cacheManager
- 不支持多個javax.cache.CacheManager的Bean存在
- 但它並不是一個org.springframework.cache.CacheManager,無法使用在@Cachable(cacheManager)上面
- @Cachable(cacheManager="cacheManager") : 這里的cacheManager就是JCacheCacheConfiguration的cacheManager方法的Bean
- 使用multiple CacheManager
- 創建javax.cache.CacheManager : jCacheManager
- 創建JCacheCacheManager Bean : new JCacheCacheManager(jCacheManager);
- new CacheManagerCustomizers(null).customize(cacheManager);
- 如果EhCache的cache配置中key,value的value不適String,而是自定義對象,會造成序列化問題
- producer使用Ehcache默認使用PlainJavaSerializer序列化方式
- web應用整合spring cache默認使用CompactJavaSerializer序列化方式
- 需要指定其中一種CacheConfiguration : .withValueSerializer(new PlainJavaSerializer
(ClassLoading.getDefaultClassLoader()))
Spring Cache和EhCache的使用
- 可以使用SpringCache創建Cache,用EhCache獲取Cache
- 也可以使用EhCache創建Cache,用SpringCache獲取Cache