go-ycsb:一個(gè) Go 的 YCSB 移植

YCSB 是一個(gè)非常出名的性能測(cè)試框架,我們可以非常方便的用它來對(duì)系統(tǒng)進(jìn)行多維度的性能測(cè)試,本來我也準(zhǔn)備使用它來對(duì)我們系統(tǒng)進(jìn)行性能測(cè)試的,但在調(diào)研了一番之后,我決定直接用 Go 來完全移植一個(gè)。過年的時(shí)候就一直在干這件事情,于是就有了 go-ycsb。

為什么需要 YCSB?

先來說說為什么我們需要 YCSB,對(duì)于一個(gè)系統(tǒng)來說,用戶在試用之前,通常都會(huì)問『你的性能怎樣?』,但其實(shí)這句話是非常不好回答的。所以通常業(yè)界都會(huì)用一些基準(zhǔn)的性能測(cè)試工具來衡量。另一方面,一個(gè)系統(tǒng),性能并不是只有一個(gè)維度,譬如 sharding 的系統(tǒng)可能隨機(jī) read 一個(gè) key 非??欤绻琼樞?scan 一批 key 性能就可能嗝屁了。再就是性能其實(shí)也跟數(shù)據(jù)的分布有關(guān)系,譬如有些數(shù)據(jù)就是熱點(diǎn),需要頻繁操作,而大部分?jǐn)?shù)據(jù)其實(shí)是冷數(shù)據(jù)。剛好 YCSB 都能很好的支持這些特性。

再來說說為什么需要 Go 的 YCSB,其實(shí)無非就是兩個(gè)原因:

  1. 我不會(huì) Java。雖然我個(gè)人對(duì)語言沒啥偏愛,譬如我就一直搞不懂為啥很多寫 C++ 的人不喜歡 Rust,但我個(gè)人對(duì) Java 卻實(shí)在提不起興趣,所以到了現(xiàn)在,看到 Java 代碼我就頭大,自然不會(huì)想著自己去寫 Java 相關(guān)的代碼。
  2. 現(xiàn)在 TiKV 只有 Go 的 API,雖然我們 TiSpark 帶了一個(gè) Java TiKV client,但不支持寫。為了能讓 YCSB 直接測(cè)試 TiKV,我現(xiàn)在必須使用 Go,但我又不知道如何 Java 調(diào)用 Go 的代碼,所以還不如用 Go 重寫 YCSB 來的簡(jiǎn)單,反正不復(fù)雜。

Benchmark Tiers

YCSB 主要測(cè)試兩層 - 性能和可擴(kuò)展性。

對(duì)于性能來說,主要關(guān)注的是 Latency,當(dāng)然,Latency 和 Throughput 是需要取舍的,在固定的硬件條件下,當(dāng)我們逐漸增加請(qǐng)求的時(shí)候,因?yàn)?disk,CPU,network 等競(jìng)爭(zhēng),請(qǐng)求的 latency 是在增加的。所以我們需要知道的是需要多少機(jī)器才能滿足用戶 lantency 和 throughput 的需求。當(dāng)然,需要機(jī)器越少,證明我們系統(tǒng)優(yōu)化的越好。這里,YCSB 采用的是非常常見的 Wisconsin Sizeup 方法,固定硬件,增加測(cè)試并發(fā)壓力,直到系統(tǒng)出現(xiàn)瓶頸過載。

而對(duì)于可擴(kuò)展性來說,一個(gè)是按比例增加,將硬件,數(shù)據(jù)量和負(fù)載等比增加,正常情況下面 latency 是保持恒定的。另一個(gè)就是彈性加速,我們測(cè)試 N 個(gè)服務(wù),然后在測(cè)試 N + 1 個(gè)服務(wù),正常情況下面 latency 是要降低的。

Workload

這里來說說 YCSB 的 Workload,YCSB 提供了一個(gè) Core workload,并且默認(rèn)提供了很多的 workloads。每個(gè) Workload 提供了一批混合讀寫的操作,數(shù)據(jù)量的大小,請(qǐng)求的分布等,所以用戶可以依據(jù)不同的 workload 多維度的對(duì)系統(tǒng)進(jìn)行測(cè)試。

操作主要包括:

  • Insert:插入一條新的記錄
  • Update:更新一條記錄的某一個(gè)或者所有 fields
  • Read:讀取一條記錄的某一個(gè)或者所有 fields
  • Scan:隨機(jī)從一個(gè) key 開始順序掃描隨機(jī)條記錄

Distribution

在測(cè)試的時(shí)候,我們還需要根據(jù)不同的業(yè)務(wù)場(chǎng)景來模擬測(cè)試,這個(gè)就是通過 Distribution 來完成的。YCSB 提供了默認(rèn)的幾種 distribution:

  • Uniform:隨機(jī)選擇一個(gè)記錄
  • Zipfian:根據(jù) Zipfian 分布來選擇記錄。一些記錄會(huì)比較熱,而大部分記錄會(huì)比較冷。
  • Latest:比較類似 Zipfian,但最近的新插入記錄是在整個(gè)分布的開頭。
  • Multinomial:根據(jù)概率指定,譬如,我們可以指定 0.95 的的 read 操作和 0.05 的 update 操作,然后 0 給 scan 和 insert,這樣就是一個(gè) read heavy workload。

Zipfian 和 Latest 的區(qū)別在于使用 Latest,新插入的記錄會(huì)變成最熱的記錄,而對(duì)于 Zipfian 來說,所有的記錄仍然保持原來的冷熱度。Latest 就比較適用于熱點(diǎn)新聞,而對(duì)于 Zipfian,可能就比較適用于明星,對(duì)于他們的 profile,即使是幾年前加入的,也會(huì)非常熱。

至于 Distribution 到底是怎么實(shí)現(xiàn)的,可以詳細(xì)參考 go-ycsb 的相關(guān) generator 實(shí)現(xiàn),后面如果有時(shí)間,也會(huì)詳細(xì)的分析。

如何使用

對(duì)于 YCSB 來說,是非常容易使用的,我們只需要選擇好自己的 workload,先使用 load 導(dǎo)入數(shù)據(jù),然后用 run 就能跑起來了。YCSB 提供常用的幾種 workload:

Workload Operations Record selection Application example
A — Update heavy Read: 50%, Update: 50% Zipfian 在用戶的 session 里面 存儲(chǔ)和訪問最近的操作
B — Read heavy Read: 95%, Update: 5% Zipfian 圖片標(biāo)記,打標(biāo)記是 update,但多數(shù)時(shí)候是 read
C — Read only Read: 100% Zipfian 用戶 profile cache
D — Read latest Read: 95%, Insert: 5% Latest 用戶最近的狀態(tài)更新
E — Short range Scan: 95%, Insert: 5% Zipfian / Uniform 不同主題帖子瀏覽

這里以 Workload A 為例,我們使用 MySQL,導(dǎo)入相關(guān)的數(shù)據(jù):

./bin/go-ycsb load mysql -P workload/workloada -p mysql.host=127.0.0.1 -p mysql.port=3306 -p mysql.db=test 

上面,我們使用 mysql 這個(gè) Database,指定了一個(gè) Workload A,然后傳入了 MySQL 相關(guān)的參數(shù),先用 load 導(dǎo)入了 1000 行數(shù)據(jù)。

然后我們開始執(zhí)行:

./bin/go-ycsb run mysql -P workload/workloada -p mysql.host=127.0.0.1 -p mysql.port=3306 -p mysql.db=test 

如何測(cè)試自己的 Database

如果要在 go-ycsb 測(cè)試自己的 Database,也非常容易。只要實(shí)現(xiàn) DB 和 DBCreator 的 interface 就可以了。首先,我們要實(shí)現(xiàn)自己的 Database,接口定義如下

type DB interface {
    Close() error
    InitThread(ctx context.Context, threadID int, threadCount int) context.Context
    CleanupThread(ctx context.Context)
    Read(ctx context.Context, table string, key string, fields []string) (map[string][]byte, error)
    Scan(ctx context.Context, table string, startKey string, count int, fields []string) ([]map[string][]byte, error)
    Update(ctx context.Context, table string, key string, values map[string][]byte) error
    Insert(ctx context.Context, table string, key string, values map[string][]byte) error
    Delete(ctx context.Context, table string, key string) error
}

對(duì)于 Read,Update, Insert,Scan,Delete 等函數(shù),非常直觀,這里不過多解釋。這里需要關(guān)注 InitThread 和 CleanupThread,對(duì)于 YCSB 來說,DB 是多線程安全的,我們會(huì)啟動(dòng)多個(gè) thread (Go 里面就是 goroutine)同時(shí)對(duì)該 DB 進(jìn)行操作,但有些時(shí)候,我們需要每個(gè) thread 上面都有該 DB 的 local thread 變量,所以在 thread 開始的時(shí)候,我們會(huì)調(diào)用 InitThread,而結(jié)束的時(shí)候會(huì) CleanupThread。

以 basic 為例,

type contextKey string
const stateKey = contextKey("basicDB")

type basicState struct {
    r *rand.Rand
    buf *bytes.Buffer
}

func (db *basicDB) InitThread(ctx context.Context, _ int, _ int) context.Context {
    state := new(basicState)
    state.r = rand.New(rand.NewSource(time.Now().UnixNano()))
    state.buf = new(bytes.Buffer)

    return context.WithValue(ctx, stateKey, state)
}
func (db *basicDB) Read(ctx context.Context, table string, key string, fields []string) (map[string][]byte, error) {
    state := ctx.Value(stateKey).(*basicState)
...
}

它對(duì)于每個(gè) thread,創(chuàng)建了一個(gè) basicState,并且掛在到 context 上面,這樣,后面我們就可以通過 context Value 得到這個(gè) basicState 了。為什么要做這個(gè)事情了,當(dāng)初設(shè)計(jì)的時(shí)候主要是為了避免全局使用 Go 的 rand 函數(shù),我們這邊已經(jīng)無數(shù)次碰到全局使用 rand 造成的性能問題了,畢竟里面有 Mutex,所以最好就是每個(gè) thread 單獨(dú)的 rand。

然后我們要實(shí)現(xiàn)一個(gè) DBCreator,接口定義如下:

type DBCreator interface {
    Create(p *properties.Properties) (DB, error)
}

這個(gè)接口很簡(jiǎn)單,就是根據(jù)當(dāng)前的配置,創(chuàng)建對(duì)應(yīng)的 DB,然后我們需要將自己的 Creator 注冊(cè)給 YCSB,并制定一個(gè)唯一的名字,譬如對(duì)于 basic 這個(gè) Database,我們?cè)?init 函數(shù)里面直接全局注冊(cè):

func init() {
    ycsb.RegisterDBCreator("basic", basicDBCreator{})
}

具體可以參考現(xiàn)有的一些例子,然后注冊(cè)給 YCSB,譬如 basic DB 就直接在 import 里面注冊(cè):

    _ "github.com/pingcap/go-ycsb/db/basic"

小結(jié)

現(xiàn)在,在我們項(xiàng)目的 issue 里面,已經(jīng)有看到有些用戶將 go-ycsb 用來測(cè)試我們的系統(tǒng),而后面,我們會(huì)用 go-ycsb 多維度的對(duì)整個(gè)系統(tǒng)進(jìn)行測(cè)試,作為我們后面優(yōu)化的一個(gè)性能基準(zhǔn)指標(biāo)。但是,現(xiàn)在 go-ycsb 還是有很多需要完善的,譬如在統(tǒng)計(jì)信息上面,現(xiàn)在就比較粗糙,而且還不能支持 export 到外面,讓外面生成圖表等。

如果你對(duì)性能測(cè)試工具感興趣,歡迎聯(lián)系我,一起來完善,我的郵箱 tl@pingcap.com。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,848評(píng)論 25 709
  • 一、源題QUESTION 74View the Exhibit. You want to create a tab...
    貓貓_tomluo閱讀 1,772評(píng)論 0 1
  • 總想寫點(diǎn)什么,但是總不知道從哪寫起。回想著,自己寫作的時(shí)候,還是高中時(shí)期。由于大學(xué)選的是工科專業(yè),就更加和寫作沒有...
    Repeat_TRG閱讀 313評(píng)論 0 4
  • 道德經(jīng)五三,使我介然有知,行于大道,惟施是畏。大道甚夷,而人好徑。朝甚除,田甚蕪,倉甚虛。服文采,帶利劍,厭飲食,...
    紅彥1閱讀 239評(píng)論 1 1
  • 說好一起走路去旅行 你怎么這么快就累了呢 算了 抱著你吧 反正 你就是你 不是狗狗
    xinanxiao閱讀 525評(píng)論 33 43

友情鏈接更多精彩內(nèi)容