GoLang实战——微服务网关

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连接就会被认为已经断开,发起方会关闭这个连接,以释放资源并避免资源的无效占用。
  • 最大空闲连接
    • 连接池中最大的空闲连接总数
  • 空闲超时时间
    • 空闲连接超过这个时间就会失效

这是一个整体的超时架构。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3017046.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

ldap对接jenkins

ldap结构 配置 - jenkins进入到 系统管理–>全局安全配置 - 安全域 选择ldap - 配置ldap服务器地址&#xff0c;和配置ldap顶层唯一标识名 配置用户搜索路径 - 配置管理员DN和密码 测试认证是否OK

MCU做死循环时,到底应该用for(;;) 还是wihile(1)

MCU做死循环时 for while stm32中老工程师用forfor while背景for版本while版本正方观点&#xff1a;哪有好的编译器&#xff1a;反方观点&#xff1a;这种代码过时了工程师实地测试&#xff1a;和编译器和优化有关 建议还是用for参考 stm32中老工程师用for /* Start scheduler …

数据库系统理论——绪论

文章目录 前言一、数据库四个基本概念1、数据2、数据库3、数据库管理系统&#xff08;DBMS&#xff09;4、数据库系统&#xff08;DBS&#xff09; 二、数据模型1、概念数据模型2、逻辑数据模型3、物理数据模型 三、三级模式1、图片解析2、二级映像 前言 最近很长时间没更新学…

windows 双网卡同时接入内外网

在公司使用wifi接入使用桌面云&#xff0c;但是公司wifi不能上外网&#xff0c;查资料不方便&#xff0c;通过手机同时接入外网。 同一台电脑设置同时连接内外网&#xff08;wifi或共享的网络&#xff09;_win7电脑同时使用手机和usb网卡使用wifi-CSDN博客 route print查看当前…

开启智能新纪元:揭秘现代化仓储物流园区的数字孪生魅力

在数字化浪潮的推动下&#xff0c;物流行业正迎来前所未有的变革&#xff0c;现代化仓储物流园区数字孪生系统正以其独特的魅力引领着物流行业迈向更加智能、高效的新时代。 图源&#xff1a;山海鲸可视化 一、数字孪生&#xff1a;物流行业的“虚拟镜像” 数字孪生技术作为工…

5.合并两个有序数组

文章目录 题目简介题目解答解法一 &#xff1a;合并后排序解法二&#xff1a;双指针排序 题目链接 大家好&#xff0c;我是晓星航。今天为大家带来的是 合并两个有序数组 相关的讲解&#xff01;&#x1f600; 题目简介 题目解答 解法一 &#xff1a;合并后排序 假设我们要合…

科研学习|可视化——ggplot2版本的网络可视化

ggplot2是R语言中一个非常流行的数据可视化包&#xff0c;它也可以用于网络可视化。以下是三个基于ggplot2并专门用于网络可视化的R包&#xff1a; ggnet2: 这个包的使用方法与传统的plot函数相似&#xff0c;易于使用。更多信息可在其官方页面查看&#xff1a;ggnet2 geomnet…

【Linux网络】PXE批量网络装机

目录 一、系统装机 1.1 三种引导方式 1.2 系统安装过程 1.3 四大重要文件 二、PXE 2.1 PXE实现原理 2.2 PXE手动搭建过程 2.3 kickstart配合pxe完成批量自动安装 一、系统装机 1.1 三种引导方式 硬盘光驱(U盘)网络启动 1.2 系统安装过程 加载boot loader加载启动安…

《安富莱嵌入式周报》第336期:开源计算器,交流欧姆表,高性能开源BLDC控制器,Matlab2024a,操作系统漏洞排名,微软开源MS-DOS V4.0

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 本周更新一期视频教程&#xff1a; BSP视频教程第30期&#xff1a;UDS ISO14229统一诊断服务CAN总线专题&#xff0c;常…

学习和分析各种数据结构所要掌握的一个重要知识——CPU的缓存利用率(命中率)

什么是CPU缓存利用率&#xff08;命中率&#xff09;&#xff0c;我们首先要把内存搞清楚。 硬盘是什么&#xff0c;内存是什么&#xff0c;高速缓存是什么&#xff0c;寄存器又是什么&#xff1f; 我们要储存数据就要运用到上面的东西。首先里面的硬盘是可以无电存储的&#…

记一次DNS故障导致用户无法充值的问题(上)

背景&#xff1a; 刚刚过去了五一劳动节&#xff0c;回来后一上班接到客服运营团队反馈的节日期间的问题&#xff0c;反馈有部分用户无法充值。拿到的反馈资料有&#xff1a; 无法充值操作视频、问题时间、手机机型、手机网络情况。 1、从视频中看到用户点击支付后没有任何反…

[CISCN2019 华北赛区 Day1 Web2]ikun

看到提示说一定要找到lv6 这要写脚本来爆破了&#xff0c;用bp是爆破不出来的 发现LV等级都是有参数挂着的 写个脚本看一下 import requests for i in range(1,1000):payload"http://node4.anna.nssctf.cn:28150/shop?page%d"%(i)resrequests.get(payload)if "…

怎么ai解答问题?这三个方法都可以

怎么ai解答问题&#xff1f;在数字化飞速发展的今天&#xff0c;人工智能&#xff08;AI&#xff09;技术已经渗透到我们生活的方方面面&#xff0c;尤其是在解答问题方面&#xff0c;AI展现出了令人瞩目的能力。那么&#xff0c;哪些软件可以利用AI技术解答问题呢&#xff1f;…

【西瓜书机器学习】第五章 神经网络

一起啃西瓜书(5)-神经网络《机器学习-周志华》 - 知乎 (zhihu.com)参考进行自我复习整理&#xff0c;侵删&#xff01; 1、神经元模型 神经网络定义&#xff1a;神经网络是由 具有适应性 的 简单单元 组成的广泛 并行互连 的网络。M-P神经元模型&#xff1a;输入、处理、输出 …

酸奶(科普)

酸奶&#xff08;yogurt&#xff09;是一种酸甜口味的牛奶饮品&#xff0c;是以牛奶为原料&#xff0c;经过巴氏杀菌后再向牛奶中添加有益菌&#xff08;发酵剂&#xff09;&#xff0c;经发酵后&#xff0c;再冷却灌装的一种牛奶制品。市场上酸奶制品多以凝固型、搅拌型和添加…

武汉星起航:策略升级,亚马逊平台销售额持续增长显实力

武汉星起航电子商务有限公司&#xff0c;一家致力于跨境电商领域的企业&#xff0c;于2023年10月30日在上海股权托管交易中心成功挂牌展示&#xff0c;这一里程碑事件标志着公司正式踏入资本市场&#xff0c;开启了新的发展篇章。公司董事长张振邦在接受【第一财经】采访时表示…

ROS机器人实用技术与常见问题解决

问题速查手册&#xff08;时实更新&#xff09;更加全面丰富的问题手册记录 1.机器人使用GPARTED挂载未分配空间 需要在图型界面下操作&#xff0c;建议使用no machine连接 安装gparted磁盘分区工具, sudo apt-get install gparted -y 启动软件 sudo gparted 点击磁盘/内存…

【C语言】动态分配内存

内存的五大分区 1、堆区&#xff08;heap&#xff09;——由程序员分配和释放&#xff0c; 若程序员不释放&#xff0c;程序结束时一般由操作系统回收。注意它与数据结构中的堆是两回事 2、栈区&#xff08;stack&#xff09;——由编译器自动分配释放 &#xff0c;存放函数的…

cmake install命令无法覆盖同名文件

文章目录 1. 问题记录2. 原因排查3. 解决方案 1. 问题记录 我有两个同名文件test.txt&#xff0c;它们内容不同&#xff0c;但时间戳相同&#xff08;文件属性中的修改时间相同&#xff09; 我希望在cmake中利用install命令&#xff0c;将${PATH_SRC}/test.txt替换${PATH_DES…

Android OTA 交流群 2024 年 4 月问题汇总

Android OTA 交流群 2024 年 4 月问题汇总 相关文章 Android OTA 问题交流微信群和知识星球Android OTA 交流群 2024 年 4 月问题汇总Android OTA 交流群 2024 年 3 月问题汇总Android OTA 交流群 2024 年 2 月问题汇总Android OTA 交流群 2024 年 1 月问题汇总 问题汇总 2…