<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Context這三個(gè)應(yīng)用場(chǎng)景,你知嗎

          共 1570字,需瀏覽 4分鐘

           ·

          2021-10-28 16:29


          653bca6b0a7ef23172031534fe292cdb.webp




          用戶發(fā)送 開(kāi)始消費(fèi) 請(qǐng)求時(shí):開(kāi)啟多個(gè)協(xié)程開(kāi)始消費(fèi)消息隊(duì)列某個(gè)topic的信息;

          用戶發(fā)送 結(jié)束消費(fèi) 請(qǐng)求時(shí):把消費(fèi)中的topic相關(guān)的協(xié)程關(guān)閉掉,結(jié)束消費(fèi);





          0e66ad5d10e3bc85ec382835f81c7814.webp

          ffb402ec1a0798b7c411329d0103bd45.webp

          8f7e02a9ce179baa519f60bb9cd85811.webp

          33aab07f80c75e1c17897fc61518730c.webp

          99934eb696ec6f9f4260f9e6de68cafe.webp

          beaf538bc703cb9a4a95fd8c05f7dca2.webp


          跨服務(wù)傳遞信息

          107be817f10a47724239169b4a8a357e.webp


          現(xiàn)在具備一定規(guī)模的互聯(lián)網(wǎng)公司都用微服務(wù)形式讓各系統(tǒng)組合起來(lái)為用戶提供服務(wù),一個(gè)簡(jiǎn)單的業(yè)務(wù)在流程上可能需要十幾個(gè)甚至幾十個(gè)系統(tǒng)間互相調(diào)用。由于每個(gè)系統(tǒng)內(nèi)部的正確性無(wú)法保證,若出現(xiàn)了case,比如用戶反饋積分少發(fā)了,就需要排查這十幾個(gè)系統(tǒng)的日志信息,看問(wèn)題出在哪里。


          此處需要一個(gè)ID憑證,ID是請(qǐng)求級(jí)別的,在各個(gè)系統(tǒng)中記錄著與此請(qǐng)求相關(guān)的日志信息,我們把它叫做trace ID。把日志采集并落盤到ES這樣的存儲(chǔ)中,有case時(shí)只需要拿到請(qǐng)求的trace ID就可以把全流程的關(guān)鍵信息還原出來(lái)。如圖所示:

          2f379dfb90497d5c265edc454dfe56c4.webp

          在Golang web服務(wù)中,每個(gè)請(qǐng)求都是開(kāi)一個(gè)協(xié)程去處理的。系統(tǒng)間傳遞信息時(shí),若通信協(xié)議用HTTP,那trace ID等信息可放在HTTP Header中,在web框架的middle層把這些信息存入Context。demo如下:

          //?檢測(cè)上游服務(wù)是否傳遞traceID信息,若傳遞了直接使用
          if?v,?ok?:=?req.Header["my-awesome-trace-ID"];?ok?{
          ???traceID?=?v[0]
          }?else?{
          ??//?若沒(méi)傳則用公共庫(kù)生成一個(gè)全局唯一的traceID信息
          ??traceID?=?GenTraceID()
          ??req.Header["my-awesome-trace-ID"]?=?[]string{traceID}
          }
          //?處理完各種請(qǐng)求上下文信息后,把這些信息統(tǒng)一存儲(chǔ)到ctx中,傳遞給業(yè)務(wù)層的對(duì)應(yīng)Handler
          ctx?=?context.WithValue(ctx,?ContentKey,?record)

          Context處理請(qǐng)求上下文這塊主要用到了WithValue,這個(gè)函數(shù)接收一個(gè)ctx和一對(duì)k-v。把k-v對(duì)存起來(lái)后返回一個(gè)子ctx,這次我們先簡(jiǎn)單介紹其使用場(chǎng)景,下篇文章會(huì)從源碼層面理解這個(gè)函數(shù)。


          ctx的生命周期是 伴隨請(qǐng)求開(kāi)始而誕生、請(qǐng)求結(jié)束而終止的。在請(qǐng)求中ctx會(huì)跨越多個(gè)函數(shù)多個(gè)協(xié)程,在打日志時(shí),第一個(gè)參數(shù)預(yù)留給ctx是因?yàn)槿罩編?kù)需要從Context中抽取trace ID等信息,從而記錄下完整的日志。獲取信息時(shí)只需要調(diào)用context的Value方法,demo如下:

          //?從Context中獲取traceID,?打到日志里
          v?:=?ctx.Value("my-awesome-trace-ID")

          這里畫(huà)個(gè)圖幫助理解:

          6c1841cd29878d8adf72332c0394456b.webp

          若我們的系統(tǒng)也需要請(qǐng)求第三方服務(wù),同樣應(yīng)把trace ID等信息放入HTTP Header后發(fā)送請(qǐng)求,其他服務(wù)按照同樣的流程接收到trace ID后開(kāi)始內(nèi)部邏輯處理。這樣一個(gè)請(qǐng)求在多個(gè)系統(tǒng)中就通過(guò)trace ID串聯(lián)起了整個(gè)流程。除trace ID外,Context還可以傳遞 URL Path、請(qǐng)求時(shí)間、Caller等信息。




          98241fc86e02d43a32919e70767fb18c.webp

          88f07f07b28259139804a093cdf66f7d.webp

          d328e98e0481e4cc96e6f2e37143d083.webp

          e5c28b46b4ed7af1f38cbbfe143f9036.webp

          多協(xié)程消費(fèi)demo:

          func?main()?{
          ?//?此協(xié)程負(fù)責(zé)監(jiān)聽(tīng)錯(cuò)誤信息,開(kāi)啟消費(fèi)
          ?go?func()?{
          ??for?{
          ???select?{
          ???//?code
          ???}
          ??}
          ?}()

          ?//?此協(xié)程負(fù)責(zé)監(jiān)聽(tīng)re-balance信息,開(kāi)啟消費(fèi)
          ?go?func()?{
          ??for?{
          ???select?{
          ???//?code
          ???}
          ??}
          ?}()
          ??//?...
          }


          d4c22c8872d53d3405ac8a6541d58a89.webp


          func?main()?{
          ?ctx,?cancel?:=?context.WithCancel(context.Background())

          ?//?此協(xié)程負(fù)責(zé)監(jiān)聽(tīng)錯(cuò)誤信息,開(kāi)啟消費(fèi)
          ?go?func()?{
          ??for?{
          ???select?{
          ???case?<-ctx.Done():
          ????fmt.Println("退出監(jiān)聽(tīng)錯(cuò)誤協(xié)程")
          ????return
          ???default:
          ????fmt.Println("邏輯處理中...")
          ???}
          ??}
          ?}()

          ?//?此協(xié)程負(fù)責(zé)監(jiān)聽(tīng)re-balance信息,開(kāi)啟消費(fèi)
          ?go?func()?{
          ??for?{
          ???select?{
          ???case?<-ctx.Done():
          ????fmt.Println("退出監(jiān)聽(tīng)re-balance協(xié)程")
          ????return
          ???default:
          ????fmt.Println("邏輯處理中...")
          ???}
          ??}
          ?}()

          ?//?調(diào)用cancelFunc,?結(jié)束消費(fèi)
          ?cancel()
          }


          9a99098652df15142f09d422932749a1.webp

          控制協(xié)程關(guān)閉



          上面代碼用到了WithCancel方法,調(diào)用它會(huì)返回一個(gè)可被取消的ctx和CancelFunc,需要取消ctx時(shí),調(diào)用cancel函數(shù)即可。context有個(gè)Done方法,這個(gè)方法返回一個(gè)channel,當(dāng)Context被取消時(shí),這個(gè)channel會(huì)被關(guān)閉。消費(fèi)中的協(xié)程通過(guò)select監(jiān)聽(tīng)這個(gè)channel,收到關(guān)閉信號(hào)后一個(gè)return就能結(jié)束消費(fèi)。


          CancelFunc可以預(yù)防系統(tǒng)做不必要的工作。比如用戶請(qǐng)求A接口時(shí),A接口內(nèi)部需要請(qǐng)求A database、B cache 、C System獲取各種數(shù)據(jù),把這些數(shù)據(jù)經(jīng)過(guò)計(jì)算后組裝到一起返回給調(diào)用方。這是正常情況的時(shí)序圖:

          29c3e300ae16220c2ffb26a94b2066eb.webp

          但如果用戶在訪問(wèn)網(wǎng)站時(shí)覺(jué)得沒(méi)意思,去其他網(wǎng)站了。此時(shí)若你的服務(wù)收到用戶請(qǐng)求后繼續(xù)去訪問(wèn)其他C system、B database就是浪費(fèi)資源。比較符合直覺(jué)的做法是:當(dāng)業(yè)務(wù)請(qǐng)求取消時(shí),你的系統(tǒng)也應(yīng)該停止請(qǐng)求下游系統(tǒng)。前面我們介紹過(guò)context在系統(tǒng)中貫穿請(qǐng)求周期,那么當(dāng)用戶取消訪問(wèn)時(shí),只要context監(jiān)聽(tīng)取消事件并在用戶取消時(shí)發(fā)送取消事件,就可以取消請(qǐng)求了。

          2831e260f6092263c7e20914305ec088.webp

          這里有份demo代碼,項(xiàng)目啟動(dòng)后,可以用curl localhost:8888訪問(wèn)這個(gè)接口,若1s內(nèi)取消請(qǐng)求,服務(wù)端會(huì)打印出request canceleld,正常情況下,服務(wù)會(huì)返回process finished。

          func?main()?{
          ?http.ListenAndServe(":8888",?http.HandlerFunc(func(w?http.ResponseWriter,?r?*http.Request)?{
          ??ctx?:=?r.Context()
          ??fmt.Println("get?request")
          ??select?{
          ??case?<-time.After(1?*?time.Second):
          ???w.Write([]byte("process?finished"))
          ??case?<-ctx.Done():
          ???fmt.Println("request?canceleld")
          ??}
          ?}))
          }

          除了用戶中途取消請(qǐng)求的情況,還有一種情況也可以用到cancelFunc:服務(wù)A的返回?cái)?shù)據(jù)依賴服務(wù)B和服務(wù)C的相關(guān)接口,若服務(wù)B或者服務(wù)C掛了,此次請(qǐng)求就算失敗了,沒(méi)必要再訪問(wèn)另一個(gè)服務(wù),此時(shí)也可以用CancelFunc。Demo如下:

          func?getUserInfoBySystemA(ctx?context.Context)?error?{
          ?time.Sleep(100?*?time.Millisecond)
          ?//?模擬請(qǐng)求出錯(cuò)的情況
          ?return?errors.New("failed")
          }

          func?getOrderInfoBySystemB(ctx?context.Context)?{
          ?select?{
          ?case?<-time.After(500?*?time.Millisecond):
          ??fmt.Println("process?finished")
          ?case?<-ctx.Done():
          ??fmt.Println("process?cancelled")
          ?}
          }

          func?main()?{
          ?ctx,?cancel?:=?context.WithCancel(context.Background())

          ?//并發(fā)從兩個(gè)服務(wù)中獲取相關(guān)數(shù)據(jù)
          ?go?func()?{
          ??err?:=?getUserInfoBySystemA(ctx)
          ??if?err?!=?nil?{
          ???//?發(fā)生錯(cuò)誤,調(diào)用cancelFunc
          ???cancel()
          ??}
          ?}()

          ?getOrderInfoBySystemB(ctx)
          }






          a2915d29ab0055891e50e4e36ceebefe.webp

          94f753de325ed758cc3759f4b9b86310.webp

          9c218caa4570c42f5e8519dd5cf55601.webp

          5d6d043ac537ecc3e9933213244b9795.webp

          19315997de1ba8153b0485c22fbba7fa.webp

          控制超時(shí)取消



          如果你的服務(wù)對(duì)外承諾的SLA是100ms,但系統(tǒng)依賴的服務(wù)B的HTTP接口有點(diǎn)不穩(wěn)定,有時(shí)50ms就能返回結(jié)果,有時(shí)100ms才能返回結(jié)果,為了保證你服務(wù)的SLA,可以用Context的WithTimeout方法設(shè)置一個(gè)超時(shí)時(shí)間,demo如下:

          func?main()?{
          ?//?設(shè)置超時(shí)時(shí)間100ms
          ?ctx,?_?:=?context.WithTimeout(context.Background(),?100*time.Millisecond)

          ?//?構(gòu)建一個(gè)HTTP請(qǐng)求
          ?req,?_?:=?http.NewRequest(http.MethodGet,?"https://www.baidu.com/",?nil)
          ?//?把ctx信息傳進(jìn)去
          ?req?=?req.WithContext(ctx)

          ?client?:=?&http.Client{}
          ?//?向百度發(fā)送請(qǐng)求
          ?res,?err?:=?client.Do(req)
          ?if?err?!=?nil?{
          ??fmt.Println("Request?failed:",?err)
          ? return }
          ?fmt.Println("Response?received,?status?code:",?res.StatusCode)
          }

          正常情況下,會(huì)得到這樣的輸出:

          Response?received,?status?code:?200

          如果我們請(qǐng)求百度超時(shí)了,會(huì)得到這樣的輸出:

          Request failed: Get https://www.baidu.com/: context deadline exceeded

          f3c70e48a47172c3e0d3bafeaaf56891.webp

          2630c7a7c3767b41442f12b2506cb1a6.webp




          歡迎關(guān)注我的公眾號(hào)~


          瀏覽 98
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  豆花视频国产精品 | 操骚逼屄视频 | 无码免费在线观看高清 | 国产精品粉嫩在线播放 | 日本三级网 |