前言
今天帶來一篇不是自己原創但覺得很有深度的文章:99th Percentile Latency at Scale with Apache Kafka
背景
在同時要求低延時和高質量數據交付的眾多Apache Kafka使用場景中,反欺詐、支付系統和股票交易平台僅僅是其中有代表性的幾個。例如,網上銀行交易就要求反欺詐系統能夠實時進行檢測且每單交易檢測時間不能超過50~100ms,否則會影響用戶體驗。
對於Kafka而言,數據交付時間(data delivery time)被定義為端到端的延時(end-to-end latency),即消費者獲取到生產者發送消息的時間。我們設定延時目標的方式就是確定一個延時值然后滿足這個它。比方說,你的延時目標可能是這樣表述的:“我希望99%的情況下端到端延時是50ms左右。”
這其實在可用性、持久性和吞吐量三個方面都對你提出了要求。達成高持久性和高吞吐量與實現低延時需要你做一個權衡。主要的挑戰是在保持低延時的同時擴展應用程序從而實現高吞吐量。由於延時依賴於你的硬件條件或所選的雲廠商,你要有能力監控並調優你的客戶端程序以實現特定的延時目標。
之前我們寫過一篇文章詳細介紹了調優Kafka集群的方法。那篇文章將有助於你直觀地理解端到端延時以及在確保延時目標達成的條件下如何配置和擴展你的應用程序。
理解Kafka端到端延時
端到端延時是指應用邏輯調用KafkaProducer.send()生產消息到該消息被應用邏輯通過KafkaConsumer.poll()消費到之間的時間。下圖展示了一條消息在此過程中流轉的完整路徑,包括從內部的Kafka producer到broker,經過備份之后被consumer獲取到。
對於端到端延時的構成, 我們定義了5個主要的組成部分:
- Produce time:內部Kafka producer處理消息並將消息打包的時間
- Publish time:producer發送到broker並寫入到leader副本log的時間
- Commit time:follower副本備份消息的時間
- Catch-up time:消費者追趕消費進度,消費到該消息位移值前所花費的時間
- Fetch time:從broker讀取該消息的時間
下面我們分別解釋下這5部分各自的含義。特定的客戶端配置或應用邏輯設計通常會極大地影響端到端延時,因此我們有必要精准定位哪個因素對延時的影響最大。
Produce time
Produce time衡量的是應用邏輯調用KafkaProducer.send()生產消息到包含該消息的PRODUCE請求被發往leader副本所在broker之間的時間。Kafka producer會將相同topic分區下的一組消息打包在一起形成一個批次(batch)以提升網絡I/O性能。默認情況下,producer會立即發送batch,這樣一個batch中通常不會包含太多的消息。為了提升打包效果,producer通過設置linger.ms參數人為地暫停一小段時間以等待更多的消息到達並加入到batch中。一旦超過了linger.ms等待時間或batch被填滿(batch大小超過了batch.size值),那么batch就被視為“已完成”狀態(completed)。
倘若啟用了壓縮(設置了compression.type),producer會將已完成batch進行壓縮。在batch完成之前,batch的大小是基於壓縮類型和之前壓縮比率估算出來的。
PRODUCE請求被發送到leader副本所在的broker處理之后,broker需要發送應答信息給producer。如果未應答的PRODUCE請求數量超過了閾值(即max.inflight.requests.per.connect,默認是5),那么在producer端進行中的batch就必須等待更長的時間。顯然,broker應答的速度越快,producer額外等待的時間就越短。
Publish time
Publish time衡量的是內部producer發送PRODUCE請求到broker與對應消息被寫入到leader副本日志之間的間隔。當PRODUCE請求達到broker端時,負責對應連接的網絡線程(也就是Processor線程)獲取到該請求並將其放置到請求隊列上。后面的某個請求處理線程(也就是IO線程或KafkaRequestHandlerThread)從請求隊列中取出該PRODUCE請求並處理之。故publish time包含了PRODUCE請求的網絡傳輸時間、在broker上的隊列等候時間以及寫入到消息日志的時間(准確說是頁緩存訪問時間)。如果broker端負載不大,網絡傳輸和日志寫入時間將會是主要的影響因素。隨着broker端負載越來越重,隊列等候時間可能會逐漸成為瓶頸。
Commit time
Commit time衡量的是消息被備份到所有ISR副本的時間。Kafka只會將已提交(committed)的消息暴露給consumer,也就是被所有ISR備份過的消息。多個Follower副本從leader副本並行地拉取消息。正常情況下一個健康集群下所有follower副本都應該在ISR集合中。這就意味着消息被commit的時間等於它被備份到ISR中最慢follower副本的時間。
為了備份數據,follower向leader發送FETCH請求——consumer獲取消息也是發送FETCH請求。對於副本發出的FETCH請求社區優化了Broker端的默認配置:leader會竭盡所能地提早發送response(只有有1個字節准備好了可以發送,leader就會馬上發出去。具體數值由replica.fetch.min.bytes參數或replica.fetch.wait.max.ms參數控制)。Commit time主要受replication factor配置和集群負載影響。
Catch-up time
Kafka中消息是按照其生產的順序被消費的,除非consumer端顯式地變更了消費順序(通過seek等方法)。同一個分區下,consumer必須要消費完之前發布的消息后才能讀取后面的消息。假設消息被提交時,consumer僅僅才消費到該消息的前N條消息。那么Catch-up time就是consumer消費這N條消息的總時間,如下圖展示的那樣:
當你使用Kafka構建實時應用時,最好讓catch-up time越小越好,甚至是0,即消息一旦被提交,consumer就會立刻消費它。如果consumer持續性地落后太多,端到端延時將變得非常不可控。因此,catch-up time的大小依賴於consumer匹配producer吞吐量的能力。
Fetch time
Consumer持續性地調用poll方法來從leader broker獲取消息數據。Fetch time衡量的是它從leader副本所在broker獲取消息的時間。Broker可能會等待足夠多的數據積累到response之后才會返回。默認的配置下,fetch.min.bytes = 0,已經針對延時進行了優化,即只要有數據能夠返回立即返回。
端到端延時 VS. producer和consumer延時
下圖展示了以Kafka clients端視角觀測到的延時,通常被稱為producer延時和consumer延時。Producer延時是指調用KafkaProducer.send()與接收到消息已提交應答之間的時間。消息被應答的方式基於acks參數的設置,該參數控制了消息的持久化水平:
- acks = 0:立即返回應答,無需等待broker端的回應
- acks = 1:等待leader副本所在broker寫入消息后返回應答
- acks = all:等待所有ISR副本寫入消息后返回應答
所以,producer延時由produce time、publish time(acks≠0)、commit time和broker發送response到producer網絡傳輸時間4部分組成。
上圖清晰地展示了acks設置對producer延時的影響:通過設置不同的acks,我們可以選擇性將producer延時中的某些組成部分移除掉,從而減少producer延時。值得注意的是,無論acks怎么設置,publish time和commit time總是端到端延時的一部分。
Consumer延時是consumer發送FETCH請求到broker與broker返回結果給consumer之間的時間間隔。計算方法是統計程序調用KafkaConsumer.poll()時點和消息返回時點的差值。Consumer延時包含了端到端延時中的Fetch time。
控制端到端延時
如果我們思考一條消息的生命周期,控制端到端延時其實就是控制消息在系統中流轉的時間總和。很多Kafka clients端和broker端參數的默認值已然對延時做了優化:比如減少人為等待時間(linger.ms=0)等。其他的延時可能來自於broker端上的隊列等候時間,控制這種延時就要設計控制broker的負載(CPU或吞吐量)。
如果我們把系統看做是一個整體,限制端到端延時也需要系統的每一層(producer、broker和consumer)都要能穩定地維持應用邏輯所需的吞吐量要求。舉個例子,如果你的應用邏輯按照100MB/秒的速度發送消息,但是由於某種原因,Kafka consumer吞吐量在接下來的幾秒時間內下降到10MB/秒,這樣絕大多數剛剛被生產出來的消息都需要等待更長的時間才能被消費到。此時,你需要一種高效的方式來擴展你的Kafka clients程序以提升吞吐量——高效地利用broker端資源來減少隊列等候時間和偶發的網絡擁塞。
限制延時最理想的方式是確保所有延時指標均低於目標值。實際情況中,這種硬性保證幾乎很難實現,因為總有各種各樣非預期的故障和突發流量。但是,你還是可以小心地設計你的應用程序並調優集群來實現95~99百分位下的延時目標,即控制所有消息95~99%情況下的延時水平。高百分位延時被稱為尾延遲(tail latency),因為它們位於延遲段(latency spectrum)的尾部。
目標延時所用的百分位越大,你需要降低或容忍應用最差表現所做的努力就越多。舉個例子,偶發的大請求可能會阻塞其后所有的請求,從而抬高后續請求的延時,這就是所謂的隊首阻塞(head of line blocking)問題。再比如,大量平素負載請求很低的clients端程序可能會同時向Kafka發送PRODUCE/FETCH請求或同時請求刷新元數據信息,導致broker端請求隊列突然拉長,從而引發比平時嚴重得多的尾延時。這就是所謂的micro-bursting(譯者:直接使用原文了。感覺沒有特別合適的翻譯。微沖擊?局部瞬時沖擊?)
下面我們分別討論一下。
評估不同Clients端配置對延時影響的測試環境
我們使用實驗結果說明clients參數和吞吐量擴展技術對性能的影響。我們使用Kafka自帶的Trogdor測試框架及producer和consumer基准值(ProduceBench&ConsumeBench)進行測試。
所有測試均跑在一個由9台broker構成的Kafka集群上,replication factor設置為3,即在不超過3台broker掛掉的情況下保證沒有消息丟失。Kafka broker機器運行在AWS r5.xlarge實例上,EBS為2TB。Broker分布在相同region下的3個不同AZ上以提升容錯性。同時,每個topic分區的副本都被放置在不同的AZ上。Kafka clients配置SASL認證和SSL信道加密,但broker之間的通訊依然使用PLAINTEXT。
最后,除非顯式提及,否則我們所有的測試都使用以下的clients端配置:
Replication factor | 3 |
topic分區數 | 108 |
security.protocol | SASL_SSL |
acks | all |
linger.ms | 5 |
compression.type | LZ4 |
消息大小 | 512B |
消息Key大小 | 4B |
Trogdor消息值生成器 | uniformRandom |
Trogdor消息Key生成器 | sequential |
Trogdor實例數 | 9 |
這個測試場景確實會引人額外的延時:多個AZ的部署架構會增加commit time,因為引入了跨AZ的備份。另外無論是clients端還是broker端,SSL加密也是有開銷的。最后由於SSL無法利用Zero Copy特性進行數據傳輸,因為consumer獲取消息時也會增加額外的開銷。雖然這些都會影響延時,但這畢竟符合真實的使用場景,因此我們決定采用這樣的部署結構進行測試。
持久性設置對延時的影響
當我們拿延時目標去PK其他指標時,我們最好先考慮持久性。我們通常都要保證滿足一定程度的持久性,畢竟你的數據是非常關鍵的。優化持久性會增加端到端延時,因為它增加了備份的代價(commit time),並且給broker添加了備份方面的負載 ,而這會增加隊列等候時間。
Replication factor
Replication factor(rf)是Kafka持久化保證的核心,它定義了Kafka集群上保存的topic副本數。Replication factor = rf表示我們最多能夠容忍rf - 1台broker宕機而不必數據丟失。Rf = 1能夠令端到端延時最小化,但卻是最低的持久化保證。
增加rf會增加備份開銷並給broker額外增加負載。如果clients端帶寬在broker端均勻分布,那么每個broker都會使用rf * w寫帶寬和r + (rf - 1) * w讀帶寬,其中w是clients端在broker上的寫入帶寬占用,r是讀帶寬占用。由此,降低rf對端到端延時影響的最佳方法就是確保每個broker上的負載是均勻的。這會降低commit time,因為commit time是由最慢的那個follower副本決定的。如果我們能減少各個broker延時之間方差,我們就能降低整體的尾延時水平。
如果你的Kafka broker使用了過多的磁盤帶寬或CPU,follower就會開始出現追不上leader的情況從而推高了commit time。我們建議為副本同步消息流量設置成使用不同的listener來減少與正常clients流量的干擾。你也可以在follower broker上增加I/O並行度,並增加副本拉取線程數量(number.replica.fetchers)來改善備份性能。
acks
縱然我們配置了多個副本,producer還是必須通過acks參數來配置可靠性水平。設置acks=all能夠提供最強的可靠性保證,但同時也會增加broker應答PRODUCE請求的時間,就像我們之前討論的那樣。
Broker端應答的速度變慢通常會降低單個producer的吞吐量,進而增加producer的等待時間。這是因為producer端會限制未應答請求的數量(max.inflight.requests.per.connection)。舉個例子,在我們的環境中acks=1,我們啟動了9個producer(同時也跑了9個consumer),吞吐量達到了195MB/秒。當acks切換成all時,吞吐量下降到161MB/秒。設置更高級別的acks通常要求我們擴展producer程序才能維持之前的吞吐量水平以及最小化producer內部的等待時間。
min.insync.replicas
min.insync.replicas是一個重要的持久化參數,因為它定義了broker端ISR副本中最少要有多少個副本寫入消息才算PRODUCE請求成功。這個參數會影響可用性,但是不會影響端到端的延時。無論min.insync.replicas取何值,消息都必須要被復制到ISR所有副本中。因此,選擇一個小一點的值並不能減少commit time或是延時。
在滿足延時目標的前提下擴展吞吐量
延時-吞吐量權衡
優化Kafka clients端吞吐量意味着優化打包的效果(batching)。Kafka producer內部會執行一類打包,即收集多條消息到一個batch中。每個batch被統一壓縮然后作為一個整體被寫入日志或從日志中讀取。這說明消息備份也是以batch為單位進行的。打包化會減少每條消息的成本,因為它將這些成本攤還到clients端和broker端。通常來說,batch越大這種開銷降低的效果就越高,減少的網絡和磁盤I/O就越多。
另一類打包就是在單個網絡請求/響應中收集(打包)多個batch以減少網絡數據傳輸量。這能降低clients端和broker端的請求處理開銷。
這類打包能夠提升吞吐量和降低延時,因為batch越大,網絡傳輸I/O量越小,CPU和磁盤使用率越低,故最終能夠優化吞吐量。另外batch越大還能減低端到端延時,因為每條消息的成本降低了,使得系統處理相同數量消息的總時間變少了。
這里的延時-吞吐量權衡是指通過人為增加等待時間來提升打包消息的能力。但過了某個程度,人為等待時間的增加可能會抵消或覆蓋你從打包機制獲得的延時收益。因此你的延時目標有可能會限制你能實施打包化的水平,進而減少所能達到的吞吐量並增加延時。如果拉低了本能達到的吞吐量或端到端延時水平,你可以通過擴展集群來換取或“購買”更多的吞吐量或處理能力。
配置Kafka producer和consumer實現打包化
對於producer而言,打包化水平由兩個參數控制:batch.size(默認值16KB)——限制每個batch的大小;linger.ms(默認值0)——限制人為等候時間。如果你的應用程序發送消息的速度很高,那么即使linger.ms=0,batch也能在被發送之前快速填滿。反之,如果發送速率很低,那么你就需要增加linger.ms值以提升打包水平。
對於consumer而言,你能夠調整每個FETCH響應中返回的數據量。具體方法是調優fetch.min.bytes(默認值1)——該參數指定了broker端能夠發送FETCH響應所能包含的最小數據字節數。另外還有一個參數fetch.max.wait.ms(默認值500ms)用於控制broker端等待數據的超時時間。FETCH響應中包含更多的數據會使得后續的FETCH請求數變少。Producer端打包化間接影響PRODUCE和FETCH請求的數量,因為batch定義了數據能夠被獲取的最小數據量。
值得注意的是,默認情況下,Kafka producer和consumer設置的是無人為等待時間,這么做的目的是為了降低延時。但是,即使你的目標就是了使延時最小化,我們依然推薦你設置一個不為0的linger.ms值,比如5~10ms。當然,這么做是有前提的:
- 如果你擴展了你的producer程序,平均下來使得每個producer實例的發送速率變得很低,那么你的batch只會包含很少的幾條消息。如果你整體的吞吐量已然很高了,那么你可能會直接把你的Kafka集群壓掛,導致超高的隊列等候時間從而推高延時。此時,設置一個較小的linger.ms值確實能夠改善延時。
- 如果你在意尾延時,那么增加linger.ms可能會降低請求速率以及同時到達broker端的瞬時沖擊流量。這種沖擊越大,請求在尾部的延時就越高。這些瞬時沖擊流量決定了你的尾延時水平。因此增加linger.ms能夠顯著降低尾延時,即使你平均的延時會有所增加,比如平均增加了linger.ms等待時間。
下面這個實驗說明了以上兩種場景。我們啟動了90個producer,向一個有108個分區的topic發送消息。生產端整體的吞吐量峰值在90MB/秒。我們跑了3次測試,每一次對應一種不同的producer配置。下圖展示了50百分位和99百分位水平下的producer延時。由於我們設置了acks=all,producer延時包含produce time、publish time和commit time。
鑒於我們使用了超多的producer實例來計算整體吞吐量,linger.ms = 0的設置基本上會驅散producer端的打包化效果。把linger.ms值提升到5ms會極大地改善打包化水平。PRODUCE請求數量從2800驟降到1100。這種變化會同時降低兩個百分位水平下的producer延時,而99百分位下的降低效果更為顯著。
增加batch.size並未直接影響producer端的等待時間,因為producer在積累batch的等待時間不會超過linger.ms值。在我們的實驗中,增加batch.size到128KB並未提升打包化,因為單個producer的吞吐量其實是很低的。就像我們期望的那樣,producer延時在兩次實驗中沒有太多改進。
總結一下,如果你的目標是為了降低延時,我們推薦保持默認的batch參數並適當增加linger.ms,前提是producer無法進一步打包更多的消息。如果你在意尾延時,最好調優下打包水平來減少請求發送率以及大請求沖擊的概率。
不增加人為等待下提升打包效果
打包效果不好的另一個原因是producer發送消息給大量分區。如果消息不是發往同一個分區的,它們就無法聚集在一個batch下。因此,通常最好設計成讓每個producer都只向有限的幾個分區發送消息。
另外,可以考慮升級到Kafka 2.4 producer。這個版本引入了一個全新的Sticky分區器。該分區器能夠改善non-keyed topic的打包效果,同時還無需引入人為等待。
Clients數量對尾延時的影響
即使整體的生產和消費的吞吐量保持不變,通常也是Clients數越多,broker上負載越大。這是因為clients數量多會導致更多的METADATA請求發到Kafka,繼而要維護更多的連接,故給broker帶來更大的開銷。
相對於50百分位或平均延時的影響,Clients數量增加對尾延時的影響更大。每個producer最多發送max.inflight.requests.per.connection個PRODUCE請求給單個broker,而每個consumer一次最多只會給一個broker發送FETCH請求。Clients越多,同一時刻發送到broker的PRODUCE和FETCH請求也就越多,這就增加了形成請求瞬時沖擊的概率,進而推高了尾延時。
Consumer數量通常由topic分區數量以及期望consumer沒有較大lag的目標共同決定。但是,我們卻很容易為了擴展吞吐量而引入大量的producer。基於吞吐量的考量增加producer實例數可能有相反的效果,因為producer會導致更少的消息被打包,畢竟每個producer處理了更少的消息,因而發送速率會變慢。同時producer還必須等待更長的時間來積累相同數量的消息進到batch里面。
在我們的實驗中,我們將producer的數量從90增加到900,發現吞吐量沒有他打變化:90MB/秒。我們使用batch.size=16KB,linger.ms = 5,acks=all的設置。下圖展示了三次實驗的producer延時:
結果顯示增加producer數量(90 =》 900)增加了60%的中位數延時值,而99百分位延時值幾乎增加了3倍!延時的增加是因為producer端打包效果變差導致的。尾延時的增加是因為更大的請求瞬時沖擊,這會拉升broker端延時,同時producer端會等待更長的時間來接收應答。在900個producer的測試中,broker完全被PRODUCE請求壓垮了。用於處理請求的時間幾乎占到了broker端CPU使用率的100%。另外由於我們使用了SSL,它也會進一步引入請求級的開銷。
如果你通過添加producer來提升吞吐量,那么可以考慮增加單個proudcer的吞吐量——方法是改善打包效果。不管怎樣,你最終可能會有很多producer實例。比如,大公司收集設備上的統計指標,而設備數可能有成千上萬。此時,你可以考慮使用一個代理來收集來自多個clieints的請求,然后把它們轉換成更高效的PRODUCE請求再發給Kafka。你也可以增加broker數來降低單個broker上的請求負載。
擴展consumer
當擴展consumer時,記住:同一個group下的所有consumer實例發送位移提交和心跳請求給同一個broker,也就是Coordinator所在的broker。Consumer數越多,位移提交間隔(auto.commit.interval.ms)內需要提交位移的頻次也就越多。位移提交本質上就是發送給__consumer_offsets主題的PRODUCE請求。因此增加consumer數量會導致broker上的請求負載增加,特別是auto.commit.interval.ms值很小的時候。
壓縮設置的影響
默認情況下,Kafka producer不做壓縮。compression.type參數決定要不要做壓縮。壓縮會在producer端引入額外的開銷來壓縮消息,在broker端做校驗時解壓縮從而引入額外的開銷,另外在consumer端解壓縮也是開銷。Broker端的壓縮參數compression.type應該設成“producer”以避免重新壓縮的成本——這樣,broker直接將原始已壓縮消息寫入日志即可。
雖然壓縮會增加CPU開銷,但它還是可能減少端到端延時的,因為它能顯著地降低處理數據所需的帶寬占用,進而減少broker端的負載。壓縮是在batch級別上完成的,故打包效果越好,壓縮效果也就越好。
更多的分區可能增加延時
topic分區是調節Kafka並行度的最小單位。不同分區的消息能夠被producer並行發送,並行寫入到不同的broker上,同時也可以被不同的consumer並行讀取。因此分區越多吞吐量越大。單純從吞吐量的角度來看,每個broker上10個分區就能夠達到最大限度的吞吐量了。你可能需要更多的分區數來支撐你的應用邏輯。
太多的分區數對端到端延時有負面影響。每個topic分區數太多通常會導致producer端更差的打包效果。每個broker上分區太多會使得follower副本產生更多的FETCH請求。每個FETCH請求必須要遍歷所有要同步的分區,而leader必須要檢查FETCH請求中每個分區的狀態和可返回的數據。這些操作都是很小的磁盤IO。因此太多的分區會導致更大的commit time以及更高的CPU負載,進而引起更長的隊列等候時間。
Commit time的增加和CPU負載變高會推高所有clients的端到端的延時,即使是那些只與一小部分分區交互的clients。
我們做了這樣的一個測試:兩個topic。一個topic有9個producer,每秒發送5MB數據,同時被由9個consumer組成的group消費。該實驗運行了幾天,之后我們增加了分區數,逐步將其從108提升到了7200(每台broker上800個分區),每一步都運行一個小時。第二個topic在整個實驗階段保持9個分區。同時啟動9個producer,每一個產生消息到一個對應的分區。另外還有一個9consumer實例構成的group。這些producer的發送速率是512B/秒。
下圖展示了分數區對99百分位端到端延時的影響。隨着每個broker上分區數的增加,clients的端到端延時大致呈線性增加趨勢。分區數的增加會推高broker上的CPU負載同時拖慢所有clients的備份,即使是對那些只與固定分區數量交互的clients而言,也會抬高端到端延遲。
為了減少延時,最好還是限制每個broker上的分區數,方法是減少總的分區數或擴展集群。你還可以通過增加fetcher線程數量的方式來改善commit time。
Broker端負載對延時的影響
我們之前討論了broker上負載會增加隊列等候時間,從而推高端到端延時。很容易看到請求速率的增加也會使隊列等候時間拉長,因為請求數越多,隊列越長。
Broker端高資源利用率(如disk、CPU)都會增加隊列等候時間,而這種等候時間一般是指數級增長。這就是隊列理論中提到的一個著名特性:Kingman公式證明等待某種資源的時間正比於(資源繁忙時間百分比/資源空閑時間百分比)
由於延時隨着資源利用率指數級增長,當broker端某種資源接近100%使用率時你會觀測到超高的延時出現。減少資源使用率的方法是減少資源使用(比如減少連接數,請求數和分區數)或者是擴展集群,這些方法都能有效地降低延時。同時,讓負載均勻地在各個broker上進行分布也是有幫助的。這對於我們改善尾延時非常有效。
如果你使用的是Confluent Cloud,你不用擔心如何維護broker的事情——我們已經幫你做了。我們會自動地檢測broker端上的資源使用率情況,然后自動為你執行負載均衡操作以維持各個broker上的負載均勻。
總結
本文論證了如果要擴展clients和分區來提升吞吐量同時又要限制延時不要惡化,我們必須要限制每個broker上的資源使用率——比如限制連接數、分區數和請求速率等。如果要優化尾延時則要求我們將任何來自clients端的瞬時沖擊降到最低。均勻分布負載對於最小化尾延時同樣重要,因為尾延時是由最慢的broker決定的。