先簡單介紹一下我的經(jīng)歷虱黄,最早在學(xué)校的時候,是在社團(tuán)里寫php和Java赴捞,創(chuàng)業(yè)時期寫js逼裆,oc和Ruby,現(xiàn)在是全職用Rails寫后端了赦政。
項目簡介
我們的主要業(yè)務(wù)有兩塊胜宇,社區(qū)和電商
整體業(yè)務(wù)的峰值qps大概在3000,也算是pv過10億的站點(diǎn)了恢着,后端team有4個人桐愉,除了一個八年老司機(jī),其他人參加工作的年限都不是太久掰派。
我們面對的是一個巨大的基于Rails的歷史遺留系統(tǒng)从诲,最早的開發(fā)成員均已離開,導(dǎo)致我們常常面對遺留代碼一臉蒙逼靡羡,到處是沒有人知道的邏輯系洛,丑陋的實(shí)現(xiàn),以及很多性能跟不上的接口略步。
與巨石應(yīng)用的斗爭
日常工作的重中之重描扯,就是與這個monolith的戰(zhàn)斗!
性能篇
以往每年我們搞活動纳像,服務(wù)器都會掛荆烈,經(jīng)濟(jì)損失不少,所以優(yōu)化性能竟趾,保證活動期間的訪問是第一要務(wù)憔购。
原來的活動整體設(shè)計還是比較科學(xué)的,活動頁面本身是靜態(tài)化托管到cdn的岔帽,從來沒有出現(xiàn)過問題玫鸟,主要瓶頸是商品詳情頁面。我們利用redis做了三層cache犀勒,解決了這個問題屎飘。第一層是數(shù)據(jù)庫的緩存,直接把商品信息緩存到redis里贾费,避免了頻繁的數(shù)據(jù)庫訪問钦购,第二層是單條數(shù)據(jù)的渲染緩存,可以理解成一小段html褂萧,第三層是整個數(shù)據(jù)集的渲染緩存押桃。第二個瓶頸出現(xiàn)在一些靜態(tài)資源上,全面遷移到云存儲解決导犹。做完這兩件事之后唱凯,上上次活動是我們有史以來第一次羡忘,沒有掛。
就在我們覺得磕昼,優(yōu)化做的不錯的時候卷雕,上次活動卻又掛了。
要知道我們特意買了新服務(wù)器票从,美滋滋覺得這下穩(wěn)了漫雕,沒想到...
上次活動掛的原因有以下幾點(diǎn)
- redis hmget,我們通過gem提供的API纫骑,緩存了一個巨大的省市區(qū)列表蝎亚,但是沒有注意到緩存是分離的,獲取整個列表先馆,其實(shí)就是一條hmget獲取所有獨(dú)立的緩存片段,這個操作block了redis躺彬,導(dǎo)致訪問極度緩慢煤墙。我們緊急把整個列表轉(zhuǎn)成json,直接貼到代碼里返回hotfix了這個問題
- 突然無法通過redis sential進(jìn)行連接宪拥,這套sential系統(tǒng)是由已經(jīng)離職的運(yùn)維搭建的仿野,我們繞開sential直接連接redis,解決了這個問題
- fd limit她君, 做完以上兩點(diǎn)脚作,依然時常502, 發(fā)現(xiàn)運(yùn)維修改的是root用戶的fd數(shù)量...坑爹....
- 在支付回調(diào)中有一段用于統(tǒng)計的sql缔刹,訂單量大了以后slow query球涛,block了數(shù)據(jù)庫,我們直接注釋了這段可有可無的老代碼校镐,解決亿扁。
總結(jié)一下,對于web應(yīng)用的場景來說鸟廓,大都是讀多寫少从祝,緩存讀請求,異步寫請求引谜,是我們經(jīng)常采用的兩種效果不錯的方式牍陌。在數(shù)據(jù)庫層面,對于遺留代碼中效率低下的查詢進(jìn)行重寫员咽,重點(diǎn)改寫了所有N+1查詢毒涧,對一些逐條插入的語句用batch insert合并寫入操作,也有不錯的提升骏融。
替換篇
做的比較有意思的事链嘀,是寫了我們內(nèi)部用的個推GEM萌狂。原來使用的是github上開源的一個GEM,但是已經(jīng)很久沒更新了怀泊,無法適應(yīng)我們的使用需求茫藏。我基于個推最新的HTTPS的API,寫了一個Ruby的包裝霹琼。
這里要吐槽的是個推的技術(shù)水平务傲。推送服務(wù)是做的不錯,但API怎么做的這么low枣申。他們定義了一個叫authorize的http header用來傳遞身份信息...違背了RFC關(guān)于HTTP頭必須大寫開頭的規(guī)范售葡。一些語言的標(biāo)準(zhǔn)庫(Go、Ruby...)會自動幫你把a(bǔ)uthorize轉(zhuǎn)化成Authorize忠藤,導(dǎo)致個推那邊一直返回auth error...而個推的接口又是HTTPS的挟伙,抓包調(diào)試很困難,浪費(fèi)了我很長時間調(diào)試這個問題模孩。
重構(gòu)篇
重構(gòu)的主要方針就是拆分尖阔,盡可能把功能從巨石應(yīng)用中拆出去。如果一時半會難以拆分的榨咐,代碼上也盡可能讓邏輯高度內(nèi)聚介却,方便以后遷移。
消息系統(tǒng)的重構(gòu)
消息系統(tǒng)是一個块茁,出點(diǎn)問題沒什么齿坷,但做得好會非常出彩的功能。我一直覺得数焊,像知乎這種社區(qū)的成功永淌,除了內(nèi)容,很大一部分要?dú)w功于消息的體驗(yàn)昌跌。目前仰禀,我們幾乎所有頁面,都會展示新消息的數(shù)量蚕愤,導(dǎo)致每次請求都會去主數(shù)據(jù)庫的消息表做count答恶,計算各種消息的數(shù)量返回給前端。我正在著手把整個系統(tǒng)遷移到另一個獨(dú)立的數(shù)據(jù)庫萍诱,以后可以作為單獨(dú)的服務(wù)供內(nèi)部調(diào)用悬嗓,降級限流什么的都很方便。
搜索的重構(gòu)
原來的搜索是基于Solr的java工程裕坊,是一個我們內(nèi)部沒人維護(hù)好多年的爛攤子包竹,雖然各方面表現(xiàn)都不錯。我們還是決定未來要用Elasticsearch換掉它。
新系統(tǒng)
我新寫了內(nèi)部的財務(wù)系統(tǒng)周瞎,過程中遇到很多問題苗缩,寫的也很痛苦,但最終效果還是不錯声诸。因?yàn)樵瓉淼母鞣N報表都是直接基于生產(chǎn)數(shù)據(jù)庫的酱讶,對業(yè)務(wù)會有沖擊,新系統(tǒng)寫了一個同步模塊彼乌,可以增量同步訂單數(shù)據(jù)到財務(wù)系統(tǒng)的專用數(shù)據(jù)庫泻肯,這樣就不會對業(yè)務(wù)帶來影響。
遇到的比較大的坑就是內(nèi)存爆炸慰照。有一些耗時計算我放到了消息對列里灶挟,整個worker進(jìn)程的內(nèi)存占用瘋狂上升。最終發(fā)現(xiàn)是Ruby內(nèi)存模型的問題毒租。
我通過時間換空間的方式稚铣,把之前加載全部數(shù)據(jù)做計算,改成了加載部分?jǐn)?shù)據(jù)做計算蝌衔,然后匯總結(jié)果這樣的方式榛泛,極大降低了內(nèi)存占用,并通過每天重啟worker進(jìn)程噩斟,解決了最主要的內(nèi)存問題。
這個項目讓我真實(shí)感覺到孤个,有些場景真的不是Ruby擅長的領(lǐng)域剃允。Ruby的內(nèi)存模型,就是盡量分配對象齐鲤,從不真正回收斥废,只會重用。Ruby VM啟動就有大量空對象等著被分配给郊,假如我加載了很多數(shù)據(jù)牡肉,空對象不夠用了,VM就向操作系統(tǒng)申請一批內(nèi)存淆九,用完后也不釋放统锤,等著下次重用。而報表計算的最佳場景就是能加載大量數(shù)據(jù)炭庙,算一下結(jié)果饲窿,算完釋放掉內(nèi)存。
監(jiān)控
可以看我之前的文章使用ELK構(gòu)建分布式日志分析系統(tǒng)
代碼篇
在日常編碼焕蹄、重構(gòu)的過程中逾雄,經(jīng)常使用的技術(shù)是
- 設(shè)計模式
- 元編程
運(yùn)用設(shè)計模式,寫出符合OOP規(guī)范的代碼。分割每個類的職責(zé)鸦泳,盡量讓各個功能的邏輯內(nèi)聚银锻,只提供彼此間調(diào)用的接口,這是我最近才剛領(lǐng)悟的代碼整潔之道做鹰。
元編程抽象代碼击纬,我很早就在使用的奇技淫巧。現(xiàn)在卻用的越來越少了誊垢,因?yàn)樗`背了OOP掉弛,可維護(hù)性比較差,對使用者的水平有很大要求喂走,也容易坑隊友殃饿。
簡單地說,我代碼中的if/else越來越少了芋肠,類越來越多了乎芳,改動起來越來方便了,改動影響的部分越來少了帖池,美滋滋奈惑。
結(jié)語
用一句古老的名言,軟件開發(fā)沒有銀彈睡汹。