分析輪詢型 Web 應用程式的限制

已完成

輪詢型 Web 應用程式。

應用程式的目前架構會藉由根據計時器從伺服器擷取所有股價資訊來報告股票資訊。 這種設計通常稱為輪詢型設計。

伺服器

股價資訊均儲存於 Azure Cosmos DB 資料庫中。 當 HTTP 要求觸發時,函數 getStocks 會傳回資料庫的所有資料列。

import { app, input } from "@azure/functions";

const cosmosInput = input.cosmosDB({
    databaseName: 'stocksdb',
    containerName: 'stocks',
    connection: 'COSMOSDB_CONNECTION_STRING',
    sqlQuery: 'SELECT * from c',
});

app.http('getStocks', {
    methods: ['GET'],
    authLevel: 'anonymous',
    extraInputs: [cosmosInput],
    handler: (request, context) => {
        const stocks = context.extraInputs.get(cosmosInput);
        
        return {
            jsonBody: stocks,
        };
    },
});
  • 取得資料:程式碼的第一個區段 (cosmosInput) 會取得 stocks 資料表中的所有項目,並在 Cosmos DB 的 stocksdb 資料庫中使用 SELECT * from c 查詢。
  • 傳回資料:程式碼的第二個區段 (app.http) 會將該資料接收至函式做為 context.extraInputs 中的輸入,然後將其當做回應主體傳回用戶端。

用戶端

範例用戶端會使用 Vue.js,撰寫 UI 和 Fetch 用戶端來處理對 API 的要求。

HTML 頁面會使用計時器,每隔五秒就將要求傳送至伺服器來要求股票。 回應會傳回股票陣列,然後向使用者顯示。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css" integrity="sha256-8B1OaG0zT7uYA572S2xOxWACq9NXYPQ+U5kHPV1bJN4=" crossorigin="anonymous" />
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
    <link rel="stylesheet" href="style.css">
    <title>Stocks | Enable automatic updates in a web application using Azure Functions and SignalR</title>
</head>
<body>
    
    <!-- BEGIN: Replace markup in this section -->
    <div id="app" class="container">
        <h1 class="title">Stocks</h1>
        <div id="stocks">
            <div v-for="stock in stocks" class="stock">
                <div class="lead">{{ stock.symbol }}: ${{ stock.price }}</div>
                <div class="change">Change:
                    <span :class="{ 'is-up': stock.changeDirection === '+', 'is-down': stock.changeDirection === '-' }">
                        {{ stock.changeDirection }}{{ stock.change }}
                    </span></div>
            </div>
        </div>
    </div>
    <!-- END  -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js" integrity="sha256-chlNFSVx3TdcQ2Xlw7SvnbLAavAQLO0Y/LBiWX04viY=" crossorigin="anonymous"></script>
    <script src="bundle.js" type="text/javascript"></script>
</body>
</html>
import './style.css';

function getApiUrl() {

    const backend = process.env.BACKEND_URL;
    
    const url = (backend) ? `${backend}` : ``;
    return url;
}

const app = new Vue({
    el: '#app',
    interval: null,
    data() { 
        return {
            stocks: []
        }
    },
    methods: {
        async update() {
            try {
                
                const url = `${getApiUrl()}/api/getStocks`;
                console.log('Fetching stocks from ', url);

                const response = await fetch(url);
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }

                app.stocks = await response.json();
            } catch (ex) {
                console.error(ex);
            }
        },
        startPoll() {
            this.interval = setInterval(this.update, 5000);
        }
    },
    created() {
        this.update();
        this.startPoll();
    }
});

一旦 update 方法啟動輪詢之後,每隔五秒系統就會呼叫 startPoll 方法。 在 update 方法內部,將 GET 要求傳送至 /api/getStocks API 端點,並將結果設定為 app.stocks 來更新 UI。

伺服器和用戶端程式碼相當簡單:取得所有資料、顯示所有資料。 如我們在分析中所發現,這種簡單性帶來了一些限制。

原型解決方案的分析

身為 Tailwind Traders 工程師,您已識別出此定時器型輪詢方法的一些缺點。

  • 不必要的 API 要求:在計時器型輪詢原型中,用戶端應用程式會連絡伺服器,而不論是否會對基礎資料進行變更。

  • 不必要的頁面重新整理:從伺服器傳回資料之後,即使沒有任何資料變更,整個股票清單也會在網頁上更新。 此輪詢機制是一個效率不佳的解決方案。

  • 輪詢間隔:為您的案例選取最佳輪詢間隔也是一項挑戰。 輪詢會強迫您在每次呼叫後端所產生的成本,以及您想要應用程式以多快的速度來回應新資料之間進行選擇。 新資料可供使用的時間和應用程式偵測到的時間之間,通常會有延遲。 下圖顯示此問題。

    此圖顯示時間軸和輪詢觸發程序,其每隔五分鐘就會檢查一次是否有新資料。新資料會在七分鐘後變成可用。直到下一次輪詢 (發生在 10 分鐘) 後,應用程式才會注意到新資料。

    在最糟的情況下,偵測新資料的延遲可能等於輪詢間隔。 那為何不使用較小的間隔?

  • 資料量:當應用程式調整規模時,用戶端與伺服器之間交換的資料量會成為問題。 每個 HTTP 要求標頭均包含數百個位元組的資料,以及工作階段的 Cookie。 這個額外負荷 (尤其當負載量很大時) 全然會建立浪費的資源,且不必要地增加伺服器的負擔。

現在您已更熟悉原型,可以讓應用程式在您的電腦上執行。

支援 CORS

在函數應用程式的 local.settings.json 檔案中,Host 區段包含下列設定。

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "<STORAGE_CONNECTION_STRING>",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
    "COSMOSDB_CONNECTION_STRING": "<COSMOSDB_CONNECTION_STRING>"
  },
  "Host" : {
    "LocalHttpPort": 7071,
    "CORS": "http://localhost:3000",
    "CORSCredentials": true
  }
}

此設定可讓 Web 應用程式在 localhost:3000 上執行,以便向在 localhost:7071 上執行的函數應用程式提出要求。 CORSCredentials 屬性會告知函數應用程式要接受來自要求的認證 Cookie。

跨原始來源資源共用 (CORS) 是一項 HTTP 功能,可讓在某個網域中執行的 Web 應用程式存取另一個網域中的資源。 網頁瀏覽器會實作稱為 相同原始原則 的安全性限制,它可防止網頁呼叫不同網域中的 API;CORS 則提供了一個安全的方式,可讓一個網域 (原始網域) 能夠呼叫其他網域中的 API。

在本機執行時,會在範例的 local.settings.json 檔案中為您設定 CORS,一律不會發佈。 當您部署用戶端應用程式 (單元 7) 時,也必須更新函數應用程式中的 CORS 設定,以允許從用戶端應用程式存取。