Apache Wicket Clustering With Multiple Options

by kinabalu on February 10, 2010

We’ve been seeing more and more implementations of IPageStore out in the wild for Apache Wicket. The interface basically decides how Wicket will store the Pagemap for your application. The default that ships with Wicket uses DiskPageStore which is an implementation that stores the serialized pages grouped in a single file per pagemap. After reading a wonderful blog post on Letsgetdugg a few days ago: Clustering Wicket for fun and profit!, I decided to take a look at writing an implementation using Hazelcast – an “open source clustering and highly scalable data distribution platform”.

The implementation below borrows heavily from Victor. It basically creates a HazelcastInstance in the constructor and then overrides all the methods necessary from AbstractPageStore. Here’s some quick code to put in your app’s Application implementation that will use this new IPageStore:

    @Override
    protected ISessionStore newSessionStore() {
        return new SecondLevelCacheSessionStore(this,
                new HazelcastPageStore("default"));
    }

And here’s the code for HazelcastPageStore:

public class HazelcastPageStore extends AbstractPageStore 
                                      implements SecondLevelCacheSessionStore.IClusteredPageStore {
 
    private Logger logger = LoggerFactory.getLogger(HazelcastPageStore.class);
 
    private String mapName;
    private HazelcastInstance client;
 
    public HazelcastPageStore(String mapName) { this(mapName, null); }
 
    public HazelcastPageStore(String mapName, Config config) {
        this.mapName = mapName;
        client = Hazelcast.newHazelcastInstance(config);
    }
 
    public String getKey(final String sessId, final String pageMapName, final int pageId, final int pageVersion) {
        return getKey(sessId, pageMapName, pageId, pageVersion, -1);
    }
 
    public String getKey(final String sessId, final String pageMapName, final int pageId, final int pageVersion, final int ajaxVersion) {
        String key = sessId + ":" + pageMapName + ":" + pageId + ":" + pageVersion + ":" + ajaxVersion;
        if (logger.isDebugEnabled()) {
            logger.debug("GetKey: " + key);
        }
        return key;
    }
 
    public String storeKey(final String sessionId, final Page page) {
        String key = sessionId + ":" + page.getPageMapName() + ":" + page.getId() + ":" + page.getCurrentVersionNumber() + ":" + (page.getAjaxVersionNumber() - 1);
        if (logger.isDebugEnabled()) {
            logger.debug("StoreKey: " + key);
        }
        return key;
    }
 
    public boolean containsPage(final String sessionId, final String pageMapName, final int pageId, final int pageVersion) {
        return client.getMap(mapName).containsKey(getKey(sessionId, pageMapName, pageId, pageVersion));
    }
 
    public void destroy() {
        client.shutdown();
    }
 
    public <T> Page getPage(final String sessionId, final String pagemap, final int id, final int versionNumber, final int ajaxVersionNumber) {
        IMap<String, Page> map = client.getMap(mapName);
 
        return map.get(getKey(sessionId, pagemap, id, versionNumber, ajaxVersionNumber));
    }
 
    public void pageAccessed(final String sessionId, final Page page) {
    }
 
    public void removePage(final String sessionId, final String pagemap, final int id) {
        String key = getKey(sessionId, pagemap, id, 0);
        key = key.substring(0, key.lastIndexOf(":"));
 
        IMap<String, Page> map = Hazelcast.getMap(mapName);
        for (String k : map.keySet()) {
            if (k.startsWith(key)) {
                map.remove(k);
            }
        }
    }
 
    public void storePage(final String sessionId, final Page page) {
        IMap<String, Page> map = client.getMap(mapName);
        map.put(storeKey(sessionId, page), page);
    }
 
    public void unbind(final String sessionId) {
        IMap<String, Page> map = client.getMap(mapName);
        for (String key : map.keySet()) {
            if (key.startsWith(sessionId)) {
                map.remove(key);
            }
        }
    }
}

Several other IPageStore implementations available:

Let us know if you find any others out in the wild so we can add them here.

Similar Posts:

Share this:
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • DZone
  • Slashdot
  • Technorati

{ 7 comments… read them below or add one }

victoriNo Gravatar February 13, 2010 at 2:22 pm

There is a bit more to it. The reason my pagestore is so simple is the fact I offload the complexity to memcached. Memcached prematurely expires older caches automatically when it needs room for more space. Last I checked I don’t know of any Java caching implementation that expires caches prematurely like memcached.

kinabaluNo Gravatar February 13, 2010 at 3:29 pm

I believe Hazelcast will handle that for you automatically, you can set up different caching structures in your Hazelcast configuration so that older caches get expired, and also set a max so once it reaches a certain point if you haven’t got a persistence mechanism, it’ll drop the oldest.

philNo Gravatar February 24, 2010 at 4:17 am

FYI, I think you should add “:” to the sessionId test in unbind :
public void unbind(final String sessionId) {
IMap map = client.getMap(mapName);
for (String key : map.keySet()) {
if (key.startsWith(sessionId+”:”)) {
map.remove(key);
}
}
}

And maybe you should define a constant named DELIMITER… and hope that it never happens elsewhere…

Martin GrotzkeNo Gravatar February 27, 2010 at 5:32 pm

I created the memcached-session-manager ([1]) which is a session manager for tomcat that replicates sessions to memcached nodes for session failover.

There are different serialization strategies available ([2]), besides default java serialization I wrote an xml based serialization (using javolution) which has the advantage, that it supports different class versions (useful for a new deployment of your application).

Right now I’m implementing a feature so that sessions are only replicated if they are modified so that readonly requests don’t cause a session replication.

I created the memcached-session-manager for a wicket application that needs to be highly scalable, and I created a very simple wicket app for performance comparison of the different serialization strategies. I still need to extend this benchmark app (and add it to the github repo), but there’s already a wiki page with a rough overview over the current results – with an older version of msm without the replicate-only-modified-sessions-feature ([3]).

Cheers,
Martin

[1] http://code.google.com/p/memcached-session-manager/
[2] http://code.google.com/p/memcached-session-manager/wiki/SerializationStrategies
[3] http://code.google.com/p/memcached-session-manager/wiki/Performance

OndraNo Gravatar May 6, 2010 at 8:21 pm

Does that really work? I don’t see any code for this [1]:

“”"Note that the versionNumber and ajaxVersionNumber parameters may be -1.
* If ajaxVersionNumber is -1 and versionNumber is specified, the page store must return the page with highest ajax version.
* If both versionNumber and ajaxVersioNumber are -1, the pagestore must return last touched (saved) page version with given id. “”"

Here is my untested implementation with Infinispan (JBoss Cache):
http://ondra.zizka.cz/stranky/programovani/java/web/wicket/wicket-pagestore-jbosscache-infinispan-integration.texy

OndraNo Gravatar May 6, 2010 at 8:21 pm
DmitryNo Gravatar May 26, 2010 at 1:19 pm

Hey, what a great post.

But I still have a question. I tried to apply the code “as is” to my Wicket app.
I started 2 instances of web server behind the haproxy to see what will happen.
But I get ‘Page Expired’ exception (especially on an Ajax request) when a request hits second instance.
The problem (traced down) is in this line:
IPageMap pageMap = session.pageMapForName(requestParameters.getPageMapName(), false);

The PageMap returned from the session is null.
Is extra configuration needed for your clustering solution to work? The

Session.(IPageMap)getAttribute(attributeForPageMapName(pageMapName));

simply returns null for the second web server instance.

Any suggestions would be really appreciated.

Leave a Comment

Previous post: