0%

缓冲更新策略

近段时间在学习缓存相关知识的时候,看到了缓存更新策略,于是就根据自己的理解,写下这篇文章

分类
  • Cache Aside
  • Read / Write Though
  • Write Behind
Cache Aside
  1. 步骤

    1. 读请求未命中缓存,取数据库数据,并回写缓存
    2. 写请求先更新数据库,再让缓存失效
  2. 优点

    1. 实现简单,调用者可控制数据持久化的细节
  3. 缺点

    1. 上层需要同时管理缓存与持久化,调用较复杂
    2. 写请求与读请求并发,读请求持续时间比写请求长,可能会覆盖旧数据到缓存中
  4. 使用场景

    1. 允许缓存数据不准确的场景
    2. 因为并发情况下,可能造成脏数据的情况,所以 QPS 较低场景也可以适用
  5. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class CacheAside<T, K> implements CacheUpdate<T, K>{
private Map<K, T> map;

@Override
public T getData(K key) {
//if cache has data, return
return map.get(key);
}

@Override
public boolean updateData(K key, T data) {
map.remove(key, data);
return true;
}

@Override
public boolean addData(K key, T data) {
return Objects.nonNull(map.put(key, data));
}

@Override
public boolean removeData(K key) {
map.remove(key);
return true;
}

public CacheAside() {
map = new HashMap<>();
}
}
  1. 调用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class CacheAsideClient<T, K> implements CacheUpdateClient<T, K>{

public CacheUpdateFactory<T, K> factory = CacheUpdateFactory.getInstance();

private CacheUpdate<T, K> cacheUpdate;

private DatabaseOperation<T, K> databaseOperation;

@Override
public T getData(K key){
//get data from cache
T dataFromCache = cacheUpdate.getData(key);
//if cache haven't, get from database and put to cache
if(Objects.nonNull(dataFromCache)){
return dataFromCache;
}
T dataFromDatabase = databaseOperation.getData(key);
cacheUpdate.addData(key, dataFromDatabase);
return dataFromDatabase;
}

@Override
public boolean updateData(K key, T data){
//update data to database
boolean updateToDatabaseRes = databaseOperation.updateData(key, data);
if(updateToDatabaseRes){
//invalid cache data
return cacheUpdate.removeData(key);
}
return false;
}

@Override
public boolean addData(K key, T data){
//add data to database
return databaseOperation.addData(key, data);
}

@Override
public boolean removeData(K key){
//remove from database
boolean removeFromDatabaseRes = databaseOperation.removeData(key);
if(removeFromDatabaseRes){
//invalid cache data
return cacheUpdate.removeData(key);
}
return false;
}

public CacheAsideClient() {
cacheUpdate = factory.getObject(CacheUpdateEnum.CACHE_ASIDE);
databaseOperation = (DatabaseOperation<T, K>) new MockDatabaseOperation<T>();
}
}
Read / Write Though
  1. 步骤

    1. 读/写请求都只依赖缓存
    2. 缓存数据同步持久化
  2. 优点

    1. 上层对数据是否持久化/持久化实现无感
  3. 缺点

    1. 同步持久化性能较低,但能有效保证数据一致性
  4. 使用场景

    1. 性能要求不高的场景
  5. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class ReadOrWriteThough<T, K> implements CacheUpdate<T, K>{

private DatabaseOperation<T, K> databaseOperation;

private Map<K, T> map;

@Override
public T getData(K key) {
//if cache has data, return
if(map.containsKey(key)){
return map.get(key);
}
//get data from database and write to cache
T data = databaseOperation.getData(key);
map.put(key, data);
return data;
}

@Override
public boolean updateData(K key, T data) {
map.put(key, data);
return databaseOperation.updateData(key, data);
}

@Override
public boolean addData(K key, T data) {
map.put(key, data);
return databaseOperation.addData(key, data);
}

@Override
public boolean removeData(K key) {
map.remove(key);
return databaseOperation.removeData(key);
}

public ReadOrWriteThough() {
databaseOperation = (DatabaseOperation<T, K>) new MockDatabaseOperation<>();
map = new HashMap<>();
}
}
  1. 调用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ReadOrWriteThoughClient<T, K> implements CacheUpdateClient<T, K>{

private CacheUpdateFactory<T, K> factory = CacheUpdateFactory.getInstance();

private CacheUpdate<T, K> cacheUpdate;

@Override
public T getData(K key) {
return cacheUpdate.getData(key);
}

@Override
public boolean updateData(K key, T data) {
return cacheUpdate.updateData(key, data);
}

@Override
public boolean addData(K key, T data) {
return cacheUpdate.addData(key, data);
}

@Override
public boolean removeData(K key) {
return cacheUpdate.removeData(key);
}

public ReadOrWriteThoughClient() {
cacheUpdate = factory.getObject(CacheUpdateEnum.READ_WRITE_THOUGH);
}
}
Write Behind
  1. 步骤

    1. 读/写请求都只依赖缓存
    2. 缓存数据异步批量持久化
  2. 优点

    1. 上层对数据是否持久化/持久化实现无感
    2. 异步持久化,性能较 Read /Write Though 提高
  3. 缺点

    1. 异步持久化可能会导致数据丢失
  4. 使用场景

    1. 性能要求较高的场景
    2. 允许持久化数据丢失场景
  5. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class WriteBehind<T, K> implements CacheUpdate<T, K> {

private Map<K, T> map;

private DatabaseOperation<T, K> databaseOperation;

private ThreadPoolExecutor threadPoolExecutor;

@Override
public T getData(K key) {
if(map.containsKey(key)){
return map.get(key);
}
T data = databaseOperation.getData(key);
map.put(key, data);
return data;
}

@Override
public boolean updateData(K key, T data) {
map.put(key, data);
threadPoolExecutor.execute(() -> databaseOperation.updateData(key, data));
return true;
}

@Override
public boolean addData(K key, T data) {
map.put(key, data);
threadPoolExecutor.execute(() -> databaseOperation.addData(key, data));
return true;
}

@Override
public boolean removeData(K key) {
map.remove(key);
threadPoolExecutor.execute(() -> databaseOperation.removeData(key));
return true;
}

public WriteBehind() {
map = new HashMap<>();
databaseOperation = (DatabaseOperation<T, K>) new MockDatabaseOperation<>();
threadPoolExecutor = new ThreadPoolExecutor(5, 10, 1000, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), new ThreadPoolExecutor.CallerRunsPolicy());
}

}
  1. 调用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class WriteBehindClient<T, K> implements CacheUpdateClient<T, K>{

private CacheUpdateFactory<T, K> cacheUpdateFactory = CacheUpdateFactory.getInstance();

private CacheUpdate<T, K> cacheUpdate;

@Override
public T getData(K key) {
return cacheUpdate.getData(key);
}

@Override
public boolean updateData(K key, T data) {
return cacheUpdate.updateData(key, data);
}

@Override
public boolean addData(K key, T data) {
return cacheUpdate.addData(key, data);
}

@Override
public boolean removeData(K key) {
return cacheUpdate.removeData(key);
}

public WriteBehindClient() {
cacheUpdate = cacheUpdateFactory.getObject(CacheUpdateEnum.WRITE_BEHIND);
}
}
总结
分类 优点 缺点 使用场景
Cache Aside 1. 实现简单,调用者可控制数据持久化的细节 1. 写请求与读请求并发,读请求持续时间比写请求长,可能会覆盖旧数据到缓存中
2. 上层需要同时管理缓存与持久化,调用较复杂
1. 允许缓存数据不准确的场景
2. 因为并发情况下,可能造成脏数据的情况,所以 QPS 较低场景也可以适用
Read / Write Though 1. 上层对数据是否持久化/持久化实现无感 1. 同步持久化性能较低,但能有效保证数据一致性 1. 性能要求不高的场景
Write Behind 1. 上层对数据是否持久化/持久化实现无感
2. 异步持久化,性能较 Read /Write Though 提高
1. 异步持久化可能会导致数据丢失 1. 性能要求较高的场景
2. 允许持久化数据丢失场景

本文首发于cartoon的博客

转载请注明出处:https://cartoonyu.github.io