Redis和MySQL共用服务器时如何优化内存使用?

Redis 与 MySQL 共用服务器时的内存优化策略

核心挑战

Redis(全内存数据库)和 MySQL(磁盘为主、缓冲池为辅)共享同一物理内存,竞争资源可能导致:

  • OOM(Out of Memory) 导致服务崩溃
  • Swap 交换 导致性能急剧下降
  • GC/缓冲竞争 影响响应时间

一、总体架构原则

┌─────────────────────────────────────────────┐
│           物理服务器 (总内存: e.g. 32GB)       │
├──────────────────┬──────────────────────────┤
│   Redis 内存池    │     MySQL Buffer Pool    │
│   + 系统预留      │     + OS Cache          │
├──────────────────┴──────────────────────────┤
│         Linux Kernel / Swap / Other Apps     │
└─────────────────────────────────────────────┘

黄金法则:永远不要将 Redis 数据全部放入内存而不留余量给 MySQL 和操作系统。


二、Redis 侧优化

1. 合理设置 maxmemory

# 示例:32GB 服务器,分配给 Redis 8-10GB
maxmemory 8gb

# 淘汰策略选择(根据业务场景)
maxmemory-policy allkeys-lru        # 适合缓存场景
# maxmemory-policy volatile-lru     # 只淘汰有 TTL 的键
# maxmemory-policy noeviction       # 生产环境慎用,可能 OOM

2. 使用高效数据结构

# ❌ 避免:大量小 String 对象(每个都有 RedisObject 开销 ~32-48B)
redis.set("user:1:name", "Alice")
redis.set("user:2:name", "Bob")

# ✅ 推荐:Hash 压缩多个字段
redis.hset("user:1", "name", "Alice", "age", 25, "email", "a@b.com")

# ✅ 更优:使用 Pipeline + 批量操作减少网络往返和序列化开销
pipeline = redis.pipeline()
for i in range(1000):
    pipeline.hset(f"user:{i}", "name", f"User{i}")
pipeline.execute()

3. 启用压缩(Redis 6+)

# Redis 6.2+ 支持主动/被动压缩
activedefrag yes
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
replica-lazy-flush yes

4. 控制 Key 大小和数量

# ❌ 避免超大 Value
redis.set("big_data", large_binary_string)  # > 1KB 考虑分片或存 MySQL

# ✅ 限制单个 key 的大小
def set_with_size_check(key, value, max_size=1024):
    if len(value.encode()) > max_size:
        raise ValueError("Value too large for Redis")
    redis.set(key, value)

5. 监控内存碎片率

# 查看内存碎片率
redis-cli INFO memory | grep mem_fragmentation_ratio

# 理想值: 1.0 - 1.5
# > 1.5: 碎片严重,启用 activedefrag
# < 1.0: 可能使用了 overcommit_memory=0,需调整

三、MySQL 侧优化

1. 精确配置 innodb_buffer_pool_size

[mysqld]
# 公式:Buffer Pool ≈ (可用内存 × 70%) - Redis 内存 - 系统预留
# 例如:32GB 服务器,Redis 用 8GB,系统预留 4GB
# Buffer Pool ≈ (32 - 8 - 4) × 0.7 ≈ 14GB → 设为 12-14GB

innodb_buffer_pool_size = 12G
innodb_buffer_pool_instances = 8

# 关键表单独分配 buffer pool(MySQL 8.0+)
innodb_buffer_pool_instances = 8

2. 禁用不必要的日志和缓冲

# 如果不需要 binlog(如只读从库)
skip-log-bin

# 减小 sort/buffer 临时空间
sort_buffer_size = 2M
read_buffer_size = 1M
read_rnd_buffer_size = 4M
join_buffer_size = 1M

# 关闭 query cache(MySQL 8.0 已移除,5.7 建议关闭)
query_cache_type = 0
query_cache_size = 0

3. 优化 InnoDB 页大小和刷盘策略

# 减少 fsync 频率,牺牲少量持久性换取内存效率
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT

四、操作系统层面优化

1. 内存隔离与控制组(cgroups)

# CentOS/RHEL: 使用 systemd slice 限制
cat <<EOF > /etc/systemd/system/redis.service.d/memory.conf
[Service]
MemoryMax=9G
MemoryHigh=8.5G
EOF

# MySQL 同样限制
cat <<EOF > /etc/systemd/system/mysqld.service.d/memory.conf
[Service]
MemoryMax=14G
MemoryHigh=13G
EOF

2. 调整 Overcommit 内存策略

# Redis 要求此值为 1,否则可能因 OOM Killer 被终止
echo "vm.overcommit_memory = 1" >> /etc/sysctl.conf
sysctl -p

# 减少 swap 使用倾向
echo "vm.swappiness = 10" >> /etc/sysctl.conf
sysctl -p

3. NUMA 亲和性(多 NUMA 节点服务器)

# 绑定 Redis 到特定 NUMA 节点
numactl --membind=0 --cpus=0-7 redis-server --port 6379

# MySQL 同样处理
numactl --membind=1 --cpus=8-15 mysqld_safe &

五、动态内存分配方案

方案对比

方案 优点 缺点
静态划分 简单可控 资源利用率低
动态协商 利用率高 实现复杂
优先级抢占 保证关键服务 可能饿死另一方

动态内存管理伪代码

import time
import psutil

class MemoryManager:
    def __init__(self, total_mem, redis_target=0.25, mysql_target=0.55):
        self.total_mem = total_mem
        self.redis_max = int(total_mem * redis_target)
        self.mysql_max = int(total_mem * mysql_target)
        self.system_reserve = int(total_mem * 0.10)

    def get_available_memory(self):
        return psutil.virtual_memory().available

    def adjust_redis_maxmemory(self):
        avail = self.get_available_memory()
        current_redis_usage = self.get_redis_used_memory()

        if current_redis_usage > self.redis_max:
            # 触发 Redis 淘汰
            redis_cli.execute_command("CONFIG SET maxmemory", self.redis_max)

        # 如果 MySQL 压力小,可临时释放部分内存给 Redis
        if self.get_mysql_buffer_pool_usage() < self.mysql_max * 0.6:
            new_limit = min(self.redis_max + 1024*1024*512, 
                          self.total_mem - self.mysql_max - self.system_reserve)
            redis_cli.execute_command("CONFIG SET maxmemory", new_limit)

    def monitor_loop(self):
        while True:
            self.adjust_redis_maxmemory()
            time.sleep(30)

六、监控与告警体系

关键监控指标

monitoring_metrics:
  redis:
    - used_memory_human          # 当前使用内存
    - maxmemory_human            # 最大允许内存
    - mem_fragmentation_ratio   # 碎片率
    - evicted_keys              # 被驱逐的 key 数
    - keyspace_hits/misses      # 缓存命中率

  mysql:
    - innodb_buffer_pool_pages_free   # 空闲页
    - innodb_buffer_pool_read_requests  # 逻辑读
    - innodb_buffer_pool_reads        # 物理读
    - QPS / TPS                       # 负载

  system:
    - available_memory                # 可用内存
    - swap_usage                      # swap 使用率
    - page_faults                     # 缺页中断
    - oom_kill_count                  # OOM 事件

Grafana 告警规则示例

# Redis 内存接近上限
redis_memory_used_bytes / redis_maxmemory_bytes > 0.85

# MySQL Buffer Pool 命中率下降
1 - (mysql_innodb_buffer_pool_read_requests / 
     mysql_innodb_buffer_pool_reads) < 0.95

# 系统 swap 开始使用
system_swap_usage_bytes > 0

# OOM 事件
increase(node_vmstat_oom_kill[5m]) > 0

七、容量规划计算示例

假设服务器: 32GB RAM, 8 CPU, SSD

步骤1: 确定 Redis 数据规模
  - 活跃数据: 5GB
  - 峰值并发缓存: 3GB
  - 安全系数: 1.5x
  → Redis maxmemory = 12GB × 1.5 = 18GB? 不!

步骤2: 保守分配
  - Redis:     8GB  (25%)
  - MySQL:    12GB  (37.5%) — innodb_buffer_pool_size
  - OS + 其他: 4GB   (12.5%)
  - 预留弹性:  8GB   (25%)  ← 动态调节空间

步骤3: 验证
  Redis 峰值使用: ~6.5GB < 8GB ✓
  MySQL 热点数据: ~10GB < 12GB ✓
  剩余 8GB 应对突发流量 ✓

八、最佳实践总结

✅ 必须做                          ❌ 绝对避免
─────────────────────────────────────────────────
设定明确的 maxmemory             让 Redis 无限制增长
使用 LRU/LFU 淘汰策略            依赖 swap 作为扩展
cgroups 限制各进程内存上限       所有组件都设默认值
监控碎片率和淘汰计数             忽略 OOM 风险
定期清理过期和无效 key           存储 >1MB 的大对象
分离冷热数据                     在同一台机器跑所有中间件

最终建议:如果业务持续增长,优先考虑拆分。Redis 和 MySQL 分离到不同服务器是更可靠的长期方案。共用服务器仅适用于中小规模、预算受限的场景,且必须有完善的监控和应急预案。

未经允许不得转载:CLOUD技术博 » Redis和MySQL共用服务器时如何优化内存使用?