事情起因
同事在 review 项目代码时,发现支付代码中的 http 接口有类似以下逻辑
go
func xxxHandler(args ... interface) {
// ...
ch := make(chan int, 1)
defer close(ch)
process(ch, arg1, arg2, ...)
select {
case code, ok := <- ch:
switch code {
case 1:
case 2:
...
}
case <-time.After(time.Second * 3):
...
}
}
由于我们是游戏研发,process()
需要经过游戏内的业务逻辑处理并返回对应的错误码
这个接口是对外暴露的,通过 nginx
做负载均衡,这样 nginx
相当于与我们的 web 服务器 建立了长连接
同事提出相关疑问: 如果在 process()
调用过程中出现阻塞或者别的情况导致 select
只能等待 3 s 定时器结束
又因为在 go
中是 one goroutine per connection 也就是针对每一个链接都会开启一个 goroutine 去处理
这样在一个链接中,只要前一个请求没有处理完成或者被阻塞住,后面所有的请求都会被阻塞卡住,导致这个接口服务宕掉
同事提出解决方案:
在接收到 HTTP 请求 后,开启一个 goroutine 去处理该请求,并尝试通过该 HTTP 请求 的 唯一标识 返回响应
我提出反驳:
由于 HTTP
是 请求-响应模型 因此在处理下一个请求前,必须返回当前请求的响应
如果需要他所描述的异步处理,则应该使用 WebSocket
,同时 API
的调用方也要做对应代码的变更
在此终于认识到了,游戏开发和 web 开发程序员在某些技术的认知差异
one goroutine per connection
每个链接都是一个 goroutine
go
实现的 HTTP
服务器源码见 src\net\http\server.go
go
func (srv *Server) Serve(l net.Listener) error {
// ...
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
if err != nil {
// ...
return err
}
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
}
从源码可以看出,原生 HTTP
也是使用监听器 Listener
监听网络连接,每当有新链接 conn
接入时就创建一个 goroutine
处理这个链接发送过来的请求 go c.serve(connCtx)
这种从链接读取请求数据并创建 goroutine
是游戏开发中常用的方式,因为 HTTP
的底层实际就是 TCP
只是根据建立在其上的协议不同,对应字节流数据的编解码方式不同
请求-响应 ( request- response ) 模型
由于一般的 Web API
使用的 HTTP 版本 都是 HTTP/1.1
所以对请求的处理都是串行的
至于为什么都是串行,这种原则上的定义仅凭个人理解相互争论没有意义,所以直接去看 RFC
文档
RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1 (rfc-editor.org)
8.1.2.2
A client that supports persistent connections MAY "pipeline" its requests (i.e., send multiple requests without waiting for each response). A server MUST send its responses to those requests in the same order that the requests were received.
在一个链接中,客户端可以通过这个链接发送多个请求,且无需等待响应,但服务端 必须 MUST 按照这些请求发送的顺序依次返回响应
这里规定了服务端的响应必须是有序的,所以服务端只能按照同步逻辑实现对请求的响应