618期間上線一個活動項目。但上線不順利,當天就出現了性能問題,接口超時,用戶沒法打開網頁,最後不得的臨時下線。花了三天兩夜,重構了後臺核心代碼,才讓活動進行下去。前端
回頭看了一下本身的時間記錄,從5月31號那天晚上8點25分開始準備上線,發現異常,分析緣由,重構代碼,離開公司時已是6月2號的23點54,經歷51小時29分,中間的睡眠時間不到5個小時,這已是爆發小宇宙了。redis
這一波剛過去了,一波未平另外一波又起,因爲活動的獎勵豐厚,大批羊毛黨聞風而至,某寶上公開賣腳本的都有了,嚴重影響了正經常使用戶薅羊毛。數據庫
某客戶反饋說:咱們別說薅羊毛了,如今是整頭羊都被他們牽走了!後端
接下來的幾天,又得和薅羊毛的腳本們鬥智鬥勇,直到活動結束。緩存
而本文就對此作一次深度的覆盤,在之後的項目中讓本身快活一點。服務器
當咱們覆盤項目過程時,能找到不少問題點,好比:併發
人力不足,需求過於複雜,開發和測試工做量大。分佈式
先後端開發、測試都是從其餘團隊抽掉的,對當前項目的業務和技術不熟悉。高併發
跨團隊組建的臨時團隊,職責定義不清晰,項目管控不嚴格。性能
開發對項目的用到的技術不熟悉,沒有通過原有項目成員的CodeReview。
測試經過太草率,壓測方案設計不合理。
....
列出問題後,很快就能一一寫出改進點。
從公司層面增強的總體項目安排,避免重複玩法的項目,資源投入到重點的幾個活動中。
增強團隊的能力培養,總結文檔,供新人學習。
對於核心代碼進行CodeReview,遇到問題時,項目經理協調資深開發協助解決。
將臨時組建團隊職責定義清晰,各負責人溝通清楚。
嚴格控制測試質量,測試有上線的否決權。
...
這些總結看起來一點問題沒有,列出了問題,也列出了改進點,甚至能夠當成樣板去使用了,是否是我們就這麼結束了呢。
固然不是, 它自己的說法沒有錯,錯在把問題的前提看成問題的緣由。
咱們來看兩種表述。
下次咱們要組建一個經驗豐富的項目團隊,避免質量問題發生。
當下次咱們面臨一個臨時組建,經驗不足的項目團隊時,如何避免質量問題發生。
這兩種表述的差別在哪?
前一種表述是由於咱們「團隊」的緣由,致使了本次質量問題,因此咱們要解決「團隊」的問題。
然後一種是咱們的團隊就是臨時組建的,咱們的開發、測試就是對新項目的業務和技術不熟悉,在這個前提下,纔會出現質量問題,那麼在這個前提下,怎麼避免質量問題呢?
臨時組建,經驗不足不是問題的緣由,它們是出現問題的前提,這是客觀存在的。
這就比如咱們說解決一個問題時,最快的方式是,咱們不解決問題,解決出問題的人就好了。
在這裏不就變成了,咱們不解決問題,解決出問題的團隊就好了。
正是由於這個誤區,咱們不少時候一出現項目質量問題,就把鍋甩給咱們團隊的協做有問題,或者咱們的項目時間緊張,而後一句下次改進就結束了。
這樣的萬能回答,看似一點沒錯,但每每就無法落地了。
明明項目時間緊,新團隊協做經驗不足原本就客觀的存在,沒有它就沒有問題,怎麼能夠看成問題自己給解決掉呢。
帶着這個前提,咱們再回頭看前面的總結,其實就能過濾出真正有價值的點了。
咱們也能夠這麼問,問題是不能避免的,但爲何在項目過程當中咱們的性能問題沒有暴露出來?
三個角度:
從項目角度,沒有嚴格按項目流程來,特別是最後測試任務緊張,bug較多時,趕工給出了測試報告。
從開發角度,沒有找熟悉業務和技術的同窗作CodeReview。
從測試角度,壓測方案設計不合理,不符合真實場景。
逐一分析下。
前面提到事故是後臺的性能問題,從項目角度,就算流程嚴謹也無法暴露出性能問題,特別是在項目過程當中,已暴露的風險是前端人力不足,中間加了人手,從功能的角度,後端進度徹底正常。
再看開發角度,這裏我沒有提開發的經驗不足,不是在推脫責任,這同咱們做爲一個臨時團隊對業務的經驗不足同樣,它是一個客觀存在的前提。當你接觸新項目,使用新技術時,經驗不足是確定存在的。
問題是在自身經驗不足時,如何去完成任務,那麼和熟悉業務和技術的同窗作CodeReview是主要的手段。
再從測試角度,功能測試是沒有問題的,但跟性能相關的壓測方案是有問題的,而且一開始就沒有引發正視。最開始的壓測方案是開發只出接口和參數文檔,直接丟給測試去壓,如今看來,這是錯誤的。
所以,此次質量問題的關鍵總結以下。
當下次咱們面臨一個臨時組建,經驗不足的項目團隊時,面對大流量的業務需求,開發們須要注意:
讓熟悉業務和技術的同窗幫忙作CodeReview。
設計出符合業務場景的壓測方案。
這兩點就能夠落地了,這也不是說項目管理上沒有改進的,而是優先保證這兩點,能更有效的下降風險。
CodeReview的技巧這裏就很少少說,來談談咱們作的幾回壓測方案的改進。
單用戶,單接口,雙機壓測
隨機用戶,多接口,全量壓測
隨機用戶,功能分組接口,全量壓測
最開始壓測方案是用一個用戶,兩臺服務器,一個緩存分片作壓測,而後簡單的用服務器QPS的均值乘以線上部署機器數量看成壓測結果。
這個方案若是是下圖左側的場景,調用鏈路上的服務器能夠同時彈性擴展天然是能夠的。
但要是右側的場景,調用鏈路上存在瓶頸,好比數據庫是一個節點,而且沒法擴展,那就問題了。
一樣的,此次項目的問題就是Redis成爲了一個單節點的瓶頸。另外因爲用戶id是固定的,因此緩存極可能被重複使用,這樣就難以測試到頻繁建立緩存的場景。
在系統重構後,改進了一種壓測方案,經過隨機用戶Id,批量輪詢接口,而且經過測試環境的彈性擴展,徹底模擬線上的部署環境。
還經過加降級開關,把入參合法性、風控、時效性校驗等臨時關閉,以便能讓壓測的請求貫穿整個主流程。
接着在這一方案的基礎上,經過對接口分組和僞造恰當的數據,編寫貼近真實的調用行爲的腳本,再次作了壓測。
在執行人員上,也經歷了從開發提供數據,測試全權負責;到測試主導,開發參與;再到開發主導,測試協助的過程。
由此,壓測方案就愈來愈貼近真實場景,壓測結論天然就更加可信 。
前面談到了系統設計的不合理致使了本次性能問題,來分析下這裏面的根本緣由。
首先要理解的是,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分,被用戶吐槽整隻羊都被人家牽走了的開發。