当前位置:首页 > 问答 > 正文

数据库优化 数据缓存 从 MySql 到 Redis,实现数据写入,写数据到redis

数据库优化 | 数据缓存:从MySQL到Redis的平滑迁移实战

场景引入:电商系统的性能瓶颈

记得去年我们上线的新电商平台吗?刚开始用户量不大,系统运行得挺顺畅,但随着"黑色星期五"促销活动的临近,数据库开始频繁告警——商品详情页的查询响应时间从原来的50毫秒飙升到2秒,高峰期甚至出现超时错误。

我们的运维团队连夜加班,最终发现问题出在MySQL数据库的频繁读写上,每次用户查看商品,系统都要从MySQL读取完整数据,而促销期间这类查询请求暴增了50倍,这时候,技术总监老张拍板:"是时候引入Redis缓存了!"

为什么需要Redis缓存?

MySQL是个优秀的关系型数据库,但在高并发读取场景下,把所有压力都丢给它就像让一个会计同时处理1000份报表——迟早要崩溃,Redis作为内存数据库,读取速度能达到微秒级,比MySQL快100倍左右。

Redis帮我们解决了:

  1. 高频读取压力:80%的请求其实都在查那20%的热门商品
  2. 复杂计算缓存:商品详情页的综合评分、月销量等聚合数据
  3. 突发流量缓冲:秒杀活动时的请求洪峰

基础架构设计

我们的目标是构建这样一个流程:

客户端请求 → 先查Redis → 命中则返回 → 未命中则查MySQL → 写入Redis → 返回数据

对于写入操作则是:

写入MySQL → 成功后同步/异步更新Redis

具体实现步骤

环境准备

首先确保你的环境已经安装:

数据库优化 数据缓存 从 MySql 到 Redis,实现数据写入,写数据到redis

  • MySQL 5.7+ (我们用的是8.0)
  • Redis 6.2+ (我们选择6.2.6稳定版)
  • 对应的客户端驱动(如Python的pymysql和redis-py)

基础读写模式实现

以Python为例,先看最基本的读取逻辑:

import redis
import pymysql
from datetime import datetime
# 初始化连接
redis_conn = redis.Redis(host='localhost', port=6379, db=0)
mysql_conn = pymysql.connect(host='localhost', 
                           user='root',
                           password='yourpassword',
                           database='ecommerce')
def get_product(product_id):
    # 先尝试从Redis获取
    cache_key = f"product:{product_id}"
    product_data = redis_conn.get(cache_key)
    if product_data:
        print(f"{datetime.now()} 缓存命中 {cache_key}")
        return eval(product_data)  # 实际项目建议用JSON
    # 缓存未命中,查询数据库
    print(f"{datetime.now()} 缓存未命中,查询数据库 {product_id}")
    with mysql_conn.cursor(pymysql.cursors.DictCursor) as cursor:
        cursor.execute("SELECT * FROM products WHERE id=%s", (product_id,))
        result = cursor.fetchone()
        if not result:
            return None
        # 写入Redis,设置30分钟过期
        redis_conn.setex(cache_key, 1800, str(result))
        return result

写入策略设计

写入时需要考虑缓存一致性,我们采用"写穿"策略:

def update_product(product_id, update_data):
    # 先更新数据库
    with mysql_conn.cursor() as cursor:
        set_clause = ", ".join([f"{k}=%s" for k in update_data.keys()])
        values = list(update_data.values())
        values.append(product_id)
        sql = f"UPDATE products SET {set_clause} WHERE id=%s"
        cursor.execute(sql, values)
    mysql_conn.commit()
    # 同步更新缓存
    cache_key = f"product:{product_id}"
    with mysql_conn.cursor(pymysql.cursors.DictCursor) as cursor:
        cursor.execute("SELECT * FROM products WHERE id=%s", (product_id,))
        updated_product = cursor.fetchone()
        if updated_product:
            redis_conn.setex(cache_key, 1800, str(updated_product))
    return True

高级优化技巧

在实际项目中,我们还需要考虑:

缓存雪崩防护

# 为不同的key设置随机过期时间
import random
expire_time = 1800 + random.randint(0, 300)  # 30-35分钟随机

热点数据永不过期

数据库优化 数据缓存 从 MySql 到 Redis,实现数据写入,写数据到redis

# 对特别热门的数据不设置过期时间,通过后台定期更新
redis_conn.set("hot:product:1001", product_data)

批量查询优化

def get_multiple_products(ids):
    # 构建所有缓存key
    cache_keys = [f"product:{id}" for id in ids]
    # 批量获取
    cached_items = redis_conn.mget(cache_keys)
    results = {}
    missing_ids = []
    # 处理结果
    for id, key, data in zip(ids, cache_keys, cached_items):
        if data:
            results[id] = eval(data)
        else:
            missing_ids.append(id)
    # 处理未命中的
    if missing_ids:
        with mysql_conn.cursor(pymysql.cursors.DictCursor) as cursor:
            placeholder = ",".join(["%s"]*len(missing_ids))
            cursor.execute(f"SELECT * FROM products WHERE id IN ({placeholder})", missing_ids)
            db_results = cursor.fetchall()
            for item in db_results:
                key = f"product:{item['id']}"
                redis_conn.setex(key, 1800, str(item))
                results[item['id']] = item
    return results

踩坑经验分享

在实施过程中,我们遇到过几个典型问题:

  1. 缓存穿透:有恶意请求查询不存在的ID,导致直接打到数据库

    • 解决方案:对空结果也进行短时间缓存
      if not result:
        redis_conn.setex(cache_key, 60, "NULL")  # 缓存空值1分钟
        return None
  2. 数据不一致:MySQL更新成功但Redis更新失败

    解决方案:增加重试机制或引入消息队列异步处理

    数据库优化 数据缓存 从 MySql 到 Redis,实现数据写入,写数据到redis

  3. 内存爆满:某次活动忘记设置过期时间,Redis内存告急

    • 解决方案:建立监控告警,设置内存淘汰策略
      # redis.conf配置
      maxmemory 2gb
      maxmemory-policy allkeys-lru

效果对比

上线Redis缓存后,我们的关键指标变化:

指标 优化前 优化后 提升幅度
平均响应时间 850ms 120ms 86%↓
数据库QPS 3200 600 81%↓
峰值承压能力 800TPS 4500TPS 462%↑
服务器负载 75% 35% 53%↓

总结建议

  1. 不是所有数据都适合缓存:低频访问的数据反而会增加复杂度
  2. 缓存策略要灵活:根据业务特点选择TTL、LRU等不同策略
  3. 监控不可少:实时关注缓存命中率、内存使用等关键指标
  4. 渐进式实施:可以先从最热的20%数据开始缓存

缓存不是银弹,而是整个系统优化中的一环,合理使用Redis,能让你的MySQL从"996"变成"朝九晚五",整个系统运行更加优雅从容。

发表评论