一、Redis概述
1.1 什么是Redis
Redis(Remote Dictionary Server)是一个开源的内存数据结构存储系统,通常被称为内存数据库或键值存储系统。它由意大利开发者Salvatore Sanfilippo(又名antirez)于2009年创建,现在已成为最流行的NoSQL数据库之一。Redis将所有数据存储在内存中,因此具有极高的读写性能,同时支持将数据持久化到磁盘,确保数据安全。
核心特点:
1. 高性能:基于内存操作,读写速度可达每秒10万+次,延迟通常在毫秒甚至微秒级别。
2. 丰富的数据结构:支持字符串、列表、集合、哈希、有序集合、位图、HyperLogLog、地理空间索引等多种数据结构。
3. 持久化机制:提供RDB快照和AOF日志两种持久化方式,兼顾性能和安全性。
4. 发布订阅:内置消息通信模式,支持生产者-消费者模型。
5. 事务支持:通过MULTI/EXEC提供原子性操作。
6. 高可用与分布式:支持主从复制、Sentinel哨兵模式、Redis Cluster集群方案。
1.2 适用场景
Redis的用途非常广泛,在Web开发中常见的应用场景包括:
- 缓存:缓存数据库查询结果、页面片段或API响应,显著降低后端负载和响应时间。
- 会话存储:替代默认的文件或数据库会话存储,实现分布式Web应用的会话共享。
- 消息队列:利用列表结构实现简单的任务队列,配合阻塞操作实现异步处理。
- 排行榜:利用有序集合(ZSet)轻松实现实时排行榜功能。
- 计数器:使用INCR/DECR命令实现访问计数、点赞数、限流等功能。
- 分布式锁:基于SETNX命令实现跨进程的分布式互斥锁。
- 实时数据:地理位置查询、在线用户状态、实时分析等。
1.3 Redis vs Memcached
| 对比维度 |
Redis |
Memcached |
| 数据结构 |
丰富(字符串、列表、集合、哈希、ZSet等) |
仅支持字符串(键值对) |
| 持久化 |
支持RDB和AOF持久化 |
不支持持久化,重启数据丢失 |
| 主从复制 |
支持 |
不支持 |
| 事务支持 |
支持MULTI/EXEC事务 |
不支持 |
| 内存管理 |
灵活,支持多种淘汰策略 |
基于LRU淘汰 |
| 适用场景 |
复杂数据结构的缓存、持久化需求 |
简单的键值缓存 |
二、Redis安装与配置
2.1 安装方式
Redis官方推荐在Linux系统上运行,但Windows/macOS也提供了多种安装方式。
Linux(Ubuntu/Debian):
# 安装Redis服务器
sudo apt update
sudo apt install redis-server
# 启动服务
sudo systemctl start redis-server
# 设置开机自启
sudo systemctl enable redis-server
macOS(Homebrew):
# 安装
brew install redis
# 启动
brew services start redis
Windows:
# 方式一:WSL(推荐)
wsl --install
# 在WSL中按Linux方式安装
# 方式二:Docker
docker run --name redis -p 6379:6379 -d redis
# 方式三:官方MSI安装包
# 从 https://github.com/microsoftarchive/redis/releases 下载安装
2.2 启动与连接
Redis安装完成后,可以通过以下方式启动和验证:
# 启动Redis服务
redis-server
# 也可指定配置文件启动
redis-server /etc/redis/redis.conf
# 使用redis-cli连接
redis-cli -h 127.0.0.1 -p 6379
# 测试连接
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
2.3 配置文件(redis.conf)
Redis的配置文件位于 /etc/redis/redis.conf,常用配置项如下:
- bind:绑定IP地址,默认为127.0.0.1,生产环境需谨慎设置。
- port:服务端口号,默认为6379。
- requirepass:设置连接密码,生产环境必配。
- maxmemory:设置最大内存使用量,超出后按淘汰策略处理。
- maxmemory-policy:内存淘汰策略,如allkeys-lru、volatile-ttl等。
- save:RDB持久化触发条件,如
save 900 1 表示900秒内至少1次修改则快照。
- appendonly:是否开启AOF持久化,设置为yes。
2.4 Python连接redis-py
Python操作Redis最常用的库是 redis-py,使用前需要先安装:
# 安装redis-py
pip install redis
# 基础连接
import redis
# 创建Redis连接
r = redis.Redis(host='localhost', port=6379, db=0, password=None)
# 测试连接
r.ping() # True
# 设置和获取
r.set('key', 'value')
print(r.get('key')) # b'value'
三、Redis核心数据结构
3.1 String(字符串)
String是Redis最基础的数据结构,一个键对应一个值,值最大可存储512MB。可以存储字符串、数字、JSON序列化对象等。
# 常用命令
SET key value # 设置键值
GET key # 获取值
INCR key # 递增1
DECR key # 递减1
INCRBY key amount # 增加指定数值
DECRBY key amount # 减少指定数值
APPEND key value # 追加字符串
STRLEN key # 获取字符串长度
# 示例
127.0.0.1:6379> SET counter 100
OK
127.0.0.1:6379> INCR counter
(integer) 101
127.0.0.1:6379> GET counter
"101"
3.2 List(列表)
List是一个有序的字符串列表,底层基于双向链表实现。适合用作消息队列、最新消息列表等场景。插入和删除操作非常高效,但索引访问相对较慢。
# 常用命令
LPUSH key value # 从左侧插入
RPUSH key value # 从右侧插入
LPOP key # 从左侧弹出
RPOP key # 从右侧弹出
LRANGE key start stop # 获取范围内元素
LLEN key # 获取列表长度
LINDEX key index # 获取指定索引的元素
LTRIM key start stop # 截取列表片段
# 示例:消息队列
127.0.0.1:6379> LPUSH tasks task:1
(integer) 1
127.0.0.1:6379> LPUSH tasks task:2
(integer) 2
127.0.0.1:6379> RPOP tasks
"task:1"
3.3 Set(集合)
Set是无序的字符串集合,元素唯一不重复。基于哈希表实现,支持集合间的交、并、差运算。适合标签系统、好友关系、去重等场景。
# 常用命令
SADD key member # 添加元素
SREM key member # 移除元素
SMEMBERS key # 获取所有元素
SISMEMBER key member # 判断是否存在
SCARD key # 获取元素个数
SINTER key1 key2 # 交集
SUNION key1 key2 # 并集
SDIFF key1 key2 # 差集
# 示例:标签系统
127.0.0.1:6379> SADD article:1:tags python redis
(integer) 2
127.0.0.1:6379> SADD article:2:tags python flask
(integer) 2
127.0.0.1:6379> SINTER article:1:tags article:2:tags
1) "python"
3.4 Hash(哈希)
Hash是一个键值对集合(类似Python的字典),特别适合存储对象类型的数据。每个Hash可以存储多达2^32-1个字段,是存储结构化数据的理想选择。
# 常用命令
HSET key field value # 设置字段值
HGET key field # 获取字段值
HGETALL key # 获取所有字段和值
HDEL key field # 删除字段
HEXISTS key field # 判断字段是否存在
HKEYS key # 获取所有字段名
HVALS key # 获取所有字段值
HINCRBY key field num # 增加字段值
# 示例:存储用户信息
127.0.0.1:6379> HSET user:1001 name "张三" age 30 email "zhangsan@example.com"
OK
127.0.0.1:6379> HGET user:1001 name
"张三"
127.0.0.1:6379> HGETALL user:1001
1) "name"
2) "张三"
3) "age"
4) "30"
5) "email"
6) "zhangsan@example.com"
3.5 ZSet(有序集合)
ZSet是带有分值的集合,元素唯一但分值可重复。基于跳表(Skip List)实现,可以根据分值进行高效排序。适合排行榜、时间线、带有权重的优先级队列等。
# 常用命令
ZADD key score member # 添加元素(含分值)
ZRANGE key start stop # 按分值升序获取
ZREVRANGE key start stop # 按分值降序获取
ZRANGEBYSCORE key min max # 按分值范围获取
ZREM key member # 移除元素
ZCARD key # 获取元素个数
ZSCORE key member # 获取元素分值
ZRANK key member # 获取元素排名(升序)
ZREVRANK key member # 获取元素排名(降序)
# 示例:游戏排行榜
127.0.0.1:6379> ZADD leaderboard 800 "玩家A"
127.0.0.1:6379> ZADD leaderboard 950 "玩家B"
127.0.0.1:6379> ZADD leaderboard 880 "玩家C"
127.0.0.1:6379> ZREVRANGE leaderboard 0 2 WITHSCORES
1) "玩家B"
2) "950"
3) "玩家C"
4) "880"
5) "玩家A"
6) "800"
3.6 数据结构与应用场景对应表
| 数据结构 |
底层实现 |
典型应用场景 |
| String |
动态字符串(SDS) |
缓存、计数器、分布式锁、Session |
| List |
双向链表 / 压缩列表 |
消息队列、最新动态列表、时间线 |
| Set |
哈希表 / 整数集合 |
标签系统、共同好友、去重、随机抽取 |
| Hash |
压缩列表 / 哈希表 |
对象存储(用户信息、商品详情等) |
| ZSet |
跳表 + 哈希表 |
排行榜、延时队列、范围查询 |
四、Redis高级特性
4.1 过期时间
Redis可以为每个键设置过期时间,到期后自动删除。这是实现缓存功能的核心机制。
# 设置过期时间
SET key value
EXPIRE key seconds # 设置键的过期时间(秒)
TTL key # 查看剩余过期时间(秒)
PEXPIRE key ms # 设置毫秒级过期
PTTL key # 查看毫秒级剩余时间
EXPIREAT key timestamp # 设置到期的时间戳
# 在SET时直接设置过期
SET key value EX 3600 # 1小时后过期
SET key value PX 5000 # 5秒后过期
# 示例
127.0.0.1:6379> SET session:token "abc123" EX 7200
OK
127.0.0.1:6379> TTL session:token
(integer) 7199
4.2 持久化
Redis提供两种持久化机制,保障数据安全:
RDB(Redis Database)快照:
在指定的时间间隔内生成内存数据的全量快照文件(dump.rdb)。优点是文件紧凑、恢复速度快;缺点是两次快照之间的数据可能丢失。
AOF(Append Only File)日志:
记录每次写操作命令,以日志形式追加保存。优点是数据安全性高(可设置每秒同步);缺点是文件体积较大、恢复速度较慢。Redis 7.0以上支持AOF混合持久化,结合了RDB和AOF的优点。
# 配置示例(redis.conf)
# RDB配置
save 900 1 # 900秒内至少1次修改
save 300 10 # 300秒内至少10次修改
save 60 10000 # 60秒内至少10000次修改
# AOF配置
appendonly yes # 开启AOF
appendfsync everysec # 每秒同步一次
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
4.3 发布订阅
Redis的发布订阅(Pub/Sub)实现了消息的广播机制,生产者发布消息到频道,所有订阅该频道的消费者都能收到消息。
# 订阅者(Terminal 1)
SUBSCRIBE news:channel
# 发布者(Terminal 2)
PUBLISH news:channel "今日头条新闻发布"
# 订阅者收到消息
1) "message"
2) "news:channel"
3) "今日头条新闻发布"
4.4 事务
Redis事务通过MULTI、EXEC、DISCARD、WATCH等命令实现,将多个命令打包成一个步骤执行,保证原子性(但不支持回滚)。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET account:1001 balance 500
QUEUED
127.0.0.1:6379> INCRBY account:1001 balance 100
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (integer) 600
4.5 Pipeline(管道)
Pipeline允许客户端一次性发送多个命令,减少网络往返次数(RTT),大幅提升批量操作性能。在需要批量读写数据的场景中特别有用。
# Redis-CLI管道示例
(printf "SET key1 value1\r\n"; printf "GET key1\r\n") | redis-cli --pipe
# Python中使用Pipeline
pipe = r.pipeline()
pipe.set('key1', 'value1')
pipe.set('key2', 'value2')
pipe.get('key1')
pipe.get('key2')
results = pipe.execute()
# results = [True, True, b'value1', b'value2']
五、Python操作Redis
5.1 建立连接
import redis
# 基础连接
r = redis.Redis(
host='localhost',
port=6379,
db=0,
password='your_password',
decode_responses=True # 自动解码为字符串
)
# 连接池配置(推荐用于生产环境)
pool = redis.ConnectionPool(
host='localhost',
port=6379,
db=0,
max_connections=10,
decode_responses=True
)
r = redis.Redis(connection_pool=pool)
5.2 数据结构操作
# 字符串缓存
r.set('cache:page:home', '...', ex=300)
home_html = r.get('cache:page:home')
# 计数器
r.incr('page:visits')
r.incrby('user:1001:score', 10)
# 列表(消息队列)
r.lpush('queue:emails', 'user1@example.com')
r.lpush('queue:emails', 'user2@example.com')
email = r.rpop('queue:emails')
# 集合运算
r.sadd('tag:python', 'article:1', 'article:2')
r.sadd('tag:redis', 'article:1', 'article:3')
common = r.sinter('tag:python', 'tag:redis')
# common = {'article:1'}
# 哈希存储
r.hset('user:1001', mapping={
'name': '张三',
'age': 30,
'email': 'zhangsan@example.com'
})
user = r.hgetall('user:1001')
# user = {'name': '张三', 'age': '30', 'email': 'zhangsan@example.com'}
# 有序集合排行榜
r.zadd('leaderboard', {'player_A': 1000, 'player_B': 950, 'player_C': 880})
top3 = r.zrevrange('leaderboard', 0, 2, withscores=True)
5.3 管道批量操作
# 批量写入,减少网络开销
pipe = r.pipeline(transaction=True)
for i in range(1000):
pipe.set(f'batch:key:{i}', f'value_{i}')
pipe.execute()
# 批量读取
pipe = r.pipeline()
for i in range(100):
pipe.get(f'batch:key:{i}')
results = pipe.execute()
六、Redis在Web中的典型应用
6.1 页面缓存
缓存数据库查询结果或渲染好的HTML片段,是Redis最经典的应用场景。当请求到来时,先检查缓存是否存在,存在则直接返回,不存在则查询数据库并写入缓存。
import json
import redis
r = redis.Redis(decode_responses=True)
def get_article(article_id):
cache_key = f'article:{article_id}'
# 尝试从缓存获取
cached = r.get(cache_key)
if cached:
return json.loads(cached)
# 模拟数据库查询
article = query_db(f"SELECT * FROM articles WHERE id={article_id}")
# 写入缓存,有效期1小时
r.setex(cache_key, 3600, json.dumps(article))
return article
6.2 Session存储
在分布式Web应用中,使用Redis存储会话数据可以实现多服务器共享会话。相比默认的文件或数据库Session存储,Redis方案具有高性能、高可用的优势。
# Flask中使用Redis存储Session
from flask import Flask, session
from flask_session import Session
import redis
app = Flask(__name__)
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.from_url('redis://localhost:6379')
app.config['SESSION_PERMANENT'] = False
Session(app)
@app.route('/login')
def login():
session['user_id'] = 1001
session['username'] = '张三'
return '登录成功'
@app.route('/profile')
def profile():
user_id = session.get('user_id')
username = session.get('username')
return f'用户ID: {user_id}, 用户名: {username}'
Session存储对比:
1. 文件Session:默认存储方式,不适用于多服务器部署。
2. 数据库Session:性能较差,每次请求都需要读写数据库。
3. Redis Session:内存存储速度快,支持过期自动清理,天然适合分布式架构。
6.3 计数器
Redis的INCR/DECR命令天然适合实现各种计数器场景,原子性操作保证了在高并发环境下的数据准确性。
# 文章访问计数
def increment_views(article_id):
key = f'views:article:{article_id}'
return r.incr(key)
# 日活用户统计(使用BitMap)
def mark_user_active(user_id, date_str):
key = f'dau:{date_str}'
r.setbit(key, user_id, 1)
def count_dau(date_str):
key = f'dau:{date_str}'
return r.bitcount(key)
6.4 消息队列
利用Redis的List结构结合BRPOP阻塞弹出操作,可以构建一个轻量级的消息队列系统。适合任务异步处理、延迟执行等场景。
# 生产者:发送邮件任务
def send_email_task(to_addr, subject, body):
task = json.dumps({
'to': to_addr,
'subject': subject,
'body': body
})
r.lpush('queue:emails', task)
# 消费者:后台处理任务
def process_email_queue():
while True:
# 阻塞等待任务,超时时间0表示永远等待
task_data = r.brpop('queue:emails', timeout=0)
if task_data:
_, task_json = task_data
task = json.loads(task_json)
# 执行发送邮件
send_email(task['to'], task['subject'], task['body'])
6.5 排行榜
有序集合ZSet是实现排行榜的完美数据结构,可以按分值排序,支持实时更新和范围查询。
# 更新玩家分数
def update_score(player_id, score):
r.zadd('game:leaderboard', {player_id: score})
# 获取前十名
def get_top10():
return r.zrevrange('game:leaderboard', 0, 9, withscores=True)
# 获取玩家排名
def get_rank(player_id):
rank = r.zrevrank('game:leaderboard', player_id)
return rank + 1 if rank is not None else None
# 获取某个分数范围内的玩家
def get_players_by_score(min_score, max_score):
return r.zrangebyscore('game:leaderboard', min_score, max_score)
6.6 分布式锁
在分布式系统中,多个进程可能需要互斥访问共享资源。Redis通过SETNX命令结合Lua脚本可以实现可靠的分布式锁。
import time
import uuid
def acquire_lock(lock_name, expire=10):
"""获取分布式锁"""
lock_key = f'lock:{lock_name}'
lock_value = str(uuid.uuid4()) # 唯一标识,防止误删
# SET NX + EX 原子操作
acquired = r.set(lock_key, lock_value, nx=True, ex=expire)
return lock_value if acquired else None
def release_lock(lock_name, lock_value):
"""释放分布式锁(Lua脚本保证原子性)"""
lock_key = f'lock:{lock_name}'
# Lua脚本:只有值匹配时才删除
lua_script = """
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
"""
return r.eval(lua_script, 1, lock_key, lock_value)
# 使用示例
lock_value = acquire_lock('order:1001', expire=10)
if lock_value:
try:
# 处理订单
process_order(1001)
finally:
release_lock('order:1001', lock_value)
else:
print('获取锁失败,订单正在被其他进程处理')
6.7 接口限流(滑动窗口)
通过Redis实现基于滑动窗口的接口限流,防止恶意请求或突发流量冲垮后端服务。
import time
def is_rate_limited(user_id, action, max_requests=60, window=60):
"""滑动窗口限流检查"""
key = f'ratelimit:{user_id}:{action}'
now = time.time()
window_start = now - window
# 移除窗口外的记录
r.zremrangebyscore(key, 0, window_start)
# 获取当前窗口内请求数
current = r.zcard(key)
if current >= max_requests:
return True # 限流
# 记录本次请求
r.zadd(key, {str(now): now})
r.expire(key, window + 1)
return False # 通过
# 使用示例
def api_view(request):
user_id = request.user.id
if is_rate_limited(user_id, 'api:search', max_requests=30, window=60):
return {'error': '请求过于频繁,请稍后再试'}, 429
return search_results(request.query)
七、Redis最佳实践
7.1 键命名规范
良好的键命名规范有助于代码的可读性和维护性。推荐使用分层命名法:
# 推荐格式:app:module:id:field
user:1001:profile # 用户信息
article:42:content # 文章内容
cache:homepage # 首页缓存
order:20260505:count # 订单计数
lock:inventory:product_5 # 分布式锁
# 禁止使用的命名方式
a # 含义不明确
user001 # 分隔不规范
cache_data_123456_test # 层级混乱
/key/user/1 # 不要使用斜杠作为分隔符
7.2 避免大Key
大Key(Big Key)是指包含大量元素或大体积数据的键,会影响Redis的性能和稳定性。单个Key的建议大小不要超过10KB。
- 大Key的危害:阻塞其他命令、内存不均、备份恢复缓慢。
- 如何发现:使用
redis-cli --bigkeys 扫描,或使用 MEMORY USAGE key 命令。
- 优化方案:对大List/Hash/ZSet进行拆分,使用分段存储;对大String考虑压缩或迁移到其他存储。
7.3 内存使用监控
# 查看内存使用情况
127.0.0.1:6379> INFO memory
# Memory
used_memory: 8388608 # 已使用内存(字节)
used_memory_human: 8.00M # 人类可读格式
maxmemory: 1073741824 # 最大内存限制
maxmemory_human: 1024.00M
mem_fragmentation_ratio: 1.02 # 内存碎片率
# 内存淘汰策略
# maxmemory-policy 可选值:
# noeviction - 不淘汰,写入时报错
# allkeys-lru - 淘汰最近最少使用的键
# volatile-lru - 淘汰设置了过期时间的最近最少使用键
# allkeys-random - 随机淘汰任意键
# volatile-ttl - 淘汰即将过期的键
7.4 缓存穿透、击穿、雪崩的解决方案
| 问题 |
描述 |
解决方案 |
| 缓存穿透 |
查询不存在的数据,缓存和数据库中都没有,请求直接打到数据库 |
1. 布隆过滤器预过滤 2. 缓存空值(短期过期) 3. 参数校验拦截非法请求 |
| 缓存击穿 |
热点Key过期瞬间,大量并发请求直接打到数据库 |
1. 互斥锁(分布式锁) 2. 热点数据永不过期 3. 提前预热+后台刷新 |
| 缓存雪崩 |
大量Key同时过期或Redis宕机,请求全部打到数据库 |
1. 过期时间加随机值分散 2. Redis高可用(主从+哨兵) 3. 服务降级和限流 4. 多级缓存(本地缓存+Redis) |
核心要点总结:
1. Redis本质上是一个内存数据库,数据读写性能极高,是解决Web应用性能瓶颈的关键工具。
2. 选择合适的数据结构至关重要——String适合简单缓存,Hash适合对象,List适合队列,Set适合集合运算,ZSet适合排行榜。
3. 生产环境务必配置密码认证(requirepass)、合理的最大内存限制(maxmemory)和持久化策略。
4. 使用连接池管理Python的Redis连接,避免频繁创建和销毁连接的开销。
5. 分布式锁使用Lua脚本确保释放锁的原子性,避免误删其他进程持有的锁。
6. 缓存穿透、击穿、雪崩是Redis缓存场景的三大经典问题,需要根据实际场景组合使用多种防护策略。
7. 键命名规范是良好的工程实践,推荐使用 app:module:id:field 分层格式。
8. Pipeline批量操作可显著减少网络开销,在需要大量读写操作时务必使用。