AI推理场景下Redis缓存策略与MySQL分库分表实战

AI推理场景的数据库性能瓶颈特征

大模型推理服务的数据库访问模式与传统Web应用差异显著:读取请求以长文本Session历史为主(单条记录可能数十KB),写入以Token计费流水和推理日志为主(高吞吐低延迟),缓存命中率对推理延迟有直接影响。当推理服务并发量达到1000+ QPS时,数据库成为首要瓶颈。本文给出Redis缓存层和MySQL分库分表的完整优化方案,覆盖推理服务场景下的读写分离、缓存穿透防护和水平分片策略。

Redis多级缓存架构设计

AI推理场景的缓存需求分为三个层级:Session历史缓存(热数据)、模型响应缓存(相似问题去重)、工具调用结果缓存(过期策略与一致性):

# Redis多级缓存配置
# redis.conf
maxmemory 64gb
maxmemory-policy allkeys-lru

# 不同缓存类型的TTL和淘汰策略
# Session历史: TTL 30min, LRU淘汰
# 推理响应: TTL 24h, volatile-ttl淘汰
# 工具结果: TTL 5min, volatile-lru淘汰

使用Redis Cluster集群(6主6从),每个分片负责不同的缓存类型:

// RedisCacheManager.java
@Configuration
public class RedisCacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration sessionConfig = RedisCacheConfiguration
            .defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            .serializeValuesWith(RedisSerializationContext
                .SerializationPair.fromSerializer(
                    new GenericJackson2JsonRedisSerializer()));

        RedisCacheConfiguration inferenceConfig = RedisCacheConfiguration
            .defaultCacheConfig()
            .entryTtl(Duration.ofHours(24))
            .serializeValuesWith(RedisSerializationContext
                .SerializationPair.fromSerializer(
                    new GenericJackson2JsonRedisSerializer()));

        RedisCacheConfiguration toolResultConfig = RedisCacheConfiguration
            .defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(5))
            .serializeValuesWith(RedisSerializationContext
                .SerializationPair.fromSerializer(
                    new GenericJackson2JsonRedisSerializer()));

        Map<String, RedisCacheConfiguration> configs = Map.of(
            "session", sessionConfig,
            "inference", inferenceConfig,
            "tool-result", toolResultConfig
        );

        return RedisCacheManager.builder(factory)
            .withInitialCacheConfigurations(configs)
            .build();
    }
}

Session缓存TTL 30分钟对应用户平均对话时长;推理响应缓存24小时覆盖相似问题去重场景;工具结果缓存5分钟平衡时效性与命中率。

缓存穿透防护与布隆过滤器

AI推理场景的缓存穿透风险来自两个方向:不存在的Session ID被大量请求查询、工具调用参数组合不可预测导致缓存未命中。布隆过滤器拦截第一种情况:

// BloomFilterService.java
@Service
public class BloomFilterService {

    private final RedissonClient redisson;
    private final RBloomFilter<String> sessionIdFilter;

    public BloomFilterService(RedissonClient redisson) {
        this.redisson = redisson;
        this.sessionIdFilter = redisson.getBloomFilter("session_id_filter");
        // 预期元素1亿,误判率0.01
        sessionIdFilter.tryInit(100_000_000L, 0.01);
    }

    public boolean mightExist(String sessionId) {
        return sessionIdFilter.contains(sessionId);
    }

    public void addSessionId(String sessionId) {
        sessionIdFilter.add(sessionId);
    }
}

// SessionCacheService.java - 带穿透防护的查询
@Service
@RequiredArgsConstructor
public class SessionCacheService {

    private final BloomFilterService bloomFilter;
    private final RedisTemplate<String, Object> redisTemplate;
    private final SessionRepository sessionRepo;

    public Session getSession(String sessionId) {
        // 1. 布隆过滤器快速判断
        if (!bloomFilter.mightExist(sessionId)) {
            return null; // 确定不存在,避免查库
        }

        // 2. 查询Redis缓存
        String cacheKey = "session:" + sessionId;
        Session cached = (Session) redisTemplate.opsForValue().get(cacheKey);
        if (cached != null) {
            return cached;
        }

        // 3. 缓存未命中,查库并回填
        Session session = sessionRepo.findById(sessionId).orElse(null);
        if (session != null) {
            redisTemplate.opsForValue().set(
                cacheKey, session, Duration.ofMinutes(30));
        } else {
            // 防穿透:空值缓存,短TTL
            redisTemplate.opsForValue().set(
                cacheKey, "NULL", Duration.ofMinutes(2));
        }
        return session;
    }
}

布隆过滤器预期容量1亿、误判率1%,内存占用约114MB。对于不存在的Session ID,查询在布隆过滤器层面即被拦截,不会穿透到数据库。空值缓存TTL设为2分钟,防止短时间内对同一不存在的key重复查库。

MySQL分库分表策略与ShardingSphere配置

AI推理场景中Token计费流水表的写入量最高,单表日增约5000万条,必须分片。按租户ID进行分库、按日期进行分表:

# application-sharding.yml - ShardingSphere分片配置
rules:
  - !SHARDING
    tables:
      token_usage:
        actual-data-nodes: ds_${0..3}.token_usage_${202601..202612}
        database-strategy:
          standard:
            sharding-column: tenant_id
            sharding-algorithm-name: tenant-mod
        table-strategy:
          standard:
            sharding-column: created_date
            sharding-algorithm-name: date-month
    sharding-algorithms:
      tenant-mod:
        type: MOD
        props:
          sharding-count: 4
      date-month:
        type: INTERVAL
        props:
          datetime-pattern: yyyyMMdd
          datetime-lower: 20260101
          datetime-upper: 20261231
          sharding-suffix-pattern: yyyyMM
          interval-amount: 1
          interval-unit: MONTHS
    key-generators:
      snowflake:
        type: SNOWFLAKE
        props:
          worker-id: 1

4个库按租户ID取模分片,每月一张表。单表月数据量约4亿/4/12等于830万条,索引查询性能可控。按租户分库保证同一租户的数据在同一物理库中,跨租户查询不会产生跨库join。

推理日志的时序存储优化

推理日志(请求参数、响应Token数、延迟、模型版本等)属于时序数据,写入后极少更新。使用MySQL按月分表存储,但查询性能随数据量增长下降。优化手段包括冷热数据分离和索引精简:

-- 推理日志表(按月分表)
CREATE TABLE inference_log_202607 (
    id BIGINT PRIMARY KEY,
    session_id VARCHAR(64) NOT NULL,
    tenant_id INT NOT NULL,
    model VARCHAR(32) NOT NULL,
    prompt_tokens INT NOT NULL,
    completion_tokens INT NOT NULL,
    latency_ms INT NOT NULL,
    status TINYINT NOT NULL,
    created_at TIMESTAMP NOT NULL,
    -- 只保留必要索引,避免写入开销
    INDEX idx_tenant_created (tenant_id, created_at),
    INDEX idx_session (session_id)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;

-- 3个月以上数据迁移到归档库
INSERT INTO archive_db.inference_log_202604
SELECT * FROM inference_log_202604
WHERE created_at < '2026-05-01';

DELETE FROM inference_log_202604
WHERE created_at < '2026-05-01';

ROW_FORMAT=COMPRESSED配合KEY_BLOCK_SIZE=8将存储空间压缩约50%。索引只保留租户+时间和Session两个组合索引,单行写入开销从32字节索引开销降至12字节。归库操作在业务低峰期(凌晨3点)通过定时任务执行,单次迁移100万行约耗时4分钟。

Redis集群监控与MySQL慢查询治理

Redis集群需要重点监控的指标:内存使用率(阈值85%)、缓存命中率(阈值90%)、主从复制延迟(阈值1秒)。Prometheus + Grafana的告警规则:

# Prometheus Redis告警规则
groups:
- name: redis_alerts
  rules:
  - alert: RedisCacheHitRateLow
    expr: rate(redis_commands_hits_total[5m]) / (rate(redis_commands_hits_total[5m]) + rate(redis_commands_misses_total[5m])) < 0.9
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "Redis cache hit rate below 90%"

  - alert: RedisMemoryHigh
    expr: redis_memory_used_bytes / redis_memory_max_bytes > 0.85
    for: 3m
    labels:
      severity: critical
    annotations:
      summary: "Redis memory usage exceeds 85%"

MySQL慢查询治理的核心是定期分析slow_query_log。AI推理场景下最常见的慢查询是Session历史表的全表扫描(缺少合适索引)和Token计费的聚合统计(数据量过大)。前者通过添加覆盖索引解决,后者通过预聚合表(每小时统计一次各租户的Token用量)消除实时聚合开销。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/ai-tui-li-chang-jing-xia-redis-huan-cun-ce-lyue-yu-mysql/

(0)
小编小编
上一篇 2小时前
下一篇 2小时前

相关推荐