Prezentacja opisuje różne techniki optymalizacji aplikacji ASP.NET. Omawiane są role poszczególnych warstw wpływających na wydajność - od optymalizacji kodu po stronie klienta (techniki stosowane na poziomie kodu HTML i JavaScript) przez różne poziomy stosowania cache, wybrane ustawienia konfiguracyjne IIS aż po same techniki optymalizacji na poziomie kodu ASP.NET.
2. Skalowalność – ilu użytkowników może pracować jednocześnie?
Aplikacja może obsługiwać tysiące użytkowników, ale być wolna
Zmiany w sprzęcie, infrastrukturze sieciowej, itp. (istotna architektura!)
Wydajność – jak szybko ładują się strony?
Zasada 8 sekund
Liczy się odczuwalna wydajność (np. reklamy dopiero na końcu)
To także:
Szybkość wprowadzania poprawek i zmian
Łatwość wdrażania aplikacji
Proces wytwórczy
…
Architektura Bing vs strony domowej (można przesadzić)
Skalowalność i wydajność
3. Lokalny cache przeglądarki
DNS – cache / zapytanie
Proxy – visible / transparent
Cache serwera
System - http.sys
IIS
ASP
.NET (dane w ram / z dysku)
Cache SQL Server
Dyski – SSD / zwykłe / cache…
Komponenty wpływające na wydajność
4. Odczuwalna wydajność
Kolejność ładowania, AJAX
Mniej żądań do serwera
Walidacja po stronie klienta, bundling, itp.
Cache na wszystkich warstwach
Minimalizacja blokujących wywołań
Synchroniczne operacje zapisu do bazy, zewnętrzne usługi, itp.
Optymalizacja I/O (dyski, itp.)
Także partycjonowanie / sharding, itp.
Najważniejsze zasady przy optymalizacji
6. Maximum Transfer Unit (MTU) - rozmiar okna odpowiedzi
Od 500 do 1500 bajtów
Dużo komunikatów SYN-ACK
TCP Slow Start (RFC 5681)
Bardzo kosztowne nawiązywanie połączenia
Długi okres między pierwszym pakietem a kolejnym
Zapobiega przeciążeniu sieci
Jak najmniej nowych połączeń (np. żądania plików)
HTTP Keep-Alives – domyślnie w IIS 120 sekund
Np. przy długich formularzach można zwiększyć (ostrożnie)
Protokół TCP
7. TCP Slow Start - przykład
IE Developer tools (F12)
Żółte – slow start + pierwszy pakiet odpowiedzi
Niebieski – reszta odpowiedzi
Jeśli <img> było w pierwszym pakiecie
8. <head>
Przeglądarka nie wyświetli nic przed pobraniem całego nagłówka
„Lookahead” – ograniczenia
Jak najwięcej w <body>
Nawet <link> i <style> (niezgodne ze specyfikacją, ale działa)
Kolejność
Np. duży baner reklamowy na górze strony – w kodzie lepiej niżej
Struktura strony
10. Późne ładowanie – preloading
<!-- Preloading - na górze strony lub w onload (jeśli może być później) -->
<script type="text/javascript">
var myimg = new Image();
myimg.src = "myimage.jpg";
</script>
<!-- Niżej na stronie, będzie załadowane z cache (dla rolloverów) -->
<img src="myimage.jpg" width="50" height="50" />
11. Wielkość liter
W Unix – system plików case sensitive
Niektóre serwery cache’ują oddzielnie
Może spowodować wysłanie dwóch żądań
Można dołączyć moduł http, który to poprawia
Referencje do tej samej domeny
Przekierowanie z domena.com/gfx.jpg na www.domena.com/gfx.jpg
Cache i URL
<img src="myimage.jpg" width="50" height="50" />
<img src="myimage.JPG" width="50" height="50" />
12. Przeglądarki mają do 6 równoległych połączeń dla domeny
Przed IE8 – tylko 2!
Można podzielić pliki na kilka domen
img.mojadomena.com, css.mojadomena.com, itp.
Aliasy lub inne usługi cookieless (np. Azure Blob)
Przetwarzanie żądań
<img src="q1.gif" height="16" width="16" />
<img src="q2.gif" height="16" width="16" />
<img src="q3.gif" height="16" width="16" />
<img src="q4.gif" height="16" width="16" />
<img src="q5.gif" height="16" width="16" />
<img src="q6.gif" height="16" width="16" />
<img src="q7.gif" height="16" width="16" />
<img src="q8.gif" height="16" width="16" />
<img src="q9.gif" height="16" width="16" />
<img src="q10.gif" height="16" width="16" />
14. Jeśli kilka aliasów – mechanizm generowania powtarzalnych url
Plik grafika.jpg zawsze z s1.domena.com, grafika3.jpg z
s2.domena.com, itp.
Wewnątrz własnej kontrolki
Control adapter dla Image
(dalej)
ASP
.NET i podział na kilka domen
private string _src;
private static string[] subdomains = {
"http://s1.12titans.net",
"http://s2.12titans.net",
"http://s3.12titans.net"
};
public string src
{
get
{
HttpContext ctx = HttpContext.Current;
if (ctx.Request.Url.Host != "localhost")
{
if (!String.IsNullOrEmpty(this._src) && !this._src.StartsWith("http") &&
!this._src.StartsWith("data:"))
{
int n = Math.Abs(this._src.GetHashCode()) % subdomains.Length;
return subdomains[n] + this._src;
}
}
return this._src;
}
set
{
this._src = ResolveUrl(value).ToLowerInvariant();
}
}
15. Skrypty inline mogą opóźniać renderowanie strony
Renderowanie dopiero po zakończeniu działania skryptów
OnLoad / DOMReady – wtedy po wyrenderowaniu
Umieszczać na końcu pliku
Jeśli skrypty zmieniają HTML
Zamiast document.write() – innerHTML (możliwe wywołania później)
Ukryty div z document.write() + odkrywanie go później
<script defer> i <script async> (HTML 5)
Nie wstrzymuje parsera, pobiera skrypt i wykonuje kod (np. podpina do onload)
Async – nie gwarantuje kolejności (wywołuje po pobraniu)
Defer – gwarantuje kolejność wywołań
Dołączać z CDN (m.in. ASP
.NET AJAX, jQuery) - http://www.asp.net/ajaxlibrary/cdn.ashx
Skrypty
16. Tylko lower case w miarę możliwości – kompresja
<img> zamiast <IMG>, itp.
Image sprites dla wielu mniejszych grafik
background-position: 0 -120px
Grafika - rozważyć data URI scheme (IE 8+)
Narzędzia lub online – np. dataurl.net
Do niewielkich grafik (base64, więc 40% większe)
Zwłaszcza w CSS, kiedy może być dodatkowo cache’owane
Zmniejszanie liczby żądań
#hdr{border:1px solid #000;height:40px;
background:url(data:image/gif;base64,R0lGODlhAQAoANUAAAAAAP///wFUzgNV0ANVzwVX0QZY0gdY0gha
0wpb1Qxd1g1e1w9g2BFh2RJj2xVk3BZm3Rho3hpp4Bxr4h5t4x9u5CFw5SRx5yVz6Cd16Sl26it47C157S987jF97
zOA8jJ+8TWB8zaD9DiE9TqF9zqG9zyH+D2I+D6J+T+K+v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAACoALAAAAAABACgAAAYlwBTqZCqRRqLQB+TpcDa
aDOZiqVAmkgjk4WgwFooE4mAoDAiCIAA7)
17. Javascript
Walidacja po stronie klienta
Wyłączanie submit po kliknięciu
Generowanie długich list (np. select i wiele elementów <option>)
Unikać obiekt.inny.jeszczeinny.zmienna (pomocnicza zmienna)
Wielokrotny document.write zamiast sklejania stringów
textContent szybszy od innerHTML (jeśli element zawiera tekst)
ASP
.NET 4.5 – unobtrusive validation
Zmniejszenie liczby żądań – c.d.
<code><add key="ValidationSettings:UnobtrusiveValidationMode" value="WebForms"/></code>
ValidationSettings.UnobtrusiveValidationMode = UnobtrusiveValidationMode.WebForms;
18. Favicon.ico – lepiej żeby był (cache vs 404 za każdym razem)
CSS – inline przy pierwszym wyświetleniu
Mniej żądań przy pierwszym wejściu
Skrypt ładowany inline – jeśli nie ustawione ciasteczko
W Onload – pobierany dynamicznie (cache dla kolejnych żądań)
Nie zamiast – plik potrzebny (cache)
Zmniejszenie liczby żądań – c.d.
<script type="text/javascript">
function getcss() {
var h = document.getElementsByTagName('head');
var l = document.createElement('link');
l.type = 'text/css';
l.rel = 'stylesheet';
l.href = 'css/file19.css';
h[0].appendChild(l);
}
</script>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Set-Cookie"
value="C=A;expires=Sat, 01-Jan-2050 00:00:00 GMT;path=/demo/css/" />
</customHeaders>
</httpProtocol>
</system.webServer>
19. Określić <!DOCTYPE>
Parser lookahead nie musi restartować
Width i Height przy grafikach
Rozmiary kolumn – tabele
Charset (dla statycznych stron)
Szybkość renderowania strony
20. Kiedy wiemy jaka będzie kolejna strona (np. „wizard”)
W pageLoad (nie document ready)
Precaching - grafiki
<script type="text/javascript" src="jquery-1.7.1.min.js"></script>
<script type="text/javascript">
$(window).load(function() {
var pre = new Image(0,0);
pre.src = "http://s1.domena.net/static/next1.jpg";
var prx = new Image(0, 0);
prx.src = "http://s2.domena.net/static/next2.jpg";
});
</script>
21. <img>- dla innych formatów nie zadziała (MIME type)
AJAX – można, ale tylko ta sama domena
Dynamicznie element <script>
Nie musi być dodawany do DOM
Uwaga – parsowane i wywoływane
Dynamicznie <link>
Musi być w DOM
Wczytywany (może być konflikt selektorów)
Precaching – CSS, JS
<script type="text/javascript">
function preload() {
var req = getreq();
if (req != null) {
req.open("GET", "/static/next.js", true);
req.send(null);
}
var rex = getreq();
if (rex != null) {
rex.open("GET", "/static/next.css", true);
rex.send(null);
}
}
</script>
<script type="text/javascript">
function preload() {
var scr = document.createElement("script");
scr.src = "http://s1.domena.net/ch02/next.js";
}
</script>
<script type="text/javascript">
function preload() {
var lnk = document.createElement("link");
lnk.rel = "stylesheet";
lnk.type = "text/css";
lnk.href = "http://s1.domena.net/next.css";
document.getElementsByTagName('head')[0].appendChild(lnk);
}
</script>
23. Każdy ma nieco inną rolę (np. ASP
.NET vs http.sys vs SQL)
Wiele rodzajów cache
24. Cache-Control: max-age lub Expires (dawniej)
Jeśli nie ustawione oblicza
Aktualny czas +10% różnicy między Last-Modified a aktualnym
Po tym czasie – nadal na dysku, ale nie używane
Conditional Get (If-Modified-Since)
HTTP 304 – not modified
Statyczne pliki
GET /check.png HTTP/1.1
Accept: */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
If-Modified-Since: Sat, 10 Jan 2012 10:52:45 GMT
If-None-Match: "80fc52fa8bb2c81:0"
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1;
WOW64; Trident/5.0)
Host: www.domena.net
Connection: Keep-Alive
<system.webServer>
. . .
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00" />
</staticContent>
</system.webServer>
25. Cache-Control: no-cache
Przeglądarka musi sprawdzić, ale może korzystać z cache (back/fwd)
Cache-Control: no-store
Całkowite wyłączenie (także back/forward)
Cache statyczny - wyłączanie
<location path="image.jpg">
<system.webServer>
<staticContent>
<clientCache cacheControlMode="DisableCache" />
</staticContent>
</system.webServer>
26. Output cache
VaryByParam, VaryByHeader – np. Accept-Language (dla różnych języków)
VaryByControl (np. „src”, jeśli taką właściwość ma nasza User Control)
Dla User Controls – ustawiać to samo ID na różnych stronach i Shared=true
Jeśli shared=false to do porównywania ID brana także nazwa Page
Dynamiczna zawartość
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="dyn-client.aspx.cs" Inherits="dyn_client" %>
<%@ OutputCache Duration="86400" Location="Client" VaryByParam="None" %>
<system.web>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name="Cache1Day" duration="86400"
location="Client" varyByParam="none" />
</outputCacheProfiles>
</outputCacheSettings>
</caching>
</system.web>
<%@ OutputCache CacheProfile="Cache1Day" %>
27. Najprościej – VaryByCustom = „browser”
Po swojemu - global.asax
Jeśli chcemy np. jedną wersję dla wszystkich IE i jedną dla Webkit
Cache dla różnych przeglądarek
public override string GetVaryByCustomString(HttpContext context, string custom)
{
switch (custom.ToLower())
{
case "iemozilla":
switch (context.Request.Browser.Browser.ToLower())
{
case "ie":
case "blazer 3.0":
return "ie";
case "mozilla":
case "firebird":
case "firefox":
case "applemac-safari":
return "mozilla";
default:
return "default";
}
default:
return base.GetVaryByCustomString(context, custom);
}
}
28. Dynamiczna zawartość - wyłączanie
this.Response.Cache.SetCacheability(HttpCacheability.NoCache);
this.Response.Cache.SetAllowResponseInBrowserHistory(true); // Aby nie było Expires (niepotrzebne)
this.Response.AppendHeader("Cache-Control", "no-store"); // Dla no-store
<%@ OutputCache Location=„None" %>
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Czasem chcemy mieć pewność, że dane aktualne
Aspx, kod lub profil w web.config
29. Domyślnie ASP
.NET ustawia Cache-Control: private
Nie będzie cache’owane przez proxy
Możliwe nadpisanie (Cache-Control: public)
<%@ OutputCache Location=„Any” %>
Location Downstream – tylko w przeglądarkach
Dynamiczna zawartość – c.d.
this.Response.Cache.SetMaxAge(age);
this.Response.Cache.SetExpires(DateTime.UtcNow + age);
this.Response.Cache.SetLastModified(DateTime.UtcNow);
this.Response.Cache.SetCacheability(HttpCacheability.Public);
this.Response.Cache.SetNoServerCaching();
31. Stan tymczasowy strony, stan kontrolek serwerowych
Często lepsze od sesji (baza przy farmie)
LosFormatter – szybki dla typów string, hashtable, arraylist, pair, triple, int, boolean
Dla pozostałych – BinaryFormatter (wolny), ale można napisać własny TypeConverter
Lepiej – kolekcja prostych typów niż własny obiekt (bez TypeConvertera)
Obecność elementu wykorzystywana m.in. do rozpoznania Page.IsPostback
ViewStateMac – zabezpieczenie przed modyfikacjami
Zabezpieczenie przez CSRF
W Page_init – ViewState[userkey] = this.User.Identy.Name;
ViewState
32. Ma tendencje do rozrastania się
Może być prościej wysłać zapytanie niż uploadować dużo większy formularz
Możliwość wyłączenia
Od ASP
.NET 4 – nie tylko dla całej strony (EnableViewState), ale pojedynczych kontrolek
ViewStateMode – enabled, disabled, inherit (domyślnie)
Warto domyślnie wyłączyć (w web.config) i włączać tam, gdzie potrzeba
ControlState – część, której nie można wyłączyć
ViewState - problemy
33. Tag Transform, aby wyłączyć Control State
Jeśli nie korzystamy z zaawansowanych funkcji kontrolki
ViewState – wyłączanie
public class ListViewNoCS : ListView
{
protected override object SaveControlState()
{
return null;
}
}
<pages>
. . .
<tagMapping>
<add tagType="System.Web.UI.WebControls.ListView"
mappedTagType="Samples.ListViewNoCS" />
</tagMapping>
</pages>
34. Dla wolnych połączeń (np. klienci mobile) można przechowywać po
stronie serwera
Można uzależnić to od typu połączenia lub rozmiaru ViewState
ViewState – wolne połączenia
protected override void SavePageStateToPersistenceMedium(object state)
{
string key = Guid.NewGuid().ToString();
this.ClientScript.RegisterHiddenField(ViewKeyName, key);
this.Cache[key] = state;
}
protected override object LoadPageStateFromPersistenceMedium()
{
string key = this.Request[ViewKeyName];
if (key == null)
throw new InvalidOperationException("Invalid ViewState Key");
object state = this.Cache[key];
if (state == null)
throw new InvalidOperationException("ViewState too old");
return state;
}
35. Dla domeny - max 50, 10 KB / cookie
Session lub persistent
Domyślna wartość path - „/”
Dołączane także do zawartości statycznej wszystkich plików!
Ustawiać zawsze cookie.Path = „/katalog/”
Bez ustawienia Domain
IE – także wszystkie subdomeny
Inne przeglądarki – tylko aktualna domena
Lepiej ustawić cookie.Domain (unikanie błędów)
Cookie.HttpOnly = true
Cookie niewidoczne dla JS (mniej możliwości ataku)
Powinno się ustawiać domyślnie dla wszystkich
Cookie.Secure = true – wysyłane tylko przez SSL
Jeśli nie możemy SSL – konwersja do Base64 (Convert.ToBase64String) i szyfrowanie symetryczne
Cookies
36. IE8+, Firefox 3.5+, Safari 4+, Chrome 4+, Opera 10.5+
10 MB – IE, 5 MB - pozostałe
2 typy
Per-session (do zamkniecia okna)
Per-domain
Tylko string, ale można serializować do JSON
Tylko klient – nie przesyłane do serwera
Można dodać np. jako parametr wywołania usługi WCF
HTML 5 Web Storage
<script type="text/javascript">
sessionStorage.setItem('mykey', 'this is my value');
var item = sessionStorage.getItem('mykey')
</script>
37. Kernel-mode driver - http.sys
Znacznie szybszy (bez context switching, itd.)
Domyślnie włączony dla zawartości statycznej
Dla dynamicznej – OutputCache, ustawienie IIS
lub applicationHost.config
Kiedy nie działa (najczęstsze)
Zawartość z querystring
Domyślny dokument (d.com zamiast d.com/default.htm)
Włączona kompresja
Domyślnie sliding, 120 sekund
HKLMSystemCurrentControlSetServicesHttpParametersUriScavengerPeriod
Warto zmienić w razie potrzeby (np. kilkanaście godzin) - ostrożnie
Windows Kernel Cache
38. Domyślnie włączony – jeśli ustawione OutputCache
Także dla żądań z querystring
Dla własnych HttpHandlerów – włączyć User Mode Caching
http://d.com/katalog/ zamiast http://d.com/katalog
W przeciwnym wypadku HTTP 302
Zapobiega cache’owaniu 3 różnych wersji
IIS Cache
39. Podobnie jak IIS, ale VaryByParam może być custom
Np. kontekst użytkownika
Fragment Cache – OutputCache w User Control
Substitution Cache
Cała strona cache’owana, oprócz <asp:Substitution>
Cache ASP
.NET
<body>
Cached time: <%= DateTime.Now.ToString() %>
<br />Page time:
<asp:Substitution ID="sub" runat="server" MethodName="SubTime" />
</body>
public static string SubTime(HttpContext context)
{
return DateTime.Now.ToString();
}
40. Plik
Baza danych
Konieczne włączenie Service Broker
Cache Dependency
CacheDependency depend = new CacheDependency(this.MapPath("~/depend.txt"));
this.CachePolicy.Dependency = depend;
using (SqlConnection conn = new SqlConnection(cs))
{
string sql = "dbo.GetInfo";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
conn.Open();
SqlCacheDependency dep = new SqlCacheDependency(cmd);
mygrid.DataSource = cmd.ExecuteReader();
mygrid.DataBind();
this.Response.AddCacheDependency(dep);
}
}
// SqlDependency.Start() w web.config
41. Np. AppFabric
Cache Provider
public class MemoryCacheProvider : OutputCacheProvider
{
public override object Add(string key, object entry, DateTime utcExpiry)
{
object result = HttpRuntime.Cache[key];
if (result == null)
{
this.Set(key, entry, utcExpiry);
result = entry;
}
return result;
}
public override object Get(string key)
{
return HttpRuntime.Cache[key];
}
public override void Remove(string key)
{
HttpRuntime.Cache.Remove(key);
}
public override void Set(string key, object entry, DateTime utcExpiry)
{
HttpRuntime.Cache.Insert(key, entry, null, utcExpiry,
Cache.NoSlidingExpiration, CacheItemPriority.High, null);
}
}
<system.web>
<caching>
<outputCache>
<providers>
<add name="MemoryCacheProvider" type="Samples.MemoryCacheProvider" />
</providers>
</outputCache>
</caching>
</system.web>
42. Kiedy chcemy jak najwięcej, jak najdłużej (na ile pamięć pozwoli)
Omijamy logikę Cache Policy - szybkość
Nie dla web farm
WeakReference i GC
Statyczne pola – uproszczony cache
public static class Weak
{
public static WeakReference MyItem { get; set; }
public static readonly Object lockObject = new Object();
public static DataSet WeakData()
{
DataSet ds = null;
lock (lockObject)
{
if (MyItem != null)
ds = MyItem.Target as DataSet;
if (ds == null)
{
ds = new DataSet();
MyItem = new WeakReference(ds);
}
}
return ds;
}
}
44. Domyślnie – jeden proces w3wp.exe
AppPool - kilka procesów dla kolekcji aplikacji (Web
Garden)
Maximum Worker Processes
AppPool Recycling
Tracimy cache, inproc session, static, …
Domyślnie 29h
Warto zmienić na określoną godzinę lub liczbę
obsłużonych requestów (NLB)
App Pool
45. Wiele AppPools
Dzielenie aplikacji na kilka puli
Błąd w jednej puli nie ma wpływu na działanie drugiej
Np. obsługa magazynu i aplikacja dla konsumentów
Web Garden
Ochrona przed błędami procesu w3wp – kilka procesów
Uwaga - context-switching między wątkami szybszy niż między procesami!
Konieczna duplikacja w pamięci danych takich jak cache czy pola static
Zalecane max 1-2 worker procesy / CPU core
Tylko, kiedy najistotniejsza ciągłość pracy
Kiedy najważniejsze jest działanie
46. ASP
.NET – ISAPI Extension
Duplikacja funkcjonalności
2 odrębne pipeline’y
Np. Logowanie, auth -> IIS, później ASP
.NET
Trudniejsza konfiguracja
IIS6 + ASP
.NET (Classic mode)
Authentication
Basic NTLM Anon
...
Determine
Handler
...
SendResponse
HTTP
Request
HTTP
CGI
Static
File
ISAPI
Compre
ssion
Log
aspnet_isapi.dll
Authentication
Map
Handler
Forms Windows
...
ASPX
Trace
...
...
48. Handler HTTP
Mapowany dla rozszerzeń (np. aspx)
Migracja z html do dynamic ->
można dodać mapowanie htm na
ASP
.NET
Moduły HTTP
ASP
.NET bezpośrednio w IIS Pipeline
Np. sesja, uwierzytelnianie
dla zawartości statycznej, php, itp.
Rozszerzalność przez .NET,
a nie ISAPI (C++)
Usuwać niepotrzebne moduły
Kolejność typów dokumentów w IIS
IIS 7 + ASP
.NET - Integrated Pipeline
ISAPI
Authentication
...
ExecuteHandler
...
SendResponse
Authorization
UpdateCache
ResolveCache
HTTP
Request
Anon
aspnet_isapi.dll
Authentication
Map
Handler
...
...
Forms Windows
ASPX
Trace
...
Basic
Compre
ssion
Log
Static
File
49. Nowe w Windows Server 2008
Rezerwacja minimalnej ilości pamięci lub CPU dla procesów
Windows System Resource Manager
50. Bezpieczeństwo i mniej pobieranych danych
X-Powered-By – IIS
Nagłówek Server – tylko z kodu
Etag – przy web farm można wyłączyć (często jest różny dla node’ów)
X-Aspnet-Version - <httpRuntime enableVersionHeader="false"/>
Usuwanie nagłówków
public class HttpHeaderCleanup : IHttpModule
{
public void Init(HttpApplication context)
{
context.PreSendRequestHeaders += OnPreSendRequestHeaders;
}
void OnPreSendRequestHeaders(object sender, EventArgs e)
{
HttpResponse response = HttpContext.Current.Response;
response.Headers.Remove("Server");
response.Headers.Remove("ETag");
}
public void Dispose()
{
}
}
52. Lepsze dla SEO
http.sys nawet przy querystringach
Krótsze URLe w odpowiedziach
Ukrywa technologię aplikacyjną
IIS (URL Rewrite) lub ASP
.NET
URL Rewriting
53. Pamiętać o botach (robots.txt,
sitemap.xml)
Bandwidth Throttling
Moduł BitRateThrottling do IIS
Głównie dla mediów, ale również można
do innych typów danych
Np. wolniej dla botów lub kawałek
dużego zdjęcia szybko
Szybkość transferu
public class Throttle : IHttpModule
{
public void Init(HttpApplication context)
{
context.PostRequestHandlerExecute += OnPostRequestHandlerExecute;
}
void OnPostRequestHandlerExecute(object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
HttpResponse response = context.Response;
if (response.ContentType == "application/x-zip-compressed")
{
HttpRequest request = context.Request;
if (!String.IsNullOrEmpty(request.ServerVariables["SERVER_SOFTWARE"]))
{
request.ServerVariables["ResponseThrottler-InitialSendSize"] = "20";
request.ServerVariables["ResponseThrottler-Rate"] = "10";
}
}
}
public void Dispose()
{
}
}
57. Domyślnie 12 / CPU
Czasem zwiększenie liczby pomaga
Wiąże się z tym koszt (start, pamięć, context switch)
Ważniejsza optymalizacja istniejących
Blokujące wywołania – baza, zewnętrzne usługi, I/O
Przy obliczeniach (CPU) nie pomoże!
Worker Threads
59. Asynchronicznie – web forms (APM)
<%@ Page Async="true„ AsyncTimeout=„30” Language="C#" AutoEventWireup="true" CodeFile="sql-async.aspx.cs" Inherits="sql_async" %>
public const string ConnString = "Data Source=.;Integrated Security=True;Async=True";
protected void Page_Load(object sender, EventArgs e)
{
PageAsyncTask pat = new PageAsyncTask(BeginAsync, EndAsync, null, null, true);
this.RegisterAsyncTask(pat);
}
private IAsyncResult BeginAsync(object sender, EventArgs e, AsyncCallback cb, object state)
{
SqlConnection conn = new SqlConnection(ConnString);
conn.Open();
SqlCommand cmd = new SqlCommand("WAITFOR DELAY '00:00:01'", conn);
IAsyncResult ar = cmd.BeginExecuteNonQuery(cb, cmd);
return ar;
}
private void EndAsync(IAsyncResult ar)
{
using (SqlCommand cmd = (SqlCommand)ar.AsyncState)
{
using (cmd.Connection)
{
int rows = cmd.EndExecuteNonQuery(ar);
}
}
}
60. Task – zaczyna od razu
APM – kolejkuje do async point
Asynchronicznie – web forms (Task)
public const string ConnString = "Data Source=.;Integrated Security=True;Async=True";
protected async void Page_PreRender(object sender, EventArgs e)
{
using (SqlConnection conn = new SqlConnection(ConnString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("WAITFOR DELAY '00:00:01'", conn))
{
await Task.Factory.FromAsync<int>(cmd.BeginExecuteNonQuery,
cmd.EndExecuteNonQuery, null);
}
}
}
61. Najlepiej agregować (np. kilka zapytań w procedurze skł.)
Wywoływane równolegle lub jedno po drugim
Asynchroniczne strony – wiele zadań
protected void Page_Load(object sender, EventArgs e)
{
// Dwa pierwsze – równolegle. Następnie BeginAsync3 (ostatni parametr PageAsyncTask)
PageAsyncTask pat = new PageAsyncTask(BeginAsync1, EndAsync1, null, null, true);
this.RegisterAsyncTask(pat);
pat = new PageAsyncTask(BeginAsync2, EndAsync2, null, null, true);
this.RegisterAsyncTask(pat);
pat = new PageAsyncTask(BeginAsync3, EndAsync3, null, null, false);
this.RegisterAsyncTask(pat);
}
62. Czasem potrzeba zarejestrowania zadania po zakończeniu poprzedniego
Np. drugie wywołanie usługi po zakończeniu poprzedniego
Konieczne wywołanie ExecuteRegeisteredAsyncTasks()
Można także wywołać, aby wymusić wywołanie przed async point
Dla Task – zwyczajnie jeden po drugim z await
Późniejsza rejestracja
private void EndAsync(IAsyncResult ar)
{
using (SqlCommand cmd = (SqlCommand)ar.AsyncState)
{
using (cmd.Connection)
{
int rows = cmd.EndExecuteNonQuery(ar);
}
}
PageAsyncTask pat = new PageAsyncTask(BeginAsync2, EndAsync2, null, null, true);
this.RegisterAsyncTask(pat);
this.ExecuteRegisteredAsyncTasks();
}
63. Usługi Pliki
Inne operacje
protected async void Page_Load(object sender, EventArgs e)
{
var terra = new TerraServiceSoapClient();
Place place = new Place()
{
City = "Seattle",
State = "WA",
Country = "US"
};
var result = await terra.GetPlaceFactsAsync(place);
PlaceFacts facts = result.Body.GetPlaceFactsResult;
this.LA.Text = String.Format("Latitude: {0:0.##}", facts.Center.Lat);
this.LO.Text = String.Format("Longitude: {0:0.##}", facts.Center.Lon);
}
// Dla web requestów:
// var r = WebRequest.Create(„http://…”);
// var res = await r.GetResponseAsync();
private IAsyncResult BeginAsync(object s, EventArgs e, AsyncCallback cb, object s)
{
FileStream fs = new FileStream(this.Server.MapPath("csg.png"),
FileMode.Open, FileAccess.Read, FileShare.Read, 4096,
FileOptions.Asynchronous | FileOptions.SequentialScan);
this.Data = new byte[64 * 1024];
IAsyncResult ar = fs.BeginRead(this.Data, 0, this.Data.Length, cb, fs);
return ar;
// dla podejścia Task:
// int size = await fs.ReadAsync(this.Data, 0, this.Data.Length);
}
64. Async controller – przed MVC 4
public class WeatherAsyncController : AsyncController
{
private string _forecastUrl = "http://weather.yahooapis.com/forecastjson?w=523920&u=c";
public void IndexAsync()
{
var webClient = new WebClient();
AsyncManager.OutstandingOperations.Increment();
webClient.DownloadStringCompleted += (sender, evt) =>
{
// capture result when web service completes
AsyncManager.Parameters["json"] = evt.Result;
AsyncManager.OutstandingOperations.Decrement();
// Exception handling - challenge...
};
// async call
webClient.DownloadStringAsync(new Uri(_forecastUrl));
}
public ActionResult IndexCompleted(string json)
{
WeatherData weather = new JavaScriptSerializer().Deserialize<WeatherData>(json);
return View("Weather", weather);
}
}
65. Async controller – MVC 4
public class WeatherTaskAsyncController : AsyncController
{
private string _forecastUrl = "http://weather.yahooapis.com/forecastjson?w=523920&u=c";
public async Task<ActionResult> Index()
{
string json = await new WebClient().DownloadStringTaskAsync(_forecastUrl);
WeatherData weather = new JavaScriptSerializer().Deserialize<WeatherData>(json);
return View("Weather", weather);
}
}
66. Możliwość wywołania kodu równolegle
Po wyrenderowaniu strony lub w trakcie
ThreadPool.QueueUserWorkItem()
Zużywa wątki z AppPool
1 wątek w tle + kolejka
Typowy consumer - producer
Np. logowanie (nie wymagane 100% działanie)
Bardziej krytyczne zadania – np. Service Broker lub Azure Worker + kolejka
Przy wielu wątkach – warto ReaderWriterLockSlim
EnterReadLock / EnterWriteLock (mniejsze ryzyko zakleszczenia)
Przykład
Background Worker Thread
67. InProc
Najszybsza, ale load balancer tylko sticky
Nie odporna na awarie sprzętu
StateServer
Web farm – ok, ale single point of failure
SQL (+SQL Agent do usuwania)
Możliwe przyspieszenie przez <sessionState compressionEnabled=„true” />
<%@ Page EnableSessionState=„false” @> - ale i tak update (timeout)
<%@ Page EnablesessionState=„ReadOnly” @> - mniej locków; ta sama SP
, która odczytuje
Przyspieszyć zapis do logu (podział na dyski, SSD, itp.)
PartitionResolver (wybór conn str na podst id sesji) i SessionIDManager (id sesji z nr maszyny)
Mimo wszystko niezbyt skalowalne – patrz: AppFabric session provider
Sesja
68. Control ID
Możliwość kontroli identyfikatorów (klienckich) kontrolek serwerowych
Control.ClientIdMode
Legacy
Static
Predictable
Inherit (domyślne dla kontrolek)
Kolekcje – ClientIDRowSuffix
Dla całej strony lub kontrolki
Ostrożnie – duplikacja, długość
69. Możliwość zmiany markupu kontrolki serwerowej
Np. GridView nie z tabelkami, ale na floatach
Control Adapter
// Zamiana URL w Image na małe litery
public class ImageControlAdapter : WebControlAdapter
{
public ImageControlAdapter()
{
}
protected override void BeginRender(System.Web.UI.HtmlTextWriter writer)
{
Image image = Control as Image;
if ((image != null) && !String.IsNullOrEmpty(image.ImageUrl))
{
if (!image.ImageUrl.StartsWith("http") && !image.ImageUrl.StartsWith("data:"))
{
image.ImageUrl = this.Page.ResolveUrl(image.ImageUrl).ToLower();
}
}
base.BeginRender(writer);
}
}
// adapter.browser
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.WebControls.Image"
adapterType="Samples.ImageControlAdapter" />
<adapter controlType="System.Web.UI.WebControls.Panel"
adapterType="Samples.NoIdControlAdapter" />
<adapter controlType="System.Web.UI.WebControls.Label"
adapterType="Samples.NoIdControlAdapter" />
<adapter controlType="System.Web.UI.WebControls.HyperLink"
adapterType="Samples.NoIdControlAdapter" />
</controlAdapters>
</browser>
</browsers>
70. Np. wyłączenie
domyślnego
generowania ID
Control Adapter c.d. – usuwanie ID
public class NoIdControlAdapter : WebControlAdapter
{
protected override void Render(HtmlTextWriter writer)
{
PageBase page = this.Page as PageBase;
if ((page != null) && page.RemoveIds &&
(this.Control.ClientIDMode != ClientIDMode.Static))
{
HtmlTextWriter noIdwriter = new NoIdHtmlWriter(writer);
base.RenderBeginTag(noIdwriter);
base.RenderContents(writer);
base.RenderEndTag(noIdwriter);
}
else
{
base.Render(writer);
}
}
}
public class NoIdHtmlWriter : HtmlTextWriter
{
public NoIdHtmlWriter(TextWriter writer)
: base(writer)
{
}
public override void AddAttribute(HtmlTextWriterAttribute key, string value)
{
if (key != HtmlTextWriterAttribute.Id)
base.AddAttribute(key, value);
}
}
public class PageBase : Page
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
this.RemoveIds = true;
}
public bool RemoveIds { get; set; }
}
71. Np. do zwracania zapisanego w bazie HTML
Bez cyklu życia ASP
.NET – szybciej
Asynchronicznie – uczestniczy w każdym żądaniu!
IsReusable – czy jedna instancja może być dla wielu żądań
HTTP Handler w .NET
<%@ WebHandler Language="C#" Class="Handler" %>
using System;
using System.Data;
using System.Web;
using System.Data.SqlClient;
public class Handler : IHttpAsyncHandler {
public const string ConnString =
"Data Source=.;Initial Catalog=Sample;Integrated Security=True;Async=True";
HttpContext Context { get; set; }
public void ProcessRequest(HttpContext context)
{
}
public IAsyncResult BeginProcessRequest(HttpContext context,
AsyncCallback cb, object extraData)
{
this.Context = context;
int fileid = 0;
string id = context.Request.QueryString["id"];
if (!String.IsNullOrEmpty(id))
72. Krótsze adresy w kodzie
http.sys caching
Łączenie np. z ASP
.NET MVC (MapRoute / MapPageRoute)
Routing
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.Add("Category", new Route("{category}", new PageRouteHandler("~/1.3 Routing.aspx")));
RouteTable.Routes.Add("CategoryAndPage", new Route("{category}/{page}", new PageRouteHandler("~/1.3 Routing.aspx")));
}
protected void Page_Load(object sender, EventArgs e)
{
lblCategory.Text = RouteData.Values["category"] as string;
lblPage.Text = RouteData.Values["page"] as string;
}
73. Klasyczne
this.Response.Redirect("~/pages/error.aspx", true);
True – czy zakończyć request
this.Response.RedirectPermanent
this.Response.RedirectToRoute
Server.Transfer(„~/pages/error.aspx”, false)
False – czy przekazać parametry z obecnej strony
True – dla ViewState wyrzuci exception; tylko dla querystring
Cross-page postback
Przekierowania
<asp:Button runat="server" PostBackUrl="~/pages/otherpage.aspx" Text="Submit" />
74. Zawartość z Render() buforowana i zwracana na koniec
Dla długotrwałych zadań może wymagać czasu
Najlepiej – z Ajax
Response.Flush() – wysłanie odpowiedzi z bufora
Przed Render() nic w nim nie ma!
Wczesny flush odpowiedzi
75. W OnPreRender
Ręczny zapis odpowiedzi
Wyrenderowanie wybranych kontrolek serwerowych
Usunięcie wyrenderowanych kontrolek (aby nie było duplikacji po Render)
Długotrwałe zadanie w OnPreRender (async) lub synchronicznie po
PreRender (po async point)
Wczesny flush – c.d.
<html>
<head id="Head1" runat="server">
<title></title>
<script type="text/javascript" src="test.js"></script>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label runat="server" ID="test" Text="testing" />
</div>
</form>
</body>
</html>
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
this.Response.Write("<!DOCTYPE html PUBLIC " +
""-//W3C//DTD XHTML 1.0 Transitional//EN" " +
""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">n");
this.Response.Write("<html xmlns="http://www.w3.org/1999/xhtml">n");
HtmlTextWriter writer = this.CreateHtmlTextWriter(this.Response.Output);
this.Header.RenderControl(writer);
writer.Flush();
this.Response.Flush();
this.Controls.Remove(this.Header);
Thread.Sleep(2000);
}
77. Minification dla dynamicznych
public class MinifyStream : Stream
{
private StreamWriter Writer { get; set; }
private Decoder Utf8Decoder { get; set; }
public MinifyStream(Stream stream)
{
this.Writer = new StreamWriter(stream, Encoding.UTF8);
this.Utf8Decoder = Encoding.UTF8.GetDecoder();
}
public override void Write(byte[] buffer, int offset, int count)
{
int characterCount = this.Utf8Decoder.GetCharCount(buffer, offset, count);
char[] result = new char[characterCount];
int decodedCount = this.Utf8Decoder.GetChars(buffer, offset, count, result, 0);
if (decodedCount <= 0)
return;
// ... wyfiltrowanie zbędnych znaków i zapis do this.Writer
}
public override void Close()
{
this.Writer.Flush();
this.Writer.Close();
base.Close();
}
public override void Flush()
{
this.Writer.Flush();
}
// ...
}
// W module HttpModule
private void Sample_PostRequestHandlerExecute(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpResponse response = application.Context.Response;
if(response.ContentType == "text/html")
response.Filter = new MinifyStream(response.Filter);
}
78. Page.IsPostBack
Rozpoznawanie odświeżenia strony
Unikanie niepotrzebnej pracy
protected virtual bool IsRefresh
{
get
{
return this.Request.Headers["Pragma"] == "no-cache" ||
this.Request.Headers["Cache-Control"] == "max-age=0";
}
}
79. Response.IsClientConnected
Czy przeglądarka jeszcze czeka na odpowiedź, czy została zamknięta?
Warto sprawdzić przed długotrwałą operacją na bazie
<compilation batch=true />
True – niewiele pakietów
False – każda strona w osobnym assembly (wolniej, ale łatwiej zmiany)
Wyłączyć debug mode!
Można wymusić w machine.config
Inne
<system.web>
<deployment retail="true" />
</system.web>