入门级:Redis基础与核心概念
1. Redis简介与价值定位
什么是Redis:Redis(Remote Dictionary Server)是一个开源的内存数据存储系统,支持多种数据结构,可作为数据库、缓存和消息中间件使用。
为什么选择Redis:
性能卓越:基于内存操作,读速达110000次/秒,写速达81000次/秒功能丰富:支持多种数据结构和高级特性高可用性:提供完整的集群方案生态完善:客户端支持几乎所有编程语言
典型应用场景:
缓存系统(减轻数据库压力)计数器(文章阅读量、商品库存)消息队列(异步通信)排行榜(游戏积分、商品销量)实时系统(秒杀、直播在线人数)
2. 环境搭建与基础配置
2.1 安装Redis(Linux环境)
# Ubuntu/Debian
sudo apt update && sudo apt install redis-server
# CentOS/RHEL
sudo yum install redis
# 启动服务
sudo systemctl start redis
sudo systemctl enable redis # 设置开机自启
# 验证安装
redis-cli ping # 应返回 PONG
2.2 核心配置解析
配置文件路径:/etc/redis/redis.conf
必知配置项:
# 网络配置
bind 0.0.0.0 # 绑定地址,生产环境建议指定具体IP
port 6379 # 端口号(默认6379,源自作者antirez的名字缩写)
# 安全配置
requirepass yourpassword # 设置访问密码
rename-command FLUSHALL "" # 重命名危险命令,增强安全性
# 持久化配置
save 900 1 # 900秒内至少1个key变化触发RDB快照
appendonly yes # 开启AOF持久化
# 内存管理
maxmemory 2gb # 最大使用内存
maxmemory-policy allkeys-lru # 内存淘汰策略
配置生效方式:
# 修改配置后重启
sudo systemctl restart redis
# 动态修改配置(无需重启,重启后失效)
redis-cli config set maxmemory 4gb
3. 基础数据结构与操作
3.1 字符串(String)
定义:二进制安全的字符串,可存储文本、数字和二进制数据(最大512MB)
核心命令:
SET name "Redis" # 设置键值对
GET name # 获取值 → "Redis"
INCR counter # 数值自增 → 1
DECR counter # 数值自减 → 0
APPEND name " Tutorial" # 追加内容 → "Redis Tutorial"
STRLEN name # 获取长度 → 14
应用场景:
缓存用户信息:SET user:1001 '{"name":"John","age":30}'计数器:INCR article:1001:views分布式锁:SET lock:order NX PX 30000
3.2 哈希(Hash)
定义:键值对集合,适合存储对象类型数据
核心命令:
HSET user:1001 name "John" age 30 # 设置字段
HGET user:1001 name # 获取字段 → "John"
HGETALL user:1001 # 获取所有字段 → name "John" age "30"
HDEL user:1001 age # 删除字段
HLEN user:1001 # 字段数量 → 1
应用场景:
存储用户资料、商品信息等对象数据配置参数管理购物车实现
3.3 列表(List)
定义:有序字符串列表,支持两端插入和弹出操作
核心命令:
LPUSH fruits apple banana # 左侧插入 → [banana, apple]
RPUSH fruits orange # 右侧插入 → [banana, apple, orange]
LPOP fruits # 左侧弹出 → banana
LRANGE fruits 0 -1 # 获取所有元素 → [apple, orange]
LLEN fruits # 列表长度 → 2
应用场景:
消息队列:LPUSH+RPOP或RPUSH+LPOP最新消息列表:LPUSH+LRANGE栈(LPUSH+LPOP)和队列(LPUSH+RPOP)
3.4 集合(Set)
定义:无序唯一元素集合,支持集合运算
核心命令:
SADD tags programming redis database # 添加元素
SMEMBERS tags # 获取所有元素
SISMEMBER tags redis # 判断元素是否存在 → 1
SINTER set1 set2 # 交集运算
SUNION set1 set2 # 并集运算
SDIFF set1 set2 # 差集运算
应用场景:
标签系统:文章标签、用户兴趣标签好友关系:共同关注、好友推荐去重操作:UV统计基础
3.5 有序集合(Sorted Set)
定义:有序唯一元素集合,每个元素关联一个分数(score)
核心命令:
ZADD ranking 95 "Alice" 88 "Bob" 92 "Charlie" # 添加元素及分数
ZRANGE ranking 0 -1 WITHSCORES # 升序获取所有元素
ZREVRANGE ranking 0 1 # 降序获取前两名
ZSCORE ranking "Alice" # 获取分数 → 95
ZINCRBY ranking 3 "Bob" # 增加分数 → 91
应用场景:
排行榜:游戏积分、商品销量延迟队列:按时间戳作为score范围查询:获取分数区间的元素
4. 客户端使用与基本操作
4.1 命令行客户端
redis-cli -h host -p port -a password # 连接远程Redis
redis-cli --raw # 显示中文不乱码
redis-cli info memory # 获取内存信息
4.2 Python客户端示例
import redis
# 建立连接
r = redis.Redis(
host='localhost',
port=6379,
password='yourpassword',
decode_responses=True # 自动解码为字符串
)
# 字符串操作
r.set('name', 'Redis')
print(r.get('name')) # 输出: Redis
# 哈希操作
r.hset('user:1001', mapping={'name': 'John', 'age': '30'})
print(r.hgetall('user:1001')) # 输出: {'name': 'John', 'age': '30'}
# 列表操作
r.lpush('fruits', 'apple', 'banana')
print(r.lrange('fruits', 0, -1)) # 输出: ['banana', 'apple']
5. 入门级必备知识与实践
5.1 键命名规范
[业务]:[模块]:[对象]:[id]
示例:
user:profile:1001(用户资料)article:views:2023(文章阅读量)order:status:pending(待处理订单)
5.2 常用命令速查
功能命令示例键管理KEYS pattern EXISTS key DEL key EXPIRE key seconds数据操作SET GET HSET HGET LPUSH LRANGE SADD ZADD服务器信息INFO CONFIG GET DBSIZE FLUSHDB5.3 入门实践任务
搭建本地Redis环境使用5种基本数据结构实现一个简单的博客系统缓存层实现文章阅读量计数器和排行榜功能设置键过期时间,观察过期效果
提高级:核心机制与原理深入
1. 数据结构进阶与内部编码
1.1 字符串(String)的三种编码
Redis会根据字符串内容自动选择合适的编码:
int:8个字节的长整型(如SET num 123)embstr:短字符串(<=44字节),内存连续分配raw:长字符串(>44字节),内存分开分配
编码转换:
SET num 123 # int编码
APPEND num "abc" # 转为raw编码
设计原因:
针对不同长度字符串优化内存使用embstr适合短字符串,减少内存碎片int编码直接存储数值,避免字符串解析开销
1.2 哈希(Hash)的两种编码
ziplist(压缩列表):字段少且小的时候使用hashtable(哈希表):字段多或大的时候使用
转换条件:
hash-max-ziplist-entries 512 # 字段数超过512转为hashtable
hash-max-ziplist-value 64 # 字段值超过64字节转为hashtable
设计原因:
ziplist内存紧凑,适合小对象存储hashtable支持O(1)查找,适合大对象
1.3 列表(List)的两种编码
ziplist(压缩列表):元素少且小的时候使用linkedlist(双向链表):元素多或大的时候使用
设计原因:
ziplist内存利用率高,适合小列表linkedlist支持高效的两端操作,适合大列表
2. 持久化机制深度解析
2.1 RDB持久化
原理:在指定时间间隔内生成数据集的快照(二进制文件)
工作流程:
执行SAVE命令,主进程直接生成RDB文件(阻塞)执行BGSAVE命令,主进程fork子进程生成RDB文件(非阻塞)子进程遍历内存数据,写入临时文件完成后替换旧RDB文件
优缺点分析:
优点缺点恢复速度快可能丢失最后一次快照后的所有数据文件体积小生成快照时可能短暂阻塞(BGSAVE的fork操作)适合备份不适合实时数据备份配置详解:
save 900 1 # 900秒内至少1个key变化触发BGSAVE
save 300 10 # 300秒内至少10个key变化触发BGSAVE
save 60 10000 # 60秒内至少10000个key变化触发BGSAVE
dbfilename dump.rdb # 文件名
dir /var/lib/redis # 存储路径
rdbcompression yes # 是否压缩RDB文件(默认yes)
rdbchecksum yes # 是否校验RDB文件(默认yes)
2.2 AOF持久化
原理:记录每次写操作到日志文件,重启时重新执行命令恢复数据
三种同步策略:
appendfsync always # 每次写操作都同步(最安全,性能最差)
appendfsync everysec # 每秒同步一次(平衡安全与性能,默认)
appendfsync no # 由操作系统决定同步时机(性能最好,最不安全)
AOF重写机制:
目的:压缩AOF文件,去除冗余命令触发:BGREWRITEAOF命令或自动触发过程:fork子进程,遍历内存数据生成新AOF文件,期间新命令写入缓冲区
优缺点分析:
优点缺点数据安全性高文件体积大支持多种同步策略恢复速度慢日志可读性强写性能略低于RDB2.3 混合持久化(Redis 4.0+)
原理:AOF文件包含RDB头部和AOF增量命令
RDB部分:快速恢复数据AOF部分:记录最近的增量命令
配置:
aof-use-rdb-preamble yes # 开启混合持久化
设计原因:
结合RDB恢复速度快和AOF数据安全的优点解决纯AOF恢复慢的问题
3. 过期策略与内存淘汰
3.1 过期键的三种删除策略
惰性删除:访问时才检查过期
优点:节省CPU资源缺点:可能浪费内存(长期不访问的过期键)
定期删除:每隔一段时间检查部分过期键
优点:平衡CPU和内存缺点:难以确定检查频率和时长
内存淘汰:内存不足时触发淘汰
优点:保证Redis不会因内存不足崩溃缺点:需要选择合适的淘汰策略
Redis实际实现:
主:惰性删除 + 定期删除辅:内存淘汰机制
3.2 内存淘汰策略详解
配置:
maxmemory-policy allkeys-lru # 设置内存淘汰策略
常用策略对比:
策略适用场景工作原理allkeys-lru通用缓存场景淘汰所有键中最近最少使用的volatile-lru部分键设置过期时间仅淘汰设置过期时间的键中最近最少使用的allkeys-lfu访问频率差异大的场景淘汰所有键中最不经常使用的volatile-lfu部分键设置过期时间且访问频率差异大仅淘汰设置过期时间的键中最不经常使用的volatile-ttl键有明确过期时间淘汰剩余时间最短的键noeviction数据不能丢失的场景不淘汰,返回错误(默认)LRU vs LFU:
LRU(Least Recently Used):最近最少使用,基于访问时间LFU(Least Frequently Used):最不经常使用,基于访问频率LFU更能反映键的长期访问模式,适合缓存热点数据
3.3 内存优化实践
大key优化:
识别大key:redis-cli --bigkeys拆分大key:将一个大Hash拆分为多个小Hash避免存储大对象:大图片、长文本等应存储到对象存储
内存碎片优化:
INFO memory # 查看mem_fragmentation_ratio(内存碎片率)
# 正常范围1.0-1.5,大于1.5表示碎片严重
CONFIG SET activedefrag yes # 开启自动碎片整理(Redis 4.0+)
4. 事务与Lua脚本
4.1 Redis事务机制
特性:
原子性:事务中的命令要么全部执行,要么全部不执行隔离性:事务执行期间,其他客户端无法插入命令
命令:
MULTI # 开始事务
SET a 1 # 命令入队
SET b 2 # 命令入队
EXEC # 执行事务
# DISCARD # 取消事务
错误处理:
语法错误:整个事务取消运行错误:仅错误命令失败,其他命令继续执行
设计局限:
不支持回滚(Redis认为事务错误多是编程错误,应在开发阶段避免)没有隔离级别概念(事务执行是串行的)
4.2 Lua脚本高级应用
优势:
原子性执行:脚本中的所有命令作为一个整体执行减少网络开销:一次发送多个命令自定义命令:组合现有命令实现复杂逻辑
基本使用:
# 原子操作:如果key存在则自增,否则设置初始值
EVAL "if redis.call('exists', KEYS[1]) == 1 then
return redis.call('incr', KEYS[1])
else
return redis.call('set', KEYS[1], ARGV[1])
end"
1 counter 1 # 1个KEYS,counter是键名,1是初始值
脚本缓存:
# 加载脚本
SCRIPT LOAD "return redis.call('get', KEYS[1])"
# 返回SHA1校验和:"a42059b356c875f0717db19a51f6aaca9ae659ea"
# 执行缓存的脚本
EVALSHA a42059b356c875f0717db19a51f6aaca9ae659ea 1 name
应用场景:
分布式锁实现复杂计数器数据聚合操作
5. 提高级必备知识与实践
5.1 性能监控基础
redis-cli info stats # 获取统计信息
redis-cli info memory # 获取内存信息
redis-cli info replication # 获取复制信息
# 慢查询监控
CONFIG SET slowlog-log-slower-than 10000 # 记录执行时间>10ms的命令
SLOWLOG GET 10 # 获取最近10条慢查询
5.2 常见问题处理
连接数耗尽:
maxclients 10000 # 增加最大连接数
内存溢出:
maxmemory 4gb # 增加内存限制
maxmemory-policy allkeys-lru # 选择合适的淘汰策略
命令阻塞:
避免使用KEYS *,改用SCAN大集合操作使用分批处理
5.3 提高级实践任务
配置RDB+AOF混合持久化,测试数据恢复使用Lua脚本实现一个带过期时间的分布式计数器分析Redis内存使用情况,优化内存碎片实现基于LRU和LFU策略的缓存,并对比效果
进阶级:高可用架构与性能优化
1. 主从复制深度实践
1.1 复制原理与流程
核心概念:
主节点(Master):接收写请求,同步数据到从节点从节点(Slave):仅接收读请求,复制主节点数据
复制流程:
建立连接:从节点发送SYNC命令全量同步:
主节点执行BGSAVE生成RDB文件主节点发送RDB文件给从节点从节点加载RDB文件
增量同步:
主节点将缓冲区的写命令发送给从节点从节点执行这些命令,保持数据同步
复制偏移量:
主从节点各自维护偏移量,确保数据同步进度INFO replication查看master_repl_offset和slave_repl_offset
1.2 复制拓扑结构
一主一从:简单架构,适合读负载不高的场景
Master <-- Slave
一主多从:主节点写入,多从节点分担读负载
Master <-- Slave1
<-- Slave2
<-- Slave3
树状复制:从节点下再挂从节点,减轻主节点压力
Master <-- Slave1 <-- Slave1-1
<-- Slave2 <-- Slave2-1
<-- Slave2-2
配置示例:
# 从节点配置
replicaof master_ip master_port # 指定主节点地址和端口
masterauth password # 主节点密码(如果有)
replica-read-only yes # 从节点只读(默认yes)
1.3 复制优化与问题处理
复制积压缓冲区:
repl-backlog-size 1mb # 缓冲区大小,越大越能容忍网络中断
repl-backlog-ttl 3600 # 从节点断开后缓冲区保留时间(秒)
数据延迟问题:
原因:网络延迟、主节点负载高、从节点数量多解决方案:
优化网络环境减少从节点数量或使用树状结构配置repl-disable-tcp-nodelay no(减少延迟但增加带宽)
脑裂问题处理:
min-replicas-to-write 2 # 至少需要2个从节点正常同步
min-replicas-max-lag 10 # 从节点延迟不能超过10秒
当不满足条件时,主节点停止接受写请求,避免数据不一致
2. 哨兵模式(Sentinel)高可用方案
2.1 哨兵的核心功能
监控(Monitoring):持续检查主从节点是否正常运行通知(Notification):当节点故障时,通过API通知管理员或其他应用自动故障转移(Auto Failover):主节点故障时,自动将从节点提升为主节点配置提供者(Configuration Provider):为客户端提供当前主节点地址
2.2 哨兵架构与配置
最小化哨兵架构:
Sentinel1 <--> Sentinel2 <--> Sentinel3
\ | /
\ | /
Master <--> Slave
哨兵配置文件(sentinel.conf):
# 监控主节点,名称为mymaster,2个哨兵同意则认为主节点下线
sentinel monitor mymaster 127.0.0.1 6379 2
# 30秒未响应则认为节点主观下线
sentinel down-after-milliseconds mymaster 30000
# 故障转移超时时间180秒
sentinel failover-timeout mymaster 180000
# 故障转移后,最多1个从节点同时同步新主节点
sentinel parallel-syncs mymaster 1
# 主节点密码
sentinel auth-pass mymaster yourpassword
启动哨兵:
redis-sentinel /path/to/sentinel.conf
# 或
redis-server /path/to/sentinel.conf --sentinel
2.3 故障转移流程详解
主观下线(SDOWN):单个哨兵认为主节点不可用客观下线(ODOWN):多数哨兵(quorum配置值)同意主节点不可用选举领导者哨兵:
发现客观下线的哨兵向其他哨兵发送投票请求其他哨兵在本轮选举中只能投票给一个候选者获得多数票的哨兵成为领导者
选择新主节点:
过滤不健康的从节点选择优先级最高的从节点(replica-priority)优先级相同则选择复制偏移量最大的从节点仍相同则选择运行ID最小的从节点
提升主节点:领导者哨兵向选中的从节点发送SLAVEOF NO ONE命令重新配置:其他从节点指向新主节点,原主节点恢复后作为从节点
3. Redis集群(Cluster)分布式方案
3.1 集群核心概念
数据分片:将数据分布在16384个槽位(slot),每个主节点负责一部分槽位主从复制:每个主节点有1个或多个从节点,提供高可用去中心化:每个节点都知道整个集群状态,客户端可连接任意节点
槽位计算公式:
SLOT = CRC16(key) % 16384
3.2 集群部署与配置
最小化集群架构(3主3从):
Master1(7000) <--> Slave1(7003)
Master2(7001) <--> Slave2(7004)
Master3(7002) <--> Slave3(7005)
节点配置(每个节点):
port 7000
cluster-enabled yes # 开启集群模式
cluster-config-file nodes-7000.conf # 集群配置文件
cluster-node-timeout 5000 # 节点超时时间(毫秒)
appendonly yes # 开启AOF持久化
创建集群:
redis-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1 # 每个主节点有1个从节点
3.3 集群数据路由与迁移
MOVED重定向:
当客户端访问的槽位不在当前节点时,返回重定向信息:
MOVED 1234 127.0.0.1:7001
客户端收到后,会更新本地槽位映射并重新发送请求
ASK重定向:
槽位迁移过程中,临时重定向:
ASK 1234 127.0.0.1:7001
槽位迁移命令:
# 迁移单个槽位
redis-cli --cluster reshard 127.0.0.1:7000 \
--cluster-from
--cluster-to
--cluster-slots 1
# 平衡槽位分布
redis-cli --cluster rebalance 127.0.0.1:7000
3.4 集群扩容与缩容
扩容步骤:
启动新节点(主节点和从节点)将新节点加入集群:redis-cli --cluster add-node分配槽位给新主节点:redis-cli --cluster reshard为新主节点分配从节点:redis-cli --cluster add-node --cluster-slave --cluster-master-id
缩容步骤:
将待下线节点的槽位迁移到其他节点确认槽位已全部迁移:redis-cli --cluster check移除节点:redis-cli --cluster del-node
4. 性能优化与最佳实践
4.1 网络优化
TCP参数调优:
tcp-backlog 511 # TCP连接队列大小
tcp-keepalive 300 # TCP保活时间(秒)
内核参数优化(/etc/sysctl.conf):
net.core.somaxconn = 1024 # 系统级TCP连接队列大小
net.ipv4.tcp_max_syn_backlog = 1024 # SYN队列大小
net.ipv4.tcp_tw_recycle = 1 # 快速回收TIME_WAIT连接
net.ipv4.tcp_tw_reuse = 1 # 复用TIME_WAIT连接
4.2 内存优化深度实践
内存分配器选择:
# Redis默认使用jemalloc
# malloc-lib /usr/local/lib/libtcmalloc.so # 使用tcmalloc
大key处理策略:
识别大key:redis-cli --bigkeys拆分大Hash:将一个大Hash拆分为多个小Hashdef get_hash_field(big_key, field, split_count=100):
sub_key = f"{big_key}:{hash(field) % split_count}"
return redis.hget(sub_key, field)
大List分段:按时间或ID范围拆分为多个小List
内存碎片优化:
activedefrag yes # 开启自动碎片整理
active-defrag-ignore-bytes 100mb # 碎片达到100MB开始整理
active-defrag-threshold-lower 10 # 碎片率超过10%开始整理
active-defrag-threshold-upper 100 # 碎片率超过100%积极整理
4.3 高并发场景优化
批量操作优化:
使用Pipeline减少网络往返:
pipe = redis.pipeline()
for key, value in data.items():
pipe.set(key, value)
pipe.execute()
使用MSET、HMSET等批量命令替代循环操作
读写分离:
主节点处理写请求,从节点处理读请求客户端实现读写分离逻辑:def get_data(key):
# 读请求发送到从节点
return slave_redis.get(key)
def set_data(key, value):
# 写请求发送到主节点
return master_redis.set(key, value)
热点数据处理:
热点数据永不过期本地缓存热点数据拆分热点key:将一个热点key拆分为多个子key
5. 进阶级必备知识与实践
5.1 分布式锁高级实现
Redlock算法:在多个独立的Redis节点上获取锁,提高可靠性
def redlock_acquire(lock_key, value, ttl=30000, retry_count=3):
"""Redlock算法实现"""
for _ in range(retry_count):
start_time = time.time()
acquired_count = 0
# 在所有节点获取锁
for node in redis_nodes:
if node.set(lock_key, value, nx=True, ex=ttl//1000):
acquired_count += 1
# 大多数节点获取成功,且总耗时小于锁超时时间
if (acquired_count > len(redis_nodes)/2 and
(time.time() - start_time) * 1000 < ttl):
return True
# 获取失败,释放已获取的锁
for node in redis_nodes:
node.delete(lock_key)
time.sleep(0.1)
return False
5.2 缓存设计模式
Cache-Aside Pattern(旁路缓存):应用先读缓存,未命中则读数据库并更新缓存Write-Through Pattern(写透缓存):写操作同时更新缓存和数据库Write-Behind Pattern(写回缓存):先更新缓存,异步更新数据库Cache Stampede Protection(缓存击穿保护):互斥锁或热点数据永不过期
5.3 进阶级实践任务
搭建3主3从Redis集群,实现数据分片和故障转移开发一个支持读写分离和故障自动切换的Redis客户端实现基于Redlock算法的分布式锁,并测试其可靠性对一个高并发场景(如秒杀)进行Redis缓存设计和性能优化
大师级:底层原理与源码分析
1. Redis底层数据结构实现
1.1 简单动态字符串(SDS)
Redis的字符串不是C语言原生字符串,而是自定义的SDS结构:
struct sdshdr {
int len; // 已使用长度
int free; // 未使用长度
char buf[]; // 字节数组
};
与C字符串的对比优势:
特性C字符串SDS获取长度O(n)O(1)缓冲区溢出可能发生杜绝修改性能可能需要多次内存重分配预分配策略减少重分配二进制安全否(以’\0’结尾)是扩容策略:
当len < 1MB时,扩容为原来的2倍当len >= 1MB时,每次增加1MB
源码解析:
// sds.c
sds sdscat(sds s, const char *t) {
return sdscatlen(s, t, strlen(t));
}
sds sdscatlen(sds s, const void *t, size_t len) {
size_t curlen = sdslen(s);
// 检查空间是否足够,不足则扩容
s = sdsMakeRoomFor(s, len);
if (s == NULL) return NULL;
// 复制数据
memcpy(s + curlen, t, len);
// 更新len和free
sdssetlen(s, curlen + len);
s[curlen + len] = '\0';
return s;
}
1.2 字典(Dictionary)
Redis中的数据库和Hash类型都基于字典实现:
typedef struct dict {
dictType *type; // 类型特定函数
void *privdata; // 私有数据
dictht ht[2]; // 两个哈希表(用于rehash)
long rehashidx; // rehash索引,-1表示未进行
unsigned long iterators; // 当前正在使用的迭代器数量
} dict;
typedef struct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表大小
unsigned long sizemask; // 掩码,用于计算索引
unsigned long used; // 已使用节点数量
} dictht;
渐进式rehash过程:
为ht[1]分配更大的空间(通常是ht[0]的2倍)设置rehashidx=0,开始rehash每次对字典进行增删改查时,迁移ht[0]的一个桶到ht[1]迁移完成后,释放ht[0],将ht[1]设置为新的ht[0]
哈希冲突解决:链地址法,每个哈希节点有一个next指针
源码解析:
// dict.c
int dictAdd(dict *d, void *key, void *val) {
dictEntry *entry = dictAddRaw(d, key, NULL);
if (!entry) return DICT_ERR;
dictSetVal(d, entry, val);
return DICT_OK;
}
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing) {
int index;
dictEntry *entry;
dictht *ht;
// 如果正在rehash,先迁移一个桶
if (dictIsRehashing(d)) _dictRehashStep(d);
// 计算哈希值和索引
if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
return NULL;
// 如果正在rehash,使用ht[1],否则使用ht[0]
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
// 创建新节点并添加到哈希表
entry = zmalloc(sizeof(*entry));
entry->next = ht->table[index];
ht->table[index] = entry;
ht->used++;
// 设置键
dictSetKey(d, entry, key);
return entry;
}
2. Redis事件驱动模型
2.1 事件循环(Event Loop)
Redis基于Reactor模式实现了高效的事件驱动模型:
// ae.c
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
// 如果有需要在事件循环中执行的函数,先执行
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
// 处理文件事件和时间事件
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}
2.2 文件事件处理器
Redis使用I/O多路复用技术同时监听多个文件描述符:
// ae.c
int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
int processed = 0, numevents;
// 如果有时间事件,计算最近的时间事件距离现在的毫秒数
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
shortest = aeSearchNearestTimer(eventLoop);
// 调用I/O多路复用API(select/poll/epoll/kqueue)
numevents = aeApiPoll(eventLoop, tvp);
// 处理文件事件
for (j = 0; j < numevents; j++) {
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
// ... 处理读/写事件
processed++;
}
// 处理时间事件
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop);
return processed;
}
2.3 多线程I/O模型(Redis 6.0+)
Redis 6.0引入了多线程处理网络I/O,但命令执行仍为单线程:
主线程:命令解析、执行、响应发送
I/O线程:网络读写、协议解析
配置:
io-threads 4 # I/O线程数量
io-threads-do-reads yes # 开启读多线程
优势:
解决网络I/O瓶颈,提高吞吐量保持单线程命令执行的原子性和简单性
3. 持久化底层实现
3.1 RDB文件结构
RDB文件是二进制格式,结构如下:
[REDIS] [db-version] [databases] [EOF] [checksum]
数据库部分结构:
SELECTDB $dbnumber:切换数据库KEYVALUE_PAIRS:键值对数据EXPIRETIME_MS $timestamp:过期时间
RDB创建过程:
// rdb.c
int rdbSave(char *filename) {
FILE *fp;
rio rdb;
int error;
fp = fopen(filename,"w");
if (!fp) return REDIS_ERR;
rioInitWithFile(&rdb,fp);
if (rdbSaveRio(&rdb,&error) == REDIS_ERR) {
fclose(fp);
unlink(filename);
return REDIS_ERR;
}
// ... 关闭文件和其他处理
return REDIS_OK;
}
3.2 AOF重写过程
AOF重写不是读取旧AOF文件,而是直接遍历内存数据生成新AOF文件:
// aof.c
int rewriteAppendOnlyFileBackground(void) {
pid_t childpid;
long long start;
start = ustime();
if ((childpid = fork()) == 0) {
// 子进程:生成新AOF文件
char tmpfile[256];
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int)getpid());
if (rewriteAppendOnlyFile(tmpfile) == REDIS_OK) {
// 重写成功,替换旧AOF文件
// ...
}
exit(0);
} else {
// 父进程:继续处理命令,将新命令写入aof_rewrite_buf
// ...
}
return REDIS_OK;
}
4. 集群数据一致性与冲突解决
4.1 槽位迁移原子性保证
槽位迁移通过MIGRATE命令实现,确保单个键迁移的原子性:
MIGRATE target_host target_port key 0 timeout COPY REPLACE
迁移过程:
源节点标记槽位为MIGRATING状态目标节点标记槽位为IMPORTING状态源节点对每个键执行DUMP命令序列化源节点发送RESTORE命令到目标节点目标节点反序列化并保存键源节点删除键并返回成功
4.2 集群配置纪元(Config Epoch)
每个主节点有一个唯一的配置纪元,用于解决冲突:
新主节点的纪元比所有旧纪元大槽位映射信息附带纪元,高纪元覆盖低纪元
故障转移时的纪元更新:
从节点晋升为主节点时,生成新的纪元(当前最大纪元+1)新主节点广播包含新纪元的槽位信息其他节点更新本地槽位映射
5. 大师级必备知识与实践
5.1 源码阅读指南
推荐阅读顺序:
sds.h/sds.c:简单动态字符串dict.h/dict.c:字典实现redis.h/redis.c:核心数据结构和命令处理ae.h/ae.c:事件驱动模型rdb.c/aof.c:持久化实现replication.c:复制实现sentinel.c:哨兵实现cluster.c:集群实现
调试Redis:
# 编译带调试信息的Redis
make CFLAGS="-g -O0"
# 使用gdb调试
gdb redis-server
(gdb) break main
(gdb) run --port 6379
5.2 Redis性能调优终极指南
CPU优化:
绑定CPU核心:taskset -c 0,1 redis-server禁用超线程:在BIOS中禁用或通过操作系统配置优化内存分配器:jemalloc vs tcmalloc
内存优化:
启用透明大页(THP):谨慎使用,可能增加延迟内存碎片率控制:mem_fragmentation_ratio < 1.5大页内存配置:echo never > /sys/kernel/mm/transparent_hugepage/enabled
网络优化:
调整TCP缓冲区大小:net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
使用Unix域套接字:减少网络栈开销
5.3 大师级实践任务
阅读Redis源码中SDS和字典的实现,理解其设计思想分析Redis事件循环模型,理解I/O多路复用的实现实现一个简单的Redis模块,扩展Redis功能针对特定场景(如高频写入)修改Redis源码并优化性能设计并实现一个基于Redis的分布式限流器,支持集群环境