How to Implement Real-Time Overdue Task Alerts with Redis Key Expiration and Strategy Pattern
This article explains how to use Redis key expiration notifications combined with a strategy pattern to create real‑time overdue reminders for workflow tasks, covering configuration, listener implementation, distributed locking, message handling, and practical usage in a SpringBoot project.
Requirement Background
During the project a new requirement emerged: when a workflow task reaches a certain node and exceeds a predefined time without being processed, trigger an overdue reminder to the task handler, e.g., "Your task has overdue, please handle promptly".
Initially considered scheduled tasks, but determining execution frequency and achieving real‑time response proved difficult.
Introducing a message queue was deemed too heavyweight for a simple reminder feature.
There is also a perpetual‑machine solution, but colleagues were unsure whether it had been considered; Redis key expiration listening is often preferred.
Implementation Idea
The project already uses Redis for caching; colleagues thought of leveraging Redis key expiration events to trigger overdue logic, satisfying real‑time requirements.
Different workflows have different overdue logic; a strategy pattern can handle these variations.
Draw a business flow diagram.
Enable Redis Key Expiration Notifications
Modify the Redis configuration file and set the notify-keyspace-events option to Ex to enable expired‑key listening.
Project Implementation of Expiration Listener
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
@Autowired
private MessageHandlerManager handlerManager;
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
// User handles their own business logic; message.toString() gets the expired key
String expiredKey = message.toString();
log.info("onMessage --> redis expired key: {}", expiredKey);
try {
handlerManager.processingHandler(expiredKey);
log.info("Expired key processing completed: {}", expiredKey);
} catch (Exception e) {
log.error("Exception handling redis expired key: {}", expiredKey, e);
}
}
}Strategy Pattern for Overdue Logic
Define a strategy interface that provides a method to handle the key; it extends InitializingBean so each implementation must register itself after construction.
public interface IMessageHandler extends InitializingBean {
/**
* Message execution method
* @param expiredKey message key
*/
void handle(String expiredKey);
}A simple strategy implementation class.
public class ExpressReturnOrderReturnChief implements IMessageHandler {
private final BaseMessListenService messListenService;
@Override
public void handle(String expiredKey) {
log.info("Processing express return order chief notification");
String[] split = expiredKey.replace(RedisConstant.KAIYI_QMS + RedisConstant.FLOW_EXPRESS_RETURN_ORDER_FANJIANFENXI_KEZHANG, "").split("@");
messListenService.checkFlowWithSendMessage(split,
"Express order (%s) %s is overdue, please urge, monitor, see: https://xxxxxx/",
false, expiredKey, "WeChat", null);
}
@Override
public void afterPropertiesSet() {
// Auto‑register
MessageHandlerManager.register(RedisConstant.KAIYI_QMS + RedisConstant.FLOW_EXPRESS_RETURN_ORDER_FANJIANFENXI_KEZHANG, this);
}
}The strategy manager registers implementations so that when a key expires the corresponding handler is invoked.
If no matching strategy is found, the key is ignored.
Before invoking the handler, a temporary Redis key is set as a distributed lock (10 seconds) to avoid duplicate processing.
public class MessageHandlerManager {
private static final Map<String, IMessageHandler> MESSAGE_HANDLERS = new ConcurrentHashMap<>();
private final RedisTemplate<String, Object> redisTemplate;
public static void register(String type, IMessageHandler handler) {
MESSAGE_HANDLERS.put(type, handler);
}
public void processingHandler(String expiredKey) {
log.info("Message listener triggered: {}", expiredKey);
Optional<String> first = MESSAGE_HANDLERS.keySet().stream()
.filter(expiredKey::startsWith).findFirst();
first.ifPresent(s -> doTempAbsent(expiredKey, MESSAGE_HANDLERS.get(s)));
}
private void doTempAbsent(String key, IMessageHandler handler) {
String tempKey = RedisUtil.md5(key, "UTF-8");
Boolean exist = redisTemplate.opsForValue()
.setIfAbsent(tempKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.FALSE.equals(exist)) {
log.error("Another service is processing");
return;
}
handler.handle(key);
}
}Common Method for Overdue Processing
The method checks whether the workflow is still at the node, then sends a reminder via WeChat or email. It can optionally re‑set the Redis key to trigger repeated reminders (default 24 hours).
public void checkFlowWithSendMessage(String[] split, String format, boolean isSend,
String expiredKey, String sendType, String title) {
// Query workflow instance, find current running nodes, match taskId, send message, etc.
// If isSend is true, re‑set the key with 1‑day expiration for repeated alerts.
}Using the Overdue Logic
public Boolean submit(Long id) {
// After workflow reaches a node, set Redis key:
String key = "flow:expressReturnOrder:fanjianfenxi:kezhang:" + taskId + "@" + piid + "@" + manager;
JinhuiRedisUtil.setString(key, "1");
JinhuiRedisUtil.expire(key, 1, TimeUnit.DAYS);
// ...
return true;
}Conclusion
Other solutions exist, but this approach fits the legacy project; after switching to a perpetual‑machine scheme it has been stable for a month, though occasional message loss can occur due to Redis expiration event timing.
Problem
After running stably for a period, the project occasionally loses messages.
Colleagues could not locate the issue; after investigation, the cause was identified.
Redis Expired‑Key Message Loss
Redis may drop expired events or delay them, especially under high load or in a cluster where not all nodes are subscribed. The timing of expired events is not guaranteed, and expiration does not equal deletion.
Large data volumes can cause 100 % delay, possibly hours.
Expiration ≠ deletion; a periodic job can query keys to trigger immediate deletion events.
In a cluster, missing events are more likely if clients do not subscribe to every node.
Therefore, this solution requires careful attention.
After switching to a perpetual‑machine approach, the system has been stable for a month with no recurrence.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
