Antonio Dragos
Antonio Dragos

Reputation: 2883

Run scheduled task once within Spring Boot app deployed to Azure

In my Spring Boot app I have a scheduled task that runs every minute. It runs some queries to find any notifications that are due, and then sends them by email

@Service
public class EmailSender {

    @Scheduled(cron = "0 * * * * *")
    public void runTask() {
      // send some emails
    }
}

It works fine when I test it locally, but when I deploy it to Azure, 2 copies of each email are sent. This is because in Azure we run (at least) 2 containers, both of which have the scheduling enabled.

I looked for a per-container environment variable, which the scheduler would check, and only run the job if this variable is set to "true", but couldn't find any reliable way to achieve this.

I'm now considering the following instead

This seems like a lot of additional complexity, and am wondering if there's a simpler solution?

Upvotes: 4

Views: 2898

Answers (2)

usr-local-ΕΨΗΕΛΩΝ
usr-local-ΕΨΗΕΛΩΝ

Reputation: 26914

Option 4

(Option 3 being the one in the comment by @MDeinum)

Factor your code so the executable job runs against a REST request, protected by proper authentication (a GUID parameter is sufficient for the purpose, feel free to use more robust schemes). Disable scheduling offered by @Scheduled annotation. Use an Azure Automation account to periodically invoke the REST API.

The idea is that the REST invocation is routed to exactly one node, so only one node is awakened.

The drawback is in security, as you must expose an API that just runs a job, ie. can be run repeatedly, and can be potentially abused. Even if it's not exposed to the internet, someone may repeatedly curl to that API using the valid developer's token.

I am comfortable with this approach because at some point I will be required to implement a "force run scheduled task" feature to be used when a previous execution of the job has failed.

Personally, I had to deliver this kind of request on every single project I participated.

Upvotes: 1

robertobatts
robertobatts

Reputation: 966

Spring, by default, cannot handle scheduler synchronization over multiple instances – it executes the jobs simultaneously on every node instead. So there are two possible solutions:

Using an external library

You can use Spring ShedLock to handle this though. Here there is a guide you can use: Spring ShedLock. You can find all the code need to implement this lock there.

It works by using a parameter in a database, that gives the information to the other nodes about the execution status of that task. In this way ShedLock prevents your scheduled tasks from being executed more than once at the same time. If a task is being executed on one node, it acquires a lock which prevents the execution of the same task from another node.

You can find more info in their Github Page about how to handle it with different databases.

Deploying the active schedulers in a different instance

You can create two different profiles, for example, one is prod and the other is cron. Then you can put the annotation @Profile("cron") on scheduler classes, so that they will be ran only on the second profile.

Then you have to build the application twice:

  1. The first time you have to build it with -Dspring.profiles.active=prod and you deploy it normally.
  2. The second time you build it with -Dspring.profiles.active=prod,cron, and you have to deploy this in an environment that is not autoscaling so that you can be sure there will be only one instance of this.

Upvotes: 5

Related Questions