如何設(shè)計(jì)并實(shí)現(xiàn)存儲(chǔ)QoS,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。
成都創(chuàng)新互聯(lián)專(zhuān)注于企業(yè)成都營(yíng)銷(xiāo)網(wǎng)站建設(shè)、網(wǎng)站重做改版、河南網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5網(wǎng)站設(shè)計(jì)、商城網(wǎng)站制作、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性?xún)r(jià)比高,為河南等各大城市提供網(wǎng)站開(kāi)發(fā)制作服務(wù)。
隨著存儲(chǔ)架構(gòu)的調(diào)整,眾多應(yīng)用服務(wù)會(huì)運(yùn)行在同一資源池中,對(duì)外提供統(tǒng)一的存儲(chǔ)能力。資源池內(nèi)部可能存在多種流量類(lèi)型,如上層業(yè)務(wù)的IO流量、存儲(chǔ)內(nèi)部的數(shù)據(jù)遷移、修復(fù)、壓縮等,不同的流量通過(guò)競(jìng)爭(zhēng)的方式確定下發(fā)到硬件的IO順序,因此無(wú)法確保某種流量IO服務(wù)質(zhì)量,比如內(nèi)部數(shù)據(jù)遷移流量可能占用過(guò)多的帶寬影響業(yè)務(wù)流量讀寫(xiě),導(dǎo)致存儲(chǔ)對(duì)外提供的服務(wù)質(zhì)量下降,由于資源競(jìng)爭(zhēng)結(jié)果的不確定性無(wú)法保障存儲(chǔ)對(duì)外能提供穩(wěn)定的集群環(huán)境。
如下面交通圖所示,車(chē)輛逆行、加塞隨心隨遇,行人橫穿、閑聊肆無(wú)忌憚,最終出現(xiàn)交通擁堵甚至安全事故。
類(lèi)比上一幅交通圖,如何規(guī)避這樣的現(xiàn)象大家可能都有自己的一些看法,這里先引入兩個(gè)名詞
QoS,即服務(wù)質(zhì)量,根據(jù)不同服務(wù)類(lèi)型的不同需求提供端到端的服務(wù)質(zhì)量。
存儲(chǔ)QoS,在保障服務(wù)帶寬與IOPS的情況下,合理分配存儲(chǔ)資源,有效緩解或控制應(yīng)用服務(wù)對(duì)資源的搶占,實(shí)現(xiàn)流量監(jiān)控、資源合理分配、重要服務(wù)質(zhì)量保證以及內(nèi)部流量規(guī)避等效果,是存儲(chǔ)領(lǐng)域必不可少的一項(xiàng)關(guān)鍵技術(shù)。
那么QoS應(yīng)該怎么去做呢?下面還是結(jié)合交通的例子進(jìn)行介紹說(shuō)明。
從前面的圖我們看到不管是什么車(chē),都以自我為中心,不受任何約束,我們首先能先到的辦法是對(duì)道路進(jìn)行分類(lèi)劃分,比如分為公交車(chē)專(zhuān)用車(chē)道、小型車(chē)專(zhuān)用車(chē)道、大貨車(chē)專(zhuān)用車(chē)道、非機(jī)動(dòng)車(chē)道以及人行橫道等,正常情況下公交車(chē)車(chē)道只允許公交車(chē)運(yùn)行,而非機(jī)動(dòng)車(chē)道上是不允許出現(xiàn)機(jī)動(dòng)車(chē)的,這樣我們可以保證車(chē)道與車(chē)道之間不受制約干擾。
同樣,存儲(chǔ)內(nèi)部也會(huì)有很多流量,我們可以為不同的流量類(lèi)型分配不同的 “車(chē)道”,比如業(yè)務(wù)流量的車(chē)道我們劃分寬一些,而內(nèi)部壓縮流量的車(chē)道相對(duì)來(lái)說(shuō)可以窄一些,由此引入了QoS中一個(gè)比較重要的概覽就是流量分類(lèi),根據(jù)分類(lèi)結(jié)果可以進(jìn)行更加精準(zhǔn)個(gè)性化的限流控制。
僅僅依靠分類(lèi)是不行的,因?yàn)榭傆幸恍┨厥馇闆r,比如急救車(chē)救人、警車(chē)抓人等,我們總不能說(shuō)這個(gè)車(chē)道只能跑普通私家小轎車(chē)把,一些特殊車(chē)輛(救護(hù)車(chē),消防車(chē)以及警車(chē)等)應(yīng)該具有優(yōu)先通行的權(quán)限。
對(duì)于存儲(chǔ)來(lái)說(shuō)業(yè)務(wù)流量就是我們的特殊車(chē)輛,我們需要保證業(yè)務(wù)流量的穩(wěn)定性,比如業(yè)務(wù)流量的帶寬跟IOPS不受限制,而內(nèi)部流量如遷移、修復(fù)則需要限定其帶寬或者IOPS,為其分配固定的“車(chē)道”。在資源充足的情況下,內(nèi)部流量可以安安靜靜的在自己的車(chē)道上行駛,但是當(dāng)資源緊張,比如業(yè)務(wù)流量突增或者持續(xù)性的高流量水位,這個(gè)時(shí)候需要限制內(nèi)部流量的道理寬度,極端情況下可以暫停。當(dāng)然,如果內(nèi)部流量都停了還是不能滿足正常業(yè)務(wù)流量的讀寫(xiě)需求,這個(gè)時(shí)候就需要考慮擴(kuò)容的事情了。
QoS中另外一個(gè)比較重要的概念就是優(yōu)先級(jí)劃分,在資源充足的情況下執(zhí)行預(yù)分配資源策略,當(dāng)資源緊張時(shí)對(duì)優(yōu)先級(jí)低的服務(wù)資源進(jìn)行動(dòng)態(tài)調(diào)整,進(jìn)行適當(dāng)?shù)囊?guī)避或者暫停,在一定程度上可以彌補(bǔ)預(yù)分配方案的不足。
前面提到當(dāng)資源不足時(shí),我們可以動(dòng)態(tài)的去調(diào)整其他流量的閾值,那我們?nèi)绾沃蕾Y源不足呢?這個(gè)時(shí)候我們是需要有個(gè)流量監(jiān)控的組件。
我們出行時(shí)經(jīng)常會(huì)使用地圖,通過(guò)選擇合適的線路以最快到達(dá)目的地。一般線路會(huì)通過(guò)不同的顏色標(biāo)記線路擁堵情況,比如紅色表示堵車(chē)、綠色表示暢通。
存儲(chǔ)想要知道機(jī)器或者磁盤(pán)當(dāng)前的流量情況有兩種方式:
統(tǒng)計(jì)機(jī)器負(fù)載情況,比如我們經(jīng)常去機(jī)器上通過(guò)iostat命名查看各個(gè)磁盤(pán)的io情況,這種方式與機(jī)器上的應(yīng)用解耦,只關(guān)注機(jī)器本身
統(tǒng)計(jì)各個(gè)應(yīng)用下發(fā)的讀寫(xiě)流量,比如某臺(tái)機(jī)器上部署了一個(gè)存儲(chǔ)節(jié)點(diǎn)應(yīng)用,那我們可以統(tǒng)計(jì)這個(gè)應(yīng)用下發(fā)下去的讀寫(xiě)帶寬及IOPS
第二種方式相對(duì)第一種可以實(shí)現(xiàn)應(yīng)用內(nèi)部更細(xì)的流量分類(lèi),比如前面提到的一個(gè)存儲(chǔ)應(yīng)用節(jié)點(diǎn),就包含了多種流量,我們不能通過(guò)機(jī)器的粒度對(duì)所有流量統(tǒng)一限流。
按時(shí)間劃分為多個(gè)限流窗口,比如1秒為一個(gè)限流窗口大?。?/p>
每個(gè)窗口都有一個(gè)計(jì)數(shù)器,每通過(guò)一個(gè)請(qǐng)求計(jì)數(shù)器會(huì)加一;
當(dāng)計(jì)數(shù)器大小超過(guò)了限制大?。ū热缫幻雰?nèi)只能通過(guò)100個(gè)請(qǐng)求),則窗口內(nèi)的其他請(qǐng)求會(huì)被丟棄或排隊(duì)等待,等到下一個(gè)時(shí)間節(jié)點(diǎn)計(jì)數(shù)器清零再處理請(qǐng)求。
固定窗口算法的理想流量控制效果如上左側(cè)圖所示,假定設(shè)置1秒內(nèi)允許的最大請(qǐng)求數(shù)為100,那么1秒內(nèi)的最大請(qǐng)求數(shù)不會(huì)超過(guò)100。
但是大多數(shù)情況下我們會(huì)得到右側(cè)的曲線圖,即可能會(huì)出現(xiàn)流量翻倍的效果。比如前T1~T2時(shí)間段沒(méi)有請(qǐng)求,T2~T3來(lái)了100個(gè)請(qǐng)求,全部通過(guò)。下一個(gè)限流窗口計(jì)數(shù)器清零,然后T3T4時(shí)間內(nèi)來(lái)了100個(gè)請(qǐng)求,全部處理成功,這個(gè)時(shí)候時(shí)間段T4T5時(shí)間段就算有請(qǐng)求也是不能處理的,因此超過(guò)了設(shè)定閾值,最終T2~T4這一秒時(shí)間處理的請(qǐng)求為200個(gè),所以流量翻倍。
小結(jié)
算法易于理解,實(shí)現(xiàn)簡(jiǎn)單;
流量控制不夠精細(xì),容易出現(xiàn)流量翻倍情況;
適合流量平緩并允許流量翻倍的模型。
前面提到固定窗口算法容易出現(xiàn)流量控制不住的情況(流量翻倍),滑動(dòng)窗口可以認(rèn)為是固定窗口的升級(jí)版本,可以規(guī)避固定窗口導(dǎo)致的流量翻倍問(wèn)題。
時(shí)間窗口被細(xì)分若干個(gè)小區(qū)間,比如之前一秒一個(gè)窗口(最大允許通過(guò)60個(gè)請(qǐng)求),現(xiàn)在一秒分成3個(gè)小區(qū)間,每個(gè)小區(qū)間最大允許通過(guò)20個(gè)請(qǐng)求;
每個(gè)區(qū)間都有一個(gè)獨(dú)立的計(jì)數(shù)器,可以理解一個(gè)區(qū)間就是固定窗口算法中的一個(gè)限流窗口;
當(dāng)一個(gè)區(qū)間的時(shí)間用完,滑動(dòng)窗口往后移動(dòng)一個(gè)分區(qū),老的分區(qū)(T1~T2)被丟棄,新的分區(qū)(T4~T5)加入滑動(dòng)窗口,如圖所示。
小結(jié)
流量控制更加精準(zhǔn),解決了固定窗口算法導(dǎo)致的流量翻倍問(wèn)題;
區(qū)間劃分粒度不易確定,粒度太小會(huì)增加計(jì)算資源,粒度太大又會(huì)導(dǎo)致整體流量曲線不夠平滑,使得系統(tǒng)負(fù)載忽高忽低;
適合流量較為穩(wěn)定,沒(méi)有大量流量突增模型。
所有的水滴(請(qǐng)求)都會(huì)先經(jīng)過(guò)“漏斗”存儲(chǔ)起來(lái)(排隊(duì)等待);
當(dāng)漏斗滿了之后,多余的水會(huì)被丟棄或者進(jìn)入一個(gè)等待隊(duì)列中;
漏斗的另外一端會(huì)以一個(gè)固定的速率將水滴排出。
對(duì)于漏斗而言,他不清楚水滴(請(qǐng)求)什么時(shí)候會(huì)流入,但是總能保證出水的速度不會(huì)超過(guò)設(shè)定的閾值,請(qǐng)求總是以一個(gè)比較平滑的速度被處理,如圖所示,系統(tǒng)經(jīng)過(guò)漏斗算法限流之后,流量能保證在一個(gè)恒定的閾值之下。
小結(jié)
穩(wěn)定的處理速度,可以達(dá)到整流的效果,主要對(duì)下游的系統(tǒng)起到保護(hù)作用;
無(wú)法應(yīng)對(duì)流量突增情況,所有的請(qǐng)求經(jīng)過(guò)漏斗都會(huì)被削緩,因此不適合有流量突發(fā)的限流場(chǎng)景;
適合沒(méi)有流量突增或想達(dá)到流量整合以固定速率處理的模型。
令牌桶算法是漏斗算法的一種改進(jìn),主要解決漏斗算法不能應(yīng)對(duì)流量突發(fā)的場(chǎng)景
以固定的速率產(chǎn)生令牌并投入桶中,比如一秒投放N個(gè)令牌;
令牌桶中的令牌數(shù)如果大于令牌桶大小M,則多余的令牌會(huì)被丟棄;
所有請(qǐng)求到達(dá)時(shí),會(huì)先從令牌桶中獲取令牌,拿到令牌則執(zhí)行請(qǐng)求,如果沒(méi)有獲取到令牌則請(qǐng)求會(huì)被丟棄或者排隊(duì)等待下一次嘗試獲取令牌。
如圖所示,假設(shè)令牌投放速率為100/s,桶能存放最大令牌數(shù)200,當(dāng)請(qǐng)求速度大于另外投放速率時(shí),請(qǐng)求會(huì)被限制在100/s。如果某段時(shí)間沒(méi)有請(qǐng)求,這個(gè)時(shí)候令牌桶中的令牌數(shù)會(huì)慢慢增加直到200個(gè),這是請(qǐng)求可以一次執(zhí)行200,即允許設(shè)定閾值內(nèi)的流量并發(fā)。
小結(jié)
流量平滑;
允許特定閾值內(nèi)的流量并發(fā);
適合整流并允許一定程度流量突增的模型。
就單純的以算法而言,沒(méi)有哪個(gè)算法最好或者最差的說(shuō)法,需要結(jié)合實(shí)際的流量特征以及系統(tǒng)需求等因素選擇最合適的算法。
一般而言一臺(tái)機(jī)器會(huì)至少部署一個(gè)存儲(chǔ)節(jié)點(diǎn),節(jié)點(diǎn)負(fù)責(zé)多塊磁盤(pán)的讀寫(xiě)請(qǐng)求,而存儲(chǔ)請(qǐng)求由分為多種類(lèi)型,比如正常業(yè)務(wù)的讀寫(xiě)流量、磁盤(pán)損壞的修復(fù)流量、數(shù)據(jù)刪除出現(xiàn)數(shù)據(jù)空洞后的空間壓縮流量以及多為了降低多副本存儲(chǔ)成本的糾刪碼(EC)遷移流量等等,不同流量出現(xiàn)在同一個(gè)存儲(chǔ)節(jié)點(diǎn)會(huì)相互競(jìng)爭(zhēng)搶占系統(tǒng)資源,為了更好的保證業(yè)務(wù)服務(wù)質(zhì)量,需要對(duì)流量的帶寬以及IOPS進(jìn)行限制管控,比如需要滿足以下條件:
可以同時(shí)限制流量的帶寬跟IOPS,單獨(dú)的帶寬或者IOPS限制都會(huì)導(dǎo)致另外一個(gè)參數(shù)不受控制而影響系統(tǒng)穩(wěn)定性,比如只控制了帶寬,但是沒(méi)有限制IOPS,對(duì)于大量小IO的場(chǎng)景就會(huì)導(dǎo)致機(jī)器的ioutil過(guò)高;
可以實(shí)現(xiàn)磁盤(pán)粒度的限流,避免機(jī)器粒度限流導(dǎo)致磁盤(pán)流量過(guò)載,比如圖所示,ec流量限制節(jié)點(diǎn)的帶寬最大值為10Mbps,預(yù)期效果是想每塊磁盤(pán)分配2Mbps,但是很有可能這10Mbps全部分配到了第一個(gè)磁盤(pán);
可以支持流量分類(lèi)控制,根據(jù)不同的流量特性設(shè)置不同的限流參數(shù),比如業(yè)務(wù)流量是我們需要重點(diǎn)保護(hù)的,因此不能對(duì)業(yè)務(wù)流量進(jìn)行限流,而EC、壓縮等其他流量均為內(nèi)部流量,可以根據(jù)其特性配置合適的限流閾值;
可以支持限流閾值的動(dòng)態(tài)適配,由于業(yè)務(wù)流量不能進(jìn)行流控,對(duì)于系統(tǒng)而言就像一匹“脫韁野馬”,可能突增、突減或持續(xù)高峰,針對(duì)突增或持續(xù)高峰的場(chǎng)景系統(tǒng)需要盡可能的為其分配資源,這就意味著需要對(duì)內(nèi)部流量的限流閾值進(jìn)行動(dòng)態(tài)的打壓設(shè)置是暫停規(guī)避。
前面提到了QoS的算法有很多,這里我們結(jié)合實(shí)際需求選擇滑動(dòng)窗口算法,主要有以下原因:
系統(tǒng)需要控制內(nèi)部流量而內(nèi)部流量相對(duì)比較穩(wěn)定平緩;
可以避免流量突發(fā)情況而影響業(yè)務(wù)流量;
QoS組件除了滑動(dòng)窗口,還需要添加一個(gè)緩存隊(duì)列,當(dāng)請(qǐng)求被限流之后不能被丟棄,需要添加至緩存隊(duì)列中,等待下一個(gè)時(shí)間窗口執(zhí)行,如下圖所示。
為了實(shí)現(xiàn)帶寬與IOPS的同時(shí)控制,QoS組件將由兩部分組成:IOPS控制組件負(fù)責(zé)控制讀寫(xiě)的IOPS,帶寬控制組件負(fù)責(zé)控制讀寫(xiě)的帶寬,帶寬控制跟IOPS控制類(lèi)似,比如帶寬限制閾值為1Mbps,那么表示一秒最多只能讀寫(xiě)1048576Bytes大小數(shù)據(jù);假定IOPS限制為20iops,表示一秒內(nèi)最多只能發(fā)送20次讀寫(xiě)請(qǐng)求,至于每次讀寫(xiě)請(qǐng)求的大小并不關(guān)心。
兩個(gè)組件內(nèi)部相互隔離,整體來(lái)看又相互影響,比如當(dāng)IOPS控制很低時(shí),對(duì)應(yīng)的帶寬可能也會(huì)較小,而當(dāng)帶寬控制很小時(shí)對(duì)應(yīng)的IOPS也會(huì)比較小。
下面以修復(fù)流量為例,分三組進(jìn)行測(cè)試
第一組:20iops-1Mbps
第二組:40iops-2Mbps
第三組:80iops-4Mbps
測(cè)試結(jié)果如上圖所示,從圖中可以看到qos模塊能控制流量的帶寬跟iops維持在設(shè)定閾值范圍內(nèi)。
為了區(qū)分不同的流量,我們對(duì)流量進(jìn)行標(biāo)記分類(lèi),并為不同磁盤(pán)上的不同流量都初始化一個(gè)QoS組件,QoS組件之間相互獨(dú)立互不影響,最終可以達(dá)到磁盤(pán)粒度的帶寬跟IOPS控制。
前面提到的QoS限流方案,雖然能夠很好的控制內(nèi)部流量帶寬或者IOPS在閾值范圍內(nèi), 但是存在以下不足
不感知業(yè)務(wù)流量現(xiàn)狀,當(dāng)業(yè)務(wù)流量突增或者持續(xù)高峰時(shí),內(nèi)部流量與業(yè)務(wù)流量仍然會(huì)存在資源搶占,不能達(dá)到流量規(guī)避或暫停效果。
磁盤(pán)上不同流量的限流相互獨(dú)立,當(dāng)磁盤(pán)的整體流量帶寬或者IOPS過(guò)載時(shí),內(nèi)部流量閾值不能動(dòng)態(tài)調(diào)低也會(huì)影響業(yè)務(wù)流量的服務(wù)質(zhì)量。
所以需要對(duì)QoS組件進(jìn)行一定的改進(jìn),增加流量監(jiān)控組件,監(jiān)控組件主要監(jiān)控不同流量類(lèi)型的帶寬與IOPS,動(dòng)態(tài)QoS限流方案支持以下功能:
通過(guò)監(jiān)控組件獲取流量增長(zhǎng)率,如果出現(xiàn)流量突增,則動(dòng)態(tài)調(diào)低滑動(dòng)窗口閾值以降低內(nèi)部流量;當(dāng)流量恢復(fù)平緩,恢復(fù)滑動(dòng)窗口最初閾值以充分利用系統(tǒng)資源。
通過(guò)監(jiān)控組件獲取磁盤(pán)整體流量,當(dāng)整體流量大小超過(guò)設(shè)定閾值,則動(dòng)態(tài)調(diào)低滑動(dòng)窗口大??;當(dāng)整體流量大小低于設(shè)定閾值,則恢復(fù)滑動(dòng)窗口至初始閾值。
下面設(shè)置磁盤(pán)整體流量閾值2Mbps-40iops,ec流量的閾值為10Mbps-600iops
當(dāng)磁盤(pán)整體流量達(dá)到磁盤(pán)閾值時(shí)會(huì)動(dòng)態(tài)調(diào)整其他內(nèi)部流量的閾值,從測(cè)試結(jié)果可以看到ec的流量受動(dòng)態(tài)閾值調(diào)整存在一些波動(dòng),磁盤(pán)整體流量下去之后ec流量閾值又會(huì)恢復(fù)到最初閾值(10Mbps-600iops),但是可以看到整體磁盤(pán)的流量并沒(méi)有控制在2Mbps-40iops以下,而是在這個(gè)范圍上下波動(dòng),所以我們?cè)诔跏蓟瘯r(shí)需要保證設(shè)置的內(nèi)部流量閾值小于磁盤(pán)的整體流量閾值,這樣才能達(dá)到比較穩(wěn)定的內(nèi)部流量控制效果。
前面提到存儲(chǔ)QoS主要是限制讀寫(xiě)的帶寬跟IOPS,具體應(yīng)該如何去實(shí)現(xiàn)呢?IO讀寫(xiě)主要涉及以下幾個(gè)接口。
Read(p []byte) (n int, err error)ReadAt(p []byte, off int64) (n int, err error)Write(p []byte) (written int, err error)WriteAt(p []byte, off int64) (written int, err error)
所以這里需要對(duì)上面幾個(gè)接口進(jìn)行二次封裝,主要是加入限流組件。
帶寬控制組件實(shí)現(xiàn)
Read實(shí)現(xiàn)
// 假定c為限流組件
func (self *bpsReader) Read(p []byte) (n int, err error) {
size := len(p)
size = self.c.assign(size) //申請(qǐng)讀取文件大小
n, err = self.underlying.Read(p[:size]) //根據(jù)申請(qǐng)大小讀取對(duì)應(yīng)大小數(shù)據(jù)
self.c.fill(size - n) //如果讀取的數(shù)據(jù)大小小于申請(qǐng)大小,將沒(méi)有用掉的計(jì)數(shù)填充至限流窗口中
return
}
Read限流之后會(huì)出現(xiàn)以下情況
讀取大小n
這里也許你會(huì)想,Read限流的實(shí)現(xiàn)怎么不弄個(gè)循環(huán)呢?如直到讀取指定大小數(shù)據(jù)才返回。這里的實(shí)現(xiàn)我們需要參考標(biāo)準(zhǔn)的IO的讀接口定義,其中有說(shuō)明在讀的過(guò)程中如果準(zhǔn)備好的數(shù)據(jù)不足len(p)大小,這里直接返回準(zhǔn)備好的數(shù)據(jù),而不是等待,也就是說(shuō)標(biāo)準(zhǔn)的語(yǔ)義是支持只讀部分準(zhǔn)備好的數(shù)據(jù),因此這里的限流實(shí)現(xiàn)保持一致。
// Reader is the interface that wraps the basic Read method.//// Read reads up to len(p) bytes into p. It returns the number of bytes// read (0 <= n <= len(p)) and any error encountered. Even if Read// returns n < len(p), it may use all of p as scratch space during the call.// If some data is available but not len(p) bytes, Read conventionally// returns what is available instead of waiting for more.// 省略//// Implementations must not retain p.type Reader interface { Read(p []byte) (n int, err error)}
ReadAt實(shí)現(xiàn)
下面介紹下ReadAt的實(shí)現(xiàn),從接口的定義來(lái)看,可能覺(jué)得ReadAt與Read相差不大,僅僅是指定了數(shù)據(jù)讀取的開(kāi)始位置,細(xì)心的小伙伴可能發(fā)現(xiàn)我們這里實(shí)現(xiàn)時(shí)多了一層循環(huán),需要讀到指定大小數(shù)據(jù)或者出現(xiàn)錯(cuò)誤才返回,相比Read而言ReadAt是不允許出現(xiàn)*n
func (self *bpsReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
for n < len(p) && err == nil {
var nn int
nn, err = self.readAt(p[n:], off)
off += int64(nn)
n += nn
}
return
}
func (self *bpsReaderAt) readAt(p []byte, off int64) (n int, err error) {
size := len(p)
size = self.c.assign(size)
n, err = self.underlying.ReadAt(p[:size], off)
self.c.fill(size - n)
return
}
// ReaderAt is the interface that wraps the basic ReadAt method.//// ReadAt reads len(p) bytes into p starting at offset off in the// underlying input source. It returns the number of bytes// read (0 <= n <= len(p)) and any error encountered.//// When ReadAt returns n < len(p), it returns a non-nil error// explaining why more bytes were not returned. In this respect,// ReadAt is stricter than Read.//// Even if ReadAt returns n < len(p), it may use all of p as scratch// space during the call. If some data is available but not len(p) bytes,// ReadAt blocks until either all the data is available or an error occurs.// In this respect ReadAt is different from Read.//省略//// Implementations must not retain p.type ReaderAt interface { ReadAt(p []byte, off int64) (n int, err error)}
Write實(shí)現(xiàn)
Write接口的實(shí)現(xiàn)相對(duì)比較簡(jiǎn)單,循環(huán)寫(xiě)直到寫(xiě)完數(shù)據(jù)或者出現(xiàn)錯(cuò)誤
func (self *bpsWriter) Write(p []byte) (written int, err error) {
size := 0
for size != len(p) {
p = p[size:]
size = self.c.assign(len(p))
n, err := self.underlying.Write(p[:size])
self.c.fill(size - n)
written += n
if err != nil {
return written, err
}
}
return
}
// Writer is the interface that wraps the basic Write method.//// Write writes len(p) bytes from p to the underlying data stream.// It returns the number of bytes written from p (0 <= n <= len(p))// and any error encountered that caused the write to stop early.// Write must return a non-nil error if it returns n < len(p).// Write must not modify the slice data, even temporarily.//// Implementations must not retain p.type Writer interface { Write(p []byte) (n int, err error)}
WriteAt實(shí)現(xiàn)
這里的實(shí)現(xiàn)跟Write類(lèi)似
func (self *bpsWriterAt) WriteAt(p []byte, off int64) (written int, err error) {
size := 0
for size != len(p) {
p = p[size:]
size = self.c.assign(len(p))
n, err := self.underlying.WriteAt(p[:size], off)
self.c.fill(size - n)
off += int64(n)
written += n
if err != nil {
return written, err
}
}
return
}
// WriterAt is the interface that wraps the basic WriteAt method.//// WriteAt writes len(p) bytes from p to the underlying data stream// at offset off. It returns the number of bytes written from p (0 <= n <= len(p))// and any error encountered that caused the write to stop early.// WriteAt must return a non-nil error if it returns n < len(p).//// If WriteAt is writing to a destination with a seek offset,// WriteAt should not affect nor be affected by the underlying// seek offset.//// Clients of WriteAt can execute parallel WriteAt calls on the same// destination if the ranges do not overlap.//// Implementations must not retain p.type WriterAt interface { WriteAt(p []byte, off int64) (n int, err error)}
IOPS控制組件實(shí)現(xiàn)
IOPS控制組件的實(shí)現(xiàn)跟帶寬類(lèi)似,這里就不詳細(xì)介紹了
func (self *iopsReader) Read(p []byte) (n int, err error) { self.c.assign(1) //這里只需要獲取一個(gè)計(jì)數(shù),如果當(dāng)前窗口一個(gè)都沒(méi)有,則會(huì)一直等待直到獲取到一個(gè)才喚醒執(zhí)行下一步 n, err = self.underlying.Read(p) return}
func (self *iopsReaderAt) ReadAt(p []byte, off int64) (n int, err error) { self.c.assign(1) n, err = self.underlying.ReadAt(p, off) return}
想想這里的ReadAt為啥不需要跟帶寬一樣循環(huán)讀了呢?
func (self *iopsWriter) Write(p []byte) (written int, err error) { self.c.assign(1) written, err = self.underlying.Write(p) return}
WriteAt
func (self *iopsWriterAt) WriteAt(p []byte, off int64) (n int, err error) { self.c.assign(1) n, err = self.underlying.WriteAt(p, off) return}
看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對(duì)創(chuàng)新互聯(lián)的支持。