Søger du en specifik kategori?

 



Oprettet tir. d. 03. juli 2012 kl. 17:56

softspot
softspot (106.629 point)
blog.softspot.dk
Guidens karaktér
1
2
3
4
5
Mangler vurderinger

Caching i ASP 3.0

Når man arbejder med større mængder forespørgsler i et system, drejer det sig om at gøre hver forespørgsel færdig så hurtigt som muligt og med så få resurser som muligt. Hvis du tilfældigvis arbejder med ASP 3.0, har jeg et bud på, hvordan du kan komme næ
Revisionshistorik:
13-06-2012: artikel oprettet
14-06-2012: tilføjelse af overvejelse ifm. webgardens og webfarms.
03-07-2012: ændring i sektion vedr. udskiftning af caching-container.

Introduktion
Nu er caching, såvidt jeg ved, ikke understøttet pr. default i ASP 3.0, så derfor må man enten have fat i en 3. partskomponent, eller flikke sin egen løsning sammen. Jeg har gjort det sidste :-)

Løsningen baserer sig på kendte teknikker til at gemme oplysninger på tværs af forespørgsler. Jeg har blot forsøgt at pakke tingene ind, så det ikke er så omstændigt at benytte caching. Et groft bud på, hvordan man kan cache ting i ASP 3.0 kunne være:

session("opdateret") = now

Denne kan aflæses således:

opdateret = session("opdateret")

Denne metode har dog nogle ulemper. Dels binder man sig til session som caching-container alle steder i koden, hvor cachen benyttes, dels er der ikke nogen styring af, hvor gammel indholdet i cachen må være, før det udløber.

Indpakning af afhængighed
Hvis man skal bløde op på den første ulempe, kunne cachingmekanismen pakkes ind i en funktion, således referencen til session kun findes to steder (get- og set-funktionerne) og dermed relativt let kan udskiftes, hvis det skulle blive nødvendigt.

function GetCache(key)
  GetCache = session(key)
end function

function SetCache(key, value)
  session(key) = value
end function

Nu kan man i sin kode sætte og læse cache-værdier således:

SetCache "opdateret", now

Ligeledes kan de hentes således:

opdateret = GetCache("opdateret")

Nu er dette jo ikke imponerende, selvom det kan spare noget arbejde, hvis man vil udskifte caching-containeren, som illustreret senere.

Forældelse af cache
Den anden ulempe med de hidtil demonstrerede løsninger er at cachen ikke udløber og data derfor kan blive noget forældet med tiden. Dette kan håndteres ved at gemme lidt metadata om den cachede værdi. Dette kunne f.eks. være tidspunktet for hvornår værdien blev gemt.

function SetCache(key, value)
  session(key) = value
  session(key & "$metadata") = now
end function

Nu kan vi ifm. aflæsningen vurdere om værdien er forældet.

function GetCache(key)
  dim setTime

  setTime = session(key & "$metadata") & ""
  if setTime = "" then
    GetCache = Empty
    exit function
  end if

  if dateadd("s", 300, setTime) > now then
    ' værdien er stadig frisk (nok)
    GetCache = session(key)
  else
    ' værdien er forældet
    GetCache = Empty
  end if
end function

Ovenstående cacher alle værdier i 300 sekunder (5 minutter) og det kan jo være fint nok, men det ville måske være rart selv at have lidt kontrol over hvor lang tid en værdi tager om at blive forældet. Derfor udvides funktionerne til også at modtage information om forældelsesfrist.

function SetCache(key, value, expireSeconds)
  session(key) = value
  session(key & "$metadata") = now & ";" & expireSeconds
end function

Nu kan vi ifm. aflæsningen vurdere om værdien er forældet.

function GetCache(key)
  dim metadata, setTime, expires

  metadata = session(key & "$metadata") & ""
  if metadata = "" then
    GetCache = Empty
    exit function
  end if

  arrMetadata = split(metadata, ";")
  setTime = arrMetadata(0)
  expires = arrMetadata(1)

  if dateadd("s", expires, setTime) > now then
    ' værdien er stadig frisk (nok)
    GetCache = session(key)
  else
    ' værdien er forældet
    GetCache = Empty
  end if
end function

Nu kan cachen sættes således:

SetCache "opdateret", now, 1000

Når den hentes skal der så tages højde for om cachen er frisk eller ej. Hvis den ikke er frisk returneres Empty og dette kan bruges som kriterium for at genopfriske cachen.

opdateret = GetCache("opdateret")
if isEmpty(opdateret) then
  opdateret = now
  SetCache "opdateret", opdateret, 1000
end if
Response.Write opdateret


Udløb efter relevans
Fint nok! Nu er der mulighed for at angive hvor lang tid en værdi er relevant, men nogle gange kunne det være rart, at de værdier som bliver brugt ofte ikke udløber, men at de de ikke bliver brugt gør. Dette omtales ofte på engelsk som "sliding expiration", det jeg har valgt at kalde "udløb efter relevans".

Dette kan f.eks. håndteres ved at angive, om en værdi kun skal udløbe, hvis den ikke bliver brugt indenfor den specificerede udløbsperiode. Cache-funktionerne tilpasses således:

function SetCache(key, value, expireSeconds, sliding)
  dim sval
  if sliding then
    sval = "S"
  else
    sval = "A"
  end if
  session(key) = value
  SetCacheMetadata key, expireSeconds, sval
end function

Aflæsningen rettes nu til også at opdatere tidspunktet for værdien hver gang den aflæses, hvis der er valgt "udløb efter relevans".

function GetCache(key)
  dim metadata, setTime, expires, sliding

  metadata = session(key & "$metadata") & ""
  if metadata = "" then
    GetCache = Empty
    exit function
  end if

  arrMetadata = split(metadata, ";")
  setTime = arrMetadata(0)
  expires = arrMetadata(1)
  sliding = arrMetadata(2)

  if dateadd("s", expires, setTime) > now then
    ' værdien er stadig frisk (nok)
    GetCache = session(key)
    if sliding = "S" then
      SetCacheMetadata key, expires, sliding
    end if
  else
    ' værdien er forældet
    GetCache = Empty
  end if
end function

sub SetCacheMetadata(key, expires, sliding)
  session(key & "$metadata") = "" & _
          now & ";" & _
          expires & ";" & _
          sliding
end sub

Da jeg vil undgå at indføre for meget redundans i min kode, har jeg indført en lille "service"-funktion (SetCacheMetadata) til min caching-løsning, nemlig en funktion til at sætte metadata, da de nu både skal sættes i SetCache og GetCache.

Udskiftning af cache-container
Nu bliver det jo spændende, for vi har lige fundet den fedeste komponent til caching, men vi har kald til caching spredt ud over 1 mia asp-filer. Så er det jo fedt at vi pakket caching-mekanismen ind i 2-3 funktioner, som bare kan ændres og så er vi kørende. Nu HAR jeg ikke lige en fed caching-komponent, men vi kan jo prøve at ændre vores caching fra at være lokal pr. bruger til at være global for alle brugere. Til dette kan vi benytte Application-objektet i stedet for Session. Samtidig vil jeg lige sørge for at udskiftning af caching-container kun skal gøre ét sted i stedet for 4-5 steder.

dim cache
set cache = application


function SetCache(key, value, expireSeconds, sliding)
  dim sval
  if sliding then
    sval = "S"
  else
    sval = "A"
  end if
  cache(key) = value
  SetCacheMetadata key, expireSeconds, sval
end function

function GetCache(key)
  dim metadata, setTime, expires, sliding

  metadata = cache(key & "$metadata") & ""
  if metadata = "" then
    GetCache = Empty
    exit function
  end if

  arrMetadata = split(metadata, ";")
  setTime = arrMetadata(0)
  expires = arrMetadata(1)
  sliding = arrMetadata(2)

  if dateadd("s", expires, setTime) > now then
    ' værdien er stadig frisk (nok)
    GetCache = cache(key)
    if sliding = "S" then
      SetCacheMetadata key, expires, sliding
    end if
  else
    ' værdien er forældet
    GetCache = Empty
  end if
end function

sub SetCacheMetadata(key, expires, sliding)
  cache(key & "$metadata") = "" & _
          now & ";" & _
          expires & ";" & _
          sliding
end sub

Det var jo ikke så galt! Nu kan vi desuden bare skifte caching-container ved at ændre referencen i cache.

Håndtering af objekter
Normalt vil jeg ikke anbefale at denne cache benyttes alt for intensivt til objekter, men det kan til tider være relevant at gemme et objekt i en periode (f.eks. en XML-fil med sprogtekster). Dette kan vores caching-mekaniske pt. ikke håndtere, men vi kan tilpasse den lidt, så den kan.

function SetCache(key, value, expireSeconds, sliding)
  dim sval
  if sliding then
    sval = "S"
  else
    sval = "A"
  end if
 
  if isObject(value) then
    set cache(key) = value
  else
    cache(key) = value
  end if

  SetCacheMetadata key, expireSeconds, sval
end function

function GetCache(key)
  dim metadata, arrMetadata, setTime, expires, sliding

  metadata = cache(key & "$metadata") & ""
  if metadata = "" then
    GetCache = Empty
    exit function
  end if

  arrMetadata = split(metadata, ";")
  setTime = arrMetadata(0)
  expires = arrMetadata(1)
  sliding = arrMetadata(2)

  if dateadd("s", expires, setTime) > now then
    ' værdien er stadig frisk (nok)
    if isObject(cache(key)) then
      set GetCache = cache(key)
    else
      GetCache = cache(key)
    end if


    if sliding = "S" then
      SetCacheMetadata key, expires, sliding
    end if
  else
    ' værdien er forældet
    GetCache = Empty
  end if
end function

sub SetCacheMetadata(key, expires, sliding)
  cache(key & "$metadata") = "" & _
          now & ";" & _
          expires & ";" & _
          sliding
end sub

Oprydning i cachen
I takt med at cachen bliver brugt, ophober der sig en del værdier i cachen som er udløbet. Nogle vil blive overskrevet med friske værdier, mens andre bare vil ligge i cachen indtil caching-containeren (session eller application i ovenstående eksempler) nulstilles. Dette kan godt (og vil givetvis) blive et problem over tid, så man bør overveje en oprydningsfunktion der køres fra tid til anden. Noget i stil med dette:

function CleanupCache()
  dim metadata, arr, setTime, expires, keylen, key
  keylen = len("$metadata")
  for each key in application.contents
    if right(key, keylen) = "$metadata" then
      metadata = application(key) & ""
      arr = split(metadata, ";")
      setTime = arr(0)
      expires = arr(1)
      if dateadd("s", expires, setTime) < now then
        application.Contents.Remove key
        application.Contents.Remove mid(key, 1, len(key) - keylen)
      end if
    end if
  next
end function

Et eller andet sted i systemet (f.eks. i forbindelse med caching-funktionerne) kan man så lave flg. kode:

cleanupts = application("cleanupts")
if not isEmpty(cleanupts) then
  doClean = dateadd("s", 120, cleanupts) < now
else
  application("cleanupts") = now
  doClean = false
end if

if doClean then
  application("cleanupts") = now
  CleanupCache
end if

Denne tjekker om det er tid til at udføre en oprydning i cachen. Ovenstående rydder op hver andet minut (eller første gang koden køres efter 2 minutter er gået). Dette kan naturligvis sættes efter behov.

Overvejelser
Hvis man benytter application som caching-container, skal man være opmærksom på at objekter ikke uden videre spiller sammen med denne. Hvis man ønsker at gemme objekter i application-objektet, skal man sørge for at det er et MTA-objekt, dvs. et flertrådet objekt. Hvis man forsøger at lægge et STA-objekt (enkelttrådet) i application, får man en kørselsfejl smidt i nakken.

Ovenstående implementering udfører ikke konsistent caching i webgardens eller webfarms, da hverken application- eller session-objektet pr. default understøttes på tværs af workerprocesser. Hvis man ønsker caching i sådan et setup, er man nok nød til at finde en professionel cachingløsning i stedet for den hjemmestrikkede udgave jeg har illustreret  (tak til Arne for den påmindelse).

Ud over det, kunne der arbejdes videre med at pakke caching-mekanismen ind i en class, men den øvelse vil jeg pt. overlade til dem der finder dette tiltag relevant... :-)

Happy Caching!

Skrevet tor. d. 14. juni 2012 kl. 11:37| #1

arne_v (1.050.303 point)
Husk også at overveje konsekvenserne hvis man har to servere som man loadbalancer.

Skrevet tor. d. 14. juni 2012 kl. 13:10| #2

softspot (106.629 point)
blog.softspot.dk
Tak for den påmindelse, Arne. Jeg har tilføjet en kommentar til dette under overvejelser.

Skrevet ons. d. 05. september 2012 kl. 08:04| #3

Det er ret smart, tak :)

Skriv en kommentar



Mest populære guides

Guidens karakter
!!!Karaktér: 3
21 stemmer
03/02 - 2009
Af: old-faithful

At finde en anden persons IP-adresse

Enhver Internetbruger har en IP-adresse, der i princippet kan spores tilbage til hans fysiske adresse. Artiklen ser nærmere på nogle metoder til at finde andres IP-adresse (fx en tyv, der har stjålet din PC), samt...
Guidens karakter
!!!Karaktér: 3
23 stemmer
02/02 - 2009
Af: old-faithful

Introduktion til trådløst netværk og access points

Artiklen introducerer grundbegreberne bag trådløse netværk. Standarder, sikkerhed og rækkevidde gennemgås. Artiklen kan også bruges til at danne et overblik over begreber, inden et eventuelt køb af access point.
Guidens karakter
!!!Karaktér: Mangler flere stemmer
Mangler stemmer
27/03 - 2013
Af: samohtrelhe

UKASH, "Politi virus" Fjernelse på den simple måde:

Hej Alle Mange porno surfere er i tiden ramt af UKASH eller "Politi Virus" programmet/Scam'en Det er nogen vældig avancerede løsninger der anvendes på denne super primitive scam, synes jeg. Den...
Virus  |  Læs »

Log ind

   








IT Kurser
Samarbejdspartnere

Udgiver · © 2013 Computerworld A/S · Hørkær 18 · 2730 Herlev · Tlf.: 77 300 300 · Fax: 77 300 301 · Brug af personoplysninger