某618大促專案的覆盤總結
一、前言
618期間上線一個活動專案。但上線不順利,當天就出現了效能問題,介面超時,使用者無法開啟網頁,最後不得的臨時下線。花了三天兩夜,重構了後臺核心程式碼,才讓活動進行下去。
回頭看了一下自己的時間記錄,從5月31號那天晚上8點25分開始準備上線,發現異常,分析原因,重構程式碼,離開公司時已經是6月2號的23點54,經歷51小時29分,中間的睡眠時間不到5個小時,這已經是爆發小宇宙了。
這一波剛過去了,一波未平另一波又起,由於活動的獎勵豐厚,大批羊毛黨聞風而至,某寶上公開賣指令碼的都有了,嚴重影響了正常使用者薅羊毛。
某客戶反饋說:我們別說薅羊毛了,現在是整頭羊都被他們牽走了!
接下來的幾天,又得和薅羊毛的指令碼們鬥智鬥勇,直到活動結束。
而本文就對此做一次深度的覆盤,在以後的專案中讓自己快活一點。
二、一份看似完美的專案總結
當我們覆盤專案過程時,能找到很多問題點,比如:
人力不足,需求過於複雜,開發和測試工作量大。
前後端開發、測試都是從其他團隊抽掉的,對當前專案的業務和技術不熟悉。
跨團隊組建的臨時團隊,職責定義不清晰,專案管控不嚴格。
開發對專案的用到的技術不熟悉,沒有經過原有專案成員的CodeReview。
測試通過太草率,壓測方案設計不合理。
....
列出問題後,很快就能一一寫出改進點。
從公司層面加強的整體專案安排,避免重複玩法的專案,資源投入到重點的幾個活動中。
加強團隊的能力培養,總結文件,供新人學習。
對於核心程式碼進行CodeReview,遇到問題時,專案經理協調資深開發協助解決。
將臨時組建團隊職責定義清晰,各負責人溝通清楚。
嚴格控制測試質量,測試有上線的否決權。
...
這些總結看起來一點問題沒有,列出了問題,也列出了改進點,甚至可以當成樣板去使用了,是不是咱們就這麼結束了呢。
當然不是, 它本身的說法沒有錯,錯在把問題的前提當作問題的原因。
我們來看兩種表述。
下次我們要組建一個經驗豐富的專案團隊,避免質量問題發生。
當下次我們面臨一個臨時組建,經驗不足的專案團隊時,如何避免質量問題發生。
這兩種表述的差異在哪?
前一種表述是因為我們“團隊”的原因,導致了本次質量問題,所以我們要解決“團隊”的問題。
而後一種是我們的團隊就是臨時組建的,我們的開發、測試就是對新專案的業務和技術不熟悉,在這個前提下,才會出現質量問題,那麼在這個前提下,怎麼避免質量問題呢?
臨時組建,經驗不足不是問題的原因,它們是出現問題的前提,這是客觀存在的。
這就好比我們說解決一個問題時,最快的方式是,我們不解決問題,解決出問題的人就行了。
在這裡不就變成了,我們不解決問題,解決出問題的團隊就行了。
正是因為這個誤區,我們很多時候一出現專案質量問題,就把鍋甩給我們團隊的協作有問題,或者我們的專案時間緊張,然後一句下次改進就結束了。
這樣的萬能回答,看似一點沒錯,但往往就沒法落地了。
明明專案時間緊,新團隊協作經驗不足本來就客觀的存在,沒有它就沒有問題,怎麼可以當作問題本身給解決掉呢。
1、質量問題的關鍵原因
帶著這個前提,我們再回頭看前面的總結,其實就能過濾出真正有價值的點了。
我們也可以這麼問,問題是不能避免的,但為什麼在專案過程中我們的效能問題沒有暴露出來?
三個角度:
從專案角度,沒有嚴格按專案流程來,特別是最後測試任務緊張,bug較多時,趕工給出了測試報告。
從開發角度,沒有找熟悉業務和技術的同學做CodeReview。
從測試角度,壓測方案設計不合理,不符合真實場景。
逐一分析下。
前面提到事故是後臺的效能問題,從專案角度,就算流程嚴謹也沒法暴露出效能問題,特別是在專案過程中,已暴露的風險是前端人力不足,中間加了人手,從功能的角度,後端進度完全正常。
再看開發角度,這裡我沒有提開發的經驗不足,不是在推脫責任,這同我們作為一個臨時團隊對業務的經驗不足一樣,它是一個客觀存在的前提。當你接觸新專案,使用新技術時,經驗不足是肯定存在的。
問題是在自身經驗不足時,如何去完成任務,那麼和熟悉業務和技術的同學做CodeReview是主要的手段。
再從測試角度,功能測試是沒有問題的,但跟效能相關的壓測方案是有問題的,並且一開始就沒有引起正視。最開始的壓測方案是開發只出介面和引數文件,直接丟給測試去壓,現在看來,這是錯誤的。
因此,這次質量問題的關鍵總結如下。
當下次我們面臨一個臨時組建,經驗不足的專案團隊時,面對大流量的業務需求,開發們需要注意:
讓熟悉業務和技術的同學幫忙做CodeReview。
設計出符合業務場景的壓測方案。
這兩點就可以落地了,這也不是說專案管理上沒有改進的,而是優先保證這兩點,能更有效的降低風險。
CodeReview的技巧這裡就不多少說,來談談我們做的幾次壓測方案的改進。
2、三輪的壓測改進
單使用者,單介面,雙機壓測
隨機使用者,多介面,全量壓測
隨機使用者,功能分組介面,全量壓測
最開始壓測方案是用一個使用者,兩臺伺服器,一個快取分片做壓測,然後簡單的用伺服器QPS的均值乘以線上部署機器數量當作壓測結果。
這個方案如果是下圖左側的場景,呼叫鏈路上的伺服器可以同時彈性擴充套件自然是可以的。
但要是右側的場景,呼叫鏈路上存在瓶頸,比如資料庫是一個節點,並且無法擴充套件,那就問題了。
同樣的,這次專案的問題就是Redis成為了一個單節點的瓶頸。另外由於使用者id是固定的,所以快取很可能被重複使用,這樣就難以測試到頻繁建立快取的場景。
在系統重構後,改進了一種壓測方案,通過隨機使用者Id,批量輪詢介面,並且通過測試環境的彈性擴充套件,完全模擬線上的部署環境。
還通過加降級開關,把入參合法性、風控、時效性校驗等臨時關閉,以便能讓壓測的請求貫穿整個主流程。
接著在這一方案的基礎上,通過對介面分組和偽造恰當的資料,編寫貼近真實的呼叫行為的指令碼,再次做了壓測。
在執行人員上,也經歷了從開發提供資料,測試全權負責;到測試主導,開發參與;再到開發主導,測試協助的過程。
由此,壓測方案就越來越貼近真實場景,壓測結論自然就更加可信 。
3、高併發場景下的設計
前面談到了系統設計的不合理導致了本次效能問題,來分析下這裡面的根本原因。
首先要理解的是,Redis叢集是由多個分片構成的,一條資料被寫到哪個分片裡,是由key的hash值來離散的。
比如說,我們要在Redis裡面快取一批使用者資訊,並且能通過ID來存取。
如果用Redis自帶的Hash表結構寫法如下:
存:redis.hset("userMap",ID,userInfo)
讀:redis.hget("userMap",ID)
那麼,因為key是固定的userMap,這意味著所有的使用者資訊都會被寫到一個分片裡。
而對於通常的分散式系統的設計,一個基本原則是:讓流量儘可能的被叢集的機器平攤。
固定的key就無法利用分散式的優勢了,並且如果併發量高,這就會讓一個分片去抗所有的流量,再加上如果使用者量數十萬,還有一次性讀取所有資料的操作,這樣就變成一場災難了。
實際設計時,直接把整個Redis叢集當作一個Hash表的方式更加高效。
存:redis.set("userMap"+ID,userInfo)
讀:redis.get("userMap"+ID)
這裡的key="userMap"+ID,ID不同key就被離散了,請求會叢集平攤,從而充分發揮分散式系統的效能。
三、黑產和羊毛黨的問題
在專案上線後另一個沒重視的問題出現了,那就是大量的黑產和羊毛黨出現,活動獎勵全被這些用指令碼的人佔據了。
對黑產的事前考慮太少了,僅做了簡單的風控校驗,根本檢測不足異常使用者,導致黑產可以通過指令碼大量刷介面。
這裡的經驗有兩點:
對包含現金、現金等價物或高價值獎勵的活動,要有面對黑產的心理預期。
在大公司,專業的事情找專業的人做,基於業務場景,提前跟風控團隊溝通好。
對於第一點,基本上只要值點錢的活動,黑產肯定跑不了,空手套白狼,搶到就是賺到,不妨想想如果你是黑產,結合下業務場景,你會怎麼來刷自己的系統。
基於第一點,公司沒有風控團隊那就只能自己做了,而一般上點規模的公司都有自己的風控團隊,利用好現成資源。
風控主要考慮兩方面:
有風控團隊的,接入他們的通用風控模型。
針對專案的業務場景,定製化一些風控模型。
通用風控模型基本是通過新老賬號、異地登入、人機識別等等使用者行為建立的使用者畫像,通過離線計算和實時校驗來處理。
定製化模型視情況而定,比如拉一個單獨的小黑戶,放進去的使用者不能參與這個活動等等。
被攔截的使用者一般是走驗證碼或直接拉黑,對於後者,別忘了和客服的妹子們打好招呼,準備下話術應對客訴。
四、結語
最後總結下專案的經驗。
首先是前提:
當你的面前的是一個臨時組建,對現在專案經驗不足的專案團隊時。
當你面臨一個大流量,包含現金或等價物的活動時。
請務必做好這三點:
找熟悉本專案的業務和技術的開發參與方案的設計和CodeReview。
請開發主動參與壓測任務,設計壓測方案,注意儘可能模擬真實場景。
做好應對黑產的心理準備,直到大促活動結束。
來自於,一個連續加班51小時29分,被使用者吐槽整隻羊都被人家牽走了的開發。