java - 如何在春季扩展 1 个以上的实例并处理计划任务?
问题描述
我每天早上 8 点在欧洲/巴黎通过 Spring Boot 向 android 和 ios 应用程序发送推送通知。
如果我运行多个实例,通知将发送多次。我正在考虑在数据库上发送每天发送的通知,并检查它们,但我担心它仍然会运行多次,这就是我正在做的事情:
@Component
public class ScheduledTasks {
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
@Autowired
private ExpoPushTokenRepository expoPushTokenRepository;
@Autowired
private ExpoPushNotificationService expoPushNotificationService;
@Autowired
private MessageSource messageSource;
// TODO: if instances > 1, this will run multiple times, save to database the notifications send and prevent multiple sending.
@Scheduled(cron = "${cron.promotions.notification}", zone = "Europe/Paris")
public void sendNewPromotionsNotification() {
List<ExpoPushToken> expoPushTokenList = expoPushTokenRepository.findAll();
ArrayList<NotifyRequest> notifyRequestList = new ArrayList<>();
for (ExpoPushToken expoPushToken : expoPushTokenList) {
NotifyRequest notifyRequest = new NotifyRequest(
expoPushToken.getToken(),
"This is a test title",
"This is a test subtitle",
"This is a test body"
);
notifyRequestList.add(notifyRequest);
}
expoPushNotificationService.sendPushNotificationToList(notifyRequestList);
log.info("{} Send push notification to " + expoPushTokenList.size() + " userse", dateFormat.format(new Date()));
}
}
有人知道如何安全地防止这种情况吗?
解决方案
Quartz将是我手头任务的主要与数据库无关的解决方案,但被排除在外,所以我们不打算讨论它。
我们将要探索的解决方案做了以下假设:
- 使用了Postgres
>= 9.5
(因为我们要使用SKIP LOCKED
Postgresl 9.5 中引入的)。 - 可以运行本机查询。
在这种情况下,我们可以通过以下查询从运行的应用程序的多个实例中检索批量通知:
SELECT * FROM expo_push_token FOR UPDATE SKIP LOCKED LIMIT 100;
这将检索并锁定表中100
的条目expose_push_token
。如果应用程序的两个实例同时执行此查询,则接收到的结果将是不相交的。100
只是一些样本值。您可能希望针对您的用例微调此值。锁保持活动状态,直到当前事务结束。
在一个实例获取了一批通知之后,它还必须从表中删除它锁定的条目,或者以其他方式标记该条目已被处理(如果我们沿着这条路线走,我们必须修改上面的查询以过滤掉已处理的整体)并关闭当前事务以释放锁。然后应用程序的每个实例将重复此查询,直到查询返回零条目。
还有一种替代方法:一个实例首先获取要发送的批量通知,使数据库的事务保持打开状态(从而继续保持数据库的锁定),发出它的通知,然后删除/更新条目并关闭交易。
这两种解决方案具有不同的优势/劣势:
- 第一个解决方案使交易保持简短。但是,如果应用程序在发送通知的过程中崩溃,则未发送的批处理部分将在此运行中丢失。
- 第二种解决方案使事务保持打开状态,可能会持续很长时间。如果它在发送通知中途崩溃,所有条目将被解锁并重新处理其批次,可能导致某些通知被发送两次。
为了使这个解决方案起作用,我们还需要某种工作来用expo_push_token
我们需要的数据填充表格。该作业应预先运行,即其执行不应与通知发送过程重叠。
推荐阅读
- sql - 试图获取非对象的属性(查看:C:\xampp\htdocs\MyP\resources\views\pekerja\index.blade.php)
- regex - RegEx for Positive Lookahead - 密码规则(包含所需的数字和字符)
- reactjs - 如何使用 redux-observable 在一个史诗中发出多个动作?
- akka - 如何解决 Akka 无法获取心跳时出现的问题?
- html - 使元素内部的元素在其他粘性元素之上具有粘性
- flutter - 基于 AppLifeCycleState 渲染小部件,查看正在运行的应用列表时隐藏页面
- javascript - 根据两个输入的结果,用 javascript 只读定义第三个输入的结果
- java - 使用来自 xsd 的 Jaxb 生成 java Array 而不是 Collection 类型
- csv - 使用合并内容处理器合并每个 csv 流文件时添加换行符
- python - 为什么我的 sklearn MDS(多维缩放)可视化看起来如此无信息(完美的圆形和圆形)?