Single-Node and Distributed Scheduled Tasks in Java: JDK, Spring, Redis, and Middleware Solutions
This article explains how to implement single-node scheduled tasks using JDK's ScheduledExecutorService and Spring @Scheduled, compares Redis‑based approaches with ZSet and key‑space notifications, and reviews popular distributed scheduling frameworks such as Quartz, elastic‑job‑lite, LTS, and XXL‑Job for microservice environments.
Single-Node Scheduled Tasks
JDK Native
Since JDK 1.5, ScheduledExecutorService replaces TimerTask for reliable timing. The following example creates a thread pool of 10 workers and schedules a task to start after 10 seconds and repeat every 30 seconds.
public class SomeScheduledExecutorService {
public static void main(String[] args) {
// Create a task queue with 10 threads
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
// Execute task: start after 10 seconds, then every 30 seconds
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println("执行任务:" + new Date());
}, 10, 30, TimeUnit.SECONDS);
}
}Spring Task
Spring Framework provides built‑in scheduling with @Scheduled and cron expressions. A simple job class can be written as:
@Configuration
@EnableScheduling
public class SomeJob {
private static final Logger LOGGER = LoggerFactory.getLogger(SomeJob.class);
/**
* Executes every minute (e.g., 18:01:00, 18:02:00)
*/
@Scheduled(cron = "0 0/1 * * * ? *")
public void someTask() {
// ... task logic ...
}
}While single-node scheduling works for small services, distributed scenarios require more robust solutions.
Redis‑Based Implementation
Using Redis ZSet, tasks are stored with their execution timestamps as scores. A scheduled worker queries the ZSet for tasks whose score is less than the current epoch second, executes them, and then removes the processed entries.
/**
* Description: Redis ZSet based delayed task
*/
@Configuration
@EnableScheduling
public class RedisJob {
public static final String JOB_KEY = "redis.job.task";
private static final Logger LOGGER = LoggerFactory.getLogger(RedisJob.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void addTask(String task, Instant instant) {
stringRedisTemplate.opsForZSet().add(JOB_KEY, task, instant.getEpochSecond());
}
@Scheduled(cron = "0 0/1 * * * ? *")
public void doDelayQueue() {
long nowSecond = Instant.now().getEpochSecond();
Set
strings = stringRedisTemplate.opsForZSet().range(JOB_KEY, 0, nowSecond);
for (String task : strings) {
LOGGER.info("执行任务:{}", task);
}
stringRedisTemplate.opsForZSet().remove(JOB_KEY, 0, nowSecond);
}
}Typical use cases include automatically canceling unpaid orders after 15 minutes or returning unclaimed red packets after 24 hours. Advantages are higher performance than MySQL and resilience to node failures.
Key‑Space Notification Method
Redis can publish expiration events when a key's TTL reaches zero. By enabling config set notify-keyspace-events Ex , a listener can react to these events and trigger the corresponding task.
/**
* Custom listener for key expiration events
*/
public class KeyExpiredListener extends KeyExpirationEventMessageListener {
public KeyExpiredListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
String channel = new String(message.getChannel(), StandardCharsets.UTF_8);
String key = new String(message.getBody(), StandardCharsets.UTF_8);
// TODO: handle expired key
}
} @Configuration
public class RedisExJob {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
return container;
}
@Bean
public KeyExpiredListener keyExpiredListener() {
return new KeyExpiredListener(redisMessageListenerContainer());
}
}Key‑space notifications are passive, consume less CPU, but lack ACK guarantees.
Distributed Scheduled Task Frameworks
Introducing Middleware Components
Separating scheduling into its own service prevents duplicate execution and simplifies scaling.
Quartz
Relies on MySQL, supports multi‑node deployment via database lock competition. No built‑in UI, configuration can be cumbersome.
elastic‑job‑lite
Uses Zookeeper for registration and discovery, enabling dynamic server addition. Provides multiple job modes, failover, status collection, and a graphical management console.
LTS
Also Zookeeper‑based, offers job logging, SPI extensions, fault‑tolerance, node monitoring, and a UI.
xxl‑job
Chinese open‑source solution built on MySQL, uses DB lock competition, supports elastic scaling, job sharding, real‑time logs, GLUE code editing, dependency management, encryption, email alerts, and internationalization.
Conclusion
In microservice architectures, using a dedicated scheduling component such as XXL‑Job is recommended for reliable task management. For small services, Spring Task remains the simplest choice, while JDK native scheduling is suitable for quick prototypes. Regardless of the approach, ensure tasks are idempotent, handle exceptions, avoid backlog, and execute at the intended time.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.