日本無碼做受網(wǎng)站企業(yè)seo顧問
需求
希望能夠?qū)崿F(xiàn)清理指定對(duì)象緩存的方法,例如緩存了User表,當(dāng)User表巨大時(shí),通過id全量去清理不現(xiàn)實(shí),耗費(fèi)資源也巨大。因此需要能夠支持清理指定本地和遠(yuǎn)程緩存的批量方法。
分析
查看jetcache生成的cache接口,并沒有提供一個(gè)例如getAll()或invalidate的方法。因此需要能夠擴(kuò)展;
查看jetcache源碼,jetcache對(duì)于單級(jí)和多級(jí)緩存實(shí)現(xiàn)了統(tǒng)一的cache接口;
當(dāng)為單級(jí)緩存時(shí),直接返回緩存的包裝Cache;
當(dāng)為多級(jí)緩存時(shí),返回MultiLevelCache;
所有的緩存,都實(shí)現(xiàn)了接口的unwrap方法,當(dāng)指定一個(gè)類型時(shí),會(huì)返回對(duì)應(yīng)的包裝Cache,否則拋出IllegalArgumentException;
源碼里MultiLevelCache的unwrap如下:
@Overridepublic <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());}
也就是說,當(dāng)多級(jí)緩存(這里是2級(jí),實(shí)際可以支持多級(jí))時(shí),可以通過類型指定unwarp到對(duì)應(yīng)的緩存,這樣我們就可以通過cache拿到對(duì)應(yīng)的本地緩存,進(jìn)而調(diào)用緩存全量清理方法
實(shí)現(xiàn)
因此,實(shí)現(xiàn)思路如下:
對(duì)于本地的緩存,直接unwrap后全量清理掉;
對(duì)于遠(yuǎn)程的緩存,直接redis緩存訪問,并通過key的查詢方法,將符合條件的緩存批量清理掉;
對(duì)于其他機(jī)器的緩存,采用訂閱發(fā)布的方式,讓其他機(jī)器收到消息后unwrap并清理本地緩存。
1) 本地緩存處理
將jetcache的緩存拿出來unwrap即可調(diào)用caffeine cache的invalidateAll實(shí)現(xiàn)清理本地緩存,實(shí)現(xiàn)一個(gè)方法:
@Overridepublic void evictCacheLocal() {//清理本地if(cache != null){com.github.benmanes.caffeine.cache.Cache caffeineCache = cache.unwrap(com.github.benmanes.caffeine.cache.Cache.class);caffeineCache.invalidateAll();}}
2) 遠(yuǎn)程緩存處理
遠(yuǎn)程的目前系統(tǒng)使用了redission驅(qū)動(dòng),使用RKeys將符合前綴的遠(yuǎn)程清理掉。getKeysByPattern是按10個(gè)一組SCAN得到,因此不會(huì)阻塞redis,按100個(gè)一組清理即可,如果數(shù)據(jù)較多,可以考慮更大點(diǎn)
@Overridepublic void evictCacheAll() {// 清理redisRKeys 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) 其它機(jī)器緩存處理
研究了下源碼,jetcache沒有很方便的擴(kuò)展點(diǎn),因此直接繞過jetcache的訂閱發(fā)布。直接用其它組件去實(shí)現(xiàn)訂閱發(fā)布,目前系統(tǒng)已經(jīng)引入了redission,因此直接使用redission的訂閱/發(fā)布。
這里實(shí)現(xiàn)了一個(gè)訂閱監(jiān)聽列表,直接在subsys.<子系統(tǒng)>.notify添加自己的發(fā)布/監(jiān)聽器即可
監(jiān)聽消息類,notifyType可以區(qū)別消息類型
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;
}
消息監(jiān)聽基類,使用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;@Autowiredprivate 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" //所有的訂閱廣播 })
然后寫一個(gè)子類,收到消息后調(diào)用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> {@Overridepublic int getNotifyType() {return 0;}@Overrideprotected void process(String message) {SpringContextHelper.getBean(StringUtils.uncapitalize(message) + "Service", BaseService.class).evictCacheLocal();}
}
因此在evictCacheAll最后調(diào)用發(fā)送清理消息,收到所有的訂閱清空本地對(duì)應(yīng)對(duì)象緩存即可
@Overridepublic void evictCacheAll() {// 清理redisRKeys 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());}
寫個(gè)controller方法測(cè)試一下
@DubboReference(check=false)private IUserService userService;@GetMapping("test")public QuartzRowDto test(@ApiIgnore HttpServletRequest request) {userService.evictCacheAll();return new QuartzRowDto();}
檢查一下,能夠收到清理消息清理本地緩存
這樣,即可快速的完成本地和遠(yuǎn)程的指定表緩存的清理,也不受表數(shù)據(jù)過大影響了