ITPub博客

首页 > 应用开发 > Go语言 > Go车联网网关:从0到100万连接,我踩过的坑

Go车联网网关:从0到100万连接,我踩过的坑

原创 Go语言 作者:suoten 时间:2026-09-25 16:27:09 0 删除 编辑

车联网网关的核心指标只有一个:能扛多少终端连接。

一台货车一个终端,一个终端一条TCP长连接。10万台车就是10万连接,100万台车就是100万连接。听起来不多,但每条连接都要处理心跳、位置上报、报警、鉴权、参数下发。写不好,几万连接就把内存打满。

我们做JTE的时候,目标很明确:单机100万连接。这篇文章讲清楚我们怎么做到的,以及踩过的坑。

第一版:一个连接一个goroutine

Go最舒服的地方就是goroutine。第一版网关,每条终端连接启动两个goroutine:一个读,一个写。 func handleConn(conn net.Conn) { go readLoop(conn) go writeLoop(conn) } 10万连接就是20万goroutine。Go的goroutine很轻,初始栈2KB,20万也就400MB。看起来能接受。

但问题来了:GC压力。

每个goroutine都在分配对象。读缓冲区、消息结构体、临时切片。10万连接每秒上报一次位置,就是10万次分配。GC每秒跑好几次,每次STW虽然只有几毫秒,但累积起来,延迟抖动很明显。

第二版:连接池 + 工作池

我们把“一个连接一个goroutine”改成“固定数量工作池”。

type WorkerPool struct { tasks chan Task workers int }

func NewWorkerPool(n int) *WorkerPool { p := &WorkerPool{ tasks: make(chan Task, 10000), workers: n, } for i := 0; i < n; i++ { go p.worker() } return p } 读goroutine只负责从连接读数据,解析成消息后丢进channel。工作池从channel取任务,处理业务逻辑。

这样goroutine数量从20万降到几千,GC压力大幅下降。

第三版:批量写入

位置上报是高频操作。如果每条位置都写一次数据库,数据库扛不住。

我们做了批量写入:位置数据先进入内存队列,后台goroutine每200毫秒或每1000条批量写一次。

type BatchWriter struct { buf    []*Position mu     sync.Mutex maxSize int interval time.Duration }

func (w *BatchWriter) Add(pos *Position) { w.mu.Lock() w.buf = append(w.buf, pos) if len(w.buf) >= w.maxSize { batch := w.buf w.buf = nil w.mu.Unlock() go w.flush(batch) return } w.mu.Unlock() } 批量写入把数据库QPS从10万降到500,效果立竿见影。

第四版:连接管理

100万连接,不能每个连接都存一个结构体指针。我们用sync.Map存会话,key是终端手机号,value是会话对象。

var sessions sync.Map

func GetSession(phone string) ( Session, bool) { v, ok := sessions.Load(phone) if !ok { return nil, false } return v.(Session), true } sync.Map在读多写少的场景下性能很好。但要注意:终端下线时要及时删除,否则内存泄漏。

第五版:背压处理

最怕的情况:终端疯狂上报,工作池处理不过来,channel满了。

如果channel满了还继续塞,就会阻塞读goroutine。读goroutine阻塞,TCP缓冲区满,终端侧发送失败,可能触发重连风暴。

我们加了背压策略:channel满了就丢弃非关键消息(比如心跳),保留关键消息(比如报警)。 select { case p.tasks <- task: // 正常 default: if task.Priority == PriorityLow { // 丢弃 return } // 关键消息阻塞等待 p.tasks <- task } 实测数据

在8核16G的云服务器上,JTE单机压测结果:

连接数:100万

每秒位置上报:20万条

内存占用:约6GB

GC暂停:平均1.2毫秒

数据库写入:批量后约500 QPS

这个数据不是极限,但足够覆盖大多数车联网场景。

踩过最大的坑:文件描述符

Linux默认文件描述符限制是1024。100万连接需要至少200万文件描述符(每个连接读写各一个?其实TCP连接一个fd,但加上epoll等)。

必须调整: ulimit -n 2000000 同时调整内核参数: net.core.somaxconn = 65535 net.ipv4.tcp maxsyn backlog = 65535 net.ipv4.iplocal portrange = 1024 65535 这个坑不踩一次,你永远不知道。

总结

Go写高并发网关,goroutine是优势,但不能滥用。工作池、批量写入、背压、连接管理,每一个都要仔细设计。JTE把这些实现在internal/gateway和pkg里,可以作为参考。

项目仓库:

Gitee:suoten/jt-engine GitHub:suoten/JTEngine

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70050062/viewspace-3183919/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2026-05-07

  • 博文量
    69
  • 访问量
    53914