Redis数据类型与场景

Redis不仅是一个缓存,更是一个数据结构服务器。理解每种数据类型的特性和适用场景是充分发挥Redis能力的关键。

String

最基本的数据类型,适合缓存、计数器、分布式ID等场景:

SET user:1001:name "张三"
INCR article:123:views
SETEX session:abc 3600 "user_data"  # 带过期时间的设置

Hash

适合存储对象数据,可单独更新某个字段:

HSET user:1001 name "张三" age 28 city "北京"
HGET user:1001 name
HINCRBY user:1001 age 1

List

基于链表实现,适合消息队列和时间线场景:

LPUSH timeline:user:1001 "post:1"
LRANGE timeline:user:1001 0 9  # 获取最近10条

Set

无序集合,适合标签、去重和社交关系:

SADD article:1:tags "技术" "Redis" "缓存"
SINTER article:1:tags article:2:tags  # 共同标签

Sorted Set

有序集合,适合排行榜、延迟队列等场景:

ZADD leaderboard 1000 "user1"
ZINCRBY leaderboard 10 "user1"
ZREVRANGE leaderboard 0 9 WITHSCORES  # Top 10

过期与淘汰策略

过期策略

Redis使用两种方式清理过期key:

  • 惰性删除:访问key时检查是否过期
  • 定期删除:每100ms随机检查一批key

内存淘汰策略

当内存达到上限时,根据配置的策略淘汰数据:

  • noeviction:不淘汰,写入返回错误(默认)
  • allkeys-lru:淘汰最近最少使用的key
  • volatile-lru:仅在设置了过期时间的key中淘汰最少使用
  • allkeys-lfu:淘汰最不频繁使用的key(Redis 4.0+)
  • volatile-ttl:淘汰即将过期的key

建议生产环境使用allkeys-lru策略,配合合理的过期时间设置。

分布式锁

基本实现

使用SET命令的NX和EX参数实现:

import redis

r = redis.Redis()

def acquire_lock(lock_name, acquire_timeout=10, lock_timeout=30):
    identifier = str(uuid.uuid4())
    end = time.time() + acquire_timeout
    while time.time() < end:
        if r.set(f"lock:{lock_name}", identifier, nx=True, ex=lock_timeout):
            return identifier
        time.sleep(0.01)
    return None

def release_lock(lock_name, identifier):
    # 使用Lua脚本保证原子性
    script = """
    if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
    else
        return 0
    end
    """
    return r.eval(script, 1, f"lock:{lock_name}", identifier)

Redlock算法

对于需要更高安全性的场景,可以使用Redlock算法在多个Redis节点上获取锁。

注意事项

  • 锁的超时时间需要根据业务执行时间合理设置
  • 使用唯一标识避免误删其他客户端持有的锁
  • 释放锁时使用Lua脚本保证检查和删除的原子性
  • 考虑可重入锁的需求

缓存常见问题

缓存穿透

查询不存在的数据导致请求直接打到数据库。

解决方案:缓存空值或使用布隆过滤器。

缓存击穿

热点key过期导致高并发请求直击数据库。

解决方案: 使用互斥锁或设置热点key永不过期。

缓存雪崩

大量key同时过期导致数据库压力激增。

解决方案:过期时间增加随机值,避免批量过期。

Redis作为高性能缓存和数据结构服务器,掌握其核心特性和常见问题的处理方案,是后端开发的必备技能。