Monday, April 27, 2009

Sharing data of Spring beans with Terracotta

This is a quick demo showing how you would share data of Spring beans between different JVMs using Terracotta.

The example is based on the Voting Booth of Spring By Example website (a great website to learn Spring by the way)

In this example, I have 2 beans defined in application-context.xml


<bean id="recorder" class="org.terracotta.demo.LocalVoteRecorder" />

<bean id="votingBooth" class="org.terracotta.demo.VotingBooth">
<property name="voteRecorder" ref="recorder" />
</bean>


So simply, the 'votingBooth' bean has a reference to the 'recorder' bean which has a map of candidates and their votes.


public class VotingBooth {
VoteRecorder recorder = null;

public void setVoteRecorder(VoteRecorder recorder) {
this.recorder = recorder;
}

public void vote(Candidate candidate) {
recorder.record(candidate);
}

public void printResult() {
recorder.tallyVotes();
}
}

/* ************* */
public class LocalVoteRecorder implements VoteRecorder {
final static Log logger = LogFactory
.getLog(LocalVoteRecorder.class);
private final Map<Candidate, Integer> votesRecord = new ConcurrentHashMap<Candidate, Integer>();

public void record(Candidate candidate) {
int count = 0;

if (!votesRecord.containsKey(candidate)) {
votesRecord.put(candidate, count);
} else {
count = votesRecord.get(candidate);
}
count++;
votesRecord.put(candidate, count);
}

public void tallyVotes() {
for (Candidate candidate : votesRecord.keySet()) {
logger.info("Candidate '" + candidate + "' has "
+ votesRecord.get(candidate) + " votes.");
}
}
}


Spring will do its magic and inject the right data to into main program at runtime. To demo, I have the main function vote 5 times between 2 known candidates then print out the result in the end.


public class Main {

final static Log logger = LogFactory.getLog(Main.class);
final static Random rand = new Random();
final static Candidate[] candidates = new Candidate[] {
new Candidate("Hung"), new Candidate("Nick") };

public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"/application-context.xml");

VotingBooth votingBooth = (VotingBooth) applicationContext
.getBean("votingBooth");

// vote 5 times
for (int i = 0; i < 5; i++) {
Candidate luckyCandidate = candidates[rand.nextInt(2)];
votingBooth.vote(luckyCandidate);
logger.info("voted for candidate " + luckyCandidate);
}

votingBooth.printResult();
}
}


So if you run this Spring app as is, it will print the result of that run and exit. Quite simple.

Now let say we want to maintain a voting record that would last beyond the life cycle of that run. And also, any subsequent runs should add to the vote tally. In other words, the data is shared between the nodes (JVMs) and persisted.

This is where Terracotta comes in. The data we're interested in sharing will be marked as "ROOT". In this case, it's the "votesRecord" field of the 'recorder' bean. This field will be transparently clustered by Terracotta, its value will be saved in Terracotta server and it will be faulted back into a new node as needed. There's no thirdparty function calls nor networking code, just plain Java. Here is what needed in Terracotta configuration file tc-config.xml:

<application>
<dso>
<instrumented-classes>
<include>
<class-expression>org.terracotta.demo.*</class-expression>
</include>
</instrumented-classes>
<roots>
<root>
<field-name>org.terracotta.demo.LocalVoteRecorder.votesRecord</field-name>
</root>
</roots>
</dso>
</application>


That piece of config tells Terracotta to instrument all classes under the org.terracotta.demo package and make "votesRecord" a root.

Since Terracotta has Maven plugin, I just use its plugin and run the example. The pom is here if you're interested. First, you need to start Terracotta server:

% mvn tc:start

Then you can run the example with Terracotta enable simply by:

% mvn tc:run

On first run, I got this result: Nick got voted twice


[INFO] [vote] INFO [main] voted for candidate Nick
[INFO] [vote] INFO [main] voted for candidate Hung
[INFO] [vote] INFO [main] voted for candidate Hung
[INFO] [vote] INFO [main] voted for candidate Nick
[INFO] [vote] INFO [main] voted for candidate Hung
[INFO] [vote] INFO [main] Candidate 'Hung' has 3 votes.
[INFO] [vote] INFO [main] Candidate 'Nick' has 2 votes.
[INFO] Finished node vote
[INFO] DSO processes finished
[INFO] Skipping stopping DSO Server
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------


On second run: Nick got voted one more time brings his total votes to 3


[INFO] [vote] INFO [main] voted for candidate Hung
[INFO] [vote] INFO [main] voted for candidate Hung
[INFO] [vote] INFO [main] voted for candidate Nick
[INFO] [vote] INFO [main] voted for candidate Hung
[INFO] [vote] INFO [main] voted for candidate Hung
[INFO] [vote] INFO [main] Candidate 'Hung' has 7 votes.
[INFO] [vote] INFO [main] Candidate 'Nick' has 3 votes.


This shows that the votes are indeed shared between 2 separate runs. To see the shared data live, you can use Terracotta Developer Console to look at the roots:

Run this Maven command to start the console:

% mvn tc:dev-console



You can check out the whole project from Subversion: http://svn.terracotta.org/svn/forge/projects/TerracottaSpringDemo

6 comments:

oliver said...

Hi,

i have a question regarding the spring beans.

If i were to declare two beans of this type, how can i only distribute one of them? if this is possible.

My problem is that i have a bean i use on multiple parts of the system, and i want them to be shared via terracotta individually.
Do i have to create a new (empty) subclass for each bean to get a them seperately on the shared heap? Or is there a way to also specify the concrete bean id?

If this is not possible yet it would be a part you should be working on =), because (from my side) this is bad for common use framework objects i use over the system and it would be great if they could be referenced by beanreference so they can store their data under their bean id in the distributed heap.

Kind Regards

Oliver

ps: i dont want this to sound too negativ, overall i am pretty impressed by terracotte. I spend two days enhancing our application (currently about 30 server processes and a bunch of webservers running distributed on several physical machines) with terracotta. And i am amazed how far i came after only two days. So i can only give you props for great documentation! (and i know how much work that is=). I want to use it as a 2nd level hibernate cache and to distribute some manually calculated and maintained caches. An so far i made great progress and already have parts of the system running under terracotta.

Unknown said...

It's not possible. Once you indicate a field of a bean class to be shared, all the instances will share that field.

movie-say said...

I got very strange exception on mvn tc:run here is the tail:

[INFO] [vote] 2009-12-17 15:39:33,312 INFO - Configuration loaded from the file at 'C:\WorkingArea\third-party\_SVN-trunks\terraco
tta\TerracottaSpringDemo\tc-config.xml'.
[INFO] [vote] 2009-12-17 15:39:33,484 INFO - Log file: 'C:\WorkingArea\third-party\_SVN-trunks\terracotta\TerracottaSpringDemo\tar
get\terracotta\client-logs\20091217153933453\terracotta-client.log'.
[INFO] [vote] 2937 [main] DEBUG Sigar - no sigar-x86-winnt.dll in java.library.path
[INFO] [vote] org.hyperic.sigar.SigarException: no sigar-x86-winnt.dll in java.library.path
[INFO] [vote] at org.hyperic.sigar.Sigar.loadLibrary(Sigar.java:174)



Whey TC is looking for a dll isn't it pure java ?

Unknown said...

The core of the product is pure Java. The native piece is the Sigar resource monitor so that you can use our developer console to check the status of the cluster ( can be run with mvn tc:dev-consol )

I've updated the project to use latest version of Terracotta so hopefully the error you're seeing has been resolved. It's ignorable error by the way.

Unknown said...

this example is based on maven plug ins. I am wondering that How we can run and deploy a terracotta application as a jar file by not worrying mvn things at all? I am getting funny exceptions related classloading even if I try my application with dso-java

Unknown said...

You should post the error and stacktrace and your tc-config.xml to Terracotta forum. A lot of people there will help you out.

http://forums.terracotta.org/forums/forums/list.page