
本文介绍在 go 语言中通过单写 goroutine + 通道(channel)模式实现高并发 ajax 请求下的安全、非阻塞文件写入,避免竞态、数据错乱和响应延迟问题。
本文介绍在 go 语言中通过单写 goroutine + 通道(channel)模式实现高并发 ajax 请求下的安全、非阻塞文件写入,避免竞态、数据错乱和响应延迟问题。
在 Web 服务中,当多个 AJAX 请求需同时向同一文件追加日志或数据时,直接使用 sync.Mutex 虽可防止并发写入,但会将请求处理逻辑串行化——不仅无法真正解决 I/O 竞争,还严重拖慢响应速度;而仅靠进程内锁也无法阻止跨进程/重启后的写冲突,更无法规避操作系统层面的文件句柄竞争。
推荐方案:分离「接收」与「写入」职责,采用“单写 goroutine + 缓冲通道”架构
核心思想是:
✅ 文件仅由一个专用 goroutine 打开并持续持有;
✅ 所有 HTTP 处理器(如 ajaxHandler)只负责将待写数据发送至通道,不执行实际 I/O;
✅ 写入逻辑完全解耦,既保证文件操作绝对串行,又让请求响应近乎零等待。
以下为完整可运行示例:
package main
import (
"fmt"
"log"
"net/http"
"os"
)
var (
f *os.File
datach = make(chan string, 100) // 使用 string 类型更符合日志场景
)
func init() {
var err error
// 以追加模式打开文件(自动创建)
f, err = os.OpenFile("requests.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatal("无法打开日志文件:", err)
}
// 启动专属写入 goroutine
go func() {
defer f.Close() // 确保退出时关闭文件
for line := range datach {
if _, err := f.WriteString(line + "\n"); err != nil {
log.Printf("写入日志失败: %v", err)
// 注意:此处错误无法反馈给客户端,需结合监控告警
}
}
}()
}
func ajaxHandler(w http.ResponseWriter, r *http.Request) {
clientIP := r.RemoteAddr
timestamp := fmt.Sprintf("%s", r.URL.Query().Get("t"))
logEntry := fmt.Sprintf("[%s] %s - AJAX request at %s",
r.Method, clientIP, timestamp)
// 非阻塞发送:若缓冲区满,可考虑丢弃或返回 503(见下方注意事项)
select {
case datach <p><strong>关键设计说明:</strong> </p>
- 缓冲通道大小(make(chan string, 100)):需根据峰值 QPS 和单次写入耗时权衡。过小易触发 default 分支导致请求被拒;过大则增加内存占用与崩溃时丢失风险。建议配合 Prometheus 监控 len(datach) 实时水位。
- 错误处理边界:写入失败无法回传给客户端(HTTP 响应已发出),因此必须记录错误日志,并配置日志落盘失败告警。
- 优雅关闭:程序退出前应调用 close(datach),使写入 goroutine 正常退出并刷新缓冲区。可通过 signal.Notify 捕获 SIGINT/SIGTERM 实现。
- 扩展性提示:如需更高可靠性,可将通道数据持久化到 WAL(Write-Ahead Log)或切换至结构化日志系统(如 Loki + Promtail)。
此模式兼顾性能、安全与可维护性,是 Go 生态中处理高频写入场景的经典实践。











