【Redis认证】Redis和MySQL认证机制深度剖析:从内核Socket通信到连接状态管理

作者介绍:大家好,我是 CodeStats。一个在底层技术上"考古"了四年的硬核爱好者,也是 WWAIC(全周项目AI编程)范式的提出者和实践者。我曾手写过一个完整的 Java Web 框架(从 IoC 容器到嵌入式 Tomcat,代码全开源),也喜欢用通俗的语言拆解 CPU、JVM、操作系统的运行本质。

📑 本文目录

  • 提问一:内核Socket通信全流程是怎样的?

  • 提问二:Redis和MySQL认证后,如何"记住"已登录状态?

  • 提问三:Redis单线程模型如何实现原子性操作?

  • 附加专题:Tomcat、Redis、MySQL的BIO与NIO模型对比


前言

我一直相信,计算机科学没有魔法 。所有看似神奇的效果------无论是 AUTH password 之后 Redis 就"记住"了你,还是 MySQL 登录后执行无数条 SQL 都无需再输密码------底层都是简单的规则层层组合。

今天,我们就从内核到应用,一层层把这些"魔法"拆解干净。

全文核心脉络 :网卡收到字节流 → 内核通过五元组找到连接 → 应用通过FD找到客户端上下文 → 上下文中存着"已认证"标记 → 后续请求直接放行。这就是认证状态持久化的全部秘密。


提问一:内核Socket通信全流程是怎样的?

1.1 从网卡到进程:数据包的"快递之旅"

当你在客户端执行 redis-cli SET name "zhangsan" 时,数据包经历了以下完整旅程:

第一步:用户态 → 内核态(发送端)

应用程序调用 write() 系统调用后,数据首先被写入 Socket发送缓冲区(SendQ) 。随后,数据依次流经:

  • 传输层:TCP协议对数据分段,添加TCP头部(包含源端口、目标端口、序列号等)

  • 网络层:IP协议添加IP头部(包含源IP、目标IP)

  • 链路层:添加以太网头部(包含MAC地址)

最终数据落入 网卡发送环形队列(Tx RingBuffer),由网卡真正发送出去。

第二步:网卡 → 内核(接收端)

数据包到达目标服务器的网卡后,触发硬件中断 ,通知内核有数据到达。内核通过五元组(协议 + 源IP + 源端口 + 目标IP + 目标端口)在连接跟踪表中查找对应的Socket。

内核将数据从网卡拷贝到内核空间的Socket接收缓冲区,然后应用程序通过 read() 系统调用将数据从内核态拷贝到用户态。

▶ 本段总结:一次网络请求的本质,是数据在"用户态缓冲区 → 内核态Socket缓冲区 → 网卡"之间的两次拷贝。内核是中间人,应用程序从不直接触碰网卡。

1.2 五元组:TCP连接的"身份证"

为什么服务器能同时处理成千上万个客户端的请求而不会搞混?答案就在五元组

组成部分 说明 示例
协议 TCP或UDP TCP
源IP 客户端IP 192.168.1.100
源端口 客户端随机分配的端口 52341
目标IP 服务器IP 192.168.1.200
目标端口 服务器监听的端口 6379 (Redis) / 3306 (MySQL)

源端口 是操作系统在调用 connect() 时,从临时端口范围(Linux上通常是32768~60999)中随机分配的一个未被占用的数字。它的作用是让服务器知道把响应数据回传给谁------就像快递单上的"寄件人地址",没有它,服务器就算处理完请求也不知道该把结果寄回哪里。

内核正是通过这五个要素,精准地将每个数据包投递到对应的进程和连接。

▶ 本段总结:五元组是内核区分不同连接的唯一凭证。客户端IP和端口共同构成"回信地址",让服务器能精准响应对应的客户端进程。

1.3 文件描述符(FD):内核给应用的"取号单"

当TCP三次握手完成,内核会为这个连接分配一个文件描述符(File Descriptor,FD) ,它是一个非负整数。内核维护着一张 FD → 五元组 的映射表。

应用程序(如Redis、MySQL)通过I/O多路复用机制(如Linux的epoll)监听这些FD。当某个FD上有数据可读时,内核通知应用程序。应用程序根据FD找到对应的客户端上下文,处理数据。

▶ 本段总结:内核通过五元组识别连接,但交给应用程序的是一张叫"FD"的取号单。应用不关心五元组细节,只凭FD号就能找到对应的客户端档案。


提问二:Redis和MySQL认证后,如何"记住"已登录状态?

2.1 Redis的认证机制:AUTH 命令与 redisClient 结构体

Redis的认证非常简单直接。当客户端发送 AUTH <password> 命令时:

  1. Redis服务器收到命令,将客户端传来的密码与配置文件 redis.conf 中的 requirepass 进行比对

  2. 如果密码正确,Redis会在内存中为该连接对应的 redisClient 结构体 中设置一个标志位

  3. 此后,该连接上的所有后续命令都会检查这个标志位,已认证则直接执行

关键点 :认证状态是绑定在TCP连接上的 。一旦认证通过,只要这个TCP连接不断开,客户端就无需再次发送 AUTH 命令。

如果用 telnet 连接Redis,每次 telnet 都会创建一个新的 TCP连接,因此需要重新执行 AUTH。而应用程序通过连接池维护的长连接,只需在连接建立时认证一次即可。

▶ 本段总结:Redis认证的本质,就是在内存中的客户端档案上盖一个"已认证"的戳。只要TCP连接还在,这个戳就一直有效。

2.2 MySQL的认证机制:挑战-响应与 THD 对象

MySQL的认证比Redis更安全,它采用了挑战-响应(Challenge-Response)机制

  1. 客户端发起TCP连接后,MySQL服务器发送一个随机的挑战码(Salt)

  2. 客户端将密码与挑战码一起通过哈希算法(如 caching_sha2_password)计算出一个响应值

  3. 服务器用自己存储的密码哈希值做同样的计算,比对结果

密码从不通过网络明文传输,这是MySQL认证安全性的核心。

认证通过后,MySQL会在内存中为该连接创建一个 THD(Thread Descriptor) 对象。这个对象包含了:

  • 认证状态(authenticated = true

  • 当前用户信息(Security_context

  • 当前默认数据库

  • 字符集设置

  • 事务状态

后续所有的SQL查询,MySQL都会通过FD找到对应的 THD 对象,检查其认证状态。只要TCP连接不断开,就不需要重新认证。

▶ 本段总结:MySQL用"挑战-响应"机制确保密码不在网络上明文传输,但认证通过后的状态管理逻辑与Redis完全一致------在内存对象中标记"已认证",后续请求凭FD找到这个对象直接放行。

2.3 对比总结

对比项 Redis MySQL
密码传输 明文(AUTH命令直接发送密码) 密文(挑战-响应机制,密码不出现在网络上)
认证状态存储 redisClient 结构体中的标志位 THD 对象中的 Security_context
状态绑定 绑定到TCP连接(FD) 绑定到TCP连接(FD)
认证一次管终身? ✅ 是(连接不断开就有效) ✅ 是(连接不断开就有效)

▶ 全文核心总结 :无论是Redis还是MySQL,认证状态都是绑定在TCP连接上的 。服务器通过FD找到对应的客户端上下文,检查其中的认证标志。这就是"登录一次,后续无需重复认证"的底层原理。一个连接一份档案,档案在,状态就在。


提问三:Redis单线程模型如何实现原子性操作?

3.1 单线程 = 天然原子性

Redis采用单线程事件循环(Event Loop) 作为其核心I/O处理模型。这意味着:

  • 在任何时刻,Redis服务器只执行一个命令

  • 所有命令按顺序执行,后一个命令必须等前一个命令执行完毕才能开始

  • 不存在并发冲突,因为根本没有多个线程同时操作数据

这就是Redis原子性的最根本保证:单线程模型天然避免了多线程环境下的锁竞争和竞态条件。

▶ 本段总结:原子性不是靠锁"加"出来的,而是靠单线程"天然免锁"。顺序执行即原子,这是Redis设计哲学的第一性原理。

3.2 事件循环(Event Loop)的工作原理

Redis的事件循环基于Reactor模式

text

复制代码
while (eventLoop未停止) {
    处理时间事件(定时任务、持久化等)
    处理文件事件(网络读写)
}

文件事件处理器通过I/O多路复用(Linux的epoll、macOS的kqueue)监听多个客户端连接上的可读/可写事件。当某个FD上有数据可读时,Redis读取数据、解析命令、执行命令、返回结果------整个过程都在同一个线程中完成

▶ 本段总结:I/O多路复用让单线程能监听海量连接,但真正执行命令时,依然是一个一个排队处理------没有并发,就没有冲突。

3.3 复合命令与Lua脚本:更复杂的原子操作

单线程只能保证单个命令的原子性。如果需要多个操作一起原子执行,Redis提供了三种方式:

① 复合指令

Redis内置了一些"一个顶多个"的复合指令,如:

  • MSET key1 value1 key2 value2:同时设置多个键值对

  • GETSET key new_value:设置新值并返回旧值

  • SETNX key value:键不存在时才设置(分布式锁的基础)

这些指令在Redis内部是一个完整的操作,不会被其他命令打断。

② Lua脚本

通过 EVALEVALSHA 执行一段Lua脚本,可以将GETIF判断、SET等多个操作打包成一个原子单元。脚本执行期间,Redis主线程被完全占用,不会执行任何其他客户端的命令。

③ MULTI/EXEC事务

Redis的事务通过 MULTI 开启排队,EXEC 一次性执行。但需要注意:Redis事务不支持回滚------如果EXEC后某个命令执行失败,其他命令依然会执行成功。

▶ 本段总结:单命令原子是"天生"的,多命令原子则需要"打包"------复合指令是官方打包,Lua脚本是自定义打包,事务是排队打包。打包方式不同,但本质都是让一组操作在单线程中连续执行、不被插队。

3.4 Pipeline ≠ 原子性

需要特别强调的是:Pipeline(管道)不具备原子性。Pipeline只是客户端将多个命令一次性发送给服务器,减少网络往返次数(RTT),但服务器仍然是逐个执行这些命令,中间可能被其他客户端的命令插队。

▶ 本段总结:Pipeline解决的是网络延迟问题,不是原子性问题。它让客户端少跑几趟路,但服务器该怎么排队还是怎么排队。


附加专题:Tomcat、Redis、MySQL的BIO与NIO模型对比

4.1 BIO(阻塞I/O):一连接一线程

BIO(Blocking I/O) 是最传统的I/O模型:线程发起I/O请求后,一直阻塞等待,直到数据就绪或操作完成。

Tomcat(传统BIO模式) :每个客户端连接对应一个线程。线程在读取请求或发送响应时被阻塞。优点是编程简单,缺点是线程数 = 连接数,高并发下线程资源耗尽。

MySQL :MySQL的连接处理更倾向于BIO模型。因为MySQL的性能瓶颈通常在磁盘I/O(查询、索引、事务日志),而非网络I/O。即使使用NIO,查询请求仍然要等待磁盘操作完成,对性能提升意义不大。

▶ 本段总结:BIO的代价是线程资源,优势是编程简单。MySQL选择BIO是因为它的真正瓶颈在磁盘,不在网络。

4.2 NIO(非阻塞I/O):一线程N连接

NIO(Non-blocking I/O) :发起I/O请求后立即返回,如果数据未就绪,返回"数据未就绪"状态,线程可以继续做其他事。

Tomcat(NIO模式) :使用Selector监听多个连接的事件,一个线程可以管理成千上万个连接。适合高并发Web场景。

Redis :Redis的网络层核心就是I/O多路复用 (本质是NIO的一种高效实现),通过epoll/kqueue让单线程处理海量连接。但Redis的处理逻辑(命令执行)是单线程的,这与Tomcat NIO的多线程处理业务逻辑有本质区别。

▶ 本段总结:NIO让一个线程管N个连接,大幅降低线程开销。Redis和Tomcat NIO都用了这个思路,但区别在于------Redis拿到数据后单线程处理,Tomcat拿到数据后丢给线程池处理。

4.3 对比总结

特性 Tomcat (BIO) Tomcat (NIO) MySQL Redis
I/O模型 BIO NIO BIO为主 NIO(多路复用)
线程模型 1连接1线程 1线程N连接 1连接1线程 单线程事件循环
瓶颈 线程数受限 业务逻辑处理 磁盘I/O CPU/内存
适用场景 低并发 高并发Web 事务型OLTP 高性能缓存

▶ 本段总结:选BIO还是NIO,本质是在"编程复杂度"和"资源利用率"之间做权衡。Redis用NIO+单线程实现了极致性能,MySQL用BIO是因为磁盘I/O才是它真正的瓶颈。


写在最后:层层递进的底层真相

现在,让我们把整篇文章的思考链条串起来:

第一层(物理世界) :网卡收到电信号,转化成字节流,触发硬件中断,内核开始接管。

第二层(内核世界) :内核提取五元组,在连接表中找到对应的Socket,把数据放入接收缓冲区,然后通过FD通知应用程序"有数据来了"。

第三层(应用世界) :Redis/MySQL收到内核的通知,根据FD找到内存中的客户端档案(redisClientTHD),检查档案里的认证标志------有戳就放行,没戳就拒绝。

第四层(逻辑世界) :认证通过后,命令进入执行阶段。Redis用单线程事件循环保证命令顺序执行、天然原子;MySQL用多线程配合行锁、MVCC来保证事务的ACID。

从网卡的电压高低,到认证标志的0和1------中间隔了五元组、Socket缓冲区、文件描述符、事件循环、客户端结构体五层抽象。每一层都只做一件简单的事,但层层叠加,就构成了我们今天使用的数据库系统。

计算机科学没有魔法。只有一层层叠起来的确定性。

📢 如果这篇文章帮到了你,欢迎:

  • 👍 点赞 让更多人看到

  • 收藏 方便随时回看

  • 👀 关注 CodeStats,一起在底层技术上"考古"

我们下篇见! 🚀