<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使用不當(dāng)引發(fā)的一個(gè)bug

          共 3272字,需瀏覽 7分鐘

           ·

          2021-03-27 06:38

          背景

          今天與大家分享一個(gè)日常開發(fā)比較容易錯(cuò)誤的點(diǎn),那就是contxt誤用導(dǎo)致的bug,我自己就因?yàn)檎`用導(dǎo)致異步更新緩存都失敗了,究竟是因?yàn)槭裁茨兀靠催@樣一個(gè)例子,光看代碼,你能看出來有什么bug嗎?

          func AsyncAdd(run func() error)  {
           //TODO: 扔進(jìn)異步協(xié)程池
           go run()
          }

          func GetInstance(ctx context.Context,id uint64) (string, error) {
           data,err := GetFromRedis(ctx,id)
           if err != nil && err != redis.Nil{
            return "", err
           }
           // 沒有找到數(shù)據(jù)
           if err == redis.Nil {
            data,err = GetFromDB(ctx,id)
            if err != nil{
             return "", err
            }
            AsyncAdd(func() error{
             return UpdateCache(ctx,id,data)
            })
           }
           return data,nil
          }

          func GetFromRedis(ctx context.Context,id uint64) (string,error) {
           // TODO: 從redis獲取信息
           return "",nil
          }

          func GetFromDB(ctx context.Context,id uint64) (string,error) {
           // TODO: 從DB中獲取信息
           return "",nil
          }

          func UpdateCache(ctx context.Context,id interface{},data string) error {
           // TODO:更新緩存信息
           return nil
          }

          func main()  {
           ctx,cancel := context.WithTimeout(context.Background(), 3 * time.Second)
           defer cancel()
           _,err := GetInstance(ctx,2021)
           if err != nil{
            return
           }
          }

          分析

          我們先簡單分析一下,這一段代碼要干什么?其實(shí)很簡單,我們想要獲取一段信息,首先會從緩存中獲取,如果緩存中獲取不到,我們就從DB中獲取,從DB中獲取到信息后,在協(xié)程池中放入更新緩存的方法,異步去更新緩存。整個(gè)設(shè)計(jì)是不是很完美,但是在實(shí)際工作中,異步更新緩存就沒有成功過?

          導(dǎo)致失敗的原因就在這一段代碼:

           AsyncAdd(func() error{
             return UpdateCache(ctx,id,data)
            })

          錯(cuò)誤的原因只有一個(gè),就是這個(gè)ctx,如果改成這樣,就啥事沒有了。

          AsyncAdd(func() error{
             ctxAsync,cancel := context.WithTimeout(context.Background(),3 * time.Second)
             defer cancel()
             return UpdateCache(ctxAsync,id,data)
            })

          看到這個(gè),想必大家就已經(jīng)知道為什么吧?

          在這個(gè)ctx樹中,根結(jié)點(diǎn)發(fā)生了cancel(),會將信號即時(shí)同步給下層,因?yàn)楫惒饺蝿?wù)的ctx也在這棵樹的節(jié)點(diǎn)上,所以當(dāng)main goroutine取消了ctx時(shí),異步任務(wù)也被取消了,導(dǎo)致了緩存更新一直失敗。

          因?yàn)槲抑皩戇^一篇關(guān)于詳解Context包,看這一篇就夠了!!!的文章,就不在這里細(xì)說其原理了,想知道其內(nèi)部是怎么實(shí)現(xiàn)的,看以前這篇文章就可以了。在這里在與大家分享一下context的使用原則,避免踩坑。

          • context.Background 只應(yīng)用在最高等級,作為所有派生 context 的根。
          • context 取消是建議性的,這些函數(shù)可能需要一些時(shí)間來清理和退出。
          • 不要把Context放在結(jié)構(gòu)體中,要以參數(shù)的方式傳遞。
          • Context作為參數(shù)的函數(shù)方法,應(yīng)該把Context作為第一個(gè)參數(shù),放在第一位。
          • 給一個(gè)函數(shù)方法傳遞Context的時(shí)候,不要傳遞nil,如果不知道傳遞什么,就使用context.TODO
          • Context的Value相關(guān)方法應(yīng)該傳遞必須的數(shù)據(jù),不要什么數(shù)據(jù)都使用這個(gè)傳遞。context.Value 應(yīng)該很少使用,它不應(yīng)該被用來傳遞可選參數(shù)。這使得 API 隱式的并且可以引起錯(cuò)誤。取而代之的是,這些值應(yīng)該作為參數(shù)傳遞。
          • Context是線程安全的,可以放心的在多個(gè)goroutine中傳遞。同一個(gè)Context可以傳給使用其的多個(gè)goroutine,且Context可被多個(gè)goroutine同時(shí)安全訪問。
          • Context 結(jié)構(gòu)沒有取消方法,因?yàn)橹挥信缮?context 的函數(shù)才應(yīng)該取消 context。

          Go 語言中的 context.Context 的主要作用還是在多個(gè) Goroutine 組成的樹中同步取消信號以減少對資源的消耗和占用,雖然它也有傳值的功能,但是這個(gè)功能我們還是很少用到。在真正使用傳值的功能時(shí)我們也應(yīng)該非常謹(jǐn)慎,使用 context.Context 進(jìn)行傳遞參數(shù)請求的所有參數(shù)一種非常差的設(shè)計(jì),比較常見的使用場景是傳遞請求對應(yīng)用戶的認(rèn)證令牌以及用于進(jìn)行分布式追蹤的請求 ID。

          總結(jié)

          寫這篇文章的目的,就是把我日常寫的bug分享出來,防止后人踩坑。已經(jīng)踩過的坑就不要再踩了,把找bug的時(shí)間節(jié)省出來,多學(xué)點(diǎn)其他知識,他不香嘛~。


          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號 「polarisxu」,回復(fù) ebook 獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 66
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  成人影视一区 | 天天干天天操天天拍 | 亚洲综合在线豆花 | 国产999高清无码精品导航 | 五月婷婷久久丁香桃色网 |