Nowadays, for almost all services, we would like to set up at least poor man’s HA. This means we would have more than one node/server/pod at a time. This is great for load balancing and availability purposes. Nevertheless, there’s a simple problem with this setup. What if you want to execute a piece of code in only one node? We can do this via a simple mutual exclusion mechanism. There are various ways to do this but I would like to limit this post to achieve mutual exclusion with Java and Spring Framework.
Now, imagine you have a cron job that sends an e-mail to your customers at 9 a.m. every day. You wouldn’t want to send the same e-mails twice, right? If you are using Spring Framework, you can simply do this via Scheduled annotation. A typical code would look like:
@Scheduled(cron = "59 59 8 * * *" /* Every day at 8:59:59am */)
public void sendEmails() {
List emails = emailDAO.getEmails();
emails.forEach(email -> sendEmail(email));
}
So, we need one node to execute above not the others. It would be great if we can do something like this, right?
@Scheduled(cron = "59 59 8 * * *" /* Every day at 8:59:59am */)
@TryLock(name = "emailLock", owner = NODE_NAME, lockFor = TEN_MINUTE)
public void sendEmails() {
List emails = emailDAO.getEmails();
emails.forEach(email -> sendEmail(email));
}
Thus, I’ve implemented a little library to exactly do this. Currently, it offers synchronization through Postgres and MySQL but one can extend it to other technologies like ZooKeeper, Consul and etcd as well.
By the way, this isn’t just about crons. You can also synchronize over different stuff as well. For instance, you might want to process a message once even if it’s received by multiple nodes.
@Component
class MessageProcessor{
...
@TryLock(name = "messageProcessor", owner = NODE_NAME, lockFor = TEN_MINUTE)
public void processMessage(Message message) {
...
}
}
@Component
class MessageService{
...
@PostConstruct
public void initProcessor(){
while (true) {
Message message = pollMessage();
messageProcessor.processMessage(message);
}
}
}
In consequence, you can implement a simple lock with a timeout with dlock.