之前寫過了Go語(yǔ)言gorm框架MySQL實(shí)踐,其中對(duì)gorm框架在操作MySQL的各種基礎(chǔ)實(shí)踐,下面分享一下如何使用gorm框架對(duì)MySQL直接進(jìn)行性能測(cè)試的簡(jiǎn)單實(shí)踐。
10余年的登封網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。全網(wǎng)整合營(yíng)銷推廣的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整登封建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無(wú)論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)公司從事“登封網(wǎng)站設(shè)計(jì)”,“登封網(wǎng)站推廣”以來(lái),每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
這里我使用了一個(gè)原始的Go語(yǔ)言版本的 FunTester 測(cè)試框架,現(xiàn)在只有一個(gè)基本的方法,實(shí)在是因?yàn)镚o語(yǔ)言特性太強(qiáng)了??蚣茉O(shè)計(jì)的主要思路之一就是利用Go語(yǔ)言的閉包和方法參數(shù)特性,將一個(gè) func() 當(dāng)做性能測(cè)試的主題,通過不斷運(yùn)行這個(gè) func() 來(lái)實(shí)現(xiàn)性能測(cè)試。當(dāng)然還有另外一個(gè)思路就是運(yùn)行一個(gè)多線程任務(wù)類,類似 Java 版本的 com.funtester.base.constaint.ThreadBase 抽象類,這樣可以設(shè)置一些類的屬性,綁定一些測(cè)試資源,適配更多的測(cè)試場(chǎng)景。
下面演示select的性能測(cè)試,這里我用了隨機(jī)ID查詢的場(chǎng)景。
這里我使用從35開始遞增的ID進(jìn)行刪除。
這里使用了select的用例部分,隨機(jī)ID,然后更新name字段,隨機(jī)10個(gè)長(zhǎng)度的字符串。
這里用到了 FunTester 字段都是隨機(jī)生成。
到這里可以看出,性能測(cè)試框架用到的都是gorm框架的基礎(chǔ)API使用,這里MySQL連接池的管理工作完全交給了gorm框架完成,看資料說非常牛逼,我們只需要設(shè)置幾個(gè)參數(shù)。這個(gè)使用體現(xiàn)很像 HttpClient 設(shè)置 HTTP 連接池類似,這里我們也可以看出這些優(yōu)秀的框架使用起來(lái)都是非常簡(jiǎn)單的。
PS:關(guān)于gorm的基礎(chǔ)使用的請(qǐng)參考上一期的文章Go語(yǔ)言gorm框架MySQL實(shí)踐。
首選,如果之前使用過redis容器,我們需要先remove掉之前的容器
然后創(chuàng)建redis容器,并運(yùn)行
進(jìn)入redis容器中
接著我們通過 redis-cli 連接測(cè)試使用 redis 服務(wù)
setex指令 可以設(shè)置數(shù)據(jù)存在的時(shí)間, setex key second value
MSET 一次設(shè)置多個(gè)key-value
MGET一次獲取多個(gè)key-value
HGET
HGETALL
Hlen和hexist
Lpush 和 Lrange
Lpop和Rpop 從鏈表取出并移走數(shù)據(jù)
刪除鏈表所有數(shù)據(jù) DEL
字符串無(wú)序 不能重復(fù)
從連接池中Get出一個(gè)conn連接
在創(chuàng)建連接池之后,起一個(gè) goroutine,每隔一段 idleTime 發(fā)送一個(gè) PING 到 Redis server。其中,idleTime 略小于 Redis server 的 timeout 配置。
連接池初始化部分代碼如下:
p, err := pool.New("tcp", u.Host, concurrency) errHndlr(err) go func() { for { p.Cmd("PING") time.Sleep(idelTime * time.Second) } }()
使用 redis 傳輸數(shù)據(jù)部分代碼如下:
func redisDo(p *pool.Pool, cmd string, args ...interface{}) (reply *redis.Resp, err error) { reply = p.Cmd(cmd, args...) if err = reply.Err; err != nil { if err != io.EOF { Fatal.Println("redis", cmd, args, "err is", err) } } return }
其中,Radix.v2 連接池內(nèi)部進(jìn)行了連接池內(nèi)連接的獲取和放回,代碼如下:
// Cmd automatically gets one client from the pool, executes the given command // (returning its result), and puts the client back in the pool func (p *Pool) Cmd(cmd string, args ...interface{}) *redis.Resp { c, err := p.Get() if err != nil { return redis.NewResp(err) } defer p.Put(c) return c.Cmd(cmd, args...) }
這樣,我們就有了 keepalive 的機(jī)制,不會(huì)出現(xiàn) timeout 的連接了,從 redis 連接池里面取出的連接都是可用的連接了??此坪?jiǎn)單的代碼,卻完美的解決了連接池里面超時(shí)連接的問題。同時(shí),就算 Redis server 重啟等情況,也能保證連接自動(dòng)重連。
開始本文之前,我們看一段Go連接數(shù)據(jù)庫(kù)的代碼:
本文內(nèi)容我們將解釋連接池背后是如何工作的,并 探索 如何配置數(shù)據(jù)庫(kù)能改變或優(yōu)化其性能。
轉(zhuǎn)自:
整理:地鼠文檔:
那么sql.DB連接池是如何工作的呢?
需要理解的最重要一點(diǎn)是,sql.DB池包含兩種類型的連接——“正在使用”連接和“空閑”連接。當(dāng)您使用連接執(zhí)行數(shù)據(jù)庫(kù)任務(wù)(例如執(zhí)行SQL語(yǔ)句或查詢行)時(shí),該連接被標(biāo)記為正在使用,任務(wù)完成后,該連接被標(biāo)記為空閑。
當(dāng)您使用Go執(zhí)行數(shù)據(jù)庫(kù)操作時(shí),它將首先檢查池中是否有可用的空閑連接。如果有可用的連接,那么Go將重用這個(gè)現(xiàn)有連接,并在任務(wù)期間將其標(biāo)記為正在使用。如果在您需要空閑連接時(shí)池中沒有空閑連接,那么Go將創(chuàng)建一個(gè)新的連接。
當(dāng)Go重用池中的空閑連接時(shí),與該連接有關(guān)的任何問題都會(huì)被優(yōu)雅地處理。異常連接將在放棄之前自動(dòng)重試兩次,這時(shí)Go將從池中刪除異常連接并創(chuàng)建一個(gè)新的連接來(lái)執(zhí)行該任務(wù)。
連接池有四個(gè)方法,我們可以使用它們來(lái)配置連接池的行為。讓我們一個(gè)一個(gè)地來(lái)討論。
SetMaxOpenConns()方法允許您設(shè)置池中“打開”連接(使用中+空閑連接)數(shù)量的上限。默認(rèn)情況下,打開的連接數(shù)是無(wú)限的。
一般來(lái)說,MaxOpenConns設(shè)置得越大,可以并發(fā)執(zhí)行的數(shù)據(jù)庫(kù)查詢就越多,連接池本身成為應(yīng)用程序中的瓶頸的風(fēng)險(xiǎn)就越低。
但讓它無(wú)限并不是最好的選擇。默認(rèn)情況下,PostgreSQL最多100個(gè)打開連接的硬限制,如果達(dá)到這個(gè)限制的話,它將導(dǎo)致pq驅(qū)動(dòng)返回”sorry, too many clients already”錯(cuò)誤。
為了避免這個(gè)錯(cuò)誤,將池中打開的連接數(shù)量限制在100以下是有意義的,可以為其他需要使用PostgreSQL的應(yīng)用程序或會(huì)話留下足夠的空間。
設(shè)置MaxOpenConns限制的另一個(gè)好處是,它充當(dāng)一個(gè)非?;镜南蘖髌鳎乐箶?shù)據(jù)庫(kù)同時(shí)被大量任務(wù)壓垮。
但設(shè)定上限有一個(gè)重要的警告。如果達(dá)到MaxOpenConns限制,并且所有連接都在使用中,那么任何新的數(shù)據(jù)庫(kù)任務(wù)將被迫等待,直到有連接空閑。在我們的API上下文中,用戶的HTTP請(qǐng)求可能在等待空閑連接時(shí)無(wú)限期地“掛起”。因此,為了緩解這種情況,使用上下文為數(shù)據(jù)庫(kù)任務(wù)設(shè)置超時(shí)是很重要的。我們將在書的后面解釋如何處理。
SetMaxIdleConns()方法的作用是:設(shè)置池中空閑連接數(shù)的上限。缺省情況下,最大空閑連接數(shù)為2。
理論上,在池中允許更多的空閑連接將增加性能。因?yàn)樗鼫p少了從頭建立新連接發(fā)生概率—,因此有助于節(jié)省資源。
但要意識(shí)到保持空閑連接是有代價(jià)的。它占用了本來(lái)可以用于應(yīng)用程序和數(shù)據(jù)庫(kù)的內(nèi)存,而且如果一個(gè)連接空閑時(shí)間過長(zhǎng),它也可能變得不可用。例如,默認(rèn)情況下MySQL會(huì)自動(dòng)關(guān)閉任何8小時(shí)未使用的連接。
因此,與使用更小的空閑連接池相比,將MaxIdleConns設(shè)置得過高可能會(huì)導(dǎo)致更多的連接變得不可用,浪費(fèi)資源。因此保持適量的空閑連接是必要的。理想情況下,你只希望保持一個(gè)連接空閑,可以快速使用。
另一件要指出的事情是MaxIdleConns值應(yīng)該總是小于或等于MaxOpenConns。Go會(huì)強(qiáng)制保證這點(diǎn),并在必要時(shí)自動(dòng)減少M(fèi)axIdleConns值。
SetConnMaxLifetime()方法用于設(shè)置ConnMaxLifetime的極限值,表示一個(gè)連接保持可用的最長(zhǎng)時(shí)間。默認(rèn)連接的存活時(shí)間沒有限制,永久可用。
如果設(shè)置ConnMaxLifetime的值為1小時(shí),意味著所有的連接在創(chuàng)建后,經(jīng)過一個(gè)小時(shí)就會(huì)被標(biāo)記為失效連接,標(biāo)志后就不可復(fù)用。但需要注意:
理論上,ConnMaxLifetime為無(wú)限大(或設(shè)置為很長(zhǎng)生命周期)將提升性能,因?yàn)檫@樣可以減少新建連接。但是在某些情況下,設(shè)置短期存活時(shí)間有用。比如:
如果您決定對(duì)連接池設(shè)置ConnMaxLifetime,那么一定要記住連接過期(然后重新創(chuàng)建)的頻率。例如,如果連接池中有100個(gè)打開的連接,而ConnMaxLifetime為1分鐘,那么您的應(yīng)用程序平均每秒可以殺死并重新創(chuàng)建多達(dá)1.67個(gè)連接。您不希望頻率太大而最終影響性能吧。
SetConnMaxIdleTime()方法在Go 1.15版本引入對(duì)ConnMaxIdleTime進(jìn)行配置。其效果和ConnMaxLifeTime類似,但這里設(shè)置的是:在被標(biāo)記為失效之前一個(gè)連接最長(zhǎng)空閑時(shí)間。例如,如果我們將ConnMaxIdleTime設(shè)置為1小時(shí),那么自上次使用以后在池中空閑了1小時(shí)的任何連接都將被標(biāo)記為過期并被后臺(tái)清理操作刪除。
這個(gè)配置非常有用,因?yàn)樗馕吨覀兛梢詫?duì)池中空閑連接的數(shù)量設(shè)置相對(duì)較高的限制,但可以通過刪除不再真正使用的空閑連接來(lái)周期性地釋放資源。
所以有很多信息要吸收。這在實(shí)踐中意味著什么?我們把以上所有的內(nèi)容總結(jié)成一些可行的要點(diǎn)。
1、根據(jù)經(jīng)驗(yàn),您應(yīng)該顯式地設(shè)置MaxOpenConns值。這個(gè)值應(yīng)該低于數(shù)據(jù)庫(kù)和操作系統(tǒng)對(duì)連接數(shù)量的硬性限制,您還可以考慮將其保持在相當(dāng)?shù)偷乃?,以充?dāng)基本的限流作用。
對(duì)于本書中的項(xiàng)目,我們將MaxOpenConns限制為25個(gè)連接。我發(fā)現(xiàn)這對(duì)于小型到中型的web應(yīng)用程序和API來(lái)說是一個(gè)合理的初始值,但理想情況下,您應(yīng)該根據(jù)基準(zhǔn)測(cè)試和壓測(cè)結(jié)果調(diào)整這個(gè)值。
2、通常,更大的MaxOpenConns和MaxIdleConns值會(huì)帶來(lái)更好的性能。但是,效果是逐漸降低的,而且您應(yīng)該注意,太多的空閑連接(連接沒有被復(fù)用)實(shí)際上會(huì)導(dǎo)致性能下降和不必要的資源消耗。
因?yàn)镸axIdleConns應(yīng)該總是小于或等于MaxOpenConns,所以對(duì)于這個(gè)項(xiàng)目,我們還將MaxIdleConns限制為25個(gè)連接。
3、為了降低上面第2點(diǎn)的風(fēng)險(xiǎn),通常應(yīng)該設(shè)置ConnMaxIdleTime值來(lái)刪除長(zhǎng)時(shí)間未使用的空閑連接。在這個(gè)項(xiàng)目中,我們將設(shè)置ConnMaxIdleTime持續(xù)時(shí)間為15分鐘。
4、ConnMaxLifetime默認(rèn)設(shè)置為無(wú)限大是可以的,除非您的數(shù)據(jù)庫(kù)對(duì)連接生命周期施加了硬限制,或者您需要它協(xié)助一些操作,比如優(yōu)雅地交換數(shù)據(jù)庫(kù)。這些都不適用于本項(xiàng)目,所以我們將保留這個(gè)默認(rèn)的無(wú)限制配置。
與其硬編碼這些配置,不如更新cmd/api/main.go文件通過命令行參數(shù)讀取配置。
ConnMaxIdleTime值比較有意思,因?yàn)槲覀兿M鼈鬟f一段時(shí)間,最終需要將其轉(zhuǎn)換為Go的time.Duration類型。這里有幾個(gè)選擇:
1、我們可以使用一個(gè)整數(shù)來(lái)表示秒(或分鐘)的數(shù)量,并將其轉(zhuǎn)換為time.Duration。
2、我們可以使用一個(gè)表示持續(xù)時(shí)間的字符串——比如“5s”(5秒)或“10m”(10分鐘)——然后使用time.ParseDuration()函數(shù)解析它。
3、兩種方法都可以很好地工作,但是在這個(gè)項(xiàng)目中我們將使用選項(xiàng)2。繼續(xù)并更新cmd/api/main.go文件如下:
File: cmd/api/main.go