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