# 查看磁盘 IO 统计
iostat -x 1
# 查看 IO 密集进程
iotop
# 查看文件系统缓存
free -h
# 查看某进程的 IO
pidstat -d 1 -p <pid>IO 瓶颈特征:
iowait(wa) 百分比高(> 20%)- 磁盘 util% 接近 100%
- 平均响应时间 (await) 高
- 进程处于 D 状态(不可中断睡眠)
性能差异:
- 顺序读写:~500 MB/s(HDD)、~3000 MB/s(SSD)
- 随机读写:~1 MB/s(HDD)、~300 MB/s(SSD)
import (
"bufio"
"os"
)
// ❌ 随机小 IO:每次写 1 字节
func writeSlow(filename string, data []byte) error {
file, _ := os.Create(filename)
defer file.Close()
for _, b := range data {
file.Write([]byte{b}) // 每次系统调用
}
return nil
}
// ✅ 批量顺序 IO:使用缓冲
func writeFast(filename string, data []byte) error {
file, _ := os.Create(filename)
defer file.Close()
writer := bufio.NewWriterSize(file, 64*1024) // 64KB 缓冲
defer writer.Flush()
writer.Write(data) // 一次写入
return nil
}性能对比(写入 1MB 数据):
- 小 IO:5000ms
- 批量 IO:50ms
- 性能提升:100x
# 查看预读大小
blockdev --getra /dev/sda
# 设置预读(单位:512字节扇区)
blockdev --setra 8192 /dev/sda # 4MB 预读应用层预读:
import "io"
// ✅ 预读优化
func readWithPrefetch(filename string, offset int64, size int) ([]byte, error) {
file, _ := os.Open(filename)
defer file.Close()
// 预读更大范围(2倍)
prefetchSize := size * 2
reader := bufio.NewReaderSize(file, prefetchSize)
file.Seek(offset, io.SeekStart)
data := make([]byte, size)
_, err := reader.Read(data)
return data, err
}适用场景:
- 自己管理缓存的数据库
- 大文件顺序读写
- 避免污染系统缓存
import (
"os"
"syscall"
)
// ✅ Direct IO 打开文件
func openDirectIO(filename string) (*os.File, error) {
return os.OpenFile(filename,
os.O_RDWR|syscall.O_DIRECT, 0666)
}
// 使用示例(需要对齐)
func writeDirectIO(filename string, data []byte) error {
file, _ := openDirectIO(filename)
defer file.Close()
// Direct IO 需要扇区对齐(512 字节)
alignedSize := (len(data) + 511) & ^511
alignedData := make([]byte, alignedSize)
copy(alignedData, data)
_, err := file.Write(alignedData)
return err
}import (
"net"
"os"
"syscall"
)
// ✅ 零拷贝发送文件
func sendFileZeroCopy(conn net.Conn, filename string) error {
file, _ := os.Open(filename)
defer file.Close()
stat, _ := file.Stat()
fileSize := stat.Size()
// 获取 socket fd
tcpConn := conn.(*net.TCPConn)
connFile, _ := tcpConn.File()
defer connFile.Close()
// sendfile 系统调用(零拷贝)
_, err := syscall.Sendfile(
int(connFile.Fd()),
int(file.Fd()),
nil,
int(fileSize),
)
return err
}性能对比(发送 100MB 文件):
- 传统方式(read + write):200ms,2 次拷贝
- sendfile(零拷贝):80ms,0 次拷贝
- 性能提升:2.5x
import (
"os"
"syscall"
)
// ✅ mmap 读取大文件
func readFileMmap(filename string) ([]byte, error) {
file, _ := os.Open(filename)
defer file.Close()
stat, _ := file.Stat()
size := int(stat.Size())
// mmap 映射文件到内存
data, err := syscall.Mmap(
int(file.Fd()),
0,
size,
syscall.PROT_READ,
syscall.MAP_SHARED,
)
if err != nil {
return nil, err
}
defer syscall.Munmap(data)
// 直接读取内存
result := make([]byte, size)
copy(result, data)
return result, nil
}适用场景:
- 随机访问大文件
- 共享内存通信
- 需要高性能的文件读写
import (
"golang.org/x/sys/unix"
"syscall"
)
// ✅ epoll 实现高性能网络服务
type EPollServer struct {
epollFd int
events []unix.EpollEvent
}
func NewEPollServer() (*EPollServer, error) {
epollFd, err := unix.EpollCreate1(0)
if err != nil {
return nil, err
}
return &EPollServer{
epollFd: epollFd,
events: make([]unix.EpollEvent, 128),
}, nil
}
func (s *EPollServer) AddConn(fd int) error {
event := unix.EpollEvent{
Events: unix.EPOLLIN | unix.EPOLLET, // 边缘触发
Fd: int32(fd),
}
return unix.EpollCtl(s.epollFd, unix.EPOLL_CTL_ADD, fd, &event)
}
func (s *EPollServer) Wait() ([]int, error) {
n, err := unix.EpollWait(s.epollFd, s.events, -1)
if err != nil {
return nil, err
}
fds := make([]int, n)
for i := 0; i < n; i++ {
fds[i] = int(s.events[i].Fd)
}
return fds, nil
}性能对比:
- select:支持 1024 个连接
- epoll:支持 100000+ 个连接
- C10K 问题解决方案
import "net"
// ✅ 禁用 Nagle 算法,立即发送
func setNoDelay(conn *net.TCPConn) error {
return conn.SetNoDelay(true)
}
// ✅ 批量发送减少系统调用
func batchWrite(conn net.Conn, messages [][]byte) error {
// 合并多个消息
var buffer []byte
for _, msg := range messages {
buffer = append(buffer, msg...)
}
// 一次发送
_, err := conn.Write(buffer)
return err
}import (
"net/http"
"time"
)
// ✅ HTTP 客户端连接池
var httpClient = &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100, // 最大空闲连接
MaxIdleConnsPerHost: 10, // 每个 host 的空闲连接
IdleConnTimeout: 90 * time.Second, // 空闲超时
DisableKeepAlives: false, // 启用 Keep-Alive
},
Timeout: 30 * time.Second,
}
func makeRequest(url string) (*http.Response, error) {
return httpClient.Get(url) // 复用连接
}性能对比:
- 短连接:建立连接 2ms + 请求 10ms = 12ms/req
- 长连接:请求 10ms = 10ms/req
- 性能提升:20%
import (
"io"
"net"
)
// ✅ splice 零拷贝(Linux)
func proxySplice(client, server net.Conn) error {
clientFile, _ := client.(*net.TCPConn).File()
serverFile, _ := server.(*net.TCPConn).File()
defer clientFile.Close()
defer serverFile.Close()
// pipe 创建内核缓冲区
r, w, _ := os.Pipe()
defer r.Close()
defer w.Close()
go func() {
// client -> pipe (splice)
syscall.Splice(
int(clientFile.Fd()),
nil,
int(w.Fd()),
nil,
1024*1024,
0,
)
}()
// pipe -> server (splice)
syscall.Splice(
int(r.Fd()),
nil,
int(serverFile.Fd()),
nil,
1024*1024,
0,
)
return nil
}特点:
- 真正的异步 IO
- 无需线程池
- 性能极高
// 使用第三方库:github.com/iceber/iouring-go
import "github.com/iceber/iouring-go"
// ✅ io_uring 异步读取
func readAsyncIOUring(filename string) ([]byte, error) {
ring, _ := iouring.New(128)
defer ring.Close()
file, _ := os.Open(filename)
defer file.Close()
buf := make([]byte, 4096)
// 提交异步读请求
request := iouring.Read(int(file.Fd()), buf)
ring.QueueSQE(request)
// 等待完成
_, err := ring.WaitCQEvents(1)
return buf, err
}性能对比(随机读取 1000 个 4KB 文件):
- 同步 IO:500ms
- AIO(线程池):250ms
- io_uring:100ms
- 性能提升:5x
import (
"context"
"sync"
)
// ✅ Goroutine 实现异步 IO
type AsyncReader struct {
pool *sync.Pool
}
func NewAsyncReader() *AsyncReader {
return &AsyncReader{
pool: &sync.Pool{
New: func() interface{} {
return make([]byte, 4096)
},
},
}
}
func (r *AsyncReader) ReadAsync(filename string) <-chan Result {
resultCh := make(chan Result, 1)
go func() {
buf := r.pool.Get().([]byte)
defer r.pool.Put(buf)
data, err := os.ReadFile(filename)
resultCh <- Result{Data: data, Err: err}
close(resultCh)
}()
return resultCh
}
type Result struct {
Data []byte
Err error
}
// 使用示例
func example() {
reader := NewAsyncReader()
// 并发读取多个文件
results := make([]<-chan Result, 0)
for _, filename := range filenames {
results = append(results, reader.ReadAsync(filename))
}
// 等待所有结果
for _, resultCh := range results {
result := <-resultCh
// 处理结果...
}
}import "bufio"
// ❌ 默认缓冲(4KB)
func readDefault(filename string) ([]byte, error) {
file, _ := os.Open(filename)
defer file.Close()
reader := bufio.NewReader(file) // 默认 4KB
return io.ReadAll(reader)
}
// ✅ 大缓冲(1MB)
func readLargeBuffer(filename string) ([]byte, error) {
file, _ := os.Open(filename)
defer file.Close()
reader := bufio.NewReaderSize(file, 1024*1024) // 1MB
return io.ReadAll(reader)
}性能对比(读取 100MB 文件):
- 4KB 缓冲:500ms
- 1MB 缓冲:200ms
- 性能提升:2.5x
// ✅ 双缓冲:边读边处理
func processLargeFile(filename string) error {
file, _ := os.Open(filename)
defer file.Close()
const bufferSize = 64 * 1024
buffers := [2][]byte{
make([]byte, bufferSize),
make([]byte, bufferSize),
}
current := 0
resultCh := make(chan []byte, 1)
go func() {
for {
n, err := file.Read(buffers[current])
if err != nil {
close(resultCh)
return
}
// 发送当前缓冲
data := make([]byte, n)
copy(data, buffers[current][:n])
resultCh <- data
// 切换缓冲
current = 1 - current
}
}()
// 并行处理
for data := range resultCh {
processData(data) // 处理数据
}
return nil
}# 查看当前调度器
cat /sys/block/sda/queue/scheduler
# 设置调度器
echo "deadline" > /sys/block/sda/queue/scheduler
# 调度器对比:
# - noop:适合 SSD(无需排序)
# - deadline:低延迟优先
# - cfq:公平调度(默认)
# - bfq:低延迟 + 公平# 设置 IO 优先级(ionice)
ionice -c 2 -n 0 ./my_app # 最高优先级
# IO 类别:
# 0 - 未设置
# 1 - 实时(RT)
# 2 - 尽力而为(BE,默认)
# 3 - 空闲(Idle)Go 代码设置 IO 优先级:
import "syscall"
// ✅ 设置进程 IO 优先级
func setIOPriority(priority int) error {
const IOPRIO_CLASS_BE = 2
const IOPRIO_WHO_PROCESS = 1
ioprio := (IOPRIO_CLASS_BE << 13) | priority
_, _, err := syscall.Syscall(
syscall.SYS_IOPRIO_SET,
IOPRIO_WHO_PROCESS,
0, // 当前进程
uintptr(ioprio),
)
if err != 0 {
return err
}
return nil
}场景:日志写入 QPS 从 1000 提升到 100000
优化步骤:
# iostat 发现大量随机写
iostat -x 1
# 发现每条日志都单独写文件
strace -p <pid> 2>&1 | grep writeimport (
"bufio"
"sync"
"time"
)
// ✅ 批量写入日志
type BatchLogger struct {
file *os.File
writer *bufio.Writer
mu sync.Mutex
buffer []string
ticker *time.Ticker
}
func NewBatchLogger(filename string) *BatchLogger {
file, _ := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
logger := &BatchLogger{
file: file,
writer: bufio.NewWriterSize(file, 1024*1024), // 1MB 缓冲
buffer: make([]string, 0, 1000),
ticker: time.NewTicker(100 * time.Millisecond),
}
// 定时刷新
go logger.flushLoop()
return logger
}
func (l *BatchLogger) Log(msg string) {
l.mu.Lock()
l.buffer = append(l.buffer, msg)
// 满 1000 条立即刷新
if len(l.buffer) >= 1000 {
l.flushLocked()
}
l.mu.Unlock()
}
func (l *BatchLogger) flushLoop() {
for range l.ticker.C {
l.mu.Lock()
l.flushLocked()
l.mu.Unlock()
}
}
func (l *BatchLogger) flushLocked() {
if len(l.buffer) == 0 {
return
}
for _, msg := range l.buffer {
l.writer.WriteString(msg)
l.writer.WriteByte('\n')
}
l.writer.Flush()
l.buffer = l.buffer[:0]
}性能对比:
- 优化前:1000 QPS,iowait 50%
- 优化后:100000 QPS,iowait 5%
- 性能提升:100x
场景:大文件传输从 100 MB/s 提升到 1 GB/s
// ✅ 零拷贝 + 并行传输
func transferFileFast(src, dst string) error {
srcFile, _ := os.Open(src)
defer srcFile.Close()
dstFile, _ := os.Create(dst)
defer dstFile.Close()
stat, _ := srcFile.Stat()
fileSize := stat.Size()
// 并行传输多个块
const chunkSize = 64 * 1024 * 1024 // 64MB
numChunks := (fileSize + chunkSize - 1) / chunkSize
var wg sync.WaitGroup
for i := int64(0); i < numChunks; i++ {
wg.Add(1)
go func(chunkID int64) {
defer wg.Done()
offset := chunkID * chunkSize
size := chunkSize
if offset+size > fileSize {
size = fileSize - offset
}
// 使用 sendfile 零拷贝
syscall.Sendfile(
int(dstFile.Fd()),
int(srcFile.Fd()),
&offset,
int(size),
)
}(i)
}
wg.Wait()
return nil
}- 是否合并小 IO 为大 IO?
- 是否使用了合适的缓冲区大小?
- 是否优化了顺序访问模式?
- 是否使用了零拷贝技术?
- 是否使用了 Direct IO(适用场景)?
- 是否使用了 mmap(大文件随机访问)?
- 是否使用了 IO 多路复用?(epoll)
- 是否使用了连接池?
- 是否启用了 Keep-Alive?
- 是否批量发送数据?
- 是否使用了零拷贝?(sendfile)
- 是否优化了 TCP 参数?
- 是否使用了异步 IO?(io_uring)
- 是否使用了 Goroutine 并发?
- 是否使用了双缓冲机制?
核心要点:
- ✅ 批量操作:合并小 IO,减少系统调用
- ✅ 零拷贝:sendfile、mmap、splice 减少内存拷贝
- ✅ 异步 IO:io_uring、Goroutine 提升并发能力
- ✅ 缓冲优化:使用合适的缓冲区大小
- ✅ 顺序访问:优化访问模式,利用预读
优化优先级:
批量IO > 零拷贝 > 异步IO > 缓冲优化 > 调度优化