通过 caffeine 中的 LoadingCache 对热点数据的缓存处理

概述

本文针对实际业务开发过程中遇到的 热点数据缓存 问题,引入了 caffeine 中的 LoadingCache 对象,并分析了具体的使用场景,最后将使用的完整过程中的关键代码罗列出来,具体内容如下

  1. caffeine 说明和 maven 依赖
  2. 使用场景分析
  3. 缓存对象的初始化
  4. 缓存数据的加载
  5. 缓存的使用

caffeine 说明和 maven 依赖

  • caffeine 提供了基于 内存 的缓存机制,没有持久化到磁盘的功能,意味着一旦应用关闭或者重启,内存中的缓冲对象需要全部重新加载
  • maven 依赖如下:
1
2
3
4
5
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.8.1</version>
</dependency>

使用场景分析

  1. 数据存在数据库中,在短期内会大量访问,比如 10qps,减少对数据库的查询,提高响应速度,提高用户体验
  2. 数据不会频繁变化,修改后期望 1 s 后缓存中的数据失效
  3. 可以限制数据库的访问频率,比如最大每秒一次(只要缓存不失效就不会访问数据库)
  4. 需要缓存的数据量不能太大

缓存对象的初始化

通过 @PostConstruct 修饰的方法来初始化缓存对象

  • SwitchCache
  1. 通过 @PostConstruct 修饰的方法来初始化缓存对象
  2. 通过 expireAfterWrite(1, TimeUnit.SECONDS) 设置缓存写入后 1s 后失效,相当于限制了最多每秒访问一次数据库
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
package com.ckjava.kqc.kone.cache;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import com.ckjava.kqc.kone.model.entity.others.KoneSwicth;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
* Function: 开关缓存
*
* @author chenkui
* @date 2021/8/19
*/
@Component
public class SwitchCache {
private static LoadingCache<String, KoneSwicth> switchCache;

private static final Logger logger = LoggerFactory.getLogger(SwitchCache.class);

@Autowired
private ApplicationContext mApplicationContext;

@PostConstruct
private void init() {
switchCache = CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build(new SwitchLoader(mApplicationContext));
}

public KoneSwicth get(final String switchKey) {
try {
System.out.println(switchCache.stats().toString());
return switchCache.get(switchKey);
} catch (final ExecutionException e) {
logger.error("SwitchCache get has error", e);
return null;
}
}
}

通过 @Configuration 创建 bean 初始化 LoadingCache 对象

或者配置到 Configuration 中,如下

  • CacheConfig: 缓冲对象初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.concurrent.TimeUnit;

/**
* Function: 缓存
*
* @author chenkui
* @date 2021/8/19
*/

@Configuration
public class CacheConfig {

@Bean
@Primary
public LoadingCache<String, KoneSwicth> switchCache(
final ApplicationContext mApplicationContext) {
return
CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.SECONDS)
.build(new SwitchCacheLoader(mApplicationContext));
}

}
  • SwitchCacheCom:缓冲方法封装
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
import com.google.common.cache.LoadingCache;
import com.ckjava.kqc.kone.model.entity.others.KoneSwicth;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* Function: 开关缓存
*
* @author chenkui
* @date 2021/8/19
*/
@Component
public class SwitchCacheCom {
private static final Logger logger = LoggerFactory.getLogger(SwitchCacheCom.class);
@Autowired
private LoadingCache<String, KoneSwicth> switchCache;

public KoneSwicth get(final String switchKey) {
try {
final KoneSwicth koneSwicth = switchCache.get(switchKey);
logger.info(switchCache.stats().toString());
return koneSwicth;
} catch (final Exception e) {
logger.error("SwitchCache get has error", e);
return null;
}
}
}

缓存数据的加载

  • SwitchLoader:用于在缓存中的数据失效后,再从数据库中获取,并自动存储到缓存中
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
package com.ckjava.kqc.kone.cache;

import com.google.common.cache.CacheLoader;
import com.ckjava.kqc.kone.dao.others.KoneSwicthMapper;
import com.ckjava.kqc.kone.model.entity.others.KoneSwicth;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

/**
* Function: switch 加载
*
* @author 执剑
* @date 2021/8/19
*/
public class SwitchLoader extends CacheLoader<String, KoneSwicth> {
private static final Logger logger = LoggerFactory.getLogger(SwitchLoader.class);

private final KoneSwicthMapper mKoneSwicthMapper;

public SwitchLoader(final ApplicationContext applicationContext) {
mKoneSwicthMapper = applicationContext.getBean(KoneSwicthMapper.class);
}

@Override
public KoneSwicth load(final String switchKey) {
logger.info("loading from db");
return mKoneSwicthMapper.findByUniq(KoneSwicth.builder().switchKey(switchKey).build());
}
}

缓存的使用

  • SwitchController:通过接口请求的时候直接使用缓存对象
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
import com.ckjava.kqc.kone.annotation.KoneResponseResult;
import com.ckjava.kqc.kone.cache.SwitchCacheCom;
import com.ckjava.kqc.kone.config.pattern.ApiPatterns;
import com.ckjava.kqc.kone.model.entity.others.KoneSwicth;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* @author chenkui
* @date 10:39 2021/8/19
*/
@Api(tags = "系统开关")
@RestController
@RequestMapping(value = ApiPatterns.PREFIX + "/switch")
public class SwitchController {

@Autowired
private SwitchCacheCom mSwitchCache;

/**
* 返回业务线列表
*/
@ApiOperation(value = "获取开关")
@KoneResponseResult("获取开关!")
@GetMapping()
public KoneSwicth getSwitch(
@RequestParam final String switchKey) {
return mSwitchCache.get(switchKey);
}
}

打赏

  • 微信

  • 支付宝