前段時間客戶端這邊做了一個爆款活動,我這邊支援了一下h5頁面,也是我做了這麼久的web頁面之後第一次嘗試做移動端和使用jsbridge,同時因為我們的服務有使用nodejs踩了一些坑,在這裡記錄一下。
端內開發程式碼規範
這裡只記錄了幾個開發的時候沒有注意到的坑
1. 無襯線字型
由於一開始UI給的prd裡的字型不是瀏覽器自帶的字型,因此我這邊使用了預設字型,沒有特意的新增font-family
屬性,後來在實際頁面中發現文字都變成了帶襯線的字型了,被UI提醒了一下才改成了Helvetica
字型。
並且iOS的字型庫和Android的字型庫不同,在Android下可以正常訪問的字型可能因為在iOS中不存在而命中黑體,因此要在全域性設定font-family
font-family: "Helvetica Neue", Helvetica, STHeiTi, sans-serif;
複製程式碼
2. 禁止使用者長按連結與圖片彈出選單和選中文字
為了為了優化頁面體驗,我們在頁面中應該禁止使用者長按連結和圖片彈出選單的功能,以及禁用選中文字功能。
a, img {
-webkit-touch-callout: none;
/* 禁止長按連結與圖片彈出選單 */
}html, body {
-webkit-user-select: none;
user-select: none;
-webkit-touch-callout: none;
}複製程式碼
3. rem與dpr
這裡我們使用的hotcss
這個庫,一開始按照正常的rem開發去做,後來發現大小一直不對,最終從頭排查了一遍,發現是hotcss
計算出的dpr是3而不是1,導致meta
標籤最後是這個樣子的:
<
meta name="viewport" content="width=device-width, initial-scale=0.3333333333333333, minimum-scale=0.3333333333333333, maximum-scale=0.3333333333333333, user-scalable=no">
複製程式碼
因此正常的計算出來的rem還要除3,如hotcss
計算出的font-size
為67.5px
,dpr
為1/3
,則最終的單位應該為$px/22.5 + rem
。
4. 效能優化
移動端的效能較web來說要差很多,因此要做很多的優化
- 圖片壓縮。壓縮圖片,儘可能將圖片壓縮2-3次,可以大幅減少圖片體積,加快頁面載入速度。圖片壓縮地址:tinypng.com/
- 儘可能將常用資料離線化,儲存在
localStorage
中。 - 首屏請求數目儘可能少,儘量限制在4個以內,小圖片儘可能使用精靈圖。即使不能使用精靈圖也儘可能壓縮圖片大小。
- 首屏之外的資源使用按需載入和懶載入。
- iOS點選有300ms延遲,因此要禁止頁面縮放從而取消點選延遲
- chrome瀏覽器有自動適配文字大小的機制,開發時如果遇到文字大小和設定的畫素大小不一致時記得去掉文字自動適配大小功能。
-webkit-text-size-adjust: none;
- 谷歌線上網站評測工具:developers.google.com/speed/pages…
壓力測試
node層具體功能:
- 服務端渲染頁面
- 轉發前端請求,本次活動頁面的程式碼存放在主站的倉庫中,由於主站的域名不在客戶端那邊的白名單中(不是主站的客戶端,另一個產品),因此需要node進行一次請求轉發。
以前我們做壓測都是對後端介面進行壓測,這次由於使用了node進行SSR,並且node還有介面轉發,因此還需要對node進行壓力測試,這也是我們團隊第一次對node進行壓測。
指標:
- 測試介面的qps負載能力
- 測試返回結果500所佔的比例
- 測試介面響應時間
- 測試頁面502的比例
壓測優化
本次活動對node層的要求是:SSR介面承載10000qps,轉發請求介面承載100000qps。這兩個數字其實都是峰值qps,也就是活動結束開獎那一刻的qps,平時qps很低,估計不到100。雖然感覺QA同學對峰值qps的估值也有點離譜,但是既然定了這個目標,我們就需要努力達成。
1. pm2開啟cluster模式
由於我們申請的例項是雙核的CPU,因此首先想到的是使用pm2的cluster模式充分利用多核機器的優勢。
踩坑:pm2裡面有一個引數是-i
,表示要開啟的程式數量,一般是開啟和CPU數量相同的程式數量。-i max
的意思是開啟和當前核心數目相同的程式數目。天真的我就這麼幹了,後來發現我們的docker是執行在物理機上面的,物理機有60+的核心,使用max
之後,pm2直接去讀取物理機的核心數目並開啟程式……一下就開了60多個程式,然後服務就崩潰了。
2. 請求快取和打散
由於後端機器數量不足,無法承載10wqps,因此需要前端這邊做一些打散和快取的策略。
- 打散請求。開獎瞬間的請求並不是同時併發傳送到後端的,前端這邊使用隨機數,將所有請求在10s內打散。
- 適當失敗。並不是所有請求都需要成功,前端只需要將適當數量的請求傳送給後端,其他請求直接返回訪問失敗請稍後重試的提示就可以了。通常秒殺活動中經常採用此策略,倒數計時結束之後所有的使用者請求,只有十分之一可以真正到達後端,大部分都在前端就被拒絕了。
- 快取請求結果。一些開獎活動的結果並不是因人而異,而是大家看到的都一樣的,比如錦鯉活動,數千萬人參加,最後只有一人中獎,中獎資訊所有人看到的都一樣。這種情況下就適合使用node快取策略。當node的介面拿到中獎資訊之後,就將該請求的結果存到node的記憶體中,此後所有的使用者請求,node直接讀取記憶體中的資料來返回,不用真的傳送給後端去等待響應。這樣基本上後端就沒有什麼壓力了,大部分工作node層就做完了,不過這樣也就意味著後端的壓力都轉移到了node這邊。
3. 快取SSR
活動頁面一開始要求儘可能快,因此我們這邊直接採用了服務端渲染,後來壓測的時候發現,由於開啟了SSR,一臺例項能夠承載的qps從3000直接降低到200,但是又不能不使用ssr,因為國外一些地區網速可能會很慢,如果純粹使用客戶端渲染的話,可能頁面的載入時間會很長。
因此只能在ssr部分進行優化。
我們第一時間想到的還是利用記憶體,因為qps很高的時候基本集中在開獎的瞬間,並且這個瞬間的頁面是固定的,因此可以直接在node中快取開獎頁面的html字串。
當第一個使用者拿到中獎資訊之後,node快取該次請求生成的html字串,此後所有的請求都直接從記憶體中拿這個字串使用就可以了。
開獎之前的頁面原本是不打算做優化的,因為每個使用者的資訊都不一樣,如果放到記憶體那種的話可能會造成記憶體洩露,但是又怕有站內的大V宣傳次活動導致qps突然增高,如果因為這種原因導致伺服器當機的話就有點丟人了,所以最後還是絕對對開獎之前的活動頁面也做一下快取。
由於頁面關於使用者資訊的部分只有使用者名稱和倒數計時剩餘時間的不同,因此我們在node中快取一個沒有使用者名稱和倒數計時時間的公共字串,然後請求到資料之後使用正則,將模板字串中的使用者名稱和倒數計時的佔位符替換掉即可。
4. 加機器
效能不夠,機器來湊。
上面說了那麼多,都沒有加機器管用。
我們為了支援活動,將4臺例項擴容到了150臺。使用者儘管訪問,能當機算我輸。