I takt med att en serverapplikation blir allt mer framgångsrik aktualiseras problemet med skalbarhet. Till en början går det bra att bara bygga ut servern med mer minne och kraftigare mikroprocessorer, men det kan vara en väldigt kostsam väg att gå, och det går inte att komma hur långt som helst på den vägen heller. Det uppenbara alternativet är istället att köpa flera billigare servrar och låta dem dela på lasten. Det är här begreppet klustring kommer in! I den här artikeln kommer vi att ge en översiktlig introduktion till klustring av Java-applikationer med Terracotta.

Begreppet ”klustring” används i flera sammanhang och har lite olika definitioner. I den här artikeln använder vi följande betydelse:

En klustrad Java-applikation är en applikation som:

  • körs parallellt i flera VMar som kan ligga på olika fysiska servrar;
  • delar samma kodbas – det är alltså samma applikation som körs samtidigt i flera VMar;
  • på något sätt kommunicerar inbördes för att upprätthålla någon form av gemensamt tillstånd.

Den sista punkten är viktig, eftersom det alltså inte handlar om att ha en tillståndslös applikation som bara körs i flera oberoende instanser, utan snarare en enda applikation som körs på flera servrar samtidigt.

En klustrad applikation kan lätt skala upp prestandan genom att lägga till fler servrar i klustret. Den får också andra egenskaper som är nästan lika viktiga (eller, beroende på applikationen, mycket viktigare), som “hot fail-over”, dvs att en server kan gå sönder eller manuellt stängas av utan att det påverkar applikationens funktion.

Klustring är alltså lite av en “silverkula” när det gäller arkitektur för avancerade serverapplikationer. Men det är (oftast) en funktion som är svår att lägga till i efterhand. Att göra om en befintlig applikation så att den kan klustras på ett effektivt sätt kan kosta många utvecklingstimmar. Ramverket som klustringen bygger på kan antingen byggas själv, eller så kan man köpa in ett sådant. Här finns det flera olika ramverk att välja mellan, ofta inriktade på olika användningsområden, t.ex. “klustring för distribuerad cache”, “klustring för effektiv fail-over” eller “klustring för ren lastdelning av tillståndslösa applikationer”. Även om man väljer ett färdigt ramverk för klustring innebär det troligen många timmars integrationsarbete och det är svårt att komma runt att man i det arbetet knyter sin applikation hårt till detta ramverk.

Men, det finns ändå ett ramverk som är gratis, har öppen källkod och kan ta i princip vilken applikation som helst och klustra den på kort tid – minuter och timmar, i stället för dagar och veckor. Ramverket heter Terracotta, och vi ska snart se hur det går till.

Aspektorientering

Klustring är ett typiskt exempel på ett så kallat “tvärsnittsproblemområde”, dvs en del av en applikation som å ena sidan inte direkt berör huvudproblemområdet (det som applikationen är till för att lösa), men ändå påverkar en stor del – ett tvärsnitt – av hela applikationen. Konventionella objektorienterade tekniker gör det svårt att modellera, modularisera och i slutänden implementera sådana tvärsnittsproblemområden på ett effektivt sätt. Sedan några år tillbaka har en metod att lösa det problemet börjat tillämpas på allvar: aspektorienterad programutveckling (AOP).

AOP är ett ämne som kräver egna artiklar för att behandlas på djupet, och Datormagazin publicerade redan 2002 en introducerande artikel i ämnet (skriven av undertecknad). Läs den här på Stacktrace!

Kortfattat går AOP ut på att modellera och implementera varje tvärsnittsproblemområde separat, och sedan definiera så kallade “aspekter” (engelska: “aspects”) som beskriver i vilka punkter problemområdena möts och på vilket sätt de påverkar varandra. Beskrivningen av dessa punkter kallas “punktsnitt” (eng: “pointcut”) och uttrycks i ett språk som beskriver det programspråk som applikationen är skriven i.

Terracotta drar nytta av aspektorienterad teknik för att “väva in” klustringslogiken utan att det syns i koden för den applikation som ska klustras. Den aspektorienterade lösningen har många fördelar:

  • Applikationsutvecklarna kan fokusera på huvudproblemområdet, dvs det problemområde som applikationen är till för att lösa. Klustringsaspekten kan hanteras separat.
  • Inget nytt API att lära sig, eller som kan förändras när nya versioner av Terracotta kommer
  • En applikation som från början inte var skriven för Terracotta kan integreras med ramverket på avsevärt kortare tid än om Terracotta var designat på konventionellt objektorienterat sätt. I många fall krävs ingen förändring av koden överhuvudtaget och i vissa viktiga specialfall behöver man inte ens ha tillgång till källkoden.

Terracotta

Terracotta är alltså ett aspektorienterat verktyg för att klustra en Java-applikation. Med hjälp av punktsnitt som definieras i en separat XML-fil anges vilka delar av applikationens tillstånd som ska klustras, dvs delas mellan alla körande instanser av applikationen.

Som andra AOP-verktyg lägger sig Terracotta som ett tunt lager mellan Java-VMen och applikationen, och fångar upp när applikationen använder sig av eller ändrar på det klustrade tillståndet. Det lyckas den med genom att modifiera bytekoden i klasserna när de laddas (dvs vid uppstart av applikationen) och lägga in notifieringsanrop runt intressanta områden i applikationskoden.

Tanken med Terracotta är att vilken korrekt kodad multitrådad applikation som helst ska kunna klustras transparent med Terracotta. Man kan se det så att applikationen fortsätter att köra multitrådat, på precis samma sätt som förut, men trådarna kan köras på separata Java-VMar i fysiskt åtskilda servrar.

I princip gäller också samma semantik för en flertrådad Java-applikation som en Terracotta-klustrad sådan: resurser som delas av flera trådar måste skyddas av ett synchronized-block, trådar kan vänta på att ett lås blir tillgängligt med wait(), och kan meddela andra trådar när ett lås blir tillgängligt, med notify() / notifyAll().

Terracotta behöver alltså i princip hålla koll på två saker: vilka synkroniseringsblock som är globala och vilka objekt som ska ha samma tillstånd i hela klustret. Det räcker för att behålla normal Java-semantik i en klustrad miljö.

Ok, dags för ett litet exempel. Antag att vi har ett system för att hantera biljettbokningar för konserter. Databasen har facit över vilka biljetter som är sålda och vilka platser som finns tillgängliga, men för att öka prestandan är det första man gör att introducera en cache som sparar informationen om de femtio mest aktuella arrangemangen. När inte heller det räcker är det dags att klustra…

Antagandet är att det räcker med att klustra cachen. Cache-klassen var naturligtvis redan sedan tidigare trådsäker, eftersom vår en-server-lösning använde sig av ett stort antal trådar för att klara lasten. Det enda som måste göras nu är att göra en Terracotta-konfigureringsfil som pekar ut den java.util.Map som innehåller den cachade datan, och ange att det är synkroniseringsblocken i cache-klassen som ska vara kontrollpunkterna för datan.

Starta sedan upp Terracotta-servern och en eller flera applikations-VMar per server i klustret och du har gått från en knäande serverapplikation till en rejält muskelstark klustrad applikation, som vid behov kan byggas ut med flera noder i klustret på bara några minuter (plus leveranstiden för hårdvaran…). Det utan att ändra en enda kodrad i applikationen!

Andra Terracotta-varianter

Terracotta DSO / POJO (DSO = ”Distributed Shared Objects”) är den generella basen för att klustra Java-applikationer med Terracotta. Men projektet kommer också med speciellt anpassade paketlösningar för vanliga tillämpningar: i första hand för klustring av Tomcat-sessioner och Spring-bönor. Ingen av dessa anpassningar har någon skillnad i Terracotta-basen, utan det handlar snarare om färdiga tc-config.xml-filer.

Med det så avrundar jag den här introduktionen till Terracotta. Jag tänkte återkomma med lite mer konkreta exempel som även inkluderar körbar kod!