FastAPI实现精准定时提醒任务:PostgreSQL取数+FCM通知方案咨询
精确闹钟提醒系统实现方案
核心思路
放弃轮询方案,采用基于事件的精准定时调度:直接计算每个提醒的下次触发时间,将通知任务精准绑定到对应时间执行;触发后自动处理重复提醒的后续调度,确保时间零偏差。
具体实现步骤
1. 选用精准的定时调度框架
使用APScheduler(Python生态)这类支持精准时间触发的调度框架,它支持单机/分布式部署,能保证任务在指定时间精确执行。如果是集群部署,可搭配Redis作为分布式调度存储,避免多实例重复触发。
2. 系统启动时加载待触发任务
系统初始化时,从数据库筛选所有未完成、且有有效下次触发时间的提醒,将对应的通知任务添加到调度器:
from apscheduler.schedulers.background import BackgroundScheduler from sqlalchemy.orm import Session from datetime import datetime from models import Reminder # 全局调度器实例 scheduler = BackgroundScheduler() def init_reminder_scheduler(db: Session): # 筛选需要调度的活跃提醒 active_reminders = db.query(Reminder).filter( Reminder.is_finished == False, Reminder.next_date.isnot(None), Reminder.next_date > datetime.now() ).all() for reminder in active_reminders: # 为每个提醒添加精准触发任务 scheduler.add_job( trigger_reminder_notification, trigger='date', run_date=reminder.next_date, args=[reminder.id], id=f"reminder_task_{reminder.id}", replace_existing=True ) scheduler.start()
3. 实现通知触发与重复提醒逻辑
当任务触发时,执行FCM通知发送,然后自动处理重复提醒的后续调度:
def trigger_reminder_notification(reminder_id: int, db: Session): reminder = db.query(Reminder).get(reminder_id) if not reminder or reminder.is_finished: return # 执行FCM通知发送逻辑 # 此处替换为你的FCM发送代码 # send_fcm_message(user_id=reminder.user_id, content=reminder.content) # 更新提醒状态 reminder.last_reminded_at = datetime.now() reminder.is_notified = True # 计算下一次触发时间 next_trigger_date = reminder.next_date if next_trigger_date: # 检查是否超过重复结束时间 if reminder.repeat_end_date is None or next_trigger_date <= reminder.repeat_end_date: # 添加下一次触发任务 scheduler.add_job( trigger_reminder_notification, trigger='date', run_date=next_trigger_date, args=[reminder.id], id=f"reminder_task_{reminder.id}", replace_existing=True ) else: # 已到重复截止时间,标记为完成 reminder.is_finished = True else: # 无后续提醒,标记为完成 reminder.is_finished = True db.commit()
4. 处理新增/修改提醒的实时调度
当用户新增或修改提醒时,同步更新调度任务:
def create_reminder(db: Session, reminder_data): reminder = Reminder(**reminder_data) db.add(reminder) db.commit() # 新增提醒时添加调度任务 if reminder.next_date and not reminder.is_finished: scheduler.add_job( trigger_reminder_notification, trigger='date', run_date=reminder.next_date, args=[reminder.id], id=f"reminder_task_{reminder.id}", replace_existing=True ) return reminder def update_reminder(db: Session, reminder_id: int, update_data): reminder = db.query(Reminder).get(reminder_id) if not reminder: return None # 先移除原调度任务 job_id = f"reminder_task_{reminder_id}" if scheduler.get_job(job_id): scheduler.remove_job(job_id) # 更新提醒数据 for key, value in update_data.items(): setattr(reminder, key, value) db.commit() # 添加新的调度任务(如果有效) if reminder.next_date and not reminder.is_finished: scheduler.add_job( trigger_reminder_notification, trigger='date', run_date=reminder.next_date, args=[reminder.id], id=job_id, replace_existing=True ) return reminder
5. 数据库层面的时间计算优化
可以直接在PostgreSQL中计算下次触发时间,减少应用层逻辑复杂度:
-- 查询未来1小时内需要触发的提醒 SELECT * FROM reminders WHERE is_finished = false AND ( -- 单次提醒:首次触发时间在未来1小时内 (repeat_type = 'NEVER' AND date BETWEEN NOW() AND NOW() + INTERVAL '1 hour') OR -- 重复提醒:计算下次触发时间在未来1小时内,且未超过结束时间 (repeat_type != 'NEVER' AND (last_reminded_at + repeat_interval) BETWEEN NOW() AND NOW() + INTERVAL '1 hour' AND (repeat_end_date IS NULL OR (last_reminded_at + repeat_interval) <= repeat_end_date)) );
关键注意事项
- 时区一致性:确保数据库、应用服务器、调度器使用同一时区,避免时间偏差
- 任务持久化:配置APScheduler的任务存储(如SQLAlchemy),避免系统重启后丢失未触发任务
- 异常重试:在FCM发送逻辑中添加异常捕获与重试机制,避免因网络问题导致通知失败
- 分布式锁:集群部署时,用Redis分布式锁确保同一提醒任务只被执行一次
内容的提问来源于stack exchange,提问作者Dmitriy Lunev
相关产品推荐
相关产品推荐

