日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

我如何將軟件系統(tǒng)的性能提高35,000%

> An on-call developer's worst nightmare (red indicates errors)

 

深入探討如何通過緩存,作業(yè)化,隊(duì)列分離等解決平臺(tái)的擴(kuò)展性,穩(wěn)定性和性能問題。

一天處理超過$ 20,000,000

先前的公司建立了用于大規(guī)模捐贈(zèng)日的支付系統(tǒng)和捐贈(zèng)日軟件,在該捐贈(zèng)日中,我們?yōu)橐淮胃?jìng)選活動(dòng)就收到數(shù)萬筆捐款。

我在那家公司的職責(zé)之一是擴(kuò)展系統(tǒng)并確保其不會(huì)傾覆。 在最壞的情況下,每秒僅3–5個(gè)請(qǐng)求就會(huì)崩潰。

由于低效的體系結(jié)構(gòu),可疑的技術(shù)選擇以及急速的開發(fā),它具有許多限制,而且是創(chuàng)可貼和巨大的性能差距的拼湊而成。 魔咒和咒語的結(jié)合將使服務(wù)器全天運(yùn)行。

在使用該平臺(tái)時(shí),它有潛力每秒處理數(shù)千個(gè)請(qǐng)求并同時(shí)運(yùn)行數(shù)千個(gè)廣告系列,而所有這些操作的成本大致相同。

怎么樣? 我會(huì)告訴你!

分析使用模式

在深入研究如何優(yōu)化該系統(tǒng)之前,我們必須了解其使用模式以及要在其下進(jìn)行優(yōu)化的特定環(huán)境和約束,否則將是在黑暗中進(jìn)行拍攝。

給一天定義了開始和結(jié)束

我如何將軟件系統(tǒng)的性能提高35,000%

> RPS: Giving days started and ended suddenly.

 

提前幾天安排好大規(guī)模的計(jì)劃活動(dòng)。 它們?cè)诜浅L囟ǖ娜掌诤蜁r(shí)間開始和停止。 有時(shí)這些日期是可移動(dòng)的。 有時(shí)不是。

強(qiáng)調(diào)分享

在競(jìng)選期間,大力宣傳捐贈(zèng)的努力可能很大。

我們的系統(tǒng)可能在一天的開始就發(fā)送數(shù)十萬封電子郵件,在整個(gè)活動(dòng)期間定期跟蹤電子郵件,以鼓勵(lì)人們參觀,參與,共享和捐贈(zèng)。

社交媒體鏈接被發(fā)布在現(xiàn)有的每個(gè)網(wǎng)絡(luò)平臺(tái)上的任何地方,其中一些我從未聽說過。

整個(gè)校園甚至還有實(shí)物海報(bào),展位和傳單。 有些客戶甚至在整個(gè)24-48期間都進(jìn)行了電視特輯。

活動(dòng)既尖刻又恒定

鑒于以上所述,我們的資源使用情況可以最好地描述為尖峰和不變。

我如何將軟件系統(tǒng)的性能提高35,000%

> CPU: mostly constant resource usage with occasional spikes in activity.

 

在奉獻(xiàn)日的某些部分,例如一天的開始和社交媒體的協(xié)調(diào)推送,我們可以看到活動(dòng)大量增加。 對(duì)于單個(gè)廣告系列,我們可以在不到一秒鐘的時(shí)間內(nèi)從每秒0個(gè)請(qǐng)求增加到每秒150個(gè)請(qǐng)求。 這種缺乏加速的行為特征有時(shí)可能與DDoS難以區(qū)分。

在這些事件之外,資源使用情況是恒定的。 當(dāng)用戶與網(wǎng)站互動(dòng)時(shí),我們將看到捐贈(zèng)和活動(dòng)。

最終,當(dāng)一天結(jié)束并且活動(dòng)全部結(jié)束時(shí),活動(dòng)一開始就突然下降。

有遠(yuǎn)見的優(yōu)勢(shì)

由于開始/結(jié)束日期是已知的,并且我們與客戶緊密合作以找出他們當(dāng)天的游戲計(jì)劃,因此它可以為我們的服務(wù)器活動(dòng)提供很多可預(yù)測(cè)性。 這種可預(yù)測(cè)性允許計(jì)劃負(fù)載。

如果我們知道客戶在日常活動(dòng)中要達(dá)到的目標(biāo)是什么,我們可以通過性能優(yōu)化和調(diào)整服務(wù)器設(shè)置以最好地管理他們的預(yù)期負(fù)載來為此做準(zhǔn)備。 可以通過一些基本計(jì)算來相對(duì)精確地估計(jì)其中的大部分。

我們正在嘗試優(yōu)化什么?

現(xiàn)在我們知道要處理的使用方式,讓我們簡(jiǎn)單地回顧一下我們已有的一些指標(biāo)。 請(qǐng)記住,在優(yōu)化之前,我們應(yīng)該基準(zhǔn)測(cè)試并衡量我們能做到的一切。

我們應(yīng)該忘記效率低下的問題,大約有97%的時(shí)間是這樣的:過早的優(yōu)化是萬惡之源。 然而,我們不應(yīng)該放棄我們那關(guān)鍵的3%的機(jī)會(huì)。"

-唐納德·努斯

正如他們所說:"測(cè)量?jī)纱危懈钜淮巍?quot;

對(duì)于我們的系統(tǒng),我們可以將指標(biāo)分為兩類:

· 衡量活動(dòng)的指標(biāo)

· 衡量績(jī)效的指標(biāo)

測(cè)量活動(dòng)

測(cè)量活動(dòng)很重要。 這是服務(wù)器性能的輸入。

每秒的請(qǐng)求很簡(jiǎn)單。 問一個(gè)問題:我們的服務(wù)器每秒處理多少個(gè)請(qǐng)求? 更多意味著更多的活動(dòng)。

CPU使用率是我們密切關(guān)注的另一項(xiàng)指標(biāo),用于檢測(cè)系統(tǒng)不可用性。 密集的計(jì)算會(huì)導(dǎo)致系統(tǒng)備份,并且系統(tǒng)不應(yīng)該首先對(duì)Web請(qǐng)求進(jìn)行密集的計(jì)算。

內(nèi)存使用情況是成敗指標(biāo)。 我們的服務(wù)器上只有這么多容量。 一些低效的代碼是內(nèi)存消耗,將成千上萬個(gè)對(duì)象實(shí)例化到內(nèi)存中。 這些內(nèi)存泄漏被發(fā)現(xiàn)并被壓縮。

由于我們使用的云服務(wù)提供商對(duì)連接數(shù)量有限制,因此連接計(jì)數(shù)值得關(guān)注。

衡量績(jī)效

性能的最大衡量標(biāo)準(zhǔn)是響應(yīng)時(shí)間。 降低它意味著我們表現(xiàn)良好,提高它意味著我們表現(xiàn)不好。 諸如DataDog或NewRelic之類的APM工具可以向我們展示層級(jí)的響應(yīng)時(shí)間,我們可以用來確定瓶頸。

從技術(shù)上講,Heroku上的整體請(qǐng)求響應(yīng)時(shí)間限制為30秒超時(shí),實(shí)際上,我們希望大多數(shù)面向客戶頁面的請(qǐng)求能在3秒內(nèi)完成。 我個(gè)人認(rèn)為超過8秒的任何時(shí)間都被視為中斷。

第50個(gè)百分位數(shù)通常在100毫秒以下,因?yàn)樵S多請(qǐng)求都是快速完成的API端點(diǎn)。

第99個(gè)百分位數(shù)可能會(huì)超過20秒而沒有問題,因?yàn)槟承┕芾眄撁鎯H花了一段時(shí)間才能完成。

我真正關(guān)心的是第95個(gè)百分點(diǎn)-我們希望95%的請(qǐng)求在3秒內(nèi)完成。 這95%代表了大部分客戶請(qǐng)求和參與,并代表了捐助者將經(jīng)歷的事情。

低掛優(yōu)化

讓我們看一下低掛的優(yōu)化成果是什么:

· 垂直和水平縮放

· N + 1個(gè)查詢

· 低效的代碼

· 背景

· 資產(chǎn)最小化

· 內(nèi)存泄漏

· 共置

垂直和水平縮放

垂直縮放

我要做的第一件事就是增加每個(gè)服務(wù)器的功能-通過垂直擴(kuò)展實(shí)現(xiàn)性能。 我為每個(gè)服務(wù)器提供了更多的內(nèi)存和處理資源,以幫助更快地服務(wù)和滿足請(qǐng)求。

我如何將軟件系統(tǒng)的性能提高35,000%

> Here, New Relic is showing a large spike in request queue time. In this case, it was time spent wa

 

但是,垂直縮放具有一些缺點(diǎn)。 其中之一是您可以垂直擴(kuò)展單個(gè)實(shí)例的數(shù)量有實(shí)際限制。

第二個(gè)缺點(diǎn)是垂直擴(kuò)展會(huì)變得非常昂貴。 當(dāng)您沒有無限的資源時(shí),成本將成為主要考慮因素,也是決定權(quán)衡因素的一個(gè)因素。

水平縮放

如果一臺(tái)服務(wù)器每秒可滿足10個(gè)用戶請(qǐng)求,則粗略估算表明10臺(tái)服務(wù)器每秒可滿足100個(gè)請(qǐng)求。 實(shí)際上,它并不能完全線性地?cái)U(kuò)展,但是對(duì)于一個(gè)假設(shè)來說是很好的。 這稱為水平縮放。

我們將服務(wù)器配置為根據(jù)各種指標(biāo)自動(dòng)擴(kuò)展。 隨著服務(wù)器啟動(dòng)以處理任何增加的活動(dòng),我們發(fā)現(xiàn)等待延遲/排隊(duì)時(shí)間通常會(huì)出現(xiàn)一個(gè)小的峰值。 一旦額外的服務(wù)器完全啟動(dòng),隨著系統(tǒng)適應(yīng)增加的負(fù)載,流量請(qǐng)求隊(duì)列時(shí)間就會(huì)減少。

我如何將軟件系統(tǒng)的性能提高35,000%

> As activity increased, we automatically spun up more servers, which allowed us to handle the incre

 

幾個(gè)挑戰(zhàn)

水平縮放并非一帆風(fēng)順。

在代碼庫中有很多不是線程安全的實(shí)踐。 例如,在代碼庫中使用類實(shí)例變量作為共享狀態(tài)非常流行,這導(dǎo)致線程彼此覆蓋。我不得不花費(fèi)大量時(shí)間來遍歷它,并修改算法和代碼來以某種方式管理數(shù)據(jù) 這對(duì)于多線程環(huán)境是安全的。

我還必須實(shí)現(xiàn)更好的連接池和管理技術(shù)-我們經(jīng)常會(huì)耗盡與各個(gè)商店的連接,因?yàn)樵S多存儲(chǔ)都是硬編碼的,并在實(shí)例化時(shí)建立了直接連接,這意味著如果存在,應(yīng)用程序?qū)嵗龑o法處理任何事務(wù)。 沒有可用的連接。

在Heroku上縮放

雖然您可以并且應(yīng)該在其他平臺(tái)上設(shè)置縮放比例,但是我們使用的是Heroku,而Heroku使縮放變得容易。

您擁有可控制的測(cè)功機(jī)數(shù)量,并且具有增加每個(gè)測(cè)功機(jī)功率的能力。 如果您需要更細(xì)粒度的控件,那么像HireFire這樣易于集成的供應(yīng)商將提供擴(kuò)展配置選項(xiàng),這些功能可為您提供強(qiáng)大的功能和靈活性。

您還可以設(shè)置與網(wǎng)絡(luò)服務(wù)器并發(fā)性相關(guān)的內(nèi)容。 我們正在使用Puma,它不僅可以通過WEB_CONCURRENCY標(biāo)志來更改工作程序的數(shù)量,還可以選擇更改每個(gè)進(jìn)程的線程數(shù)。

結(jié)果

可自定義的垂直和水平縮放比例相結(jié)合,使我們?cè)跒楦鞣N性能特征準(zhǔn)備場(chǎng)地時(shí)具有極大的靈活性。

這是一項(xiàng)長期的工作。 在確定將成本,性能和資源使用量平衡到可接受水平之前,我不得不在擴(kuò)展閾值方面做很多工作。 由于可接受的級(jí)別在公司及其環(huán)境中會(huì)有所不同,因此我建議將其作為一種實(shí)踐,以不斷地適當(dāng)?shù)販y(cè)試擴(kuò)展配置。

N + 1個(gè)查詢

N + 1查詢是需要其他查詢才能完整了解數(shù)據(jù)的查詢。 它們通常是由于數(shù)據(jù)檢索注意事項(xiàng)或體系結(jié)構(gòu)問題而引起的。

例如,假設(shè)您有一個(gè)需要返回捐贈(zèng)的端點(diǎn)和捐贈(zèng)的捐贈(zèng)者。 N + 1查詢可能隱藏在其中-首先必須進(jìn)行查詢以檢索所有捐贈(zèng),然后對(duì)于每次捐贈(zèng),還必須獲取捐贈(zèng)者記錄。

通常,附加查詢會(huì)隱藏在檢索后的序列化器中,尤其是在Ruby on Rails中:

class DonationsController
  def index donations = Donation.all 
  end
end

class DonationSerializer
  belongs_to :donor 
  # This will result in a N+1 query (see above) 
  # because the query it is being used on doesn't load donors.
end

N + 1查詢的解決方案通常包括急于加載相關(guān)記錄并確保在初始查詢中將其提取:

Donation.all.includes(:donor)

我如何將軟件系統(tǒng)的性能提高35,000%

> Finding the hidden N+1 queries reduced our response times, sometimes drastically.

 

低效的代碼

在代碼中有很多實(shí)例,它們?cè)诓恍枰獣r(shí)執(zhí)行資源密集型的工作。

轉(zhuǎn)向更快的庫

一些可用的庫非常慢。

對(duì)于序列化,在序列化較大的集合時(shí),使用更快的庫(例如oj)可以大大提高性能。

流媒體

我們處理了很多Excel電子表格以及其他批量數(shù)據(jù)報(bào)告和上傳。 最初編寫了大量代碼,首先將整個(gè)電子表格加載到內(nèi)存中,然后對(duì)其進(jìn)行操作,這可能會(huì)占用大量時(shí)間,CPU和內(nèi)存。

許多先前的現(xiàn)有代碼試圖在不真正了解手頭問題的情況下變得聰明和優(yōu)化。 這些解決方案通常可以通過將整個(gè)工作表加載到內(nèi)存中并將其推入內(nèi)存高速緩存中來工作,這會(huì)導(dǎo)致重大問題,因?yàn)楣ぷ鞅砣栽趦?nèi)存中。 它解決了使問題惡化的癥狀,而不是原因。

我不得不重寫大量代碼及其算法來支持流傳輸,以最大程度地減少內(nèi)存和CPU占用空間。 這樣一來,算法和代碼就不必加載整個(gè)電子表格,這對(duì)加快處理速度具有重要作用。

將集合遍歷移動(dòng)到數(shù)據(jù)庫

當(dāng)數(shù)據(jù)庫可以輕松地處理它時(shí),有很多代碼可以在應(yīng)用程序中執(zhí)行操作。 示例包括遍歷數(shù)千條記錄以添加一些內(nèi)容,而不是計(jì)算數(shù)據(jù)庫中的總和,或者急于加載整個(gè)文檔以訪問單個(gè)字段。

我進(jìn)行的一個(gè)特定代碼優(yōu)化涉及用一個(gè)總數(shù)據(jù)庫查詢替換耗時(shí)數(shù)秒并運(yùn)行多個(gè)查詢的長時(shí)間運(yùn)行的計(jì)算。

有問題的查詢是拉出每一個(gè)捐贈(zèng)的用戶,遍歷每條記錄,從該用戶那里拉相關(guān)的標(biāo)簽(例如,"學(xué)生","校友"等),將它們?nèi)亢喜ⅲ缓鬁p少結(jié)果 放入一組不同的標(biāo)簽中

它看起來像下面的樣子:

def get_unique_tags 
  all_tags = [] 
  @cause.donations.each{ 
    |donation| donation.cause.account.tags.each{ 
      |cause_tag| all_tags << tag if donation.tags.include?(tag.value) 
    } 
  } 
  unique_tags = [] 
  all_tags.each{ 
    |tag| unique_tags << tag unless unique_tags.include?(tag) 
  }
end

隱藏在廣告系列頁面呈現(xiàn)生命周期最深處的此代碼在每次單個(gè)請(qǐng)求時(shí)都被調(diào)用。

我如何將軟件系統(tǒng)的性能提高35,000%

> Much of the page time spent loading the campaign page was spent in the database (brown).

 

對(duì)于只有幾個(gè)標(biāo)簽的較小捐贈(zèng)天數(shù),這不是問題,也絕不是問題。 但是,那一年的新情況是,我們的一些大客戶在捐贈(zèng)當(dāng)天上傳了成千上萬個(gè)不同的標(biāo)簽。

我將該邏輯移到單個(gè)聚合查詢中,如下所示,結(jié)果是瞬時(shí)的:

我如何將軟件系統(tǒng)的性能提高35,000%

> A code optimization I did reduced the load time of most campaign pages to 447ms, down from 2500ms.

 

背景

有些事情不需要立即在網(wǎng)絡(luò)請(qǐng)求中發(fā)生-諸如發(fā)送電子郵件之類的事情可能會(huì)延遲幾秒鐘,或者完全由系統(tǒng)的其他部分處理。

這被稱為"背景",它會(huì)移動(dòng)本應(yīng)逐步執(zhí)行的操作并使它們平行。

如果您可以使請(qǐng)求周期的一部分異步進(jìn)行,則意味著響應(yīng)將更快地返回給用戶,從而減少了使用的資源。

我提供了對(duì)核心生命周期無關(guān)緊要的所有內(nèi)容的背景信息:電子郵件發(fā)送,上傳,報(bào)告生成等。

資產(chǎn)最小化

事實(shí)證明,我們的許多前端資產(chǎn)并未經(jīng)過壓縮或優(yōu)化。 這是一個(gè)相當(dāng)容易的更改,將這些資產(chǎn)的加載時(shí)間縮短了多達(dá)70%。

我們有一個(gè)部署腳本,可以將前端資產(chǎn)推送到AWS S3。 我要做的就是生成并上傳壓縮的壓縮版本,同時(shí)告訴S3通過設(shè)置內(nèi)容編碼和內(nèi)容類型來提供gzip。

如下所示的Webpack配置將執(zhí)行此操作:

plugins.push(new CompressionPlugin({ 
  test: /.(js|css)$/,}));
  let s3Plugin = new S3Plugin({ 
    s3Options: { 
      accessKeyId: <ACCESS_KEY_ID>, 
      secretAccessKey: <SECRET_ACCESS_KEY>, 
      region: <REGION> 
    }, 
    s3UploadOptions: { 
      Bucket: <BUCKET>, 
      asset: '[path][query]', 
      ContentEncoding(fileName) { 
    		if (/.gz/.test(fileName)) { return 'gzip' } 
			}, ContentType(fileName) { 
        if (/.css/.test(fileName)) { return 'text/css' } 
        if (/.js/.test(fileName)) { return 'text/JAVAscript' } 
      } 
	},
});
plugins.push(s3Plugin);

內(nèi)存泄漏

我花了大量時(shí)間來尋找內(nèi)存泄漏,當(dāng)我們開始使用交換內(nèi)存時(shí),內(nèi)存泄漏極大地降低了性能(詛咒您,R14錯(cuò)誤)。

在尋找導(dǎo)致泄漏的實(shí)際原因時(shí),我們做了傳統(tǒng)的"以特定頻率重啟服務(wù)器"創(chuàng)可貼。 我積極地調(diào)整了設(shè)置:我們更改了垃圾收集時(shí)間,交換了序列化程序庫,甚至將ruby垃圾收集器更改為jemalloc

內(nèi)存泄漏的主題完全是一篇文章,但是這里有兩個(gè)非常有用的鏈接可以節(jié)省您的時(shí)間和精力:

· 我如何花兩周的時(shí)間來尋找Ruby中的內(nèi)存泄漏

· 使用jemalloc改善ruby應(yīng)用程序的內(nèi)存使用率和性能

代管

我們使用的某些服務(wù)所關(guān)注的區(qū)域與服務(wù)器所在的區(qū)域不同。

我們的服務(wù)器位于弗吉尼亞北部(us-east-2),但某些服務(wù)(例如S3)位于俄勒岡州(us-west-2)。 當(dāng)執(zhí)行許多操作的工作流必須與該服務(wù)進(jìn)行通信時(shí),所產(chǎn)生的延遲會(huì)迅速加起來。

這里的幾個(gè)MS和幾個(gè)MS可以快速累加起來。 通過確保我們的服務(wù)位于同一區(qū)域,我們消除了不必要的延遲,從而極大地加快了查詢和操作的速度。

帕累托再次罷工

上面的部分說明了我為提高性能而使用的各種性能杠桿。 但是,我很快發(fā)現(xiàn),它們是低落的果實(shí)。

調(diào)整和拉動(dòng)杠桿可以顯著提高性能和穩(wěn)定性,但很快就可以看出,系統(tǒng)的單個(gè)部分負(fù)責(zé)絕大部分的性能,穩(wěn)定性和擴(kuò)展性問題。 這完全是80/20規(guī)則。

這是瓶頸。 這是我的白鯨。

停機(jī)時(shí)間剖析

我加入公司后不久,就在一天結(jié)束的那一天,我們突然收到了來自客戶成功團(tuán)隊(duì)的大量錯(cuò)誤警報(bào)和瘋狂消息。

SOS很清楚:該站點(diǎn)已關(guān)閉且無法使用。

我如何將軟件系統(tǒng)的性能提高35,000%

> The pale green section is request queuing time.

 

上圖說明了發(fā)生的情況-負(fù)載顯著增加,導(dǎo)致該站點(diǎn)長時(shí)間無法使用。

隨著數(shù)據(jù)庫使用率的增加(黃色區(qū)域),每個(gè)請(qǐng)求處理的時(shí)間也增加了,導(dǎo)致其他請(qǐng)求開始備份和排隊(duì)(淺綠色區(qū)域)。

令人印象深刻的是停機(jī)時(shí)間的速度。 事情非常非常迅速地備份。 白天所有信號(hào)都很好,然后服務(wù)器突然不堪重負(fù)。

過時(shí)的事件響應(yīng)手冊(cè)

當(dāng)時(shí)我們執(zhí)行了她的標(biāo)準(zhǔn)操作程序,這是啟動(dòng)更多服務(wù)器。

不幸的是,它的影響為零,因?yàn)榇罅康挠?jì)算都延遲了所有Web請(qǐng)求,因此增加應(yīng)用服務(wù)器的數(shù)量并不能解決問題。

與直覺相反,這實(shí)際上使問題變得更糟-向服務(wù)器提供更多請(qǐng)求使數(shù)據(jù)庫承受更大壓力。

是什么原因造成的?

發(fā)生了什么? 我們有一個(gè)緩存系統(tǒng),從所有方面來看,它都運(yùn)行良好。

深入研究,我發(fā)現(xiàn)如何實(shí)現(xiàn)緩存存在多個(gè)明顯的問題。 大量的漏洞使緩存系統(tǒng)成為整個(gè)平臺(tái)的單點(diǎn)故障。

緩存為王

讓我們深入研究我們的緩存系統(tǒng)如何工作。

class Campaign 
cache_fields :
first_name, 
:total_raiseddef total_raised #
 ...complex calculation here endend

cache_fields將調(diào)用一個(gè)混合函數(shù),該函數(shù)將對(duì)屬性的訪問包裝在一個(gè)函數(shù)中,該函數(shù)將在嘗試訪問屬性(或函數(shù)結(jié)果)之前先查看緩存。

但是,如果由于某種原因或其他原因在redis緩存中不存在值,會(huì)發(fā)生什么情況?

處理緩存未命中

像所有高速緩存未命中一樣,它將嘗試實(shí)時(shí)重新計(jì)算該值并提供它,將新計(jì)算的值保存到高速緩存中。

但是,這有一些問題。 如果存在緩存丟失,請(qǐng)求將在高負(fù)載時(shí)間內(nèi)強(qiáng)制執(zhí)行資源密集型計(jì)算。

很明顯,以前的開發(fā)人員曾考慮過這一點(diǎn)-代碼已經(jīng)嘗試過一種解決方案:計(jì)劃緩存。

按計(jì)劃緩存

每5分鐘將運(yùn)行CacheUpdateJob,它將更新所有設(shè)置為要緩存的字段。

該緩存系統(tǒng)在理論上運(yùn)行良好-通過定期緩存,該系統(tǒng)可以將內(nèi)容保留在緩存中。

但是,它在實(shí)踐中存在很多問題,我們?cè)趲滋斓姆瞰I(xiàn)中發(fā)現(xiàn)了這些問題。

緩存更新

問題的主要原因是緩存的填充和更新時(shí)間。

CacheUpdateJob將每5分鐘運(yùn)行一次,以盡責(zé)的方式計(jì)算值,并自計(jì)算之時(shí)起設(shè)置5分鐘的到期時(shí)間。

這是一個(gè)隱藏的問題。 從本質(zhì)上講,它保證了CacheUpdateJob始終僅在值從高速緩存中丟失后才進(jìn)行更新。

緩存未命中

當(dāng)用戶嘗試在某個(gè)值從緩存中調(diào)出之后但在CacheUpdateJob可以緩存新值之前嘗試訪問該值時(shí),將導(dǎo)致緩存未命中,從而導(dǎo)致實(shí)時(shí)計(jì)算該值。

對(duì)于少量的人來說,這是可以接受的,但是在主要的捐贈(zèng)日,它將為每個(gè)請(qǐng)求執(zhí)行重新計(jì)算。

我如何將軟件系統(tǒng)的性能提高35,000%

> Cache failures led to increased 500 Internal Server Error responses — a result of timeouts.

 

發(fā)生高速緩存未命中之后,直到任何一個(gè)請(qǐng)求成功完成并成功將值插入高速緩存為止,所有訪問該數(shù)據(jù)的請(qǐng)求都將執(zhí)行資源密集型查詢,從而大大提高了使用率,尤其是在數(shù)據(jù)庫CPU上 。

對(duì)于需要大量計(jì)算的值,這意味著它可以快速阻塞數(shù)據(jù)庫的資源:

我如何將軟件系統(tǒng)的性能提高35,000%

> When multiple cache misses occurred, the database could get overwhelmed quickly.

 

然后,用戶的行為使問題更加復(fù)雜,并使整個(gè)問題變得更加糟糕。 當(dāng)用戶遇到延遲時(shí),他們將刷新頁面并重試,從而導(dǎo)致更多的額外負(fù)載:

我如何將軟件系統(tǒng)的性能提高35,000%

> Long-running database queries retried repeatedly caused us to lose our ability to read from the da

 

解決方案的前三分之一-垂直縮放

我實(shí)施的首批解決方案之一是垂直擴(kuò)展—改進(jìn)了數(shù)據(jù)庫的資源配置。

擴(kuò)展數(shù)據(jù)庫只是解決該問題的一個(gè)臨時(shí)工具。 在負(fù)載增加的某個(gè)時(shí)刻,我們將再次遇到此問題。

這也是一個(gè)昂貴的解決方案-花數(shù)千美元垂直擴(kuò)展數(shù)據(jù)庫集群并不是一個(gè)合理的支出。

解決方案的第二個(gè)三分之一-水平縮放

我們有一個(gè)數(shù)據(jù)庫集群,其中沒有以任何方式使用只讀副本。 我們可以轉(zhuǎn)換長期運(yùn)行的報(bào)表和其他對(duì)時(shí)間敏感的查詢,以便在只讀副本而不是主副本上運(yùn)行,從而將負(fù)載分布在整個(gè)集群上,而不是只分布在整個(gè)集群上。

解決方案的最后三分之一-防止比賽條件

我們需要一種方法,通過防止系統(tǒng)一次又一次地重新計(jì)算相同的精確數(shù)據(jù)來防止系統(tǒng)過載。

我解決了這一問題,方法是添加了在多個(gè)請(qǐng)求同時(shí)請(qǐng)求重新生成緩存時(shí)返回陳舊數(shù)據(jù)的功能。

只有一個(gè)請(qǐng)求會(huì)導(dǎo)致重新計(jì)算,其余請(qǐng)求將處理過時(shí)的數(shù)據(jù),直到完成該計(jì)算,而不是一遍又一遍地觸發(fā)相同的計(jì)算。

Rails通過race_condition_ttl和expires_in參數(shù)的組合來支持這一點(diǎn):

Rails.cache.fetch(cache_key, race_condition_ttl: 30.seconds, expires_in: 15.minutes)

火車不準(zhǔn)時(shí)

隨著我們成功的成長,我們進(jìn)行的競(jìng)選活動(dòng)也增加了。 反過來,這使CacheUpdateJob花費(fèi)的時(shí)間越來越長,才能遍歷數(shù)千個(gè)廣告系列。

有一天,我收到了團(tuán)隊(duì)遇到的潛在錯(cuò)誤的通知。 他們已經(jīng)在幾個(gè)小時(shí)前將電子郵件排隊(duì),但沒有人收到。 我檢查并意識(shí)到,傳統(tǒng)上只有幾個(gè)作業(yè)的隊(duì)列中有成千上萬的作業(yè)-所有CacheUpdateJob。

調(diào)查進(jìn)一步表明發(fā)生了什么事。 CacheUpdateJob達(dá)到了這樣的程度,即作業(yè)的運(yùn)行時(shí)間要比其運(yùn)行的時(shí)間長。

這意味著,盡管CacheUpdateJob每5分鐘運(yùn)行一次,但要花費(fèi)10多分鐘才能完成。 在此期間,紙從緩存中丟失,并且作業(yè)在隊(duì)列中堆積。 這也意味著CacheUpdateJob一直在運(yùn)行,并收取相當(dāng)可觀的使用費(fèi)。

這阻止了所有其他工作的進(jìn)行。

分成多個(gè)隊(duì)列

這里的解決方案是將我們擁有的各種作業(yè)分成多個(gè)隊(duì)列,我們可以獨(dú)立擴(kuò)展。

郵件程序和其他用戶觸發(fā)的批量作業(yè)被放在一個(gè)隊(duì)列中。 事務(wù)性工作被放置在另一個(gè)中。 昂貴的報(bào)告作業(yè)被放置在第三個(gè)隊(duì)列中。 使系統(tǒng)保持運(yùn)行狀態(tài)的作業(yè)(例如CacheUpdateJob)被放置在資源豐富的隊(duì)列中。

這有助于確保任何一個(gè)隊(duì)列中的備份不會(huì)對(duì)系統(tǒng)的其余部分造成很大的影響,并且使我們能夠在緊急情況下關(guān)閉系統(tǒng)中不需要的部分。

將觸發(fā)器與執(zhí)行分開

我們進(jìn)行的其他更改之一是確保CacheUpdateJob本身不會(huì)完成工作,并將此職責(zé)轉(zhuǎn)移給它排隊(duì)的其他作業(yè)。 這也使我們能夠在排隊(duì)之前檢查重復(fù)作業(yè)的存在。 如果我們已經(jīng)為某個(gè)廣告系列排隊(duì)等待緩存更新,則沒有必要在隊(duì)列中添加第二個(gè)作業(yè)以緩存同一廣告系列。

這確保了我們可以與觸發(fā)緩存更新的事物并行化并獨(dú)立擴(kuò)展緩存更新的處理,并以最佳方式進(jìn)行。

在需要的地方分批

我意識(shí)到,拆分成單個(gè)工作的開銷抵消了最初將它們拆分出來的一些好處。

我們實(shí)施了批處理,以便CacheUpdateJob不會(huì)為每條記錄創(chuàng)建一個(gè)新作業(yè),而是將記錄分為約100個(gè)左右的可自定義組。 這確保了批次較小且可以快速完成,同時(shí)仍為我們提供了所需的分離功能。

僅緩存所需的內(nèi)容

我們還查看了CacheUpdateJob,發(fā)現(xiàn)它正在不加區(qū)別地更新緩存-甚至緩存了幾年前運(yùn)行的活動(dòng)。

我創(chuàng)建了一個(gè)設(shè)置機(jī)制,使我們可以確定每個(gè)廣告系列緩存內(nèi)容的頻率。

對(duì)于不經(jīng)常訪問的舊版廣告系列,我們無需費(fèi)心更新這些值。 對(duì)于那些每天運(yùn)行活躍的日子,我們更新的頻率更高,并且它們具有更高的緩存優(yōu)先級(jí)。

內(nèi)存不足

當(dāng)我們付出很多天時(shí),我們開始看到越來越多的企業(yè)成功。 業(yè)務(wù)量的增加意味著以前可以接受的內(nèi)存分配突然達(dá)到了極限。

這意味著在某個(gè)時(shí)候,我們會(huì)突然開始發(fā)現(xiàn)我們無法將項(xiàng)目添加到緩存中而導(dǎo)致整個(gè)卡片卡癱瘓的能力出現(xiàn)了故障。

主要搬遷

我們確定了原因之一-我們的緩存服務(wù)器配置不正確。

我們的主要逐出過程設(shè)置為永不撤離,并且在達(dá)到內(nèi)存時(shí)拋出錯(cuò)誤。 這就是導(dǎo)致我們?cè)谪?fù)載增加的情況下達(dá)到內(nèi)存限制的原因。

解決方案看起來很簡(jiǎn)單-將Redis緩存服務(wù)器上的密鑰逐出設(shè)置為volatile-lru。 從理論上講,這將確保只有帶有TTL的鍵才會(huì)引起問題。

如果真那么容易就好了

這帶來了系統(tǒng)從未設(shè)計(jì)過的其他挑戰(zhàn)。 我們有很多值依賴于其他值進(jìn)行重新計(jì)算,這些值又被用于計(jì)算其他值。

因?yàn)榫彺媸桥R時(shí)構(gòu)建的,而且是偶然的,所以這些項(xiàng)目中的一些預(yù)計(jì)會(huì)被緩存,而其他則不會(huì),并且它們都有不同的TTL。

收回一段時(shí)間未使用的密鑰的行為可能會(huì)觸發(fā)一系列的再生故障,從而使系統(tǒng)癱瘓。

我們有一個(gè)難題:

· 我們需要逐出密鑰,以確保不會(huì)耗盡內(nèi)存

· 如果我們收回任意密鑰,將導(dǎo)致值再生失敗

· 從架構(gòu)上講,我們無法過渡到這些查詢

· 我們受到運(yùn)營成本的限制,因此我們無法擴(kuò)展$

這個(gè)看似棘手的問題雖然簡(jiǎn)單易懂,卻有一個(gè)簡(jiǎn)單的解決方案。

后備緩存

我在數(shù)據(jù)庫層實(shí)現(xiàn)了后備緩存。

對(duì)于我們通過cache_fields緩存的每個(gè)字段,我們還添加了隨附的時(shí)間戳和緩存值:

cache_fields :total_raised

每當(dāng)更新緩存的字段時(shí),cache_fields函數(shù)將創(chuàng)建并更新兩個(gè)額外的屬性:

· cached_total_raised

· cached_timestamp_total_raised

每當(dāng)在Redis緩存中找不到該值時(shí),它將使用存儲(chǔ)在數(shù)據(jù)庫中的值,該值永遠(yuǎn)不會(huì)過期。 所得的提取速度比從Redis提取的速度慢,但比重新計(jì)算的速度快得多。

如果數(shù)據(jù)庫中沒有緩存的值,它將重新計(jì)算該值。

這確保了幾乎在每種情況下,緩存值都以一種或另一種形式存在,從而阻止了計(jì)算的運(yùn)行,除非該值由CacheUpdateJob強(qiáng)制更新或由客戶成功團(tuán)隊(duì)要求手動(dòng)更新。

陳舊的緩存

所有這些緩存都導(dǎo)致了一個(gè)問題-我們經(jīng)常會(huì)遇到陳舊且不再準(zhǔn)確的舊數(shù)據(jù)。 我們通常不知道其緩存在什么級(jí)別。

一個(gè)小例子

我們遇到的情況將向您顯示一些后果。

Account.find('12345a').campaigns.limit(10)

Account.find('12345a').campaigns.limit(20)

由于我只能將其描述為過于激進(jìn)的查詢緩存或ORM中的錯(cuò)誤,因此如果連續(xù)運(yùn)行,上述命令將返回相同的結(jié)果。

如果您之后立即執(zhí)行以下操作,您將獲得更多有趣的結(jié)果:

Account.find('12345a').campaigns.limit(20).count

Account.find('12345a').campaigns.limit(20).to_a.length

奇怪的是,#count將返回20,但是#to_a將返回10。

它帶來了可怕的用戶體驗(yàn)

從用戶體驗(yàn)的角度來看,這是不可接受的。 人們進(jìn)行捐贈(zèng)時(shí),他們希望能夠立即在總金額中看到新的捐贈(zèng)。 他們不認(rèn)為"哦,這個(gè)系統(tǒng)一定已經(jīng)緩存了以前的值。"

同樣,緩存必須足夠頻繁地更新以跟蹤籌款活動(dòng)的進(jìn)度。 客戶成功管理團(tuán)隊(duì)每天與客戶保持密切聯(lián)系,并且必須提供進(jìn)度報(bào)告。 如果報(bào)告已過時(shí),他們將無法做到這一點(diǎn)。

它造成了一些非常嚴(yán)重的潛在錯(cuò)誤

想象一下,如果要對(duì)集合進(jìn)行范圍界定以進(jìn)行批量刪除。 您以為您要?jiǎng)h除20條記錄,但實(shí)際上是在刪除類似查詢返回的先前的記錄集。

這就是噩夢(mèng),我希望您擁有良好的備份和審核表。

解決方案—緩存清除工具

我構(gòu)建了多個(gè)工具,客戶成功可使用這些工具來強(qiáng)制在特定隊(duì)列上進(jìn)行緩存刷新。 這樣可以確保每當(dāng)需要最新數(shù)據(jù)時(shí),他們就可以擁有它們。

通過將緩存的屬性訪問器更改為接受并使用一組可選參數(shù),我現(xiàn)在可以在需要的任何時(shí)候強(qiáng)制刷新緩存:

@campaign.total_raised(force_refresh: true)

在對(duì)新鮮度敏感的操作中,這將確保每次都處理正確類型的數(shù)據(jù)。

我還確保關(guān)鍵報(bào)告之類的功能使用了較薄的緩存層,并盡可能地利用了最新數(shù)據(jù)。

最終結(jié)果

我如何將軟件系統(tǒng)的性能提高35,000%

 

在所有優(yōu)化的最后,我們有了一個(gè)系統(tǒng),可以處理我們預(yù)期的下一個(gè)數(shù)量級(jí)的負(fù)載-每秒2000個(gè)以上的請(qǐng)求,數(shù)千個(gè)并發(fā)活動(dòng)。 大多數(shù)面向捐助者的端點(diǎn)的加載時(shí)間均少于50ms,而面向客戶頁面的加載時(shí)間則在300ms之內(nèi)。

這是一段漫長的旅程,進(jìn)行了許多高壓部署,但最終結(jié)果不言而喻。 最終,我們有了一個(gè)在贈(zèng)予日中可以忽略的系統(tǒng)-大部分情況下。

(本文翻譯自Joseph Gefroh的文章《How I Scaled a Software System's Performance By 35,000%》,參考:https://medium.com/swlh/how-i-scaled-a-software-systems-performance-by-35-000-6dacd63732df)

分享到:
標(biāo)簽:軟件系統(tǒng)
用戶無頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定