You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

高负载场景下特性灰度发布服务缓存优化方案咨询

高负载场景下特性灰度发布服务的缓存优化方案

问题根源

现有缓存键包含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. 请求匹配逻辑

处理请求时按以下步骤执行:

  1. 从缓存中获取当前feature对应的维度索引Map。
  2. 生成当前请求的所有可能维度组合标识(从最具体到最宽泛):
    例如请求参数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
  3. 依次在索引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. 缓存键设计

不再使用包含所有参数的键,而是按规则匹配优先级层级生成缓存键,层级从高到低对应规则优先级:

  1. feature:xxx:hotel:456:ota:123:propertyType:HOTEL:country:CN
  2. feature:xxx:hotel:456:ota:123:propertyType:HOTEL
  3. feature: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 22:43:15