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

需求

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

分析

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

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

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

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

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

源码里MultiLevelCache的unwrap如下:

    @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实现清理本地缓存,实现一个方法:

	@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();
		}
	}

2) 远程缓存处理

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

	@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()]));
		}
	}

3) 其它机器缓存处理

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

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

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

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通知消息

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的清理方法清理本地缓存

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最后调用发送清理消息,收到所有的订阅清空本地对应对象缓存即可

	@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方法测试一下

    @DubboReference(check=false)
    private IUserService userService;


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

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

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

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-03-25 05:54:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-25 05:54:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-25 05:54:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-25 05:54:04       18 阅读

热门阅读

  1. Web前端css基本内容

    2024-03-25 05:54:04       22 阅读
  2. 深入理解与使用go之函数与方法--使用

    2024-03-25 05:54:04       19 阅读
  3. labelImg安装方法

    2024-03-25 05:54:04       20 阅读
  4. MacOS快速安装FFmpeg、ffprobe、ffplay

    2024-03-25 05:54:04       16 阅读
  5. 【云开发笔记No.13】Jenkins和持续集成

    2024-03-25 05:54:04       20 阅读
  6. VUE3v-text、v-html、:style的理解

    2024-03-25 05:54:04       19 阅读
  7. DNS、DNS劫持与HTTPDNS:原理、应用与安全分析

    2024-03-25 05:54:04       18 阅读
  8. 基于单片机的语音识别智能窗帘控制器的设计

    2024-03-25 05:54:04       16 阅读
  9. MySQL基础复习

    2024-03-25 05:54:04       19 阅读
  10. 【几何】平面方程

    2024-03-25 05:54:04       21 阅读