前言 Redis 是目前最流行的内存数据库。几乎所有后端系统里都能看到它的身影——做缓存、做分布式锁、做排行榜、做消息队列。它把”快”做到了极致(单机 10 万 QPS 是常态),同时又提供了丰富的数据结构。
这篇文章覆盖 Redis 最核心的数据结构和实际开发中最常用的几个场景。
一、Redis 是什么 简单说,Redis 是一个基于内存的键值存储系统 。和 MySQL 的区别在于:
MySQL
Redis
存储位置
硬盘
内存
速度
毫秒级
微秒级
数据结构
表(行列)
String、Hash、List、Set、Sorted Set
持久化
天然持久
可选(RDB 快照 / AOF 日志)
典型用途
持久存储业务数据
缓存、临时数据、实时计算
内存意味着快,但也意味着数据在重启后会丢失(除非开了持久化)。所以 Redis 通常不用于存”丢了就没了”的核心业务数据,而是作为 MySQL 前面的缓存层或临时数据存储。
二、安装 1 2 3 4 5 6 7 8 9 10 brew install redis brew services start redis sudo apt install redis-serversudo systemctl start redisdocker run -d --name redis -p 6379:6379 redis:7-alpine
连接验证:
1 2 3 redis-cli 127.0.0.1:6379> PING PONG
三、五大数据结构 3.1 String(字符串) 最基础的类型,也是缓存最常用的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 SET user:1:name "July" GET user:1:name SETEX verification_code:13800138000 300 "123456" SET article:42:views 100 INCR article:42:views INCRBY article:42:views 10 SET lock:order:20260530001 "holder" NX EX 30
典型场景:
1 2 3 4 缓存 JSON → SET user:1 '{"name":"July","age":25}' EX 3600 计数器 → INCR page:home:visits 分布式锁 → SET lock:resource "owner" NX EX 30 限流 → INCR rate:api:user:1 配合 EXPIRE
3.2 Hash(哈希表) 存对象的首选,比把 JSON 序列化成 String 更省空间、支持部分字段读写。
1 2 3 4 5 6 7 HSET user:1 name "July" age 25 city "深圳" HGET user:1 name HGETALL user:1 HINCRBY user:1 age 1 HSET user:1 email "new@example.com"
3.3 List(列表) 有序、可重复的双向链表,天然适合做队列和栈。
1 2 3 4 5 6 7 8 9 10 11 12 LPUSH queue:emails "email1" "email2" "email3" RPOP queue:emails LLEN queue:emails BRPOP queue:emails 5 LPUSH login_logs "2026-05-30 14:00:01 user July登录" LTRIM login_logs 0 9 LRANGE login_logs 0 -1
3.4 Set(集合) 无序、不重复,擅长交集并集差集运算。
1 2 3 4 5 6 7 8 9 10 11 SADD article:1:tags "Redis" "数据库" "缓存" SADD article:2:tags "Redis" "分布式锁" SISMEMBER article:1:tags "Redis" SINTER article:1:tags article:2:tags SADD user:1:followers "user2" "user3" "user4" SADD user:2:followers "user3" "user4" "user5" SINTER user:1:followers user:2:followers
3.5 Sorted Set(有序集合) 每个成员带一个分数,自动按分数排序。最常用的场景是排行榜。
1 2 3 4 5 6 7 8 9 10 11 12 13 ZADD leaderboard 100 "player1" 200 "player2" 150 "player3" ZINCRBY leaderboard 50 "player1" ZREVRANGE leaderboard 0 9 WITHSCORES ZREVRANK leaderboard "player1" ZRANGEBYSCORE leaderboard 150 300 WITHSCORES
四、六大典型应用场景 场景 1:缓存(最常见) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import redisimport jsonr = redis.Redis(host='localhost' , port=6379 , decode_responses=True ) def get_user (user_id ): cache_key = f"user:{user_id} " cached = r.get(cache_key) if cached: return json.loads(cached) user = db.query(f"SELECT * FROM users WHERE id = {user_id} " ) if user: r.setex(cache_key, 3600 , json.dumps(user)) return user def update_user (user_id, data ): db.execute(f"UPDATE users SET ... WHERE id = {user_id} " ) r.delete(f"user:{user_id} " )
缓存穿透、击穿、雪崩:
问题
场景
解决方案
缓存穿透
查一个不存在的数据,每次都穿透到 DB
布隆过滤器,或把空结果也缓存(短过期时间)
缓存击穿
热点 key 过期瞬间大量请求打到 DB
互斥锁,只让一个请求去查 DB 并重建缓存
缓存雪崩
大量 key 同时过期
过期时间加随机偏移 EX 3600 + random(0,600)
场景 2:分布式锁 多台服务器竞争同一个资源时,需要一把”大家都能看到的锁”。Redis 的 SET NX EX 天然适合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import timedef acquire_lock (lock_name, holder_id, expire_seconds=30 ): """获取分布式锁""" return r.set (lock_name, holder_id, nx=True , ex=expire_seconds) def release_lock (lock_name, holder_id ): """释放锁(用 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 , lock_name, holder_id) lock_key = "lock:order:20260530001" holder = f"server-3-{time.time()} " if acquire_lock(lock_key, holder, 30 ): try : process_order() finally : release_lock(lock_key, holder)
释放锁必须用 Lua 脚本的原因是:不能先 GET 再 DEL,这两步之间可能被别的线程插入——必须保证”判断 + 删除”是原子操作。
场景 3:排行榜 Sorted Set 是排行榜的完美数据结构,单机 Redis 能轻松支撑百万用户的实时排名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def update_score (user_id, score ): r.zadd("game:leaderboard" , {user_id: score}) def get_top100 (): return r.zrevrange("game:leaderboard" , 0 , 99 , withscores=True ) def get_user_rank (user_id ): rank = r.zrevrank("game:leaderboard" , user_id) nearby = r.zrevrange("game:leaderboard" , max (0 , rank-5 ), rank+5 , withscores=True ) return rank, nearby r.delete("game:leaderboard" )
场景 4:消息队列 List 的阻塞读可以当简易消息队列用。简单场景(无需消息确认、无需复杂路由)完全够用。
1 2 3 4 5 6 7 8 9 10 11 12 def enqueue_task (task_data ): r.lpush("task:queue" , json.dumps(task_data)) def worker (): while True : result = r.brpop("task:queue" , timeout=5 ) if result: _, task_json = result task = json.loads(task_json) process_task(task)
复杂场景建议用 RabbitMQ 或 Kafka,但 Redis 队列胜在零配置。
场景 5:Session 共享 多台 Web 服务器共享用户登录状态。
1 2 3 4 5 6 7 8 from flask import Flask, sessionimport redisfrom datetime import timedeltaapp = Flask(__name__) app.config['SESSION_TYPE' ] = 'redis' app.config['SESSION_REDIS' ] = redis.Redis(host='redis-server' , port=6379 ) app.config['PERMANENT_SESSION_LIFETIME' ] = timedelta(hours=2 )
这样无论用户请求被分发到哪台服务器,都能读到同一个 Session。
场景 6:限流 防止某个用户或 IP 短时间内大量请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def rate_limit (user_id, max_requests=10 , window_seconds=60 ): key = f"rate:{user_id} " current = r.get(key) if current is None : r.setex(key, window_seconds, 1 ) return True current = int (current) if current >= max_requests: return False r.incr(key) return True if not rate_limit(user_id, 10 , 60 ): return "请求过于频繁,请稍后再试" , 429
五、持久化策略 Redis 提供两种持久化方式,可以只开一种,也可以同时开。
方式
原理
优点
缺点
RDB
定时快照,把整个内存写入磁盘
恢复快、文件小
可能丢失最后一次快照后的数据
AOF
记录每一条写命令
数据更安全
文件大、恢复慢
配置(redis.conf):
1 2 3 4 5 6 7 8 save 900 1 save 300 10 save 60 10000 appendonly yes appendfsync everysec
实际选择 :对数据要求不高的缓存场景,不开持久化也没问题。需要持久化的场景,推荐同时开 RDB + AOF,用 RDB 做备份(恢复快),用 AOF 保证数据安全。
六、常见问题 Q:Redis 为什么这么快?
纯内存操作,没有磁盘 IO
单线程模型,没有锁切换开销
IO 多路复用,一个线程处理大量连接
数据结构设计简单高效
Q:Redis 单线程为什么还这么快? 因为它的瓶颈不在 CPU,而在网络和内存带宽。单线程避免了上下文切换和锁竞争,反而效率更高。Redis 6.0 之后引入了多线程处理网络 IO,但命令执行还是单线程的。
Q:数据量超过内存怎么办? Redis 不是用来存海量数据的。如果数据量大,用 Redis Cluster(分片)或者冷热分离(热数据在 Redis,全量在 MySQL)。
以上覆盖了 Redis 日常开发最常用的内容。实际项目中先从缓存用起,分布式锁、排行榜这些遇到具体场景再深入研究。