当前位置:首页 > 学习笔记 > 正文内容

Redis 缓存技术实战完全指南:从入门到精通

Redis 是当今最流行的内存数据库之一,它以高性能、丰富的数据结构和持久化能力著称。本文将带你从零开始掌握 Redis 的核心概念、数据类型、持久化机制、集群部署以及实战应用技巧,助你成为 Redis 技术专家。

一、核心概念

Redis(Remote Dictionary Server)是一个开源的内存键值数据库,由 Salvatore Sanfilippo 于 2009 年开发。与传统的关系型数据库不同,Redis 将所有数据存储在内存中,这使得它的读写速度极快,每秒可以处理超过 10 万次操作。

1.1 为什么选择 Redis?

在高并发场景下,传统数据库往往成为性能瓶颈。Redis 的出现解决了这个问题:

  • 极致性能:内存存储,读写延迟在微秒级别
  • 丰富数据类型:支持字符串、哈希、列表、集合、有序集合等
  • 数据持久化:RDB 和 AOF 两种持久化机制,保障数据安全
  • 原子操作:所有操作都是原子性的,无需担心并发问题
  • 支持集群:轻松实现水平扩展,支持海量数据存储

1.2 Redis 的典型应用场景

Redis 在实际项目中有着广泛的应用:

  • 缓存系统:减轻数据库压力,提升响应速度
  • 会话存储:分布式 Session 管理,支持集群部署
  • 排行榜:利用有序集合实现实时排名
  • 消息队列:轻量级消息中间件,支持发布订阅
  • 计数器:文章点赞、访问统计等高频计数场景
  • 分布式锁:基于 SETNX 实现跨进程互斥

二、核心内容

2.1 数据类型详解

Redis 支持 5 种基础数据类型,每种都有其独特的应用场景。

String(字符串)

字符串是 Redis 最基础的数据类型,可以存储字符串、整数或浮点数。一个键最大能存储 512MB 的数据。

# 设置键值
SET user:1:name "张三"
SET user:1:age 25

# 获取值
GET user:1:name  # 返回 "张三"

# 原子递增
INCR user:1:age  # 返回 26
INCRBY user:1:age 5  # 返回 31

# 设置过期时间(秒)
SETEX session:token 3600 "abc123"

# 仅当键不存在时设置
SETNX lock:order:123 "locked"  # 用于分布式锁

Hash(哈希)

哈希是一个键值对集合,特别适合存储对象。相比于将对象 JSON 序列化为字符串,哈希可以只更新单个字段。

# 存储用户对象
HMSET user:1 name "张三" age 25 email "zhangsan@example.com"

# 获取单个字段
HGET user:1 name  # 返回 "张三"

# 获取所有字段
HGETALL user:1

# 只获取字段名或字段值
HKEYS user:1
HVALS user:1

# 判断字段是否存在
HEXISTS user:1 name  # 返回 1(存在)

# 删除字段
HDEL user:1 email

List(列表)

列表是一个双向链表,支持从两端插入和弹出。常用于消息队列和最新列表。

# 从左侧插入(最新消息在前)
LPUSH news:latest "新闻1"
LPUSH news:latest "新闻2"

# 获取列表范围
LRANGE news:latest 0 9  # 获取最新10条

# 从右侧弹出
RPOP news:latest

# 阻塞弹出(用于消息队列)
BRPOP queue:tasks 30  # 30秒超时

Set(集合)

集合是无序的不重复元素集合,支持交集、并集、差集等操作。

# 添加元素
SADD tags:article:1 "Redis" "缓存" "数据库"

# 获取所有元素
SMEMBERS tags:article:1

# 判断元素是否存在
SISMEMBER tags:article:1 "Redis"  # 返回 1

# 集合运算
SADD tags:article:2 "MySQL" "数据库"
SINTER tags:article:1 tags:article:2  # 交集:数据库
SUNION tags:article:1 tags:article:2  # 并集
SDIFF tags:article:1 tags:article:2   # 差集

ZSet(有序集合)

有序集合在集合基础上增加了分数(score)属性,元素按分数排序。非常适合排行榜场景。

# 添加成员和分数
ZADD leaderboard 100 "player1"
ZADD leaderboard 200 "player2"
ZADD leaderboard 150 "player3"

# 获取排名(升序,从0开始)
ZRANK leaderboard "player1"  # 返回 0

# 获取排名(降序)
ZREVRANK leaderboard "player2"  # 返回 0(第一名)

# 获取排行榜 Top10
ZREVRANGE leaderboard 0 9 WITHSCORES

# 增加分数
ZINCRBY leaderboard 50 "player1"

# 按分数范围获取
ZRANGEBYSCORE leaderboard 100 200 WITHSCORES

2.2 持久化机制

Redis 提供两种持久化方式,可以单独使用或组合使用。

RDB(快照)

RDB 将某个时间点的数据快照保存到磁盘的二进制文件中。优点是文件紧凑、恢复速度快,缺点是可能丢失最后一次快照后的数据。

# redis.conf 配置
save 900 1      # 900秒内至少1次修改则快照
save 300 10     # 300秒内至少10次修改
save 60 10000   # 60秒内至少10000次修改

# 手动触发快照
SAVE    # 阻塞主进程
BGSAVE  # 后台执行

AOF(追加文件)

AOF 记录所有写操作命令,恢复时重新执行这些命令。优点是数据安全性高,缺点是文件较大、恢复较慢。

# redis.conf 配置
appendonly yes
appendfilename "appendonly.aof"

# 同步策略
appendfsync always     # 每次写入都同步,最安全但最慢
appendfsync everysec   # 每秒同步,推荐
appendfsync no         # 由操作系统决定,最快但可能丢数据

# AOF 重写(压缩文件)
BGREWRITEAOF

混合持久化(Redis 4.0+)

结合 RDB 和 AOF 的优点,重写时先写入 RDB 内容,再追加增量 AOF 数据。

# redis.conf 配置
aof-use-rdb-preamble yes

2.3 过期策略与内存淘汰

Redis 的内存管理是保证高性能的关键。

过期策略

Redis 采用惰性删除 + 定期删除的组合策略:

  • 惰性删除:访问键时检查是否过期,过期则删除
  • 定期删除:每秒执行 10 次过期检查,随机抽取部分键

内存淘汰策略

当内存达到上限时,Redis 提供多种淘汰策略:

# redis.conf 配置
maxmemory 2gb

# 淘汰策略
maxmemory-policy allkeys-lru  # 推荐用于缓存场景

# 可选策略:
# noeviction       - 不淘汰,写入报错
# allkeys-lru      - 从所有键中淘汰最近最少使用的
# volatile-lru     - 从设置了过期时间的键中淘汰 LRU
# allkeys-random   - 随机淘汰
# volatile-ttl     - 淘汰即将过期的键
# allkeys-lfu      - 淘汰访问频率最低的键(Redis 4.0+)

2.4 Redis 集群

当单机 Redis 无法满足需求时,可以通过集群实现水平扩展。

主从复制

主从复制实现读写分离,提升读性能并提供数据备份。

# 在从节点配置
REPLICAOF 192.168.1.100 6379

# 查看复制状态
INFO replication

哨兵模式

哨兵监控主节点健康状态,自动进行故障转移。

# sentinel.conf 配置
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 180000

Redis Cluster

Redis Cluster 提供分布式数据存储,自动分片和故障转移。

# 创建集群(至少6个节点)
redis-cli --cluster create \\
  192.168.1.101:6379 192.168.1.102:6379 192.168.1.103:6379 \\
  192.168.1.104:6379 192.168.1.105:6379 192.168.1.106:6379 \\
  --cluster-replicas 1

# 查看集群状态
CLUSTER INFO
CLUSTER NODES

三、实战案例

3.1 缓存穿透解决方案

缓存穿透指查询不存在的数据,请求直接穿透到数据库。

import redis
import json

r = redis.Redis(host="localhost", port=6379, db=0)

def get_user(user_id):
    """获取用户信息,防止缓存穿透"""
    cache_key = f"user:{user_id}"
    
    # 1. 查询缓存
    cached = r.get(cache_key)
    if cached:
        data = json.loads(cached)
        # 空值标记
        if data.get("__null__"):
            return None
        return data
    
    # 2. 查询数据库
    user = db.query_user(user_id)
    
    if user:
        # 3. 缓存真实数据
        r.setex(cache_key, 3600, json.dumps(user))
    else:
        # 4. 缓存空值,防止穿透
        r.setex(cache_key, 60, json.dumps({"__null__": True}))
    
    return user

3.2 分布式锁实现

分布式锁用于跨进程、跨服务器的互斥控制。

import redis
import uuid
import time

r = redis.Redis(host="localhost", port=6379, db=0)

class DistributedLock:
    """分布式锁实现"""
    
    def __init__(self, lock_name, expire=30):
        self.lock_key = f"lock:{lock_name}"
        self.expire = expire
        self.identifier = str(uuid.uuid4())
    
    def acquire(self, timeout=10):
        """获取锁"""
        end_time = time.time() + timeout
        while time.time() < end_time:
            # SET NX EX 原子操作
            if r.set(self.lock_key, self.identifier, nx=True, ex=self.expire):
                return True
            time.sleep(0.001)
        return False
    
    def release(self):
        """释放锁(Lua 脚本保证原子性)"""
        script = """
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
        """
        r.eval(script, 1, self.lock_key, self.identifier)

# 使用示例
lock = DistributedLock("order:pay:12345")
if lock.acquire():
    try:
        # 执行业务逻辑
        process_order()
    finally:
        lock.release()

3.3 限流器实现

使用 Redis 实现令牌桶或滑动窗口限流。

import redis
import time

r = redis.Redis(host="localhost", port=6379, db=0)

def rate_limit(key, limit=100, period=60):
    """滑动窗口限流器
    
    Args:
        key: 限流键名
        limit: 时间窗口内最大请求数
        period: 时间窗口(秒)
    
    Returns:
        bool: 是否允许请求
    """
    now = time.time()
    window_start = now - period
    
    # 使用事务保证原子性
    pipe = r.pipeline()
    pipe.zremrangebyscore(key, 0, window_start)
    pipe.zadd(key, {str(now): now})
    pipe.zcard(key)
    pipe.expire(key, period)
    
    results = pipe.execute()
    count = results[2]
    
    return count <= limit

# 使用示例:每分钟最多100次请求
if rate_limit("api:user:12345", limit=100, period=60):
    # 处理请求
    handle_request()
else:
    # 返回限流错误
    return {"error": "Rate limit exceeded"}, 429

3.4 缓存预热与更新策略

import redis
import json
import threading

r = redis.Redis(host="localhost", port=6379, db=0)

class CacheManager:
    """缓存管理器"""
    
    @staticmethod
    def warm_up():
        """缓存预热:启动时加载热点数据"""
        hot_users = db.get_hot_users(limit=1000)
        pipe = r.pipeline()
        for user in hot_users:
            pipe.setex(
                f"user:{user.id}", 
                3600, 
                json.dumps(user.to_dict())
            )
        pipe.execute()
        print(f"预热线 {len(hot_users)} 条热点数据")
    
    @staticmethod
    def cache_aside_read(key, db_query, expire=3600):
        """Cache-Aside 读策略"""
        cached = r.get(key)
        if cached:
            return json.loads(cached)
        
        data = db_query()
        if data:
            r.setex(key, expire, json.dumps(data))
        return data
    
    @staticmethod
    def cache_aside_write(key, db_update, data):
        """Cache-Aside 写策略"""
        # 先更新数据库
        db_update(data)
        # 再删除缓存
        r.delete(key)

总结

Redis 作为高性能内存数据库,在现代应用架构中扮演着不可替代的角色。本文从核心概念、数据类型、持久化机制、集群部署到实战案例,全面介绍了 Redis 的关键技术点。

核心要点回顾:

  1. 掌握 5 种基础数据类型的特点和使用场景
  2. 理解 RDB 和 AOF 持久化的区别,合理配置混合持久化
  3. 设置合适的内存淘汰策略,避免内存溢出
  4. 根据业务规模选择主从复制、哨兵或 Cluster 部署方案
  5. 在实践中解决缓存穿透、分布式锁、限流等典型问题

Redis 的学习是一个循序渐进的过程。建议先在本地环境实践各种数据操作,然后深入理解持久化和集群原理,最后在实际项目中应用。只有通过大量的实践,才能真正掌握 Redis 这门技术,让它为你的系统带来质的飞跃。

本文链接:https://www.kkkliao.cn/?id=914 转载需授权!

分享到:

版权声明:本文由廖万里的博客发布,如需转载请注明出处。


发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。