Spring @Scheduled annotation random delay
Asked Answered
S

8

24

I am using the @Scheduled annotation from Spring framework to invoke a method. But I have multiple nodes in my setup and I do not want them to all run at exactly the same time. So I'd like to set a random value to the initial delay to offset them from each other.

import org.springframework.scheduling.annotation.Scheduled;

@Scheduled(fixedRate = 600000, initialDelay = <random number between 0 and 10 minutes> )

Unfortunately, I am only allowed to use a constant expression here. Is there any other way around this? I thought of using Spring expression language.

Sanguine answered 9/1, 2015 at 21:48 Comment(1)
O
20

You can configure the initialDelay through Spring Expression Language:

@Scheduled(fixedRate = 600000, initialDelayString = "#{ T(java.util.concurrent.ThreadLocalRandom).current().nextInt(10*60*1000) }" )

I don't have an IDE to test that code right now, so you may need to adapt that a bit.

Orphrey answered 10/1, 2015 at 1:18 Comment(4)
Don't SpEl commands need to be in strings? I've only seen them used in string based values. This unfortunately is expecting a long value. I tried it unquoted and got unresolved compilation problems. I tried it quoted as a string and the scheduler never got invoked but no error messages.Sanguine
Have you noticed the use of initialDelayString instead of initialDelay? The latter takes a long, the first a string which can be an expression.Lennox
The example does not quite work. First, it must quoted or the compiler will complain. But even then, all I get is null. Is there som kind of configuration that I need to do?Kylander
Confirmed with multiple Spring versions that this does not workConfiguration
C
20

To make the initial delay randomly somewhere between 0 and the fixedRate try this:

@Scheduled(fixedDelayString = "${some.delay}", initialDelayString = "${random.int(${some.delay})}")

Where you define some.delay (but pick a more suitable name) as 10 minutes as a property like so in your application.properties or equivalent.

some.delay = 600000

Of course if you want to be lazy and hard code it you can always just use ${random.int(600000)}

Carbohydrate answered 12/7, 2016 at 2:1 Comment(3)
Just wanted to add this alternative answer as the 'correct' answer isn't really helpful and yet this page is high up in search results.Carbohydrate
Confirmed with multiple Spring versions that this does not workConfiguration
I have used this exact solution in multiple projects, and it worked great. Have not tried it with most recent versions of spring.Dorking
D
5

In this working example, the random delay will be between 5 and 10 seconds.

@Scheduled(fixedDelayString = "#{new Double((T(java.lang.Math).random() + 1) * 5000).intValue()}")
Disinfection answered 23/1, 2017 at 21:26 Comment(0)
E
3

Keep in mind, that the initialDelayString is evaluated only once at startup and then this same values is used whenever the job is scheduled.

See org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#processScheduled

Elegiac answered 25/7, 2016 at 7:2 Comment(1)
Do you know a way to have the scheduler not only have a constant random value evaluated at startup, but a random value between each triggering?Disinfection
C
2

This should work

@Scheduled(fixedRate = 600000, initialDelay = "#{new java.util.Random().nextInt(700)}")

Verified it with this simple test:

    @Test
    public void testSpEL() {
       ExpressionParser parser = new SpelExpressionParser();
       Expression exp = parser.parseExpression("new java.util.Random().nextInt(500)");
       Integer value =(Integer) exp.getValue();
       Assertions.assertThat(value).isNotNull();
}
Cloris answered 6/11, 2020 at 16:28 Comment(0)
Q
0

In kotlin this works:

@Component
class MyJob {
   companion object {
      const val INTERVAL = 24*3600*1000L // once a day
   }

   @Scheduled(fixedRate = INTERVAL, initialDelayString = "\${random.long($INTERVAL)}")
   fun doDaily() {
      ...
   }
}
Quathlamba answered 28/8, 2019 at 11:40 Comment(0)
K
0

The shortest way I found is:

@Scheduled(initialDelayString = "#{T(Math).round(T(Math).random()*600000)}")
Keele answered 19/10, 2022 at 11:51 Comment(0)
W
-5

Or you could just add Thread.sleep(...) at the end of your function.

@Scheduled(fixedDelay = 10000)
public void replicateData() {

    ... do stuff ...

    try {
        Thread.sleep(RandomUtils.nextLong(1000, 10000));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
Whitewing answered 19/5, 2017 at 9:59 Comment(1)
It would need to be a random delay at the start of the function. And there would need to be code to make that delay only the first time. That might work but probably a bit clunkySanguine

© 2022 - 2024 — McMap. All rights reserved.