1. 网关
1.1. 网关应该具备的基本功能
- 支持多种协议代理:tcp/http/websocket/grpc
- 支持多种负载均衡策略:轮询/权重轮询/hash一致性
- 支持下游服务发现:主动探测/自动服务发现
- 支持横向扩容:加机器就能解决高并发
1.2. 借助网关处理使得服务高可用、高并发
- 限流:请求QPS限制
- 熔断:错误率达阈值则服务熔断
- 降级:确保核心业务可用
- 权限认证:请求拦截
2. 网络基础
2.1 OSI七层网络协议
2.2 经典协议与数据包
2.2.1 TCP的数据包
2.2.1 TCP的装包过程
2.3 三次握手与四次挥手
- 三次握手的最主要目的是保证连接是双工的,可靠更多是通过重传机制来保证的
- 因为连接是全双工的,双方必须都收到对方的FIN包及确认才可关闭
2.3.1 三次握手
2.3.2 四次握手
问题1:为什么TIME-WAIT需要等待2MSL
-
保证TCP协议的全双工连接能够可靠关闭
-
保证这次连接的重复数据段从网络中消失
-
MSL:Maximum Segment Lifetime,30秒~60秒
-
因为如果被关闭方(服务器),未接受到客户端发出了ACK。则会重新发出FIN包,如果客户端关闭过快的话,则服务端会一直处于LAST-ACK而无法关闭。客户端等待2MSL的时间足够服务器重传FIN包,之后客户端接收到FIN包就知道之前的包已经丢失,重新传输一个ACK包即可。
问题2:为什么会出现大量的CLOSE-WAIT
- 首先close_wait一般出现在被动关闭放
- 并发请求太多导致
- 在高并发场景下,服务器的CPU、内存和I/O资源可能变得紧张,导致某些需要处理连接关闭的线程或进程被阻塞或延迟执行。这可能延缓了对CLOSE-WAIT状态连接的处理,使得这些连接积累得更多。
- 被动关闭方未及时释放端口资源导致
被动关闭方未释放端口资源导致
package mainimport ("fmt""net"
)func main() {//1、监听端口listener, err := net.Listen("tcp", "0.0.0.0:9090")if err != nil {fmt.Printf("listen fail, err: %v\n", err)return}//2.建立套接字连接for {conn, err := listener.Accept()if err != nil {fmt.Printf("accept fail, err: %v\n", err)continue}//3. 创建处理协程go func(conn net.Conn) {//defer conn.Close() //思考题:这里不填写会有啥问题?for {var buf [128]byten, err := conn.Read(buf[:])if err != nil {fmt.Printf("read from connect failed, err: %v\n", err)break}str := string(buf[:n])fmt.Printf("receive from client, data: %v\n", str)}}(conn)}
}
2.3.3 实战抓包
2.4 TCP流量、拥塞控制
2.4.1 为什么需要流量控制
- 由于通讯双发,网速和处理速度不同。通讯任意一方发送过快都会导致对方消息处理不过来,所以就需要把数据放到缓冲区中
- 如果缓冲区满了,发送方还在疯狂发送,那接收方只能把数据包丢弃。因此我们需要控制发送速率。
- 缓冲区的剩余大小称之为接收窗口,用变量win表示,如果win=0,则发送方停止发送
此时会有疑问了?那发送方怎么知道下一次发送是什么时候
不用担心,发送方会有一个探测包,专门用来获取接收方空余的win的数量
2.4.2 为什么需要拥塞控制
- 流量控制与拥塞控制是两个概念
- 拥塞控制是调解网络的负载
- 流量控制是调解发送方和接收方
- 接收方网络资源繁忙,因未及时相应ACK导致发送方重传大量数据,这样将会导致网络更加拥堵
- 拥塞控制是动态调整win大小,而不是依赖缓冲区大小确定窗口大小
2.4.3 慢启动、拥塞避免、快恢复
2.5 为啥会出现粘包、拆包,如何处理?
- 应用程序写入的数据大于套接字缓冲区大小,这将会发生拆包
- 应用程序写入的数据小于套接字缓冲区大小,网卡将应用程序多次写入的数据一次性发送到网络上,这将会发送粘包。
- 进行MSS(TCP的最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包
- 接收方法不及时读取套接字内缓冲区数据,这将发生粘包
2.5.1 如何获取完整应用数据报文
- 使用带消息头的协议,头部写入包长度,然后再读取包内容
- 设置定长消息,每次读取定长内容,长度不够时空位补固定字符
- 设置消息便捷,服务端从网络流中按消息边界分离出消息内容,一般使用‘\n’
- 更复杂的协议,例如json。protobuf
2.5.2 代码示意
解包
const Msg_Header = "12345678"func Encode(bytesBuffer io.Writer, content string) error {//msg_header+content_len+content//8+4+content_len// 写入headerif err := binary.Write(bytesBuffer, binary.BigEndian, []byte(Msg_Header)); err != nil {return err}// 写入lenclen := int32(len([]byte(content)))if err := binary.Write(bytesBuffer, binary.BigEndian, clen); err != nil {return err}// 写入contentif err := binary.Write(bytesBuffer, binary.BigEndian, []byte(content)); err != nil {return err}return nil
}func Decode(bytesBuffer io.Reader) (bodyBuf []byte, err error) {MagicBuf := make([]byte, len(Msg_Header))if _, err = io.ReadFull(bytesBuffer, MagicBuf); err != nil {return nil, err}if string(MagicBuf) != Msg_Header {return nil, errors.New("msg_header error")}lengthBuf := make([]byte, 4)if _, err = io.ReadFull(bytesBuffer, lengthBuf); err != nil {return nil, err}// 通过大端字节序进行解码length := binary.BigEndian.Uint32(lengthBuf)bodyBuf = make([]byte, length)if _, err = io.ReadFull(bytesBuffer, bodyBuf); err != nil {return nil, err}return bodyBuf, err
}
客户端
func main() {conn, err := net.Dial("tcp", "localhost:9090")defer conn.Close()if err != nil {fmt.Printf("connect failed, err : %v\n", err.Error())return}unpack.Encode(conn, "hello world 0!!!")
}
服务端
func main() {//simple tcp server//1.监听端口listener, err := net.Listen("tcp", "0.0.0.0:9090")if err != nil {fmt.Printf("listen fail, err: %v\n", err)return}//2.接收请求for {conn, err := listener.Accept()if err != nil {fmt.Printf("accept fail, err: %v\n", err)continue}//3.创建协程go process(conn)}
}func process(conn net.Conn) {defer conn.Close()for {bt, err := unpack.Decode(conn)if err != nil {fmt.Printf("read from connect failed, err: %v\n", err)break}str := string(bt)fmt.Printf("receive from client, data: %v\n", str)}
}
2.6 基于golang实现TCP、UDP、Http服务器与客户端
2.6.1 TCP服务器与客户端
2.6.2.1 tcp_client
package clientimport ("bufio""fmt""net""os""strings"
)func main() {doSend()fmt.Print("doSend over")doSend()fmt.Print("doSend over")//select {}
}func doSend() {//1、连接服务器conn, err := net.Dial("tcp", "localhost:9090")defer conn.Close() //思考题:这里不填写会有啥问题?if err != nil {fmt.Printf("connect failed, err : %v\n", err.Error())return}//2、读取命令行输入inputReader := bufio.NewReader(os.Stdin)for {// 3、一直读取直到读到\ninput, err := inputReader.ReadString('\n')if err != nil {fmt.Printf("read from console failed, err: %v\n", err)break}// 4、读取Q时停止trimmedInput := strings.TrimSpace(input)if trimmedInput == "Q" {break}// 5、回复服务器信息_, err = conn.Write([]byte(trimmedInput))if err != nil {fmt.Printf("write failed , err : %v\n", err)break}}
}
2.6.2.1 tcp_server
package mainimport ("fmt""net"
)func main() {//1、监听端口listener, err := net.Listen("tcp", "0.0.0.0:9090")if err != nil {fmt.Printf("listen fail, err: %v\n", err)return}//2.建立套接字连接for {conn, err := listener.Accept()if err != nil {fmt.Printf("accept fail, err: %v\n", err)continue}//3. 创建处理协程go process(conn)}
}func process(conn net.Conn) {defer conn.Close() //思考题:这里不填写会有啥问题?for {var buf [128]byten, err := conn.Read(buf[:])if err != nil {fmt.Printf("read from connect failed, err: %v\n", err)break}str := string(buf[:n])fmt.Printf("receive from client, data: %v\n", str)}
}
2.6.2 UDP服务器与客户端
2.6.2.1 udp_client
func main() {//step 1 连接服务器conn, err := net.DialUDP("udp", nil, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1),Port: 9090,})if err != nil {fmt.Printf("connect failed, err: %v\n", err)return}for i := 0; i < 100; i++ {//step 2 发送数据_, err = conn.Write([]byte("hello server!"))if err != nil {fmt.Printf("send data failed, err : %v\n", err)return}//step 3 接收数据result := make([]byte, 1024)n, remoteAddr, err := conn.ReadFromUDP(result)if err != nil {fmt.Printf("receive data failed, err: %v\n", err)return}fmt.Printf("receive from addr: %v data: %v\n", remoteAddr, string(result[:n]))}
}
2.6.2.2 udp_server
func main() {//step 1 监听服务器listen, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0),Port: 9090,})if err != nil {fmt.Printf("listen failed, err:%v\n", err)return}//step 2 循环读取消息内容for {var data [1024]byten, addr, err := listen.ReadFromUDP(data[:])if err != nil {fmt.Printf("read failed from addr: %v, err: %v\n", addr, err)break}go func() {//todo sth//step 3 回复数据fmt.Printf("addr: %v data: %v count: %v\n", addr, string(data[:n]), n)_, err = listen.WriteToUDP([]byte("received success!"), addr)if err != nil {fmt.Printf("write failed, err: %v\n", err)}}()}
}
2.6.3 Http服务器与客户端
2.6.3.1 http_server
package mainimport ("log""net/http""time"
)var (Addr = ":1210"
)func main() {// 创建路由器mux := http.NewServeMux()// 设置路由规则mux.HandleFunc("/bye", sayBye)// 创建服务器server := &http.Server{Addr: Addr,WriteTimeout: time.Second * 3,Handler: mux,}// 监听端口并提供服务log.Println("Starting httpserver at "+Addr)log.Fatal(server.ListenAndServe())
}func sayBye(w http.ResponseWriter, r *http.Request) {time.Sleep(1 * time.Second)w.Write([]byte("bye bye ,this is httpServer"))
}
2.6.3.2 http_client
package mainimport ("fmt""io/ioutil""net""net/http""time"
)func main() {// 创建连接池transport := &http.Transport{DialContext: (&net.Dialer{Timeout: 30 * time.Second, //连接超时KeepAlive: 30 * time.Second, //探活时间}).DialContext,MaxIdleConns: 100, //最大空闲连接IdleConnTimeout: 90 * time.Second, //空闲超时时间TLSHandshakeTimeout: 10 * time.Second, //tls握手超时时间ExpectContinueTimeout: 1 * time.Second, //100-continue状态码超时时间}// 创建客户端client := &http.Client{Timeout: time.Second * 30, //请求超时时间Transport: transport,}// 请求数据resp, err := client.Get("http://127.0.0.1:1210/bye")if err != nil {panic(err)}defer resp.Body.Close()// 读取内容bds, err := ioutil.ReadAll(resp.Body)if err != nil {panic(err)}fmt.Println(string(bds))
}
2.7 linux常用和网关的命令
# 查看相关端口的连接状态
netstat -Aaln | grep 9090
3. http服务器源码解读
函数是一等公民
当称函数为“一等公民”(First-Class Citizen或First-Class Function)时,这意味着函数被当作与其他数据类型同等重要的基本单位来对待,享有与其他数据类型相同的操作权限。具体来说,一个语言如果支持以下几点,就可以认为其将函数视为一等公民:
- 可存储在变量中:函数可以被赋值给一个变量,就像整数、字符串等其他数据类型一样。
- 可作为参数传递:函数可以作为另一个函数的参数传递进去,使得程序能够接受函数作为输入,这是高阶函数的基础。
- 可作为返回值:一个函数执行完毕后,可以返回另一个函数作为结果。
- 可存于数据结构中:函数可以被放入数组、列表、字典等数据结构中,与其他数据混合存储和操作。
- 支持匿名函数:语言支持无需名称的函数定义(如lambda表达式),这些函数可以现场创建并立即使用。
这些特性使得函数式编程风格成为可能,允许开发者以更加灵活和强大的方式组织和复用代码。例如,在实现策略模式时,可以轻松地交换不同的函数策略,因为它们都是作为同等地位的对象来处理的。总的来说,函数作为一等公民极大地丰富了编程的表达力,使得代码更加简洁、动态,并促进了诸如函数式编程这样的编程范式的应用。
func main() {// 创建路由器mux := http.NewServeMux()// 设置路由规则mux.HandleFunc("/bye", sayBye)// 创建服务器server := &http.Server{Addr: Addr,WriteTimeout: time.Second * 3,Handler: mux,}// 监听端口并提供服务log.Println("Starting httpserver at "+Addr)log.Fatal(server.ListenAndServe())
}func sayBye(w http.ResponseWriter, r *http.Request) {time.Sleep(1 * time.Second)w.Write([]byte("bye bye ,this is httpServer"))
}
3.1 注册路由
3.1.1 相关的主体
// 路由器的主体
type ServeMux struct {// 读写锁mu sync.RWMutex// 存路径模式和对应的回调的实体m map[string]muxEntry// 存前缀匹配回调的实体es []muxEntry // slice of entries sorted from longest to shortest.hosts bool // whether any patterns contain hostnames
}// 回调的实体
type muxEntry struct {// 回调函数h Handler// 模式pattern string
}// 我们设置的回调函数都需要实现这个接口
type Handler interface {ServeHTTP(ResponseWriter, *Request)
}
通过函数类型来实现这个
// 使用了适配器的模式,自己需要你的函数有和我一样的参数即可,名字可以任意取
type HandlerFunc func(http.ResponseWriter, *http.Request)func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {f(w, r)
}func HelloHandler(res http.ResponseWriter, req *http.Request) {res.Write([]byte("Hello world"))
}
3.1.2 如何注册路由
func (mux *ServeMux) Handle(pattern string, handler Handler) {// 获取锁mux.mu.Lock()defer mux.mu.Unlock()// 异常判断if pattern == "" {panic("http: invalid pattern")}if handler == nil {panic("http: nil handler")}if _, exist := mux.m[pattern]; exist {panic("http: multiple registrations for " + pattern)}// 是否是第一次注册if mux.m == nil {mux.m = make(map[string]muxEntry)}e := muxEntry{h: handler, pattern: pattern}mux.m[pattern] = e// 该模式是否是前缀匹配if pattern[len(pattern)-1] == '/' {mux.es = appendSorted(mux.es, e)}if pattern[0] != '/' {mux.hosts = true}
}
3.2 开启服务
server.ListenAndServe()
// 创建服务器
server := &http.Server{Addr: Addr,WriteTimeout: time.Second * 3,// 需要把路由放入其中Handler: mux,
}
// 开启端口并且开始服务
func (srv *Server) ListenAndServe() error {// 避免在服务器已经关闭或正在关闭的过程中再次尝试启动监听。if srv.shuttingDown() {return ErrServerClosed}addr := srv.Addrif addr == "" {addr = ":http"}// 创建一个TCP监听器ln, err := net.Listen("tcp", addr)if err != nil {return err}// 启动服务器并开始在创建的监听器(ln)上接受和处理连接。这个方法会阻塞直到服务器关闭(例如通过调用Server.Close())或者监听出现错误。return srv.Serve(ln)
}
func (srv *Server) Serve(l net.Listener) error {// 检查是否有测试钩子testHookServerServe,如果有,则调用它,这通常是用于测试目的的。if fn := testHookServerServe; fn != nil {fn(srv, l) // call hook with unwrapped listener}// 包装监听器: 为了确保监听器只关闭一次,代码创建了一个onceCloseListener包装器,// 它在关闭时会防止重复关闭底层监听器。原始监听器l被替换为这个包装器,并在方法最后确保关闭它。origListener := ll = &onceCloseListener{Listener: l}defer l.Close()// 根据监听器返回的连接类型及配置,决定是否启用HTTP/2支持if err := srv.setupHTTP2_Serve(); err != nil {return err}// 使用srv.trackListener来管理监听器的状态,确保在服务器关闭时能正确处理监听器。if !srv.trackListener(&l, true) {return ErrServerClosed}defer srv.trackListener(&l, false)// 如果服务器配置了BaseContext回调函数,会使用它来获取基础上下文。此上下文会作为后续请求处理的基础,并且必须非nil。baseCtx := context.Background()if srv.BaseContext != nil {baseCtx = srv.BaseContext(origListener)if baseCtx == nil {panic("BaseContext returned a nil context")}}var tempDelay time.Duration // how long to sleep on accept failurectx := context.WithValue(baseCtx, ServerContextKey, srv)// 重要的循环for {// 监听端口并尝试获取连接rw, err := l.Accept()// 根据错误类型采用不同的处理方式if err != nil {if srv.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}srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)time.Sleep(tempDelay)continue}return err}connCtx := ctx// 如果服务器配置了ConnContext回调函数,会使用它来基于基础上下文创建特定于连接的上下文。if cc := srv.ConnContext; cc != nil {connCtx = cc(connCtx, rw)if connCtx == nil {panic("ConnContext returned nil")}}tempDelay = 0c := srv.newConn(rw)c.setState(c.rwc, StateNew, runHooks) // before Serve can return// 启动一个新的goroutine来通过调用c.serve(connCtx)来处理这个连接上的请求。go c.serve(connCtx)}
}
3.3 处理连接
c.serve(connCtx)
这一段的源代码挺长的,但有一句十分重要
serverHandler{c.server}.ServeHTTP(w, w.req)
利用server创建了一个serverHandler
type serverHandler struct {srv *Server
}func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {// 读取的是我们创建Server时候放入的Handlerhandler := sh.srv.Handlerif handler == nil {handler = DefaultServeMux}if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" {handler = globalOptionsHandler{}}// 这里实际上调用的是ServeMux的ServeHTTPhandler.ServeHTTP(rw, req)
}
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {if r.RequestURI == "*" {if r.ProtoAtLeast(1, 1) {w.Header().Set("Connection", "close")}w.WriteHeader(StatusBadRequest)return}// 这一句其实调用的是mux.handlerh, _ := mux.Handler(r)h.ServeHTTP(w, r)
}
输入的是主机+访问路径。返回的是对应的Handler和匹配的模式。之后就可以根据对应的handler来处理连接请求了
4. http客户端源码解读
4.1 主要结构体
- type Client struct{}
- Transport RoundTripper(连接池)
- Timeout time.Duration(超时时间)
- type RoundTripper interface {}
- RoundTrip(*Request)(*Response,error)请求下游接口
4.2 请求流程
- func(c *Client)Get(url string)
- c.Do(req)
- c.do(req)
- c.send(req,deadline)
- send(req,c.transport(),deadline)
- resp,err = rt.RoundTrip(req)
4.3 Transport(连接池)
4.3.1 结构体
// Transport 结构体代表了执行HTTP请求的传输层。它负责处理连接复用、超时以及网络通信的其他方面。type Transport struct {// idleMu 用于保护 idleConn 和 closeIdle 字段,防止并发访问。idleMu sync.Mutex// closeIdle 表示用户已请求关闭所有空闲连接。closeIdle bool// idleConn 是一个映射,按连接方法键(connectMethodKey)存储最近空闲的持久连接列表。idleConn map[connectMethodKey][]*persistConn // idleConnWait 是一个映射,关联了当前空闲但可能需要的连接的等待队列和对应的连接方法键。idleConnWait map[connectMethodKey]wantConnQueue// idleLRU 负责管理空闲连接的最近最少使用(LRU)淘汰,以维持连接池的大小限制。idleLRU connLRU......
}// 往往代表的是一个网站的连接地址
type connectMethodKey struct {// 代理、协议、地址proxy, scheme, addr stringonlyH1 bool
}
4.3.2 roundTrip的流程
Transport实现了roundTrip这个接口
并在这个接口中调用了获取连接的方法
此方法展示了 Transport 如何智能地管理连接,
- 优先复用空闲连接queueForIdleConn
- 没有的话调用queueForDial尝试创建,若已经达到上线则等待之前的完成放回连接池
- 并妥善处理请求取消和上下文超时,确保资源的有效利用和请求的正确处理。
func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) {req := treq.Requesttrace := treq.tracectx := req.Context()if trace != nil && trace.GetConn != nil {trace.GetConn(cm.addr())}// 初始化一个 wantConn 结构体 w,用于记录请求的上下文、连接方法、就绪通道等信息,并设置了测试钩子函数。w := &wantConn{cm: cm,key: cm.key(),ctx: ctx,ready: make(chan struct{}, 1),beforeDial: testHookPrePendingDial,afterDial: testHookPostPendingDial,}defer func() {if err != nil {w.cancel(t, err)}}()// 尝试获取空闲连接if delivered := t.queueForIdleConn(w); delivered {// 有空闲的连接pc := w.pc// 直接返回空闲连接 pc,并根据追踪需求记录连接复用信息。if pc.alt == nil && trace != nil && trace.GotConn != nil {trace.GotConn(pc.gotIdleConnTrace(pc.idleAt))}// 设置请求取消器以便跟踪请求取消状态。t.setReqCanceler(treq.cancelKey, func(error) {})return pc, nil}// 若未获取到空闲连接,则准备新建连接:// 创建一个用于取消请求的通道 cancelc。cancelc := make(chan error, 1)//设置请求取消器,当请求被取消时通过 cancelc 发送错误信息。t.setReqCanceler(treq.cancelKey, func(err error) { cancelc <- err })//将 wantConn 加入到等待新建连接的队列中(queueForDial(w)),此时连接可能尚未建立,需等待。t.queueForDial(w)// 等待连接完成或请求取消:select {case <-w.ready:// w.ready 通道有消息,表示连接准备完成:// 检查是否成功获取连接 w.pc,并记录连接获取成功信息(仅HTTP/1)。// 处理连接建立过程中的错误 w.err,优先处理请求取消的情况。if w.pc != nil && w.pc.alt == nil && trace != nil && trace.GotConn != nil {trace.GotConn(httptrace.GotConnInfo{Conn: w.pc.conn, Reused: w.pc.isReused()})}if w.err != nil {select {case <-req.Cancel:return nil, errRequestCanceledConncase <-req.Context().Done():return nil, req.Context().Err()case err := <-cancelc:if err == errRequestCanceled {err = errRequestCanceledConn}return nil, errdefault:// return below}}return w.pc, w.errcase <-req.Cancel:return nil, errRequestCanceledConncase <-req.Context().Done():return nil, req.Context().Err()case err := <-cancelc:if err == errRequestCanceled {err = errRequestCanceledConn}return nil, err}
}
func (t *Transport) queueForDial(w *wantConn) {w.beforeDial()// 小于0,代表没有上限,立刻创建新链接if t.MaxConnsPerHost <= 0 {go t.dialConnFor(w)return}t.connsPerHostMu.Lock()defer t.connsPerHostMu.Unlock()// 未达到上限,也创建if n := t.connsPerHost[w.key]; n < t.MaxConnsPerHost {if t.connsPerHost == nil {t.connsPerHost = make(map[connectMethodKey]int)}t.connsPerHost[w.key] = n + 1go t.dialConnFor(w)return}// 否则加入等待队列if t.connsPerHostWait == nil {t.connsPerHostWait = make(map[connectMethodKey]wantConnQueue)}q := t.connsPerHostWait[w.key]q.cleanFront()q.pushBack(w)t.connsPerHostWait[w.key] = q
}
func (t *Transport) tryPutIdleConn(pconn *persistConn) error {...// 获取持久链接的keykey := pconn.cacheKey// 查看是否有和该链接相同的key在等待if q, ok := t.idleConnWait[key]; ok {done := falseif pconn.alt == nil {for q.len() > 0 {// 从等待队列中取出一个请求wantRequestw := q.popFront()// 通过调用tryDeliver并close(w.ready)向case传递信号,表明已经接受到了复用通道if w.tryDeliver(pconn, nil) {done = truebreak}}} else {for q.len() > 0 {w := q.popFront()w.tryDeliver(pconn, nil)}}if q.len() == 0 {delete(t.idleConnWait, key)} else {t.idleConnWait[key] = q}if done {return nil}}......
}
4.4 http超时控制
// 创建连接池transport := &http.Transport{DialContext: (&net.Dialer{Timeout: 30 * time.Second, //连接超时KeepAlive: 30 * time.Second, //探活时间}).DialContext,MaxIdleConns: 100, //最大空闲连接IdleConnTimeout: 90 * time.Second, //空闲超时时间TLSHandshakeTimeout: 10 * time.Second, //tls握手超时时间ExpectContinueTimeout: 1 * time.Second, //100-continue状态码超时时间}// 创建客户端client := &http.Client{Timeout: time.Second * 30, //请求超时时间Transport: transport,}
- TCP连接超时:是指在尝试建立TCP连接过程中,如果在预定时间内没有完成连接的握手过程,就会发生连接超时。
- TCP探活时间:是指在TCP连接空闲一段时间后,为了检测连接是否仍然有效,发起方(通常是客户端或服务器端)会向对方发送一个特殊的数据包,称为TCP Keepalive探测报文。这个时间间隔就是TCP探活时间,它是TCP协议栈中可配置的参数之一。
- 在没有数据传输的情况下,如果超过这个设定的时间没有通信,发起方就会自动发送一个探活包。如果对方是活跃的,它会响应一个ACK报文,这样就确认了连接仍然是有效的。如果多次(通常也是可配置的)发送探活包都没有得到响应,TCP连接就会被认为已经断开,发起方会关闭这个连接,以释放资源并避免资源的无效占用。
- 最大空闲连接
- 连接池中最大的空闲连接总数
- 空闲超时时间
- 空闲连接超过这个时间就会失效
这是一个整体的超时架构。