Apache ShenYu匹配缓存设计分析
1.前言
Apache ShenYu是一款支持多语言、多协议(Dubbo,SpringCloud,gRPC,Motan,Sofa,Tars, BRPC)、插件化设计、高度可动态化配置、高度可自主化开发的Java网关。内置丰富的插件支持,鉴权,限流,熔断,防火墙等等。流量配置动态化,性能极高。支持集群部署,支持 A/B Test,蓝绿发布等功能。
Apache ShenYu的匹配缓存主要由3级缓存构成,第一级缓存是由Caffeine实现的本地缓存,第二级缓存是由Caffeine与ConcurrentHashMap实现的前缀树缓存,第三级缓存是由ConcurrentHashMap构成的全局缓存。其中一级缓存和二级缓存都是需要配置开启的,三级缓存是默认使用的。
2.总体思路
在所有缓存都开启的前提下,如果一级缓存没有命中,那么会从前缀树缓存进行匹配,如果前缀树缓存也没有命中,那么会默认的缓存中进行匹配,如果默认的缓存没有匹配成功,则说明匹配失效。
3.一级缓存
3.1 一级缓存的设计
shenyu为selector和rule都设计了一级缓存,一级使用ConcurrentHashMap实现,key为pluginName,value是基于Caffeine实现的WindowTinyLFUMap。
当一级缓存是selector缓存时,value为Map<String, SelectorData>,其中lfuMap的key是path,value为SelectorData。
当一级缓存是rule缓存时,value为Map<String, RuleData>,其中lfuMap的key是path,value为RuleData。
/**
* pluginName -> LRUMap.
* LRUMap: path -> selector data.
*/
private static final ConcurrentMap<String, Map<String, SelectorData>> SELECTOR_DATA_MAP = Maps.newConcurrentMap();
/**
* plugin name -> LRU Map.
* LRU Map: path -> rule data.
*/
private static final ConcurrentMap<String, Map<String, RuleData>> RULE_DATA_MAP = Maps.newConcurrentMap();
3.2 何时存入数据
- 1.当二级缓存或者三级缓存(默认缓存)命中的时候,会将
<pluginName, <<path, Rule/Selector>>
数据存入一级缓存中。 - 2.当默认缓存没有命中的时候,也会将数据存入一级缓存中,存储的数据Rule或者Selector为只有pluginName字段,表示当前数据不可能命中(不可能被访问)。
public abstract class AbstractShenyuPlugin implements ShenyuPlugin {
/**
* cache rule data.
*
* @param path current uri path
* @param ruleData rule data
*/
private void cacheRuleData(final String path, final RuleData ruleData) {
// if the ruleCache is disabled or rule data is null, not cache rule data.
if (Boolean.FALSE.equals(ruleMatchConfig.getCache().getEnabled()) || Objects.isNull(ruleData)
|| Boolean.TRUE.equals(ruleData.getMatchRestful())) {
return;
}
int initialCapacity = ruleMatchConfig.getCache().getInitialCapacity();
long maximumSize = ruleMatchConfig.getCache().getMaximumSize();
// 将空数据存入缓存中,表示当前数据不可能命中(不可能被访问)
if (StringUtils.isBlank(ruleData.getId())) {
MatchDataCache.getInstance().cacheRuleData(path, ruleData, initialCapacity, maximumSize);
return;
}
List<ConditionData> conditionList = ruleData.getConditionDataList();
if (CollectionUtils.isNotEmpty(conditionList)) {
// 如果所有的条件都是uri条件,那么将数据存入缓存中
boolean isUriCondition = conditionList.stream().allMatch(v -> URI_CONDITION_TYPE.equals(v.getParamType()));
if (isUriCondition) {
MatchDataCache.getInstance().cacheRuleData(path, ruleData, initialCapacity, maximumSize);
}
}
}
private RuleData trieMatchRule(final ServerWebExchange exchange, final SelectorData selectorData, final String path) {
if (!ruleMatchConfig.getTrie().getEnabled()) {
return null;
}
RuleData ruleData = null;
ShenyuTrieNode shenyuTrieNode = ruleTrie.match(path, selectorData.getId());
if (Objects.nonNull(shenyuTrieNode)) {
LogUtils.info(LOG, "{} rule match path from shenyu trie", named());
List<?> collection = shenyuTrieNode.getPathCache().get(selectorData.getId());
if (CollectionUtils.isNotEmpty(collection)) {
Pair<Boolean, RuleData> ruleDataPair;
if (collection.size() > 1) {
ruleDataPair = matchRule(exchange, ListUtil.castList(collection, RuleData.class::cast));
} else {
Object ruleObj = collection.stream().findFirst().orElse(null);
RuleData rule = Objects.nonNull(ruleObj) ? (RuleData) ruleObj : null;
boolean cached = Objects.nonNull(rule) && rule.getConditionDataList().stream().allMatch(condition -> URI_CONDITION_TYPE.equals(condition.getParamType()));
ruleDataPair = Pair.of(cached, rule);
}
ruleData = ruleDataPair.getRight();
if (ruleDataPair.getLeft() && Objects.nonNull(ruleData)) {
// exist only one rule data, cache rule
cacheRuleData(path, ruleData);
}
}
}
return ruleData;
}
private RuleData defaultMatchRule(final ServerWebExchange exchange, final List<RuleData> rules, final String path) {
Pair<Boolean, RuleData> matchRulePair = matchRule(exchange, rules);
RuleData ruleData = matchRulePair.getRight();
// 如果匹配成功则表示,当前的路径情况下可以匹配成功
if (Objects.nonNull(ruleData)) {
LOG.info("{} rule match path from default strategy", named());
// cache rule data
if (matchRulePair.getLeft()) {
cacheRuleData(path, ruleData);
}
return ruleData;
} else {
// if not match rule, cache empty rule data.
if (matchRulePair.getLeft()) {
// 如果没有匹配成功,那么将空数据存入缓存中,表示当前数据不可能命中(不可能被访问)
RuleData emptyRuleData = RuleData.builder().pluginName(named()).build();
cacheRuleData(path, emptyRuleData);
}
return null;
}
}
}
3.3 何时匹配数据
在一级缓存开启的前提下,只有继承了org.apache.shenyu.plugin.base.AbstractShenyuPlugin
类的插件才会访问一级缓存。
public abstract class AbstractShenyuPlugin implements ShenyuPlugin {
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final ShenyuPluginChain chain) {
// 省略部分代码
SelectorData selectorData = obtainSelectorDataCacheIfEnabled(path);
// ...
// 如果不能匹配命中,则从trie缓存中匹配
RuleData ruleData = obtainRuleDataCacheIfEnabled(path);
}
private SelectorData obtainSelectorDataCacheIfEnabled(final String path) {
return selectorMatchConfig.getCache().getEnabled() ? MatchDataCache.getInstance().obtainSelectorData(named(), path) : null;
}
private RuleData obtainRuleDataCacheIfEnabled(final String path) {
return ruleMatchConfig.getCache().getEnabled() ? MatchDataCache.getInstance().obtainRuleData(named(), path) : null;
}
}
public final class MatchDataCache {
public SelectorData obtainSelectorData(final String pluginName, final String path) {
final Map<String, SelectorData> lruMap = SELECTOR_DATA_MAP.get(pluginName);
return Optional.ofNullable(lruMap).orElse(Maps.newHashMap()).get(path);
}
}
3.4 何时删除数据
- 1.当更新当前插件的时候,会将当前插件相关的selector缓存或者rule缓存删除
如果是更新插件的任意一个选项,如果插件开启的前提下,shenyu将会根据pluginName删除当前插件相关的selector缓存和rule缓存,核心的代码如下:MatchDataCache.getInstance().removeSelectorData(pluginName);
, MatchDataCache.getInstance().removeRuleData(pluginName);
- 2.当更新当前selector或者rule的时候,shenyu会将当前selector或者rule相关的数据删除
当更新selector的时候,shenyu会根据插件名称和selectorId删除当前selector相关的缓存数据,同时也会删除空的selector缓存数据。
在更新selector的同时,shenyu同时也会删除当前selector相关的rule缓存,同时也会删除空的rule缓存数据。
当更新rule的时候,shenyu会根据插件名称和ruleId删除当前rule相关的缓存数据,同时也会删除空的rule缓存数据。
public class CommonPluginDataSubscriber implements PluginDataSubscriber {
private <T> void updateCacheData(@NonNull final T data) {
if (data instanceof PluginData) {
PluginData pluginData = (PluginData) data;
// ...... 省略部分代码
final String pluginName = pluginData.getName();
// 如果是更新插件,那么会将当前插件相关的selector缓存和rule缓存删除,并且是根据pluginName进行删除的。
if (selectorMatchConfig.getCache().getEnabled()) {
MatchDataCache.getInstance().removeSelectorData(pluginName);
}
if (ruleMatchCacheConfig.getCache().getEnabled()) {
MatchDataCache.getInstance().removeRuleData(pluginName);
}
} else if (data instanceof SelectorData) {
SelectorData selectorData = (SelectorData) data;
// 如果是更新更新selector的话,首先会将当前selector相关的缓存删除,同时也会删除空SelectorData的数据
if (selectorMatchConfig.getCache().getEnabled()) {
MatchDataCache.getInstance().removeSelectorData(selectorData.getPluginName(), selectorData.getId());
MatchDataCache.getInstance().removeEmptySelectorData(selectorData.getPluginName());
}
// 同时删除当前selector相关的rule缓存,同时也会删除空的rule缓存数据
if (ruleMatchCacheConfig.getCache().getEnabled()) {
MatchDataCache.getInstance().removeRuleDataBySelector(selectorData.getPluginName(), selectorData.getId());
MatchDataCache.getInstance().removeEmptyRuleData(selectorData.getPluginName());
}
updateSelectorTrieCache(selectorData);
} else if (data instanceof RuleData) {
RuleData ruleData = (RuleData) data;
// 如果是更新更新rule的话,首先会将当前rule相关的缓存删除,同时也会删除空RuleData的数据
if (ruleMatchCacheConfig.getCache().getEnabled()) {
MatchDataCache.getInstance().removeRuleData(ruleData.getPluginName(), ruleData.getId());
MatchDataCache.getInstance().removeEmptyRuleData(ruleData.getPluginName());
}
updateRuleTrieCache(ruleData);
}
}
}
- 3.当删除当前selector或者rule的时候,shenyu会将当前selector或者rule相关的数据删除
当删除当前selector的时候会将当前selector相关的缓存数据删除,同时也会删除空的selector缓存数据。
当删除当前rule的时候会将当前rule相关的缓存数据删除,同时也会删除空的rule缓存数据。
public class CommonPluginDataSubscriber implements PluginDataSubscriber {
private <T> void removeCacheData(@NonNull final T data) {
if (data instanceof PluginData) {
} else if (data instanceof SelectorData) {
SelectorData selectorData = (SelectorData) data;
// remove selector match cache
if (selectorMatchConfig.getCache().getEnabled()) {
MatchDataCache.getInstance().removeSelectorData(selectorData.getPluginName(), selectorData.getId());
MatchDataCache.getInstance().removeEmptySelectorData(selectorData.getPluginName());
}
} else if (data instanceof RuleData) {
RuleData ruleData = (RuleData) data;
if (ruleMatchCacheConfig.getCache().getEnabled()) {
MatchDataCache.getInstance().removeRuleData(ruleData.getPluginName(), ruleData.getId());
MatchDataCache.getInstance().removeEmptyRuleData(ruleData.getPluginName());
}
}
}
}
3.5 何时使用一级缓存
- shenyu的一级缓存只有在rule condition中只有uri match的时候才会使用,其他的情况都不会将数据缓存到一级缓存。
- 如果在同一个selector中有多个相同uri condition的rule,那么就不要开启一级缓存。
- 当rule或者selector中matchRestful为true的时候(表示匹配restful风格的数据,这样能够节省内存),shenyu不会将数据缓存到一级缓存中。
4.二级缓存匹配
4.1 二级缓存的设计
shenyu的二级缓存是基于Caffeine实现的WindowLFUMap实现的前缀树缓存。shenyu的前缀树主要包含了由caffeine实现的keyRootMap和匹配模式。
- shenyu的前缀树支持两种匹配模式,一种是antPathMatch,一种是pathPattern。antPathMatch支持路径中待通配符访问,包括支持
*
,**
,*.json
格式通配符。 - shenyu的前缀树的keyRootMap是一个Map<String, ShenyuTrieNode>,当前缀树是selector前缀树的时候,keyRootMap的key是pluginName,value是ShenyuTrieNode, 当前缀树是rule前缀树的时候,keyRootMap的key是selectorId,value是ShenyuTrieNode。
public class ShenyuTrie {
/**
* when the trie is selector trie, the key is pluginName, when the trie is rule trie, the key is selectorId.
*/
private final Map<String, ShenyuTrieNode> keyRootMap;
/**
* the mode includes antPathMatch and pathPattern
* antPathMatch means all full match, pathPattern is used in web.
*/
private final TrieMatchModeEnum matchMode;
public ShenyuTrie(final Long cacheSize, final String matchMode) {
this.matchMode = TrieMatchModeEnum.acquireTrieMatch(matchMode);
this.keyRootMap = new WindowTinyLFUMap<>(cacheSize);
}
}
- ShenyuTrieNode的数据结构设计如下:
public class ShenyuTrieNode implements Serializable {
private static final long serialVersionUID = -2347426887850566364L;
/**
* abc match abc, :a match all words as a variable names a, * match all words ,** match all words and children.
*/
private String matchStr;
/**
* full path.
*/
private String fullPath;
/**
* in path /a/b/c, b is child of a, c is child of b.
*/
private Map<String, ShenyuTrieNode> children;
/**
* path variables.
*/
private Map<String, ShenyuTrieNode> pathVariables;
/**
* path variable node.
*/
private ShenyuTrieNode pathVariableNode;
/**
* isWildcard, match all nodes, /a/b/** /** is a match all Node.
*/
private boolean isWildcard;
/**
* if true means a real path exists, /a/b/c/d only node of d is true, a,b,c is false.
*/
private boolean endOfPath;
/**
* selectorId mapping to RuleData.
*/
private Map<String, List<?>> pathCache;
/**
* biz info, if the trie is selector trie, the bizInfo is pluginName, if the trie is rule trie, the bizInfo is selectorId.
*/
private String bizInfo;
/**
* parent node.
*/
private ShenyuTrieNode parentNode;
/**
* fail to node.
*/
private ShenyuTrieNode failToNode;
}
4.2 何时存入数据
在前缀树缓存开启的前提下,只有通过shenyu-admin或者本地模式添加selector或者rule数据并且是包含了uri匹配的规则时会将ruledata或者selectordata相关的数据存入对应的RuleTri或者SelectorTrie中。
public class CommonPluginDataSubscriber implements PluginDataSubscriber {
private void updateSelectorTrieCache(final SelectorData selectorData) {
if (!selectorMatchConfig.getTrie().getEnabled()) {
return;
}
if (Boolean.TRUE.equals(selectorData.getEnabled())) {
if (CollectionUtils.isEmpty(selectorData.getBeforeConditionList())) {
eventPublisher.publishEvent(new TrieEvent(TrieEventEnum.INSERT, TrieCacheTypeEnum.SELECTOR, selectorData));
} else {
// if selector data has before condition, update trie
eventPublisher.publishEvent(new TrieEvent(TrieEventEnum.UPDATE, TrieCacheTypeEnum.SELECTOR, selectorData));
}
} else {
eventPublisher.publishEvent(new TrieEvent(TrieEventEnum.REMOVE, TrieCacheTypeEnum.SELECTOR, selectorData));
}
}
private void updateRuleTrieCache(final RuleData ruleData) {
if (!ruleMatchCacheConfig.getTrie().getEnabled()) {
return;
}
if (Boolean.TRUE.equals(ruleData.getEnabled())) {
if (CollectionUtils.isEmpty(ruleData.getBeforeConditionDataList())) {
eventPublisher.publishEvent(new TrieEvent(TrieEventEnum.INSERT, TrieCacheTypeEnum.RULE, ruleData));
} else {
// if rule data has before condition, update trie
eventPublisher.publishEvent(new TrieEvent(TrieEventEnum.UPDATE, TrieCacheTypeEnum.RULE, ruleData));
}
} else {
eventPublisher.publishEvent(new TrieEvent(TrieEventEnum.REMOVE, TrieCacheTypeEnum.RULE, ruleData));
}
}
}
public class ShenyuTrieListener implements ApplicationListener<TrieEvent> {
@Override
public void onApplicationEvent(final TrieEvent event) {
// 省略部分代码
if (CollectionUtils.isNotEmpty(filterConditions)) {
List<String> uriPaths = filterConditions.stream().map(ConditionData::getParamValue).collect(Collectors.toList());
switch (eventEnum) {
case INSERT:
insertTrieNode(uriPaths, source, cacheTypeEnum, shenyuTrie);
break;
case UPDATE:
updateTrieNode(uriPaths, source, cacheTypeEnum, shenyuTrie);
break;
case REMOVE:
removeTrieNode(uriPaths, source, cacheTypeEnum, shenyuTrie);
break;
default:
throw new IllegalStateException("Unexpected value: " + event.getTrieEventEnum());
}
}
}
}
4.3 何时匹配数据
在开启二级缓存(Trie缓存)缓存的前提下,只有继承了org.apache.shenyu.plugin.base.AbstractShenyuPlugin
类的插件才会访二级缓存(前缀树缓存)。
shenyu前缀树的匹配逻辑大致如下(此处以RuleTrie说明):
1.先从keyRootMap中通过selectorId获取到对应的ShenyuTrieNode,如果没有则返回null。
2.通过path以
/
进行划分,从第一个节点在trieNode的children中进行访问,如果存在则继续访问,如果不存在则返回null,3.如果在访问过程中,存在冲突节点,则通过AC自动机构建的失配节点继续访问,知道找到对应的有效节点,如果访问root节点还找不到,则说明无法匹配。
public abstract class AbstractShenyuPlugin implements ShenyuPlugin {
private RuleData trieMatchRule(final ServerWebExchange exchange, final SelectorData selectorData, final String path) {
if (!ruleMatchConfig.getTrie().getEnabled()) {
return null;
}
RuleData ruleData = null;
ShenyuTrieNode shenyuTrieNode = ruleTrie.match(path, selectorData.getId());
if (Objects.nonNull(shenyuTrieNode)) {
LogUtils.info(LOG, "{} rule match path from shenyu trie", named());
List<?> collection = shenyuTrieNode.getPathCache().get(selectorData.getId());
if (CollectionUtils.isNotEmpty(collection)) {
Pair<Boolean, RuleData> ruleDataPair;
if (collection.size() > 1) {
ruleDataPair = matchRule(exchange, ListUtil.castList(collection, RuleData.class::cast));
} else {
Object ruleObj = collection.stream().findFirst().orElse(null);
RuleData rule = Objects.nonNull(ruleObj) ? (RuleData) ruleObj : null;
boolean cached = Objects.nonNull(rule) && rule.getConditionDataList().stream().allMatch(condition -> URI_CONDITION_TYPE.equals(condition.getParamType()));
ruleDataPair = Pair.of(cached, rule);
}
ruleData = ruleDataPair.getRight();
if (ruleDataPair.getLeft() && Objects.nonNull(ruleData)) {
// exist only one rule data, cache rule
cacheRuleData(path, ruleData);
}
}
}
return ruleData;
}
}
4.4 何时删除数据
shenyu删除前缀树主要有两种方案
- 当禁用当前选择器或者规则时,会将前缀树的缓存的selector或者rule根据uri进行删除,该过程发生在更新选择器或者规则的时候。
public class CommonPluginDataSubscriber implements PluginDataSubscriber {
private <T> void updateCacheData(@NonNull final T data) {
if (Boolean.TRUE.equals(ruleData.getEnabled())) {
if (CollectionUtils.isEmpty(ruleData.getBeforeConditionDataList())) {
eventPublisher.publishEvent(new TrieEvent(TrieEventEnum.INSERT, TrieCacheTypeEnum.RULE, ruleData));
} else {
// if rule data has before condition, update trie
eventPublisher.publishEvent(new TrieEvent(TrieEventEnum.UPDATE, TrieCacheTypeEnum.RULE, ruleData));
}
} else {
// 如果是禁用选择器或者规则就删除前缀树中的缓存
eventPublisher.publishEvent(new TrieEvent(TrieEventEnum.REMOVE, TrieCacheTypeEnum.RULE, ruleData));
}
}
}
- 当删除当前选择器或者规则时,会将前缀树的缓存的selector或者rule进行删除。
public class CommonPluginDataSubscriber implements PluginDataSubscriber {
private <T> void removeCacheData(@NonNull final T data) {
if (data instanceof PluginData) {
} else if (data instanceof SelectorData) {
// remove selector trie cache
// 删除selector trie中的缓存
if (selectorMatchConfig.getTrie().getEnabled()) {
eventPublisher.publishEvent(new TrieEvent(TrieEventEnum.REMOVE, TrieCacheTypeEnum.SELECTOR, selectorData));
}
} else if (data instanceof RuleData) {
// 删除rule trie中的缓存
if (ruleMatchCacheConfig.getTrie().getEnabled()) {
eventPublisher.publishEvent(new TrieEvent(TrieEventEnum.REMOVE, TrieCacheTypeEnum.RULE, ruleData));
}
}
}
4.5 何时使用二级缓存
- shenyu二级缓存只有在selector/rule的condition中有uri类型的参数时才会缓存到trie中,如果没有uri类型的参数,则不会使用二级缓存。
- 尽可能在同一个selector中不要设置多个相同uri的规则,多个相同的uri会导致前缀会执行默认的筛选条件,这样会导致性能的损耗。
- 尽可能少使用含通配符
*
,**
的uri,这样会导致前缀树的匹配性能损耗。
5.三级缓存匹配
该缓存是shenyu默认的匹配缓存,selector和rule都是基于ConcurrentHashMap实现的缓存,其主要的数据结构如下:
/**
* pluginName -> SelectorData.
*/
private static final ConcurrentMap<String, List<SelectorData>> SELECTOR_MAP = Maps.newConcurrentMap();
/**
* selectorId -> RuleData.
*/
private static final ConcurrentMap<String, List<RuleData>> RULE_MAP = Maps.newConcurrentMap();
其中SELECTOR_MAP中key是pluginName,value是对应插件的选择器的集合。RULE_MAP中的key是selectorId,value是对应选择器的规则集合。
5.1 何时存入数据
shenyu默认的缓存是在网关启动后,通过数据同步或者本地模式添加数据将所有的选择器和规则都存入缓存中。并且在每次更新selector或者rule的时候都会更新对应的selector或者rule数据。
public class CommonPluginDataSubscriber implements PluginDataSubscriber {
private <T> void updateCacheData(@NonNull final T data) {
if (data instanceof PluginData) {
PluginData pluginData = (PluginData) data;
final PluginData oldPluginData = BaseDataCache.getInstance().obtainPluginData(pluginData.getName());
BaseDataCache.getInstance().cachePluginData(pluginData);
Optional.ofNullable(handlerMap.get(pluginData.getName()))
.ifPresent(handler -> handler.handlerPlugin(pluginData));
} else if (data instanceof SelectorData) {
SelectorData selectorData = (SelectorData) data;
BaseDataCache.getInstance().cacheSelectData(selectorData);
Optional.ofNullable(handlerMap.get(selectorData.getPluginName()))
.ifPresent(handler -> handler.handlerSelector(selectorData));
} else if (data instanceof RuleData) {
RuleData ruleData = (RuleData) data;
BaseDataCache.getInstance().cacheRuleData(ruleData);
Optional.ofNullable(handlerMap.get(ruleData.getPluginName()))
.ifPresent(handler -> handler.handlerRule(ruleData));
}
}
}
5.2 何时匹配数据
- 1.在一级缓存以及二级未匹配到数据的时候,会使用默认缓存进行匹配
- 2.在一级、二级缓存未开启的时候,会使用默认缓存进行匹配。
shenyu的默认缓存匹配是通过spi实现的匹配,会从rule中获取rule condition,然后根据condition的type进行匹配。
public abstract class AbstractShenyuPlugin implements ShenyuPlugin {
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final ShenyuPluginChain chain) {
List<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
}
private Boolean filterRule(final RuleData ruleData, final ServerWebExchange exchange) {
return ruleData.getEnabled() && MatchStrategyFactory.match(ruleData.getMatchMode(), ruleData.getConditionDataList(), exchange);
}
}
5.3 何时删除数据
当在shenyu-admin或者本地模式通过接口删除选择器或规则时,shenyu将会从默认缓存中删除选择器和规则的数据。
public class CommonPluginDataSubscriber implements PluginDataSubscriber {
private <T> void removeCacheData(@NonNull final T data) {
if (data instanceof PluginData) {
} else if (data instanceof SelectorData) {
SelectorData selectorData = (SelectorData) data;
BaseDataCache.getInstance().removeSelectData(selectorData);
Optional.ofNullable(handlerMap.get(selectorData.getPluginName()))
.ifPresent(handler -> handler.removeSelector(selectorData));
} else if (data instanceof RuleData) {
RuleData ruleData = (RuleData) data;
BaseDataCache.getInstance().removeRuleData(ruleData);
Optional.ofNullable(handlerMap.get(ruleData.getPluginName()))
.ifPresent(handler -> handler.removeRule(ruleData));
}
}
}