De beperkingen van een op polling gebaseerde web-app analyseren
De huidige architectuur van de toepassing rapporteert voorraadgegevens door alle informatie over de aandelenkoers van de server op te halen op basis van een timer. Dit ontwerp wordt vaak een ontwerp op basis van polling genoemd.
Bedieningscomputer
De informatie over de aandelenkoers wordt opgeslagen in een Azure Cosmos DB-database. Wanneer deze wordt geactiveerd door een HTTP-aanvraag, retourneert de functie getStocks
alle rijen uit de database.
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,
};
},
});
-
Gegevens ophalen: Het eerste codegedeelte, cosmosInput, haalt alle items in de
stocks
tabel op, met de querySELECT * from c
, in destocksdb
-database in Cosmos DB. -
Gegevens retourneren: het tweede codegedeelte, app.http, ontvangt die gegevens in de functie als invoer in
context.extraInputs
retourneert deze vervolgens als antwoordtekst terug naar de client.
Klant
De voorbeeldclient gebruikt Vue.js om de gebruikersinterface en de fetch-client samen te stellen voor het verwerken van aanvragen voor de API.
De HTML-pagina gebruikt een timer om elke vijf seconden een aanvraag naar de server te verzenden om aandelen aan te vragen. Het antwoord retourneert een matrix met aandelen, die vervolgens aan de gebruiker worden weergegeven.
<!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();
}
});
Zodra de startPoll
-methode polling start, wordt de update
methode elke vijf seconden aangeroepen. In de update
-methode wordt een GET-aanvraag verzonden naar het /api/getStocks
API-eindpunt en wordt het resultaat ingesteld op app.stocks
, waarmee de gebruikersinterface wordt bijgewerkt.
De server- en clientcode is relatief eenvoudig: alle gegevens ophalen, alle gegevens weergeven. Zoals we in onze analyse ontdekken, brengt deze eenvoud enkele beperkingen met zich mee.
Analyse van prototypeoplossing
Als Tailwind Traders-engineer hebt u enkele nadelen van deze polling-benadering op basis van timers geïdentificeerd.
Onnodige API-aanvragen: in het prototype van de timergebaseerde polling neemt de clienttoepassing contact op met de server, ongeacht of er wijzigingen in de onderliggende gegevens bestaan.
Onnodige paginavernieuwingen: Zodra gegevens van de server worden geretourneerd, wordt de volledige lijst van aandelen op de webpagina bijgewerkt, zelfs als er niets gewijzigd is aan de gegevens. Dit pollingmechanisme is een inefficiënte oplossing.
polling-intervallen: het selecteren van het beste polling-interval voor uw scenario is ook een uitdaging. Polling dwingt u om een keuze te maken tussen hoeveel elke aanroep naar de back-end kost en hoe snel uw app moet reageren op nieuwe gegevens. Vertragingen bestaan vaak tussen de tijd waarop nieuwe gegevens beschikbaar komen en de tijd waarop de app deze detecteert. In de volgende afbeelding ziet u het probleem.
In het ergste geval is de potentiële vertraging voor het detecteren van nieuwe gegevens gelijk aan het polling-interval. Dus waarom niet een kleiner interval gebruiken?
Hoeveelheid gegevens: naarmate de toepassing wordt geschaald, wordt de hoeveelheid gegevens die tussen de client en de server worden uitgewisseld, een probleem. Elke HTTP-aanvraagheader bevat honderden bytes aan gegevens, samen met de cookie van de sessie. Al deze overhead, met name bij zware belasting, creëert verspilde middelen en belast de server onnodig.
Nu u bekend bent met het prototype, is het tijd om de toepassing op uw computer uit te voeren.
Ondersteuning voor CORS
In het local.settings.json bestand van de Functions-app bevat de sectie Host
de volgende instellingen.
{
"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
}
}
Met deze configuratie kan een webtoepassing die wordt uitgevoerd op localhost:3000 aanvragen indienen bij de functie-app die wordt uitgevoerd op localhost:7071. De eigenschap CORSCredentials
geeft aan dat de functie-app referentiecookies van de aanvraag accepteert.
Cross-Origin Resource Sharing (CORS) is een HTTP-functie waarmee een webtoepassing die onder het ene domein wordt uitgevoerd, toegang heeft tot resources in een ander domein. Webbrowsers implementeren een beveiligingsbeperking die bekend staat als beleid voor dezelfde oorsprong waarmee wordt voorkomen dat een webpagina API's in een ander domein aanroept; CORS biedt een veilige manier om toe te staan dat één domein (het oorspronkelijke domein) API's in een ander domein aanroept.
Wanneer CORS lokaal draait, wordt CORS geconfigureerd in het local.settings.json-bestand van het voorbeeld, dat nooit wordt gepubliceerd. Wanneer u de client-app (les 7) implementeert, moet u ook de CORS-instellingen in de functie-app bijwerken om toegang vanuit de client-app toe te staan.