小蝌蚪傳記:讓介面提速60%的優化與原理

第一名的小蝌蚪發表於2020-11-10

背景

好久沒寫文章了,沉寂了大半年

持續性萎靡不振,間歇性癲癇發作

天天來大姨爹,在迷茫、焦慮中度過每一天

不得不承認,其實自己就是個廢物

作為一名低階前端工程師

最近處理了一個十幾年的祖傳老介面

它繼承了一切至尊級複雜度邏輯

傳說中呼叫一次就能讓cpu負載飆升90%的日天服務

專治各種不服與老年痴呆

我們欣賞一下這個介面的耗時

平均呼叫時間在3s以上

導致頁面出現嚴重的轉菊花

經過各種深度剖析與專業人士答疑

最後得出結論是:放棄醫療

魯迅在《狂人日記》裡曾說過:“能打敗我的,只有女人和酒精,而不是bug

每當身處黑暗之時

這句話總能讓我看到光

所以這次要硬起來

我決定做一個node代理層

用下面三個方法進行優化:

  • 按需載入 -> graphQL
  • 資料快取 -> redis
  • 輪詢更新 -> schedule

程式碼地址:github

按需載入 -> graphQL

天秀老介面存在一個問題,我們每次請求1000條資料,返回的陣列中,每一條資料都有上百個欄位,其實我們前端只用到其中的10個欄位而已。

如何從一百多個欄位中,抽取任意n個欄位,這就用到graphQL。

graphQL按需載入資料只需要三步:

  • 定義資料池 root
  • 描述資料池中資料結構 schema
  • 自定義查詢資料 query

定義資料池

我們針對屌絲追求女神的場景,定義一個資料池,如下:

// 資料池
var root = {
    girls: [{
        id: 1,
        name: '女神一',
        iphone: 12345678910,
        weixin: 'xixixixi',
        height: 175,
        school: '劍橋大學',
        wheel: [{ name: '備胎1號', money: '24萬元' }, { name: '備胎2號', money: '26萬元' }]
    },
    {
        id: 2,
        name: '女神二',
        iphone: 12345678910,
        weixin: 'hahahahah',
        height: 168,
        school: '哈佛大學',
        wheel: [{ name: '備胎3號', money: '80萬元' }, { name: '備胎4號', money: '200萬元' }]
    }]
}

裡面有兩個女神的所有資訊,包括女神的名字、手機、微信、身高、學校、備胎集合等資訊。

接下來我們就要對這些資料結構進行描述。

描述資料池中資料結構

const { buildSchema } = require('graphql');

// 描述資料結構 schema
var schema = buildSchema(`
    type Wheel {
        name: String,
        age: Int
    }
    type Info {
        id: Int
        name: String
        iphone: Int
        weixin: String
        height: Int
        school: String
        wheel: [Wheel]
    }
    type Query {
        girls: [Info]
    }
`);

上面這段程式碼就是女神資訊的schema。

首先我們用type Query定義了一個對女神資訊的查詢,裡面包含了很多女孩girls的資訊Info,這些資訊是一堆陣列,所以是[Info]

我們在type Info中描述了一個女孩的所有資訊的維度,包括了名字(name)、手機(iphone)、微信(weixin)、身高(height)、學校(school)、備胎集合(wheel)

定義查詢規則

得到女神的資訊描述(schema)後,就可以自定義獲取女神的各種資訊組合了。

比如我想和女神認識,只需要拿到她的名字(name)和微訊號(weixin)。查詢規則程式碼如下:

const { graphql } = require('graphql');

// 定義查詢內容
const query = `
    { 
        girls {
            name
            weixin
        }
    }
`;

// 查詢資料
const result = await graphql(schema, query, root)

篩選結果如下:

又比如我想進一步和女神發展,我需要拿到她備胎資訊,查詢一下她備胎們(wheel)的家產(money)分別是多少,分析一下自己能不能獲取優先擇偶權。查詢規則程式碼如下:

const { graphql } = require('graphql');

// 定義查詢內容
const query = `
    { 
        girls {
            name
            wheel {
                money
            }
        }
    }
`;

// 查詢資料
const result = await graphql(schema, query, root)

篩選結果如下:

我們通過女神的例子,展現瞭如何通過graphQL按需載入資料。

對映到我們業務具體場景中,天秀介面返回的每條資料都包含100個欄位,我們配置schema,獲取其中的10個欄位,這樣就避免了剩下90個不必要欄位的傳輸。

graphQL還有另一個好處就是可以靈活配置,這個介面需要10個欄位,另一個介面要5個欄位,第n個介面需要另外x個欄位

按照傳統的做法我們要做出n個介面才能滿足,現在只需要一個介面配置不同schema就能滿足所有情況了。

感悟

在生活中,我們們舔狗真的很缺少graphQL按需載入的思維

渣男渣女,各取所需

你的真情在名媛面前不值一提

我們要學會投其所好

上來就亮車鑰匙,沒有車就秀才藝

今晚我有一條祖傳的染色體想與您分享一下

行就行,不行就換下一個

直奔主題,簡單粗暴

快取 -> redis

第二個優化手段,使用redis快取

天秀老介面內部呼叫了另外三個老介面,而且是序列呼叫,極其耗時耗資源,秀到你頭皮發麻

我們用redis來快取天秀介面的聚合資料,下次再呼叫天秀介面,直接從快取中獲取資料即可,避免高耗時的複雜呼叫,簡化後程式碼如下:

const redis = require("redis");
const { promisify } = require("util");

// 連結redis服務
const client = redis.createClient(6379, '127.0.0.1');

// promise化redis方法,以便用async/await
const getAsync = promisify(client.get).bind(client);
const setAsync = promisify(client.set).bind(client);

async function list() {
    // 先獲取快取中資料,沒有快取就去拉取天秀介面
    let result = await getAsync("快取");
    if (!result) {
          // 拉介面
          const data = await 天秀介面();
          result = data;
          // 設定快取資料
          await setAsync("快取", data)
    }
       return result;
}

list(); 

先通過getAsync來讀取redis快取中的資料,如果有資料,直接返回,繞過介面呼叫,如果沒有資料,就會呼叫天秀介面,然後setAsync更新到快取中,以便下次呼叫。因為redis儲存的是字串,所以在設定快取的時候,需要加上JSON.stringify(data),為了便於大家理解,我就不加了,會把具體細節程式碼放在github中。

將資料放在redis快取裡有幾個好處

可以實現多介面複用、多機共享快取

這就是傳說中的雲備胎

追求一個女神的成功率是1%

同時追求100個女神,那你獲取到一個女神的概率就是100%

魯迅《狂人日記》裡曾說過:“舔一個是舔狗,舔一百個你就是戰狼

你是想當舔狗還是當戰狼?

來吧,快取用起來,redis用起來

輪詢更新 -> schedule

最後一個優化手段:輪詢更新 -> schedule

女神的備胎用久了,會定時換一批備胎,讓新鮮血液進來,發現新的快樂

快取也一樣,需要定時更新,保持與資料來源的一致性,程式碼如下:

const schedule = require('node-schedule');

// 每個小時更新一次快取
schedule.scheduleJob('* * 0 * * *', async () => {
    const data = await 天秀介面();
    // 設定redis快取資料
    await setAsync("快取", data)
});

我們用node-schedule這個庫來輪詢更新快取,* * 0 * * *這個的意思就是設定每個小時的第0分鐘就開始執行快取更新邏輯,將獲取到的資料更新到快取中,這樣其他介面和機器在呼叫快取的時候,就能獲取到最新資料,這就是共享快取和輪詢更新的好處。

早年我在當舔狗的時候,就將輪詢機制發揮到淋漓盡致

每天向白名單裡的女神,定時輪詢發訊息

無限迴圈雲跪舔三件套:

  • “啊寶貝,最近有沒有想我”
  • “啊寶貝早安安”
  • “寶貝晚安,麼麼噠”

雖然女神依然看不上我

但仍然時刻準備著為女神服務!

結尾

經過以上三個方法優化後

介面請求耗時從3s降到了860ms

這些程式碼都是在業務中由繁化簡後抽離出的邏輯

真實的業務場景遠比這要複雜:分段式資料儲存、主從同步 讀寫分離、高併發同步策略等等

每一個模組都晦澀難懂

就好像每一個女神都高不可攀

屌絲戰勝了所有bug,唯獨戰勝不了她的心

受傷了只能在深夜裡獨自買醉

但每當夢到女神開啟我做的頁面

被極致流暢的體驗驚豔到

在精神高潮中享受靈魂昇華

那一刻

我覺得我又行了

(完)

程式碼地址:github

作者:第一名的小蝌蚪,公眾號:前端屌絲

FFCreator是我們團隊做的一個輕量、靈活的短視訊加工庫。您只需要新增幾張圖片或文字,就可以快速生成一個類似抖音的酷炫短視訊。github地址:https://github.com/tnfe/FFCreator 歡迎小夥伴star。

相關文章