首页 > 其他分享 >net/http shutdown退出的原理

net/http shutdown退出的原理

时间:2024-05-29 11:59:29浏览次数:12  
标签:return Shutdown ctx srv shutdown error http net

  使用nginx reload的时候,nginx会close 掉listen fd,然后启动新的worker,老的worker继续工作直到当前的fd完全关闭后worker退出。

目前使用gin框架的时候也需要频繁的在http:9000 监听和htttps:9000之间切换。所以也涉及到上述逻辑

看下gin 框架中run 启动listen的逻辑

// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) Run(addr ...string) (err error) {
	defer func() { debugPrintError(err) }()

	if engine.isUnsafeTrustedProxies() {
		debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
			"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
	}

	address := resolveAddress(addr)
	debugPrint("Listening and serving HTTP on %s\n", address)
	err = http.ListenAndServe(address, engine.Handler())
	return
}

// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

核心也是调用http.ListenAndServe(address, engine.Handler())---->server.ListenAndServe()

由于gin中的*Engine 实现了Handler接口.

所以可以httpServer = http.Server{Addr: addr, Handler: s.router} 然后自行调用listenandserver

目前发现http自带优雅退出:golang在http里加入了shutdown方法,用来控制优雅退出;

  平滑启动的原理很简单,fork子进程,继承listen fd, 老进程优雅退出,---->目前基于http.shutdown可以完美做到 

 

http shutdown 源码分析

 http shutdown 的主方法实现逻辑。用atomic来做退出标记的状态,然后关闭各种的资源,然后一直阻塞的等待无空闲连接,每 500ms 轮询一次。

// Shutdown gracefully shuts down the server without interrupting any
// active connections. Shutdown works by first closing all open
// listeners, then closing all idle connections, and then waiting
// indefinitely for connections to return to idle and then shut down.
// If the provided context expires before the shutdown is complete,
// Shutdown returns the context's error, otherwise it returns any
// error returned from closing the Server's underlying Listener(s).
//
// When Shutdown is called, Serve, ListenAndServe, and
// ListenAndServeTLS immediately return ErrServerClosed. Make sure the
// program doesn't exit and waits instead for Shutdown to return.
//
// Shutdown does not attempt to close nor wait for hijacked
// connections such as WebSockets. The caller of Shutdown should
// separately notify such long-lived connections of shutdown and wait
// for them to close, if desired. See RegisterOnShutdown for a way to
// register shutdown notification functions.
//
// Once Shutdown has been called on a server, it may not be reused;
// future calls to methods such as Serve will return ErrServerClosed.



func (srv *Server) Shutdown(ctx context.Context) error {
 // 标记退出的状态 Store atomically原子存储
	srv.inShutdown.Store(true)

	srv.mu.Lock()
    / 关闭listen fd,新连接无法建立。
	lnerr := srv.closeListenersLocked()
     // 执行回调方法,我们可以注册shutdown的回调方法
	for _, f := range srv.onShutdown {
		go f()
	}
	srv.mu.Unlock()
	srv.listenerGroup.Wait()

     // 每1ms----500ms来之间检查下,是否没有空闲的连接了,或者监听上游传递的ctx上下文。
	pollIntervalBase := time.Millisecond
    //生成随机休眠时间最大500ms
	nextPollInterval := func() time.Duration {
		// Add 10% jitter.
		interval := pollIntervalBase + time.Duration(rand.Intn(int(pollIntervalBase/10)))
		// Double and clamp for next time.
		pollIntervalBase *= 2
		if pollIntervalBase > shutdownPollIntervalMax {
			pollIntervalBase = shutdownPollIntervalMax
		}
		return interval
	}
    // 每1ms----500ms来之间检查下,是否没有空闲的连接了,或者监听上游传递的ctx上下文。
	timer := time.NewTimer(nextPollInterval())
	defer timer.Stop()
	for {
		if srv.closeIdleConns() {
			return lnerr
		}
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-timer.C:
			timer.Reset(nextPollInterval()) //重新reset定时器,等待随机nextPollInterval定时器到来
		}
	}
}

// Store atomically stores val into x.
func (x *Bool) Store(val bool) { StoreUint32(&x.v, b32(val)) }

是否没有空闲的连接 遍历连接,当客户单的连接已空闲,则关闭连接,并在 activeConn 连接池中剔除该连接。 

// closeIdleConns closes all idle connections and reports whether the
// server is quiescent.
func (s *Server) closeIdleConns() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	quiescent := true
	for c := range s.activeConn {
		st, unixSec := c.getState()
		// Issue 22682: treat StateNew connections as if
		// they're idle if we haven't read the first request's
		// header in over 5 seconds.
		if st == StateNew && unixSec < time.Now().Unix()-5 {
			st = StateIdle
		}
		if st != StateIdle || unixSec == 0 {
			// Assume unixSec == 0 means it's a very new
			// connection, without state set yet.
			quiescent = false
			continue
		}
		c.rwc.Close()
		delete(s.activeConn, c)
	}
	return quiescent
}

 

 

eturns a non-nil error and closes l.
// After Shutdown or Close, the returned error is ErrServerClosed.
func (srv *Server) Serve(l net.Listener) error {
	-----------------------------------
	var tempDelay time.Duration // how long to sleep on accept failure

	ctx := 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
		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)
	}
}

 那么如何保证用户在请求完成后,再关闭连接的?

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
	c.remoteAddr = c.rwc.RemoteAddr().String()
	ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
	var inFlightResponse *response
	defer func() {
		
		if !c.hijacked() {
			if inFlightResponse != nil {
				inFlightResponse.conn.r.abortPendingRead()
				inFlightResponse.reqBody.Close()
			}
			c.close()  // 关闭连接,并且标记退出
			c.setState(c.rwc, StateClosed, runHooks)
            将此conn从map中delete
		}
	}()

	---------
	ctx, cancelCtx := context.WithCancel(ctx)
	c.cancelCtx = cancelCtx
	defer cancelCtx()

	c.r = &connReader{conn: c}
	c.bufr = newBufioReader(c.r)
	c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

	for {
		w, err := c.readRequest(ctx)
	
		
		inFlightResponse = w
		serverHandler{c.server}.ServeHTTP(w, w.req)
		inFlightResponse = nil
		w.cancelCtx()
		if c.hijacked() {
			return
		}
		
		if !w.conn.server.doKeepAlives() {
			// We're in shutdown mode. We might've replied
			// to the user without "Connection: close" and
			// they might think they can send another
			// request, but such is life with HTTP/1.1.
			return
		}
	}
}

 

 

标签:return,Shutdown,ctx,srv,shutdown,error,http,net
From: https://www.cnblogs.com/codestack/p/18219932

相关文章

  • asp.net mvc 多语言环境配置
    新建资源文件夹添加ASP.NET文件夹选择App_GlobalResources新建资源文件language.resxlanguage.en.resx创建了中文、英文两个语言的资源文件,中文是程序的默认语言。然后是language.en.resx,中间的“en”是英语的CultureName。如果你需要法语,那么你只需要再创建lang......
  • .net 8 WPF发布程序只生成exe
    右击项目选择发布配置发布信息修改FolderProfile.pubxml文件内容ReleaseAnyCPUbin\Release\net8.0-windows\publish\win-x64</PublishDir>FileSystem<_TargetId>Folder</_TargetId>net8.0-windowswin-x64truefalsetruetrue<_SuppressWpfTrimError>......
  • asp.net core 中hangfire面板的配置及使用
    Hangfire项目实践分享-Ecin-博客园(cnblogs.com) 1、定义校验授权类DyDashboardAuthorizationFilter///<summary>///Hangfire仪表盘配置授权///</summary>publicclassMyDashboardAuthorizationFilter:IDashboardAuthorizationFilter......
  • 232转Profinet网关接扫码枪与PLC通讯在物流分拣线上的应用
    一、背景随着生活节奏的加快,网络购物需求非常大,从而造成快递站需要快速提取快递信息已达到快速出站的效果,这就用到了扫码枪,扫码枪作为采集设备,能够迅速准确地读取货物信息。并将数据传输至PLC控制器,实现自动化分拣的功能。二、现场情况采用霍尼韦尔的扫码枪,需要接到PLC上,进行......
  • 【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service
    在使用AppService服务部署业务应用,因为有些第三方的接口需要调用者携带TLS/SSL证书(X509Certificate),在官方文档中介绍了两种方式在代码中使用证书:1)直接使用证书文件路径加载证书2)从系统的证书库中通过指纹加载证书本文中,将分别通过代码来验证以上两种方式. 第一步:使用P......
  • go net/http send req
      之前写到net/http客户端发送http请求时,会开启HTTP事务发送HTTP请求并等待远程的响应,经过下面一连串的调用,我们最终来到了标准库实现底层HTTP协议的结构体— net/http.Transport:net/http.Client.Donet/http.Client.donet/http.Client.sendnet/http.sendnet/htt......
  • .NET周刊【5月第3期 2024-05-26】
    国内文章开源低代码框架ReZeroAPI正式版本发布,界面操作直接生成APIhttps://www.cnblogs.com/sunkaixuan/p/18201175ReZero是一款.NET6+的中间件,采用MIT许可证开源,目的是降低.NETCore开发的门槛。它提供界面操作生成API的功能,支持集成到各种.NET项目中。它还提供了一系列的......
  • YOLOv5改进 | 注意力机制 | 添加全局注意力机制 GcNet【附代码+小白必备】
    ......
  • YOLOv8改进 | 注意力机制 | 添加全局注意力机制 GcNet【附代码+小白必备】
    ......
  • 在Spring Boot应用中使用RestTemplate类发送不同的HTTP GET请求(带请求头、不带请求头)
    原文链接:https://www.cnblogs.com/windyWu/p/16872871.html在本文中,你将学会在SpringBoot应用中使用RestTemplate类发送不同的HTTPGET请求。简单GET请求发送GETHTTP请求,可以使用getForObject()或getForEntity()方法。如下示例,使用getForObject()方法获取JSON字符串形式的用......