Redis基础学习与C++调用

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

  1. 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
  2. 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.loginuser.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
  • 实现:使用最小堆维护时间事件队列,确保快速获取最近到期事件

事件处理流程

  1. 从时间事件和文件事件中找出最近要执行的事件
  2. 计算等待时间(timeout),调用 I/O 多路复用 aeApiPoll() 等待事件就绪
  3. 优先处理所有就绪的文件事件(客户端读写)
  4. 检查是否有到达时间的时间事件,依次执行
  5. 循环回到第 1 步,持续运行

注意:由于是单线程,长时间阻塞操作(如大 key 删除、复杂 Lua 脚本)会影响整体吞吐,建议使用 UNLINKSCAN 等异步或渐进式命令。

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)用于实现数据冗余、高可用和读写分离

工作流程(全量同步)

  1. 从节点发送 REPLCONF 命令连接主节点
  2. 发送 PSYNC <replid> <offset> 请求同步
  3. 若无法增量同步,主节点执行 BGSAVE 生成 RDB
  4. 主节点将 RDB 文件发送给从节点
  5. 从节点加载 RDB 完成基础数据同步
  6. 主节点将缓冲区中的增量命令持续发送给从节点

增量复制(Redis 2.8+)

  • 依赖 Replication IDOffset(偏移量)
  • 即使网络中断,从节点重连后可通过 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.SETJSON.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-lruallkeys-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 环境配置

  1. 使用vcpkg安装

    # 安装redis-plus-plus(会自动安装hiredis依赖)
    vcpkg install redis-plus-plus:x64-windows
    # 集成到 Visual Studio
    vcpkg integrate install
    # 验证安装
    vcpkg list | findstr redis
  2. 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 异常处理与使用优化

  1. 异常处理

    使用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;
    }
  2. 连接超时处理

    // 设置合适的超时
    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);
  3. 缓存雪崩处理

    // 添加随机过期时间
    int random_seconds = std::rand() % 300;
    redis.setex(key, std::chrono::seconds(300 + random_seconds), value);
  4. 保证高并发下的数据一致性

    使用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;
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇