Redis综合应用
本课程将深入讲解实战项目的核心概念和实践应用。主要内容包括:缓存系统、分布式锁、限流器、排行榜。
Redis的实战项目是非常重要的功能模块,在实际开发中有广泛的应用。通过本课程的学习,你将全面掌握实战项目的使用技巧。
# 示例:用户信息缓存
# 1. 尝试从缓存获取用户信息
GET user:1001
# 2. 如果缓存不存在,从数据库获取,然后存入缓存
# 伪代码
user = db.query("SELECT * FROM users WHERE id = ?", 1001)
if user:
redis.setex("user:1001", 3600, json.dumps(user))
# 3. 返回用户信息
return user
# 实际应用:使用哈希存储用户信息
HSET user:1001 name "zhangsan"
HSET user:1001 age "25"
HSET user:1001 email "zhangsan@example.com"
EXPIRE user:1001 3600
# 获取用户信息
HGETALL user:1001
# 方案1:布隆过滤器
# 示例:使用Redis实现简单的布隆过滤器
# 添加数据到布隆过滤器
BF.ADD user_bloom 1001
BF.ADD user_bloom 1002
# 检查数据是否存在
BF.EXISTS user_bloom 1001 # 存在
BF.EXISTS user_bloom 9999 # 不存在,直接返回
# 方案2:缓存空值
# 伪代码
user = redis.get("user:" + id)
if user:
return user
else:
user = db.query("SELECT * FROM users WHERE id = ?", id)
if user:
redis.setex("user:" + id, 3600, json.dumps(user))
else:
# 缓存空值,设置较短的过期时间
redis.setex("user:" + id, 60, "{}")
return user
# 方案1:热点数据永不过期
# 示例:设置热点数据永不过期,定期更新
SET user:1001 "{\"name\": \"zhangsan\", \"age\": 25}"
# 方案2:互斥锁
# 伪代码
user = redis.get("user:" + id)
if user:
return user
else:
# 获取锁
lock = redis.setnx("lock:user:" + id, 1)
if lock:
# 设置锁过期时间
redis.expire("lock:user:" + id, 10)
# 从数据库获取数据
user = db.query("SELECT * FROM users WHERE id = ?", id)
if user:
redis.setex("user:" + id, 3600, json.dumps(user))
# 释放锁
redis.delete("lock:user:" + id)
return user
else:
# 等待一段时间后重试
time.sleep(0.1)
return get_user(id)
# 方案1:设置随机过期时间
# 示例:为缓存设置随机过期时间,避免同时过期
EXPIRE user:1001 $((3600 + RANDOM % 1800))
# 方案2:分层缓存
# 示例:使用两级缓存
# 一级缓存:短期过期(如1小时)
# 二级缓存:长期过期(如24小时)
# 方案3:预热缓存
# 示例:系统启动时预热热点数据
# 伪代码
for id in hot_user_ids:
user = db.query("SELECT * FROM users WHERE id = ?", id)
if user:
redis.setex("user:" + id, 3600, json.dumps(user))
分布式锁是在分布式系统中实现互斥访问共享资源的机制。Redis实现分布式锁的核心原理是使用SETNX命令(或SET命令的NX选项)。
# 获取锁
SET lock:resource 1 NX EX 10
# 释放锁
DEL lock:resource
# 示例:使用redis-cli获取和释放锁
127.0.0.1:6379> SET lock:order 1 NX EX 10
OK
# 执行业务逻辑
# ...
# 释放锁
127.0.0.1:6379> DEL lock:order
(integer) 1
# 示例:使用Lua脚本确保只有持有锁的客户端才能释放锁
# Lua脚本
local key = KEYS[1]
local value = ARGV[1]
if redis.call("GET", key) == value then
return redis.call("DEL", key)
else
return 0
end
# 调用示例
EVAL "local key = KEYS[1] local value = ARGV[1] if redis.call('GET', key) == value then return redis.call('DEL', key) else return 0 end" 1 lock:resource client1
# 解释:
# KEYS[1]:锁的键名
# ARGV[1]:客户端标识
# 只有当锁的值等于客户端标识时,才释放锁
# 实际应用:使用UUID作为客户端标识
import uuid
import redis
r = redis.Redis()
lock_key = "lock:resource"
client_id = str(uuid.uuid4())
# 获取锁
try:
locked = r.set(lock_key, client_id, nx=True, ex=10)
if locked:
# 执行业务逻辑
print("获取锁成功,执行业务逻辑")
else:
print("获取锁失败")
finally:
# 释放锁
script = "local key = KEYS[1] local value = ARGV[1] if redis.call('GET', key) == value then return redis.call('DEL', key) else return 0 end"
r.eval(script, 1, lock_key, client_id)
# 示例:使用Redisson实现分布式锁
// 初始化Redisson
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 获取锁
RLock lock = redisson.getLock("lock:resource");
try {
// 尝试获取锁,最多等待10秒,锁过期时间30秒
boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (locked) {
// 执行业务逻辑
System.out.println("获取锁成功,执行业务逻辑");
} else {
System.out.println("获取锁失败");
}
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
// 关闭Redisson
redisson.shutdown();
# 实际应用:Redisson提供了丰富的分布式锁实现
# - 可重入锁(Reentrant Lock)
# - 公平锁(Fair Lock)
# - 联锁(MultiLock)
# - 红锁(RedLock)
# - 读写锁(ReadWriteLock)
# 示例:实现简单的固定窗口限流器
# 伪代码:限制每分钟最多100个请求
def is_allowed(user_id):
key = f"rate_limit:{user_id}:{int(time.time() / 60)}"
current = redis.incr(key)
if current == 1:
redis.expire(key, 60)
return current <= 100
# 实际应用:使用Redis实现
127.0.0.1:6379> INCR rate_limit:user1:16000000
(integer) 1
127.0.0.1:6379> EXPIRE rate_limit:user1:16000000 60
(integer) 1
# 检查是否超过限制
127.0.0.1:6379> GET rate_limit:user1:16000000
"50" # 未超过限制
127.0.0.1:6379> GET rate_limit:user1:16000000
"101" # 超过限制
# 示例:使用Redis的有序集合实现滑动窗口限流器
# 伪代码:限制最近60秒最多100个请求
def is_allowed(user_id):
key = f"rate_limit:{user_id}"
now = int(time.time())
window_start = now - 60
# 移除窗口外的请求
redis.zremrangebyscore(key, 0, window_start)
# 统计当前窗口内的请求数
current = redis.zcard(key)
if current < 100:
# 添加当前请求
redis.zadd(key, {str(now): now})
# 设置过期时间,避免内存泄漏
redis.expire(key, 60)
return True
else:
return False
# 实际应用:使用redis-cli执行
127.0.0.1:6379> ZADD rate_limit:user1 1600000000 "1600000000"
(integer) 1
127.0.0.1:6379> ZADD rate_limit:user1 1600000001 "1600000001"
(integer) 1
127.0.0.1:6379> ZREMRANGEBYSCORE rate_limit:user1 0 1599999940
(integer) 0
127.0.0.1:6379> ZCARD rate_limit:user1
(integer) 2
127.0.0.1:6379> EXPIRE rate_limit:user1 60
(integer) 1
# 示例:使用Redis实现令牌桶限流器
# 伪代码:每秒生成10个令牌,桶容量100
def is_allowed(user_id, rate=10, capacity=100):
key = f"token_bucket:{user_id}"
now = int(time.time() * 1000) # 毫秒时间戳
# 获取当前令牌数和上次生成令牌的时间
current_tokens = redis.hget(key, "tokens")
last_refill_time = redis.hget(key, "last_refill_time")
if not current_tokens:
# 初始化令牌桶
current_tokens = capacity
last_refill_time = now
else:
current_tokens = int(current_tokens)
last_refill_time = int(last_refill_time)
# 计算需要生成的令牌数
elapsed = now - last_refill_time
tokens_to_add = (elapsed / 1000) * rate
if tokens_to_add > 0:
current_tokens = min(current_tokens + tokens_to_add, capacity)
last_refill_time = now
# 更新令牌桶
redis.hset(key, "tokens", current_tokens)
redis.hset(key, "last_refill_time", last_refill_time)
redis.expire(key, 3600) # 设置过期时间
# 尝试获取令牌
if current_tokens >= 1:
current_tokens -= 1
redis.hset(key, "tokens", current_tokens)
return True
else:
return False
# 实际应用:使用Lua脚本优化
# Lua脚本可以保证操作的原子性
local key = KEYS[1]
local rate = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4]) or 1
local values = redis.call('HMGET', key, 'tokens', 'last_refill_time')
local current_tokens = tonumber(values[1]) or capacity
local last_refill_time = tonumber(values[2]) or now
local elapsed = now - last_refill_time
local tokens_to_add = (elapsed / 1000) * rate
if tokens_to_add > 0 then
current_tokens = math.min(current_tokens + tokens_to_add, capacity)
last_refill_time = now
redis.call('HMSET', key, 'tokens', current_tokens, 'last_refill_time', last_refill_time)
redis.call('EXPIRE', key, 3600)
end
local allowed = false
if current_tokens >= requested then
current_tokens = current_tokens - requested
redis.call('HSET', key, 'tokens', current_tokens)
allowed = true
end
return {allowed, current_tokens}
# 调用示例
EVAL "local key = KEYS[1] local rate = tonumber(ARGV[1]) local capacity = tonumber(ARGV[2]) local now = tonumber(ARGV[3]) local requested = tonumber(ARGV[4]) or 1 local values = redis.call('HMGET', key, 'tokens', 'last_refill_time') local current_tokens = tonumber(values[1]) or capacity local last_refill_time = tonumber(values[2]) or now local elapsed = now - last_refill_time local tokens_to_add = (elapsed / 1000) * rate if tokens_to_add > 0 then current_tokens = math.min(current_tokens + tokens_to_add, capacity) last_refill_time = now redis.call('HMSET', key, 'tokens', current_tokens, 'last_refill_time', last_refill_time) redis.call('EXPIRE', key, 3600) end local allowed = false if current_tokens >= requested then current_tokens = current_tokens - requested redis.call('HSET', key, 'tokens', current_tokens) allowed = true end return {allowed, current_tokens}" 1 token_bucket:user1 10 100 1600000000000
Redis的有序集合(Sorted Set)是实现排行榜的理想数据结构。有序集合中的每个成员都关联一个分数(score),Redis会根据分数自动对成员进行排序。
# 示例:实现一个简单的分数排行榜
# 添加或更新用户分数
ZADD leaderboard 100 user1
ZADD leaderboard 200 user2
ZADD leaderboard 150 user3
# 获取排行榜(从高到低)
ZREVRANGE leaderboard 0 9 WITHSCORES
# 输出
1) "user2"
2) "200"
3) "user3"
4) "150"
5) "user1"
6) "100"
# 获取排行榜(从低到高)
ZRANGE leaderboard 0 9 WITHSCORES
# 输出
1) "user1"
2) "100"
3) "user3"
4) "150"
5) "user2"
6) "200"
# 获取用户排名(从高到低)
ZREVRANK leaderboard user1
# 输出
(integer) 2 # 排名第3(从0开始)
# 获取用户分数
ZSCORE leaderboard user1
# 输出
"100"
# 增加用户分数
ZINCRBY leaderboard 50 user1
# 输出
"150"
# 检查用户是否在排行榜中
ZSCORE leaderboard user1
# 输出
"150" # 存在
# 移除用户
ZREM leaderboard user1
# 输出
(integer) 1
# 示例:实现每日排行榜
# 添加今日分数
ZADD leaderboard:2023-10-01 100 user1
ZADD leaderboard:2023-10-01 200 user2
# 获取今日排行榜
ZREVRANGE leaderboard:2023-10-01 0 9 WITHSCORES
# 示例:实现每周排行榜
ZADD leaderboard:2023-W40 1000 user1
ZADD leaderboard:2023-W40 2000 user2
# 获取每周排行榜
ZREVRANGE leaderboard:2023-W40 0 9 WITHSCORES
# 实际应用:使用脚本自动管理时间窗口
# 伪代码:清理过期的排行榜数据
for date in expired_dates:
redis.delete(f"leaderboard:{date}")
# 示例:实现多个维度的排行榜
# 按分数排行
ZADD leaderboard:score 100 user1
ZADD leaderboard:score 200 user2
# 按等级排行
ZADD leaderboard:level 5 user1
ZADD leaderboard:level 10 user2
# 按经验值排行
ZADD leaderboard:exp 1000 user1
ZADD leaderboard:exp 2000 user2
# 获取各维度排行榜
ZREVRANGE leaderboard:score 0 9 WITHSCORES
ZREVRANGE leaderboard:level 0 9 WITHSCORES
ZREVRANGE leaderboard:exp 0 9 WITHSCORES
# 实际应用:使用哈希存储用户的多个属性
HSET user:1001 name "zhangsan"
HSET user:1001 score "100"
HSET user:1001 level "5"
HSET user:1001 exp "1000"
# 更新排行榜
ZADD leaderboard:score 100 user:1001
ZADD leaderboard:level 5 user:1001
ZADD leaderboard:exp 1000 user:1001
# 示例:使用Redis存储用户会话
# 生成会话ID
SESSION_ID = generate_uuid()
# 存储会话数据
HSET session:{SESSION_ID} user_id "1001"
HSET session:{SESSION_ID} login_time "2023-10-01 10:00:00"
HSET session:{SESSION_ID} last_access "2023-10-01 10:00:00"
EXPIRE session:{SESSION_ID} 3600 # 1小时过期
# 更新会话最后访问时间
HSET session:{SESSION_ID} last_access "2023-10-01 10:30:00"
EXPIRE session:{SESSION_ID} 3600 # 重置过期时间
# 获取会话数据
HGETALL session:{SESSION_ID}
# 销毁会话
DEL session:{SESSION_ID}
# 实际应用:使用Redis实现分布式会话
# 优点:
# 1. 支持水平扩展
# 2. 会话数据集中管理
# 3. 支持会话共享
# 4. 自动过期清理
# 示例:使用Redis的列表实现简单的消息队列
# 生产者:发送消息
LPUSH queue:tasks "task1"
LPUSH queue:tasks "task2"
LPUSH queue:tasks "task3"
# 消费者:获取消息(阻塞方式)
BRPOP queue:tasks 0
# 输出
1) "queue:tasks"
2) "task1"
# 消费者:获取消息(非阻塞方式)
RPOP queue:tasks
# 输出
"task2"
# 查看队列长度
LLEN queue:tasks
# 输出
(integer) 1
# 实际应用:使用Redis实现消息队列的优缺点
# 优点:
# 1. 实现简单
# 2. 性能高
# 3. 支持阻塞读取
# 缺点:
# 1. 消息可能丢失(无持久化或持久化配置不当)
# 2. 不支持消息确认机制
# 3. 不支持消息优先级
# 4. 队列长度受限于内存
# 注意:对于生产环境的关键消息,建议使用专业的消息队列系统,如RabbitMQ、Kafka等
# 示例:使用Redis实现计数器
# 页面访问计数器
INCR page:views:home
# 获取页面访问次数
GET page:views:home
# 重置计数器
SET page:views:home 0
# 带过期时间的计数器
INCR page:views:home:today
EXPIRE page:views:home:today 86400 # 24小时过期
# 实际应用:使用Redis实现各种计数器
# 1. 网站访问量统计
# 2. 商品浏览次数
# 3. 用户登录次数
# 4. API请求次数限制
# 5. 实时在线人数统计
# 示例:实时在线人数统计
# 用户上线
SADD online:users user1
INCR online:count
# 用户下线
SREM online:users user1
DECR online:count
# 获取在线用户列表
SMEMBERS online:users
# 获取在线用户数
GET online:count
# 实际应用:使用Redis的Set和INCR/DECR命令实现实时在线人数统计
# 优点:
# 1. 实时性高
# 2. 实现简单
# 3. 支持查看在线用户列表
| 命令 | 功能说明 | 使用场景 |
|---|---|---|
| SET | 设置键值对 | 缓存基本数据 |
| GET | 获取键值 | 获取缓存数据 |
| SETNX | 仅当键不存在时设置 | 分布式锁 |
| EXPIRE | 设置键的过期时间 | 缓存过期 |
| INCR | 自增键值 | 计数器 |
| ZADD | 添加有序集合成员 | 排行榜 |
| ZREVRANGE | 获取有序集合倒序范围 | 排行榜 |
| ZINCRBY | 增加有序集合成员分数 | 排行榜 |
| LPUSH | 左侧推入列表 | 消息队列 |
| BRPOP | 阻塞右侧弹出列表 | 消息队列 |
| HSET | 设置哈希字段 | 存储对象 |
| HGETALL | 获取哈希所有字段 | 获取对象 |
| SADD | 添加集合成员 | 在线用户 |
| SMEMBERS | 获取集合所有成员 | 在线用户 |
| EVAL | 执行Lua脚本 | 原子操作 |
通过本课程的学习,你应该已经掌握了Redis实战项目的核心概念和使用方法。以下是本课程的主要内容总结:
Redis是一个功能强大的内存数据库,通过本课程的学习,你应该已经掌握了Redis在实际项目中的应用技巧。继续深入学习和实践,你将能够更好地运用Redis来解决实际问题,构建高性能、高可用的系统。
在实际项目中,需要根据具体的业务需求和场景选择合适的Redis功能和实现方案,同时注意性能优化和可靠性保障。通过不断地学习和实践,你将成为Redis的专家,为项目的成功做出贡献。