一级缓存:本地缓存 -》二级缓存:redis缓存 -》数据库
本地缓存
:基于jvm, 意思是程序放在哪,数据就存储在哪,不需要网络请求,特别快,但是需要占用jvm的内存,所以存储的东西不能太大,也不能太久,有内存淘汰机制。缓存组件包括: Guava Cache
、Caffeine
、Encache
分布式缓存
:请求需要网络传输,速度慢一点,组件包括:Redis
,MongoDB
/**
* 查询邀请入会的助力排行榜
* @param activityId
* @param pageSize
* @return
*/
@Override
@MallCache(value = "queryHelpRankingList", expireTime = 1, timeUnit = TimeUnitEnum.MINUTES)
public List<ActivityHelpStatisticsEntity> queryRankingList(String activityId, Integer pageSize) {
List<ActivityHelpStatisticsLogPO> statisticsLogs = mapper.queryRankingList(activityId, pageSize);
if (CollectionUtils.isEmpty(statisticsLogs)) {
return new ArrayList<>();
}
return statisticsLogs.stream().map(po -> ActivityHelpStatisticsConverter.convert(po))
.collect(Collectors.toList());
}
@MallCache(value = "checkExistGroup", expireTime = 5, timeUnit = TimeUnitEnum.SECONDS)
private boolean checkExistGroupCache(long groupId, String nascentId, String groupIds){
return OpenPlatformHelperV4.checkExistGroup(groupId, nascentId, groupIds);
}
@MallCache(value = "GradeSystem", expireTime = 30)
@Override
public GradeSystem queryGradeSystem(Long groupId, Long viewId, Long shopId) {
GradeSystemGetRequest gradeSystemGetRequest = new GradeSystemGetRequest();
gradeSystemGetRequest.setViewId(viewId);
gradeSystemGetRequest.setShopId(shopId);
gradeSystemGetRequest.setGroupId(groupId);
GradeSystemGetResponse gradeResponse = OpenPlatformClient.exec(groupId, gradeSystemGetRequest);
GradeSystem result = gradeResponse.getResult();
return result;
}
/**
* 获取等级名称
*
* @param groupId
* @param shopId
* @param grade
* @return
*/
@MallCache(value = "CustomerGradeName", expireTime = 30)
public String getCustomerGradeName(long groupId, long shopId, int grade) {
List<Long> viewIds = cloudPlatformService.queryViewIdByAreaIdOrBrandId(groupId, getMallId(), Arrays.asList(shopId));
return getCustomerGradeName(groupId, shopId, grade, viewIds.get(0));
}
public String getCustomerGradeName(long groupId, long shopId, int grade, long viewId) {
if (grade == 0) {
return "非会员";
}
GradeInfoGetResponse response = OpenPlatformHelperV4.getGradeInfo(groupId, shopId, grade, viewId);
AssertUtil.assertTrue(response != null && response.getSuccess(), "获取等级信息失败");
GradeInfo gradeInfo = response.getResult();
AssertUtil.assertNotNull(gradeInfo, "获取等级信息失败");
return gradeInfo.getGradeName();
}
@Override
@MallCache(value = "VIEWID", expireTime = 60)
public Long queryViewIdByAreaIdOrBrandId(Long groupId, Long mallId, Long shopId) {
WmCompanyDO company = WmCompanyDao.dao().findByCompanyId(groupId);
List<Long> viewIds = CloudPlatformHelper.queryViewIdByShopIds(Arrays.asList(shopId), groupId, company.getViewOperationType());
AssertUtil.assertTrue(!CollectionUtils.isEmpty(viewIds), "查询运营视角失败,请检查");
return viewIds.get(0);
}
/**
*
* @Description 获取线下门店列表,并存入缓存
* @Param
* @param groupId
* @param shopId
* @return
*/
@MallCache(value = "offlineShops", expireTime = 60)
public TableResponse<Record> getOfflineShops(Long groupId,Long shopId){
TableResponse<Record> tableResponse = new TableResponse<>();
List<Record> offlineShops = WmOfflineShopDao.dao().getOfflineByDigitalShopId(groupId,shopId);
tableResponse.setData(offlineShops);
return tableResponse;
}
/**
* 积分别名
*
* @param groupId
* @param shopId
* @param sysId
* @return
*/
@MallCache(value = "getGameIntegralAlias", expireTime = 60)
public String getIntegralAlias(Long groupId, Long mallId, Long shopId, String sysId) {
Long viewId = cloudViewUtil.getViewId(groupId, mallId, shopId);
return openIntegralCacheUtil.getIntegralAliasCache(groupId, mallId, shopId, sysId, viewId);
}
基于AOP的切面注解
/**
* 二级缓存:redis+本地缓存
* 默认启用redis缓存,本地缓存由withLocalCache控制
* @author
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MallCache {
String value() default "MallCache::";
/**
* 是否启用本地缓存
*
* @return
*/
boolean withLocalCache() default false;
/**
* 分钟级缓存:
* 一级缓存的失效时间为10分钟,这里二级缓存的失效时间应该大于等于10分钟,
* 不然失去二级缓存的意义
* 秒级缓存:本地缓存失效时间是5s
* @return 缓存失效时间 (默认分钟)
*/
long expireTime() default 20;
/**
* 缓存失效时间单位
* TimeUnitEnum.MINUTES使用分钟级缓存
* TimeUnitEnum.SECONDS使用秒级缓存
*
* @return
*/
TimeUnitEnum timeUnit() default TimeUnitEnum.MINUTES;
}
/**
* 查询邀请入会的助力排行榜前100
* @param activityId
* @param pageSize
* @return
*/
@Override
@MallCache(value = "queryHelpRankingList", expireTime = 1, timeUnit = TimeUnitEnum.MINUTES)
public List<ActivityHelpStatisticsEntity> queryRankingList(String activityId, Integer pageSize) {
List<ActivityHelpStatisticsLogPO> statisticsLogs = mapper.queryRankingList(activityId, pageSize);
if (CollectionUtils.isEmpty(statisticsLogs)) {
return new ArrayList<>();
}
return statisticsLogs.stream().map(po -> ActivityHelpStatisticsConverter.convert(po))
.collect(Collectors.toList());
}
比如 上面的queryRankingList(String activityId, Integer pageSize)
方法引用了@MallCache(value = "queryHelpRankingList", expireTime = 1, timeUnit = TimeUnitEnum.MINUTES)
注解。
最终执行过程应该是:
queryHelpRankingList
,activityId
,pageSize
拼接成key
,比如activityId
=hh34hrnih352nb3kh5o3g34
, pageSize
=100,那拼接的key
= queryHelpRankingList_hh34hrnih352nb3kh5o3g34_100
queryRankingList(String activityId, Integer pageSize)
的代码去查数据库用于切面加强
@Slf4j
@Aspect
@Component
@Order(-1)
@ConditionalOnBean(CommonCacheManager.class)
public class LocalCacheAspect {
@Autowired
private CommonCacheManager commonCacheManager;
@Around("@annotation(com.nascent.ecrp.mall.core.cache.annotation.MallCache)")
public Object mallCache(ProceedingJoinPoint proceedingJoinPoint){
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = methodSignature.getMethod();
Type genericReturnType = methodSignature.getMethod().getAnnotatedReturnType().getType();
MallCache mallCache = method.getAnnotation(MallCache.class);
String cachePrefix = mallCache.value();
long expire = mallCache.expireTime();
boolean withLocalCache = mallCache.withLocalCache();
TimeUnitEnum timeUnit = mallCache.timeUnit();
Object[] args = proceedingJoinPoint.getArgs();
// 用切点方法的参数拼接成cacheKey 比如groupId_viewId_shopId
String cacheKey = Arrays.stream(args).map(arg -> {
return String.valueOf(arg);
}).collect(Collectors.joining("_"));
Object result = commonCacheManager.queryData(cachePrefix + "_" + cacheKey,
expire ,timeUnit, withLocalCache, genericReturnType ,()->{
try {
// 接口查询数据库拿到数据
return proceedingJoinPoint.proceed(args);
} catch (Throwable throwable) {
log.error("LocalCacheAspect.mallCache 异常:{}", throwable);
return null;
}
});
return result;
}
}
本地缓存的初始化类
public class CommonCacheFactory {
/**
* 秒级时间的缓存
*/
private static Cache<String, Object> localCacheSecond;
/**
* 分钟时间的缓存
*/
private static Cache<String, Object> localCacheMinute;
static {
localCacheSecond = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.SECONDS)
.recordStats()
.build();
localCacheMinute = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats()
.build();
}
private CommonCacheFactory(){
}
public static Cache<String, ? > get() {
return getMinute();
}
public static void set(String key, Object value){
setMinute(key, value);
}
public static Cache<String, ? > getSecond() {
return localCacheSecond;
}
public static void setSecond(String key, Object value){
localCacheSecond.put(key, value);
}
public static Cache<String, ? > getMinute() {
return localCacheMinute;
}
public static void setMinute(String key, Object value){
localCacheMinute.put(key, value);
}
}
本地缓存管理类,用于本地缓存的查询和更新
@Slf4j
@Component
public class CommonCacheManager extends AbstractCacheManager<String, String>{
private static final String LOCAL_CACHE_ENABLE_KEY = "LOCAL_CACHE_ENABLE";
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public boolean useCache() {
// todo 优化成直接在内存中获取
String enable = redisTemplate.opsForValue().get(LOCAL_CACHE_ENABLE_KEY);
if(StringUtil.isNotBlank(enable) && "CLOSE".equals(enable)){
return false;
}else {
return true;
}
}
@Override
public Cache<String, String> primaryCache(TimeUnitEnum timeUnit) {
if(TimeUnitEnum.SECONDS.equals(timeUnit)){
Cache<String, String> stringResultCache = (Cache<String, String>) CommonCacheFactory.getSecond();
return stringResultCache;
}
return (Cache<String, String>) CommonCacheFactory.getMinute();
}
@Override
public String cacheKey() {
return "LOCAL::CACHE::";
}
public <T> T queryData(String k, Long expireTime, TimeUnitEnum timeUnit, boolean withLocalCache, Type type, Supplier<T> supplier) {
if (!useCache()) {
// 不使用本地缓存,默认是使用的
log.info("直接在缓存中获取数据:{}", k);
return supplier.get();// supplier.get()能获取函数的返回值,直接返回数据库数据
}
try {
CacheContext<String,T> resultCacheContext =
CacheContext
.<String,T>builder()
.key(cacheKey() + k)
.reference(type)
.expireSeconds(expireTime)
.timeUnit(timeUnit.getTimeUnit())
.callback(() -> supplier.get())// 数据库数据,这里只作为实参,还并没有调用
.build();
// 不用本地缓存,用redis缓存
if(!withLocalCache){
return JSON.parseObject(getFromSecondary(resultCacheContext), type);
}
// 用本地缓存
String result = primaryCache(timeUnit).get(k, (x) -> getFromSecondary(resultCacheContext));
return JSON.parseObject(result, type);
} catch (Exception e) {
log.error("一级缓存获取数据失败:{}", e.getMessage());
return supplier.get();
}
}
}
redis缓存的管理接口类
@Slf4j
public abstract class AbstractCacheManager<K, V> implements CacheManage<K, V> {
/** 查询redis缓存、存在则读取并返回,不存在则更新成最新数据
*
* @param context
* @return
* @param <T>
*/
protected <T> V getFromSecondary(CacheContext<K,T> context) {
K key = context.key;
T dbValue = null;
try {
String valueFromSecondary = secondaryCache().getValueFromSecondary(key);
// 二级缓存,即redis层缓存,有数据就使用redis缓存
if (StringUtil.isNotBlank(valueFromSecondary)) {
return (V) valueFromSecondary;
}
// redis缓存没有,查询数据库
dbValue = context.getCallback().get();
if (dbValue == null) {
return null;
}
Object jsonValue = JSONObject.toJSONString(dbValue);
//把数据库的数据跟新到二级缓存
secondaryCache().setValueForSecondary(key, context.expireSeconds, context.timeUnit, jsonValue);
return (V)jsonValue;
} catch (Exception e) {
log.error("二级缓存中获取数据失败:{}", e.getMessage());
return null;
}
}
@Override
public SecondaryCache secondaryCache() {
RedisSecondaryCache redisSecondaryCache = SecondaryCacheFactory.create(RedisSecondaryCache.class);
return redisSecondaryCache;
}
@Override
public void invalidate(Object o) {
for(TimeUnitEnum timeUnit : TimeUnitEnum.values()){
primaryCache(timeUnit).invalidate(o);
}
secondaryCache().invalidate(o);
}
@Override
public void invalidateAll(List o) {
for(TimeUnitEnum timeUnit : TimeUnitEnum.values()){
primaryCache(timeUnit).invalidateAll();
}
secondaryCache().invalidateAll(o);
}
@Override
public Map<String,Map<String, V>> showAsMap() {
Map<String,Map<String, V>> map = Maps.newHashMap();
for(TimeUnitEnum timeUnit : TimeUnitEnum.values()) {
Map<String,V> newMap = Maps.newHashMap();
primaryCache(timeUnit).asMap().entrySet().forEach(entry -> newMap.put(Objects.toString(entry.getKey()),entry.getValue()));
map.put(timeUnit.name(), newMap);
}
return map;
}
@Override
public Map<String,Set<K>> keys() {
Map<String,Set<K>> map = new HashMap();
for(TimeUnitEnum timeUnit : TimeUnitEnum.values()) {
map.put(timeUnit.name(), primaryCache(timeUnit).asMap().keySet());
}
return map;
}
}
@Slf4j
@Component
public class RedisSecondaryCache implements SecondaryCache<String>{
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public String getValueFromSecondary(String key){
Object redisCache = redisTemplate.opsForValue().get(key);
return redisCache == null ? null : redisCache.toString();
}
@Override
public String getValueFromSecondaryBatch(List<String> cacheKeys) {
Object redisCache = redisTemplate.opsForValue().multiGet(cacheKeys);
return redisCache == null ? null : redisCache.toString();
}
@Override
public <T> void setValueForSecondary(String cacheKey,long expireSeconds, TimeUnit timeUnit, T value) {
redisTemplate.opsForValue().set(cacheKey,JSONObject.toJSON(value),expireSeconds, timeUnit);
}
@Override
public <T> void setValueForSecondaryBatch(Map<String, T> kv, long expireSeconds) {
kv.forEach((k, v) -> {
redisTemplate.opsForValue().set(k, v, expireSeconds, TimeUnit.MINUTES);
});
}
@Override
public void invalidate(Object o) {
redisTemplate.delete(String.valueOf(o));
}
@Override
public void invalidateAll(List keys) {
redisTemplate.delete(keys);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CacheContext<K, T> {
K key;
Type reference;
// 传递函数,Supplier可以把函数作为实参传递,调用Supplier.get()时返回函数的结果
Supplier<T> callback;
Long expireSeconds;
TimeUnit timeUnit;
}
缓存管理接口
-定义
public interface CacheManage<K, V> extends InvalidateCommand {
boolean useCache();
String cacheKey();
Cache<K, V> primaryCache(TimeUnitEnum timeUnit);
SecondaryCache secondaryCache();
Map<String, Map<String, V>> showAsMap();
Map<String,Set<K>> keys();
}
redis缓存接口类
public interface SecondaryCache<K> extends InvalidateCommand{
String getValueFromSecondary(K key);
String getValueFromSecondaryBatch(List<K> keys);
<T> void setValueForSecondary(K key, long expireSeconds, TimeUnit timeUnit, T value);
<T> void setValueForSecondaryBatch( Map<K, T > kv, long expireSeconds);
}
二级缓存工厂类
public class SecondaryCacheFactory {
private SecondaryCacheFactory(){
}
public static <T> T create(Class<T> clazz) {
return (T) SpringContext.me().getBean(clazz);
}
}
/**
* 缓存支持的时间的单位
* @author
*/
public enum TimeUnitEnum {
MINUTES(TimeUnit.MINUTES),
SECONDS(TimeUnit.SECONDS);
@Getter
private TimeUnit timeUnit;
TimeUnitEnum(TimeUnit timeUnit){
this.timeUnit = timeUnit;
}
}
public interface InvalidateCommand<K> {
void invalidate(K k);
void invalidateAll(List<K> k);
}