系统设计:日历应用
发布: (2026年1月7日 GMT+8 04:41)
5 min read
原文: Dev.to
Source: Dev.to
请提供您希望翻译的具体文本内容(文章正文、代码块除外),我将为您翻译成简体中文并保持原有的 Markdown 格式。
功能需求
- 创建事件、修改事件、取消事件
- 查看日历(每日、每周或每年)
- 设置重复会议
- 通过电子邮件发送任何更改的通知
非功能性需求
- 高可用性 → 一致性(同步事件的最终一致性)
- 应支持 10亿 用户
- 查看日历的低延迟(读多写少 > 写多读少)
数据模型
- 用户
- 事件
- 重复
API
POST /events/
{
"title": "Meeting title",
"userId": "creator_id", // creator
"userIds": ["participant1", "participant2"], // optional list of attendees
"startTime": "2025-01-10T15:00:00Z",
"endTime": "2025-01-10T16:00:00Z",
"content": { // JSON blob: video call link, description, etc.
"videoCallLink": "https://example.com/meet/123",
"description": "Discuss project status"
},
"recurrence": "weekly|biweekly|monthly|yearly" // optional
}
GET /events?startDay=&endDay=
返回一个事件列表,这些事件位于请求的日期范围内。
高层设计
(概述组件、缓存层、数据库和客户端同步。)
Source: …
深入探讨
1. 存储每日或循环事件
事件创建与存储
案例 1 – 简单(一次性)事件
示例:医生预约
| event_id | owner_id | title | start_time_utc | end_time_utc | rrule | tz | version |
|---|---|---|---|---|---|---|---|
| evt_101 | user_1 | 医生预约 | 2025‑01‑10 15:00 | 2025‑01‑10 16:00 | NULL | UTC | 1 |
返回该行并直接渲染;无需展开。
案例 2 – 循环事件(每周)
示例:团队同步
| event_id | owner_id | title | start_time_utc | end_time_utc | rrule | tz | version |
|---|---|---|---|---|---|---|---|
| evt_201 | user_1 | 团队同步 | 2025‑01‑06 10:00 | 2025‑01‑06 11:00 | FREQ=WEEKLY;BYDAY=MO | UTC | 1 |
GET 行为(周视图) – 获取基础行,仅在请求的时间范围内展开规则,在内存中生成出现实例。
案例 3 – 带异常的循环事件(一次取消)
示例:2025‑01‑20 的团队同步被取消
event_exceptions 表:
| exception_id | event_id | exception_date | type |
|---|---|---|---|
| ex_301 | evt_201 | 2025‑01‑20 | 已取消 |
GET 行为(2025‑01‑20 那一周) – 展开每周规则,匹配异常,剔除该实例,返回其余事件。
存储概览
- 一次性事件 → 1 行
- 多年周期的每周事件 → 1 行
- 每周 + N 异常 → 1 + N 行
结果:存储最小化。
2. 视图生成与低延迟
用户流程: 服务器向客户端发送事件列表。
优化 – Redis 缓存
- 客户端请求事件。
- 服务器检查 Redis:
- 缓存命中 → 返回缓存的事件。
- 缓存未命中 → 查询数据库,填充 Redis,返回结果。
优点:读取快速,降低 DB 负载。
缺点:缓存失效复杂,存在最终一致性窗口。
结论: 混合方案 – 客户端本地展开 + SQLite 离线存储,既能实现低延迟,又能提供离线能力。
3. 冲突检测与锁策略
- 乐观锁(默认) – 争用低,适用于个人日历;服务层需要重试逻辑。
- 悲观锁 – 适用于高争用日历;锁定多行以避免竞争条件。
4. 多设备同步(推送 & 拉取)
- 拉取(增量同步) – 用于冷启动、重新连接、错过的更新。
- 推送(SSE / 推送通知) – 获取更新的规则,在本地重新展开,更新 SQLite + UI。SSE 更受青睐:单向、轻量、对电池友好。
- 混合模型 – 推送保证新鲜度,拉取保证正确性。
5. 数据库选择:SQL vs NoSQL
SQL 的优势
- 写入量适中,关系型数据库足以扩展。
- 事务简化冲突检查。
- 循环查询(RRULE 展开)适配关系模型。
- 强一致性(或可配置的最终一致性)更易保证。
NoSQL 的权衡
- 没有事务时冲突检查困难。
- 循环查询不适配。
- 默认是最终一致性,需要额外工作确保正确性。
- 为获得相同保证而导致实现复杂度提升。