<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>

          列舉幾個(gè) Go 語言常見的坑

          共 4236字,需瀏覽 9分鐘

           ·

          2021-03-26 03:14

          點(diǎn)擊上方“Go編程時(shí)光”,選擇“加為星標(biāo)

          一時(shí)間關(guān)注Go技術(shù)干貨!


          364979c4fe55cbba4b224f559b04de11.webp

          我喜歡 Go 語言有幾個(gè)原因:

          1. 語言本身極其簡潔(只有 25 個(gè)關(guān)鍵字);

          2. 能輕而易舉地實(shí)現(xiàn)交叉編譯;

          3. 天然支持創(chuàng)建可靠的 HTTP 服務(wù)器;

          從根本上來講,Go 是一種 boring 的語言,可能這就是為什么可以用它來開發(fā)一些諸如 Docker 和 Kubernetes 等很棒的項(xiàng)目,像 Cloudflare 等具有高性能和彈性要求的公司也正在使用它。

          盡管上手很容易,但是有很多細(xì)節(jié)還是值得關(guān)注。如果你在不清楚的情況下編寫代碼,很可能會(huì)導(dǎo)致各種稀奇古怪的問題,并且很難發(fā)現(xiàn)和糾正錯(cuò)誤。

          下面會(huì)給大家列舉一些常見錯(cuò)誤,是在 review 生產(chǎn)代碼時(shí)發(fā)現(xiàn)的。希望你再遇到相同問題時(shí)能輕松地解決。

          HTTP 超時(shí)時(shí)間

          HTTP 超時(shí)時(shí)間,其實(shí)在點(diǎn)擊已經(jīng)跟大家討論過這個(gè)問題。但仍然值得再提一提,因?yàn)楹玫慕鉀Q方案總是需要更多的時(shí)間思考的。

          使用默認(rèn)的 HTTP 客戶端可以發(fā)出 HTTP 請求,為了說明問題,下面是一個(gè)使用 GET 請求訪問 google.com 的例子:

          package?main

          import?(
          ????"io/ioutil"
          ????"log"
          ????"net/http"
          )
          var?(
          ????c?=?&http.Client{}
          )
          func?main()?{
          ????req,?err?:=?http.NewRequest("GET",?"google.com",?nil)
          ????if?err?!=?nil?{
          ????????log.Fatal(err)
          ????}
          ????res,?err?:=?c.Do(req)
          ????if?err?!=?nil?{
          ????????log.Fatal(err)
          ????}
          ????defer?res.Body.Close()
          ????b,?_?:=?ioutil.ReadAll(res.Body)
          ????...
          }

          正如文章指出的,默認(rèn)的 HTTP 客戶端沒有設(shè)置超時(shí)時(shí)間,這意味著請求有可能會(huì)被長時(shí)間掛起(ps:具體原因可以查看原文)

          所以,解決這個(gè)問題最好的辦法是什么呢?

          &http.Client{Timeout: time.Minute},給 HTTP 客戶端定義一個(gè)合理的超時(shí)時(shí)間。你也可以考慮給 HTTP 請求加上 context,這樣做有幾個(gè)好處:

          1. 有能力取消正在進(jìn)行的 HTTP 請求;

          2. 為一些特殊請求指定超時(shí)時(shí)間;

          第 2 個(gè)好處顯得尤為重要,比如你知道有幾個(gè)請求需要耗時(shí)很長時(shí)間,超過 1 個(gè)小時(shí)。但是你又不想每個(gè)請求都設(shè)置這么長的超時(shí)時(shí)間,你就可以只針對特殊請求設(shè)置比較長的超時(shí)時(shí)間。

          上面的例子中,如果加上 context 代碼會(huì)像下面這樣:

          ctx,?cancel?:=?context.WithTimeout(context.Background(),?time.Minute)
          defer?cancel()
          req?=?req.WithContext(ctx)
          res,?err?:=?c.Do(req)
          ...

          請求時(shí)間如果超過了超時(shí)時(shí)間,c.Do() 調(diào)用就會(huì)返回 DeadlineExceeded 錯(cuò)誤,可以很容易地處理錯(cuò)誤或者重試。

          數(shù)據(jù)庫連接

          我參與的每一個(gè) Go 項(xiàng)目幾乎都會(huì)出現(xiàn)數(shù)據(jù)庫連接問題。我認(rèn)為對剛?cè)腴T Go 語言的新手來說,有個(gè)難以繞過去的點(diǎn),sql.DB 對象是并發(fā)安全的連接池,而不是單個(gè)數(shù)據(jù)庫連接。這意味著連接使用完之后如果沒有返還給進(jìn)程池,會(huì)輕易導(dǎo)致連接數(shù)耗盡,甚至最后導(dǎo)致應(yīng)用程序宕掉。

          例如,數(shù)據(jù)庫連接池包含打開和空閑連接,分別是通過下面這些選項(xiàng)設(shè)置的:

          • SetConnMaxLifetime,連接可以重用的最長時(shí)間;

          • SetMaxIdleConns,最大的空閑連接數(shù)量;

          • SetMaxOpenConns,最大的打開連接數(shù)量;

          需要注意的是,即使你的最大打開連接數(shù)設(shè)置成 200,如果連接使用完不返還連接池,應(yīng)用程序也有可能會(huì)耗盡數(shù)據(jù)庫能接受的最大連接數(shù),最后導(dǎo)致宕機(jī)、重啟服務(wù)。你需要檢查數(shù)據(jù)庫設(shè)置,以確保正確設(shè)置了這些參數(shù)。

          如果數(shù)據(jù)庫沒有設(shè)置這些參數(shù),應(yīng)用程序?qū)⑤p而易舉地耗盡數(shù)據(jù)庫能接受的連接數(shù)。

          讓我們回到進(jìn)程池的問題上,查詢數(shù)據(jù)庫之后,很多開發(fā)人員會(huì)忘記關(guān)閉 *sql.Rows 對象,這就會(huì)導(dǎo)致超出最大連接數(shù)限制,并導(dǎo)致死鎖或者高延遲。下面給大家展示下類似的代碼片段:

          package?main
          import?(
          ????"context"
          ????"database/sql"
          ????"fmt"
          ????"log"
          )
          var?(
          ????ctx?context.Context
          ????db??*sql.DB
          )
          func?main()?{
          ????age?:=?27
          ????ctx,?cancel?:=?context.WithTimeout(context.Background(),?time.Minute)
          ????defer?cancel()
          ????rows,?err?:=?db.QueryContext(ctx,?"SELECT?name?FROM?users?WHERE?age=?",?age)
          ????if?err?!=?nil?{
          ????????log.Fatal(err)
          ????}
          ????for?rows.Next()?{
          ????????var?name?string
          ????????if?err?:=?rows.Scan(&name);?err?!=?nil?{
          ????????????log.Fatal(err)
          ????????}
          ????????fmt.Println(name)
          ????}
          ????...
          }

          相信你也注意到,正如能在 HTTP 請求上添加 context 一樣,我們也可以在數(shù)據(jù)庫查詢時(shí)添加超時(shí)時(shí)間的 context。這沒什么問題。

          正如上面討論的,我們需要關(guān)閉 rows 對象將連接返還給進(jìn)程池,防止連接數(shù)超出。

          rows,?err?:=?db.QueryContext(ctx,?"SELECT?name?FROM?users?WHERE?age=?",?age)
          if?err?!=?nil?{
          ????log.Fatal(err)
          }
          defer?rows.Close()

          如果在函數(shù)或者包之間傳遞數(shù)據(jù)庫連接,尤其難以發(fā)現(xiàn)這一點(diǎn)。

          goroutine 或者內(nèi)存泄漏

          最后一個(gè)要討論的常見問題是 goroutine 泄漏,一般這個(gè)問題難以發(fā)現(xiàn),但通常是由開發(fā)人員的錯(cuò)誤引起的。

          使用 channel 時(shí)通常會(huì)發(fā)生這種問題,比如:

          package?main
          func?main()?{
          ????c?:=?make(chan?error)
          ????go?func()?{
          ????????for?err?:=?range?c?{
          ????????????if?err?!=?nil?{
          ????????????????panic(err)
          ????????????}
          ????????}
          ????}()
          ????c?<-?someFunc()
          ????...
          }

          如果我們不關(guān)閉通道 c 或者 someFunc() 不返回錯(cuò)誤,我們初始化的 goroutine 將會(huì)掛起直到程序終止。

          我們不可能找出每一個(gè)導(dǎo)致 goroutine 泄漏的 地方,我通常采用兩種方法來檢測和消除它們。

          第一種方法是在單元測試方法里使用探測器,比如使用 Uber 開源的 goleak 庫,就像下面這個(gè)例子一樣:

          func?TestA(t?*testing.T)?{
          ????defer?goleak.VerifyNone(t)
          ????//?test?logic?here.
          }

          這段代碼就會(huì)驗(yàn)證,在代碼優(yōu)美關(guān)閉 30s 之后是否還有多余的 goroutine 在運(yùn)行。

          另一種方法是在應(yīng)用程序的運(yùn)行實(shí)例上使用 Go profiler,并查看存活的 goroutine 數(shù)量。其中一種方法就是使用 net/http/pprof 庫,并查看生成的火焰圖。

          就像下面這樣使用它:

          import?_?"net/http/pprof"
          func?someFunc()?{
          ????go?func()?{
          ????????log.Println(http.ListenAndServe("localhost:6060",?nil))
          ????}
          }

          上面這段代碼,pprof 占用 6060 端口,對于特別嚴(yán)重的泄漏,如果你刷新將會(huì)看到協(xié)程數(shù)量在增多;對于更多的一些微小泄漏問題,則需要查看 profile 發(fā)現(xiàn)具體的問題,profile 頁面就像下面這樣:

          goroutine?profile:?total?39
          2?@?0x43cf10?0x44ca6b?0x980600?0x46b301
          #????0x9805ff????database/sql.(*DB).connectionCleaner+0x36f??/usr/local/go/src/database/sql/sql.go:950

          2?@?0x43cf10?0x44ca6b?0x980b18?0x46b301
          #????0x980b17????database/sql.(*DB).connectionOpener+0xe7????/usr/local/go/src/database/sql/sql.go:1052

          2?@?0x43cf10?0x44ca6b?0x980c4b?0x46b301
          #????0x980c4a????database/sql.(*DB).connectionResetter+0xfa??/usr/local/go/src/database/sql/sql.go:1065
          ...

          如果你的應(yīng)用程序是空閑的,但是你又看見大數(shù)據(jù)量的 goroutine,這說明程序已經(jīng)有問題了。確認(rèn)泄漏位置之后,我仍然建議在單元測試中使用探測器,以確保解決問題。

          希望上面討論的這些常見錯(cuò)誤,如果以后你也遇到,可以幫助你更快地識(shí)別并完美地解決問題。


          作者:Tyler Finethy
          原文:https://medium.com/better-programming/common-go-pitfalls-a92197cd96d2

          ? ?

          --?END?--


          喜歡明哥文章的同學(xué)歡迎長按下圖訂閱!

          ???

          瀏覽 53
          點(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>
                  日韩伦人妻无码 | 国产黄色影院 | 欧美操逼逼视频 | 大色吻97综合 | 2022天天操 |