欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

golang中http請求的context傳遞到異步任務(wù)的坑及解決

 更新時(shí)間:2024年03月28日 11:05:00   作者:童話ing  
這篇文章主要介紹了golang中http請求的context傳遞到異步任務(wù)的坑及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

前言

在golang中,context.Context可以用來用來設(shè)置截止日期、同步信號(hào),傳遞請求相關(guān)值的結(jié)構(gòu)體。 與 goroutine 有比較密切的關(guān)系。

在web程序中,每個(gè)Request都需要開啟一個(gè)goroutine做一些事情,這些goroutine又可能會(huì)開啟其他的 goroutine去訪問后端資源,比如數(shù)據(jù)庫、RPC服務(wù)等,它們需要訪問一些共享的資源,比如用戶身份信息、認(rèn)證token、請求截止時(shí)間等 這時(shí)候可以通過Context,來跟蹤這些goroutine,并且通過Context來控制它們, 這就是Go語言為我們提供的Context,中文可以理解為“上下文”。

簡單看一下Context結(jié)構(gòu)

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}
  • Deadline方法是獲取設(shè)置的截止時(shí)間的意思,第一個(gè)返回值是截止時(shí)間,到了這個(gè)時(shí)間點(diǎn),Context會(huì)自動(dòng)發(fā)起取消請求; 第二個(gè)返回值ok==false時(shí)表示沒有設(shè)置截止時(shí)間,如果需要取消的話,需要調(diào)用取消函數(shù)(CancleFunc)進(jìn)行取消。
  • Done方法返回一個(gè)只讀的chan,類型為struct{},在goroutine中,如果該方法返回的chan可以讀取,則意味著parent context已經(jīng)發(fā)起了取消請求, 我們通過Done方法收到這個(gè)信號(hào)后,就應(yīng)該做清理操作,然后退出goroutine,釋放資源。之后,Err 方法會(huì)返回一個(gè)錯(cuò)誤,告知為什么 Context 被取消。
  • Err方法返回取消的錯(cuò)誤原因,Context被取消的原因。
  • Value方法獲取該Context上綁定的值,是一個(gè)鍵值對,通過一個(gè)Key才可以獲取對應(yīng)的值,這個(gè)值一般是線程安全的。

常用的

// 傳遞一個(gè)父Context作為參數(shù),返回子Context,以及一個(gè)取消函數(shù)用來取消Context。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
// 和WithCancel差不多,它會(huì)多傳遞一個(gè)截止時(shí)間參數(shù),意味著到了這個(gè)時(shí)間點(diǎn),會(huì)自動(dòng)取消Context,
// 當(dāng)然我們也可以不等到這個(gè)時(shí)候,可以提前通過取消函數(shù)進(jìn)行取消。
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

// WithTimeout和WithDeadline基本上一樣,這個(gè)表示是超時(shí)自動(dòng)取消,是多少時(shí)間后自動(dòng)取消Context的意思
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

//WithValue函數(shù)和取消Context無關(guān),它是為了生成一個(gè)綁定了一個(gè)鍵值對數(shù)據(jù)的Context,
// 綁定的數(shù)據(jù)可以通過Context.Value方法訪問到,這是我們實(shí)際用經(jīng)常要用到的技巧,一般我們想要通過上下文來傳遞數(shù)據(jù)時(shí),可以通過這個(gè)方法,
// 如我們需要tarce追蹤系統(tǒng)調(diào)用棧的時(shí)候。
func WithValue(parent Context, key, val interface{}) Context

HTTP請求的Context傳遞到異步任務(wù)的坑

看下面例子

我們將http的context傳遞到goroutine 中:

package main

import (
	"context"
	"fmt"
	"net/http"
	"time"
)

func IndexHandler(resp http.ResponseWriter, req *http.Request) {
	ctx := req.Context()
	go func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done():
				fmt.Println("gorountine off,the err is: ", ctx.Err())
				return
			default:
				fmt.Println(333)
			}
		}
	}(ctx)

	time.Sleep(1000)
	resp.Write([]byte{1})
}
func main() {

	http.HandleFunc("/test1", IndexHandler)
	http.ListenAndServe("127.0.0.1:8080", nil)
}

結(jié)果:

從上面結(jié)果來看,在http請求返回之后,傳入gorountine的context被cancel掉了,如果不巧,你在gorountine中進(jìn)行一些http調(diào)用或者rpc調(diào)用傳入了這個(gè)context,那么對應(yīng)的請求也將會(huì)被cancel掉。

因此,在http請求中異步任務(wù)出去時(shí),如果這個(gè)異步任務(wù)中需要進(jìn)行一些rpc類請求,那么就不要直接使用或者繼承http的context,否則將會(huì)被cancel。

糾其原因

http請求再結(jié)束后,將會(huì)cancel掉這個(gè)context,所以異步出去的請求中收到的context是被cancel掉的。

下面來看下源代碼:

ListenAndServe–>Server:Server方法中有一個(gè)大的for循環(huán),這個(gè)for循環(huán)中,針對每個(gè)請求,都會(huì)起一個(gè)協(xié)程進(jìn)行處理。

serve方法處理一個(gè)連接中的請求,并在一個(gè)請求serverHandler{c.server}.ServeHTTP(w, w.req)結(jié)束后cancel掉對應(yīng)的context:

// 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())
	defer func() {
		if err := recover(); err != nil && err != ErrAbortHandler {
			const size = 64 << 10
			buf := make([]byte, size)
			buf = buf[:runtime.Stack(buf, false)]
			c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
		}
		if !c.hijacked() {
			c.close()
			c.setState(c.rwc, StateClosed, runHooks)
		}
	}()

	if tlsConn, ok := c.rwc.(*tls.Conn); ok {
		if d := c.server.ReadTimeout; d != 0 {
			c.rwc.SetReadDeadline(time.Now().Add(d))
		}
		if d := c.server.WriteTimeout; d != 0 {
			c.rwc.SetWriteDeadline(time.Now().Add(d))
		}
		if err := tlsConn.Handshake(); err != nil {
			// If the handshake failed due to the client not speaking
			// TLS, assume they're speaking plaintext HTTP and write a
			// 400 response on the TLS conn's underlying net.Conn.
			if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {
				io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n")
				re.Conn.Close()
				return
			}
			c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
			return
		}
		c.tlsState = new(tls.ConnectionState)
		*c.tlsState = tlsConn.ConnectionState()
		if proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) {
			if fn := c.server.TLSNextProto[proto]; fn != nil {
				h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
				// Mark freshly created HTTP/2 as active and prevent any server state hooks
				// from being run on these connections. This prevents closeIdleConns from
				// closing such connections. See issue https://golang.org/issue/39776.
				c.setState(c.rwc, StateActive, skipHooks)
				fn(c.server, tlsConn, h)
			}
			return
		}
	}

	// HTTP/1.x from here on.

	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)
		if c.r.remain != c.server.initialReadLimitSize() {
			// If we read any bytes off the wire, we're active.
			c.setState(c.rwc, StateActive, runHooks)
		}
		.....
		.....
		// Expect 100 Continue support
		req := w.req
		if req.expectsContinue() {
			if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
				// Wrap the Body reader with one that replies on the connection
				req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
				w.canWriteContinue.setTrue()
			}
		} else if req.Header.get("Expect") != "" {
			w.sendExpectationFailed()
			return
		}

		c.curReq.Store(w)
		
		// 啟動(dòng)協(xié)程后臺(tái)讀取連接
		if requestBodyRemains(req.Body) {
			registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
		} else {
			w.conn.r.startBackgroundRead() 
		}

		// HTTP cannot have multiple simultaneous active requests.[*]
		// Until the server replies to this request, it can't read another,
		// so we might as well run the handler in this goroutine.
		// [*] Not strictly true: HTTP pipelining. We could let them all process
		// in parallel even if their responses need to be serialized.
		// But we're not going to implement HTTP pipelining because it
		// was never deployed in the wild and the answer is HTTP/2.
		serverHandler{c.server}.ServeHTTP(w, w.req)
		/**
		* 重點(diǎn)在這兒,處理完請求后將會(huì)調(diào)用w.cancelCtx()方法cancel掉context
		**/
		w.cancelCtx()
		if c.hijacked() {
			return
		}
		w.finishRequest()
		if !w.shouldReuseConnection() {
			if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
				c.closeWriteAndWait()
			}
			return
		}
		c.setState(c.rwc, StateIdle, runHooks)
		c.curReq.Store((*response)(nil))

		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
		}

		if d := c.server.idleTimeout(); d != 0 {
			c.rwc.SetReadDeadline(time.Now().Add(d))
			if _, err := c.bufr.Peek(4); err != nil {
				return
			}
		}
		c.rwc.SetReadDeadline(time.Time{})
	}
}

至此,我們知道,http請求在正常結(jié)束后將會(huì)主動(dòng)cancel掉context。

此外,在請求異常時(shí)候也會(huì)主動(dòng)cancel掉context(cancel目的就是為了快速失?。唧w可見w.conn.r.startBackgroundRead() 其中的實(shí)現(xiàn)。

在日常開發(fā)中,我們知道有時(shí)候會(huì)存在客戶端超時(shí)情況,和ctx相關(guān)的原因可歸納如下:

  • 服務(wù)端收到的請求的request context被cancel掉。
  • 客戶端本身收到context deadline exceeded錯(cuò)誤
  • 服務(wù)端業(yè)務(wù)業(yè)務(wù)使用了http的context,但沒有用于做rpc等需要建立連接的任務(wù),那么客戶端即使收到了context canceled的錯(cuò)誤,服務(wù)端實(shí)際上還是在繼續(xù)執(zhí)行業(yè)務(wù)代碼。
  • 服務(wù)端業(yè)務(wù)業(yè)務(wù)使用了http的context,并用于做rpc等需要建立連接的任務(wù),那么客戶端收到context canceled錯(cuò)誤,并且服務(wù)端也會(huì)在對應(yīng)的rpc等建立連接任務(wù)處返回context cancled的錯(cuò)誤。

最后,如果context cancel掉了,但是業(yè)務(wù)又在繼續(xù)執(zhí)行,有時(shí)候并不是我們想要的結(jié)果,因?yàn)檫@會(huì)占用資源,因此我們可以主動(dòng)在業(yè)務(wù)中通過監(jiān)聽context Done的信號(hào)來做context canceled的處理,從而可以達(dá)到快速失敗,節(jié)約資源的目的。

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Golang通脈之方法詳情

    Golang通脈之方法詳情

    這篇文章主要介紹了Golang通脈方法,Go語言中的方法(Method)是一種作用于特定類型變量的函數(shù)。這種特定類型變量叫做接收者(Receiver)。接收者的概念就類似于,其他語言中的this或者 self,具體內(nèi)容請和小編一起來學(xué)習(xí)下面文章內(nèi)容吧
    2021-10-10
  • go 熔斷原理分析與源碼解讀

    go 熔斷原理分析與源碼解讀

    這篇文章主要為大家介紹了go 熔斷原理分析與源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • golang grpc配置使用實(shí)戰(zhàn)

    golang grpc配置使用實(shí)戰(zhàn)

    本文主要介紹了golang grpc配置使用實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • Golang網(wǎng)絡(luò)模型netpoll源碼解析(具體流程)

    Golang網(wǎng)絡(luò)模型netpoll源碼解析(具體流程)

    本文介紹了Golang的網(wǎng)絡(luò)模型netpoll的實(shí)現(xiàn)原理,本文將從為什么需要使用netpoll模型,以及netpoll的具體流程實(shí)現(xiàn)兩個(gè)主要角度來展開學(xué)習(xí),感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • Golang 并發(fā)以及通道的使用方式

    Golang 并發(fā)以及通道的使用方式

    這篇文章主要介紹了Golang 并發(fā)以及通道的使用方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-03-03
  • 總結(jié)Golang四種不同的參數(shù)配置方式

    總結(jié)Golang四種不同的參數(shù)配置方式

    這篇文章主要介紹了總結(jié)Golang四種不同的參數(shù)配置方式,文章圍繞主題展開詳細(xì)的內(nèi)容戒殺,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-09-09
  • 淺談go語言中別名類型的使用

    淺談go語言中別名類型的使用

    類型別名是 Go 1.9 版本添加的新功能,主要用于解決代碼升級(jí)、遷移中存在的類型兼容性問題,本文主要介紹了go語言中別名類型的使用,感興趣的可以了解一下
    2024-01-01
  • go內(nèi)存緩存如何new一個(gè)bigcache對象示例詳解

    go內(nèi)存緩存如何new一個(gè)bigcache對象示例詳解

    這篇文章主要為大家介紹了go內(nèi)存緩存如何new一個(gè)bigcache對象示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • golang中select語句的簡單實(shí)例

    golang中select語句的簡單實(shí)例

    Go的select語句是一種僅能用于channl發(fā)送和接收消息的專用語句,此語句運(yùn)行期間是阻塞的,下面這篇文章主要給大家介紹了關(guān)于golang中select語句的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-06-06
  • Go語言Gin框架獲取請求參數(shù)的兩種方式

    Go語言Gin框架獲取請求參數(shù)的兩種方式

    在添加路由處理函數(shù)之后,就可以在路由處理函數(shù)中編寫業(yè)務(wù)處理代碼了,而編寫業(yè)務(wù)代碼第一件事一般就是獲取HTTP請求的參數(shù)吧,Gin框架在net/http包的基礎(chǔ)上封裝了獲取參數(shù)的方式,本文小編給大家介紹了獲取參數(shù)的兩種方式,需要的朋友可以參考下
    2024-01-01

最新評(píng)論