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/