工程实践 Redis

Redis 使用规范:高并发场景实践要点

生产环境 Redis 常见规范:键设计、命令选择、内存管理、连接池配置、监控告警,以及高合规场景下的安全要求。

发布于 2026/03/21 更新于 2026/03/21 1 分钟

“Redis 用不对,轻则性能下降,重则引发数据不一致。“

前言

Redis 在高并发业务中常承担热点数据缓存、分布式锁、会话存储、实时排行榜等职责。用错了,轻则响应变慢,重则出现数据不一致甚至生产事故。以下规范主要基于通用工程经验整理。

1. 键设计规范

1.1 命名规范

# 格式:业务:实体:唯一标识:属性
# 优点:语义清晰,通过 SCAN 扫描时方便过滤

# ✅ 推荐命名
payment:lock:order-12345
payment:session:user-1000:token-abc
cache:user:profile:1000
cache:product:detail:SKU-8888
rate:limit:api:/v1/payment:client-hsbc
dau:2026-03-21
sign:user-1000:2026-03

# ❌ 避免命名
mykey                           # 无意义
user1000info                    # 难以分割
UserProfile_1000                # 用了下划线,不一致
data                            # 过于通用,易冲突

1.2 键过期策略

// 1. 所有缓存类 key 必须设置 TTL
redis.opsForValue().set("cache:user:1000", userJson,
    Duration.ofHours(1));  // 必须指定过期时间

// 2. 生产者-消费者模式:消息队列 key TTL = 处理时间 × 重试次数 + buffer
redis.opsForStream().add(StreamRecords.newRecord()
    .in("task:queue")
    .ofMap(payload));
// 同时设置 key 的 maxmemory-policy 为 LRU/LFU

// 3. 会话 key:TTL = 业务超时时间 × 1.5(网络延迟 buffer)
// 银行会话:30 分钟超时 → key TTL = 45 分钟

// 4. 分布式锁:TTL = 业务最大处理时间 + 30 秒
RLock lock = redisson.getLock("payment:lock:" + orderId);
lock.tryLock(0, 45, TimeUnit.SECONDS);  // TTL = 45 秒

1.3 键大小控制

# ✅ 单个 key 的 value 大小建议 < 10MB
# ✅ 单个 key 包含的成员数:
#    String: < 10MB
#    Hash: 字段数 < 10000
#    Set/List/ZSet: 成员数 < 50000

# ❌ 避免的 Big Key:
GET cache:file:large-document  # 返回 50MB → 网络超时
SMEMBERS cache:tags:all          # 返回百万成员 → 主线程阻塞
LRANGE notification:queue 0 -1   # 返回全量 → O(N) 阻塞
// Big Key 检测(SCAN + TYPE)
public void detectBigKeys() {
    ScanOptions options = ScanOptions.scanOptions()
        .match("*")
        .count(1000)
        .build();

    try (Cursor<String> cursor = redis.scan(options)) {
        cursor.forEachRemaining(key -> {
            String type = redis.type(key).toString();
            long size = estimateSize(key, type);
            if (size > 10 * 1024 * 1024) {  // > 10MB
                log.warn("Big Key detected: {} (type={}, size={}MB)",
                    key, type, size / 1024 / 1024);
            }
        });
    }
}

private long estimateSize(String key, String type) {
    return switch (type) {
        case "string" -> {
            String val = redis.opsForValue().get(key);
            yield val != null ? val.getBytes().length : 0;
        }
        case "hash" -> {
            Long size = redis.opsForHash().size(key);
            yield size != null ? size : 0;
        }
        case "list" -> {
            Long size = redis.opsForList().size(key);
            yield size != null ? size : 0;
        }
        default -> 0;
    };
}

2. 命令使用规范

# ❌ 禁止在生产环境使用的命令
KEYS *                    # O(N) 全表扫描,阻塞 Redis
FLUSHDB / FLUSHALL        # 删除所有数据(测试环境也慎用)
CONFIG SET/GET           # 运行时修改配置(危险)
DEBUG SEGFAULT           # 触发崩溃(绝对禁止)
SLOWLEN 0                # 记录所有命令(影响性能)

# ⚠️ 谨慎使用的命令(确认数据量小)
SMEMBERS set             # 返回全部成员
LRANGE list 0 -1         # 返回全部元素
SORT set                 # 排序(非 O(log N))
KEYS user:*              # 匹配扫描(用 SCAN 替代)

# ✅ 推荐替代方案
SCAN cursor MATCH user:* COUNT 1000  # 渐进式遍历
SSCAN myset 0 COUNT 100             # Set 遍历
HSCAN myhash 0 COUNT 100            # Hash 遍历
L_RANGE list 0 99                   # 分页获取

3. 内存管理规范

# 1. 设置 maxmemory(物理内存的 70-80%)
CONFIG SET maxmemory 8gb

# 2. 选择合适的淘汰策略
# noeviction(默认):不淘汰,返回错误 → 适合写入不多的场景
# allkeys-lru:所有 key 中淘汰最近最少使用 → 适合缓存场景 ✅
# allkeys-lfu:所有 key 中淘汰最不常用 → 适合热点数据 ✅
# volatile-lru:在设置了过期时间的 key 中淘汰 LRU → 适合混合场景
# allkeys-random:随机淘汰 → 不推荐
CONFIG SET maxmemory-policy allkeys-lru

# 3. 开启内存碎片整理
CONFIG SET activedefrag yes
CONFIG SET active-defrag-ignore-bybytes 100mb
CONFIG SET active-defrag-threshold-lower 10
// 内存水位监控
public boolean isMemoryPressure() {
    String info = redis.getConnectionFactory().getConnection().info("memory");
    Map<String, String> memory = parseInfo(info);

    long used = parseLong(memory.get("used_memory"));
    long maxmemory = parseLong(memory.get("maxmemory"));
    double ratio = (double) used / maxmemory;

    if (ratio > 0.85) {
        log.error("Redis 内存使用率过高: {}%", String.format("%.1f", ratio * 100));
        return true;
    }
    return false;
}

4. 连接池规范

# Spring Boot 配置示例(生产环境)
spring:
  redis:
    host: redis.prod.internal
    port: 6379
    password: ${REDIS_PASSWORD}
    database: 0
    timeout: 2000ms
    lettuce:
      pool:
        enabled: true
        max-active: 200      # 最大连接数(按并发量调整)
        max-idle: 20         # 最大空闲连接
        min-idle: 10         # 最小空闲连接(预热)
        max-wait: 1000ms     # 获取连接超时
      # 连接池监控
      metrics:
        enabled: true
// 连接池使用规范

// 1. 使用完必须释放连接(Spring Data Redis 自动归还)
// 不用手动释放,但不要在方法内持有连接引用

// 2. 避免在单次操作中频繁创建连接
// ❌ 错误:循环内每次创建连接
for (String userId : userIds) {
    redis.opsForValue().get("user:" + userId);  // N 次网络往返
}

// ✅ 正确:使用 Pipeline 批量操作
List<Object> results = redis.executePipelined((RedisCallback<Object>) conn -> {
    for (String userId : userIds) {
        conn.stringCommands().get(("user:" + userId).getBytes());
    }
    return null;
});

// 3. 合理设置超时
redisTemplate.getConnectionFactory().getConnection()
    .setTimeout(TimeUnit.SECONDS.toMillis(2));

5. 集群与高可用规范

# 1. 生产环境必须使用 Sentinel 或 Cluster
# 单节点 Redis 只用于开发/测试

# 2. Sentinel 最小配置:3 个节点(多数投票)
# sentinel monitor mymaster 127.0.0.1 6379 2

# 3. Cluster 最小配置:6 个节点(3 主 3 从)
redis-cli --cluster create 10.0.0.1:6379 10.0.0.2:6379 \
  10.0.0.3:6379 10.0.0.4:6379 10.0.0.5:6379 10.0.0.6:6379 \
  --cluster-replicas 1

# 4. 读写分离配置
# 读操作:优先读从节点(降低主节点压力)
# 写操作:必须写主节点
@Configuration
public class RedisConfig {
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        // 配置读写分离
        return new LettuceConnectionFactory();
    }
}

6. 安全规范(银行必读)

# 1. 防火墙:只允许应用服务器访问 Redis 端口
# iptables -A INPUT -p tcp -s 10.0.0.0/8 --dport 6379 -j ACCEPT
# iptables -A INPUT -p tcp --dport 6379 -j DROP

# 2. 强密码(Redis 6.0+ ACL)
# requirepass <强密码>(≥32 位,包含特殊字符)
CONFIG SET requirepass "你的强密码"

# 3. 禁止公网访问
# bind 127.0.0.1 10.0.0.0/8  # 只绑定内网 IP

# 4. 禁止危险命令重命名
CONFIG SET rename-command FLUSHDB ""
CONFIG SET rename-command FLUSHALL ""
CONFIG SET rename-command KEYS ""

# 5. 使用 SSL/TLS(Redis 6.0+)
# tls-port 6379
# port 0
# requirepass <密码>

# 6. 审计日志(银行合规要求)
# 记录所有写操作到单独日志文件
# appendonly yes
# appendfsync everysec
// 应用层安全:敏感数据加密存储
@Service
public class SecureCacheService {
    private final RedisTemplate<String, byte[]> redis;

    // 敏感数据(如账号余额)存入前加密
    public void cacheSecureData(String key, Object data) {
        String json = JSON.toJSONString(data);
        byte[] encrypted = encrypt(json);  // AES-256 加密
        redis.opsForValue().set(key, encrypted,
            Duration.ofHours(1));
    }

    // 读取后解密
    public Object getSecureData(String key) {
        byte[] encrypted = redis.opsForValue().get(key);
        if (encrypted == null) return null;
        String json = decrypt(encrypted);
        return JSON.parse(json);
    }
}

7. 监控与告警规范

// Redis 监控指标(使用 Micrometer 上报 Prometheus)
@Component
@Slf4j
public class RedisMetricsCollector {
    private final RedisTemplate<String, Object> redis;
    private final MeterRegistry meterRegistry;

    @Scheduled(fixedRate = 30000)  // 每 30 秒采集
    public void collectMetrics() {
        Properties info = redis.getConnectionFactory()
            .getConnection().serverCommands().info();

        // 内存使用率
        double memUsed = Double.parseDouble(
            info.getProperty("used_memory"));
        double memMax = Double.parseDouble(
            info.getProperty("maxmemory"));
        meterRegistry.gauge("redis.memory.used.ratio",
            memUsed / memMax);

        // 连接数
        long connected = Long.parseLong(
            info.getProperty("connected_clients"));
        meterRegistry.gauge("redis.clients.connected",
            connected);

        // QPS
        long cmdCount = Long.parseLong(
            info.getProperty("total_commands_processed"));
        meterRegistry.gauge("redis.qps",
            cmdCount);

        // 命中率
        double hits = Double.parseDouble(
            info.getProperty("keyspace_hits"));
        double misses = Double.parseLong(
            info.getProperty("keyspace_misses"));
        double hitRate = (hits / (hits + misses)) * 100;
        log.info("Redis 命中率: {}%", String.format("%.2f", hitRate));
    }
}

8. 规范速查表

类别规范级别
键设计使用冒号分隔的语义化命名必须
键设计所有缓存 key 必须设置 TTL必须
键设计单个 value < 10MB必须
命令禁止使用 KEYS/FLUSHDB/FLUSHALL必须
命令大数据量用 SCAN 替代 KEYS必须
内存设置 maxmemory 和淘汰策略必须
连接使用连接池(max-active 200)必须
连接批量操作使用 Pipeline推荐
安全设置强密码 + ACL必须
安全禁止公网访问 + bind 内网必须
监控内存使用率 > 85% 告警必须
监控QPS 异常下跌告警必须
高可用生产环境必须使用 Sentinel/Cluster必须

相关阅读:[Redis Scan 命令用法](/coding/Redis/Redis Scan 命令用法) · [Redis 实现分布式锁](/coding/Redis/Redis 实现分布式锁) · [Redis 配置文件解析](/coding/Redis/Redis 配置文件解析)