本文主要介紹調(diào)查數(shù)據(jù)庫(kù)(調(diào)查數(shù)據(jù)庫(kù)建立項(xiàng)目),下面一起看看調(diào)查數(shù)據(jù)庫(kù)(調(diào)查數(shù)據(jù)庫(kù)建立項(xiàng)目)相關(guān)資訊。
在本文的開始,我們將介紹go-ycsb是如何使用的,然后我們將分析它是如何作為基準(zhǔn)測(cè)試使用的,以查看其優(yōu)缺點(diǎn)。
轉(zhuǎn)載請(qǐng)聲明出處~,本文發(fā)表于羅志云 的博客:,這樣才能橫向比較不同數(shù)據(jù)庫(kù)的性能。
ycsb,全稱是 雅虎!云服務(wù)基準(zhǔn)和測(cè)試是雅虎開發(fā)的一個(gè)工具,用于構(gòu)建云服務(wù)的測(cè)試,涵蓋常見(jiàn)的nosql數(shù)據(jù)庫(kù)產(chǎn)品,如cassandra、mongodb、hbase、redis等。
作為一名圍棋開發(fā)者,我們使用pingcap開發(fā)的go ycsb對(duì)測(cè)試進(jìn)行了基準(zhǔn)測(cè)試。
要安裝#,首先確保本地go版本不低于1.16,然后下載編譯:
復(fù)制git clon-ycsb.gitcd go-ycsbmake在b in文件夾中有我們編譯的程序go-ycsb。
讓 讓我們先看看工作負(fù)載文件夾。目錄中有各種工作負(fù)載模板,可以基于工作負(fù)載模板進(jìn)行定制和修改。默認(rèn)的六種測(cè)試場(chǎng)景如下:
workloada:讀寫平衡型,50%/50%,reads/writesworkloadb:多讀少寫,95%/5%,reads/writesworkloadc:只讀,100%,readsworkloadd:讀取最近寫入的記錄,95%/5%,reads/insertworkloade。95%/5%,scan/ins測(cè)試。工作量中的操作主要包括:
插入:插入新記錄更新:更新記錄的一個(gè)或所有字段讀取:讀取記錄的一個(gè)或所有字段。有字段掃描:從一個(gè)鍵中按順序隨機(jī)掃描隨機(jī)記錄。當(dāng)我們?cè)跍y(cè)試時(shí),我們需要根據(jù)不同的業(yè)務(wù)場(chǎng)景模擬測(cè)試,因此我們可以通過(guò)r測(cè)試階段操作的記錄數(shù)。如果設(shè)置了threadcount,那么每個(gè)線程操作的記錄數(shù)=操作數(shù)/threadcountoptioncount = 3000000 # thread count = 500 #如果一個(gè)表中已經(jīng)有記錄,那么在加載數(shù)據(jù)時(shí),從這個(gè)記錄號(hào)開始。insertstart=0 #一行數(shù)據(jù)中的字段數(shù)。fieldcount=10 #每個(gè)字段的大小。fieldlength=100 #是否應(yīng)該加載所有字段?真的嗎?是否應(yīng)該更新所有字段?writeallfields=false #字段長(zhǎng)度分布fieldlength distribution = constant # fieldlength distribution = uniform # fieldlength distribution = zipfian #讀取操作概率readproportion=0.95 #更新操作概率update proportion = 0 #插入操作概率insertproportion=0 #先讀取后寫入相同的記錄概率readmodifywriteproportion=0 #范圍操作概率scanproportion=0 #范圍操作,可操作記錄的最大數(shù)量maxscanlength=1000 #用于選擇掃描期間訪問(wèn)的記錄數(shù)量的分布。iform # scanlength distribution = zipfian #記錄應(yīng)該順序插入還是偽隨機(jī)插入order = hashed # insert order = ordered #如何模擬測(cè)試requestdistribution = zipfian # request distribution = uniform # request distribution = latest #以下兩種,當(dāng)request distribution為熱點(diǎn)時(shí)#, 構(gòu)成熱點(diǎn)集的數(shù)據(jù)項(xiàng)百分比hotspotdatafraction=0.2 #訪問(wèn)熱點(diǎn)集的數(shù)據(jù)操作百分比hotspotopnfraction=0.8 #操作數(shù)據(jù)表的表名=usertable #延遲了測(cè)量結(jié)果的展現(xiàn)形式,測(cè)量類型= histogram測(cè)試 #暫時(shí)沒(méi)有實(shí)現(xiàn)。 例如,如果我們現(xiàn)在想要測(cè)試 redis的性能,我們應(yīng)該首先編寫一個(gè)工作負(fù)載:
copyrecordcount = 1000000 operation count = 1000000 workload = core read all fields = true readmodifywriteproportion = 1 request distribution = uniform redis . addr = 127.0。0.1 : 6379 thread count = 50上面的工作負(fù)載表示加載時(shí),將有100萬(wàn)條數(shù)據(jù)插入到庫(kù)中,要操作的數(shù)據(jù)量也是100萬(wàn),但有50個(gè)線程,即每個(gè)線程實(shí)際操作2萬(wàn)行記錄;
測(cè)試模式采用readmodifywriteproportion,先讀后寫,操作記錄采用uniform,即隨機(jī)模式。
首先加載數(shù)據(jù):
收到。/bin/go-ycsb load r測(cè)試:
收到。/bin/go-ycsb runr: 18.8,count: 499312、ops: 26539.8、avg(us): 1388、min(us): 107、max(us): 42760、99th(us): 3000、99.9th(us): 7000、99.99th(us): 26000 tak測(cè)試總耗時(shí);計(jì)數(shù):操作記錄的數(shù)量;ops:每秒運(yùn)算次數(shù),一般是運(yùn)算次數(shù),和qps差別不大;avg、min、max:平均、最小、最大單次記錄操作耗時(shí);第99、99.9、99.99:p99、p99.9、p99.99延時(shí);代碼實(shí)現(xiàn)分析#當(dāng)然,對(duì)我來(lái)說(shuō),肯定是要看它的代碼是怎么做的,了解一下老板是怎么寫代碼的也很有幫助。
對(duì)于go ycsb,它總共有幾個(gè)組件:
工作負(fù)載:加載初始化配置文件,創(chuàng)建一個(gè)線程執(zhí)行測(cè)試;客戶端:封裝工作負(fù)載、配置參數(shù)、數(shù)據(jù)庫(kù)等。,并用于運(yùn)行測(cè)試;db:配置了一堆可執(zhí)行的數(shù)據(jù)庫(kù)客戶端來(lái)讀寫特定的數(shù)據(jù)庫(kù);測(cè)量:數(shù)據(jù)統(tǒng)計(jì)模塊,統(tǒng)計(jì)執(zhí)行次數(shù),時(shí)間延遲等。讓 讓我們以redis為例,看看如果我們想要測(cè)試 ;自己的數(shù)據(jù)庫(kù)。
在go ycsb中定義db#,所有的db都放在db:
所以,我們可以在這個(gè)文件夾下創(chuàng)建自己的db,然后構(gòu)造一個(gè)struct來(lái)實(shí)現(xiàn)db的接口:
copytype db接口{ tosqldb *sql。db close錯(cuò)誤initthread(ctx上下文。context,threadid int,threadcount int)上下文。上下文cleanupthread(ctx上下文。上下文)讀取(ctx上下文。context,table string,key string,fields[]string)(map[string][]字節(jié),error)掃描(ctx上下文。context,table string,startkey string,count int,fields[]string)([]map[string][]byte,error) update(ctx context。上下文,表字符串,鍵字符串,值映射[字符串][]字節(jié))錯(cuò)誤插入(ctx上下文。上下文、表字符串、鍵字符串、值映射[字符串] []字節(jié))錯(cuò)誤刪除(ctxcontext。context,tablestring,keystring) error}定義特定的db操作。
然后,您需要定義一個(gè)工廠來(lái)創(chuàng)建這個(gè)db結(jié)構(gòu)并實(shí)現(xiàn)dbcreator接口:
復(fù)制類型dbcreator接口{create (p * properties。properties) (db,error)}然后您需要定義一個(gè)init函數(shù)在啟動(dòng)時(shí)注冊(cè)dbcreator:
copyfunc init {ycsb。registerdbcreator( redis ,rediscreator { })} var db creators = map[string]dbcreator { } func registerdbcreator(name string,creator dbcreator) {_,ok : = db creators[name]if ok { panic(fmt . sprintf( 重復(fù)的注冊(cè)數(shù)據(jù)庫(kù)% s ,name))} dbcreators[name]= creator } register dbcreator在初始化時(shí)會(huì)被調(diào)用。用于獲取由init方法注冊(cè)的數(shù)據(jù)庫(kù)。這樣go ycsb實(shí)現(xiàn)了db的定制化。
全局參數(shù)初始化#首先,go ycsb會(huì)根據(jù)傳入的是load還是run,使用cobra執(zhí)行以下兩種不同的方法:
copy func runloadcommandfunc(cmd * cobra.command,args[]string){ runclientcommandfunc(cmd,args,false)} func runtranscommandfunc(cmd * cobra . command,args[]string){ runclientcommandfunc(cmd,args,true)}這將被調(diào)用到runclientcommandfunc函數(shù)中。
copy func runclientcommandfunc(cmd * cobra . command,args [] string,do transactions bool){ dbname : = args[0]//初始化全局參數(shù)initialglobal (dbname,func{ dotranslag : = 。do transactions { do transflag = 虛假 }globalprops。設(shè)置(道具。dotransactions,dotransflag)如果cmd。標(biāo)志。已更改( 線程和){//我們通過(guò)命令行設(shè)置threadarg . global props . set(prop。threadcount,strconv。itoa(threadsarg))}if cmd。標(biāo)志。已更改( 目標(biāo) ){globalprops。設(shè)置(道具。目標(biāo),strconv。itoa(target arg))}如果cmd。標(biāo)志。已更改( 音程 ){globalprops。設(shè)置(道具。loginterval,strconv。itoa(reportinterval))}})fmt。println( ****************屬性* * * * * * * * * * * * )對(duì)于key,值: = range global props。map {fmt。printf( \ % s \ = \ % s \ \ n ,key,value)}fmt。println( ********************** * * * * * * * * * * * * * * * * * * )//初始化client : = client . new client(全局道具,全局工作量,globaldb)啟動(dòng): = time . now//運(yùn)行測(cè)試.run(global cont測(cè)試結(jié)果輸出測(cè)量。output}參數(shù)主要在initialglobal中完成:
copy func initial global(dbname string,onproperties func) {...go func {http。listenandserve(addr,nil)}//初始化測(cè)量measurement。init measure(global props)iflen(tablename)= = 0 { tablename = global props . getstring(prop . tablename,prop。tablename default)}//get workloadcreatorworkloadname : = global props . getstring(prop . workload, 核心 )工作負(fù)荷創(chuàng)建者: = ycsb . get workload creator(工作負(fù)荷名稱)//創(chuàng)建工作負(fù)荷var errorif全局工作負(fù)荷,err = workload creator.create(全局道具);呃!= nil {util。fatalf( 創(chuàng)建工作負(fù)荷% s失敗,工作負(fù)荷名稱,err)}/get dbdbcreator : = ycsb . get dbcreator(dbname)if dbcreator = = nil { util . fatalf( %s沒(méi)有注冊(cè),dbname)}/create dbif globaldb,err = dbcreator . create(global props);呃!= nil { util . fatalf( createdb% s失敗,dbname,err)} global db = client . db wrapper { global db } }這里最重要的是創(chuàng)建工作負(fù)載和db。工作負(fù)載將初始化配置文件中的大量信息。
運(yùn)行測(cè)試 # runclientcommandfunc將調(diào)用客戶端 執(zhí)行測(cè)試的運(yùn)行方法:
copyfunc (c *client)運(yùn)行(ctx上下文。上下文){var wg sync。waitgroupthreadcount : = c . p . getint(prop。線程計(jì)數(shù),1)wg。add(threadcount)measurectx,measure cancel : = context。with cancel(ctx)measure ch : = make(chan struct { },1)go func{ defer func{ measure ch-struct { } { }/這個(gè)很有意思,因?yàn)橛袝r(shí)候我們?cè)谧鰯?shù)據(jù)庫(kù)的時(shí)候需要初始化緩存中的數(shù)據(jù)//這樣我們就可以 測(cè)試統(tǒng)計(jì)中不算初期,這里有熱身時(shí)間??梢耘渲胏 . p . get bool(prop . do transactions,true){ dure cho 45-@ .com = c . p . getint 64(prop . warm uptime,0)選擇{case-ctx。done: return case-time . after(time . dur)* time . second): } }//測(cè)量。:= time。newticker(時(shí)間。持續(xù)時(shí)間*時(shí)間。第二)defer t.stopfor {select {//輸出統(tǒng)計(jì)信息case-t . c: measurement . outputcase-measure ctx . done: return } }//做一些初始化工作,比如mysql需要?jiǎng)?chuàng)建一個(gè)表if err: = c . err!= nil { fmt . printf( 初始化工作負(fù)載失敗,err)return }//根據(jù)threadcount為i := 0創(chuàng)建多個(gè)線程操作數(shù)據(jù)庫(kù);我threadcounti { go func(threadid int){ defer wg . done//initialize worker : = new worker(c . p,threadid,threadcount,c.workload,c.db)ctx : = c . workload . init thread(ctx,threadid,thread count)ctx = c . db . init thread(ctx,threadid,threadcount)//開始運(yùn)行測(cè)試w.run(ctx)//運(yùn)行測(cè)試后,做清理工作c . dbwaitmeasure cancel-measure ch }這個(gè)分為兩部分:第一部分是創(chuàng)建一個(gè)線程,這個(gè)線程會(huì)控制是否啟動(dòng)測(cè)試統(tǒng)計(jì),然后每10秒輸出一次統(tǒng)計(jì)信息;第二部分是根據(jù)設(shè)置的threadcount創(chuàng)建一個(gè)線程,并運(yùn)行work測(cè)試。
新工人根據(jù)operationcount設(shè)置totalopcount,表示要執(zhí)行的總次數(shù),設(shè)置totalopcount/int64(threadcount)表示單線程操作的記錄數(shù)。
復(fù)制func (w * worker)運(yùn)行(ctxcontext。context){//分發(fā)線程操作,這樣它們就不會(huì) 不要同時(shí)打db。如果w . targetopsperms 0.0 w . targetopsperms = 1.0 { time。睡眠(時(shí)間。持續(xù)時(shí)間(蘭特。int 63n(w . targetopstickns))} start time : = time。now//循環(huán)直到操作數(shù)到達(dá)opsdonefor w . op count = = 0 | | w . opsdonew . op count { var err err count : = 1//這里是執(zhí)行基準(zhǔn)測(cè)試 if w . do transactions { if w . do batch { err = w . workload . dobatch transaction(ctx,w.batchsize,w . work db)ops count = w . batch size } else { err = w . workload . do transaction(ctx,w.workdb)}//這里是iswarupfinished{ w . ops done = int 64(ops count)w . throttle(ctx,開始時(shí)間)} select {case-ctx。done: return default : } }基準(zhǔn)測(cè)試的具體實(shí)現(xiàn)就交給工作了。負(fù)載與負(fù)載 dotransaction方法來(lái)確定執(zhí)行。
copy func(c * core)do transaction(ctx上下文。上下文,dbycsb。db)錯(cuò)誤{ state : = ctx。值(statekey)。(* corestate)re: = stat測(cè)試場(chǎng)景,進(jìn)入不同的測(cè)試分支// next方根據(jù)設(shè)置的readproportion、updateproportion、scanproportion等概率得到相應(yīng)的操作類型-@ .com = operation type(c . operation chooser . next(r))。開關(guān)操作{ case read: return c . dotransactionread(ctx,db,state)case update : return c . dotransactionupdate(ctx,db,state)case insert : return c . dotransactioninsert(ctx,db,state)case scan: return c . dotransactionscan(ctx,db,state)default : return c . dotransactionread modify(ctx
這個(gè)算法很簡(jiǎn)單。在初始化operationchooser時(shí),將readproportion、updateproportion、scanproportion等設(shè)置參數(shù)的值以數(shù)組的形式加到operationchooser的值上,然后隨機(jī)選取一個(gè)0~1的小數(shù),檢查這個(gè)隨機(jī)數(shù)落在哪個(gè)范圍內(nèi)。
copy func(d * discrete)next(r * rand。跑d)int 64 { sum : = float 64(0)for _,p : = range d . values { sum = p . weight }//random a decimal val : = r . float 64(。p : = range d . values { pw : = p . weight/sumif val pw { d . setlastvalue(p . value)return p . value } val-= pw } panic( 哎呀,不應(yīng)該到這里。 )}在代碼實(shí)現(xiàn)方面,通過(guò)將所有值相加得到sum,然后計(jì)算每個(gè)值的比例是否達(dá)到隨機(jī)值。
最后,讓 讓我們看看dotransactionread是如何實(shí)現(xiàn)的:
copy func(c * core)dotransactionread(ctx上下文。上下文,db ycsb。db,state * corestate)error { r : = state . r//獲取一個(gè)鍵值keynum : = c . next keynum(state)根據(jù)我們?cè)O(shè)置的requestdistribution,keyname: = c . build keyname(keynum)//讀取字段varfields [] stringiff!c.readallfields {//如果沒(méi)有讀取所有字段,那么根據(jù)fieldchooser選擇一個(gè)字段執(zhí)行字段名: = state . field names[c . field chooser . next(r)]fields = append(fields,fieldname)} else {fields = state。field names }//調(diào)用db的read方法值,err : = db . read(ctx,c.table,keyname,fields) if err!= nil {return err}//檢查數(shù)據(jù)完整性if c . data integrity { c . verify row(state,keyname,values)} return nil}這里會(huì)調(diào)用nextkeynum先獲取鍵值,這里的key會(huì)根據(jù)我們?cè)O(shè)置的requestdistribution參數(shù)按照一定的規(guī)則獲取。然后調(diào)用dbwrapper的read方法,在檢查需要讀取哪些字段后讀取數(shù)據(jù)。
copyfunc (db dbwrapper) read(ctx上下文。context,table string,key string,fields[]string)(_ map[string][]byte,error){ start: = time . nowdefer func{//進(jìn)行測(cè)試統(tǒng)計(jì)量(start, 閱讀 ,err)} return db.db.read (ctx,表,鍵,字段)} db wrapper會(huì)封裝一個(gè)層,使用defer wrapper。
但我在這里的問(wèn)題是,在讀取數(shù)據(jù)時(shí),會(huì)根據(jù)傳入的字段進(jìn)行解析,這也會(huì)損失一些性能。不知道是否合理,比如redis的read方法:
copyfunc (r *redis) read(ctx上下文。context,table string,key string,fields[]string)(map[string][]byte,error){ data : = make(map[string][]byte,len(fields))res,err : = r . cli:按字段過(guò)濾返回?cái)?shù)據(jù),err} statistics #它將在每次操作后進(jìn)行調(diào)整。采用測(cè)量法對(duì)測(cè)試數(shù)據(jù)進(jìn)行統(tǒng)計(jì)。
復(fù)制funcmeasure (starttime。time,opstring,err error){//需要時(shí)間計(jì)算lan: = time . now。如果出錯(cuò),則執(zhí)行sub (start )!=零{測(cè)量值。測(cè)量(fmt。sprintf( %s錯(cuò)誤 ,op),lan) return}測(cè)量。measure (op,lan)} statistics由于將有多個(gè)線程同時(shí)運(yùn)行,因此有必要以線程安全的運(yùn)行:
復(fù)制函數(shù)(h *直方圖)測(cè)量(等待時(shí)間。持續(xù)時(shí)間){//這是us微秒n : = int 64(latency/time .微秒)原子。add 64(h . sum,n)原子。addint64(h.count,1)//這里轉(zhuǎn)換成ms bound : = int(n/h . bound interval)//bound counts是并發(fā)映射。用于統(tǒng)計(jì)每個(gè)時(shí)間段內(nèi)的操作次數(shù)(單位:ms)。h .受約束的縣。upsert (bound,1,func (ok bool,existed value int 64,new value int 64)int 64 { if ok { return existing value new value } return new value })//設(shè)置{ old min : = atomic . load int 64(h . min)的最小延遲。if n = oldmin {break}if atomic。compareandswapint64(h.min,oldmin,n) {break}}//設(shè)置{ oldmax: = atomic的最大延遲。load int 64(h . max)if n = old max { break } if atomic。compareandswapint64 (h.max,oldmax,n) {break}}每個(gè)時(shí)間段的統(tǒng)計(jì)數(shù)據(jù)(h.max,n)}}undcounts是go ycsb自己實(shí)現(xiàn)的保證線程安全的concurrentmap,用于統(tǒng)計(jì)單位時(shí)間內(nèi)的運(yùn)算次數(shù);
最大和最小延遲是通過(guò)cas操作的,也是為了確保線程安全。
統(tǒng)計(jì)完成后將調(diào)用getinfo計(jì)算耗時(shí):
copy func(h *直方圖)getinfomap[string]interface { } { min : = atomic。load int 64(h . min)max : = atomic。load int 64(h . max)sum : = atomic。load int 64(h . sum)count : = atomic。load int 64(h . count)bounds : = h . boundcounts . keyssort。ints(bounds)avg : = int 64(float 64(sum)/float 64(count))per 99 : = 0 per 999 : = 0 per 999 : = 0 pcount: = int 64(0)//計(jì)算p99,p99.9,p99.99//這其實(shí)是一個(gè)比例的統(tǒng)計(jì)。// bound將為_,bound cho 45-@ .com = range bounds { boundcount,_ : = h . boundcounts . get(bound)op count = boundcountper : = float 64(op count)/float 64(count)//下面是per99 = = 0 per = 0.99 { per99 =(bound 1)* 1000時(shí)99%的操作落在哪個(gè)時(shí)間間隔內(nèi)}//計(jì)算經(jīng)過(guò)的: = time . now。sub (h .開始時(shí)間)。seconds//計(jì)算單位時(shí)間內(nèi)的運(yùn)算次數(shù)qps : = float 64(count)/elapsedres : = make(map[string]interface { })res[elapsed]= elapsedres[count]= count res[qps]= qpsres[avg]= avgres[min]= minres[max]= maxres[per 99 th]= per 99 res[per 999 th]= per 999 res[per 999 th]= per 999 returnres }
總結(jié)#通過(guò)以上分析可以發(fā)現(xiàn),go ycsb設(shè)計(jì)還是很精妙的,用很少的代碼就可以擴(kuò)展db;配置也相當(dāng)靈活,可以根據(jù)不同的r測(cè)試環(huán)境,在測(cè)試可以隨意調(diào)整讀寫概率,保證盡可能模擬在線環(huán)境。
然而,它也有許多缺點(diǎn)。一方面文檔很不充分,基本只寫了幾個(gè)參數(shù)配置;另一方面,許多功能還沒(méi)有實(shí)現(xiàn),在線測(cè)試經(jīng)常出現(xiàn)錯(cuò)誤。你看代碼,結(jié)果沒(méi)有實(shí)現(xiàn)。三年前,作者在他的博客中說(shuō),應(yīng)該實(shí)現(xiàn)測(cè)試結(jié)果導(dǎo)出功能,但結(jié)果尚未實(shí)現(xiàn)。我已經(jīng)給作者tl@pingcap.com發(fā)了一封電子郵件,等待回復(fù)。
referenc-ycsb
github . com/brianfrankcooper/ycsb/wiki/running-a-workload
標(biāo)簽:
測(cè)試行動(dòng)
了解更多調(diào)查數(shù)據(jù)庫(kù)(調(diào)查數(shù)據(jù)庫(kù)建立項(xiàng)目)相關(guān)內(nèi)容請(qǐng)關(guān)注本站點(diǎn)。