惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

aimingoo的专栏
aimingoo的专栏
博客园 - 三生石上(FineUI控件)
GbyAI
GbyAI
大猫的无限游戏
大猫的无限游戏
M
MIT News - Artificial intelligence
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
Engineering at Meta
Engineering at Meta
I
InfoQ
T
Tailwind CSS Blog
N
Netflix TechBlog - Medium
S
SegmentFault 最新的问题
H
Help Net Security
博客园 - 【当耐特】
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
美团技术团队
博客园 - 叶小钗
T
The Blog of Author Tim Ferriss
腾讯CDC
雷峰网
雷峰网
Martin Fowler
Martin Fowler
The GitHub Blog
The GitHub Blog
D
Docker

博客园 - 邓磊Lei

我可以双击 shift 打开 visual studio 的代码搜索 我决定把 gittoy 更名为 git blame for vs 为了可以重新拥抱 Visual Studio, 我做了一个 vs 版的 gittoolbox ABP DTO 与对象映射:AutoMapper Profile 配置与 Mapperly 源生成器 ABP 应用服务源码解读:ApplicationService 基类内置了哪些能力 ABP 仓储模式源码解读:自动过滤是如何实现的 ABP DDD 实体源码解读:7 层基类的设计逻辑和适用场景 ABP 模块系统源码学习:启动时模块是如何加载和排序的 gin怎么处理一次http请求的 我的经验:git提交信息分别用什么icon C# 也能像 Python 一样写脚本 | .NET 10 构建基于文件的应用 .NET 10 使用 Microsoft.AspNetCore.OpenApi 实现 API 版本管理 ASP.NET Core 内存缓存实战:一篇搞懂该怎么配、怎么避坑 Microsoft Agent Framework + Kimi API 实战:控制台应用跑通单次与多轮 Agent 对话 开发实战:asp.net core + ef core 实现动态可扩展的分页方案 聊聊 ASP.NET Core 中间件和过滤器的区别 Python 入门:从“其他语言”到 Pythonic 思维的完整迁移手册 .NET 进阶之路:异步、并发与内存管理的系统性认知 Redis:延迟双删的适用边界与落地细节 Serilog:从结构化日志认知到 .NET 工程落地 EF Core 原生 SQL 实战:FromSql、SqlQuery 与对象映射边界 EF Core 拦截器实战:SaveChangesInterceptor、CommandInterceptor 与审计落地 ASP.NET Core 外部依赖调用治理实战:HttpClientFactory、Polly 与幂等边界 .NET .Result 避坑指南:不同框架下的死锁与线程池饥饿 EF Core 慢查询排查实战:TagWith、OpenTelemetry、执行计划,30 分钟定位性能瓶颈 EF Core 并发冲突实战:乐观锁、RowVersion 与 DbUpdateConcurrencyException 怎么处理 EF Core 写入链路深拆:从 ChangeTracker 到 SQL Batch 的性能诊断与优化 ASP.NET Core 认证鉴权实战:JWT、Policy 与权限边界怎么落地 C# 异步编程深水区:Task、ValueTask、线程池饥饿与背压设计 EF Core 查询性能黑洞:Include、投影与跟踪策略的边界
为什么 Go 一个 HTTP 服务可以同时处理数万连接
邓雷 · 2026-07-16 · via 博客园 - 邓磊Lei

Why: 一个 Go 服务为什么可以同时处理几万甚至几十万连接?

答案并不复杂,Go net/http 的实现采用了每个 TCP 连接对应一个 Goroutine 的模型。

服务启动流程

一个 HTTP 服务通常从下面这行代码开始:

http.ListenAndServe(":8080", nil)

它内部的调用流程如下:

go-http-server-can-handle-thousands-of-connections

核心代码:

	for {
		rw, err := l.Accept()
		if err != nil {
			if s.shuttingDown() {
				return ErrServerClosed
			}
			if ne, ok := err.(net.Error); ok && ne.Temporary() {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				s.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return err
		}
		connCtx := ctx
		if cc := s.ConnContext; cc != nil {
			connCtx = cc(connCtx, rw)
			if connCtx == nil {
				panic("ConnContext returned nil")
			}
		}
		tempDelay = 0
		c := s.newConn(rw)
		c.setState(c.rwc, StateNew, runHooks) // before Serve can return
		go c.serve(connCtx)
	}

流程非常简单:

  1. 监听端口。
  2. 不断调用 Accept() 接收新的 TCP 连接。
  3. 每接收到一个连接,就启动一个新的 Goroutine 执行 conn.serve()

因此,一个 TCP 连接对应一个 conn.serve Goroutine。


为什么可以支持数万连接?

假设:

  • 有 10000 个客户端在线。
  • 每个客户端保持一个 Keep-Alive 连接。

那么服务端大约会维护:

10000 TCP Connection
≈
10000 conn.serve Goroutine

Go 的 Goroutine 初始栈只有几 KB,并由运行时负责调度,因此创建数万个 Goroutine 的成本远低于传统线程模型。

这也是 Go 非常适合编写网络服务器的重要原因之一。

总结一下

在 http1.1 前提,开启 Keep-Alive 的情况下:

  • 每一个tcp连接一个conn.serve goroutine
  • 浏览器通常限制同域约6条TCP并发
  • 服务端 goroutine 数量约等于所有客户端 TCP 连接总和,比如 1万用户 x 6 约等于 6 万TCP连接,约等于6万 groutine

keep-alive 价值

  • 减少 TCP 三次握手
  • 减少 TLS 握手(HTTPS)
  • 减少 TIME_WAIT
  • 减少 socket 创建关闭
  • 减少 conn.serve goroutine 的创建销毁
  • 提高连接复用率,降低延迟