背景
在平时的工作做中,为了提升系统的吞吐量,减少对数据库的访问,我们会使用缓存,优化系统的查询接口,这样可以极大的提升我们的系统吞吐量
我们可以简单地看一下增加了缓存的流程图
对于一般的应用,使用一级缓存已经可以满足我们的要求,但是,对于那些需要支持上万,十万,甚至百万并发的应用,使用一级缓存已经无法满足我们的要求
一级缓存存在的问题
- 无法通过扩容应用节点来解决请求量不断增加的问题,不满足云原生的要求
- 每次都需要通过网络请求,浪费了很多时间
那么我们应该如何解决一级缓存存在的问题的,接下来,二级缓存应运而生了。
我们来简单地看一下二级缓存的流程图
二级缓存是在应用本地增加了一层内存缓存,这样,既解决了访问缓存的网络请求问题,又解决了通过扩容应用节点承接更多请求的问题。
优点和缺点
一级缓存使用本地缓存节省了网络请求,访问速度更快,并且通过扩容节点就可以承接更多的流量。但是它同时也存在一些其他的问题,例如缓存一致性问题,当我们使用两级缓存的时候,这个问题会变得更加突出,我们不仅要保持这两级缓存的一致,通过还需要保证缓存和数据库的一致性
对于分布式缓存,由于一级缓存是存储在每个应用实例上,因此,还存在一级缓存之间的一致性问题,当一个节点的一级缓存更新后,还需要通知其他节点更新一级缓存。
代码实现
/**
* 双重缓存切面
*
* @author haidechizi
*/
@Aspect
public class DoubleCacheAspect {
@Pointcut("@annotation(com.haidechizi.doublecache.annotation.DoubleCache)")
public void cacheAspect() {
}
@Around("cacheAspect()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DoubleCache doubleCache = method.getAnnotation(DoubleCache.class);
ProcessService processService = ProcessHolder.getProcessService(doubleCache.type().getCode());
ProcessContext processContext = new AopProcessContext(point);
processService.process(processContext);
return processContext.getValue();
}
}
/**
* 基类
*/
public abstract class BaseProcessService implements ProcessService {
@Autowired
protected LocalCacheService localCacheService;
@Autowired
protected RemoteCacheService remoteCacheService;
@PostConstruct
public void init() {
ProcessHolder.addProcessService(name(), this);
}
@Override
public void process(ProcessContext processContext) throws Throwable {
cacheKey(processContext);
doProcess(processContext);
}
/**
* do
*
* @param processContext
*/
protected abstract void doProcess(ProcessContext processContext) throws Throwable;
protected void cacheKey(ProcessContext processContext) {
//拼接解析springEl表达式的map
String[] paramNames = processContext.getParameterNames();
Object[] args = processContext.getArgs();
Map<String, Object> treeMap = new TreeMap<>();
for (int i = 0; i < paramNames.length; i++) {
treeMap.put(paramNames[i], args[i]);
}
DoubleCache doubleCache = processContext.getDoubleCache();
String paramKey = ElParser.parse(doubleCache.key(), treeMap);
String cacheKey = doubleCache.cacheName() + CacheConstant.CACHE_SEPARATOR + paramKey;
processContext.setCacheKey(cacheKey);
}
protected abstract String name();
}
public class DeleteProcessService extends BaseProcessService {
@Override
protected void doProcess(ProcessContext processContext) throws Throwable {
String cacheKey = processContext.getCacheKey();
localCacheService.deleteCache(cacheKey);
remoteCacheService.deleteCache(cacheKey);
Object value = processContext.proceed();
processContext.setValue(value);
}
@Override
protected String name() {
return CacheType.DELETE.getCode();
}
}
@Slf4j
public class GetProcessService extends BaseProcessService {
@Override
protected void doProcess(ProcessContext processContext) throws Throwable {
//读写,查询Caffeine
String cacheKey = processContext.getCacheKey();
DoubleCache doubleCache = processContext.getDoubleCache();
Object localCache = localCacheService.getCache(cacheKey);
if (Objects.nonNull(localCache)) {
log.info("get data from localCache");
processContext.setValue(localCache);
return;
}
//查询Redis
Object removeCache = remoteCacheService.getCache(cacheKey);
if (Objects.nonNull(removeCache)) {
log.info("get data from removeCache");
localCacheService.setCache(cacheKey, removeCache, doubleCache.firstCacheTimeout());
processContext.setValue(removeCache);
return;
}
log.info("get data from database");
Object object = processContext.proceed();
if (Objects.nonNull(object)) {
localCacheService.setCache(cacheKey, object, doubleCache.firstCacheTimeout());
remoteCacheService.setCache(cacheKey, object, doubleCache.secondCacheTimeout());
}
processContext.setValue(object);
}
@Override
protected String name() {
return CacheType.GET.getCode();
}
}
public class PutProcessService extends BaseProcessService {
@Override
protected void doProcess(ProcessContext processContext) throws Throwable {
Object object = processContext.proceed();
String cacheKey = processContext.getCacheKey();
DoubleCache doubleCache = processContext.getDoubleCache();
localCacheService.setCache(cacheKey, object, doubleCache.firstCacheTimeout());
remoteCacheService.setCache(cacheKey, object, doubleCache.secondCacheTimeout());
processContext.setValue(object);
}
@Override
protected String name() {
return CacheType.PUT.getCode();
}
}
详细代码可以看https://gitee.com/haidechizi/double-cache