Golang Gorilla WebSocket客户端无法加入Map池的负载测试问题
问题原因及解决办法
核心原因1:连接池的通道处理瓶颈
你的pool.Start()是单goroutine串行处理Connect和Disconnect事件,如果pool.Connect是无缓冲通道,serveWs函数中pool.Connect <- client会被阻塞,直到pool.Start()里的select接收这个客户端对象。当大量并发连接请求过来时,单goroutine的处理速度远跟不上连接创建的速度,导致后续的WebSocket升级请求被阻塞,无法建立新连接,最终表现为连接数停滞。
核心原因2:操作系统文件句柄限制
每个WebSocket连接本质是TCP连接,会占用一个系统文件句柄。Linux/macOS默认的进程文件句柄上限通常是1024(可通过ulimit -n查看),当服务端进程占用的文件句柄接近这个上限时,无法创建新的TCP连接,导致连接数无法继续增长。
原因3:k6客户端侧的资源限制
如果运行k6的机器本身存在CPU、内存或文件句柄限制,k6无法创建足够多的并发WebSocket客户端,也会导致服务端接收的连接数达不到预期。
解决办法
1. 优化连接池的通道处理
- 给
pool.Connect和pool.Disconnect通道添加缓冲:根据预期的并发峰值设置合理的缓冲大小,比如make(chan *Client, 1000),暂时容纳待处理的连接请求,避免serveWs被阻塞。 - 并行处理连接事件:启动多个worker goroutine处理连接事件,提升处理效率。同时要给
pool.Clients这个map添加互斥锁,避免并发读写的竞态问题:import "sync" type Pool struct { Clients map[*Client]bool Connect chan *Client Disconnect chan *Client mu sync.RWMutex // 添加互斥锁 } func (pool *Pool) Start() { // 启动10个worker并行处理事件 for i := 0; i < 10; i++ { go func() { for { select { case client := <-pool.Connect: pool.mu.Lock() pool.Clients[client] = true pool.mu.Unlock() fmt.Println("Size of Connection Pool: ", len(pool.Clients), " & ", client.Id) case client := <-pool.Disconnect: client.UnsubChan <- true pool.mu.Lock() delete(pool.Clients, client) pool.mu.Unlock() fmt.Println("Size of Connection Pool: ", len(pool.Clients), " & ", client.Id) } } }() } }
2. 调整操作系统文件句柄限制
- 临时调整:启动服务端前执行
ulimit -n 65535(Linux/macOS),将进程文件句柄上限调高到65535。 - 永久调整:Linux下修改
/etc/security/limits.conf,添加以下内容后重启系统或重新登录:* soft nofile 65535 * hard nofile 65535
3. 优化k6测试配置
- 确保运行k6的机器有足够的CPU、内存资源,同时调高其文件句柄限制(和服务端调整方法一致)。
- 合理设置k6的
vus(虚拟用户数)和duration参数,避免瞬间耗尽客户端侧资源。
额外优化:WebSocket服务端的HTTP配置
显式调整Go的http.Server参数,避免默认配置限制连接数:
import "net/http" import "time" func main() { server := &http.Server{ Addr: ":8080", MaxOpenConns: 10000, // 设置最大打开连接数 ReadTimeout: 30 * time.Minute, // 根据业务需求调整超时时间 WriteTimeout: 30 * time.Minute, } // 注册WebSocket路由... server.ListenAndServe() }
内容的提问来源于stack exchange,提问作者Suriya Varman N
相关产品推荐
相关产品推荐

