高负载场景下特性灰度发布服务缓存优化方案咨询
高负载场景下特性灰度发布服务的缓存优化方案
问题根源
现有缓存键包含hotelId,但多数规则为国家维度(hotelId=null),导致每个hotelId都会生成独立缓存项,无法复用缓存内容,缓存命中率极低,进而引发数据库查询量暴增,无法支撑百万级日调用量。
方案一:缓存全量规则集,按特征维度分层构建索引
1. 缓存结构设计
缓存全量规则数据,按feature_name作为顶级键,下一层按规则的非空维度组合构建哈希索引:
- 每个feature对应一个Map,键为
维度标识字符串(例如country:CN、ota:123:country:CN、hotel:456),值为该维度下预计算的最高优先级规则结果。 - 维度标识生成逻辑:仅包含规则中非空的维度,按权重从高到低排序(
hotelId > otaId > propertyType > countryId,对应权重8>4>2>1),避免冗余维度干扰匹配。
2. 缓存刷新策略
- 定时刷新:每5分钟拉取全量
FeatureRolloutRule数据,重新构建缓存索引。 - 事件驱动刷新(可选):监听规则变更事件,主动触发缓存更新,降低脏数据延迟。
3. 请求匹配逻辑
处理请求时按以下步骤执行:
- 从缓存中获取当前feature对应的维度索引Map。
- 生成当前请求的所有可能维度组合标识(从最具体到最宽泛):
例如请求参数hotelId=456、otaId=123、countryId=CN、propertyType=HOTEL,生成的标识依次为:hotel:456:ota:123:propertyType:HOTEL:country:CN→hotel:456:ota:123:propertyType:HOTEL→hotel:456:ota:123:country:CN→ ... →global - 依次在索引Map中查找标识,找到第一个存在的条目,直接返回预计算的结果。
代码示例(缓存构建逻辑)
// 缓存结构:Key为Feature,Value为维度索引Map private Map<Feature, Map<String, RuleResult>> featureRuleCache; private void buildFeatureRuleIndex(List<FeatureRolloutRule> allRules) { featureRuleCache = new HashMap<>(); for (FeatureRolloutRule rule : allRules) { Feature feature = rule.getFeatureName(); featureRuleCache.computeIfAbsent(feature, k -> new HashMap<>()); // 生成非空维度的标识字符串 StringBuilder keyBuilder = new StringBuilder(); if (rule.getPropertyId() != null) { keyBuilder.append("hotel:").append(rule.getPropertyId()).append(":"); } if (rule.getOtaId() != null) { keyBuilder.append("ota:").append(rule.getOtaId()).append(":"); } if (rule.getPropertyType() != null) { keyBuilder.append("propertyType:").append(rule.getPropertyType()).append(":"); } if (rule.getCountryId() != null) { keyBuilder.append("country:").append(rule.getCountryId()).append(":"); } String indexKey = keyBuilder.length() > 0 ? keyBuilder.substring(0, keyBuilder.length()-1) : "global"; // 预计算该维度下的最高优先级规则结果 Map<String, RuleResult> featureIndex = featureRuleCache.get(feature); RuleResult existingResult = featureIndex.get(indexKey); int currentRuleLevel = getRuleLevel(rule); if (existingResult == null || currentRuleLevel > existingResult.getRuleLevel()) { boolean verdict = "ENABLE".equals(rule.getRuleType()); featureIndex.put(indexKey, new RuleResult(verdict, currentRuleLevel)); } } } // 存储预计算结果和规则优先级的辅助类 static class RuleResult { private boolean verdict; private int ruleLevel; public RuleResult(boolean verdict, int ruleLevel) { this.verdict = verdict; this.ruleLevel = ruleLevel; } // getter方法省略 }
方案优势
- 缓存复用率极高:同一国家/OTA维度的请求共享缓存项,不会因
hotelId不同重复缓存。 - 内存占用可控:仅存储预计算结果,而非全量规则列表,按维度聚合避免冗余。
方案二:优化现有缓存键,按规则优先级层级缓存
1. 缓存键设计
不再使用包含所有参数的键,而是按规则匹配优先级层级生成缓存键,层级从高到低对应规则优先级:
feature:xxx:hotel:456:ota:123:propertyType:HOTEL:country:CNfeature:xxx:hotel:456:ota:123:propertyType:HOTELfeature:xxx:hotel:456:ota:123:country:CN
...
N.feature:xxx:global
2. 缓存查询逻辑
处理请求时从最高层级的键开始查询缓存,命中则直接返回;未命中则向下一层级查询,直到找到命中项。若最终未命中,查询数据库后将结果缓存到所有未命中的层级,提升后续请求命中率。
方案优势
- 兼容现有缓存框架:无需大幅修改代码,仅需调整缓存键生成逻辑和查询顺序。
- 命中率逐步提升:相同维度的后续请求直接命中缓存,避免重复计算。
方案三:引入布隆过滤器(可选,快速过滤无规则请求)
如果大部分请求无匹配规则,可引入布隆过滤器优化:
- 针对每个feature,构建布隆过滤器,存储所有规则涉及的具体维度值(如hotelId、otaId、countryId等)。
- 请求到来时先通过布隆过滤器判断是否存在匹配可能,若不存在则直接返回默认结果(如
false),无需查询缓存或数据库。
额外优化建议
- 数据库索引优化:为
FeatureRolloutRule表添加联合索引(feature_name, property_id, ota_id, property_type, country_id),提升查询速度。 - 缓存内存控制:使用Caffeine的
maximumSize限制缓存内存占用,结合expireAfterWrite避免脏数据。 - 异步刷新缓存:将缓存刷新操作放在异步线程中执行,避免阻塞业务请求。
内容的提问来源于stack exchange,提问作者Mukul Malik
相关产品推荐
相关产品推荐

