当前位置: 首页 > news >正文

Linux 系统编程中的Redis

Redis 是什么?

从 Linux 系统编程的角度看,Redis 的本质是:

一个开源的、基于内存的、键值型数据结构存储系统。它通常被用作数据库、缓存和消息中间件。

我们可以从以下几个关键点来理解它:

  • 一个网络服务器进程:Redis 不是一个静态的库(.so 或 .a 文件),而是一个独立运行的守护进程(redis-server)。你的应用程序(客户端)通过网络协议(TCP/IP 或 Unix Socket)与它进行通信。这意味着它可以在同一台机器上,也可以在远程服务器上。

  • 基于内存:Redis 将其所有数据主要存储在内存(RAM)中。这使得它的读写速度极快(通常达到微秒级),远超基于磁盘的数据库(如 MySQL, PostgreSQL)。这是它高性能的根本原因。

  • 键值存储:Redis 最基本的模型是 Key-Value。你可以通过一个唯一的 Key 来存储和检索一个 Value。

    • Key:通常是字符串。

    • Value:不仅仅是简单的字符串。这是 Redis 强大之处,它支持丰富的数据结构作为 Value。

  • 支持持久化:虽然数据主要在内存中,但 Redis 提供了两种机制(RDB 快照和 AOF 日志)将内存中的数据异步写入硬盘,以防止服务器重启或宕机导致数据丢失。

  • 使用标准协议:Redis 使用自己设计的 RESP (REdis Serialization Protocol) 协议与客户端通信。这个协议简单、高效,且是人类可读的。这也意味着你可以直接用 telnetnc (netcat) 命令来与 Redis 服务器交互。

守护进程

守护进程(Daemon Process,在 Windows 中通常称为“服务”)是一种在后台运行的特殊进程,它独立于控制终端(Terminal)。它没有标准的输入输出(stdin, stdout, stderr),或者将其重定向到其他地方(如日志文件)。它通常在系统启动时开始运行,并在系统关闭时终止,为用户或其他程序提供持续的服务。

你可以把它理解为一个“后台服务员”,默默无闻地工作,你不需要直接和它交互(比如通过终端输入命令),但它一直在那里等待为你服务(比如响应网络请求)。

主要特点:

  • 生命周期长:从系统启动开始,一直运行到系统关闭。

  • 在后台运行:不与任何终端关联。即使你关闭了启动它的终端窗口,它也不会退出。

  • 脱离控制终端:这是实现“后台运行”的关键。通常通过 fork() 创建子进程,然后让父进程退出,使得子进程被 initsystemd 这样的系统进程收养,从而脱离原终端。

  • 通常以 root 或其他特殊用户权限运行:以便访问系统资源。

常见例子:

  • sshd:SSH 守护进程,监听 22 端口,处理你的远程登录请求。

  • nginx / apache:Web 服务器守护进程,监听 80/443 端口,处理 HTTP 请求。

  • redis-server:Redis 服务器守护进程,监听 6379 端口,处理数据库命令。

  • cron:定时任务守护进程,定期执行预设的命令。

所以,当我们说“启动 Redis”时,实际上就是在启动一个名为 redis-server 的守护进程。

RDB 快照和 AOF 日志的实现

这是 Redis 实现数据持久化(将内存中的数据保存到硬盘以防数据丢失)的两种主要策略。

RDB(Redis Database)快照

RDB 类似于给当前内存中的数据拍一张完整的快照,并保存到一个压缩的二进制文件(.rdb)中。

如何实现?

  • 创建快照的时机:

  • 手动触发:执行 SAVE(阻塞主进程,不常用)或 BGSAVE(后台异步执行,常用)命令。

  • 自动触发:在配置文件中设置规则,如 save 900 1(在 900 秒内如果至少有 1 个 key 发生变化,则触发 BGSAVE)。

  • BGSAVE 的工作流程(核心)

  • 主进程 fork() 一个子进程。这个子进程由操作系统创建,拥有与父进程(主进程)完全相同的内存数据副本。

  • 主进程继续正常处理客户端请求。由于操作系统的 Copy-on-Write (COW,写时复制) 机制,如果主进程要修改某块数据,它会先复制一份副本再进行修改。这样就能保证子进程看到的内存数据是 fork() 那一瞬间的静止状态。

  • 子进程负责将整个内存数据写入一个临时的 RDB 文件

  • 子进程完成写盘后,用新的 RDB 文件替换旧的 RDB 文件

  • 子进程退出。

优点

  • 性能好fork() 子进程进行持久化,主进程几乎不受影响。

  • 文件紧凑:二进制压缩文件,体积小,适合做灾难恢复(如备份后传输到远程机房)。

  • 恢复速度快:恢复大数据集时比 AOF 快很多。

缺点

  • 可能丢失更多数据:如果 Redis 突然宕机,从上一次 RDB 快照到宕机之间的数据会全部丢失。

  • fork() 可能阻塞:如果数据集非常大,fork() 过程本身可能会耗时较长,导致主进程短暂阻塞。

AOF(Append Only File)日志

AOF 更像是记录所有写操作命令的日志。它通过追加(Append)的方式,将每一个会修改数据的命令写入一个日志文件。当 Redis 重启时,通过重放(Replay) 这个日志文件中的所有命令来重建数据。

如何实现?

  • 工作流程

    • 命令追加(Append):主进程在执行完一个写命令后,会以协议的格式将该命令追加到 aof_buf 缓冲区。

    • 文件写入(Write)和同步(Sync):根据配置的 appendfsync 策略,定期将缓冲区的内容写入到操作系统内核的页面缓存,并最终同步(fsync)到硬盘。

      • appendfsync always:每个命令都同步。数据最安全,性能最差。

      • appendfsync everysec:每秒同步一次。是性能和安全性的折中方案,默认推荐。最多丢失1秒数据。

      • appendfsync no:由操作系统决定何时同步。性能最好,但可能丢失大量数据。

  • AOF 重写(Rewrite)

    • 问题:AOF 文件会不断变大,恢复时间变长,且包含大量冗余命令(如对一个 key 先后执行了 set a 1, set a 2, set a 3,其实只需要最后一条 set a 3)。

    • 解决方案:Redis 会定期创建子进程,根据当前内存中的数据逆向生成一套最精简的命令集,写入一个新的 AOF 临时文件,最终替换掉旧的、庞大的 AOF 文件。

优点:

  • 数据更安全:everysec 策略下最多丢失1秒数据。

  • 可读性强:AOF 文件是文本格式,可以手动查看甚至修改。

缺点:

  • 文件通常更大:即使经过重写,一般也比同数据的 RDB 文件大。

  • 恢复速度较慢:需要逐条执行命令来重建数据,比 RDB 慢。

生产环境中,通常两者结合使用,用 RDB 做冷备,用 AOF 保证数据安全性。

在 Linux 系统编程中,Redis 的作用是什么?

在开发和部署应用程序时,Redis 就像一个高性能的“瑞士军刀”,解决了多种架构难题。其主要作用包括:

1.缓存(Cache) - 最核心、最常用的场景

  • 问题:直接访问后端主数据库(如 MySQL)的读写速度较慢,尤其是在高并发场景下,频繁的数据库查询会成为系统瓶颈,导致响应变慢。

  • 解决方案:将频繁访问且不常变动的“热点数据”(如用户信息、商品详情、页面片段)存储在 Redis 中。

  • 工作流程(经典缓存模式):

    • 应用程序需要读取数据。

    • 首先查询 Redis 中是否存在该数据。

    • 如果存在(缓存命中),直接从高速的 Redis 中返回数据。

    • 如果不存在(缓存未命中),则从慢速的主数据库中查询,取得数据后并将其写入 Redis(以便下次快速读取),再返回给用户。

  • 效果:极大减轻后端数据库的压力,显著提升应用程序的响应速度和处理能力。

2.消息队列(Message Broker)

  • 问题:系统组件之间需要异步通信或解耦。例如,用户注册后需要发送邮件和短信,但不希望这些耗时操作阻塞主流程。

  • 解决方案:利用 Redis 的 List(使用 LPUSH/BRPOP 命令)或 Pub/Sub(发布/订阅)功能实现简单的消息队列。

    • 生产者将任务消息放入 Redis List。

    • 消费者从 List 的另一端取出消息进行处理。

  • 效果:实现异步处理、流量削峰和系统解耦。

3.会话存储(Session Store)

  • 问题:在 Web 集群中,用户的登录会话(Session)如果存储在单台服务器的内存里,当用户的请求被负载均衡分配到其他服务器时,会话会丢失。

  • 解决方案:使用 Redis 作为集中式的会话存储。所有 Web 服务器都从一个中央 Redis 服务中读写同一用户的 Session 信息。

  • 效果:实现了分布式环境下的会话共享,支持应用的水平扩展。

4. 实时系统与排行榜

  • 问题:需要实时更新和查询的数据,如游戏排行榜、实时计数(点赞、转发数)、热点新闻。

  • 解决方案:利用 Redis 的 Sorted Set(有序集合)数据结构。可以非常高效地对成员按分数进行排序和范围查询。

  • 效果:实现复杂的实时排序和计数功能,性能极高。

5. 分布式锁(Distributed Lock)

  • 问题:在分布式系统中,多个进程或服务可能需要互斥地访问某个共享资源,防止数据冲突。

  • 解决方案:使用 Redis 的 SET 命令的 NX(Not eXists)选项可以方便地实现一个简单的分布式锁。

  • 效果:在分布式环境中协调多个进程的行为。

负载均衡(Load Balancing)

负载均衡是一种将网络流量或计算任务分布到多个服务器(或称为服务节点)上的技术。它的核心目标是优化资源使用、最大化吞吐量、最小化响应时间,并避免任何单一服务器过载,从而提高整个系统的可用性、可靠性和可扩展性。

想象一下一个热门餐厅只有一个服务员,他很快就会忙不过来。负载均衡就像是雇佣了一个接待员(负载均衡器),他的工作是根据每个服务员的忙碌情况,将新来的顾客引导到最空闲的服务员那里。

如何实现?

负载均衡通常由一个独立的硬件设备(如 F5)或软件(如 Nginx, HAProxy, LVS)来实现,这个设备/软件被称为负载均衡器(Load Balancer)。

基本工作流程:

  • 客户端负载均衡器(LB) 发送请求。

  • 负载均衡器根据预设的算法,从后端的服务器池(Server Pool / Cluster) 中选择一个最合适的服务器。

  • 负载均衡器将客户端的请求转发给选中的服务器。

  • 服务器处理请求并将响应返回给负载均衡器。

  • 负载均衡器最终将响应发回给客户端。

关键概念:

  • 负载均衡算法

    • 轮询(Round Robin):依次将请求分发给每台服务器,循环往复。

    • 加权轮询(Weighted Round Robin):给性能好的服务器分配更高的权重,让它处理更多请求。

    • 最少连接(Least Connections):将新请求发给当前连接数最少的服务器。

    • IP 哈希(IP Hash):根据客户端的 IP 地址计算哈希值,将同一 IP 的请求总是发给同一台服务器(常用于保持会话)。

  • 健康检查(Health Check):负载均衡器会定期检查后端服务器是否还“活着”(能否正常响应)。如果某台服务器宕机,LB 会自动将其从服务器池中移除,不再向其转发流量,从而保证服务的高可用性。

作用:

  • 提高性能:通过分散请求,充分利用多台服务器的计算能力。

  • 提高可用性(高可用):一台服务器宕机,其他服务器可以继续提供服务,用户无感知。

  • 提高可扩展性(Scalability):当流量增加时,可以简单地通过向服务器池中添加新的服务器来水平扩展系统能力。

  • 容错:自动屏蔽故障节点。

在 Redis 的语境中,我们谈论负载均衡,通常是指将客户端的请求分发到多个 Redis 从节点(Replica) 上进行读操作(读写分离),或者是在 Redis Cluster 模式中,将请求根据 key 的哈希值分发到不同的分片(Shard)上。

如何与 Redis 交互?

在 Linux 下用 C/C++ 编程时,你不需要自己实现 Redis 协议,通常使用官方提供的 hiredis 客户端库。

基本使用步骤:

  • 安装 hiredis:通过包管理器安装,如 sudo apt-get install libhiredis-dev。

  • 包含头文件#include <hiredis/hiredis.h>。

  • 连接数据库:使用 redisConnect 函数建立到 Redis 服务器的连接。

  • 执行命令:使用 redisCommand 函数发送任何 Redis 命令(如 SET, GET, LPUSH)。

  • 处理回复:检查命令返回的 redisReply 对象,从中提取数据。

  • 释放连接和回复:使用 freeReplyObjectredisFree 来释放资源。

简单示例代码:

#include <stdio.h>
#include <hiredis/hiredis.h>int main() {// 1. 连接到本地Redis服务器,默认端口6379redisContext *c = redisConnect("127.0.0.1", 6379);if (c == NULL || c->err) {if (c) {printf("Connection error: %s\n", c->errstr);redisFree(c);} else {printf("Connection error: can't allocate redis context\n");}return 1;}// 2. 执行一个SET命令redisReply *reply = (redisReply *)redisCommand(c, "SET mykey \"Hello from C!\"");freeReplyObject(reply); // 对于简单的SET,我们不太关心回复,直接释放// 3. 执行一个GET命令reply = (redisReply *)redisCommand(c, "GET mykey");printf("Response from GET: %s\n", reply->str); // 打印获取到的值freeReplyObject(reply); // 释放回复对象// 4. 断开连接并清理redisFree(c);return 0;
}

编译:gcc -o example example.c -lhiredis

Redis内部数据结构

Redis 之所以如此快速和灵活,很大程度上归功于其精巧的内部数据结构设计。

简单来说,Redis 使用了一个名为 「哈希表」 的结构作为所有数据的全局索引,但这个索引的「值」并不仅仅是简单的数据,而是指向各种丰富底层数据结构的指针。这些底层数据结构是为了在不同场景下达到最优的性能和内存效率。

全局哈希表:一切的起点

你可以把整个 Redis 数据库想象成一个巨大的 字典(Dict),也就是哈希表(Hash Table)

  • Key:就是你写入数据时用的字符串键,比如 user:1000:name。

  • Value不是一个简单的字符串,而是一个叫 redisObject 的结构体指针。这个 redisObject 才是真正承载数据的地方。

这个全局哈希表让 Redis 可以在 O(1) 时间复杂度内根据 Key 找到对应的 redisObject,这是它速度快的基础。

RedisObject:值的通用包装器

Redis 中的所有数据值,无论是字符串、列表还是集合,都不是直接存储的,而是被包装在一个叫做 redisObject 的结构里。它的定义简化后如下:

typedef struct redisObject {unsigned type:4;        // 数据类型(5大基础类型)unsigned encoding:4;    // 底层使用的编码(具体数据结构)unsigned lru:24;        // 最近一次访问时间/LRU信息(用于内存淘汰)int refcount;           // 引用计数(用于内存回收)void *ptr;              // 指向实际底层数据结构的指针
} robj;

这个结构体的两个最关键字段是:

  • type:表示这个对象对外展现的数据类型,也就是我们常说的 5 种基础类型

    • REDIS_STRING

    • REDIS_LIST

    • REDIS_HASH

    • REDIS_SET

    • REDIS_ZSET

  • encoding:表示这个对象在内部真正使用的底层数据结构编码。这才是 Redis 设计的精妙之处:同一种 type 可以根据数据的大小和特征,采用不同的 encoding 来实现,以达到效率和空间的最优平衡。

ptr 指针则根据 encoding 的不同,指向不同的底层数据结构。

底层数据结构(encoding):真正的引擎

以下是 Redis 使用的几种核心底层数据结构,它们才是数据最终被存储的形式。

1.简单动态字符串 (SDS - Simple Dynamic String)

  • 用于:所有字符串类型的 Key 和 Value(当它是字符串时),以及各种其他结构中的字符串元素。

  • 特点:相比 C 原生字符串,SDS 具有:

    • O(1) 时间复杂度获取字符串长度

    • 避免缓冲区溢出

    • 减少修改字符串时带来的内存重分配次数(通过预分配空间和惰性空间释放)。

    • 二进制安全,可以存储任何二进制数据,而不仅仅是文本。

2.字典 (Dict)

  • 用于:全局哈希表、Hash 类型的数据结构。

  • 特点:就是哈希表的实现。使用链地址法解决哈希冲突。包含两个哈希表,用于渐进式 Rehash(在扩容时避免服务阻塞)。

3.跳跃表 (SkipList)

  • 用于:ZSET(有序集合)的底层实现之一。

  • 特点:一种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。平均查找复杂度为 O(log N)。可以高效地进行范围查询。

4.压缩列表 (ZipList)

  • 用于:List、Hash、ZSET 在元素数量较少、元素值较小时使用的编码方式。

  • 特点:是一块连续的内存空间,元素之间紧挨着存储,没有冗余指针,非常节省内存。但由于是连续空间,修改操作可能引发连锁更新,所以只适用于小数据量场景。

5.快速列表 (QuickList)

  • 用于:List 类型的主要底层实现。

  • 特点:是 Redis 3.2 之后引入的。它是 ZipList 和双向链表的结合体。一个 QuickList 是一个由多个 ZipList 节点组成的双向链表。它平衡了 ZipList 的内存效率和链表修改效率高的优点。

6.整数集合 (IntSet)

  • 用于:Set 类型在元素全是整数且数量较少时的底层实现。

  • 特点:是一块连续的内存空间,有序地存储所有的整数元素。查找效率高(二分查找),且非常节省内存。

数据类型与底层结构的映射关系

现在,我们把 typeencoding 联系起来,看看一个 SET mykey “Hello”ZADD myzset 100 “member1” 在内部到底是如何存储的。
|

对外数据类型 (type)条件(大致)底层编码 (encoding)说明
STRING (字符串)数值型字符串INT内部用长整型存储,减少空间
普通字符串,且长度 <= 44 字节EMBSTR字符串和 redisObject 分配在同一块连续内存中
普通字符串,且长度 > 44 字节RAW典型的 SDS 动态字符串
LIST (列表)老版本:元素小且少ZIPLIST使用压缩列表
新版本(3.2+)一律使用QUICKLIST使用快速列表(默认)
HASH (哈希)字段数量少(hash-max-ziplist-entries)且所有值长度小(hash-max-ziplist-value)ZIPLIST所有字段和值在同一个压缩列表中紧挨着存储
不满足上述条件HT (Hashtable)使用字典(哈希表)存储
SET (集合)元素全是整数,且数量少(set-max-intset-entries)INTSET使用整数集合
不满足上述条件HT (Hashtable)使用字典(哈希表),但字典的 value 全部设为 NULL,只有 key 有用
ZSET (有序集合)元素数量少(zset-max-ziplist-entries)且所有成员长度小(zset-max-ziplist-value)ZIPLIST成员和分值在压缩列表中交替存储,并按分值排序
不满足上述条件SKIPLIST组合结构:同时使用跳跃表(用于范围查询和排序)和字典(用于 O(1) 分值查找)

小结

在这里插入图片描述

  • 全局哈希索引:保证根据 Key 的查找速度极快。

  • redisObject 抽象层:统一管理所有数据类型的内存分配、回收、共享等信息。

  • 多编码适配:一种数据类型对应多种底层实现,根据数据的具体情况(大小、类型、数量)智能选择最节省内存或最高效的结构。这种空间换时间时间换空间的权衡是 Redis 高效的关键。

  • 高性能数据结构:大量使用如跳跃表、SDS、压缩列表等精心设计的数据结构。

正是这种复杂而精巧的内部分层设计,使得 Redis 能够对外提供简单易用的接口,同时 internally (在内部) 保持极致的性能和高效的内存利用。

因此,在 Linux 系统编程中,当你需要解决性能瓶颈、分布式协调或实时数据处理等问题时,Redis 是一个非常强大且常用的工具。它更像是一个构建复杂、高性能系统的“多功能组件”,而非一个传统的磁盘数据库。

http://www.dtcms.com/a/393233.html

相关文章:

  • 【OpenGL】绘制彩色立方体
  • 21.继承与混入
  • Python 开发!ImprovePdf 用算法提升PDF清晰度,免费开源工具
  • P1879 [USACO06NOV] Corn Fields G-提高+/省选-
  • 二分答案:跳石头
  • 注解学习,简单测试框架
  • python 自定义装饰器 + 框架
  • 【星闪】Hi2821 | KEYSCAN矩阵按键扫描
  • ​​[硬件电路-282]:PWM信号通用解读、应用场景及在步进电机中的应用详解
  • 给链表装上“后视镜”:深入理解双向链表的自由与高效
  • Off-Grid Direction of Arrival Estimation Using Sparse Bayesian Inference (II)
  • Unity中的渲染管线
  • PyMuPDF 库
  • 【故障】windows7开机后能出现windows启动界面,但停在黑屏很久才进入系统界面
  • tqdm 库
  • 模块化编程规范与分层设计指南
  • Photoshop - Photoshop 调整照片的颜色强度
  • 【ROS2】通讯机制 Topic 常用命令行
  • DeepSeek的GPU优化秘籍:解锁大规模AI训练的底层效率
  • Gemini in Chrome深度解析:反垄断胜诉后,Chrome开启AI智能浏览时代!
  • 如何修复 Google Chrome 上的白屏问题
  • Camera2原生api级 Demo答
  • 【Redis】Redis缓存与数据库DB数据如何保持同步?
  • 考研408---C语言复习
  • 批量抓取图片
  • WinDivert学习文档之五-————编程API(十一)
  • 【打印菱形】
  • XC7Z100-2FFG900I Xilinx AMD Zynq-7000 FPGA SoC
  • 成本价的SEO优化服务供应链
  • dock生命周期体验-生到死的命令