目录
- 答辩中的遗存问题
- 一、进程
- 1. 进程间通信 IPC
- 2. 虚拟内存
- 二、MySQL底层架构
- 2.1 Server层
- 2.2 存储引擎层
- 2.3 InnoDB 存储结构
- 三、Redis底层架构
- 3.1 整体架构分层
- 3.2 为什么Redis这么快
- 四、零拷贝技术(sendfile/mmap)
- 4.1 传统IO的性能瓶颈
- 4.2 mmap + write
- 4.3 sendfile 零拷贝
- 项目中的问题分析
- 一、发送消息慢问题现象
- 1. 根因分析
- 2. 优化方案
- 二、发送文件慢问题现象
- 1. 根因分析
- 2. 优化方案
答辩中的遗存问题
一、进程
进程是操作系统资源分配的基本单位,是程序的一次运行实例。每个进程拥有独立的虚拟地址空间,包含代码段、数据段、堆、栈等资源,进程间相互隔离。
线程是CPU调度的基本单位,同一进程内的多个线程共享进程的地址空间与系统资源,切换开销远小于进程。
1. 进程间通信 IPC
为了解决进程地址空间隔离、无法直接交换数据的问题。实现分为两大思想:共享内存和消息传递。
1.1 共享内存
多个进程直接映射同一块物理内存区域,数据不需要在内核态与用户态之间来回拷贝,是速度最快的IPC方式。
- 优势:数据直接读写内存,无额外拷贝,性能最高。
- 劣势:进程间没有访问隔离,必须配合同步机制保证数据安全。
需要配合的同步机制: - 信号量:本质是计数器,用于控制共享资源的并发访问数量。分为二元信号量和计数信号量;通过P操作申请资源、V操作释放资源。
- 互斥锁:保证同一时刻只有一个进程进入临界区访问共享内存。
- 条件变量:实现进程间的等待-通知机制,必须与互斥锁配合使用。
1.2 消息传递
(1)管道
- 匿名管道
pipe:数据只能单向流动;仅能用于有亲缘关系的进程,通过fork继承文件描述符实现;生命周期随进程结束而销毁;数据是无格式字节流。 - 命名管道
mkfifo:支持无亲缘关系的任意进程通信,可持久存在。
(2)消息队列
对比管道:自带消息边界、有数据格式,无需额外同步,生命周期随内核,进程退出后数据仍可保留。
(3)套接字Socket
- Unix域套接字:用于同一主机进程间通信,无需经过网络协议栈打包拆包、计算校验和,性能远高于网络套接字。接口与网络套接字一致
(socket/bind/listen/connect),仅地址结构不同。 - 网络套接字(TCP/UDP):基于TCP/IP协议栈实现跨主机通信,TCP是可靠字节流,UDP是不可靠数据报,是分布式系统通信的基础。
(4)信号
唯一的异步IPC方式,内核向进程发送事件通知,进程可注册处理函数响应信号。
- 标准信号:1~31号,不可靠信号;相同信号多次到达会合并,只处理一次,不保证传递顺序,不携带额外数据。
- 实时信号:34~64号,可靠信号;支持排队,每个信号都会被处理,保证按发送顺序到达,可携带附加数据。
2. 虚拟内存
虚拟内存是操作系统最核心的内存管理技术,为每个进程提供独立、连续、私有的虚拟地址空间,让进程"以为"自己独占全部内存。
核心作用
- 进程隔离:每个进程地址空间独立,一个进程崩溃不会影响其他进程,提升系统稳定性与安全性。
- 内存扩展:通过Swap交换分区,将不常用的内存页换出到磁盘,用磁盘空间模拟内存,让物理内存可以运行更多进程。
- 简化内存管理:进程无需关心物理内存的分配与碎片问题,统一使用虚拟地址,由操作系统完成映射。
- 实现原理:主流采用分页机制。将虚拟内存与物理内存都划分为固定大小的页,虚拟地址分为虚拟页号 + 页内偏移。通过页表维护虚拟页号到物理页号的映射关系。
二、MySQL底层架构
MySQL采用经典的插件式存储引擎架构,整体分为Server层与存储引擎层。
2.1 Server层
负责MySQL的通用逻辑与核心功能,与存储引擎无关,所有存储引擎共享这一层能力。
- 连接器:管理客户端连接,负责身份认证、权限校验,维持连接会话与连接池。
- 查询缓存:执行查询前先匹配缓存,命中则直接返回结果。
- 分析器:对SQL进行词法分析、语法分析,构建语法树,校验SQL语法与表、字段的合法性。
- 优化器:生成SQL执行计划,选择最优索引,决定表的连接顺序与执行方式,是SQL性能的关键环节。
- 执行器:根据执行计划调用存储引擎接口,真正执行SQL语句,遍历并返回结果集。
2.2 存储引擎层
负责数据的存储、读取、事务、锁等底层能力,以插件形式挂载到Server层。
- InnoDB:支持ACID事务、行级锁、外键约束;采用聚簇索引组织表,数据与主键索引存储在一起;适合高并发读写、事务型场景。
2.3 InnoDB 存储结构
InnoDB的存储单位从小到大依次为:
页(Page,默认16KB)→ 区(Extent,1MB,64个连续页)→ 段(Segment,分为数据段、索引段等)。
- 聚簇索引:主键索引的叶子节点直接存储完整行数据。
- 二级索引:叶子节点存储主键值,查询非主键字段需要回表。
三、Redis底层架构
Redis是高性能内存键值数据库,核心采用单线程命令执行 + IO多路复用模型。
3.1 整体架构分层
- 网络层:基于
epoll实现IO多路复用,单线程处理所有客户端的网络连接与命令请求,避免线程切换与锁竞争开销。 - 命令执行层:解析客户端命令,路由到对应处理函数,操作内存数据。
存储层:内存存储核心,支持多种数据类型,底层由高效的数据结构实现。
持久化层:提供RDB快照、AOF日志两种持久化机制,防止内存数据丢失。
3.2 为什么Redis这么快
- 纯内存操作:数据读写都在内存中完成,访问延迟低。
- 单线程执行命令:无多线程上下文切换开销,无锁竞争。
- IO多路复用模型:单线程可处理上万并发连接。
四、零拷贝技术(sendfile/mmap)
零拷贝是IO性能优化的核心技术,核心目标是减少CPU参与的数据拷贝次数,降低内核态与用户态的上下文切换开销,广泛应用于文件服务器、消息队列、网关等场景。
4.1 传统IO的性能瓶颈
使用read + write传输文件到网络时,会经历 4次数据拷贝 + 4次上下文切换:
- 第一步,DMA将磁盘数据拷贝到内核缓冲区(内核态)。
- 第二步,CPU将内核缓冲区数据拷贝到用户缓冲区(用户态,上下文切换)。
- 第三步,CPU将用户缓冲区数据拷贝到内核Socket缓冲区(内核态,上下文切换)。
- 第四步,DMA将Socket缓冲区数据拷贝到网卡发送。
- 性能损耗核心:两次不必要的CPU拷贝,以及频繁的状态切换。
4.2 mmap + write
零拷贝mmap将内核缓冲区的地址映射到用户空间,用户进程可以直接操作内核缓冲区,省去内核到用户的CPU拷贝。
- 第一步,mmap 调用:DMA将磁盘数据拷贝到内核缓冲区,用户空间与内核空间共享该缓冲区。
- 第二步,
write调用:CPU直接将内核缓冲区数据拷贝到Socket缓冲区。 - 第三步,DMA拷贝到网卡发送。
- 拷贝次数:3次(2次DMA拷贝 + 1次CPU拷贝)
4.3 sendfile 零拷贝
- 第一步,sendfile调用:DMA将磁盘数据拷贝到内核缓冲区。
- 第二步,CPU仅向Socket缓冲区传递文件描述符与数据长度(无数据拷贝)。
- 第三步,DMA根据描述符直接将内核缓冲区数据拷贝到网卡。
- 拷贝次数:2次(2次DMA拷贝,0次CPU拷贝)。
项目中的问题分析
一、发送消息慢问题现象
消息推送模块初期,每条消息直接同步写入MySQL,高并发下消息延迟高、吞吐低,甚至出现数据库连接超时。
1. 根因分析
- 第一,每次请求都新建数据库连接,频繁创建销毁连接开销大。
- 第二,单条消息单次写入MySQL,磁盘IO次数多,事务提交与行锁竞争严重。
- 第三,无流量缓冲,突发流量直接打到底层数据库,容易引发阻塞崩溃。
2. 优化方案
- 引入MySQL连接池:复用数据库连接,避免频繁创建销毁的开销;通过连接池控制最大连接数,防止连接数过多压垮数据库。
- Redis做异步缓冲 + 批量落库:消息先写入Redis(内存操作,毫秒级响应),立即返回成功,实现削峰填谷;后台启动定时消费线程,批量将Redis中的消息聚合后写入MySQL。
二、发送文件慢问题现象
文件传输功能使用传统 read + write 实现,大文件传输速度慢,服务器CPU占用率高,并发上传下载能力弱。
1. 根因分析
- 传统IO模型下,文件数据需要经过磁盘→内核→用户→内核→网卡的多次拷贝,CPU大量时间消耗在无意义的数据复制上;同时频繁的内核/用户态切换进一步放大系统开销。
2. 优化方案
使用sendfile,减少内核到用户的拷贝,减少上下午切换。