1. Redis介绍
Redis(Remote Dictionary Server)是一款开源的内存数据结构存储系统,由Salvatore Sanfilippo于2009年开发。它本质上是一个键值对存储数据库,但与传统的键值存储不同,Redis支持丰富的数据结构,使其在各种应用场景中具有极高的灵活性。
Redis的主要特点包括:
- 高性能:数据存储在内存中,读写速度极快,官方数据显示可达到10万次/秒以上的读写性能
- 丰富的数据结构:支持字符串、哈希、列表、集合、有序集合等多种数据结构
- 持久化:提供RDB快照和AOF日志两种持久化方式,保证数据安全
- 高可用性:支持主从复制、哨兵模式和集群模式
- 扩展性强:支持分片处理大规模数据,可通过模块系统扩展功能
- 多语言支持:几乎所有主流编程语言都有Redis客户端库
Redis常用于以下场景:
- 缓存系统:减轻数据库压力,加速数据访问
- 会话存储:存储用户会话信息
- 排行榜/计数器:利用有序集合实现游戏排行榜等
- 消息队列:使用列表结构实现简单的消息队列
- 实时数据分析:处理实时事件流
由于其高性能和灵活的数据结构,Redis已成为现代Web应用中不可或缺的组件之一。
2. Redis环境配置
2.1 Windows安装Redies
可参考https://blog.csdn.net/weixin_44893902/article/details/123087435
Redis官网不提供Windows版本,但有很多三方打包的Redis服务
给出以下两个Redis For Windows项目(任选其一下载即可)
https://github.com/tporadowski/redis/releases
https://github.com/redis-windows/redis-windows/releases
下载Release的压缩包并解压后,在Redis根目录下使用CMD命名启动服务:
建议将Redis根目录添加到系统变量Path中,以便全局启动Redis服务(不用指定在Redis根目录下)
redis-server.exe redis.windows.conf
Redis服务需要保持运行,启动服务的CMD窗口不能关闭,可另外启动CMD窗口访问Redis:
使用Redis根目录的客户端访问本机地址以及Redis服务默认端口号6379
redis-cli.exe -h 127.0.0.1 -p 6379
可以访问目的地址端口号,即说明连接成功
2.2 Linux安装Redis
-
Ubuntu/Debian
使用APT包管理器安装
# 更新软件包列表 sudo apt update # 安装Redis服务器 sudo apt install redis-server # 安装完成后检查状态 sudo systemctl status redis-server
使用源码编译安装
# 安装编译依赖 sudo apt update sudo apt install build-essential tcl # 下载Redis源码 wget http://download.redis.io/releases/redis-7.0.12.tar.gz tar xzf redis-7.0.12.tar.gz cd redis-7.0.12 # 编译源码 make # 测试编译结果 make test # 安装到系统 sudo make install
-
CentOS/RHEL/Fedora
使用YUM/DNF安装
# CentOS/RHEL 7/8 sudo yum install epel-release sudo yum install redis # CentOS/RHEL 8/9 或 Fedora sudo dnf install redis # 启动Redis服务 sudo systemctl start redis
使用源码编译安装
# 安装编译工具 sudo yum groupinstall "Development Tools" sudo yum install tcl # 下载和编译Redis wget http://download.redis.io/releases/redis-7.0.12.tar.gz tar xzf redis-7.0.12.tar.gz cd redis-7.0.12 make make test sudo make install
配置与运行服务
注意Ubuntu/Debian中服务名为redis-server
,而CentOS/RHEL中为redis
,针对性使用
手动运行服务
# 使用默认配置启动
redis-server
# 使用指定配置文件启动(配置文件路径可能是这个,也可由此修改配置文件)
redis-server /etc/redis/redis.conf
# 启动后端服务
redis-server /etc/redis/redis.conf --daemonize yes
也可设置开机自启动
# 启动Redis服务
sudo systemctl start redis-server # Ubuntu/Debian
# 设置开机自启动
sudo systemctl enable redis-server
# 查看服务状态
sudo systemctl status redis-server
启动服务后能连接上Redis客户端即说明服务正常:
输入redis-cli
出现redis 127.0.0.1:6379>
3. Redis基本数据类型
3.1 字符串(String)
字符串是Redis最基本的数据类型,可以包含任意数据,如文本、序列化对象或二进制数据。最大容量为512MB。
常用操作:
SET key value
:设置键值对GET key
:获取值INCR key
:原子自增(值必须为整数)DECR key
:原子自减APPEND key value
:追加内容
应用场景:
- 缓存HTML片段或API响应
- 存储序列化后的对象(JSON、Protobuf等)
3.2 哈希(Hash)
哈希是一种键值对集合类型,适合存储对象。内部实现采用压缩列表或哈希表。
常用操作:
HSET key field value
:设置哈希字段HGET key field
:获取哈希字段值HGETALL key
:获取所有字段和值HINCRBY key field increment
:字段值自增
应用场景:
- 存储用户信息(用户名、邮箱、手机号等)
- 任何结构化对象的存储
3.3 列表(List)
列表是按插入顺序排序的字符串元素集合,底层使用双端链表或压缩列表实现。
常用操作:
LPUSH key value [value ...]
:从左侧插入RPUSH key value [value ...]
:从右侧插入LPOP key
:左边弹出RPOP key
:右边弹出LRANGE key start stop
:获取范围内的元素
应用场景:
- 消息队列(生产者-消费者模式)
- 最新消息展示(如微博、朋友圈)
- 任务队列
3.4 集合(Set)
集合是无序的、唯一的字符串集合,支持基本的集合运算。
常用操作:
SADD key member [member ...]
:添加成员SMEMBERS key
:返回所有成员SISMEMBER key member
:检查成员是否存在SINTER key [key ...]
:求交集SUNION key [key ...]
:求并集
应用场景:
- 标签系统
- 抽奖系统(随机选择唯一元素)
- IP黑名单/白名单
3.5 有序集合(ZSet,Sorted Set)
有序集合是带有分数的集合,元素按分数排序,且唯一。
常用操作:
ZADD key score member
:添加元素ZRANGE key start stop [WITHSCORES]
:获取范围内的元素ZRANK key member
:获取元素排名ZSCORE key member
:获取元素分数ZREVRANGEBYSCORE key max min [LIMIT]
:按分数倒序获取
应用场景:
- 带权重的任务队列
- 延迟队列
- 带优先级的缓存
3.6 位图(Bitmaps)
位图不是一种独立的数据类型,而是基于字符串类型的位操作。
常用操作:
SETBIT key offset value
:设置位GETBIT key offset
:获取位BITCOUNT key [start end]
:统计设置为1的位数BITOP operation destkey key [key ...]
:位运算
应用场景:
- 用户签到系统
- 活跃用户统计
- 简单布隆过滤器
- 大数据去重
3.7 超日志(HyperLogLogs)
HyperLogLog是一种概率数据结构,用于估算集合的基数(不重复元素的数量)。
常用操作:
PFADD key element [element ...]
:添加元素PFCOUNT key [key ...]
:估算基数PFMERGE destkey sourcekey [sourcekey ...]
:合并多个HyperLogLog
应用场景:
- 网站UV统计
- 搜索关键词去重统计
- 大规模唯一计数
3.8 流(Streams)
流是Redis 5.0引入的数据结构,类似于消息队列,但功能更强大。
常用操作:
XADD key * field value [field value ...]
:添加消息XREAD [COUNT count] STREAMS key [key ...] ID [ID ...]
:读取消息XGROUP CREATE key groupname ID
:创建消费者组XREADGROUP GROUP group consumer STREAMS key ID
:消费者组读取
应用场景:
- 消息队列系统
- 事件溯源
- 数据管道
- 可靠消息传递
4. Redis核心知识
4.1 持久化
Redis 提供两种主要的持久化机制:RDB(Redis Database) 和 AOF(Append-Only File),从 Redis 4.0 起还支持两者的结合——混合持久化,以兼顾性能与数据安全性。
4.1.1 RDB(Redis Database)
-
原理:通过在指定条件下执行
BGSAVE
命令,由子进程生成数据集的二进制快照(snapshot),保存为.rdb
文件。 -
触发方式:
- 自动触发:通过
save
配置项(如save 900 1
表示 900 秒内至少有 1 次修改则触发) - 手动触发:使用
SAVE
(阻塞主线程)或BGSAVE
(非阻塞,推荐) - 主从全量同步时也会触发
- 自动触发:通过
-
优点:
- 恢复速度快,适合灾难恢复
- 文件紧凑,便于备份和传输
- 对性能影响小(仅
BGSAVE
时 fork 子进程)
-
缺点:
- 容易丢失最后一次快照之后的数据(取决于配置频率)
- 大数据集下 fork 可能导致短暂延迟
-
配置示例:
save 900 1 save 300 10 save 60 10000 dbfilename dump.rdb dir ./redis-data
4.1.2 AOF(Append-Only File)
-
原理:记录每一个写命令到日志文件中,服务器重启时重新执行这些命令来重建数据。
-
优点:
- 数据更安全,最多丢失 1 秒数据(取决于同步策略)
- 日志可读性强,便于调试
- 支持后台重写机制避免无限增长
-
缺点:
- 文件体积通常比 RDB 大
- 恢复速度较慢
- 写入频繁时 I/O 压力较大
-
同步策略(appendfsync):
no
:由操作系统决定刷盘时机(最快但最不安全)everysec
:每秒刷盘一次(推荐,平衡性能与安全)always
:每次写命令都刷盘(最安全但性能差)
-
配置示例:
appendonly yes appendfilename "appendonly.aof" appendfsync everysec auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
4.1.3 混合持久化
-
Redis 4.0+新增的两者结合的方式
-
原理:开启后,AOF 重写时将以 RDB 格式写入前半部分,后续增量命令以 AOF 格式追加。
-
优势:
- 开机恢复时先加载 RDB 部分,提升加载速度
- 保留 AOF 的增量日志,提高数据完整性
-
开启配置:
aof-use-rdb-preamble yes
-
建议:生产环境强烈推荐开启混合持久化,实现快速恢复与高可用性之间的平衡。
持久化选择建议:
- 仅用 RDB:适用于允许丢失部分数据、追求高性能恢复的场景
- 仅用 AOF:对数据完整性要求高的场景
- 推荐组合:同时开启 RDB + AOF(混合模式),既保证定期快照,又减少数据丢失风险
4.2 订阅/发布
Redis 的发布/订阅(Pub/Sub)是一种轻量级的消息通信模型,基于频道进行消息广播。
基本概念
- 频道(Channel):消息发送的目标主题
- 发布者(Publisher):向指定频道发送消息的客户端
- 订阅者(Subscriber):监听一个或多个频道,接收消息
核心命令
PUBLISH channel message
:向指定频道发送消息SUBSCRIBE channel [channel...]
:订阅一个或多个频道PSUBSCRIBE pattern
:订阅匹配通配符模式的频道(如news.*
)UNSUBSCRIBE [channel]
:取消订阅指定频道PUNSUBSCRIBE [pattern]
:取消模式订阅
通配符模式:使用
*
匹配任意数量非分隔符字符,使用?
匹配单个字符。示例:PSUBSCRIBE user.*
可接收user.login
、user.logout
等消息。
特点与限制
- 不支持消息持久化:若订阅者离线,消息将被丢弃
- 即时通信:消息实时投递,无队列缓冲
- 广播式分发:每个订阅者都能收到相同消息
- 适用于轻量级通知系统:如在线状态更新、日志广播、服务发现等
- 不适用于可靠消息队列:如需持久化、确认机制、重试等功能,应使用 Redis Streams 或 Kafka、RabbitMQ 等专业消息中间件
4.3 事件机制
Redis 基于 单线程事件循环 模型,使用 I/O 多路复用技术 高效处理并发请求,核心依赖于事件驱动架构。
两类核心事件
1.文件事件(File Events)
- 作用:处理网络 I/O 操作
- 类型:
AE_READABLE
:客户端有数据可读(如命令请求)AE_WRITABLE
:可向客户端写入响应
- 实现机制:
- 底层采用多路复用技术,根据平台选择:
- Linux:
epoll
- BSD/macOS:
kqueue
- 通用:
select
/poll
- 通过事件驱动,实现高并发连接管理而无需多线程
2.时间事件(Time Events)
- 作用:周期性或定时执行任务
- 常见用途:
serverCron
:每 100ms 执行一次,负责:- 清理过期键
- 更新统计信息
- 触发主从心跳
- 执行 AOF 重写判断
- 复制偏移同步、客户端超时断开等
- 分类:
- 一次性事件:执行完成后移除
- 周期性事件:如
serverCron
- 实现:使用最小堆维护时间事件队列,确保快速获取最近到期事件
事件处理流程
- 从时间事件和文件事件中找出最近要执行的事件
- 计算等待时间(timeout),调用 I/O 多路复用
aeApiPoll()
等待事件就绪 - 优先处理所有就绪的文件事件(客户端读写)
- 检查是否有到达时间的时间事件,依次执行
- 循环回到第 1 步,持续运行
注意:由于是单线程,长时间阻塞操作(如大 key 删除、复杂 Lua 脚本)会影响整体吞吐,建议使用
UNLINK
、SCAN
等异步或渐进式命令。
4.4 事务
Redis 事务提供命令的批量执行能力,但不同于传统数据库的 ACID 事务,其本质是序列化执行+有限原子性。
核心命令
MULTI
:开启事务,之后命令进入队列EXEC
:提交事务,顺序执行所有队列命令DISCARD
:取消事务,清空队列WATCH key [key...]
:监视一个或多个键,若在EXEC
前被修改则事务失败UNWATCH
:取消监视所有键
事务执行流程
WATCH balance
MULTI
DECRBY balance 100
INCRBY profit 100
EXEC # 若 balance 未被其他客户端修改,则执行;否则返回 nil
特点
- 顺序执行:事务内命令按提交顺序执行,不会被其他客户端插入
- 隔离性:事务执行期间不会被中断
- 不支持回滚:即使某条命令失败(如类型错误),其余命令仍会继续执行
- 乐观锁支持:借助
WATCH
实现 CAS(Compare-and-Set)机制 - 非传统事务:不具备一致性与持久性保障,仅提供“要么全部执行”的弱原子性
应用场景
- 原子性递增/递减操作(如库存扣减)
- 多个相关键的批量更新
- 结合
WATCH
实现条件更新(如防止超卖) - 与 Lua 脚本配合实现更复杂的原子逻辑(更强一致性)
替代方案建议:对于需要严格原子性和回滚能力的场景,推荐使用 Lua 脚本(
EVAL
/EVALSHA
),其在服务器端原子执行,避免了事务的局限性。
4.5 主从复制
Redis 主从复制(Master-Replica Replication)用于实现数据冗余、高可用和读写分离。
工作流程(全量同步)
- 从节点发送
REPLCONF
命令连接主节点 - 发送
PSYNC <replid> <offset>
请求同步 - 若无法增量同步,主节点执行
BGSAVE
生成 RDB - 主节点将 RDB 文件发送给从节点
- 从节点加载 RDB 完成基础数据同步
- 主节点将缓冲区中的增量命令持续发送给从节点
增量复制(Redis 2.8+)
- 依赖 Replication ID 和 Offset(偏移量)
- 即使网络中断,从节点重连后可通过 offset 请求缺失部分命令
- 减少全量同步频率,提升复制效率
配置方式
# 方法一:配置文件(redis.conf)
replicaof 192.168.1.10 6379
# 方法二:运行时动态设置
SLAVEOF 192.168.1.10 6379
# 方法三:断开复制
SLAVEOF NO ONE
读写分离
- 写操作全部由主节点处理
- 读请求可分散至多个从节点,提升系统吞吐量
- 注意:从节点数据有一定延迟(异步复制),不适用于强一致性读
哨兵模式(Sentinel)
-
功能:
- 监控主从节点健康状态
- 自动故障转移(failover):主节点宕机时选举新主
- 通知客户端新的主节点地址
-
部署建议:
- 至少部署 3 个 Sentinel 实例(奇数个,避免脑裂)
- 分布在不同物理机或可用区
-
配置示例:
sentinel monitor mymaster 192.168.1.10 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 10000
4.6 地理空间
Redis 从 3.2 版本起引入地理空间索引功能,基于 Sorted Set + GeoHash 编码 实现地理位置存储与查询。但其精度受 GeoHash 位数限制,不适合极高精度需求
底层实现
- 使用 GeoHash 将二维坐标编码为字符串
- 存储在 Sorted Set 中,Score 为 GeoHash 编码值
- 利用 ZRANGE 等命令实现高效范围查询
核心命令
GEOADD key long lat member
:添加成员及其经纬度GEOPOS key member
:获取成员坐标GEODIST key m1 m2 [unit]
:计算两个成员间距离(支持 m/km/mi/ft)GEORADIUS key lon lat radius unit [WITHDIST][WITHCOORD]
:查询某点半径内成员GEORADIUSBYMEMBER key member radius unit
:根据成员位置查询周围目标
示例
GEOADD cities 116.405285 39.904989 "Beijing"
GEOADD cities 121.473701 31.230416 "Shanghai"
GEODIST cities Beijing Shanghai km
# 返回:1068.43 km
GEORADIUS cities 116.405285 39.904989 500 km
# 查询北京 500km 范围内的城市
4.7 模块扩展
Redis 提供模块系统(Module System),允许开发者以插件形式扩展其功能,无需修改核心代码。
常用官方/社区模块
- RediSearch:全文搜索、二级索引、聚合查询
- RedisJSON:支持原生 JSON 数据类型,提供
JSON.SET
、JSON.GET
等命令 - RedisGraph:图数据库引擎,支持 Cypher 查询语言
- RedisTimeSeries:时间序列数据处理,支持降采样、聚合
- RedisBloom:布隆过滤器、Cuckoo Filter,用于去重判断
- AI-Model-Broker (RedisAI):集成机器学习模型推理(已逐渐被 RedisGears 替代)
模块使用示例
# 加载模块(redis.conf 或命令行)
loadmodule /path/to/redisjson.so
# 使用 RedisJSON
JSON.SET user:1001 $ '{"name":"Alice","age":30}'
JSON.GET user:1001 $.name
4.8 缓存策略与淘汰机制
在使用 Redis 作为缓存时,合理配置内存管理策略和键的生命周期至关重要,以避免内存耗尽并提升命中率。
4.8.1 缓存设计模式
常见的缓存访问模式包括:
模式 | 说明 | 优缺点 |
---|---|---|
Cache-Aside(旁路缓存) | 应用先查缓存,未命中则查数据库并回填 | 最常用,控制灵活,但需处理双写不一致 |
Read/Write Through(读写穿透) | 应用只访问缓存,缓存负责与数据库同步 | 封装逻辑于缓存层,一致性更高 |
Write Behind(异步写回) | 先写缓存,异步批量写入数据库 | 高性能,但可能丢数据 |
大多数场景使用 Cache-Aside 模式,配合合理的过期策略。
4.8.2 键的过期策略
Redis 支持为键设置生存时间(TTL),控制其自动删除。
-
设置方式:
EXPIRE key seconds
:相对时间过期PEXPIRE key milliseconds
EXPIREAT key timestamp
:指定绝对时间点TTL key
/PTTL
:查看剩余时间
-
过期实现机制:
- 惰性删除:访问一个键时才检查是否过期,过期则删除(延迟清理)
- 定期删除:Redis 周期性随机抽查一批设置了过期时间的键进行清理(主动回收)
注意:过期 ≠ 立即释放内存,可能导致内存占用虚高
4.8.3 内存淘汰策略
当 Redis 使用内存超过 maxmemory
配置时,触发淘汰机制。
配置方式
maxmemory 2gb
maxmemory-policy allkeys-lru
常见淘汰策略
策略 | 说明 | 适用场景 |
---|---|---|
noeviction |
达到内存上限后拒绝写操作(默认) | 数据完整性优先 |
allkeys-lru |
从所有键中淘汰最近最少使用的 | 通用缓存场景(推荐) |
volatile-lru |
仅淘汰设置了过期时间的键中 LRU 者 | 混合存储:部分缓存+部分持久数据 |
allkeys-lfu |
淘汰使用频率最低的键(Redis 4.0+) | 热点数据稳定、长尾访问多的场景 |
volatile-lfu |
类似 LFU,但仅限带 TTL 的键 | 同上,限制范围 |
allkeys-random |
随机淘汰任意键 | 均匀访问分布 |
volatile-random |
随机淘汰带过期时间的键 | 同上 |
volatile-ttl |
优先淘汰剩余 TTL 最短的键 | 快速清理即将过期但未访问的数据 |
使用建议:
缓存专用实例:使用
allkeys-lru
或allkeys-lfu
混合用途实例:使用
volatile-*
系列策略,保护无过期的持久数据开启
lazyfree-lazy-eviction yes
可异步释放内存,降低主线程阻塞提高缓存命中率的实践:
设置合理的过期时间(避免过短导致频繁回源)
使用前缀+命名规范统一管理缓存键
批量获取(
MGET
)减少网络开销预热热点数据(如启动时加载热门商品)
使用 Bloom Filter(可通过 RedisBloom 模块)防止缓存穿透
4.9 性能监控与调优
为了保障 Redis 的稳定与高效,必须建立完善的性能监控体系,及时发现瓶颈并优化。
关键性能指标
指标 | 监控意义 |
---|---|
used_memory / used_memory_rss |
实际内存使用 vs 系统分配,判断碎片率 |
mem_fragmentation_ratio |
内存碎片比(>1.5 需关注) |
connected_clients |
当前连接数,突增可能异常 |
blocked_clients |
被阻塞的客户端数(如 BLPOP) |
instantaneous_ops_per_sec |
每秒操作数,反映负载 |
total_commands_processed |
累计命令处理量 |
rejected_connections |
因内存或连接数限制被拒的连接 |
expired_keys , evicted_keys |
过期和被淘汰的键数量,判断缓存压力 |
keyspace_hits / keyspace_misses |
缓存命中/未命中,计算命中率 |
master_link_status |
主从复制状态(up/down) |
lag |
从节点复制延迟(秒级) |
查看性能信息的命令
-
INFO
命令查看各模块状态:INFO memory # 内存使用 INFO stats # 命令统计 INFO replication # 主从信息 INFO cpu # CPU 使用 INFO commandstats # 各命令调用次数与耗时
-
SLOWLOG GET 10
:查看最慢的 10 条命令(需提前配置slowlog-log-slower-than
) -
LATENCY LATEST
/LATENCY HISTORY
:检测延迟事件(如 AOF 刷盘、大 key 删除等)
性能调优建议
问题 | 优化措施 |
---|---|
高内存碎片 | 启用 activedefrag yes (Redis 4.0+),或重启实例释放内存 |
大 key 导致阻塞 | 使用 UNLINK 替代 DEL ,或拆分数据结构 |
慢查询过多 | 分析 SLOWLOG ,避免 KEYS * ,改用 SCAN |
高并发下性能下降 | 启用 pipeline 批量发送命令 |
主从延迟高 | 检查网络、从节点负载,调整 client-output-buffer-limit |
CPU 使用高 | 检查热点命令(INFO commandstats ),考虑分片或升级硬件 |
监控工具推荐
- Redis 自带:
redis-cli --stat
实时监控 - Prometheus + Grafana + redis_exporter:企业级可视化监控方案
- RedisInsight(官方 GUI 工具):支持性能分析、内存洞察、Slow Log 查看
- Zabbix / Open-Falcon:传统监控平台集成
4.10 安全配置
Redis 原本设计为可信环境下的高性能数据库,但暴露在公网时存在严重安全风险。必须加强安全防护。
4.10.1 常规手段
绑定监听地址
禁止绑定 0.0.0.0
,仅允许内网访问:
bind 127.0.0.1 192.168.1.10
设置访问密码
启用密码认证(虽非加密,但基础防护):
requirepass your_strong_password
连接时需认证:
AUTH your_strong_password
⚠️ 注意:密码明文传输,仅在内网安全环境下有效
4.10.2 ACL(Access Control List)
从Redis 6.0起支持ACL,用以取代传统单一密码,提供细粒度权限控制。实际使用中,执行最小权限原则,即为不同业务模块分配独立用户,才能达到最大安全控制。
核心概念
- 用户(User):每个访问者可对应一个用户
- 权限(Permissions):命令级别、键模式、Pub/Sub 频道控制
常用命令
# 创建用户并授权
ACL SETUSER alice on >password ~cache:* +get +set +mget
# 允许 alice 访问以 cache: 开头的键,仅允许 GET/SET/MGET
# >password 表示密码为 password
# 查看当前用户
ACL WHOAMI
# 列出所有用户
ACL LIST
# 加载 ACL 配置(redis.conf 中也可配置)
aclfile /etc/redis/users.acl
权限语法
+@read
:允许所有读命令-SET
:禁止 SET 命令~*
:可访问所有键~order:*
:仅可访问order:
前缀的键&*
:可订阅所有频道&news:*
:仅可订阅news:
相关频道
4.10.2 传输加密(TLS/SSL)
TLS(Transport Layer Security,传输层安全协议)是 SSL(Secure Sockets Layer)协议的继任者。主要作用是在客户端和服务器之间建立一条加密的、安全的通信通道,确保数据在传输过程中不会被第三方窃取或篡改。
从 Redis 6.0 起支持原生 TLS 加密通信,防止窃听和中间人攻击。在公网或敏感环境中建议强制启用 TLS 。
启用 TLS
tls-port 6380
port 0 # 关闭非 TLS 端口
tls-cert-file /path/to/redis.crt
tls-key-file /path/to/redis.key
tls-ca-cert-file /path/to/ca.crt
tls-auth-clients yes # 强制客户端验证证书
客户端连接示例(redis-cli)
redis-cli --tls --cert /client.crt --key /client.key \ --cacert /ca.crt -p 6380
5. Redis应用示例
5.1 缓存系统
最常见用途,用于减轻后端数据库压力:
# 设置缓存,300秒后过期
SET user:1001 "{id:1001, name:'John', email:'john@example.com'}" EX 300
# 获取缓存
GET user:1001
# 缓存不存在时从数据库加载并设置
if not EXISTS user:1001
user = db.get_user(1001)
SET user:1001 user_json EX 300
缓存穿透解决方案:
# 空值缓存
if not user:
SET user:1001 "" EX 60 # 缓存空结果,60秒过期
缓存雪崩解决方案:
-
给缓存时间添加随机值:
SET user:1001 user_json EX 300 + RANDOM(0, 300)
5.2 简易消息队列
使用列表实现简单消息队列:
生产者:
LPUSH task_queue "task_data"
消费者:
# 阻塞式获取
BRPOP task_queue 0
# 非阻塞式获取
RPOP task_queue
增强版消息队列(使用Stream):
# 添加任务
XADD queue * task "process_image" image_id "12345"
# 消费者组消费
XREADGROUP GROUP mygroup consumer1 COUNT 10 STREAMS queue >
5.5 位图示例
# 用户签到系统
# 用户1001签到(今天是2023-08-01)
# 计算天数:相对于起始日期的天数
SETBIT user:1001:sign:2023:08 0 1
# 检查用户是否签到
GETBIT user:1001:sign:2023:08 0
# 计算本月累计签到天数
BITCOUNT user:1001:sign:2023:08
5.6 地理空间索引
# 附近的人
# 添加用户位置
GEOADD locations 116.403963 39.915119 user:1001
GEOADD locations 116.412222 39.912345 user:1002
# 查找5公里内的用户
GEORADIUS locations 116.403963 39.915119 5 km WITHDIST
5. C++的redis-plus-plus使用
需要使用redis-plus-plus客户端。Redis官方为C语言提供了客户端库hiredis,但是使用起来很繁琐,而redis-plus-plus基于hiredis构建,提供现代C++风格的API,更易使用。
5.1 环境配置
-
使用vcpkg安装
# 安装redis-plus-plus(会自动安装hiredis依赖) vcpkg install redis-plus-plus:x64-windows # 集成到 Visual Studio vcpkg integrate install # 验证安装 vcpkg list | findstr redis
-
CMake配置
# 查找redis-plus-plus包 find_package(redis-plus-plus CONFIG REQUIRED) # 链接redis-plus-plus库 target_link_libraries(redis_demo PRIVATE redis-plus-plus::redis-plus-plus )
5.2 基本操作
5.2.1 连接Redis
#include <sw/redis++/redis++.h>
#include <iostream>
#include <string>
int main() {
try {
// 创建连接 (默认参数)
sw::redis::Redis redis("tcp://127.0.0.1:6379");
// 或使用ConnectionOptions
sw::redis::ConnectionOptions connection_options;
connection_options.host = "127.0.0.1";
connection_options.port = 6379;
connection_options.password = ""; // 如有密码
connection_options.connect_timeout = std::chrono::seconds(1);
connection_options.socket_timeout = std::chrono::seconds(1);
sw::redis::Redis redis(connection_options);
// 测试连接
bool is_alive = redis.ping();
std::cout << "Redis connection " << (is_alive ? "successful" : "failed") << std::endl;
} catch (const sw::redis::Error &e) {
std::cerr << "Redis error: " << e.what() << std::endl;
}
return 0;
}
5.2.2 字符串操作
// 设置键值,带过期时间
redis.set("user:1001:name", "John",
std::chrono::seconds(3600)); // 1小时后过期
// 获取值
auto name = redis.get("user:1001:name");
if (name) {
std::cout << "Name: " << *name << std::endl;
}
// 原子自增
long long views = redis.incr("article:101:views");
std::cout << "Article views: " << views << std::endl;
5.2.3 哈希操作
// 存储用户信息
redis.hset("user:1001", "name", "John");
redis.hset("user:1001", "email", "john@example.com");
redis.hset("user:1001", "age", "30");
// 获取所有字段
auto user_hash = redis.hgetall("user:1001");
for (auto &item : user_hash) {
std::cout << item.first << ": " << item.second << std::endl;
}
// 原子增长
redis.hincrby("user:1001", "login_count", 1);
5.2.4 列表操作
// 添加任务
redis.rpush("task_queue", "process_image_123");
redis.rpush("task_queue", "generate_thumbnail_456");
redis.rpush("task_queue", "send_notification_789");
// 获取任务(消费者)
auto task = redis.lpop("task_queue");
if (task) {
std::cout << "Processing task: " << *task << std::endl;
}
// 查看队列长度
long len = redis.llen("task_queue");
std::cout << "Remaining tasks: " << len << std::endl;
5.3 高级操作
5.3.1 管道
// 创建管道
auto pipe = redis.pipeline();
// 添加多个命令
for (int i = 0; i < 100; i++) {
pipe.set("counter:" + std::to_string(i),
std::to_string(i),
std::chrono::seconds(3600));
}
// 原子执行所有命令
auto replies = pipe.exec();
// 获取单个结果
auto reply0 = replies.get<long long>(0);
if (reply0) {
std::cout << "First SET result: " << *reply0 << std::endl;
}
5.3.2 事务
// 创建事务
auto tx = redis.transaction();
// 添加命令
tx.set("tx:key1", "value1");
tx.set("tx:key2", "value2");
tx.incr("tx:counter");
// 执行事务
auto replies = tx.exec();
// 处理结果
auto counter_val = replies.get<long long>(2);
if (counter_val) {
std::cout << "New counter value: " << *counter_val << std::endl;
}
5.3.3 发布/订阅
// 订阅者线程
std::thread subscriber_thread([]() {
sw::redis::Redis redis("tcp://127.0.0.1:6379");
auto sub = redis.subscriber();
// 定义消息处理回调
sub.on_message([](std::string channel, std::string msg) {
std::cout << "Received message on " << channel
<< ": " << msg << std::endl;
});
// 订阅频道
sub.subscribe("news", "alerts");
// 保持订阅状态
while (true) {
try {
sub.consume();
} catch (const sw::redis::Error &err) {
std::cerr << "Subscription error: " << err.what() << std::endl;
break;
}
}
});
// 发布者
std::this_thread::sleep_for(std::chrono::seconds(1));
redis.publish("news", "Breaking news: Redis is awesome!");
redis.publish("alerts", "Security alert: All clear");
// 等待订阅者
subscriber_thread.join();
5.3.4 连接池
#include <sw/redis++/redis++.h>
#include <memory>
#include <thread>
#include <vector>
// 创建连接池
sw::redis::Redis redis = sw::redis::Redis(
sw::redis::ConnectionOptions(),
sw::redis::ConnectionPoolOptions {
.size = 10 // 连接池大小
}
);
// 多线程使用示例
void worker_function(sw::redis::Redis &redis, int id) {
redis.set("worker:" + std::to_string(id), "active");
auto val = redis.get("worker:" + std::to_string(id));
std::cout << "Worker " << id << " status: " << (val ? *val : "(nil)") << std::endl;
}
int main() {
const int num_workers = 5;
std::vector<std::thread> workers;
for (int i = 0; i < num_workers; ++i) {
workers.emplace_back(worker_function, std::ref(redis), i);
}
for (auto &t : workers) {
t.join();
}
return 0;
}
5.4 异常处理与使用优化
-
异常处理
使用
try...catch...
try { redis.set("key", "value"); } catch (const sw::redis::Error &e) { // 处理Redis错误 std::cerr << "Redis error: " << e.what() << std::endl; } catch (const std::exception &e) { // 处理其他标准异常 std::cerr << "Standard exception: " << e.what() << std::endl; }
-
连接超时处理
// 设置合适的超时 sw::redis::ConnectionOptions opts; opts.host = "127.0.0.1"; opts.port = 6379; opts.connect_timeout = std::chrono::milliseconds(100); // 连接超时 opts.socket_timeout = std::chrono::milliseconds(200); // 读写超时 sw::redis::Redis redis(opts);
-
缓存雪崩处理
// 添加随机过期时间 int random_seconds = std::rand() % 300; redis.setex(key, std::chrono::seconds(300 + random_seconds), value);
-
保证高并发下的数据一致性
使用
WATCH
实现乐观锁,WATCH
命令用于监视一个或多个key,在后续的事务执行之前,如果被监视的key被其他客户端修改,事务将被取消。// WATCH执行流程 1. 客户端1: WATCH key1 2. 客户端2: SET key1 "new_value" // 修改被监视的key 3. 客户端1: MULTI 4. 客户端1: INCR key1 5. 客户端1: EXEC // 返回nil,事务被取消
那么由此机制可以实现
while (true) { try { // WATCH命令的作用(乐观锁核心机制): // 1. 告诉Redis开始监视"user:1001:balance"这个key // 2. 如果在这个事务执行前该key被其他客户端修改,当前事务将自动被取消(返回nil) redis.watch("user:1001:balance"); auto balance = redis.get("user:1001:balance"); if (balance && std::stoi(*balance) >= amount) { auto tx = redis.transaction(); tx.decrby("user:1001:balance", amount); tx.exec(); break; } else { break; // 余额不足 } } catch (const sw::redis::WatchError &) { // 监视的key被修改,重试 continue; } }
5.5 应用示例:缓存系统
#include <sw/redis++/redis++.h>
#include <iostream>
#include <string>
#include <nlohmann/json.hpp>
// 用户数据结构
struct User {
long id;
std::string name;
std::string email;
int age;
// 转换为JSON字符串
std::string to_json() const {
nlohmann::json j;
j["id"] = id;
j["name"] = name;
j["email"] = email;
j["age"] = age;
return j.dump();
}
// 从JSON字符串解析
static User from_json(const std::string &json_str) {
auto j = nlohmann::json::parse(json_str);
User user;
user.id = j["id"].get<long>();
user.name = j["name"].get<std::string>();
user.email = j["email"].get<std::string>();
user.age = j["age"].get<int>();
return user;
}
};
class UserCache {
public:
UserCache(const std::string &host = "127.0.0.1",
int port = 6379,
int ttl_seconds = 300)
: redis_(sw::redis::Redis("tcp://" + host + ":" + std::to_string(port))),
ttl_seconds_(ttl_seconds) {}
// 尝试从缓存获取用户,如果不存在则调用loader函数并缓存结果
User get_user(long user_id,
const std::function<User(long)> &loader) {
std::string key = "user:" + std::to_string(user_id);
// 尝试从缓存获取
auto user_json = redis_.get(key);
if (user_json) {
return User::from_json(*user_json);
}
// 缓存不存在,从数据源加载
User user = loader(user_id);
// 存储到缓存
redis_.setex(key,
std::chrono::seconds(ttl_seconds_),
user.to_json());
return user;
}
// 清除缓存
void invalidate_user(long user_id) {
std::string key = "user:" + std::to_string(user_id);
redis_.del(key);
}
private:
sw::redis::Redis redis_;
int ttl_seconds_; // 缓存过期时间(秒)
};
// 模拟数据库加载
User load_from_db(long user_id) {
std::cout << "Loading user " << user_id << " from database..." << std::endl;
// 实际应用中应该是数据库查询
User user;
user.id = user_id;
user.name = "User" + std::to_string(user_id);
user.email = "user" + std::to_string(user_id) + "@example.com";
user.age = 20 + (user_id % 10);
return user;
}
int main() {
UserCache cache;
// 第一次访问(触发数据库查询)
User user1 = cache.get_user(1001, load_from_db);
std::cout << "User 1001: " << user1.name
<< " (" << user1.email << ")" << std::endl;
// 第二次访问(从缓存获取)
User user2 = cache.get_user(1001, load_from_db);
std::cout << "User 1001 again: " << user2.name << std::endl;
// 清除缓存
cache.invalidate_user(1001);
// 再次访问(再次触发数据库查询)
User user3 = cache.get_user(1001, load_from_db);
std::cout << "User 1001 after invalidate: " << user3.name << std::endl;
return 0;
}