主题模式
Redis 深度解析:内存数据库的全场景应用实战
在现代分布式系统中,数据存储的选择早已不再是非 SQL 即 NoSQL 的单选题。Redis 以其亚毫秒级的访问延迟、丰富的数据结构支持和强大的发布/订阅机制,成为缓存、Session 管理、消息队列、实时排行榜、限流等场景的首选方案。掌握 Redis,就等于掌握了一把让系统性能提升 10 倍的钥匙。本文从数据结构、持久化、主从复制、集群到实战场景,带你全面掌握这个最流行的内存数据库。
一、Redis 核心概念
1.1 Redis 是什么
┌─────────────────────────────────────────────────────────────────┐
│ Redis 核心定位 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Redis = Remote Dictionary Server(远程字典服务器) │
│ │
│ 核心特点: │
│ ├─ 内存存储:数据存储在内存,读写速度极快(~100K QPS) │
│ ├─ 数据结构丰富:String、List、Hash、Set、ZSet 等 │
│ ├─ 持久化支持:RDB + AOF,可靠性有保障 │
│ ├─ 主从复制:读写分离,横向扩展读能力 │
│ ├─ 集群支持:分片存储,支持海量数据 │
│ └─ 发布订阅:内置消息队列功能 │
│ │
│ 与 Memcached 对比: │
│ ├─ Memcached: 仅 String 类型,无持久化,无集群 │
│ └─ Redis: 多种数据结构 + 持久化 + 集群 = 功能完整 │
│ │
└─────────────────────────────────────────────────────────────────┘1.2 数据结构全景
┌────────────────────────────────────────────────────────────────────┐
│ Redis 数据结构一览 │
├──────────────┬───────────────────────────────────┬────────────────┤
│ 数据结构 │ 底层实现 │ 典型用途 │
├──────────────┼───────────────────────────────────┼────────────────┤
│ STRING │ SDS(简单动态字符串) │ 缓存、计数器、 │
│ │ │ Session、锁 │
├──────────────┼───────────────────────────────────┼────────────────┤
│ LIST │ QuickList(压缩列表+双向链表) │ 消息队列、 │
│ │ │ 时间线、最新列表│
├──────────────┼───────────────────────────────────┼────────────────┤
│ HASH │ ZIPLIST / HT(压缩列表/哈希表) │ 对象存储、 │
│ │ │ 购物车、配置 │
├──────────────┼───────────────────────────────────┼────────────────┤
│ SET │ INTSET / HT(整数集/哈希表) │ 标签系统、 │
│ │ │ 去重、关注列表 │
├──────────────┼───────────────────────────────────┼────────────────┤
│ ZSET │ ZIPLIST / SKIPLIST(有序集合) │ 排行榜、 │
│ │ │ 权重排序、延迟队列│
├──────────────┼───────────────────────────────────┼────────────────┤
│ BITMAP │ String(位操作) │ 用户签到、 │
│ │ │ 布隆过滤器基础 │
├──────────────┼───────────────────────────────────┼────────────────┤
│ HYPERLOGLOG │ String(概率数据结构) │ UV 统计 │
├──────────────┼───────────────────────────────────┼────────────────┤
│ GEOSPATIAL │ Sorted Set(地理位置) │ 附近的人/商家 │
├──────────────┼───────────────────────────────────┼────────────────┤
│ STREAM │ Radix Tree(流式数据) │ 消息队列、 │
│ │ │ 事件流、日志 │
└──────────────┴───────────────────────────────────┴────────────────┘二、安装与基础操作
2.1 安装与启动
bash
# macOS
brew install redis
brew services start redis
redis-server /usr/local/etc/redis.conf
# Ubuntu/Debian
sudo apt update
sudo apt install redis-server
sudo systemctl start redis-server
# Docker
docker run -d --name redis \
-p 6379:6379 \
redis:latest \
redis-server --appendonly yes
# Docker Compose
# docker-compose.yml
version: '3.8'
services:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
redis_data:2.2 客户端连接与配置
bash
# 命令行客户端
redis-cli
# 指定主机和端口
redis-cli -h localhost -p 6379
# 带密码认证
redis-cli -a mypassword
# 选择数据库(0-15,默认16个逻辑库)
redis-cli
> SELECT 0
> PING
PONG
# 配置文件(redis.conf 关键配置)
# redis.conf
bind 127.0.0.1 # 监听地址
port 6379 # 端口
protected-mode yes # 保护模式
requirepass yourpassword # 密码认证
databases 16 # 数据库数量
dir /var/lib/redis # 数据目录
dbfilename dump.rdb # RDB 文件名
appendfilename "appendonly.aof" # AOF 文件名
# 内存配置
maxmemory 2gb # 最大内存(生产必设!)
maxmemory-policy allkeys-lru # 内存淘汰策略2.3 通用命令
bash
# ===== 连接与状态 =====
PING # 测试连接,返回 PONG
ECHO "hello" # 返回字符串
CLIENT LIST # 查看所有客户端
CLIENT KILL ID xxx # 关闭指定客户端
DBSIZE # 当前数据库键数量
INFO # 服务器详细信息
INFO memory # 内存使用信息
INFO stats # 操作统计信息
CONFIG GET maxmemory # 查看配置项
CONFIG SET maxmemory 1gb # 动态修改配置
# ===== 键操作 =====
KEYS pattern # 查找键(生产禁用!用 SCAN)
SCAN 0 MATCH user:* COUNT 100 # 游标遍历键
EXISTS key # 检查键是否存在
TYPE key # 查看键的数据类型
DEL key [key2...] # 删除键(返回删除数量)
UNLINK key # 异步删除(不阻塞主线程)
RENAME oldkey newkey # 重命名(覆盖)
RENAMENX oldkey newkey # 重命名(仅当新键不存在)
EXPIRE key 3600 # 设置过期时间(秒)
EXPIREAT key 1735123456 # 设置过期时间戳
TTL key # 查看剩余 TTL(-1=永不过期,-2=不存在)
PTTL key # 查看剩余 TTL(毫秒)
PERSIST key # 移除过期时间(永不过期)
# ===== 数据库管理 =====
SELECT db # 切换数据库
SWAPDB db1 db2 # 交换两个数据库
FLUSHDB # 清空当前数据库(危险!)
FLUSHDB ASYNC # 异步清空(不阻塞)
FLUSHALL # 清空所有数据库(危险!)三、数据结构详解
3.1 String(字符串)
String 是 Redis 最基础的数据类型,value 最大 512MB:
bash
# ===== 基本操作 =====
SET key value # 设置值
SET key value NX # 仅当键不存在时设置(原子)
SET key value XX # 仅当键存在时设置
SET key value EX 3600 # 设置值并指定过期时间(秒)
SET key value PX 3600000 # 设置值并指定过期时间(毫秒)
SET key value GET # 设置值并返回旧值
GET key # 获取值
MGET key1 key2 key3 # 批量获取
# ===== 数值操作(原子递增/递减)=====
SET counter 100
INCR counter # +1 → 101
INCRBY counter 5 # +5 → 106
DECR counter # -1 → 105
DECRBY counter 3 # -3 → 102
INCRBYFLOAT counter 2.5 # +2.5 → 104.5
# ===== 位操作 =====
SETBIT bitmap 100 1 # 设置第100位的值为1
GETBIT bitmap 100 # 获取第100位的值
BITCOUNT bitmap [start end] # 统计1的位数(用户签到统计)
BITOP AND result key1 key2 # 位运算(AND/OR/XOR/NOT)
BITPOS bitmap 1 [start byte] # 查找第一个1的位置
# 场景:用户签到(Bitmap 位图)
# 一年365天,每个用户占365位 ≈ 46字节(vs 365条字符串的 365*40字节)
SETBIT user:1001:2024:08 25 1 # 用户1001在2024年8月26日签到
BITCOUNT user:1001:2024:08 # 8月份签到天数typescript
// ts/redis-string.ts - String 操作实战
import { createClient, RedisClientType } from 'redis';
const redis: RedisClientType = await createClient({
url: process.env.REDIS_URL,
password: process.env.REDIS_PASSWORD,
}).connect();
// ===== 缓存模式 =====
async function cacheAside<T>(
key: string,
fetcher: () => Promise<T>,
ttlSeconds = 3600
): Promise<T> {
// 1. 尝试从缓存读取
const cached = await redis.get(key);
if (cached) {
console.log('Cache hit:', key);
return JSON.parse(cached) as T;
}
// 2. 缓存未命中,从数据源获取
console.log('Cache miss:', key);
const data = await fetcher();
// 3. 回填缓存
await redis.setEx(key, ttlSeconds, JSON.stringify(data));
return data;
}
// ===== 分布式锁 =====
async function acquireLock(
lockKey: string,
ttlMs = 10000
): Promise<string | null> {
const lockValue = `${Date.now()}-${Math.random()}`;
// SET NX EX 原子操作(获取锁)
const acquired = await redis.set(lockKey, lockValue, {
NX: true,
PX: ttlMs,
});
return acquired === 'OK' ? lockValue : null;
}
async function releaseLock(lockKey: string, lockValue: string): Promise<boolean> {
// Lua 脚本保证原子性(检查值后才能删除)
const script = `
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
`;
const result = await redis.eval(script, {
keys: [lockKey],
arguments: [lockValue],
});
return result === 1;
}
// ===== 计数器(限流)=====
async function rateLimit(
userId: string,
maxRequests: number,
windowSeconds: number
): Promise<{ allowed: boolean; remaining: number }> {
const key = `ratelimit:${userId}:${Math.floor(Date.now() / 1000 / windowSeconds)}`;
const count = await redis.incr(key);
if (count === 1) {
// 第一次请求,设置过期时间
await redis.expire(key, windowSeconds);
}
return {
allowed: count <= maxRequests,
remaining: Math.max(0, maxRequests - count),
};
}
// ===== 分布式信号量 =====
async function acquireSemaphore(
semKey: string,
maxCount: number,
ttlSeconds = 30
): Promise<string | null> {
const myId = `${Date.now()}-${Math.random()}`;
// 先移除过期信号量
const now = Date.now();
const keys = await redis.zRange(semKey, 0, -1);
for (const k of keys) {
const score = await redis.zScore(semKey, k);
if (score < now) {
await redis.zRem(semKey, k);
}
}
// 检查当前信号量数量
const current = await redis.zCard(semKey);
if (current >= maxCount) {
return null; // 信号量已满
}
// 获取信号量
await redis.zAdd(semKey, { score: now + ttlSeconds * 1000, value: myId });
await redis.expire(semKey, ttlSeconds + 10);
return myId;
}3.2 Hash(哈希)
Hash 是字段值对,适合存储对象,比 JSON 字符串更节省空间且支持字段级操作:
bash
# ===== 基本操作 =====
HSET user:1001 name "Alice" age "28" email "alice@example.com"
HGET user:1001 name # 获取单个字段
HMGET user:1001 name email # 批量获取字段
HGETALL user:1001 # 获取所有字段值
HLEN user:1001 # 字段数量
# ===== 字段操作 =====
HEXISTS user:1001 name # 检查字段是否存在(1/0)
HDEL user:1001 age # 删除字段(返回删除数量)
HINCRBY user:1001 age 1 # 字段递增(原子)
HINCRBYFLOAT user:1001 score 2.5 # 浮点递增
# ===== 游标遍历 =====
HSCAN user:1001 0 MATCH name:* COUNT 100
# ===== 字段级过期(Redis 7.2+)=====
HSET user:1001 last_login "2024-08-25"
HEXPIRE user:1001 last_login 86400 # last_login 字段 24h 后过期
HEXpireAT user:1001 last_login 1735123456
HTTL user:1001 last_login # 查看字段剩余 TTLtypescript
// ts/redis-hash.ts - Hash 实战
// ===== 用户会话存储 =====
async function saveUserSession(userId: string, sessionData: Record<string, string>) {
const key = `session:${userId}`;
// HSET 批量写入
await redis.hSet(key, sessionData);
// 设置整个 Hash 的过期时间
await redis.expire(key, 86400); // 24小时
}
async function updateUserPreference(userId: string, pref: string, value: string) {
await redis.hSet(`user:pref:${userId}`, pref, value);
}
async function getUserPreferences(userId: string) {
return await redis.hGetAll(`user:pref:${userId}`);
}
// ===== 购物车 =====
async function addToCart(userId: string, productId: string, quantity: number) {
const key = `cart:${userId}`;
if (quantity <= 0) {
// 数量 <= 0 时移除商品
await redis.hDel(key, productId);
} else {
await redis.hSet(key, productId, quantity.toString());
}
// 设置购物车过期时间(30天不活跃则清空)
await redis.expire(key, 30 * 86400);
}
async function getCart(userId: string) {
const cart = await redis.hGetAll(`cart:${userId}`);
return Object.entries(cart).map(([productId, qty]) => ({
productId,
quantity: parseInt(qty, 10),
}));
}
// ===== 实时排行榜元数据 =====
async function updatePlayerScore(playerId: string, score: number, metadata: {
name: string;
level: number;
}) {
const key = 'leaderboard:v1';
// 更新分数
await redis.zAdd(key, { score, value: playerId });
// 单独存储玩家元数据
await redis.hSet(`player:${playerId}`, {
name: metadata.name,
level: metadata.level.toString(),
score: score.toString(),
updatedAt: new Date().toISOString(),
});
await redis.expire(`player:${playerId}`, 86400 * 7); // 7天过期
}3.3 List(列表)
List 是有序字符串列表,支持两端操作,适合消息队列和最新列表:
bash
# ===== 基本操作 =====
LPUSH tasks "task1" # 左侧插入(栈)
RPUSH tasks "task2" # 右侧插入(队列)
LPOP tasks # 左侧弹出
RPOP tasks # 右侧弹出
LPOP tasks 2 # 弹出多个(Redis 7.2+)
LRANGE tasks 0 -1 # 获取所有元素(0 -1 表示全部)
LLEN tasks # 列表长度
# ===== 范围操作 =====
LTRIM tasks 0 99 # 修剪列表(只保留索引0-99)
LINSERT tasks BEFORE "task1" "new_task" # 指定位置插入
LSET tasks 0 "new_head" # 设置指定索引的值
# ===== 阻塞操作(消息队列核心)=====
BLPOP tasks 30 # 阻塞左弹出(超时30秒)
BRPOP tasks 30 # 阻塞右弹出
# BLPOP 是 Redis 队列模式的核心——生产者 LPUSH,消费者 BLPOP
# ===== 列表作为队列 =====
LPUSH queue:tasks "task:001"
LPUSH queue:tasks "task:002"
LPUSH queue:tasks "task:003"
BRPOP queue:tasks 0 # 阻塞消费(FIFO)
# ===== 列表作为栈 =====
LPUSH stack:undo "action1"
LPUSH stack:undo "action2"
LPOP stack:undo # 撤销最近的操作typescript
// ts/redis-list.ts - List 实战
// ===== 消息队列(可靠队列模式)=====
// 生产者
async function enqueueTask(queue: string, task: Record<string, unknown>) {
const messageId = `${Date.now()}:${Math.random()}`;
await redis.lPush(`${queue}:pending`, JSON.stringify({
id: messageId,
payload: task,
enqueuedAt: Date.now(),
}));
// 也记录到处理中队列(用于追踪)
await redis.hSet(`${queue}:tracking`, messageId, JSON.stringify({
status: 'pending',
enqueuedAt: Date.now(),
}));
return messageId;
}
// 消费者(可靠消费模式)
async function consumeTask(
queue: string,
handler: (task: any) => Promise<void>
): Promise<void> {
const result = await redis.brPop(`${queue}:pending`, 5); // 阻塞5秒
if (!result) return;
const queueName = result[0];
const rawMessage = result[1];
if (queueName !== `${queue}:pending`) return;
const message = JSON.parse(rawMessage);
try {
await handler(message.payload);
// 处理成功:移到完成队列
await redis.lPush(`${queue}:completed`, rawMessage);
await redis.hSet(`${queue}:tracking`, message.id, JSON.stringify({
status: 'completed',
completedAt: Date.now(),
}));
} catch (error) {
// 处理失败:移到重试队列
await redis.lPush(`${queue}:retry`, rawMessage);
await redis.hSet(`${queue}:tracking`, message.id, JSON.stringify({
status: 'retry',
error: (error as Error).message,
retriedAt: Date.now(),
}));
}
}
// ===== 最新文章列表(时间线)=====
async function publishArticle(article: { id: string; title: string }) {
const timelineKey = 'timeline:latest';
// 左侧插入(最新在前)
await redis.lPush(timelineKey, JSON.stringify({
id: article.id,
title: article.title,
publishedAt: Date.now(),
}));
// 只保留最近100条
await redis.lTrim(timelineKey, 0, 99);
// 设置过期(1天后自动清理)
await redis.expire(timelineKey, 86400);
}
async function getLatestArticles(limit = 20) {
const articles = await redis.lRange('timeline:latest', 0, limit - 1);
return articles.map(a => JSON.parse(a));
}
// ===== 用户最近搜索历史 =====
async function saveSearchQuery(userId: string, query: string) {
const key = `search:history:${userId}`;
// 先去重(移除旧的相同查询)
const existing = await redis.lPos(key, query);
if (existing !== null) {
await redis.lRem(key, 1, query);
}
// 左侧插入
await redis.lPush(key, query);
// 只保留最近20条
await redis.lTrim(key, 0, 19);
// 30天过期
await redis.expire(key, 30 * 86400);
}
async function getSearchHistory(userId: string, limit = 10) {
return await redis.lRange(`search:history:${userId}`, 0, limit - 1);
}3.4 Set(集合)
Set 是无序不重复字符串集合,适合标签、去重、交集运算:
bash
# ===== 基本操作 =====
SADD tags:article:1001 "redis" "database" "cache"
SMEMBERS tags:article:1001 # 获取所有成员
SISMEMBER tags:article:1001 "redis" # 检查成员是否存在(1/0)
SCARD tags:article:1001 # 成员数量
SREM tags:article:1001 "cache" # 删除成员
SPOP tags:article:1001 2 # 随机弹出2个成员
SRANDMEMBER tags:article:1001 3 # 随机获取3个成员(不删除)
# ===== 集合运算 =====
SINTER tags:article:1001 tags:article:1002 # 交集(共同标签)
SUNION tags:article:1001 tags:article:1002 # 并集(所有标签)
SDIFF tags:article:1001 tags:article:1002 # 差集(在1001但不在1002)
SINTERSTORE common_tags tags:article:1001 tags:article:1002 # 交集并存储
# ===== 场景:文章标签 =====
SADD tags:article:1001 "redis" "database" "performance"
SADD tags:article:1002 "redis" "golang" "performance"
SINTER tags:article:1001 tags:article:1002
# 结果: {"redis", "performance"} — 两篇文章的共同标签typescript
// ts/redis-set.ts - Set 实战
// ===== 文章标签系统 =====
async function addTagToArticle(articleId: string, tag: string) {
await redis.sAdd(`article:${articleId}:tags`, tag);
}
async function removeTagFromArticle(articleId: string, tag: string) {
await redis.sRem(`article:${articleId}:tags`, tag);
}
async function getArticleTags(articleId: string) {
return await redis.sMembers(`article:${articleId}:tags`);
}
async function getArticlesWithAllTags(tags: string[]) {
// 获取所有标签对应的文章 ID 集合
const keys = tags.map(tag => `tag:${tag}:articles`);
return await redis.sInter(...keys);
}
async function getArticlesWithAnyTag(tags: string[]) {
const keys = tags.map(tag => `tag:${tag}:articles`);
return await redis.sUnion(...keys);
}
// 标签下所有文章
async function addArticleToTag(tag: string, articleId: string) {
await redis.sAdd(`tag:${tag}:articles`, articleId);
}
// ===== 用户关注/粉丝系统 =====
async function followUser(followerId: string, followeeId: string) {
await Promise.all([
redis.sAdd(`user:${followeeId}:followers`, followerId),
redis.sAdd(`user:${followerId}:following`, followeeId),
]);
}
async function unfollowUser(followerId: string, followeeId: string) {
await Promise.all([
redis.sRem(`user:${followeeId}:followers`, followerId),
redis.sRem(`user:${followerId}:following`, followeeId),
]);
}
async function getFollowers(userId: string, page = 0, pageSize = 20) {
const start = page * pageSize;
const end = start + pageSize - 1;
return await redis.sMembers(`user:${userId}:followers`);
}
async function getMutualFollowers(userId1: string, userId2: string) {
return await redis.sInter(`user:${userId1}:following`, `user:${userId2}:following`);
}
// ===== UV 统计(去重计数)=====
async function recordUniqueView(itemId: string, userId: string) {
// 使用 SET 去重
return await redis.sAdd(`views:${itemId}`, userId);
}
async function getUniqueViewCount(itemId: string) {
return await redis.sCard(`views:${itemId}`);
}3.5 ZSet(有序集合)
ZSet 是带分数的有序集合,分数用于排序,支持按分数范围查询:
bash
# ===== 基本操作 =====
ZADD leaderboard 100 "alice" 200 "bob" 150 "charlie" # 添加(分数, 成员)
ZSCORE leaderboard "alice" # 获取分数
ZRANK leaderboard "alice" # 获取排名(从小到大,0起)
ZREVRANK leaderboard "alice" # 获取排名(从大到小,即时榜)
ZCARD leaderboard # 成员数量
ZRANGE leaderboard 0 9 WITHSCORES # 获取排名0-9(从小到大)
ZREVRANGE leaderboard 0 9 WITHSCORES # 获取排名前10(从大到小)
ZINCRBY leaderboard 50 "alice" # 分数递增(+50)
# ===== 分数范围查询 =====
ZRANGEBYSCORE leaderboard 100 200 # 100-200分的成员
ZREVRANGEBYSCORE leaderboard 200 100 # 200-100分的成员(从大到小)
ZRANGEBYSCORE leaderboard -inf +inf WITHSCORES # 全部成员
# ===== 去重计数(ZSet 版本)=====
ZADD uv:2024:08:25 1 "user:1001"
ZADD uv:2024:08:25 1 "user:1002"
ZADD uv:2024:08:25 1 "user:1001" # 重复用户,分数+1
ZCARD uv:2024:08:25 # UV = 2(不同用户数)typescript
// ts/redis-zset.ts - ZSet 实战
// ===== 实时排行榜 =====
async function updateScore(playerId: string, scoreIncrement: number) {
await redis.zIncrBy('leaderboard:daily', scoreIncrement, playerId);
}
async function getTopPlayers(limit = 10) {
const results = await redis.zRangeWithScores('leaderboard:daily', 0, limit - 1, {
REV: true, // 从大到小
});
return results.map((item, index) => ({
rank: index + 1,
playerId: item.value,
score: item.score,
}));
}
async function getPlayerRank(playerId: string) {
const rank = await redis.zRevRank('leaderboard:daily', playerId);
const score = await redis.zScore('leaderboard:daily', playerId);
return {
rank: rank !== null ? rank + 1 : null,
score,
};
}
async function getPlayersAround(playerId: string, range = 2) {
const rank = await redis.zRevRank('leaderboard:daily', playerId);
if (rank === null) return [];
const start = Math.max(0, rank - range);
const end = rank + range;
const results = await redis.zRangeWithScores('leaderboard:daily', start, end, { REV: true });
return results.map((item, index) => ({
rank: start + index + 1,
playerId: item.value,
score: item.score,
}));
}
// ===== 时间衰减排行榜(最近N天活跃用户)=====
async function recordDailyActive(userId: string, date: string) {
const key = `dau:${date}`;
await redis.zAdd(key, { score: Date.now(), value: userId });
await redis.expire(key, 8 * 86400); // 保留8天
}
async function getTopActiveUsers(date: string, limit = 10) {
const results = await redis.zRangeWithScores(`dau:${date}`, 0, limit - 1, {
REV: true,
});
return results;
}
// ===== 延迟队列 =====
async function enqueueDelay(key: string, payload: unknown, delayMs: number) {
const score = Date.now() + delayMs;
await redis.zAdd(`delay:${key}`, {
score,
value: JSON.stringify(payload),
});
}
async function processDelayQueue(key: string, processor: (payload: any) => Promise<void>) {
const now = Date.now();
// 取出所有已到期的消息
const messages = await redis.zRangeByScore(`delay:${key}`, 0, now);
for (const raw of messages) {
// 从队列移除
const removed = await redis.zRem(`delay:${key}`, raw);
if (removed === 1) {
const payload = JSON.parse(raw);
await processor(payload);
}
}
}
// ===== IP 黑名单(分数=封禁时间戳)=====
async function blockIP(ip: string, durationSeconds: number) {
const unblockAt = Date.now() + durationSeconds * 1000;
await redis.zAdd('ip:blacklist', { score: unblockAt, value: ip });
}
async function isIPBlocked(ip: string): Promise<boolean> {
const score = await redis.zScore('ip:blacklist', ip);
if (score === null) return false;
if (score <= Date.now()) {
// 已过期,自动清理
await redis.zRem('ip:blacklist', ip);
return false;
}
return true;
}
// 定期清理过期黑名单
async function cleanupExpiredBlocks() {
await redis.zRemRangeByScore('ip:blacklist', 0, Date.now());
}3.6 高级数据类型
bash
# ===== GEO(地理位置)=====
GEOADD locations 116.4074 39.9042 "beijing"
GEOADD locations 121.4737 31.2304 "shanghai" 120.1536 30.2885 "hangzhou"
GEODIST locations beijing shanghai km # 两地距离(km/m/ft/mi)
GEOPOS locations beijing # 获取坐标
GEORADIUS locations 116 39 100 km WITHDIST ASC COUNT 10 # 附近100km的城市
# ===== HyperLogLog(概率去重计数)=====
PFADD uv:2024:08:25 "user:1001"
PFADD uv:2024:08:25 "user:1002"
PFADD uv:2024:08:25 "user:1001" # 重复用户,不影响
PFCOUNT uv:2024:08:25 # 近似UV ≈ 2(标准误差 ≈ 0.81%)
PFMERGE uv:weekly uv:2024:08:25 uv:2024:08:26 # 合并
# ===== Stream(流)=====
XADD orders:stream * item "product:001" qty 1 price 99.9
XADD orders:stream * item "product:002" qty 2 price 49.9
XLEN orders:stream # 消息数量
XRANGE orders:stream - + # 读取所有消息(- + 表示全部)
XRANGE orders:stream $ + # 从当前最大ID读取新消息(阻塞监听)
XREAD STREAMS orders:stream $ # 阻塞读取新消息
# 消费者组(消息队列模式)
XGROUP CREATE orders:stream consumer-group BEGINNING
XREADGROUP GROUP consumer-group worker1 STREAMS orders:stream ">"
# ">" 表示新消息(非历史消息)
XPENDING orders:stream consumer-group # 待确认消息
XACK orders:stream consumer-group <msg_id> # 确认消息四、持久化机制
4.1 RDB 与 AOF 对比
┌─────────────────────────────────────────────────────────────────┐
│ Redis 持久化方案对比 │
├───────────────────────┬──────────────────┬───────────────────────┤
│ 特性 │ RDB │ AOF │
├───────────────────────┼──────────────────┼───────────────────────┤
│ 存储方式 │ 二进制快照文件 │ 命令追加日志 │
│ 文件大小 │ 紧凑(小) │ 较大(需压缩) │
│ 恢复速度 │ 快(二进制直接加载)│ 慢(重放命令) │
│ 数据完整性 │ ❌ 可能有丢失 │ ✅ 取决于刷盘策略 │
│ 写入性能 │ fork() 瞬间阻塞│ 不影响主线程 │
│ 适用场景 │ 冷备、定期快照 │ 实时持久化 │
├───────────────────────┼──────────────────┼───────────────────────┤
│ 优点 │ 紧凑、恢复快 │ 更安全、append-only │
│ 缺点 │ 可能丢失最后快照 │ 文件大、重放慢 │
│ │ 之间的数据 │ │
└───────────────────────┴──────────────────┴───────────────────────┘4.2 配置实战
bash
# ===== RDB 配置(redis.conf)=====
save 900 1 # 900秒内≥1个key变化 → 触发BGSAVE
save 300 10 # 300秒内≥10个key变化
save 60 10000 # 60秒内≥10000个key变化
stop-writes-on-bgsave-error yes # BGSAVE失败时停止写入
rdbcompression yes # 压缩 RDB 文件
rdbchecksum yes # 校验 RDB 文件
dbfilename dump.rdb # 文件名
dir /var/lib/redis # 存储目录
# ===== AOF 配置 =====
appendonly yes # 启用 AOF
appendfilename "appendonly.aof"
appendfsync everysec # everysec(推荐:每秒刷盘,最多丢1秒)
# appendfsync always # 每次写入都刷盘(最安全,最慢)
# appendfsync no # 由系统决定(最快,最不安全)
auto-aof-rewrite-percentage 100 # 文件比上次大100%时触发重写
auto-aof-rewrite-min-size 64mb # 文件至少64MB才触发重写
aof-load-truncated yes # 加载截断的 AOF 文件(不中断启动)
aof-use-rdb-preamble yes # 混合持久化(RDB+AOF,启动更快)
# ===== 混合持久化(推荐!Redis 4.0+)=====
# RDB 格式开头(快速加载)+ AOF 追加(数据完整)
# AOF 重写时使用 RDB 格式写入开头
# 启动时:先加载 RDB 部分,再重放 AOF 部分bash
# ===== 手动命令 =====
BGSAVE # 后台异步保存(不阻塞主线程)
SAVE # 同步保存(阻塞,不推荐)
LASTSAVE # 返回上次保存时间戳
BGREWRITEAOF # 后台重写 AOF4.3 主从复制
┌────────────────────────────────────────────────────────────────┐
│ Redis 主从复制架构 │
│ │
│ Master │
│ ┌──────────┐ 异步同步 ┌──────────┐ 异步同步 ┌─────┐ │
│ │ 写操作 │ ──────────▶ │ Slave 1 │ ──────────▶ │ S2 │ │
│ └──────────┘ └──────────┘ └─────┘ │
│ │ │ │
│ ┌────┴────┐ ┌─────┴─────┐ │
│ │ RDB/AOF │ │ 只读副本 │ │
│ └─────────┘ └───────────┘ │
│ │
│ 故障转移:Sentinel / Cluster 自动选主 │
└────────────────────────────────────────────────────────────────┘bash
# ===== 从节点配置(redis.conf)=====
replicaof 127.0.0.1 6379 # 主节点地址
replica-serve-stale-data yes # 主节点不可用时仍可读取
replica-read-only yes # 从节点只读
repl-diskless-sync yes # 无盘复制(直接通过网络)
repl-diskless-sync-delay 5 # 无盘复制延迟
repl-ping-replica-period 10 # PING 间隔(秒)
repl-timeout 60 # 复制超时
# ===== 复制安全 =====
requirepass yourpassword # 主节点密码
masterauth yourpassword # 从节点连接密码
# ===== 运维命令 =====
INFO replication # 查看复制状态
ROLE # 查看节点角色
REPLICAOF no one # 取消从属(成为独立节点)
CLIENT SETNAME replica-1 # 设置客户端名称五、主从与哨兵
5.1 Redis Sentinel(哨兵)
Sentinel 负责监控主从节点、自动故障转移和客户端配置更新:
┌──────────────────────────────────────────────────────────────┐
│ Redis Sentinel 架构 │
│ │
│ ┌─────────────┐ │
│ │ Sentinel 1 │ │
│ │ (Monitor) │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────┴──────┐ ┌────────────┐ │
│ │ Sentinel 2 │──────│ Sentinel 3 │ (3个Sentinel达成共识)│
│ └─────────────┘ └────────────┘ │
│ │
│ ▼ 监控 & 故障检测 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Redis Master │ │
│ │ (写 + 读/同步) │ │
│ └───────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────┐ │
│ │ Redis Replica 1 │ │
│ │ (只读) │ │
│ └───────────────────────────┘ │
│ ┌───────────────────────────┐ │
│ │ Redis Replica 2 │ │
│ │ (只读) │ │
│ └───────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘conf
# sentinel.conf
port 26379
daemonize no
bind 0.0.0.0
# 监控主节点
sentinel monitor mymaster 127.0.0.1 6379 2
# 格式: sentinel monitor <name> <ip> <port> <quorum>
# quorum = 2 表示至少2个Sentinel投票认为主节点下线才触发故障转移
sentinel auth-pass mymaster yourpassword
sentinel down-after-milliseconds mymaster 30000 # 30秒无响应认为主观下线
sentinel parallel-syncs mymaster 1 # 故障转移时同时同步的从节点数
sentinel failover-timeout mymaster 180000 # 故障转移超时(3分钟)
sentinel notification-script mymaster /path/to/notify.sh
sentinel client-reconfig-script mymaster /path/to/reconfig.shtypescript
// ts/redis-sentinel.ts - Sentinel 客户端
import { createClient, RedisClientType } from 'redis';
const sentinelAddresses = [
'redis://sentinel1:26379',
'redis://sentinel2:26379',
'redis://sentinel3:26379',
];
// Redis 6+ 官方 Sentinel 支持
const redis: RedisClientType = await createClient({
socket: {
// DNS SRV 记录自动发现(推荐)
// 需要在 DNS 配置: _redis-sentinel._tcp.example.com SRV ...
},
}).connect();
// 手动 Sentinel 路由
async function getMasterClient() {
// 从 Sentinel 获取主节点地址
const SENTINEL_PORT = 26379;
const masterInfo = await fetchFromSentinel(SENTINEL_PORT, 'SENTINEL get-master-addr-by-name mymaster');
const [host, port] = masterInfo;
return createClient({ url: `redis://${host}:${port}` });
}
async function fetchFromSentinel(port: number, command: string) {
const response = await fetch(`http://localhost:${port}/-${command}`);
return response.text();
}六、Redis Cluster
6.1 分片集群架构
┌──────────────────────────────────────────────────────────────┐
│ Redis Cluster(6节点分片) │
│ │
│ Slot 0-5460 Slot 5461-10922 Slot 10923-16383 │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Node A (M) │ │ Node B (M) │ │ Node C (M) │ │
│ │ 192.168.1.1 │ │ 192.168.1.3 │ │ 192.168.1.5 │ │
│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │
│ │ │ Replica A1 │ │ │ │ Replica B1 │ │ │ │ Replica C1 │ │ │
│ │ │ (从节点) │ │ │ │ (从节点) │ │ │ │ (从节点) │ │ │
│ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │
│ 故障转移:主节点不可用时,从节点自动升级 │
│ 槽迁移:在线重新分配 Slot,数据自动迁移 │
│ 客户端路由:MOVED 重定向(ASK/REDIR) │
└──────────────────────────────────────────────────────────────┘bash
# ===== 创建集群(6节点:3主3从)=====
redis-cli --cluster create \
192.168.1.1:6379 \
192.168.1.2:6379 \
192.168.1.3:6379 \
192.168.1.4:6379 \
192.168.1.5:6379 \
192.168.1.6:6379 \
--cluster-replicas 1
# ===== 集群命令 =====
redis-cli -c -p 6379 # 集群模式客户端(-c)
CLUSTER INFO # 集群状态信息
CLUSTER NODES # 所有节点信息
CLUSTER SLOTS # 槽分配信息
CLUSTER KEYSLOT <key> # 查看 key 属于哪个槽
# ===== 槽迁移 =====
redis-cli --cluster reshard 192.168.1.1:6379
# 交互式分配槽:源节点 → 目标节点
# ===== 故障转移 =====
CLUSTER FAILOVER # 从节点手动触发故障转移
CLUSTER RESET # 重置节点集群配置6.2 客户端路由策略
typescript
// ts/redis-cluster.ts - Cluster 客户端
import { createCluster, RedisClusterType } from 'redis';
const cluster: RedisClusterType = await createCluster({
rootNodes: [
{ url: 'redis://192.168.1.1:6379' },
{ url: 'redis://192.168.1.2:6379' },
{ url: 'redis://192.168.1.3:6379' },
],
defaults: {
// 每个主节点的从节点配置
replicas: [{ url: 'redis://192.168.1.4:6379' }],
},
useReplicas: true, // 优先读从节点
});
// ===== 智能路由 =====
await cluster.set('user:1001', JSON.stringify(user));
const value = await cluster.get('user:1001');
// 批量操作(MGET/MSET)
const values = await cluster.mGet(['key1', 'key2', 'key3']);
// 跨节点操作(Lua 脚本自动处理 MOVED)
const script = `
local result = {}
for i, key in ipairs(KEYS) do
table.insert(result, redis.call('GET', key))
end
return result
`;
await cluster.eval(script, { keys: ['key1', 'key2', 'key3'], arguments: [] });七、实战场景
7.1 缓存策略
typescript
// ts/cache-strategy.ts - 完整缓存策略
// ===== Cache-Aside(旁路缓存,最常用)=====
async function getUser(userId: string): Promise<User | null> {
const cacheKey = `user:${userId}`;
// 1. 读缓存
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// 2. 缓存未命中,读数据库
const user = await db.users.findById(userId);
if (!user) return null;
// 3. 写缓存
await redis.setEx(cacheKey, 3600, JSON.stringify(user));
return user;
}
async function updateUser(userId: string, data: Partial<User>) {
// 1. 更新数据库
const user = await db.users.update(userId, data);
// 2. 删除缓存(而非更新,防止并发不一致)
await redis.del(`user:${userId}`);
return user;
}
// ===== Write-Through(写入穿透)=====
async function saveProduct(product: Product) {
const cacheKey = `product:${product.id}`;
await Promise.all([
db.products.save(product),
redis.setEx(cacheKey, 1800, JSON.stringify(product)),
]);
}
// ===== Write-Behind(异步写入)=====
// 更新时不立即写数据库,放到写入队列
const writeQueue: string[] = [];
async function incrementViewCount(productId: string) {
// 1. 先更新缓存
await redis.zIncrBy('product:views:daily', 1, productId);
// 2. 异步批量写数据库(通过定时任务)
writeQueue.push(productId);
if (writeQueue.length >= 100) {
await flushToDatabase();
}
}
async function flushToDatabase() {
const batch = [...new Set(writeQueue.splice(0))];
await db.batchUpdateViews(batch);
}
// ===== 缓存预热 =====
async function warmUpCache() {
// 首页数据预热
const hotProducts = await db.products.findHot({ limit: 100 });
await Promise.all(hotProducts.map(p =>
redis.setEx(`product:${p.id}`, 3600, JSON.stringify(p))
));
// 排行榜预热
const leaderboard = await db.scores.getTop(100);
await redis.del('leaderboard:daily');
await redis.zAdd('leaderboard:daily',
leaderboard.map((p, i) => ({ score: p.score, value: p.playerId }))
);
}7.2 分布式锁详解
typescript
// ts/distributed-lock.ts - 生产级分布式锁
import { createClient, RedisClientType } from 'redis';
class DistributedLock {
constructor(private redis: RedisClientType) {}
// ===== 获取锁 =====
async acquire(
resource: string,
ttlMs = 10000,
retryCount = 3,
retryDelayMs = 200
): Promise<string | null> {
const lockKey = `lock:${resource}`;
const lockValue = `${Date.now()}-${Math.random()}-${process.pid}`;
for (let i = 0; i < retryCount; i++) {
// SET NX PX — 原子操作
const acquired = await this.redis.set(lockKey, lockValue, {
NX: true,
PX: ttlMs,
});
if (acquired === 'OK') {
return lockValue;
}
// 重试前等待
if (i < retryCount - 1) {
await new Promise(r => setTimeout(r, retryDelayMs + Math.random() * 100));
}
}
return null; // 获取锁失败
}
// ===== 释放锁(Lua 脚本保证原子性)=====
async release(resource: string, lockValue: string): Promise<boolean> {
const lockKey = `lock:${resource}`;
const script = `
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
`;
const result = await this.redis.eval(script, {
keys: [lockKey],
arguments: [lockValue],
});
return result === 1;
}
// ===== 续期(防止锁提前释放)=====
async extend(resource: string, lockValue: string, ttlMs: number): Promise<boolean> {
const lockKey = `lock:${resource}`;
const script = `
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
else
return 0
end
`;
const result = await this.redis.eval(script, {
keys: [lockKey],
arguments: [lockValue, ttlMs.toString()],
});
return result === 1;
}
// ===== 带锁的包装函数 =====
async withLock<T>(
resource: string,
fn: () => Promise<T>,
ttlMs = 10000
): Promise<T> {
const lockValue = await this.acquire(resource, ttlMs);
if (!lockValue) {
throw new Error(`Failed to acquire lock: ${resource}`);
}
try {
return await fn();
} finally {
await this.release(resource, lockValue);
}
}
}
// 使用示例
const lock = new DistributedLock(redis);
async function processPayment(orderId: string) {
await lock.withLock(`order:${orderId}`, async () => {
// 幂等处理:检查是否已处理
const processed = await redis.get(`processed:${orderId}`);
if (processed) {
console.log('Order already processed:', orderId);
return;
}
// 执行支付逻辑...
await doPayment(orderId);
// 标记已处理
await redis.setEx(`processed:${orderId}`, 86400, '1');
}, 30000); // 30秒超时
}7.3 实时排行榜系统
typescript
// ts/leaderboard.ts - 完整排行榜系统
class Leaderboard {
constructor(private redis: RedisClientType) {}
// ===== 提交分数 =====
async submitScore(playerId: string, score: number) {
await this.redis.zAdd('leaderboard:global', {
score,
value: playerId,
});
}
// ===== 获取排名 =====
async getRank(playerId: string): Promise<number | null> {
const rank = await this.redis.zRevRank('leaderboard:global', playerId);
return rank !== null ? rank + 1 : null;
}
// ===== 获取 Top N =====
async getTop(limit = 10): Promise<Array<{ rank: number; playerId: string; score: number }>> {
const results = await this.redis.zRangeWithScores(
'leaderboard:global',
0,
limit - 1,
{ REV: true }
);
return results.map((item, i) => ({
rank: i + 1,
playerId: item.value,
score: item.score,
}));
}
// ===== 获取指定玩家的周边排名 =====
async getPlayersAround(playerId: string, range = 5) {
const rank = await this.redis.zRevRank('leaderboard:global', playerId);
if (rank === null) return null;
const start = Math.max(0, rank - range);
const end = rank + range;
const results = await this.redis.zRangeWithScores(
'leaderboard:global',
start,
end,
{ REV: true }
);
return results.map((item, i) => ({
rank: start + i + 1,
playerId: item.value,
score: item.score,
}));
}
// ===== 分数变化通知(Redis 6.2+)=====
async getScoreChange(playerId: string): Promise<number | null> {
return await this.redis.zScore('leaderboard:global', playerId);
}
// ===== 移除玩家 =====
async removePlayer(playerId: string) {
await this.redis.zRem('leaderboard:global', playerId);
}
// ===== 清空排行榜 =====
async reset() {
await this.redis.del('leaderboard:global');
}
}
// ===== 多维度排行榜(天/周/月/年)=====
class MultiDimensionalLeaderboard {
constructor(
private redis: RedisClientType,
private leaderboard: Leaderboard
) {}
private getKey(period: 'daily' | 'weekly' | 'monthly' | 'alltime'): string {
const suffixes = {
daily: new Date().toISOString().slice(0, 10), // 2024-08-25
weekly: this.getWeekKey(),
monthly: new Date().toISOString().slice(0, 7), // 2024-08
alltime: 'all',
};
return `leaderboard:${period}:${suffixes[period]}`;
}
private getWeekKey(): string {
const now = new Date();
const startOfYear = new Date(now.getFullYear(), 0, 1);
const days = Math.floor((now.getTime() - startOfYear.getTime()) / 86400000);
const week = Math.ceil((days + startOfYear.getDay() + 1) / 7);
return `${now.getFullYear()}-W${week}`;
}
async submitScore(playerId: string, score: number, periods = ['daily', 'weekly', 'monthly', 'alltime'] as const) {
for (const period of periods) {
const key = this.getKey(period);
await this.redis.zIncrBy(key, score, playerId);
}
}
async getTop(period: 'daily' | 'weekly' | 'monthly' | 'alltime', limit = 10) {
const key = this.getKey(period);
const results = await this.redis.zRangeWithScores(key, 0, limit - 1, { REV: true });
return results.map((item, i) => ({
rank: i + 1,
playerId: item.value,
score: item.score,
}));
}
}7.4 限流系统
typescript
// ts/rate-limiter.ts - 生产级限流器
// ===== 滑动窗口限流(最精确)=====
class SlidingWindowRateLimiter {
constructor(private redis: RedisClientType) {}
async isAllowed(
key: string,
limit: number,
windowMs: number
): Promise<{ allowed: boolean; remaining: number; resetAt: number }> {
const now = Date.now();
const windowKey = `ratelimit:sliding:${key}`;
const windowStart = now - windowMs;
// Lua 脚本:原子操作
const script = `
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ${windowStart})
local count = redis.call('ZCARD', KEYS[1])
if count < tonumber(ARGV[1]) then
redis.call('ZADD', KEYS[1], ARGV[2], ARGV[2])
redis.call('PEXPIRE', KEYS[1], ARGV[3])
return {1, ${limit} - count - 1, ${windowMs}}
else
local oldest = redis.call('ZRANGE', KEYS[1], 0, 0, 'WITHSCORES')
local resetAt = 0
if #oldest > 0 then
resetAt = tonumber(oldest[2]) + ${windowMs}
end
return {0, 0, resetAt}
end
`;
const result = await this.redis.eval(script, {
keys: [windowKey],
arguments: [limit.toString(), now.toString(), windowMs.toString()],
});
return {
allowed: result[0] === 1,
remaining: result[1],
resetAt: result[2],
};
}
}
// ===== 令牌桶限流(允许突发)=====
class TokenBucketRateLimiter {
constructor(private redis: RedisClientType) {}
async consume(
key: string,
capacity: number,
refillRate: number // 每秒补充的令牌数
): Promise<{ allowed: boolean; tokensLeft: number }> {
const script = `
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refillRate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local bucket = redis.call('HMGET', key, 'tokens', 'lastRefill')
local tokens = tonumber(bucket[1]) or capacity
local lastRefill = tonumber(bucket[2]) or now
-- 补充令牌
local elapsed = now - lastRefill
local added = math.floor(elapsed * refillRate / 1000)
tokens = math.min(capacity, tokens + added)
lastRefill = now
if tokens >= 1 then
tokens = tokens - 1
redis.call('HMSET', key, 'tokens', tokens, 'lastRefill', lastRefill)
redis.call('EXPIRE', key, 3600)
return {1, tokens}
else
redis.call('HMSET', key, 'tokens', tokens, 'lastRefill', lastRefill)
redis.call('EXPIRE', key, 3600)
return {0, tokens}
end
`;
const result = await this.redis.eval(script, {
keys: [`ratelimit:token:${key}`],
arguments: [
capacity.toString(),
refillRate.toString(),
Date.now().toString(),
],
});
return {
allowed: result[0] === 1,
tokensLeft: result[1],
};
}
}
// 使用示例
const slidingLimiter = new SlidingWindowRateLimiter(redis);
const tokenLimiter = new TokenBucketRateLimiter(redis);
async function handleAPIRequest(req: Request) {
const userId = req.headers.get('X-User-ID') || 'anonymous';
const ip = req.socket.remoteAddress || 'unknown';
// 滑动窗口:100次/分钟
const result = await slidingLimiter.isAllowed(
`api:${userId}:${ip}`,
100,
60000
);
if (!result.allowed) {
return new Response('Too Many Requests', {
status: 429,
headers: {
'X-RateLimit-Limit': '100',
'X-RateLimit-Remaining': result.remaining.toString(),
'X-RateLimit-Reset': new Date(result.resetAt).toISOString(),
},
});
}
// 处理请求...
}八、安全与监控
8.1 安全配置
bash
# redis.conf 安全配置
bind 127.0.0.1 # 仅本地访问
protected-mode yes # 保护模式(无密码时仅本地连接)
requirepass <strong-password> # 强密码认证
rename-command FLUSHDB "" # 重命名危险命令(空=禁用)
rename-command FLUSHALL ""
rename-command CONFIG ""
rename-command SHUTDOWN ""
# 内存限制
maxmemory 4gb # 生产必设
maxmemory-policy allkeys-lru # 内存满时删除最少使用的键
# 慢查询日志
slowlog-log-slower-than 10000 # 记录 >10ms 的命令(微秒)
slowlog-max-len 128 # 最多保留128条
# 最大客户端连接数
maxclients 100008.2 监控命令
bash
# ===== 内存分析 =====
INFO memory | grep -E "used_memory_human|peak_memory_human|mem_fragmentation_ratio|instantaneous_ops_per_sec"
# ===== 客户端连接 =====
CLIENT LIST | grep -v "cmd=mclient" | wc -l # 活跃连接数
CLIENT KILL TYPE normal # 关闭普通客户端连接
# ===== 慢查询 =====
SLOWLOG GET 10 # 最近10条慢查询
SLOWLOG LEN # 慢查询队列长度
SLOWLOG RESET # 清空慢查询日志
# ===== 持久化状态 =====
INFO persistence | grep -E "rdb_|aof_"
# ===== 复制状态 =====
INFO replication | grep -E "role|connected_slaves|master_repl_offset"
# ===== Keyspace 统计 =====
INFO keyspace
# db0: keys=1000,expires=500,avg_ttl=3600000九、Redis 7 新特性与最佳实践
9.1 Redis 7 重要新特性
bash
# ===== 多线程 I/O(Redis 6)=====
# io-threads 4
# io-threads-do-reads yes
# ===== ACL v2(更细粒度权限)=====
ACL SETUSER reader ON >password ~cached:* -@all +get
ACL SETUSER writer ON >password ~* +@write -dangerous
# ===== 函数(Lua 脚本升级)=====
# Redis 7 函数库替代 Lua 脚本,支持更复杂的逻辑
# ===== 集群管理器改进 =====
# ===== Redis Stack(RedisSearch/RedisJSON/RedisGraph/RedisTimeSeries/RedisGears)=====
# RedisSearch: 全文搜索引擎
FT.CREATE idx:posts ON HASH PREFIX 1 "post:" SCHEMA title TEXT WEIGHT 5 body TEXT
FT.SEARCH idx:posts "redis performance"9.2 最佳实践总结
┌────────────────────────────────────────────────────────────┐
│ Redis 开发与运维最佳实践 │
├────────────────────────────────────────────────────────────┤
│ │
│ 🔑 键设计 │
│ ├─ 格式: 业务:实体:ID[:子属性] │
│ │ 例: user:1001:profile, order:abc123:items │
│ ├─ 避免过长的键(消耗内存) │
│ ├─ 避免过短的值(语义不清晰) │
│ └─ 控制 key 数量(Redis 7+ 支持 2^32 个 key) │
│ │
│ 💾 内存管理 │
│ ├─ 设置 maxmemory 并配置淘汰策略 │
│ ├─ 热点数据缓存(设置合理 TTL) │
│ ├─ 生产禁用 KEYS 命令,使用 SCAN 代替 │
│ └─ 定期监控 used_memory_human 和 mem_fragmentation_ratio │
│ │
│ ⚡ 性能 │
│ ├─ 批量操作用 MGET/MSET/ZMADD,代替循环 GET/SET │
│ ├─ 管道(Pipeline)批量发送命令,减少 RTT │
│ ├─ Lua 脚本代替多次往返(原子性 + 性能) │
│ └─ 避免大键(>10MB),考虑拆分 │
│ │
│ 🛡️ 安全 │
│ ├─ 生产环境必须设置密码 │
│ ├─ 禁止公网暴露 │
│ ├─ 重命名危险命令 │
│ └─ 使用最小权限用户运行 │
│ │
│ 🔄 持久化与复制 │
│ ├─ 生产推荐:RDB + AOF(everysec)+ 混合持久化 │
│ ├─ 主从异步复制,务必了解可能的数据延迟 │
│ ├─ 定期备份 RDB 和 AOF 文件 │
│ └─ 从节点可用于读操作分流 │
│ │
└────────────────────────────────────────────────────────────┘十、总结
Redis 远不止一个"缓存"工具——它是一个功能完备的内存数据平台。从 String 的原子计数到 ZSet 的排行榜,从 List 的消息队列到 Stream 的事件流,从 GEO 的地理位置到 HyperLogLog 的海量统计,Redis 用统一的内存存储和极致的性能,覆盖了现代应用开发中几乎所有高性能数据场景。
掌握 Redis 的关键,在于理解它的数据结构选择和数据一致性问题。合理的缓存策略、正确的分布式锁实现、精确的限流算法——这些都需要对 Redis 能力边界有深刻理解。
当你的应用需要亚毫秒级响应、需要处理突发流量、需要实现实时交互时,Redis 就是那把不可或缺的钥匙。
本文由小虾子 🦐 撰写