Java 9 on kymmenen kertaa nopeampaa!

Törmäsin mielenkiintoiseen ilmiöön taannoin. Valmistelin esitystä suorituskyvystä ja Java muistimallista, ja ajoin eräänlaista benchmark sovellusta Dockerin avulla eri virtuaalikoneissa. Sovelluksen ideana on sisältää aika typerästi kirjoitettua mutta varsin tavanomaista Java-koodia. Silmukoita, ehtolauseita, merkkijonojen käsittelyä, laskentaa, kokoelmien käsittelyä, ja kellottaa paljonko kaikkeen menee aikaa. Tällä voidaan myös nähdä nykyaikaisen virtuaalikoneen itseään optimoiva vaikutus – kun ajo jatkuu, nopeus yleensä kasvaa. Tässä on sovelluksen mittaustuloksia Oracle Java 6 Docker kontissa ajettuna:

Screenshot 2016-10-16 13.19.54.png

Eli, yhden kierroksen aika on n. 84 sekuntia, laskien 82 sekuntiin jahka virtuaalikone vähän ”lämpenee” eli käytännössä jit-kääntää enemmän koodia ja tekee muita tarvittavia optimointeja.

 

Tässä on OpenJDK+Java 8 ajotulokset:

Screenshot 2016-10-16 13.23.31.png

Kuten tuloksista näkyy, uudempi virtuaalikone optimoi usein tehokkaammin. Tässä kierrosajat pyörivät n. 62 sekunnin pinnassa – Java 6 verraten on irronnut noin 20 sekuntia, tai 1/4 suoritusajasta. Paras kierrosaika oli jopa 52 sekuntia. Aika hyvä tulos!

Kokeillaanpa G1 roskankeruualgoritmilla:

Screenshot 2016-10-16 13.35.12.png

Oops! Vaikka G1 on teoriassa kauneinta ja uusinta mitä roskankeruualgoritmeihin tulee, se ei ole joka ongelmaan optimaalinen ratkaisu. Suoritusajat vaihtelevat 98 ja 104 sekunnin välillä ja ovat nousemaan päin. Tällä sovelluksella ja tämän koneen muistilla tässä tuli takapakkia, huonompi suorituskyky. Varmaan tästä syystä G1 ei ole vielä oletusalgoritmina vaan pitää erikseen kytkeä päälle lisäparametrilla -XX:+UseG1GC.

Java 9 julkaistaan vasta pitkällä ensi vuonna. Siitä on kuitenkin jo prereleaseja liikkeellä, ja jopa Docker virtuaalikuva. Tämän ansiosta on lasten leikkiä ajaa sama koodi Java 9:llä. Tulokset tässä:

Screenshot 2016-10-16 13.36.26.png

WUT? Sama koodi, tekee saman asian, sama koneympäristö ja resurssit. Suoritusajat vaihtelevat 9 ja 12 sekunnin  välillä. Karkeasti ottaen noin kymmenen kertaa nopeampaa kuin useimmat muut testiajot, ja yli viisi kertaa nopeampaa kuin paras tulos tähän asti.

Jotain on muuttunut. Mitä, en tiedä vielä. Epäilen että yksi tekijä voi olla Jigsaw moduulimallit. Toinen tekijä lienee, että on taas opittu tunnistamaan joku negatiivinen koodaustapa, ja optimoimaan sen suoritus. Tulokset tuskin ovat yleispäteviä, ne pätevät lähinnä tähän tyypilliseen koodiesimerkkiin mitä käytin, ja tähän ympäristöön. Docker välissä voi myös vaikuttaa jotain, tuskin kuitenkaan paljoa. Niin tai näin, koodi otti taas kerran hurjan tehokkuushypyn. Tätä herkkua olisi luvassa ensi vuonna.

Virtuaalikoneiden ihanuus on siinä, että nautit kaikista edistysaskelista, ilman koodimuutoksiakin. IBM nimesi juuri oman open source JDK 9 versionsa JIT-kääntäjän Testarossaksi, joten veikkaisin että sieltä on myös suurta hyvyyttä tulossa.

p.s. Docker on ihana keksintö!

p.p.s. Niin on Cathode terminaalikin :p

 

Klusteri-uniikit id-arvot Java EE/EJB/JPA

Uniikkien avainten kuten Primary Key-arvojen generointi on hauska juttu. Siihen on tarve lähes joka projektissa, ja siihen on lukemattomia eri tapoja. Yksi hurjimmista on laavalampun kuvioiden käyttö satunnaisluvun generointiin – mutta konservatiivisemmalta puolen löytyy mm. sequence, identity keinot jotka ovat kantakohtaisia. Korkeamman tason abstraktiot kuten JPA abstraktoivat halutessaan myös tämän: GeneratedValue-annotaatio antaa kannan päättää mikä kolmesta id-generointitavasta on fiksuin.

Tähän tulee kuitenkin vähän pykälää lisää jos ei voida syystä tai toisesta käyttää JPA generointia. Kenties halutaan ottaa itse avaimen generointi hallintaan koska siihen liittyy erityissääntöjä. Kenties on tarpeen generoida identity-kentän ohella toinen, uniikki avain joka ei kuitenkaan ole primary key. Pikkasen lisää haastetasoa saadaan jos homma pitää vielä tehdä klusterissa – silloin mikään muistinvarainen ratkaisu ei piisaa – ellei muistia synkronoida verkon yli sopivalla tapaa.

JPA:sta ja Hibernatesta löytyy kyllä valmiina generaattoreita, mutta ainakin JPA standardipuolella pääsy niiden mekanismeihin erillään primary key autogeneroinnista on heikko.Mitä? Minä olen ainakin uniikki!

Joten tähän vähän omia mietelmiäni ja koodia siirrettävästä geneerisestä klusteriystävällisestä primary key generoinnista. Tämä on iteraatio 1 joten päättelyssä ja toteutuksessa voi olla aukkoja, mutta päätin silti paljastaa itseni maailmalle – yksi mukava juttu blogeissa on että niitä voi kommentoida ja palaute on tässäkin tervetullutta jos jokin pistää silmään.

Ensiksi tarvitaan taulu kantaan, josta voi saada niitä arvoja. Sen voi hoitaa esim. JPA Entity Objectilla tähän tapaan:

 

@Entity
public class GenTableEntity implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  private String tableName;
  private Long lastId;

  public String getTableName() {
    return tableName;
  }

  public void setTableName(String tableName) {
    this.tableName = tableName;
  }

  public Long getLastId() {
    return lastId;
  }

  public void setLastId(Long lastUsed) {
    this.lastId = lastId;
  }
}

Tästä esimerkistä jätetty pois Javan rakkaat equals, hashCode ja toString toteukset sekä muut hienosäädöt. Kyseessä on siis taulu jossa on String primary key, ja Long arvo jota voidaan (transaktiossa) paukutella.

Seuraava elementti: Singleton, joka pitää yllä id listaa, ja imaisee tarvittaessa serveriltä lisää.

@Singleton
public class UniqueIdGeneratorSingleton {

  private long ceiling; 
  private long current; 
  private static final long RANGE = 1000; 
  private static final String SEQUENCE_NAME = "myitem";

  @PersistenceContext
  private EntityManager entityManager;

  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public long getUniqueId() {
    if (ceiling == 0 || current > ceiling) {
     GenTableEntity entity = entityManager.find(GenTableEntity.class,
        SEQUENCE_NAME, LockModeType.PESSIMISTIC_WRITE);
      if (entity == null) {
        entity = new GenTableEntity();
        entity.setTableName(SEQUENCE_NAME);
        entity.setLastId(0L); // Reserve space
        entityManager.persist(entity);
      }
      current = entity.getLastId() + 1;
      ceiling = entity.getLastId() + RANGE; 
      entity.getLastId(ceiling); 
    }
    return current++;
 }
}

Mitä täällä tapahtuu? Kyseessä on generaattori-EJB joka hakee ja tallettaa kantaan mihin lukemaan asti on id:t käytössä. Tässä on optimointijippo: kannasta ei haeta yksi id kerrallaan, vaan tässä koodissa tuhannen erissä. Yhtälailla erä voi olla sata, tai kymmenentuhatta. Ceiling-arvo ja kannassa oleva lastId arvo pitävät kirjaa mihin asti id avaruutta on varattu. Singleton jakelee ensin omasta jäsenmuuttuja-avaruudestaan kaikki arvot, ja kun ne loppuvat, haetaan kannasta seuraava tuhannen viipale.

Koska kyseessä on Singleton bean, metodiin pääsee yksi säie kerrallaan – serverissä. Klusterissa voi kuitenkin olla useampi serveri, joissa voi käydä niin huonosti että jokaisessa ajetaan juuri samaa riviä samasta singleton-koodista. Siksi tässä on päällä vielä transaktiot, ja pessimistinen lukulukko. Kun yksi säie on lukenut rivin kannasta, se lukitaan ja seuraavan kerran siihen pääsee käsiksi vasta transaktion päätyttyä  -kun arvoa on onnistuneesti muutettu.

Huom. tässä mallissa SEQUENCE_NAME on kovakoodattu arvoon ’myitem’ – joten kaikki generoitavat id:t ovat osa samaa, suurta, globaalia arvoavaruutta. Tätä voi muokata helposti siten että parametrina annetaan taulunimi, silloin joutuu tosin id-cachen rakentamaan Map-muotoiseksi.

Testailin tätä hieman eri kanteilta. Suorituskyky riippuu hyvin paljon range-arvosta. Id-generointi ilman kantaosumaa on muutamia millisekunteja, kannan kanssa jutellessa niitä alkaa palamaan satakertaisesti. Range arvona tuhat on aika mukava, turhan suuret range arvot voivat syödä avaruutta turhankin nopeasti, etenkin jos servereitä tiheään buuttaillaan, päivitellään, tai ne kaatuilevat useita kertoja päivässä (No jos niin käy, tämä on pienimpiä ongelmista).

Tätä on tietysti kiva testata myös rinnakkaisesti. Testausta helpottaa kovasti jos tässä on esim. REST api edes hetkellisesti päällä. Testasin tätä restassured + java concurrency kirjastolla esim. näin:

 

@Test
public void getUniqueIdShouldReturnTwentyUniqueValuesWithParallelExecution() throws Exception {

 final int setSize = 2000;

 Callable<Set<Long>> c = new Callable() {
    @Override
    public Set<Long> call() throws Exception {
      Set<Long> idSet = new HashSet<>();
      for (int i = 0; i < setSize; i++) {
        idSet.add(fetchUniqueId());
      }
      return idSet;
    }
  };

  Set<Long> masterSet = new HashSet<>();

  Future<Set<Long>>[] futures = new Future[10];

  for (int i = 0; i < 10; i++) {
    futures[i] = Executors.newCachedThreadPool().submit(c);
  }

  for (int i = 0; i < futures.length; i++) {
    Set<Long> keys = futures[i].get();
    masterSet.addAll(keys);
  }

   // Set only accepts unique values, duplicates are not added
   // as long as equals() and hashcode() are implemented properly
   assertEquals(setSize * 10, masterSet.size());
}

private Long fetchUniqueId() {
  Response response = given()
    .when()
    .get("version/uniqueid")
    .then()
    .statusCode(200)
    .extract().response();
  
    Long id1 = response.jsonPath().getLong("id");
    return id1;
}

Eli ihan mukavasti tuo toimii, hyvä niksi hihassa. Aika armottomasti yritin tätä paukutella nurin mutta tarpeettomankin vakaasti pelittää. En löytänyt taas pikaisella googletuksella suoranaisesti tällaista mistään, joten kirjoittelin itselleni muistiin.

Mutta palautetta tulemaan jos tulee jotain omia ajatuksia mieleen! Tämä on taas semmoinen juttu attä aivan varmasti joku jossain on jo paremmin tehnyt. Toisaalta 90% JPA käyttäjistä ei asiaa koskaan mietikään koska @GeneratedValue. :=)

 

 

 

 

 

High Availability Mysql: Galera Cluster ja haproxy

Kun sovelluspalvelin on vikasietoinen ja vakaa, on aika tuunailla tietokantakerrosta. Vikasietoisessa järjestelmässä olisi suotavaa että myös tietokannasta on useampi kopio, jotta tietoa ja tapahtumia ei katoa vaikka tapahtuisi normimaanantai.

Sain vinkin Galera klusterituotteesta jolla Mysql kannat saa replikoimaan nätisti. Sen eteen tarvitaan joku kuormantasaus, ja testailemani haproxy vaikutti enemmän kuin riittävältä siihen. Lähtökohtana tulisi olla vähintään kolmen noden rinki, jotta enemmistö voi ristiriitatilanteissa äänestää nurin vähemmistön (2 vs 1 ei ole ehkä reilua, mutta se antaa vähän enemmän takeita siitä mikä on validein tieto – jos kaikki ovat eri mieltä ollaankin pulassa). Rakentelin kuitenkin enemmän proof-of-concept ideana parista nodesta klusteroidun, vikasietoisen mysql härvelin johon kytkeydytään Glassfish connection poolin kautta. Ja hyvältä vaikuttaa!

Huom! Kahden noden mallissa voi odotaa tulevan enemmän virhetilanteita, jotka voivat vaatia mysql boottausta ja replikoinnin resetointia. Hyödylliseksi on osoittautunut komento: sudo rm -r -f /var/lib/mysql/grastate.dat

Tässä siis muistiinpanoja ja rakennusohjeita. Huom! Joku muu voi tuunatata tämän eri tavalla ja ilmiselvästi tuotantokäyttöön tarvitaan toinen kierros koventamista, mutta näillä ohjeilla pitäisi päästä perille jos haluat maistella vikasietoisuutta itsekin. MariaDB on ehkä jopa paremmin tuettu nykyisellään jos haluat hakea MySQL:lle vaihtoehtoa.

Mysql + Galera replikoiva klusteri

Asennukset ensin. Tarkista, että olet ajan tasalla, ja asenna sitten MySQL + wsrep patchi ja Galera:

sudo apt-get update

sudo apt-get dist-upgrade

sudo apt-get install libaio1 libdbi-perl libdbd-mysql-perl mysql-client rsync

sudo wget http://ftp.de.debian.org/debian/pool/main/o/openssl/libssl0.9.8_0.9.8o-4squeeze14_amd64.deb && sudo dpkg -i libssl0.9.8_0.9.8o-4squeeze14_amd64.deb

sudo wget https://launchpad.net/codership-mysql/5.5/5.5.34-25.9/+download/mysql-server-wsrep-5.5.34-25.9-amd64.deb && sudo dpkg -i mysql-server-wsrep-5.5.34-25.9-amd64.deb

sudo wget https://launchpad.net/galera/3.x/25.3.5/+download/galera-25.3.5-amd64.deb && sudo dpkg -i galera-25.3.5-amd64.deb

Seuraavaksi tehdään mysql:ään vähän tunnuksia:

sudo /etc/init.d/mysql start

mysql -u root -p

#root on ylijumala ja sillä on salasana
GRANT ALL ON *.* TO root@'%' IDENTIFIED BY 'munsalainensana';

#galera haluaa myös päästä kiinni kantoihin
GRANT ALL ON *.* to sst@'%' IDENTIFIED BY 'sstpasswd';

# ja aikanaan myös haproxy haluaa tietää ovatko kannat pystyssä
INSERT INTO mysql.user (Host,User) VALUES ('%','haproxy');

flush privileges;

Hyvä, pistetään mysql automaattikäynnistymään:

sudo  update-rc.d mysql defaults

Editoidaan mysql wsrep.cnf tiedostoa tähän tapaan:

sudo nano /etc/mysql/conf.d/wsrep.cnf

Make these changes:

# Full path to wsrep provider library or 'none'
wsrep_provider=/usr/lib/galera/libgalera_smm.so

# Group communication system handle
wsrep_cluster_address="gcomm://"

# State Snapshot Transfer method
wsrep_sst_method=rsync

# SST authentication string. This will be used to send SST to joining nodes.
# Depends on SST method. For mysqldump method it is root:
wsrep_sst_auth=sst:sstpasswd

Sitten vain mysql käynnistäen uudelleen

sudo /etc/init.d/mysql restart

Hyvä, nyt olisi yksi node pystyssä. Kakkosnodelle tehdään samat operaatiot muuten, mutta editoitaessa tämä yksi rivi muuttuu vähän: siihen tulee ykkösnoden nimi tai ip-osoite:

# Group communication system handle
wsrep_cluster_address="gcomm://debian1,debian2"

Mutta muuten käy läpi samat temput. Kun olet valmis, jos ei mysql käynnistyksessä ole ollut ongelmia, buuttaa ensin node1, sitten node2 pienellä viiveellä.

Ei ongelmia? Hyvä, kirjaudu mysql:ään haluamallasi tunnuksella ja salasanalla, ja aja seuraava komento:

show status like '%wsrep%';

Kiinnitä huomioita erityisesti kohtiin wsrep_ready (pitäisi olla ON), ja wsrep_cluster_size (pitäisi olla 2).

Jos pääsit tähän asti, onnittelut. Jos et, vedä syvään henkeä, ja aloita uudet toistot.

Voit todeta klusterin toimivuuden esim. kirjautumalla toiseen nodeista ja lisäämällä johonkin tauluun rivejä. Niiden pitäisi ilmestyä pikimmiten myös toiseen kantaan.

Haproxy asentaminen Galeran päälle

Hauskasti haproxy ei löydykään oletuksena debianin wheezystä, joten lisätään listalle uusi site. Editoi /etc/apt/sources.list tiedostoa, lisää sinne tämä:

deb http://ftp.debian.org/debian/ wheezy-backports main

Aja seuraavat komennot asentaaksesi haproxyn:

sudo apt-get update

sudo apt-get install haproxy -y

sudo service haproxy start

Pistetään se myös automaattikäynnistykseen:

sudo sed -i s/0/1/ /etc/default/haproxy

Editoi myös /etc/init.d/haproxy tiedostoa, aseta sinne ENABLED arvoon 1 (oletuksena 0).

Seuraavaksi varmuuskopioidaan haproxy.config, ja editoidaan sitä vähän:

mv /etc/haproxy/haproxy.cfg{,.original}
sudo nano /etc/haproxy/haproxy.cfg

Kopioi/editoi sinne jotain tämäntapaista:

global
  log 127.0.0.1   local0
  log 127.0.0.1   local1 notice
  #log loghost    local0 info
  maxconn 1024
  #chroot /usr/share/haproxy
  user haproxy
  group haproxy
  daemon
  #debug
  #quiet

defaults
  log     global
  mode    http
  option  tcplog
  option  dontlognull
  retries 3
  option redispatch
  maxconn 1024
  timeout connect 5000ms
  timeout client 750000ms
  timeout server 750000ms

listen galera_cluster 0.0.0.0:3307
  mode tcp
  balance roundrobin
  option tcpka
  option mysql-check user haproxy
  server debian1 192.168.0.100:3306 check weight 1 maxconn 20
  server debian2 192.168.0.101:3306 check weight 1 maxconn 20
 
listen stats 0.0.0.0:9090
  mode http
  stats enable
  stats uri /
  stats realm Strictly\ Private
  stats auth myusername:mypassword

 

Aikalailla oletusasetuksilla mennään tässä. Mutta muutama mielenkiintoinen säätö tässä tapahtui:

– timeout client ja timeout server on nostettu 5 sekunnista 75 sekuntiin – koska sovelluspalvelinten connection poolit roikuttavat yhteyksiä hyvinkin pitkään auki, 60 sekuntia on tyypillinen aika – näin yhteydet pysyvät valideina

– galera cluster pyörii tässä samassa koneessa, portissa 3307. Mysql oletusportti on 3306. Tähän voisi varata eri koneen jossa olisi haproxy, mutta ajattelin itse replikoida senkin joka nodeen, eli haproxy pyörii joka koneessa portissa 3307, sieltä saa vikasietoista ja tasattua palvelua.

– Tuossa ip osoitteet 192.168.0.100 ja 101 edustavat mysql servereitä joille kuormaa tasataan, listaan tosiaan lisää mieluusti ainakin yksi node lisää. Olen asettanut maksimiyhteysmäärän 20 kappaleeseen – tämäkin voisi olla hyvä synkata connection poolin kanssa. weight eli painotus on joka nodella tässä sama  – eli kuorma jaetaan tasaisesti.

– lopuksi on stats serveri, portissa 9090 (jonka tulisi olla vapaa) – se kertoo mukavasti selaimella tietoa haproxy toiminnasta ja nodejen tilasta. Keksi sille haluamasi osoite, portti, tunnus ja salasana, ja sitten vain testaamaan.

No niin, nyt sitten proxy käyntiin:

sudo service haproxy restart

Ja tämä tietysti joka nodessa, asennukset, säädöt ja käynnistykset.

Voit testata toimintaa esim:

telnet localhost 3307

Tai mysql clientille:

mysql -h 127.0.0.1 --port 3307 -u haproxy_root -p -e "SHOW DATABASES"

Ja voit myös ruuvata selaimesi osoitteeseen:

http://127.0.0.1:9090/stats

No niin, jos kaikki pelaa taas, voit seuraavaksi muuttaa glassfish serverisi connection poolin osoittamaan joko virtuaali-ip:hen, tai yksittäiseen haproxy nodeen, porttiin 3307 portin 3306 sijasta.

Glassfish vikasietoinen connection pool

On hyvä samalla tehdä vähän säätöjä; jos haluat connection poolin aidosti toipuvan proxyjen ja kantojen kaatumisista, hyvä tehdä seuraavaa:

Avaa mysql admin console, mene kohtaan resources-jdbc-connection pools – ja avaa connection pool jolla haluat klusteria käytellä.

Klikkaa ensin Ping ruutuun rasti, ja valitse Save.

Mene seuraavaksi Advanced välilehdelle ja tee tällaisia säätöjä:

  1. Validate At Most Once: 60 seconds
  2. Creation Retry Attempts: 3
  3. Retry Interval: 10 (taitaa oletuksena jo ollakin)
  4. Connection Validation: (Tick) Required
  5. Validation Method: custom-validation
    1. validation class name: org.glassfish.api.jdbc.validation.MySQLConnectionValidation (select from list)

Ja mitä muita säätöjä vain haluatkaan. Sitten vain Save ja mahdollisesti restart. Ja kokeilemaan.

Ja mikä hienointa – itse softan ei tarvitse mitenkään olla tietoinen vikasietoisuudesta tai kuormantasauksesta 😉

 

[edit]: Kahdella nodella tulee sitten tosiaan replikointipulmia aika ajoin, joissa korjaus on:

sudo rm /var/lib/mysql/grastate.dat
sudo service mysql restart

Joten vaivan välttämiseksi kannattaa pistää suosista kolme nodea pystyyn. Voisi olla metkaa tehdä kolmen noden Raspberry Pi-klusteri 😉

 

 

Tuossa muuten aika hyvä artikkeli hienommista tuunauksista:

https://www.digitalocean.com/community/tutorials/how-to-use-haproxy-to-set-up-mysql-load-balancing–3

 

 

Linux + Glassfish 4.0 High availability Load Balancing Cluster – osa 3

Viime kerroille rakensimme Glassfish servereistä klusterin, joka voisi elää vaikka virtuaalikoneissa tai Amazonin palveluissa. Pistimme sen eteen Apache Http-palvelimesta rakennetun Load Balancer ratkaisun – josta kannattaa myös tehdä High Availability ratkaisu, eli pistää niitäkin useampi kopio käyttöön. Vielä pitäisi saada aikaan se, että jos yksi Load Balancer kaatuu, muut ottavat sen paikan. Sitä pohdimme tällä kertaa.

Ideana on viritellä keepalived-niminen daemon ohjelma siten, että sille annetaan yksi yhteinen jaettu virtuaalinen ip-osoite. Keepalived reitittää sen ensisijaisesti ykköskoneelle, mutta jos se kaatuu, kakkoskone ottaa virtuaalisen ip:n haltuunsa. Näin ollen elleivät kaikki load balancerit kaadu yhtäaikaa, palvelun pitäisi pyöriä lähes katkotta.

Tässä esimerkissä virtuaalinen ip-osoite on 192.168.1.100, ja sen takana olevien koneiden todelliset ip-osoitteet voivat olla vaikkapa 192.168.1.101, 102, 103, jne – niitä ei tässä esimerkissä käytetä, vaan koneiden domain nimiä, jotka ovat edelleen debian1, debian2, jne.

Ensin asenna keepalived joka nodelle:

sudo apt-get install keepalived

Seuraavaksi editoi keepalived konfiguraatiota – sitä ei alussa ole ollenkaan, joten aloitetaan tyhjästä:

sudo nano /etc/keepalived/keepalived.conf

Ensimmäiselle koneelle, joka on oletuksena master, kopioi tiedostoon jotain tämäntapaista:

vrrp_script chk_http_port {   # Requires keepalived-1.1.13

script "wget -q -T 1.0 -t 2 --delete-after -O /tmp/test.wget http://localhost:80/index.html"

interval 5            # check every 5 seconds

weight 2              # add 2 points of prio if OK

}

vrrp_instance VI_1 {

interface eth0

state MASTER          # MASTER debian1, BACKUP debian2

virtual_router_id 51  # same id in both hosts

priority 101          # 101 on master, 100 on backup

virtual_ipaddress {

192.168.1.100         # virtual IP

}

track_script {

chk_http_port

}

}

 

Ja kakkoskoneelle modifioidaan tuota vähän, tähän tapaan:

 

vrrp_script chk_http_port {   # Requires keepalived-1.1.13

script "wget -q -T 1.0 -t 2 --delete-after -O /tmp/test.wget http://localhost:80/index.html"

interval 5            # check every 5 seconds

weight 2              # add 2 points of prio if OK

}

vrrp_instance VI_1 {

interface eth0

state BACKUP          # MASTER debian1, BACKUP debian2

virtual_router_id 51  # same id in both hosts

priority 100          # 101 on master, 100 on backup

virtual_ipaddress {

192.168.1.100         # virtual IP

}

track_script {

chk_http_port

}

}

 

Eli muuten sama, mutta prioriteetti vähän pienempi, ja tilana tosiaan BACKUP. Toki sovelletaan sen mukaan onko verkkokortti eth0, ja löytyykö serverin juuresta index.html tiedosto jolla voi testata vastaako serveri, miten tiheään haluat pollailla, jne.

Mutta viiden sekunnin välein siis testataan löytyykö index.html tiedostoa – jos ei, serverin prioriteetti nousee kahdella. Eli jos molemmat serverit ovat pystyssä, debian1 prioriteetti on 101+2 eli 103, ja debian2 on 100+2 eli 102, eli debian1 saa virtuaalisen ip-osoitteen. Jos ykkösserveri kaatuu, sen prioriteetti laskee 101+0, eli 101, ja näin debian2 saa virtuaalisen ip-osoitteen.

Lopuksi potkaistaan keepalived käyntiin ja testataan:

sudo service keepalived start

Ota yhteyttä selaimella virtuaaliseen ip-osoitteeseen – voit testata suoraan aiemmin käytettyä clusterjsp-sovellusta:

http://192.168.1.100/clusterjsp

Toimiiko? Hyvä. Oletuksena kytkeydyit varmaankin debian1 koneeseen, sekä apache että glassfish instanssiin. Seuraavaksi voitaisiin nitistää debian1 koneen glassfish:

sudo service glassfish-inst stop

Toimiiko? Hyvä, en olisi uskonut. Debian1 koneen apache ohjaa kuorman debian2 koneen glassfish serverille. Nitistetään seuraavaksi debian1 koneen apache:

sudo service apache2 stop

Kokeile vielä kerran virtuaali-ip:tä. Nyt sen pitäisi ohjata debian2 koneen apachelle, joka ohjaa debian2-koneen glassfishille.

Jos ei toimi, mokasit. Savun hälvettyä tarkista kytkennät ja yritä.uudestaan. Paljon on säätöjä näissä. Mutta metkasti toimii 😉 Kannattaa vielä buutata palvelimet ja katsoa että kaikki pelaa edelleen.

 

No niin, ja tässä pari kuvaruutukaappausta high availabilitystä toiminnassa. Olen hieman retusoinut serverinimiä ja ip osoitteita suojellakseni alkuperäisten tietokonehenkien identiteettiä ja persoonaa julkisuudelta, mutta näistä käy idea selväksi:

HAClient_1

Eli sessioon on pumpattu vähän dataa, tässä välissä kävin Glassfish konsolissa nitistämässä debian1 instanssin.

HAClient_2

Ja heittämällä mentiin kakkosnodelle, sessio data replikoituneena verkon yli ja edelleen käytettävissä ilman käyttäjälle näkyvää katkosta. Samoin voisi nitistää kumman hyvänsä load balancerin, tai vaikka vetää töpselin seinästä koko koneesta.

 

Nyt kun saisi vielä tietokantakerroksen vikasietoiseksi ja kuormaa tasaavaksi…..

Linux + Glassfish 4.0 High availability Load Balancing Cluster – osa 2

Viime kerralla viritettiin kaksi Glassfish serveriä juttelemaan keskenään – ja tarvittaessa replikoimaan istunnotkin. Tällä kertaa laitetaan niiden eteen Apache Http serveri LoadBalanceriksi. LoadBalancerin tehtävänä on ohjata liikenne toimiville servereille ja ohittaa vikatilassa tai sammuneina olevat – sekä tasata kuormaa serverien välillä. Näitäkin kannattaa tehdä ainakin kaksi kappaletta – yksi joka instanssikoneeseen – koska muuten loadbalancer on uusi kriittinen pisteesi – heikoin lenkki.

Aloitin ensin MOD_JK:lla, koska se on load balancer vaihtoehdoista ’javamaisempi’, joka tarkoittaa että se osaa tehdä monenmoisia temppuja mitä muut eivät osaa, mm. ssl-salauksen, monitoroinnin ja dynaamisten säätöjen suhteen. Valitettavasti testatessa paljastui, että vaikka se toimi hienosti clusterjsp esimerkille, se antoi suurikokoisilla tiedostoilla mehukkaita Grizzly-poikkeuksia (mm. HeapBuffer has already been disposed). Tämä on ilmeisesti Glassfish 4.0 bugi – en ollut ainoa joka tästä kärsi. Kenties 4.1 korjaa tämän – 4.0:han on tarkoituskin olla high availability early access versio. Mutta kärsimätön mieli ei jaksa eikä pysty odottamaan. Joten tein ratkaisun mod_proxyllä. Listaan kuitenkin testatut mod_jk käyttöönotto-ohjeet tähän samaan artikkeliin – loppuun. Mutta ensin mod_proxy ja toimiva versio:

MOD_PROXY Load balancer asennusohjeet

Ohjeissa oletetaan että koneet joihin asennat nämä ovat nimeltään debian1 ja debian2, ja asennat kaiken joka koneelle.

Aloita asentamalla ja aktivoimalla tarvittavat moduulit:

sudo apt-get install apache2 libapache2-mod-proxy-html libxml2-dev

sudo a2enmod proxy proxy_http proxy_balancer rewrite

Testaa käynnistyykö apache2:

sudo service apache2 start

Jos kaikki hyvin, seuraavaksi editoi tiedostoa /etc/apache2/sites-available/default lempieditorillasi. Sen voisi muokata esim. tämän näköiseksi (muista muokata se klusterin kaikille koneille, ja vastaavasti muutaa host name osia, jotta load balanceristä on kopioita):

<VirtualHost *:80>
 ServerName debian1.mycompany.com
 ServerAdmin webmaster@localhost

 ErrorLog ${APACHE_LOG_DIR}/error.log

 # Possible values include: debug, info, notice, warn, error, crit,
 # alert, emerg.
 LogLevel warn

 CustomLog ${APACHE_LOG_DIR}/access.log combined

 RewriteEngine On
 ProxyRequests Off
 ProxyPreserveHost On

 <Proxy *>
 Order deny,allow
 Allow from all
 </Proxy>

 <Location /balancer-manager>
 SetHandler balancer-manager
 Order Allow,Deny
 Allow from all
 # Only allow from internal network
 #Allow from 10.0.0.0/8
 </Location>

 <Proxy balancer://mycluster>
 BalancerMember http://debian1:8080 route=debian1
 BalancerMember http://debian2:8080 route=debian2
 ProxySet lbmethod=byrequests
 </Proxy>

 ProxyPass /balancer-manager !
 ProxyPass / balancer://mycluster/ stickysession=JSESSIONID
 ProxyPassReverse / http://debian1:8080/
 ProxyPassReverse / http://debian2:8080/

</VirtualHost>

Käynnistä apache uudelleen, ja kokeile ohjata selaimesi load balancer-koneen osoitteeseen. Sen pitäisi ohjata vuoronperään eri myllyille – voit testata tätä vaikkapa avaamalla pari eri selainta, tai sammuttamalla ykkösmyllyn. Proxypasseja voi tietysti lisätä ja muokata sen mukaan mitä kaikkia kansioita haluat uudelleenohjata ja miten. Load balancer algoritmejakin löytyy useampia.

No niin, seuraavaksi vaihtoehtoiset ohjeet mod_jk versiolle. Sitä en itse päätynyt käyttämään, mutta se voi olla testaamisen arvoinen Glassfish 4.1 kanssa aikanaan.

MOD_JK load balancer asennusohjeet

Aloita asentamalla softat kaikille haluamillesi koneille:

sudo apt-get install apache2 libapache2-mod-jk

Voisit tässä kohtaa editoida tiedostoa /etc/apache2/mods-available/jk.conf  – mutta siellä ei ole mitään pakollista muutettavaa, joten mennään sensijaan suoraan editoimaan toista tiedostoa:

sudo nano /etc/libapache2-mod-jk/workers.properties

Editoi tätä tiedostoa, voit kommentoida pois esim. oletus-jk13 workerin säätöjä, mutta muuta yksi riveistä tällaiseksi:

worker.list=loadbalancer,jk-status

Ja lisää tämätapaista (jos laitteita on enemmän kuin kaksi, lisää rivejä vain – tietysti sovella verkkonimet jne):

worker.debian1.port=8009
worker.debian1.host=debian1.mycompany.com
worker.debian1.type=ajp13
worker.debian1.lbfactor=1
worker.debian2.port=8009
worker.debian2.host= debian2.mycompany.com
worker.debian2.type=ajp13
worker.debian2.lbfactor=1
worker.loadbalancer.type=lb
worker.loadbalancer.balance_workers=debian1,debian2
worker.loadbalancer.sticky_session=1
worker.jk-status.type=status

Huomaa miten voit load balance factoreilla esim. painottaa miten kutsuja jaetaan. Tässä on käytössä sticky_session joka ei ole välttämätöntä. Olemme jättäneet myös status-tiedot päälle. Huomaa että jk moduuli myös kirjoittelee logia oletuksena paikkaan /var/log/apache2/mod_jk.log – logit on hyvä tarkastaa aika ajoin näitä testatessa.

Hommaa alkaa olla melkein valmis. Seuraavaksi editoidaan apache serverin omaa oletuskonfiguraatiota, ja lisätään kaikki halutut hakemistot joita halutaan uudelleenohjata:

sudo nano /etc/apache2/sites-available/default

Ja konfiguraatio voi mennä esim. näin:

<Location /jk-status>
 # Inside Location we can omit the URL in JkMount
 JkMount jk-status
 #Order deny,allow
 #Deny from all
 #Allow from 127.0.0.1
 </Location>

# mount clusterjsp
 JkMount /clusterjsp loadbalancer
 JkMount /clusterjsp/* loadbalancer

Nämä tulevat tietysti kaikki juurielementin VirtualHost sisään, loppuun, ja jos haluat lisää hakemistoja loadbalanceriin clusterjsp:n lisäksi, määrittele ne kaikki tähän.

Seuraavaksi enabloidaan mod_jk (luultavasti jo päällä), buutataan apache, ja aletaan kokeilemaan:

sudo a2enmod jk
sudo /etc/init.d/apache2 restart

Näppärä testi on ensin avata selain osoitteeseen http://debian1/clusterjsp – ja katsoa aukeaako sovellus. Apache pyörii portissa 80 – glassfishit porteissa 8080 omissa koneissaan. Jos sovellus toimii, näet miltä serveriltä se on noudettu. Nyt voit mennä glassfish hallintaan (DAS) ja sammuttaa instanssin joka on käytössä. Jos load balancer toimii oikein, sen pitäisi seuraavan kerran sivua päivitettäessä saumattomasti pompauttaa sinut toiselle instanssille.

Muista tehdä tämä load balancer molempiin/kaikkiin instansseihin niin voimme seuraavalla kertaa rakentaa siitäkin high availability version. Koska mitä jos Load Balancer kaatuu?

 

 

Linux + Glassfish 4.0 High availability Load Balancing Cluster – osa 1

Sain taannoin loistavan syyn perehtyä toimintavarman ja skaalautuvan järjestelmän rakentamiseen Glassfish serverin vinkkelistä – olen aiemmin rakentanut vastaavia mm. JBOSS alustalla. Tässä mitä teen ei ole mitään mullistavaa, nykyisellään löytyy paljon teknisesti jännempiäkin juttuja mm. Riak, MongoDB, Hadoop, ja pilvipalveluiden ajatusmaailmasta. Mutta tämä on yksi kätevä työkalu pakissa: miten saada Java sovelluspalvelin toimimaan katkotta yli käyttöhäiriöiden ja päivitysten, ja miten saada se skaalautumaan yli satojen yhtäaikaisten käyttäjien (satoja saa yhdelläkin serverillä vaikka mitenpäin aikaiseksi).

Perusidea on vähintäänkin tuplata kaikki: Jotta saadaan serveri vikasietoiseksi, niitä tulisi olla ainakin se kaksi (mieluiten enemmän), ja jos jotain tilaa on käytetty, se pitäisi jotenkin replikoitua. Mutta vikasietoisuus on yhtä vahva kuin heikoin lenkki: glassfish serverien replikointi ei vielä riitä. Siihen tarvitaan eteen mylly joka reitittää pyynnöt toimiville serverille, load balancer toisinsanoen. Mutta mitä jos load balancer kaatuu? No niitäkin tarvitaan kaksi, tai enemmän, siten että yhden ollessa maissa toinen ottaa sen paikan. Entäpä.. taustatietokannat? Verkko? On paljon kahdennettavia kohtia.

Tämän voi tehdä monella tapaa, mutta itseä viehätti tällainen malli: Joka serveri on virtuaalikone, jossa on Glassfish serveri, Apache Http serveri Load balancerina, ja MySQL tietokanta. Periaatteessa siis joka mylly on lähes identtinen, ja sisältää kaiken tarvittavan. Skaalautuvuutta ja vikasietoisuutta saadaan siis lisäämällä myllyjä – ainakin tiettyyn rajaan asti (Session replikointi, sen tarve ja ratkaisuperiaate vaikuttaa skaalautuvuuden skaalautuvuuteen – ja kaikki serveriresurssit eivät muutenkaan skaalaudu niin hyvin 😉

Tuossa on kuva siitä miltä tämmöinen voisi näyttää – ei ole oma mutta kuvaa niin hyvin mistä on kyse että menköön (lopussa on lähdeviittaus sivustoon josta kuva ja alkuperäiset ideat ovat peräisin):

diagram

No niin, olen jo aiemmin käsitellyt aihetta, miten asennetaan Debian alustalle yksi Glassfish instanssi – joten se tarvitaan tietysti pohjaksi. Samalla kaavalla tehdään vähintään kaksi lähes identtistä serveriä – jotka näkevät toisensa (ping toimii ristiin).

Ensimmäinen askel on poistaa paikallinen serveri ja http kuuntelijat – koska niiden tilalle tulee klusterin instanssikuuntelijat. Tämä tehdään molemmille myllyille, ja Glassfish täytyy olla käynnissä aluksi:

#run these on both debian1 and debian2 machines

$ asadmin delete-http-listener http-listener-1

$ asadmin delete-http-listener http-listener-2

$ asadmin delete-virtual-server server

Selvä pyy. Vaihdetaan seuraavaksi Glassfish tunnukseen, koska sillä serverit ajavat käskyjään, ja luodaan ssh avainparit joilla serverit voivat viestiä. Tämän voit ajaa vain node1 koneesta – ellet sitten halua Domain Admin nodea molempiin koneisiin. Huomaa että glassfish tunnuksella tulisi tässä kohtaa olla myös salasana – sama molemmissa nodeissa. Ellet ole sellaista luonut, aja passwd glassfish nyt.

Tässä esimerkissä koneiden verkkonimet ovat: debian1.mycompany.com ja debian2.mycompany.com:

#run this only on debian1 machine

sudo su --shell /bin/bash glassfish

asadmin setup-ssh --generatekey=true debian1.mycompany.com

# seuraava komento antaa virheen koska glassfish on jo asennettu kakkosnodeen, mutta 
# se myös mukavasti tarkistaa että kommunikaatio pelaa

asadmin install-node debian2.mycompany.com

Seuraava askel, luodaan kaksi hallinta-nodea Glassfishiin:

# run this on debian1 machine

asadmin create-node-ssh --nodehost localhost debian1-ssh 

asadmin create-node-ssh --nodehost debian2.mycompany.com debian2-ssh

Nämä ovat ssh nodeja, eli näiden avulla Glassfish voi yhdestä DA-serveristä käsin lähettää ssh-protokollalla komentoja kaikille klusterin nodeille.

Seuraavaksi luodaan klusteri ja kaksi serveri-instanssia siihen:

# run this on debian1 machine

asadmin create-cluster --systemproperties HTTP_SSL_LISTENER_PORT=8181:HTTP_LISTENER_PORT=8080 cluster1 

asadmin create-instance --cluster cluster1 --node debian1-ssh debian1-gf

asadmin create-instance --cluster cluster1 --node debian2-ssh debian2-gf

Jos tähän asti kaikki sujui, seuraavaksi tehdään Load Balanceriä varten AJP-palvelut. Tässä kikkaillaan vähän – ja asetetaan järjestelmämuuttujiin (system properties) instanssin nimi jota voi serverissä sitten käyttää esim. virtuaalikoneparametreissa kätevästi:

# run this on debian1 machine

asadmin create-http-listener --listenerport 8009 --listeneraddress 0.0.0.0 --defaultvs server --target cluster1 jk-connector

asadmin set configs.config.cluster1-config.network-config.network-listeners.network-listener.jk-connector.jk-enabled=true

asadmin create-jvm-options --target cluster1 "-DjvmRoute=\${AJP_INSTANCE_NAME}"

asadmin create-system-properties --target debian1-gf AJP_INSTANCE_NAME=debian1

asadmin create-system-properties --target debian2-gf AJP_INSTANCE_NAME=debian2

Seuraavaksi, piipahdetaan molemmilla koneilla vaihtamassa masterpassword – mutta siten että se tallettuu tiedostoon jolloin sitä ei koko aikaa kysellä:

# run this on debian1 machine

asadmin change-master-password --savemasterpassword true debian1-ssh

#run this on debian2 machine:

asadmin change-master-password --savemasterpassword true debian2-ssh

Ja lopulta, talletetaan myös tavallinen admin salasana tiedostoon, jotta asadmin työkalun ajo ei juutu dialogeihin:

# on debian1:

asadmin --host debian2 --port 4848 login 

#on debian2:

asadmin --host debian1 --port 4848 login

Nyt ollaan jo aika loppusuoralla. Meillä on jo ennestään init scripti joka ajaa glassfish domain serverin (ja tässä tapauksessa DAS) käyntiin, mutta kannattaa ehkä lisätä scripti joka käynnistää myös molemmissa koneissa instanssin – tai clusterin. Editoi tiedostoa etc/init.d/glassfish-inst ja pistä sinne jotain tämäntapaista:

#!/bin/sh
#
# glassfish init script for Linux 
# It only starts one instance by name. The instance needs to have
# .asadminpass and .asadmintruststore password files created
# for the user.
#
# .asadminpass is created via "asadmin login" command
# .asadmintruststore is created via 
# "asadmin change-master-password --savemasterpassword true"
### BEGIN INIT INFO
# Provides: glassfish-inst
# Required-Start: $local_fs $remote_fs $network $syslog $named
# Required-Stop: $local_fs $remote_fs $network $syslog $named
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# X-Interactive: true
# Short-Description: Start/stop a glassfish instance
### END INIT INFO

GLASSFISH_HOME=/opt/glassfish4
GLASSFISH_USER=glassfish
GLASSFISH_INSTANCE=debian1-gf

case $1 in

start)
 echo "Starting Glassfish local instance ${GLASSFISH_INSTANCE}..."
 su - "${GLASSFISH_USER}" -c \
 "${GLASSFISH_HOME}/bin/asadmin start-local-instance "${GLASSFISH_INSTANCE}""
 ;;

stop)
 echo "Stopping Glassfish local instance ${GLASSFISH_INSTANCE}..."
 su - "${GLASSFISH_USER}" -c \
 "$GLASSFISH_HOME/bin/asadmin stop-local-instance "${GLASSFISH_INSTANCE}""
 ;;

restart)
 echo "Restarting Glassfish local instance ${GLASSFISH_INSTANCE}..."
 su - "${GLASSFISH_USER}" -c \
 "$GLASSFISH_HOME/bin/asadmin restart-local-instance "${GLASSFISH_INSTANCE}""
 ;;

*)
 echo "usage: $0 (start|stop|restart|help)"

esac

Huom! Vaihda ylläolevassa tuo GLASSFISH_INSTANCE arvoon 1, 2, tai mitä serveriä käsitteletkään. Joka serverillä se tulee tietysti olla omansa. Jos käynnistät clusterin, se tietysti potkaisee joka instanssin käyntiin kerralla. Tätä tapaa en kuitenkaan vielä ehtinyt kokeilemaan.

Seuraavaksi pistetään tämäkin automaattikäynnistymään:

sudo chmod 755 /etc/init.d/glassfish-inst

sudo update-rc.d glassfish-inst defaults

No niin, sitten vain boottia kehiin. Serverin 1 pitäisi toimia DAS-serverinä, joten sen kautta voit käydä ihan hallintakonsolista katsomassa miltä kaikki näyttää. Kannattaa myös kokeilla asentaa joku pieni ja varmasti toimiva klusterisovellus ja testata vähän miten se asentuu kaikille instansseille, ja miten se käyttäytyy kun instanssit sammuvat ja taas käynnistyvät.

Sellainen löytyy esim. täältä: http://blogs.nologin.es/rickyepoderi/uploads/SimplebutFullGlassfishHAUsingDebian/clusterjsp.war

Pari huomiota sessioreplikoinnin vaatimuksista ja nykytilasta:

Glassfish 4.0 ei virallisesti tue HA piirteitä, mutta siellä on olemassa early access joka toimii ihan hyvin. 4.1 versiossa olisi paremmin testattuna tulossa samat piirteet. Glassfish versioissahan on aina joissain klusterituki, joissain ei, eli kannattaa olla tarkkana version kanssa.

Glassfish 4.0 tukee eri replikointitavoista vain in-memory tapaa, joka tarkoittaa että nodet heittelevät verkon yli toisilleen notifikaatioita, ja replikoivat sessiodataa. Tämä ei ole parhaiten skaalautuva malli, mutta pienille ja keskikokoisille klustereille joissa sessiotilaa ei ole valtavasti ihan ok malli. Tämä edellyttää että klusterissa on GMS palvelu päällä, ja nodet näkevät toisensa verkossa. Hyvä myös tarkistaa että konfiguraation availability service alla on rastit oikeissa laatikoissa, mutta oletuksena pitäisi olla.

gms_service_enabled

availability_service_enabled

Mutta vielä on tärkeä huomioitava asia: Asennettava sovellus tulee olla availability-yhteensopiva ja availability-moodissa. Ensimmäinen edellyttää web.xml tiedostossa distributable=true arvoa (tai ei ollenkaan web.xml tiedostoa). Toinen vaatii että availability laitetaan päälle joko asennettaessa sovellus tai sen jälkeen. Esim. kun asensin em. clusterjsp sovelluksen oletusasetuksilla, siitä jäi rasti pois ruudusta, jolloin sessiot eivät replikoituneet.

availability_enabled

Voit myös kokeilla näkevätkö serverit toisensa ajamalla molemmissa nodeissa about yhtäaikaa tämän:

asadmin validate-multicast

 

Tässä tämä tällä kertaa. Tästä lähteestä ammensin paljon ideoita ja käytännön säätöjä:

http://blogs.nologin.es/rickyepoderi/index.php?/archives/53-Simple-but-Full-Glassfish-HA-Using-Debian.html

Jos tykkäät mielummin tehdä näitä UI:n puolella, tässä hyvä tutoriaali siihen (Glassfish 3 oppaat pätevät suurimmalta osin Glassfish 4:seen):

http://javadude.wordpress.com/2011/04/25/glassfish-3-1-clustering-tutorial/

 

Java-projektin laatua paremmaksi – SonarQube

No niin, tämä ei ole varsinaisen uusi tuote, ja jotkut aiemmista asiakkaistani ovat hehkuttaneet tätä jo aiemminkin, mutta vasta nyt oma tieni vei tämän äärelle.

Olen metriikkafriikki. Tarkoittaen että minusta on mukavaa saada jotain kättä pidempää analyysiä projektin laadusta, ja pidemmälle vieden teknisestä velasta. Siitä spagettikoodi-sotkusta jota helposti kertyy kun kiireessä ja paineen alla tehdään ja mennään siitä missä aita on matalin.

Ideana ei toki ole vain paljastaa tuskaa huonosta koodista, vaan parantaa läpinäkyvyyttä ja tukea parhaita käytäntöjä. Ideaali työlooppiin liittyy testi (speksi), minimikoodi joka sen läpäisee (ruma), riittävästi iteraatioita jotta testi ja implementaatio toimivat ok, jonka jälkeen refaktorointikierros – joka usein unohdetaan kiireessä. Toisin sanoen, jos metriikkaa kerää vanhasta projektista jossa on muutama miljoona riviä, tuska on valtaisa ja refaktoroinnin määrä kohtuuton ja epärealistinen. Eli paras hetki ottaa näkyvyyttä on itseasiassa projektin alusta alkaen, ja ideana tehdä siitä kehittäjille hyvin halpa, säännöllinen, havainnollinen, helppo, ja jopa hauska (Gamifikaatio olisi tässä kova juttu – saavutuksia ja tasoja 😉

No niin, rönsyilyksi meni. Joka tapauksessa, itse kerään metriikkaa mm. Jenkinsin avulla, GitHub näyttää kivasti metriikkaa, ja olen tottunut ajamaan code coverage vimpaimia kuten Cobertura ja JaCoCo, sekä staattisen lähdekoodin analyysejä kuten FindBugs ja PMD. Mutta nyt otin käyttöön myös Sonarin joka yhdistelee näitä, ja mikä mukavinta solahtaa mukavasti Maveniin jotta jokainen koodaaja voi sen halutessaan ajaa – tai Jenkinsiin jotta saadaan säännöllisiä raportteja. Näillä on hyvä näyttää teknisen velan määrä, idea, ja totuttaa säännölliseen refaktorointiin – joka taas vaatii testikattavuutta.

SonarQube sai huomioni kahdesta syystä: Se alkoi juuri tukemaan ainakin osittain Java 8 tasoa, ja siinä on hieno JavaScript plugin. Siinä on useita hyödyllisiä maksuttomia plugineita, ja kyseessä on aluperinkin open source tuote, mutta tarvittaessa löytyy maksullista tukea ja parempia plugareita rahalla.

Tässäpä kokoonpanoa:

Testasin ensin SonarQube serveriä omalla koneella. Latailin sen osoitteesta http://www.sonarqube.org/ – ja ajoin scriptin, joka potkaisi serverin ja muistitietokannan käyntiin porttiin 9000. Tämän jälkeen ajoin Mavenissä komennon

mvn sonar:sonar

— ja raporttihan sieltä putkahti esiin serverille. Tämän jälkeen viritin serverin toimimaan pysyvämmälle palvelimelle – valitettavasti .war paketointia ei ole enää nykyisin tarjolla, ja menin siitä missä aita on matalin eli latasin suoraan linux-asennuksen ja asensin sen servicenä. Mutta sama lopputulos kuin edellä – nyt vain serveri jyllää yötäpäivää saatavilla. Pystytin myös mysql kannan ja muutin SonarQube serverin properties tiedostosta oikeat ajurit ja yhteystiedot.

Maven työasemapäässä sitten piti opettaa sonar käyttämään etäserveriä paikallisen sijasta. Tein säädöt henk koht settings.xml tiedostoon (.m2 kansion alla oman kotihakemiston alla, tämä ei mene lähdekoodihallintaan vaan on joka kehittäjällä oma) – ja se näyttää about tältä:

<profiles>
 <profile>
 <id>sonar</id>
 <activation>
 <activeByDefault>true</activeByDefault>
 </activation>
 <properties>
 <sonar.jdbc.url>
 jdbc:mysql://myserver.mycompany.com:3306/sonar?useUnicode=true&amp;characterEncoding=utf8
 </sonar.jdbc.url>
 <sonar.jdbc.username>myusernamethatisnotroot</sonar.jdbc.username>
 <sonar.jdbc.password>notrootpassword</sonar.jdbc.password>
 <sonar.host.url>
 http://myserver.mycompany.com:9000
 </sonar.host.url>
 </properties>
 </profile>
 </profiles>

Ja taas voi ajella mvn sonar:sonar komentoa kehitysasemissa – nyt vain raportit tallettuvat jaetulle palvelimelle ja mysql kantaan.

Seuraava steppi: Oletuksena sonarissa on java-plugin joka ei oikein java 8:n kanssa tule toimeen. Sen voi onneksi päivittää uusimpaan versioon ja se alkaa tulemaan vähän paremmin toimeen.

(Yhteensopivuusongelmat ovat mm. pmd ja jacoco ja findbugs kanssa, ne voi halutessaan toki disabloida. PMD ja JaCoCo ja FindBugs taas ovat Java8 kanssa ongelmissa kiitos ASM kirjaston, jonka aiemmat/nykyiset versiot eivät tajua Java 8 bytecodea. Tässä on aika sotkuinen vyyhti ja hyvin mielenkiintoista että tätä ei ole fiksattu jo ennen Java 8 julkaisua – mutta useimpia näistä tekevät harrastetyönään kehittäjät joilla on muutakin elämää)

Joka tapauksessa, Java plugarin päivityksen ohella itse tuli asennettua mm. JavaScript plugari, sekä pdf raportointiplugari. Monenmoista muutakin herkullista löytyy.

Tuossa kuvaa Sonar raportista – kaikkine karuuksineen – ennen kuin aletaan siistimään sen perusteella:

sonar raportti

Ja JavaScriptistäkin tämä sai rouskutettua esiin metriikkaa – vaikka kyseessä on aika monimuotoinen projekti – mielestäni tosin aika lepsu analyysi 😉

sonar_javascript

Mutta – metriikaa näkyviin alkumetreistä, ja säännöllisesti. Näin saadaan koodin löyhkät esiin aiemmin. Näin saadaan bugeja korjattua ennen kuin niistä tulee bugeja – ja levitetään parhaita käytäntöjä tasaisesti tiimin kesken. SonarQube näyttäisi olevan taas yksi palikka pinossa joka saa oman hyväksyntäleiman.

Paholaismainen puoleni on seuraavaksi kiinnostunut miettimään laittaisiko buildin failaamaan jos coverage report ei ole riittävällä tasolla, jos javadoc generoinnissa on virheitä tai varoituksia, tai jos koodin kompleksisuusmetriikka mättää :p