jetcache 2级缓存模式实现批量清除

需求

希望能够实现清理指定对象缓存的方法,例如缓存了User表,当User表巨大时,通过id全量去清理不现实,耗费资源也巨大。因此需要能够支持清理指定本地和远程缓存的批量方法。

分析

查看jetcache生成的cache接口,并没有提供一个例如getAll()或invalidate的方法。因此需要能够扩展;

查看jetcache源码,jetcache对于单级和多级缓存实现了统一的cache接口;

当为单级缓存时,直接返回缓存的包装Cache;

当为多级缓存时,返回MultiLevelCache;

所有的缓存,都实现了接口的unwrap方法,当指定一个类型时,会返回对应的包装Cache,否则抛出IllegalArgumentException;

源码里MultiLevelCache的unwrap如下:

java 复制代码
    @Override
    public <T> T unwrap(Class<T> clazz) {
        Objects.requireNonNull(clazz);
        for (Cache cache : caches) {
            try {
                T obj = (T) cache.unwrap(clazz);
                if (obj != null) {
                    return obj;
                }
            } catch (IllegalArgumentException e) {
                // ignore
            }
        }
        throw new IllegalArgumentException(clazz.getName());
    }

也就是说,当多级缓存(这里是2级,实际可以支持多级)时,可以通过类型指定unwarp到对应的缓存,这样我们就可以通过cache拿到对应的本地缓存,进而调用缓存全量清理方法

实现

因此,实现思路如下:

对于本地的缓存,直接unwrap后全量清理掉;

对于远程的缓存,直接redis缓存访问,并通过key的查询方法,将符合条件的缓存批量清理掉;

对于其他机器的缓存,采用订阅发布的方式,让其他机器收到消息后unwrap并清理本地缓存。

  1. 本地缓存处理

将jetcache的缓存拿出来unwrap即可调用caffeine cache的invalidateAll实现清理本地缓存,实现一个方法:

java 复制代码
	@Override
	public void evictCacheLocal() {
		//清理本地
		if(cache != null){
			com.github.benmanes.caffeine.cache.Cache caffeineCache = cache.unwrap(com.github.benmanes.caffeine.cache.Cache.class);
			caffeineCache.invalidateAll();
		}
	}
  1. 远程缓存处理

远程的目前系统使用了redission驱动,使用RKeys将符合前缀的远程清理掉。getKeysByPattern是按10个一组SCAN得到,因此不会阻塞redis,按100个一组清理即可,如果数据较多,可以考虑更大点

java 复制代码
	@Override
	public void evictCacheAll() {
		// 清理redis
		RKeys keys = redissonClient.getKeys();
		Iterator<String> keysList = keys.getKeysByPattern(GlobalEx.CACHEREGION_ENTITY + entityClass.getSimpleName() + "-*").iterator();
		List<String> processList = new ArrayList<>();
		while(keysList.hasNext()) {
			processList.add(keysList.next());
			if(processList.size() == 100){
				keys.delete(processList.toArray(new String[processList.size()]));
				processList.clear();
			}
		}
		if(processList.size() > 0){
			keys.delete(processList.toArray(new String[processList.size()]));
		}
	}
  1. 其它机器缓存处理

研究了下源码,jetcache没有很方便的扩展点,因此直接绕过jetcache的订阅发布。直接用其它组件去实现订阅发布,目前系统已经引入了redission,因此直接使用redission的订阅/发布。

这里实现了一个订阅监听列表,直接在subsys.<子系统>.notify添加自己的发布/监听器即可

监听消息类,notifyType可以区别消息类型

java 复制代码
package org.ccframe.commons.notify;

import com.alibaba.fastjson2.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class NotifyMessage {

    private int notifyType;

    @JSONField(name = "message")
    private String message;
}

消息监听基类,使用Json通知消息

java 复制代码
package org.ccframe.commons.notify;

import com.alibaba.fastjson2.JSON;
import lombok.extern.log4j.Log4j2;
import org.ccframe.commons.helper.CcNotifyHelper;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

@Log4j2
public abstract class BaseNotifyListener<T> {
    public abstract int getNotifyType();

    private final Class<T> messageClass;

    @Autowired
    private CcNotifyHelper ccNotifyHelper;

    public BaseNotifyListener(){
        Type genType = getClass().getGenericSuperclass();
        this.messageClass = (Class<T>) ((ParameterizedType) genType).getActualTypeArguments()[0];
    }

    public void receiveData(String notifyMessage){
        T message = JSON.parseObject(notifyMessage, messageClass);
        try {
            process(message);
        }catch (Throwable tr){
            log.error(tr);
        }
    }

    public void sendData(T notifyMessage){
        ccNotifyHelper.sendNotify(getNotifyType(), JSON.toJSONString(notifyMessage));
    }

    protected abstract void process(T message);
}

记得配置一下包扫描

复制代码
@ComponentScan({
    "org.ccframe.subsys.*.notify" //所有的订阅广播
})

然后写一个子类,收到消息后调用baseservice的清理方法清理本地缓存

java 复制代码
package org.ccframe.subsys.core.notify;

import org.apache.commons.lang3.StringUtils;
import org.ccframe.commons.base.BaseService;
import org.ccframe.commons.helper.SpringContextHelper;
import org.ccframe.commons.notify.BaseNotifyListener;
import org.springframework.stereotype.Component;

@Component
public class ClearLocalCacheNotifyListener extends BaseNotifyListener<String> {
    @Override
    public int getNotifyType() {
        return 0;
    }

    @Override
    protected void process(String message) {
        SpringContextHelper.getBean(StringUtils.uncapitalize(message) + "Service", BaseService.class).evictCacheLocal();
    }
}

因此在evictCacheAll最后调用发送清理消息,收到所有的订阅清空本地对应对象缓存即可

java 复制代码
	@Override
	public void evictCacheAll() {
		// 清理redis
		RKeys keys = redissonClient.getKeys();
		Iterator<String> keysList = keys.getKeysByPattern(GlobalEx.CACHEREGION_ENTITY + entityClass.getSimpleName() + "-*").iterator();
		List<String> processList = new ArrayList<>();
		while(keysList.hasNext()) {
			processList.add(keysList.next());
			if(processList.size() == 100){
				keys.delete(processList.toArray(new String[processList.size()]));
				processList.clear();
			}
		}
		if(processList.size() > 0){
			keys.delete(processList.toArray(new String[processList.size()]));
		}
		// 广播清理本地缓存
		clearLocalCacheNotifyListener.sendData(entityClass.getSimpleName());
	}

写个controller方法测试一下

java 复制代码
    @DubboReference(check=false)
    private IUserService userService;


    @GetMapping("test")
    public QuartzRowDto test(@ApiIgnore HttpServletRequest request) {
        userService.evictCacheAll();
        return new QuartzRowDto();
    }

检查一下,能够收到清理消息清理本地缓存

这样,即可快速的完成本地和远程的指定表缓存的清理,也不受表数据过大影响了

相关推荐
boonya3 天前
Redisson原理与面试问题解析
面试·职场和发展·redission·分布式中间件框架
鼠鼠我捏,要死了捏5 天前
Redis 集群模式读写分离与分片策略方案对比分析与实践指南
redis·cache·cluster
鼠鼠我捏,要死了捏5 天前
Redis缓存穿透、缓存击穿与雪崩防护及性能优化实战指南
redis·cache·performance
XueminXu12 天前
Spark引擎中RDD的性质
spark·cache·map·rdd·flatmap·弹性分布式数据集·collect
驱动探索者14 天前
Linux arm cache 入门
linux·运维·arm开发·cache
triticale16 天前
【计算机组成原理】LRU计数器问题
cache·计算机组成原理·lru
鼠鼠我捏,要死了捏1 个月前
生产环境Redis缓存穿透与雪崩防护性能优化实战指南
redis·cache
Jeaten3 个月前
Cross-Edge Orchestration of Serverless Functions With Probabilistic Caching
edge·serverless·cache
快乐肚皮4 个月前
Redission学习专栏(一):快速入门及核心API实践
学习·实战·api·redission
mikey棒棒棒4 个月前
lua脚本+Redission实现分布式锁
redis·分布式·lua·看门狗·redission