doc: 阅读redis sentinel文档
This commit is contained in:
@@ -1,33 +1,33 @@
|
||||
# Spring Cloud简介
|
||||
## eureka
|
||||
eureka是由netflix公司开源的一个服务注册与发现组件。eureka和其他一些同样由netflix公司开源的组建一起被整合为spring cloud netflix模块。
|
||||
### eureka和zookeeper的区别
|
||||
#### CAP原则
|
||||
CAP原则指的是在一个分布式系统中,一致性(Consistency)、可用性(Aailability)和分区容错性(Partition Tolerance),三者中最多只能实现两者,无法实现三者兼顾。
|
||||
- C:一致性表示分布式系统中,不同节点中的数据都要相同
|
||||
- A:可用性代表可以允许某一段时间内分布式系统中不同节点的数据可以不同,只要分布式系统能够保证最终数据的一致性。中途,允许分布式系统中的节点数据存在不一致的情形
|
||||
- P:及Partition Tolerance,通常情况下,节点之间的网络的断开被称之为network partition。故而,partition tolerance则是能够保证即使发生network partition,分布式系统中的节点也能够继续运行
|
||||
|
||||
通常P是必选的,而在满足P的前提下,A和C只能够满足一条,即AP或CP。
|
||||
#### zookeeper遵循的原则
|
||||
zookeeper遵循的是cp原则,如果zookeeper集群中的leader宕机,那么在新leader选举出来之前,zookeeper集群是拒绝向外提供服务的。
|
||||
#### eureka遵循的原则
|
||||
和zookeeper不同,eureka遵循的是ap原则,这这意味着eureka允许多个节点之间存在数据不一致的情况,即使某个节点的宕机,eureka仍然能够向外提供服务。
|
||||
|
||||
### eureka使用
|
||||
可以通过向项目中添加eureka-server的依赖来启动一个eureka-server实例。eureka-server作为注册中心,会将其本身也作为一个服务注册到注册中心中。
|
||||
#### 注册实例id
|
||||
注册实例id由三部分组成,`主机名称:应用名称:端口号`构成了实例的id。每个实例id都唯一。
|
||||
#### eureka-server配置
|
||||
- eviction-interval-timer-in-ms:eureka-server会运行固定的scheduled task来清除过期的client,eviction-interval-timer-in-ms属性用于定义task之间的间隔,默认情况下该属性值为60s
|
||||
- renewal-percent-threshold:基于该属性,eureka来计算每分钟期望从所有客户端接受到的心跳数。根据eureka-server的自我保护机制,如果eureka-server收到的心跳数小于threshold,那么eureka-server会停止进行客户端实例的淘汰,直到接收到的心跳数大于threshold
|
||||
#### eureka-instance配置
|
||||
eureka-server-instance本身也作为一个instance注册到注册中心中,故而可以针对eureka-instance作一些配置。
|
||||
#### eureka集群
|
||||
eureka集群是去中心化的集群,没有主机和从机的概念,eureka节点会向集群中所有其他的节点广播数据的变动。
|
||||
## Ribbon
|
||||
Spring Cloud Ribbon是一个基于Http和Tcp的客户端负载均衡工具,基于Netflix Ribbon实现,Ribbon主要用于提供负载均衡算法和服务调用。Ribbon的客户端组件提供了一套完善的配置项,如超时和重试等。
|
||||
在通过Spring Cloud构建微服务时,Ribbon有两种使用方法,一种是和RedisTemplate结合使用,另一种是和OpenFegin相结合。
|
||||
## OpenFeign
|
||||
OpenFeign是一个远程调用组件,使用接口和注解以http的形式完成调用。
|
||||
Feign中集成了Ribbon,而Ribbon中则集成了eureka。
|
||||
# Spring Cloud简介
|
||||
## eureka
|
||||
eureka是由netflix公司开源的一个服务注册与发现组件。eureka和其他一些同样由netflix公司开源的组建一起被整合为spring cloud netflix模块。
|
||||
### eureka和zookeeper的区别
|
||||
#### CAP原则
|
||||
CAP原则指的是在一个分布式系统中,一致性(Consistency)、可用性(Aailability)和分区容错性(Partition Tolerance),三者中最多只能实现两者,无法实现三者兼顾。
|
||||
- C:一致性表示分布式系统中,不同节点中的数据都要相同
|
||||
- A:可用性代表可以允许某一段时间内分布式系统中不同节点的数据可以不同,只要分布式系统能够保证最终数据的一致性。中途,允许分布式系统中的节点数据存在不一致的情形
|
||||
- P:及Partition Tolerance,通常情况下,节点之间的网络的断开被称之为network partition。故而,partition tolerance则是能够保证即使发生network partition,分布式系统中的节点也能够继续运行
|
||||
|
||||
通常P是必选的,而在满足P的前提下,A和C只能够满足一条,即AP或CP。
|
||||
#### zookeeper遵循的原则
|
||||
zookeeper遵循的是cp原则,如果zookeeper集群中的leader宕机,那么在新leader选举出来之前,zookeeper集群是拒绝向外提供服务的。
|
||||
#### eureka遵循的原则
|
||||
和zookeeper不同,eureka遵循的是ap原则,这这意味着eureka允许多个节点之间存在数据不一致的情况,即使某个节点的宕机,eureka仍然能够向外提供服务。
|
||||
|
||||
### eureka使用
|
||||
可以通过向项目中添加eureka-server的依赖来启动一个eureka-server实例。eureka-server作为注册中心,会将其本身也作为一个服务注册到注册中心中。
|
||||
#### 注册实例id
|
||||
注册实例id由三部分组成,`主机名称:应用名称:端口号`构成了实例的id。每个实例id都唯一。
|
||||
#### eureka-server配置
|
||||
- eviction-interval-timer-in-ms:eureka-server会运行固定的scheduled task来清除过期的client,eviction-interval-timer-in-ms属性用于定义task之间的间隔,默认情况下该属性值为60s
|
||||
- renewal-percent-threshold:基于该属性,eureka来计算每分钟期望从所有客户端接受到的心跳数。根据eureka-server的自我保护机制,如果eureka-server收到的心跳数小于threshold,那么eureka-server会停止进行客户端实例的淘汰,直到接收到的心跳数大于threshold
|
||||
#### eureka-instance配置
|
||||
eureka-server-instance本身也作为一个instance注册到注册中心中,故而可以针对eureka-instance作一些配置。
|
||||
#### eureka集群
|
||||
eureka集群是去中心化的集群,没有主机和从机的概念,eureka节点会向集群中所有其他的节点广播数据的变动。
|
||||
## Ribbon
|
||||
Spring Cloud Ribbon是一个基于Http和Tcp的客户端负载均衡工具,基于Netflix Ribbon实现,Ribbon主要用于提供负载均衡算法和服务调用。Ribbon的客户端组件提供了一套完善的配置项,如超时和重试等。
|
||||
在通过Spring Cloud构建微服务时,Ribbon有两种使用方法,一种是和RedisTemplate结合使用,另一种是和OpenFegin相结合。
|
||||
## OpenFeign
|
||||
OpenFeign是一个远程调用组件,使用接口和注解以http的形式完成调用。
|
||||
Feign中集成了Ribbon,而Ribbon中则集成了eureka。
|
||||
|
||||
@@ -1,234 +1,234 @@
|
||||
- [caffeine](#caffeine)
|
||||
- [Cache](#cache)
|
||||
- [注入](#注入)
|
||||
- [手动](#手动)
|
||||
- [Loading](#loading)
|
||||
- [异步(手动)](#异步手动)
|
||||
- [Async Loading](#async-loading)
|
||||
- [淘汰](#淘汰)
|
||||
- [基于时间的](#基于时间的)
|
||||
- [基于时间的淘汰策略](#基于时间的淘汰策略)
|
||||
- [基于引用的淘汰策略](#基于引用的淘汰策略)
|
||||
- [移除](#移除)
|
||||
- [removal listener](#removal-listener)
|
||||
- [compute](#compute)
|
||||
- [统计](#统计)
|
||||
- [cleanup](#cleanup)
|
||||
- [软引用和弱引用](#软引用和弱引用)
|
||||
- [weakKeys](#weakkeys)
|
||||
- [weakValues](#weakvalues)
|
||||
- [softValues](#softvalues)
|
||||
|
||||
|
||||
# caffeine
|
||||
caffeine是一个高性能的java缓存库,其几乎能够提供最佳的命中率。
|
||||
cache类似于ConcurrentMap,但并不完全相同。在ConcurrentMap中,会维护所有添加到其中的元素,直到元素被显式移除;而Cache则是可以通过配置来自动的淘汰元素,从而限制cache的内存占用。
|
||||
## Cache
|
||||
### 注入
|
||||
Cache提供了如下的注入策略
|
||||
#### 手动
|
||||
```java
|
||||
Cache<Key, Graph> cache = Caffeine.newBuilder()
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.maximumSize(10_000)
|
||||
.build();
|
||||
|
||||
// Lookup an entry, or null if not found
|
||||
Graph graph = cache.getIfPresent(key);
|
||||
// Lookup and compute an entry if absent, or null if not computable
|
||||
graph = cache.get(key, k -> createExpensiveGraph(key));
|
||||
// Insert or update an entry
|
||||
cache.put(key, graph);
|
||||
// Remove an entry
|
||||
cache.invalidate(key);
|
||||
```
|
||||
Cache接口可以显示的操作缓存条目的获取、失效、更新。
|
||||
条目可以直接通过cache.put(key,value)来插入到缓存中,该操作会覆盖已存在的key对应的条目。
|
||||
也可以使用cache.get(key,k->value)的形式来对缓存进行插入,该方法会在缓存中查找key对应的条目,如果不存在,会调用k->value来进行计算并将计算后的将计算后的结果插入到缓存中。该操作是原子的。如果该条目不可计算,会返回null,如果计算过程中发生异常,则是会抛出异常。
|
||||
除上述方法外,也可以通过cache.asMap()返回map对象,并且调用ConcurrentMap中的接口来对缓存条目进行修改。
|
||||
#### Loading
|
||||
```java
|
||||
// build方法可以指定一个CacheLoader参数
|
||||
LoadingCache<Key, Graph> cache = Caffeine.newBuilder()
|
||||
.maximumSize(10_000)
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
|
||||
// Lookup and compute an entry if absent, or null if not computable
|
||||
Graph graph = cache.get(key);
|
||||
// Lookup and compute entries that are absent
|
||||
Map<Key, Graph> graphs = cache.getAll(keys);
|
||||
```
|
||||
LoadingCache和CacheLoader相关联。
|
||||
可以通过getAll方法来执行批量查找,默认情况下,getAll方法会为每个cache中不存在的key向CacheLoader.load发送一个请求。当批量查找比许多单独的查找效率更加高时,可以重写CacheLoader.loadAll方法。
|
||||
#### 异步(手动)
|
||||
```java
|
||||
AsyncCache<Key, Graph> cache = Caffeine.newBuilder()
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.maximumSize(10_000)
|
||||
.buildAsync();
|
||||
|
||||
// Lookup an entry, or null if not found
|
||||
CompletableFuture<Graph> graph = cache.getIfPresent(key);
|
||||
// Lookup and asynchronously compute an entry if absent
|
||||
graph = cache.get(key, k -> createExpensiveGraph(key));
|
||||
// Insert or update an entry
|
||||
cache.put(key, graph);
|
||||
// Remove an entry
|
||||
cache.synchronous().invalidate(key);
|
||||
```
|
||||
AsyncCache允许异步的计算条目,并且返回CompletableFuture。
|
||||
AsyncCache可以调用synchronous方法来提供同步的视图。
|
||||
默认情况下executor是ForkJoinPool.commonPool(),可以通过Caffeine.executor(threadPool)来进行覆盖。
|
||||
#### Async Loading
|
||||
```java
|
||||
AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()
|
||||
.maximumSize(10_000)
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
// Either: Build with a synchronous computation that is wrapped as asynchronous
|
||||
.buildAsync(key -> createExpensiveGraph(key));
|
||||
// Or: Build with a asynchronous computation that returns a future
|
||||
.buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
|
||||
|
||||
// Lookup and asynchronously compute an entry if absent
|
||||
CompletableFuture<Graph> graph = cache.get(key);
|
||||
// Lookup and asynchronously compute entries that are absent
|
||||
CompletableFuture<Map<Key, Graph>> graphs = cache.getAll(keys);
|
||||
```
|
||||
AsyncLoadingCache是一个AsyncCache加上一个AsyncCacheLoader。
|
||||
同样地,AsyncCacheLoader支持重写load和loadAll方法。
|
||||
### 淘汰
|
||||
Caffeine提供了三种类型的淘汰:基于size的,基于时间的,基于引用的。
|
||||
#### 基于时间的
|
||||
```java
|
||||
// Evict based on the number of entries in the cache
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.maximumSize(10_000)
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
|
||||
// Evict based on the number of vertices in the cache
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.maximumWeight(10_000)
|
||||
.weigher((Key key, Graph graph) -> graph.vertices().size())
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
```
|
||||
如果你的缓存不应该超过特定的容量限制,应该使用`Caffeine.maximumSize(long)`方法。该缓存会对不常用的条目进行淘汰。
|
||||
如果每条记录的权重不同,那么可以通过`Caffeine.weigher(Weigher)`指定一个权重计算方法,并且通过`Caffeine.maximumWeight(long)`指定缓存最大的权重值。
|
||||
#### 基于时间的淘汰策略
|
||||
```java
|
||||
// Evict based on a fixed expiration policy
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.expireAfterAccess(5, TimeUnit.MINUTES)
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
|
||||
// Evict based on a varying expiration policy
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.expireAfter(new Expiry<Key, Graph>() {
|
||||
public long expireAfterCreate(Key key, Graph graph, long currentTime) {
|
||||
// Use wall clock time, rather than nanotime, if from an external resource
|
||||
long seconds = graph.creationDate().plusHours(5)
|
||||
.minus(System.currentTimeMillis(), MILLIS)
|
||||
.toEpochSecond();
|
||||
return TimeUnit.SECONDS.toNanos(seconds);
|
||||
}
|
||||
public long expireAfterUpdate(Key key, Graph graph,
|
||||
long currentTime, long currentDuration) {
|
||||
return currentDuration;
|
||||
}
|
||||
public long expireAfterRead(Key key, Graph graph,
|
||||
long currentTime, long currentDuration) {
|
||||
return currentDuration;
|
||||
}
|
||||
})
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
```
|
||||
caffine提供三种方法来进行基于时间的淘汰:
|
||||
- expireAfterAccess(long, TimeUnit):基于上次读写操作过后的时间来进行淘汰
|
||||
- expireAfterWrite(long, TimeUnit):基于创建时间、或上次写操作执行的时间来进行淘汰
|
||||
- expireAfter(Expire):基于自定义的策略来进行淘汰
|
||||
过期在写操作之间周期性的进行触发,偶尔也会在读操作之间进行出发。调度和发送过期事件都是在o(1)时间之内完成的。
|
||||
为了及时过期,而不是通过缓存活动来触发过期,可以通过`Caffeine.scheuler(scheduler)`来指定调度线程
|
||||
#### 基于引用的淘汰策略
|
||||
```java
|
||||
// Evict when neither the key nor value are strongly reachable
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.weakKeys()
|
||||
.weakValues()
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
|
||||
// Evict when the garbage collector needs to free memory
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.softValues()
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
```
|
||||
caffeine允许设置cache支持垃圾回收,通过使用为key指定weak reference,为value制定soft reference
|
||||
|
||||
### 移除
|
||||
可以通过下述方法显式移除条目:
|
||||
```java
|
||||
// individual key
|
||||
cache.invalidate(key)
|
||||
// bulk keys
|
||||
cache.invalidateAll(keys)
|
||||
// all keys
|
||||
cache.invalidateAll()
|
||||
```
|
||||
#### removal listener
|
||||
```java
|
||||
Cache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.evictionListener((Key key, Graph graph, RemovalCause cause) ->
|
||||
System.out.printf("Key %s was evicted (%s)%n", key, cause))
|
||||
.removalListener((Key key, Graph graph, RemovalCause cause) ->
|
||||
System.out.printf("Key %s was removed (%s)%n", key, cause))
|
||||
.build();
|
||||
```
|
||||
在entry被移除时,可以指定listener来执行一系列操作,通过`Caffeine.removalListener(RemovalListener)`。操作是通过Executor异步执行的。
|
||||
当想要在缓存失效之后同步执行操作时,可以使用`Caffeine.evictionListener(RemovalListener)`.该监听器将会在`RemovalCause.wasEvicted()`时被触发
|
||||
### compute
|
||||
通过compute,caffeine可以在entry创建、淘汰、更新时,原子的执行一系列操作:
|
||||
```java
|
||||
Cache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.evictionListener((Key key, Graph graph, RemovalCause cause) -> {
|
||||
// atomically intercept the entry's eviction
|
||||
}).build();
|
||||
|
||||
graphs.asMap().compute(key, (k, v) -> {
|
||||
Graph graph = createExpensiveGraph(key);
|
||||
... // update a secondary store
|
||||
return graph;
|
||||
});
|
||||
```
|
||||
### 统计
|
||||
通过`Caffeine.recordStats()`方法,可以启用统计信息的收集,`cache.stats()`方法将返回一个CacheStats对象,提供如下接口:
|
||||
- hitRate():返回请求命中率
|
||||
- evictionCount():cache淘汰次数
|
||||
- averageLoadPenalty():load新值花费的平均时间
|
||||
```java
|
||||
Cache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.maximumSize(10_000)
|
||||
.recordStats()
|
||||
.build();
|
||||
```
|
||||
### cleanup
|
||||
默认情况下,Caffine并不会在自动淘汰entry后或entry失效之后立即进行清理,而是在写操作之后执行少量的清理工作,如果写操作很少,则是偶尔在读操作后执行少量读操作。
|
||||
如果你的缓存是高吞吐量的,那么不必担心过期缓存的清理,如果你的缓存读写操作都比较少,那么需要新建一个外部线程来调用`Cache.cleanUp()`来进行缓存清理。
|
||||
```java
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.scheduler(Scheduler.systemScheduler())
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
```
|
||||
scheduler可以用于及时的清理过期缓存
|
||||
|
||||
### 软引用和弱引用
|
||||
caffeine支持基于引用来设置淘汰策略。caffeine支持针对key和value使用弱引用,针对value使用软引用。
|
||||
#### weakKeys
|
||||
`caffeine.weakKeys()`存储使用弱引用的key,如果没有强引用指向key,那么key将会被垃圾回收。垃圾回收时只会比较对象地址,故而整个缓存在比较key时会通过`==`而不是`equals`来进行比较
|
||||
#### weakValues
|
||||
`caffeine.weakValues()`存储使用弱引用的value,如果没有强引用指向value,value会被垃圾回收。同样地,在整个cache中,会使用`==`而不是`equals`来对value进行比较
|
||||
#### softValues
|
||||
软引用的value通常会在垃圾回收时按照lru的方式进行回收,根据内存情况决定是否进行回收。由于使用软引用会带来性能问题,通常更推荐使用基于max-size的回收策略。
|
||||
同样地,基于软引用的value在整个缓存中会通过`==`而不是`equals()`来进行垃圾回收。
|
||||
- [caffeine](#caffeine)
|
||||
- [Cache](#cache)
|
||||
- [注入](#注入)
|
||||
- [手动](#手动)
|
||||
- [Loading](#loading)
|
||||
- [异步(手动)](#异步手动)
|
||||
- [Async Loading](#async-loading)
|
||||
- [淘汰](#淘汰)
|
||||
- [基于时间的](#基于时间的)
|
||||
- [基于时间的淘汰策略](#基于时间的淘汰策略)
|
||||
- [基于引用的淘汰策略](#基于引用的淘汰策略)
|
||||
- [移除](#移除)
|
||||
- [removal listener](#removal-listener)
|
||||
- [compute](#compute)
|
||||
- [统计](#统计)
|
||||
- [cleanup](#cleanup)
|
||||
- [软引用和弱引用](#软引用和弱引用)
|
||||
- [weakKeys](#weakkeys)
|
||||
- [weakValues](#weakvalues)
|
||||
- [softValues](#softvalues)
|
||||
|
||||
|
||||
# caffeine
|
||||
caffeine是一个高性能的java缓存库,其几乎能够提供最佳的命中率。
|
||||
cache类似于ConcurrentMap,但并不完全相同。在ConcurrentMap中,会维护所有添加到其中的元素,直到元素被显式移除;而Cache则是可以通过配置来自动的淘汰元素,从而限制cache的内存占用。
|
||||
## Cache
|
||||
### 注入
|
||||
Cache提供了如下的注入策略
|
||||
#### 手动
|
||||
```java
|
||||
Cache<Key, Graph> cache = Caffeine.newBuilder()
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.maximumSize(10_000)
|
||||
.build();
|
||||
|
||||
// Lookup an entry, or null if not found
|
||||
Graph graph = cache.getIfPresent(key);
|
||||
// Lookup and compute an entry if absent, or null if not computable
|
||||
graph = cache.get(key, k -> createExpensiveGraph(key));
|
||||
// Insert or update an entry
|
||||
cache.put(key, graph);
|
||||
// Remove an entry
|
||||
cache.invalidate(key);
|
||||
```
|
||||
Cache接口可以显示的操作缓存条目的获取、失效、更新。
|
||||
条目可以直接通过cache.put(key,value)来插入到缓存中,该操作会覆盖已存在的key对应的条目。
|
||||
也可以使用cache.get(key,k->value)的形式来对缓存进行插入,该方法会在缓存中查找key对应的条目,如果不存在,会调用k->value来进行计算并将计算后的将计算后的结果插入到缓存中。该操作是原子的。如果该条目不可计算,会返回null,如果计算过程中发生异常,则是会抛出异常。
|
||||
除上述方法外,也可以通过cache.asMap()返回map对象,并且调用ConcurrentMap中的接口来对缓存条目进行修改。
|
||||
#### Loading
|
||||
```java
|
||||
// build方法可以指定一个CacheLoader参数
|
||||
LoadingCache<Key, Graph> cache = Caffeine.newBuilder()
|
||||
.maximumSize(10_000)
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
|
||||
// Lookup and compute an entry if absent, or null if not computable
|
||||
Graph graph = cache.get(key);
|
||||
// Lookup and compute entries that are absent
|
||||
Map<Key, Graph> graphs = cache.getAll(keys);
|
||||
```
|
||||
LoadingCache和CacheLoader相关联。
|
||||
可以通过getAll方法来执行批量查找,默认情况下,getAll方法会为每个cache中不存在的key向CacheLoader.load发送一个请求。当批量查找比许多单独的查找效率更加高时,可以重写CacheLoader.loadAll方法。
|
||||
#### 异步(手动)
|
||||
```java
|
||||
AsyncCache<Key, Graph> cache = Caffeine.newBuilder()
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.maximumSize(10_000)
|
||||
.buildAsync();
|
||||
|
||||
// Lookup an entry, or null if not found
|
||||
CompletableFuture<Graph> graph = cache.getIfPresent(key);
|
||||
// Lookup and asynchronously compute an entry if absent
|
||||
graph = cache.get(key, k -> createExpensiveGraph(key));
|
||||
// Insert or update an entry
|
||||
cache.put(key, graph);
|
||||
// Remove an entry
|
||||
cache.synchronous().invalidate(key);
|
||||
```
|
||||
AsyncCache允许异步的计算条目,并且返回CompletableFuture。
|
||||
AsyncCache可以调用synchronous方法来提供同步的视图。
|
||||
默认情况下executor是ForkJoinPool.commonPool(),可以通过Caffeine.executor(threadPool)来进行覆盖。
|
||||
#### Async Loading
|
||||
```java
|
||||
AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()
|
||||
.maximumSize(10_000)
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
// Either: Build with a synchronous computation that is wrapped as asynchronous
|
||||
.buildAsync(key -> createExpensiveGraph(key));
|
||||
// Or: Build with a asynchronous computation that returns a future
|
||||
.buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
|
||||
|
||||
// Lookup and asynchronously compute an entry if absent
|
||||
CompletableFuture<Graph> graph = cache.get(key);
|
||||
// Lookup and asynchronously compute entries that are absent
|
||||
CompletableFuture<Map<Key, Graph>> graphs = cache.getAll(keys);
|
||||
```
|
||||
AsyncLoadingCache是一个AsyncCache加上一个AsyncCacheLoader。
|
||||
同样地,AsyncCacheLoader支持重写load和loadAll方法。
|
||||
### 淘汰
|
||||
Caffeine提供了三种类型的淘汰:基于size的,基于时间的,基于引用的。
|
||||
#### 基于时间的
|
||||
```java
|
||||
// Evict based on the number of entries in the cache
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.maximumSize(10_000)
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
|
||||
// Evict based on the number of vertices in the cache
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.maximumWeight(10_000)
|
||||
.weigher((Key key, Graph graph) -> graph.vertices().size())
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
```
|
||||
如果你的缓存不应该超过特定的容量限制,应该使用`Caffeine.maximumSize(long)`方法。该缓存会对不常用的条目进行淘汰。
|
||||
如果每条记录的权重不同,那么可以通过`Caffeine.weigher(Weigher)`指定一个权重计算方法,并且通过`Caffeine.maximumWeight(long)`指定缓存最大的权重值。
|
||||
#### 基于时间的淘汰策略
|
||||
```java
|
||||
// Evict based on a fixed expiration policy
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.expireAfterAccess(5, TimeUnit.MINUTES)
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
|
||||
// Evict based on a varying expiration policy
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.expireAfter(new Expiry<Key, Graph>() {
|
||||
public long expireAfterCreate(Key key, Graph graph, long currentTime) {
|
||||
// Use wall clock time, rather than nanotime, if from an external resource
|
||||
long seconds = graph.creationDate().plusHours(5)
|
||||
.minus(System.currentTimeMillis(), MILLIS)
|
||||
.toEpochSecond();
|
||||
return TimeUnit.SECONDS.toNanos(seconds);
|
||||
}
|
||||
public long expireAfterUpdate(Key key, Graph graph,
|
||||
long currentTime, long currentDuration) {
|
||||
return currentDuration;
|
||||
}
|
||||
public long expireAfterRead(Key key, Graph graph,
|
||||
long currentTime, long currentDuration) {
|
||||
return currentDuration;
|
||||
}
|
||||
})
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
```
|
||||
caffine提供三种方法来进行基于时间的淘汰:
|
||||
- expireAfterAccess(long, TimeUnit):基于上次读写操作过后的时间来进行淘汰
|
||||
- expireAfterWrite(long, TimeUnit):基于创建时间、或上次写操作执行的时间来进行淘汰
|
||||
- expireAfter(Expire):基于自定义的策略来进行淘汰
|
||||
过期在写操作之间周期性的进行触发,偶尔也会在读操作之间进行出发。调度和发送过期事件都是在o(1)时间之内完成的。
|
||||
为了及时过期,而不是通过缓存活动来触发过期,可以通过`Caffeine.scheuler(scheduler)`来指定调度线程
|
||||
#### 基于引用的淘汰策略
|
||||
```java
|
||||
// Evict when neither the key nor value are strongly reachable
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.weakKeys()
|
||||
.weakValues()
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
|
||||
// Evict when the garbage collector needs to free memory
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.softValues()
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
```
|
||||
caffeine允许设置cache支持垃圾回收,通过使用为key指定weak reference,为value制定soft reference
|
||||
|
||||
### 移除
|
||||
可以通过下述方法显式移除条目:
|
||||
```java
|
||||
// individual key
|
||||
cache.invalidate(key)
|
||||
// bulk keys
|
||||
cache.invalidateAll(keys)
|
||||
// all keys
|
||||
cache.invalidateAll()
|
||||
```
|
||||
#### removal listener
|
||||
```java
|
||||
Cache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.evictionListener((Key key, Graph graph, RemovalCause cause) ->
|
||||
System.out.printf("Key %s was evicted (%s)%n", key, cause))
|
||||
.removalListener((Key key, Graph graph, RemovalCause cause) ->
|
||||
System.out.printf("Key %s was removed (%s)%n", key, cause))
|
||||
.build();
|
||||
```
|
||||
在entry被移除时,可以指定listener来执行一系列操作,通过`Caffeine.removalListener(RemovalListener)`。操作是通过Executor异步执行的。
|
||||
当想要在缓存失效之后同步执行操作时,可以使用`Caffeine.evictionListener(RemovalListener)`.该监听器将会在`RemovalCause.wasEvicted()`时被触发
|
||||
### compute
|
||||
通过compute,caffeine可以在entry创建、淘汰、更新时,原子的执行一系列操作:
|
||||
```java
|
||||
Cache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.evictionListener((Key key, Graph graph, RemovalCause cause) -> {
|
||||
// atomically intercept the entry's eviction
|
||||
}).build();
|
||||
|
||||
graphs.asMap().compute(key, (k, v) -> {
|
||||
Graph graph = createExpensiveGraph(key);
|
||||
... // update a secondary store
|
||||
return graph;
|
||||
});
|
||||
```
|
||||
### 统计
|
||||
通过`Caffeine.recordStats()`方法,可以启用统计信息的收集,`cache.stats()`方法将返回一个CacheStats对象,提供如下接口:
|
||||
- hitRate():返回请求命中率
|
||||
- evictionCount():cache淘汰次数
|
||||
- averageLoadPenalty():load新值花费的平均时间
|
||||
```java
|
||||
Cache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.maximumSize(10_000)
|
||||
.recordStats()
|
||||
.build();
|
||||
```
|
||||
### cleanup
|
||||
默认情况下,Caffine并不会在自动淘汰entry后或entry失效之后立即进行清理,而是在写操作之后执行少量的清理工作,如果写操作很少,则是偶尔在读操作后执行少量读操作。
|
||||
如果你的缓存是高吞吐量的,那么不必担心过期缓存的清理,如果你的缓存读写操作都比较少,那么需要新建一个外部线程来调用`Cache.cleanUp()`来进行缓存清理。
|
||||
```java
|
||||
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
|
||||
.scheduler(Scheduler.systemScheduler())
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.build(key -> createExpensiveGraph(key));
|
||||
```
|
||||
scheduler可以用于及时的清理过期缓存
|
||||
|
||||
### 软引用和弱引用
|
||||
caffeine支持基于引用来设置淘汰策略。caffeine支持针对key和value使用弱引用,针对value使用软引用。
|
||||
#### weakKeys
|
||||
`caffeine.weakKeys()`存储使用弱引用的key,如果没有强引用指向key,那么key将会被垃圾回收。垃圾回收时只会比较对象地址,故而整个缓存在比较key时会通过`==`而不是`equals`来进行比较
|
||||
#### weakValues
|
||||
`caffeine.weakValues()`存储使用弱引用的value,如果没有强引用指向value,value会被垃圾回收。同样地,在整个cache中,会使用`==`而不是`equals`来对value进行比较
|
||||
#### softValues
|
||||
软引用的value通常会在垃圾回收时按照lru的方式进行回收,根据内存情况决定是否进行回收。由于使用软引用会带来性能问题,通常更推荐使用基于max-size的回收策略。
|
||||
同样地,基于软引用的value在整个缓存中会通过`==`而不是`equals()`来进行垃圾回收。
|
||||
|
||||
Reference in New Issue
Block a user