goroutine省心是相对的,生产环境里最容易翻车的恰恰是它太便宜了。
C++起线程成本高,大家天然会克制。Go里随手一个go func(),高并发场景下几万个goroutine瞬间起飞。这时候如果底层有阻塞操作(比如没设超时的数据库查询、DNS解析),调度器里的OS线程会被打满。我们之前跑模型服务的时候踩过一次,外部API超时导致goroutine积压,内存直接飙到OOM。根因不是锁的问题,是goroutine leak。
建议上生产必加两个东西:
net/http/pprof,定期看goroutine数量和调用栈。
- 引入
uber-go/goleak在测试阶段做泄露检测,成本极低但能挡掉大部分坑。
简单说
关于channel解决数据同步,补充一点:channel适合传递控制权或流水线任务,但不适合所有共享状态。Rob Pike那句“不要通过共享内存来通信,而要通过通信来共享内存”被很多人当成教条了。实际工程里,高频读写的计数器、配置热更新,用sync.RWMutex甚至atomic包性能远好于channel。channel底层的锁竞争在极高并发下并不轻。
其实标准库net/http起服务确实快,但要注意默认没有读写超时。裸跑的话一个慢连接就能占住一个goroutine和文件描述符。一定要手动配好ReadTimeout、WriteTimeout和IdleTimeout,或者直接用http.Server结构体把参数写死。
泛型这块,1.18加了之后其实解决了不少重复代码,但别滥用。Go的哲学还是组合优于继承,interface加上鸭子类型在90%的场景已经够用。至于error handling啰嗦,习惯if err != nil后反而会觉得显式错误处理让代码流向更可控,总比C++里漏抓一个异常导致整个进程崩掉强。
跨平台编译确实是降维打击,CGO_ENABLED=0一关,丢个静态二进制上去就跑,这点对运维太友好了。你们现在微服务是用原生RPC还是gRPC?