设备端SDK多种情况同步流程
概述
OpenIM SDK 客户端根据不同的使用场景和数据量,设计了四种不同的同步机制,每种机制都有其特定的触发条件、执行策略和适用场景。本文档将详细分析这四种同步机制的实现原理、执行流程、优缺点和设计理念。
四种同步机制概览
| 同步类型 | 触发场景 | 数据量 | 执行策略 | 用户体验 |
|---|---|---|---|---|
| 唤醒同步 | 应用从后台切换到前台 | 小-中等 | 快速增量同步 | 无感知,快速响应 |
| 普通同步 | 网络重连,会话数量 < 500 | 中等 | 标准同步流程 | 显示同步进度 |
| 大数据同步 | 网络重连,会话数量 ≥ 500 | 大量 | 并发批量同步 | 显示同步进度,时间较长 |
| 重装同步 | 应用重新安装后首次启动 | 全量 | 完整数据恢复 | 显示安装进度,时间最长 |
一、唤醒同步 (doWakeupDataSync)
1.1 触发场景与特点
触发条件:
- 应用从后台切换到前台
- 接收到
CmdWakeUpDataSync命令 - 用户主动刷新数据
核心特点:
- 轻量级增量同步
- 无UI进度提示
- 快速响应用户操作
- 静默执行,不打扰用户
1.2 执行流程
唤醒同步包含两个主要阶段:事件触发阶段和数据同步阶段
1.2.1 事件触发阶段流程
sequenceDiagram
participant App as 应用前台
participant MsgSyncer as 消息同步器
participant EventQueue as 事件队列
participant ConvModule as 会话模块
participant Server as 服务器
App->>MsgSyncer: CmdWakeUpDataSync
Note over MsgSyncer: doWakeupDataSync()
%% 第一部分:触发数据同步事件
MsgSyncer->>EventQueue: DispatchSyncData(CmdSyncData)
Note over EventQueue: 事件队列分发
EventQueue->>ConvModule: Work(CmdSyncData)
Note over ConvModule: 路由到syncData()
ConvModule->>ConvModule: syncData() 执行数据同步
%% 第二部分:获取服务器序列号并进行同步
MsgSyncer->>Server: GetMaxSeqReq (获取最新序列号)
Server->>MsgSyncer: GetMaxSeqResp (返回各会话最新序列号)
Note over MsgSyncer: compareSeqsAndBatchSync()
MsgSyncer->>MsgSyncer: 计算序列号差异needSyncSeqMap
MsgSyncer->>MsgSyncer: syncAndTriggerMsgs(批量同步消息)
MsgSyncer->>Server: pullMsgBySeqRange(拉取消息)
Server->>MsgSyncer: 返回消息数据
MsgSyncer->>ConvModule: triggerConversation(触发会话消息)
MsgSyncer->>ConvModule: triggerNotification(触发通知消息)
1.2.2 数据同步阶段详细流程
sequenceDiagram
participant ConvModule as 会话模块
participant UserModule as 用户模块
participant RelationModule as 关系模块
participant GroupModule as 群组模块
participant DB as 本地数据库
participant UI as 用户界面
Note over ConvModule: syncData() 开始执行
ConvModule->>ConvModule: 获取同步锁 (conversationSyncMutex)
Note over ConvModule: 第1阶段:核心数据同步 (syncWait)
ConvModule->>ConvModule: SyncAllConUnreadAndCreateNewCon
ConvModule->>DB: 同步会话未读数并创建新会话
Note over ConvModule: 第2阶段:辅助数据异步同步 (asyncNoWait)
par 并发执行多个同步任务
ConvModule->>UserModule: SyncLoginUserInfo (同步登录用户信息)
ConvModule->>RelationModule: SyncAllBlackList (同步黑名单)
ConvModule->>RelationModule: SyncAllFriendApplication (同步好友申请)
ConvModule->>RelationModule: SyncAllSelfFriendApplication (同步自己的好友申请)
ConvModule->>GroupModule: SyncAllAdminGroupApplication (同步群组管理申请)
ConvModule->>GroupModule: SyncAllSelfGroupApplication (同步自己的群组申请)
ConvModule->>GroupModule: IncrSyncJoinGroupWithLock (增量同步加入的群组)
ConvModule->>RelationModule: IncrSyncFriendsWithLock (增量同步好友)
ConvModule->>ConvModule: IncrSyncConversationsWithLock (增量同步会话)
end
Note over ConvModule: 释放同步锁,完成同步
ConvModule->>UI: 触发相关更新事件
1.3 源码流程分析
1.3.1 唤醒同步入口 (doWakeupDataSync)
func (m *MsgSyncer) doWakeupDataSync(ctx context.Context) {
// 1. 触发同步开始事件,分发到事件队列
common.DispatchSyncData(ctx, m.conversationEventQueue)
// 2. 获取服务端最新序列号
var resp sdkws.GetMaxSeqResp
if err := m.longConnMgr.SendReqWaitResp(ctx,
&sdkws.GetMaxSeqReq{UserID: m.loginUserID},
constant.GetNewestSeq, &resp); err != nil {
log.ZError(ctx, "get max seq error", err)
return
}
// 3. 比较序列号并执行批量同步
m.compareSeqsAndBatchSync(ctx, resp.MaxSeqs, defaultPullNums)
}
1.3.2 事件分发链路 (DispatchSyncData)
// 步骤1: DispatchSyncData 触发事件
func DispatchSyncData(ctx context.Context, queue *EventQueue) {
_ = DispatchCmd(ctx, constant.CmdSyncData, nil, queue)
}
// 步骤2: DispatchCmd 封装命令
func DispatchCmd(ctx context.Context, cmd string, val any, queue *EventQueue) error {
c2v := Cmd2Value{Cmd: cmd, Value: val, Ctx: ctx}
return sendCmdToQueue(ctx, queue, c2v, timeout)
}
// 步骤3: sendCmdToQueue 发送到事件队列
func sendCmdToQueue(ctx context.Context, queue *EventQueue, value Cmd2Value, timeout time.Duration) error {
priority, ok := CmdPriorityMap[value.Cmd]
if !ok {
priority = 1 // 默认优先级
}
ctxWithTimeout, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// 向队列发送事件
_, err := queue.ProduceWithContext(ctxWithTimeout, value, priority)
return err
}
1.3.3 事件处理链路 (Work -> syncData)
// 步骤4: Work 方法路由命令
func (c *Conversation) Work(c2v common.Cmd2Value) {
switch c2v.Cmd {
case constant.CmdSyncData:
c.syncData(c2v) // 路由到数据同步方法
// ... 其他命令处理
}
}
// 步骤5: syncData 执行具体同步逻辑
func (c *Conversation) syncData(c2v common.Cmd2Value) {
c.conversationSyncMutex.Lock()
defer c.conversationSyncMutex.Unlock()
ctx := c2v.Ctx
c.startTime = time.Now()
// 第1阶段:同步执行核心数据同步
syncFuncs := []func(c context.Context) error{
c.SyncAllConUnreadAndCreateNewCon, // 同步所有会话未读数并创建新会话
}
runSyncFunctions(ctx, syncFuncs, syncWait)
// 第2阶段:异步执行辅助数据同步
asyncFuncs := []func(c context.Context) error{
c.user.SyncLoginUserInfo, // 同步登录用户信息
c.relation.SyncAllBlackList, // 同步黑名单
c.relation.SyncAllFriendApplication, // 同步好友申请
c.relation.SyncAllSelfFriendApplication, // 同步自己的好友申请
c.group.SyncAllAdminGroupApplication, // 同步群组管理申请
c.group.SyncAllSelfGroupApplication, // 同步自己的群组申请
c.group.IncrSyncJoinGroupWithLock, // 增量同步加入的群组(带锁)
c.relation.IncrSyncFriendsWithLock, // 增量同步好友(带锁)
c.IncrSyncConversationsWithLock, // 增量同步会话(带锁)
}
runSyncFunctions(ctx, asyncFuncs, asyncNoWait)
}
1.3.4 同步执行策略 (runSyncFunctions)
func runSyncFunctions(ctx context.Context, funcs []func(c context.Context) error, mode int) {
var wg sync.WaitGroup
for _, fn := range funcs {
switch mode {
case asyncWait:
// 异步等待模式:并发执行但等待所有任务完成
wg.Add(1)
go executeSyncFunction(ctx, fn, &wg)
case asyncNoWait:
// 异步不等待模式:并发执行不等待完成
go executeSyncFunction(ctx, fn, nil)
case syncWait:
// 同步等待模式:串行执行
executeSyncFunction(ctx, fn, nil)
}
}
if mode == asyncWait {
wg.Wait() // 等待所有异步任务完成
}
}
1.3.5 序列号比较与批量同步 (compareSeqsAndBatchSync)
compareSeqsAndBatchSync是唤醒同步的核心逻辑,负责比较本地和服务端的序列号差异,计算需要同步的消息范围,并执行实际的消息拉取和触发操作。
func (m *MsgSyncer) compareSeqsAndBatchSync(ctx context.Context, maxSeqToSync map[string]int64, pullNums int64) {
needSyncSeqMap := make(map[string][2]int64)
// 正常运行模式:计算所有会话的同步范围
for conversationID, maxSeq := range maxSeqToSync {
if syncedMaxSeq, ok := m.syncedMaxSeqs[conversationID]; ok {
// 如果本地有同步记录,且服务端序列号更大,则需要同步中间的间隙
if maxSeq > syncedMaxSeq {
needSyncSeqMap[conversationID] = [2]int64{syncedMaxSeq + 1, maxSeq}
}
} else {
if maxSeq != 0 { // 序列号为0表示无需同步
// 如果本地没有同步记录,从序列号0开始同步到最新
needSyncSeqMap[conversationID] = [2]int64{0, maxSeq}
}
}
}
// 执行正常模式的消息同步
_ = m.syncAndTriggerMsgs(ctx, needSyncSeqMap, pullNums)
}
// syncAndTriggerMsgs 同步并触发消息,这是核心的消息同步方法
// 功能:
// 1. 按序列号范围批量拉取消息
// 2. 分批处理大量消息,避免内存溢出
// 3. 区分处理普通会话和通知会话
// 4. 触发相应的事件回调
// 5. 更新本地同步状态
// 参数:
// - ctx: 上下文,用于控制和日志记录
// - seqMap: 会话ID到序列号范围[起始,结束]的映射
// - syncMsgNum: 每次同步的消息数量限制
//
// 返回:
// - error: 同步过程中的错误,如果成功则返回nil
func (m *MsgSyncer) syncAndTriggerMsgs(ctx context.Context, seqMap map[string][2]int64, syncMsgNum int64) error {
if len(seqMap) == 0 {
log.ZDebug(ctx, "nothing to sync", "syncMsgNum", syncMsgNum)
return nil
}
log.ZDebug(ctx, "current sync seqMap", "seqMap", seqMap)
var (
tempSeqMap = make(map[string][2]int64, 50) // 临时序列号映射,用于分批处理
msgNum = 0 // 当前批次累计的消息数量
)
// 遍历所有需要同步的会话
for k, v := range seqMap {
oneConversationSyncNum := v[1] - v[0] + 1 // 计算当前会话需要同步的消息数量
tempSeqMap[k] = v
// 根据会话类型计算消息数量
if IsNotification(k) {
// 对于通知会话,使用实际需要同步的消息数量
msgNum += int(oneConversationSyncNum)
} else {
// 对于普通会话,限制每次同步的消息数量,确保不超过syncMsgNum
msgNum += int(min(oneConversationSyncNum, syncMsgNum))
}
// 如果累计消息数量达到分批阈值,触发一次批量拉取
if msgNum >= SplitPullMsgNum {
resp, err := m.pullMsgBySeqRange(ctx, tempSeqMap, syncMsgNum)
if err != nil {
log.ZError(ctx, "syncMsgFromServer error", err, "tempSeqMap", tempSeqMap)
return err
}
// 触发会话消息事件
_ = m.triggerConversation(ctx, resp.Msgs)
// 触发通知消息事件
_ = m.triggerNotification(ctx, resp.NotificationMsgs)
// 更新本地同步状态
for conversationID, seqs := range tempSeqMap {
m.syncedMaxSeqs[conversationID] = seqs[1]
}
// 重置临时变量,准备处理下一批
tempSeqMap = make(map[string][2]int64, 50)
msgNum = 0
}
}
// 处理剩余的消息,确保所有消息都被同步
if len(tempSeqMap) > 0 {
resp, err := m.pullMsgBySeqRange(ctx, tempSeqMap, syncMsgNum)
if err != nil {
log.ZError(ctx, "syncMsgFromServer error", err, "tempSeqMap", tempSeqMap)
return err
}
_ = m.triggerConversation(ctx, resp.Msgs)
_ = m.triggerNotification(ctx, resp.NotificationMsgs)
for conversationID, seqs := range tempSeqMap {
m.syncedMaxSeqs[conversationID] = seqs[1]
}
}
return nil
}
核心处理逻辑:
-
序列号间隙计算
- 比较本地已同步的最大序列号(
syncedMaxSeq)与服务端最新序列号(maxSeq) - 计算需要同步的序列号范围:
[syncedMaxSeq + 1, maxSeq] - 对于新会话(本地无记录),从序列号0开始同步
- 比较本地已同步的最大序列号(
-
同步范围优化
- 跳过序列号为0的会话(表示无消息需要同步)
- 只同步有间隙的会话,避免不必要的网络请求
- 生成精确的同步映射表
needSyncSeqMap
-
三阶段消息处理
阶段一:消息拉取 (syncAndTriggerMsgs)
- 主要功能:按序列号范围批量拉取消息,分批处理避免内存溢出
- 处理策略:区分普通会话和通知会话,使用不同的拉取限制
- 性能优化:当累计消息数量达到
SplitPullMsgNum(100条)时触发批量拉取 - 调用关系:
syncAndTriggerMsgs→pullMsgBySeqRange
阶段二:网络请求 (pullMsgBySeqRange)
- 主要功能:构造批量拉取消息的网络请求,通过长连接获取服务端消息
- 请求构造:为每个会话创建
SeqRange对象,指定起始和结束序列号 - 并发控制:支持多个会话同时拉取,提高同步效率
- 错误处理:网络请求失败时记录详细错误信息
阶段三:事件触发 (triggerConversation + triggerNotification)
triggerConversation处理:
- 目标方法:
conversation_msg.go中的doMsgNew方法 - 核心功能:处理普通会话消息的完整生命周期
- 主要职责:
- 消息去重和状态更新
- 会话信息创建和维护
- 未读计数统计和更新
- 数据持久化存储
- UI事件通知触发
triggerNotification处理:
- 目标方法:
notification.go中的通知处理逻辑 - 核心功能:处理系统通知类型的消息
- 主要职责:
- 好友关系变更通知(添加、删除、申请)
- 群组状态变更通知(创建、解散、成员变更)
- 用户信息变更通知(头像、昵称更新)
- 会话设置变更通知(置顶、免打扰等)
1.2.4 完整同步执行流程图
graph TD
A["doWakeupDataSync()"] --> B["DispatchSyncData()"]
B --> C["DispatchCmd()"]
C --> D["sendCmdToQueue()"]
D --> E["EventQueue.ProduceWithContext()"]
E --> F["事件队列存储 CmdSyncData"]
F --> G["EventQueue 消费者"]
G --> H["Conversation.Work()"]
H --> I{"判断命令类型"}
I -->|"CmdSyncData"| J["syncData()"]
J --> K["获取同步锁<br/>conversationSyncMutex"]
K --> L["第1阶段:核心数据同步<br/>(syncWait)"]
L --> M["SyncAllConUnreadAndCreateNewCon"]
M --> N["第2阶段:辅助数据同步<br/>(asyncNoWait)"]
N --> O["并发执行9个同步任务"]
O --> P1["SyncLoginUserInfo"]
O --> P2["SyncAllBlackList"]
O --> P3["SyncAllFriendApplication"]
O --> P4["SyncAllSelfFriendApplication"]
O --> P5["SyncAllAdminGroupApplication"]
O --> P6["SyncAllSelfGroupApplication"]
O --> P7["IncrSyncJoinGroupWithLock"]
O --> P8["IncrSyncFriendsWithLock"]
O --> P9["IncrSyncConversationsWithLock"]
%% 同时进行的消息同步流程
A --> A1["GetMaxSeqReq<br/>获取服务端最新序列号"]
A1 --> A2["compareSeqsAndBatchSync<br/>比较序列号差异"]
A2 --> A3["计算needSyncSeqMap<br/>序列号范围[start,end]"]
A3 --> A4["syncAndTriggerMsgs<br/>分批同步消息"]
A4 --> B1["pullMsgBySeqRange<br/>批量拉取消息"]
B1 --> B2["构造SeqRange请求<br/>conversationID+Begin+End"]
B2 --> B3["长连接发送请求<br/>PullMessageBySeqsReq"]
B3 --> B4["接收服务端响应<br/>PullMessageBySeqsResp"]
B4 --> C1["triggerConversation<br/>触发会话消息事件"]
C1 --> C2["doMsgNew处理<br/>@conversation_msg.go"]
C2 --> C3["消息去重与状态更新"]
C3 --> C4["会话信息维护"]
C4 --> C5["未读计数统计"]
C5 --> C6["数据持久化存储"]
B4 --> D1["triggerNotification<br/>触发通知消息事件"]
D1 --> D2["好友关系通知处理<br/>@notification.go"]
D1 --> D3["群组变更通知处理"]
D1 --> D4["用户信息通知处理"]
D1 --> D5["会话设置通知处理"]
%% 汇聚点
P1 --> Q["辅助数据同步完成"]
P2 --> Q
P3 --> Q
P4 --> Q
P5 --> Q
P6 --> Q
P7 --> Q
P8 --> Q
P9 --> Q
C6 --> E1["UI事件通知"]
D5 --> E1
Q --> E1
E1 --> R["唤醒同步完成"]
style A fill:#e1f5fe
style A2 fill:#ffeb3b,color:#000
style A4 fill:#fff9c4
style B1 fill:#f1f8e9
style C1 fill:#fce4ec
style C2 fill:#f8bbd9
style D1 fill:#e8eaf6
style D2 fill:#c8e6c9
style J fill:#fff3e0
style O fill:#e8f5e8
style R fill:#ffebee
二、普通同步 (MsgSyncBegin)
2.1 触发场景与特点
触发条件:
- 网络重连后,需要同步的会话数量 < 500
- 应用启动后的常规数据同步
- 不是重装场景
核心特点:
- 标准的同步流程
- 有进度提示
- 包含消息和元数据同步
- 用户体验平衡
- 会话重新排序(与唤醒同步的重要区别)
2.2 执行流程
sequenceDiagram
participant LongConn as 长连接
participant MsgSyncer as 消息同步器
participant ConvModule as 会话模块
participant BatchProcessor as 批处理器
participant Server as 服务器
participant DB as 本地数据库
participant UI as 用户界面
LongConn->>MsgSyncer: CmdConnSuccesss (连接成功)
Note over MsgSyncer: doConnected()
MsgSyncer->>Server: GetMaxSeqReq (获取序列号)
Server->>MsgSyncer: GetMaxSeqResp (返回序列号信息)
MsgSyncer->>MsgSyncer: getNeedSyncConversations<br/>(计算需要同步的会话)
Note over MsgSyncer: 与唤醒同步的关键区别开始
MsgSyncer->>MsgSyncer: SyncAndSortConversations<br/>(同步并排序会话)
Note over MsgSyncer: 获取会话排序信息<br/>唤醒同步跳过此步骤
MsgSyncer->>MsgSyncer: 判断同步类型 (会话数 < 500)
MsgSyncer->>ConvModule: DispatchSyncFlag(MsgSyncBegin)
ConvModule->>UI: OnSyncServerStart(false) (开始消息同步)
ConvModule->>ConvModule: syncData (执行数据同步)
par 核心数据同步
ConvModule->>DB: SyncAllConUnreadAndCreateNewCon
and 辅助数据异步同步
ConvModule->>DB: SyncLoginUserInfo
ConvModule->>DB: SyncAllBlackList
ConvModule->>DB: SyncAllFriendApplication
ConvModule->>DB: IncrSyncJoinGroup
ConvModule->>DB: IncrSyncFriends
ConvModule->>DB: IncrSyncConversations
end
Note over MsgSyncer: 分批处理消息同步
MsgSyncer->>BatchProcessor: NewConversationBatchProcessor<br/>(创建批处理器)
BatchProcessor->>MsgSyncer: handleMessage (分批调用)
MsgSyncer->>MsgSyncer: syncAndTriggerMsgs (批量同步消息)
MsgSyncer->>ConvModule: DispatchSyncFlag(MsgSyncEnd)
ConvModule->>UI: OnSyncServerFinish(false) (同步完成)
2.3 源码流程分析
2.3.1 连接成功触发入口 (doConnected)
func (m *MsgSyncer) doConnected(ctx context.Context) {
reinstalled := m.reinstalled // 保存重装状态
var resp sdkws.GetMaxSeqResp
// 1. 获取服务端最新的序列号信息
if err := m.longConnMgr.SendReqWaitResp(ctx, &sdkws.GetMaxSeqReq{UserID: m.loginUserID}, constant.GetNewestSeq, &resp); err != nil {
log.ZError(ctx, "get max seq error", err)
common.DispatchSyncFlag(ctx, constant.MsgSyncFailed, m.conversationEventQueue)
return
}
// 2. 计算需要同步的会话列表
needSyncAllSeqMap := m.getNeedSyncConversations(ctx, resp.MaxSeqs)
convCount := len(needSyncAllSeqMap)
if convCount == 0 {
log.ZInfo(ctx, "no conversations need sync")
return
}
// 3. 检查是否为大数据量同步
if len(needSyncAllSeqMap) >= maxConversations {
m.isLargeDataSync = true
}
// 4. 【关键区别】同步并排序会话(唤醒同步没有此步骤)
maxSeqs, sortConversationList, err := m.SyncAndSortConversations(ctx, reinstalled)
if err != nil {
log.ZError(ctx, "SyncAndSortConversations err", err)
}
// 5. 根据数据量触发不同的同步事件
if reinstalled {
common.DispatchSyncFlagWithMeta(ctx, constant.AppDataSyncBegin, maxSeqs, m.conversationEventQueue)
} else {
if m.isLargeDataSync {
common.DispatchSyncFlagWithMeta(ctx, constant.LargeDataSyncBegin, maxSeqs, m.conversationEventQueue)
} else {
// 普通同步事件
common.DispatchSyncFlagWithMeta(ctx, constant.MsgSyncBegin, maxSeqs, m.conversationEventQueue)
}
}
// 6. 【关键区别】使用批处理器分批同步(唤醒同步直接调用compareSeqsAndBatchSync)
sort_conversation.NewConversationBatchProcessor(sortConversationList, needSyncAllSeqMap, synMaxConversations).Run(ctx, m.handleMessage)
// 7. 触发同步结束事件
if !reinstalled && !m.isLargeDataSync {
common.DispatchSyncFlag(ctx, constant.MsgSyncEnd, m.conversationEventQueue)
}
}
2.3.2 计算需要同步的会话 (getNeedSyncConversations)
func (m *MsgSyncer) getNeedSyncConversations(ctx context.Context, maxSeqToSync map[string]int64) map[string][2]int64 {
needSyncSeqMap := make(map[string][2]int64)
// 正常运行模式:同步所有类型的消息
for conversationID, maxSeq := range maxSeqToSync {
if syncedMaxSeq, ok := m.syncedMaxSeqs[conversationID]; ok {
// 如果本地有同步记录,且服务端序列号更大,则需要同步中间的间隙
if maxSeq > syncedMaxSeq {
needSyncSeqMap[conversationID] = [2]int64{syncedMaxSeq + 1, maxSeq}
}
} else {
if maxSeq != 0 { // 序列号为0表示无需同步
// 如果本地没有同步记录,从序列号0开始同步到最新
needSyncSeqMap[conversationID] = [2]int64{0, maxSeq}
}
}
}
return needSyncSeqMap
}
与唤醒同步的区别:
- 唤醒同步:直接在
compareSeqsAndBatchSync中计算,立即执行同步 - 普通同步:先计算需要同步的会话,然后进行排序和分批处理
2.3.3 同步并排序会话 (SyncAndSortConversations)
这是普通同步独有的重要步骤,唤醒同步为了性能考虑跳过了此步骤:
func (m *MsgSyncer) SyncAndSortConversations(ctx context.Context, reinstalled bool) (map[string]*msg.Seqs, *sort_conversation.SortConversationList, error) {
startTime := time.Now()
// 1. 获取服务端的会话序列号和已读状态信息
resp := msg.GetConversationsHasReadAndMaxSeqResp{}
req := msg.GetConversationsHasReadAndMaxSeqReq{UserID: m.loginUserID, ReturnPinned: reinstalled}
err := m.longConnMgr.SendReqWaitResp(ctx, &req, constant.GetConvMaxReadSeq, &resp)
if err != nil {
return nil, nil, err
}
seqs := resp.Seqs
if len(seqs) == 0 {
return nil, nil, nil
}
// 2. 获取本地所有会话信息
conversationsOnLocal, err := m.db.GetAllConversations(ctx)
if err != nil {
return nil, nil, err
}
// 3. 构建会话排序元数据
var list []*sort_conversation.ConversationMetaData
var pinnedConversationIDs []string
// 将服务端置顶会话ID列表转换为映射
pinnedConversationIDsMap := datautil.SliceSetAny(resp.PinnedConversationIDs, func(e string) string {
return e
})
// 4. 处理每个会话的排序元数据
for conversationID, v := range seqs {
sortConversationMetaData := &sort_conversation.ConversationMetaData{
ConversationID: conversationID,
LatestMsgSendTime: v.MaxSeqTime, // 最新消息时间
}
// 检查服务端置顶状态
if _, ok := pinnedConversationIDsMap[conversationID]; ok {
sortConversationMetaData.IsPinned = true
pinnedConversationIDs = append(pinnedConversationIDs, conversationID)
}
// 合并本地会话状态(草稿时间、本地置顶状态)
if conversation, ok := conversationsOnLocalMap[conversationID]; ok {
if conversation.IsPinned {
sortConversationMetaData.IsPinned = true
pinnedConversationIDs = append(pinnedConversationIDs, conversationID)
}
if conversation.DraftTextTime > 0 {
sortConversationMetaData.DraftTextTime = conversation.DraftTextTime
}
}
list = append(list, sortConversationMetaData)
}
// 5. 创建排序后的会话列表
sortConversationList := sort_conversation.NewSortConversationList(list, pinnedConversationIDs)
return resp.Seqs, sortConversationList, nil
}
为什么唤醒同步跳过排序?
- 性能考虑:唤醒同步频繁发生(用户前后台切换),排序操作耗时
- 影响有限:短时间内会话顺序变化不大,用户体验影响小
- 快速响应:优先保证消息同步速度,让用户快速看到新消息
2.3.4 事件触发和数据同步 (syncFlag -> syncData)
func (c *Conversation) syncFlag(c2v common.Cmd2Value) {
switch syncFlag {
case constant.MsgSyncBegin:
c.seqs = seqs
// 通知UI开始消息同步(false表示普通同步,非大数据同步)
c.ConversationListener().OnSyncServerStart(false)
// 执行数据同步(与唤醒同步相同的逻辑)
c.syncData(c2v)
case constant.MsgSyncEnd:
// 通知UI同步完成
c.ConversationListener().OnSyncServerFinish(false)
}
}
// syncData 数据同步处理器
// 执行完整的数据同步流程,包括各个业务模块的数据同步
// 这个方法通常在应用启动或网络重连后调用
//
// 参数:
// - c2v: 同步命令对象
func (c *Conversation) syncData(c2v common.Cmd2Value) {
c.conversationSyncMutex.Lock()
defer c.conversationSyncMutex.Unlock()
ctx := c2v.Ctx
c.startTime = time.Now()
// 1. 同步执行核心数据同步
syncFuncs := []func(c context.Context) error{
c.SyncAllConUnreadAndCreateNewCon, // 同步所有会话未读数并创建新会话
}
runSyncFunctions(ctx, syncFuncs, syncWait)
// 2. 异步执行辅助数据同步
asyncFuncs := []func(c context.Context) error{
c.user.SyncLoginUserInfo, // 同步登录用户信息
c.relation.SyncAllBlackList, // 同步黑名单
c.relation.SyncAllFriendApplication, // 同步好友申请
c.relation.SyncAllSelfFriendApplication, // 同步自己的好友申请
c.group.SyncAllAdminGroupApplication, // 同步群组管理申请 同步用户作为管理员跟群主的群的关系
c.group.SyncAllSelfGroupApplication, // 同步自己的群组申请
c.group.IncrSyncJoinGroupWithLock, // 增量同步加入的群组(带锁)
c.relation.IncrSyncFriendsWithLock, // 增量同步好友(带锁)
c.IncrSyncConversationsWithLock, // 增量同步会话(带锁)
}
runSyncFunctions(ctx, asyncFuncs, asyncNoWait)
}
syncData执行逻辑与唤醒同步完全相同,包含两个阶段:
- 核心数据同步:
SyncAllConUnreadAndCreateNewCon - 辅助数据异步同步:9个并发同步任务
2.3.5 批处理器分批同步 (NewConversationBatchProcessor)
这是普通同步的另一个关键区别,使用批处理器优化大量会话的同步:
// 创建批处理器
func NewConversationBatchProcessor(list *SortConversationList, needSyncSeqMap map[string][2]int64, batchSize int) *ConversationBatchProcessor {
return &ConversationBatchProcessor{
iter: list.NewIterator(), // 排序后的会话迭代器
needSyncSeqMap: needSyncSeqMap, // 需要同步的序列号映射
batchSize: batchSize, // 批处理大小(100个会话)
isFirst: true,
batchID: 1,
}
}
// 运行批处理器
func (p *ConversationBatchProcessor) Run(ctx context.Context, handler BatchHandler) {
result := make(map[string][2]int64)
for {
// 获取下一批会话
batch := p.iter.NextTop(p.batchSize)
if len(batch) == 0 {
break
}
// 处理当前批次的会话
for _, conv := range batch {
// 添加普通会话的同步范围
if v, ok := p.needSyncSeqMap[conv.ConversationID]; ok {
result[conv.ConversationID] = v
}
// 添加对应通知会话的同步范围
notificationID := GetNotificationConversationIDByConversationID(conv.ConversationID)
if v, ok := p.needSyncSeqMap[notificationID]; ok {
result[notificationID] = v
}
// 如果当前批次达到大小限制,触发处理
if len(result) >= p.batchSize {
p.emitResult(ctx, handler, result)
result = make(map[string][2]int64)
}
}
}
// 处理剩余的会话
if len(result) > 0 {
p.emitResult(ctx, handler, result)
}
}
2.3.6 批处理调用消息同步 (handleMessage)
func (m *MsgSyncer) handleMessage(ctx context.Context, batchID int, needSyncTopSeqMap map[string][2]int64, isFirst bool) {
ctx = mcontext.WithTriggerIDContext(ctx, stringutil.IntToString(batchID))
if !m.reinstalled && !m.isLargeDataSync {
// 普通同步模式:使用标准的同步方法
_ = m.syncAndTriggerMsgs(ctx, needSyncTopSeqMap, connectPullNums)
}
// 注意:syncAndTriggerMsgs的具体实现在唤醒同步章节已详细分析
}
2.4 普通同步详细流程图
flowchart TD
A[长连接成功 CmdConnSuccesss] --> B[doConnected 开始]
B --> C[获取服务端最新序列号 GetMaxSeqReq]
C --> D[计算需要同步的会话 getNeedSyncConversations]
D --> E{会话数量判断}
E -->|"< 500个会话"| F[设置普通同步模式]
E -->|"≥ 500个会话"| G[跳转到大数据同步]
F --> H[同步并排序会话 SyncAndSortConversations]
H --> I[获取会话排序信息和置顶状态]
I --> J[触发同步开始事件 MsgSyncBegin]
J --> K[syncFlag 事件处理]
K --> L[OnSyncServerStart false]
L --> M[执行 syncData]
M --> N[同步执行核心数据]
N --> O[SyncAllConUnreadAndCreateNewCon]
O --> P[异步执行辅助数据同步]
P --> Q1[SyncLoginUserInfo]
P --> Q2[SyncAllBlackList]
P --> Q3[SyncAllFriendApplication]
P --> Q4[SyncAllSelfFriendApplication]
P --> Q5[SyncAllAdminGroupApplication]
P --> Q6[SyncAllSelfGroupApplication]
P --> Q7[IncrSyncJoinGroupWithLock]
P --> Q8[IncrSyncFriendsWithLock]
P --> Q9[IncrSyncConversationsWithLock]
Q1 --> R[创建批处理器 NewConversationBatchProcessor]
Q2 --> R
Q3 --> R
Q4 --> R
Q5 --> R
Q6 --> R
Q7 --> R
Q8 --> R
Q9 --> R
R --> S[分批处理会话 每批100个]
S --> T[handleMessage 处理每批]
T --> U[syncAndTriggerMsgs 标准同步]
U --> V[分批拉取消息 SplitPullMsgNum=100]
V --> W[pullMsgBySeqRange 网络请求]
W --> X[triggerConversation 触发会话事件]
W --> Y[triggerNotification 触发通知事件]
X --> Z[同步完成 MsgSyncEnd]
Y --> Z
style A fill:#e1f5fe
style J fill:#f3e5f5
style Z fill:#e8f5e8
2.5 与唤醒同步的关键区别
| 对比维度 | 唤醒同步 | 普通同步 |
|---|---|---|
| 会话排序 | ❌ 跳过排序 | ✅ SyncAndSortConversations |
| 批处理器 | ❌ 直接同步 | ✅ NewConversationBatchProcessor |
| 进度提示 | ❌ 无UI提示 | ✅ OnSyncServerStart/Finish |
| 触发频率 | 高频(前后台切换) | 低频(网络重连) |
| 性能优化 | 极致优化 | 平衡优化 |
| 用户体验 | 无感知 | 有进度反馈 |
2.6 优缺点分析
优点:
- 完整的数据同步,包含消息和元数据
- 会话排序确保最新状态,用户体验好
- 批处理器优化大量会话同步效率
- 有进度反馈,用户了解同步状态
- 同步策略成熟稳定
缺点:
- 同步时间比唤醒同步长
- 会话排序增加额外开销
- 会话数量接近500时性能下降
设计理念: 普通同步是SDK的标准同步模式,设计理念是"完整准确,体验平衡"。相比唤醒同步,它牺牲了一些性能来换取更完整的数据状态和更好的用户体验,特别是会话排序功能确保用户看到的会话列表是最新的排序状态。
三、大数据同步 (LargeDataSyncBegin)
3.1 触发场景与特点
触发条件:
- 网络重连后,需要同步的会话数量 ≥ 500
- 长时间未上线的用户(概率很小的场景)
- 企业用户或重度使用场景
- 不是重装场景
核心特点:
- 与普通同步共享相同的前期流程
- 高并发批量处理策略
- 使用重装同步的并发优化方法
- 专门的大数据同步进度提示
- 长时间同步过程
3.2 执行流程
大数据同步的前期流程与普通同步完全一致,区别在于会话数量判断和后续处理策略:
sequenceDiagram
participant LongConn as 长连接
participant MsgSyncer as 消息同步器
participant ConvModule as 会话模块
participant GroupModule as 群组模块
participant RelationModule as 关系模块
participant UserModule as 用户模块
participant BatchProcessor as 批处理器
participant Server as 服务器
participant DB as 本地数据库
participant UI as 用户界面
LongConn->>MsgSyncer: CmdConnSuccesss (连接成功)
Note over MsgSyncer: doConnected() - 与普通同步相同入口
MsgSyncer->>Server: GetMaxSeqReq (获取序列号)
Server->>MsgSyncer: GetMaxSeqResp (返回序列号信息)
MsgSyncer->>MsgSyncer: getNeedSyncConversations<br/>(计算需要同步的会话)
Note over MsgSyncer: 与普通同步完全相同的逻辑
MsgSyncer->>MsgSyncer: SyncAndSortConversations<br/>(同步并排序会话)
Note over MsgSyncer: 与普通同步完全相同的逻辑
MsgSyncer->>MsgSyncer: 判断会话数量 ≥ 500<br/>设置isLargeDataSync=true
Note over MsgSyncer: 关键分支点:大数据同步判断
MsgSyncer->>ConvModule: DispatchSyncFlag(LargeDataSyncBegin)
Note over ConvModule: syncFlag处理 - 与AppDataSyncBegin完全相同的逻辑
ConvModule->>UI: OnSyncServerStart(true) (大数据同步开始)
ConvModule->>UI: OnSyncServerProgress(1%) (初始进度)
Note over ConvModule: 第一阶段:异步等待执行关键业务数据同步
par 并发执行基础数据同步(asyncWait模式)
ConvModule->>GroupModule: IncrSyncJoinGroup<br/>(同步用户加入的群组信息)
and
ConvModule->>RelationModule: IncrSyncFriends<br/>(同步好友关系信息)
end
ConvModule->>UI: OnSyncServerProgress(40%) (进度40%)
Note over ConvModule: 第二阶段:同步等待执行核心会话数据
ConvModule->>ConvModule: IncrSyncConversations<br/>(同步会话列表)
ConvModule->>ConvModule: SyncAllConUnreadAndCreateNewCon<br/>(同步未读计数并创建新会话)
ConvModule->>UI: OnSyncServerProgress(100%) (进度100%)
Note over ConvModule: 第三阶段:异步不等待执行辅助数据同步
par 并发执行辅助数据同步(asyncNoWait模式)
ConvModule->>UserModule: SyncLoginUserInfoWithoutNotice<br/>(静默同步用户信息)
ConvModule->>RelationModule: SyncAllBlackListWithoutNotice<br/>(静默同步黑名单)
ConvModule->>RelationModule: SyncAllFriendApplicationWithoutNotice<br/>(静默同步好友申请)
ConvModule->>RelationModule: SyncAllSelfFriendApplicationWithoutNotice<br/>(静默同步自己的好友申请)
ConvModule->>GroupModule: SyncAllAdminGroupApplicationWithoutNotice<br/>(静默同步群组管理申请)
ConvModule->>GroupModule: SyncAllSelfGroupApplicationWithoutNotice<br/>(静默同步自己的群组申请)
end
Note over MsgSyncer: 分批处理消息同步(与普通同步相同)
MsgSyncer->>BatchProcessor: NewConversationBatchProcessor<br/>(创建批处理器)
BatchProcessor->>MsgSyncer: handleMessage (分批调用)
Note over MsgSyncer: 使用重装同步的并发策略
MsgSyncer->>MsgSyncer: syncAndTriggerReinstallMsgs<br/>(并发消息同步)
MsgSyncer->>ConvModule: DispatchSyncFlag(LargeDataSyncEnd)
ConvModule->>UI: OnSyncServerProgress(100%) (最终进度)
ConvModule->>UI: OnSyncServerFinish(true) (大数据同步完成)
3.3 源码流程分析
3.3.1 大数据同步判断逻辑 (doConnected)
大数据同步的前期流程与普通同步完全一致,直到会话数量判断:
func (m *MsgSyncer) doConnected(ctx context.Context) {
// 1-2. 获取序列号和计算需要同步的会话(与普通同步相同)
var resp sdkws.GetMaxSeqResp
if err := m.longConnMgr.SendReqWaitResp(ctx, &sdkws.GetMaxSeqReq{UserID: m.loginUserID}, constant.GetNewestSeq, &resp); err != nil {
log.ZError(ctx, "get max seq error", err)
common.DispatchSyncFlag(ctx, constant.MsgSyncFailed, m.conversationEventQueue)
return
}
needSyncAllSeqMap := m.getNeedSyncConversations(ctx, resp.MaxSeqs)
convCount := len(needSyncAllSeqMap)
if convCount == 0 {
return
}
// 3. 【关键判断】检查是否为大数据量同步
if len(needSyncAllSeqMap) >= maxConversations {
log.ZDebug(ctx, "large conversations to sync", nil, "length", len(needSyncAllSeqMap))
m.isLargeDataSync = true // 设置大数据同步标志
}
// 4. 同步并排序会话(与普通同步相同)
maxSeqs, sortConversationList, err := m.SyncAndSortConversations(ctx, reinstalled)
if err != nil {
log.ZError(ctx, "SyncAndSortConversations err", err)
}
// 5. 【关键区别】触发大数据同步事件
if m.isLargeDataSync {
log.ZWarn(ctx, "too many conversations to sync", nil, "maxConversations", maxConversations)
common.DispatchSyncFlagWithMeta(ctx, constant.LargeDataSyncBegin, maxSeqs, m.conversationEventQueue)
}
// 6. 使用批处理器分批同步(与普通同步相同)
sort_conversation.NewConversationBatchProcessor(sortConversationList, needSyncAllSeqMap, synMaxConversations).Run(ctx, m.handleMessage)
// 7. 大数据同步不在此处触发结束事件,由handleMessage控制
}
关键常量定义:
const (
maxConversations = 500 // 大数据同步的阈值
synMaxConversations = 100 // 批处理器的批次大小
)
3.3.2 大数据同步事件处理 (syncFlag)
在notification.go中的事件处理逻辑:
func (c *Conversation) syncFlag(c2v common.Cmd2Value) {
switch syncFlag {
case constant.LargeDataSyncBegin:
log.ZDebug(ctx, "LargeDataSyncBegin")
// 1. 初始化同步状态(与重装同步相同)
c.seqs = seqs
c.startTime = time.Now()
c.ConversationListener().OnSyncServerStart(true) // true表示大数据同步
c.ConversationListener().OnSyncServerProgress(1) // 设置初始进度
// 2. 第一阶段:异步等待执行关键业务数据同步
asyncWaitFunctions := []func(c context.Context) error{
c.group.IncrSyncJoinGroup, // 同步用户加入的群组信息
c.relation.IncrSyncFriends, // 同步好友关系信息
}
runSyncFunctions(ctx, asyncWaitFunctions, asyncWait)
c.addInitProgress(InitSyncProgress * 4 / 10) // 增加40%进度
c.ConversationListener().OnSyncServerProgress(c.progress) // 通知进度更新
// 3. 第二阶段:同步等待执行核心会话数据
syncWaitFunctions := []func(c context.Context) error{
c.IncrSyncConversations, // 同步会话列表
c.SyncAllConUnreadAndCreateNewCon, // 同步未读计数并创建新会话
}
runSyncFunctions(ctx, syncWaitFunctions, syncWait)
log.ZWarn(ctx, "core data sync over", nil, "cost time", time.Since(c.startTime).Seconds())
c.addInitProgress(InitSyncProgress * 6 / 10) // 增加60%进度
c.ConversationListener().OnSyncServerProgress(c.progress) // 通知进度更新
// 4. 第三阶段:异步不等待执行辅助数据同步
asyncNoWaitFunctions := []func(c context.Context) error{
c.user.SyncLoginUserInfoWithoutNotice, // 同步登录用户信息(静默)
c.relation.SyncAllBlackListWithoutNotice, // 同步黑名单(静默)
c.relation.SyncAllFriendApplicationWithoutNotice, // 同步好友申请(静默)
c.relation.SyncAllSelfFriendApplicationWithoutNotice, // 同步自己发出的好友申请(静默)
c.group.SyncAllAdminGroupApplicationWithoutNotice, // 同步群组管理申请(静默)
c.group.SyncAllSelfGroupApplicationWithoutNotice, // 同步自己的群组申请(静默)
}
runSyncFunctions(ctx, asyncNoWaitFunctions, asyncNoWait)
case constant.LargeDataSyncEnd:
// 大数据同步完成
c.ConversationListener().OnSyncServerFinish(true) // true表示大数据同步
}
}
3.3.3 批处理器处理 (NewConversationBatchProcessor)
大数据同步使用与普通同步完全相同的批处理器逻辑:
// 创建批处理器(与普通同步相同)
sort_conversation.NewConversationBatchProcessor(sortConversationList, needSyncAllSeqMap, synMaxConversations)
// 批处理器的Run方法会调用handleMessage
func (p *ConversationBatchProcessor) Run(ctx context.Context, handler BatchHandler) {
// 按批次处理会话,每批最多100个会话
// 具体逻辑与普通同步完全一致
}
3.3.4 消息同步处理 (handleMessage)
大数据同步的关键区别在于使用重装同步的并发策略:
func (m *MsgSyncer) handleMessage(ctx context.Context, batchID int, needSyncTopSeqMap map[string][2]int64, isFirst bool) {
ctx = mcontext.WithTriggerIDContext(ctx, stringutil.IntToString(batchID))
reinstalled := m.reinstalled
if m.isLargeDataSync {
// 大数据同步模式:使用重装模式的并发同步方法提高效率
log.ZWarn(ctx, "handleMessage large conversations to sync", nil, "length", len(needSyncTopSeqMap), "isFirst", isFirst, "maxConversations", maxConversations)
_ = m.syncAndTriggerReinstallMsgs(ctx, needSyncTopSeqMap, isFirst, connectPullNums)
if isFirst {
// 第一批处理完成,触发大数据同步结束事件
common.DispatchSyncFlag(ctx, constant.LargeDataSyncEnd, m.conversationEventQueue)
}
}
// 注意:syncAndTriggerReinstallMsgs使用并发策略,效率比普通的syncAndTriggerMsgs更高
}
3.3.5 并发消息同步策略 (syncAndTriggerReinstallMsgs)
大数据同步使用重装同步的高并发策略:
// syncAndTriggerReinstallMsgs 重装模式的消息同步和触发方法
// 功能:
// 1. 专门用于应用重装或大数据量场景的消息同步
// 2. 支持并发拉取多个会话的消息,提高同步效率
// 3. 使用goroutine池限制并发数,避免资源耗尽
// 4. 检查消息有效性,自动获取最新有效消息
// 5. 线程安全地更新同步状态
// 参数:
// - ctx: 上下文,用于控制和日志记录
// - seqMap: 会话ID到序列号范围[起始,结束]的映射
// - isFirst: 是否为第一批次,用于事件触发判断
// - syncMsgNum: 每次同步的消息数量限制
//
// 返回:
// - error: 同步过程中的错误,如果成功则返回nil
func (m *MsgSyncer) syncAndTriggerReinstallMsgs(ctx context.Context, seqMap map[string][2]int64, isFirst bool, syncMsgNum int64) error {
if len(seqMap) > 0 {
log.ZDebug(ctx, "current sync seqMap", "seqMap", seqMap)
var (
tempSeqMap = make(map[string][2]int64, 50) // 临时序列号映射,用于分批处理
msgNum = 0 // 当前批次累计的消息数量
total = 0 // 总会话数量,用于进度计算
gr *errgroup.Group // 并发控制组
)
// 如果是第一批,记录总数用于进度展示
if isFirst {
total = len(seqMap)
}
// 创建并发控制组,限制并发goroutine数量
gr, _ = errgroup.WithContext(ctx)
gr.SetLimit(pullMsgGoroutineLimit) // 设置最大并发数,避免资源耗尽
// 遍历所有需要同步的会话,进行分批处理
for k, v := range seqMap {
oneConversationSyncNum := min(v[1]-v[0]+1, syncMsgNum) // 计算当前会话实际需要同步的消息数
tempSeqMap[k] = v
// 根据会话类型累计消息数量
if IsNotification(k) {
// 通知会话:使用实际需要同步的消息数量
msgNum += int(oneConversationSyncNum)
} else {
// 普通会话:限制每次同步的消息数量,确保不超过syncMsgNum
msgNum += int(min(oneConversationSyncNum, syncMsgNum))
}
// 如果累计消息数量达到分批阈值,启动一个并发任务
if msgNum >= SplitPullMsgNum {
// 复制当前批次的序列号映射,避免并发访问冲突
tpSeqMap := make(map[string][2]int64, len(tempSeqMap))
for k, v := range tempSeqMap {
tpSeqMap[k] = v
}
// 启动并发任务处理当前批次
gr.Go(func() error {
// 批量拉取消息
resp, err := m.pullMsgBySeqRange(ctx, tpSeqMap, syncMsgNum)
if err != nil {
log.ZError(ctx, "syncMsgFromServer err", err, "tempSeqMap", tpSeqMap)
return err
}
// 检查消息有效性,获取最新有效消息
m.checkMessagesAndGetLastMessage(ctx, resp.Msgs)
// 触发重装模式的会话消息事件
_ = m.triggerReinstallConversation(ctx, resp.Msgs, total)
// 触发通知消息事件
_ = m.triggerNotification(ctx, resp.NotificationMsgs)
// 线程安全地更新同步状态
for conversationID, seqs := range tpSeqMap {
m.syncedMaxSeqsLock.Lock()
m.syncedMaxSeqs[conversationID] = seqs[1]
m.syncedMaxSeqsLock.Unlock()
}
return nil
})
// 重置临时变量,准备处理下一批
tempSeqMap = make(map[string][2]int64, 50)
msgNum = 0
}
}
// 处理剩余的会话,确保所有会话都被同步
if len(tempSeqMap) > 0 && msgNum > 0 {
gr.Go(func() error {
// 拉取剩余会话的消息
resp, err := m.pullMsgBySeqRange(ctx, tempSeqMap, syncMsgNum)
if err != nil {
log.ZError(ctx, "syncMsgFromServer err", err, "seqMap", seqMap)
return err
}
// 检查消息有效性,获取最新有效消息
m.checkMessagesAndGetLastMessage(ctx, resp.Msgs)
// 触发重装模式的会话消息事件
_ = m.triggerReinstallConversation(ctx, resp.Msgs, total)
// 触发通知消息事件
_ = m.triggerNotification(ctx, resp.NotificationMsgs)
// 线程安全地更新同步状态
for conversationID, seqs := range tempSeqMap {
m.syncedMaxSeqsLock.Lock()
m.syncedMaxSeqs[conversationID] = seqs[1]
m.syncedMaxSeqsLock.Unlock()
}
return nil
})
}
// 等待所有并发任务完成
if err := gr.Wait(); err != nil {
return err
}
} else {
log.ZDebug(ctx, "noting conversation to sync", "syncMsgNum", syncMsgNum)
}
return nil
}
3.3.6 syncAndTriggerReinstallMsgs 详细执行流程
syncAndTriggerReinstallMsgs是大数据同步和重装同步的核心并发处理方法,其执行流程可以分为以下几个关键步骤:
步骤一:初始化和参数准备
// 1. 变量初始化
tempSeqMap := make(map[string][2]int64, 50) // 临时序列号映射,容量50
msgNum := 0 // 累计消息数量计数器
total := 0 // 总会话数量(用于进度计算)
var gr *errgroup.Group // 并发控制组
// 2. 设置总数(仅第一批次)
if isFirst {
total = len(seqMap) // 记录总会话数,用于UI进度展示
}
// 3. 创建并发控制组
gr, _ = errgroup.WithContext(ctx)
gr.SetLimit(pullMsgGoroutineLimit) // 限制最大并发数为10
步骤二:分批处理逻辑
// 遍历所有需要同步的会话
for conversationID, seqRange := range seqMap {
// 1. 计算当前会话需要同步的消息数量
oneConversationSyncNum := min(seqRange[1]-seqRange[0]+1, syncMsgNum)
tempSeqMap[conversationID] = seqRange
// 2. 根据会话类型累计消息数量
if IsNotification(conversationID) {
msgNum += int(oneConversationSyncNum) // 通知会话:使用实际数量
} else {
msgNum += int(min(oneConversationSyncNum, syncMsgNum)) // 普通会话:限制数量
}
// 3. 达到分批阈值时启动并发任务
if msgNum >= SplitPullMsgNum { // 100条消息阈值
启动并发任务处理当前批次()
重置临时变量()
}
}
步骤三:并发任务执行
// 每个并发任务的处理流程
gr.Go(func() error {
// 1. 网络请求:批量拉取消息
resp, err := m.pullMsgBySeqRange(ctx, tpSeqMap, syncMsgNum)
if err != nil {
return err // 网络错误直接返回
}
// 2. 消息有效性检查
m.checkMessagesAndGetLastMessage(ctx, resp.Msgs)
// 3. 事件触发:分发消息到不同处理器
_ = m.triggerReinstallConversation(ctx, resp.Msgs, total) // 普通会话消息
_ = m.triggerNotification(ctx, resp.NotificationMsgs) // 通知消息
// 4. 线程安全更新同步状态
for conversationID, seqs := range tpSeqMap {
m.syncedMaxSeqsLock.Lock()
m.syncedMaxSeqs[conversationID] = seqs[1] // 更新最大已同步序列号
m.syncedMaxSeqsLock.Unlock()
}
return nil
})
步骤四:等待所有任务完成
// 等待所有并发任务完成
if err := gr.Wait(); err != nil {
return err // 任何一个任务失败都会导致整体失败
}
关键设计特点:
- 内存控制:通过
tempSeqMap容量限制和分批阈值避免内存溢出 - 并发控制:使用
errgroup限制最大并发数,防止资源耗尽 - 错误处理:任意一个并发任务失败都会中断整个同步流程
- 线程安全:使用读写锁保护共享的
syncedMaxSeqs映射 - 性能优化:批量网络请求减少RTT,并发处理提高吞吐量
3.4 大数据同步详细流程图
flowchart TD
A["长连接成功 CmdConnSuccesss"] --> B["doConnected 开始"]
B --> C["获取服务端最新序列号 GetMaxSeqReq"]
C --> D["计算需要同步的会话 getNeedSyncConversations"]
D --> E{"会话数量判断"}
E -->|"< 500个会话"| F["跳转到普通同步"]
E -->|"≥ 500个会话"| G["设置大数据同步模式 isLargeDataSync=true"]
G --> H["同步并排序会话 SyncAndSortConversations"]
H --> I["获取会话排序信息和置顶状态"]
I --> J["触发大数据同步开始事件 LargeDataSyncBegin"]
J --> K["syncFlag 事件处理"]
K --> L["OnSyncServerStart true"]
L --> M["OnSyncServerProgress 1%"]
M --> N["第一阶段:异步等待基础数据同步"]
N --> O1["IncrSyncJoinGroup 并发"]
N --> O2["IncrSyncFriends 并发"]
O1 --> P["等待第一阶段完成"]
O2 --> P
P --> Q["OnSyncServerProgress 40%"]
Q --> R["第二阶段:同步等待核心数据"]
R --> S1["IncrSyncConversations 串行"]
R --> S2["SyncAllConUnreadAndCreateNewCon 串行"]
S1 --> T["等待第二阶段完成"]
S2 --> T
T --> U["OnSyncServerProgress 100%"]
U --> V["第三阶段:异步不等待辅助数据"]
V --> W1["SyncLoginUserInfoWithoutNotice"]
V --> W2["SyncAllBlackListWithoutNotice"]
V --> W3["SyncAllFriendApplicationWithoutNotice"]
V --> W4["SyncAllSelfFriendApplicationWithoutNotice"]
V --> W5["SyncAllAdminGroupApplicationWithoutNotice"]
V --> W6["SyncAllSelfGroupApplicationWithoutNotice"]
U --> X["创建批处理器 NewConversationBatchProcessor"]
X --> Y["分批处理会话 每批100个"]
Y --> Z["handleMessage 处理每批"]
Z --> AA["syncAndTriggerReinstallMsgs 高并发同步"]
AA --> BB["errgroup 限制并发数10"]
BB --> CC["分批拉取消息 SplitPullMsgNum=100"]
CC --> DD["pullMsgBySeqRange 并发网络请求"]
DD --> EE["checkMessagesAndGetLastMessage"]
EE --> FF["triggerReinstallConversation"]
EE --> GG["triggerNotification"]
FF --> HH["同步完成 LargeDataSyncEnd"]
GG --> HH
style A fill:#e1f5fe
style J fill:#f3e5f5
style HH fill:#e8f5e8
style AA fill:#fff3e0
设计理念: 大数据同步是为长时间未上线用户设计的特殊场景优化。设计理念是"高效处理,极端优化"。当用户有大量会话需要同步时,传统的串行同步方式效率太低,因此采用重装同步的并发批量处理策略,以资源换取吞吐量,确保即使在极端情况下也能高效完成数据同步。
四、重装同步 (AppDataSyncBegin)
4.1 触发场景与特点
触发条件:
- 应用重新安装后首次启动
- 本地数据库为空或版本信息缺失
- 数据完全丢失需要重新同步
- SDK检测到
reinstalled = true状态
核心特点:
- 完整的数据恢复流程
- 跳过通知消息同步,只恢复重要数据
- 使用最高效的并发同步策略
- 专门的重装进度提示系统
- 分阶段执行,确保核心数据优先
- 静默同步辅助数据,减少用户感知
4.2 执行流程
重装同步的前期流程与普通同步和大数据同步完全一致,但在事件处理和数据优先级上有特殊设计:
sequenceDiagram
participant LongConn as 长连接
participant MsgSyncer as 消息同步器
participant ConvModule as 会话模块
participant GroupModule as 群组模块
participant RelationModule as 关系模块
participant UserModule as 用户模块
participant BatchProcessor as 批处理器
participant Server as 服务器
participant DB as 本地数据库
participant UI as 用户界面
LongConn->>MsgSyncer: CmdConnSuccesss (连接成功)
Note over MsgSyncer: doConnected() - 与普通同步相同入口
MsgSyncer->>Server: GetMaxSeqReq (获取序列号)
Server->>MsgSyncer: GetMaxSeqResp (返回序列号信息)
MsgSyncer->>MsgSyncer: getNeedSyncConversations<br/>(计算需要同步的会话)
Note over MsgSyncer: 重装模式:跳过通知消息同步
MsgSyncer->>MsgSyncer: SyncAndSortConversations<br/>(同步并排序会话)
Note over MsgSyncer: 与普通同步完全相同的逻辑
MsgSyncer->>MsgSyncer: 检测到 reinstalled = true
Note over MsgSyncer: 关键分支点:重装同步判断
MsgSyncer->>ConvModule: DispatchSyncFlag(AppDataSyncBegin)
ConvModule->>UI: OnSyncServerStart(true) (重装同步开始)
ConvModule->>UI: OnSyncServerProgress(1%) (初始进度)
Note over ConvModule: 第一阶段:异步等待执行关键业务数据同步
par 并发执行基础数据同步(asyncWait模式)
ConvModule->>GroupModule: IncrSyncJoinGroup<br/>(同步用户加入的群组信息)
and
ConvModule->>RelationModule: IncrSyncFriends<br/>(同步好友关系信息)
end
ConvModule->>UI: OnSyncServerProgress(40%) (进度40%)
Note over ConvModule: 第二阶段:同步等待执行核心会话数据
ConvModule->>ConvModule: IncrSyncConversations<br/>(同步会话列表)
ConvModule->>ConvModule: SyncAllConUnreadAndCreateNewCon<br/>(同步未读计数并创建新会话)
ConvModule->>UI: OnSyncServerProgress(100%) (进度100%)
Note over ConvModule: 第三阶段:异步不等待执行辅助数据同步
par 并发执行辅助数据同步(asyncNoWait模式)
ConvModule->>UserModule: SyncLoginUserInfoWithoutNotice<br/>(静默同步用户信息)
ConvModule->>RelationModule: SyncAllBlackListWithoutNotice<br/>(静默同步黑名单)
ConvModule->>RelationModule: SyncAllFriendApplicationWithoutNotice<br/>(静默同步好友申请)
ConvModule->>RelationModule: SyncAllSelfFriendApplicationWithoutNotice<br/>(静默同步自己的好友申请)
ConvModule->>GroupModule: SyncAllAdminGroupApplicationWithoutNotice<br/>(静默同步群组管理申请)
ConvModule->>GroupModule: SyncAllSelfGroupApplicationWithoutNotice<br/>(静默同步自己的群组申请)
end
Note over MsgSyncer: 分批处理消息同步(与普通同步相同)
MsgSyncer->>BatchProcessor: NewConversationBatchProcessor<br/>(创建批处理器)
BatchProcessor->>MsgSyncer: handleMessage (分批调用)
Note over MsgSyncer: 使用重装同步的并发策略
MsgSyncer->>MsgSyncer: syncAndTriggerReinstallMsgs<br/>(并发消息同步)
MsgSyncer->>ConvModule: DispatchSyncFlag(AppDataSyncEnd)
ConvModule->>UI: OnSyncServerProgress(100%) (最终进度)
ConvModule->>UI: OnSyncServerFinish(true) (重装同步完成)
Note over MsgSyncer: 重装完成后的清理工作
MsgSyncer->>DB: SetAppSDKVersion(Installed: true)
MsgSyncer->>MsgSyncer: reinstalled = false
4.3 源码流程分析
4.3.1 重装同步入口判断 (doConnected)
重装同步的前期流程与普通同步、大数据同步完全一致,直到重装状态判断:
func (m *MsgSyncer) doConnected(ctx context.Context) {
reinstalled := m.reinstalled // 保存重装状态,用于后续处理
var resp sdkws.GetMaxSeqResp
// 1-2. 获取序列号和计算需要同步的会话(与普通同步相同)
if err := m.longConnMgr.SendReqWaitResp(ctx, &sdkws.GetMaxSeqReq{UserID: m.loginUserID}, constant.GetNewestSeq, &resp); err != nil {
log.ZError(ctx, "get max seq error", err)
common.DispatchSyncFlag(ctx, constant.MsgSyncFailed, m.conversationEventQueue)
return
}
needSyncAllSeqMap := m.getNeedSyncConversations(ctx, resp.MaxSeqs)
convCount := len(needSyncAllSeqMap)
if convCount == 0 {
return
}
// 3. 大数据同步判断(与普通同步相同)
if len(needSyncAllSeqMap) >= maxConversations {
m.isLargeDataSync = true
}
// 4. 同步并排序会话(与普通同步相同)
maxSeqs, sortConversationList, err := m.SyncAndSortConversations(ctx, reinstalled)
if err != nil {
log.ZError(ctx, "SyncAndSortConversations err", err)
}
// 5. 【关键区别】重装状态判断,触发重装同步事件
if reinstalled {
common.DispatchSyncFlagWithMeta(ctx, constant.AppDataSyncBegin, maxSeqs, m.conversationEventQueue)
} else {
// 非重装模式的其他同步分支
if m.isLargeDataSync {
common.DispatchSyncFlagWithMeta(ctx, constant.LargeDataSyncBegin, maxSeqs, m.conversationEventQueue)
} else {
common.DispatchSyncFlagWithMeta(ctx, constant.MsgSyncBegin, maxSeqs, m.conversationEventQueue)
}
}
// 6. 使用批处理器分批同步(与普通同步相同)
sort_conversation.NewConversationBatchProcessor(sortConversationList, needSyncAllSeqMap, synMaxConversations).Run(ctx, m.handleMessage)
// 7. 【关键区别】重装同步的后续处理
if reinstalled {
defer m.markInstallDone(ctx) // 标记安装完成
} else {
if !m.isLargeDataSync {
common.DispatchSyncFlag(ctx, constant.MsgSyncEnd, m.conversationEventQueue)
}
}
}
4.3.2 重装模式的特殊处理 (getNeedSyncConversations)
重装同步在计算需要同步的会话时有特殊逻辑,跳过通知消息同步:
func (m *MsgSyncer) getNeedSyncConversations(ctx context.Context, maxSeqToSync map[string]int64) map[string][2]int64 {
needSyncSeqMap := make(map[string][2]int64)
// 【重装模式特殊处理】当应用重新安装时,不拉取通知消息,只同步普通消息
if m.reinstalled {
notificationsSeqMap := make(map[string]int64) // 通知消息序列号映射
messagesSeqMap := make(map[string]int64) // 普通消息序列号映射
// 分离通知消息和普通消息
for conversationID, seq := range maxSeqToSync {
if IsNotification(conversationID) {
if seq != 0 { // 序列号为0表示无需同步
notificationsSeqMap[conversationID] = seq
}
} else {
messagesSeqMap[conversationID] = seq
}
}
var notificationSeqs []*model_struct.NotificationSeqs
// 【关键优化】处理通知消息:直接更新到数据库,不进行消息拉取
for conversationID, seq := range notificationsSeqMap {
notificationSeqs = append(notificationSeqs, &model_struct.NotificationSeqs{
ConversationID: conversationID,
Seq: seq,
})
// 直接更新本地同步状态,跳过实际的消息同步
m.syncedMaxSeqs[conversationID] = seq
}
// 批量插入通知消息的序列号状态到数据库
if len(notificationSeqs) > 0 {
err := m.db.BatchInsertNotificationSeq(ctx, notificationSeqs)
if err != nil {
log.ZWarn(ctx, "BatchInsertNotificationSeq err", err)
}
}
// 【重点】处理普通消息:计算需要同步的序列号范围
for conversationID, maxSeq := range messagesSeqMap {
if syncedMaxSeq, ok := m.syncedMaxSeqs[conversationID]; ok {
if maxSeq > syncedMaxSeq {
needSyncSeqMap[conversationID] = [2]int64{syncedMaxSeq + 1, maxSeq}
}
} else {
// 重装时从序列号0开始同步到最新
needSyncSeqMap[conversationID] = [2]int64{0, maxSeq}
}
}
return needSyncSeqMap
} else {
// 正常运行模式的逻辑(与普通同步相同)
// ...
}
}
4.3.3 重装同步事件处理 (syncFlag)
在notification.go中的重装同步事件处理逻辑:
func (c *Conversation) syncFlag(c2v common.Cmd2Value) {
switch syncFlag {
case constant.AppDataSyncBegin:
log.ZDebug(ctx, "AppDataSyncBegin")
// 1. 初始化重装同步状态
c.seqs = seqs
c.startTime = time.Now()
c.ConversationListener().OnSyncServerStart(true) // true表示重装同步
c.ConversationListener().OnSyncServerProgress(1) // 设置初始进度为1%
// 2. 第一阶段:异步等待执行关键业务数据同步
// 这些数据对用户体验至关重要,必须优先同步
asyncWaitFunctions := []func(c context.Context) error{
c.group.IncrSyncJoinGroup, // 同步用户加入的群组信息
c.relation.IncrSyncFriends, // 同步好友关系信息
}
runSyncFunctions(ctx, asyncWaitFunctions, asyncWait)
c.addInitProgress(InitSyncProgress * 4 / 10) // 增加40%进度
c.ConversationListener().OnSyncServerProgress(c.progress) // 通知进度更新
// 3. 第二阶段:同步等待执行核心会话数据
// 这些是会话列表显示的核心数据,必须同步完成
syncWaitFunctions := []func(c context.Context) error{
c.IncrSyncConversations, // 同步会话列表
c.SyncAllConUnreadAndCreateNewCon, // 同步未读计数并创建新会话
}
runSyncFunctions(ctx, syncWaitFunctions, syncWait)
log.ZWarn(ctx, "core data sync over", nil, "cost time", time.Since(c.startTime).Seconds())
c.addInitProgress(InitSyncProgress * 6 / 10) // 增加60%进度,总进度达到100%
c.ConversationListener().OnSyncServerProgress(c.progress) // 通知进度更新
// 4. 第三阶段:异步不等待执行辅助数据同步
// 这些是辅助数据,静默同步,不影响用户使用
asyncNoWaitFunctions := []func(c context.Context) error{
c.user.SyncLoginUserInfoWithoutNotice, // 同步登录用户信息(静默)
c.relation.SyncAllBlackListWithoutNotice, // 同步黑名单(静默)
c.relation.SyncAllFriendApplicationWithoutNotice, // 同步好友申请(静默)
c.relation.SyncAllSelfFriendApplicationWithoutNotice, // 同步自己发出的好友申请(静默)
c.group.SyncAllAdminGroupApplicationWithoutNotice, // 同步群组管理申请(静默)
c.group.SyncAllSelfGroupApplicationWithoutNotice, // 同步自己的群组申请(静默)
}
runSyncFunctions(ctx, asyncNoWaitFunctions, asyncNoWait)
case constant.AppDataSyncEnd:
// 重装同步完成
c.ConversationListener().OnSyncServerFinish(true) // true表示重装同步
}
}
4.3.4 重装同步的分阶段执行策略
重装同步采用三阶段执行策略,确保核心数据优先,用户体验最佳:
// 第一阶段:异步等待模式 (asyncWait)
// 关键业务数据必须等待完成,影响后续功能
asyncWaitFunctions := []func(c context.Context) error{
c.group.IncrSyncJoinGroup, // 群组关系数据
c.relation.IncrSyncFriends, // 好友关系数据
}
runSyncFunctions(ctx, asyncWaitFunctions, asyncWait)
// 第二阶段:同步等待模式 (syncWait)
// 核心会话数据必须串行完成,确保数据一致性
syncWaitFunctions := []func(c context.Context) error{
c.IncrSyncConversations, // 会话列表
c.SyncAllConUnreadAndCreateNewCon, // 未读计数
}
runSyncFunctions(ctx, syncWaitFunctions, syncWait)
// 第三阶段:异步不等待模式 (asyncNoWait)
// 辅助数据静默同步,不阻塞用户使用
asyncNoWaitFunctions := []func(c context.Context) error{
c.user.SyncLoginUserInfoWithoutNotice, // 用户信息
c.relation.SyncAllBlackListWithoutNotice, // 黑名单
c.relation.SyncAllFriendApplicationWithoutNotice, // 好友申请
c.relation.SyncAllSelfFriendApplicationWithoutNotice, // 自己的好友申请
c.group.SyncAllAdminGroupApplicationWithoutNotice, // 群组管理申请
c.group.SyncAllSelfGroupApplicationWithoutNotice, // 自己的群组申请
}
runSyncFunctions(ctx, asyncNoWaitFunctions, asyncNoWait)
4.3.5 批处理器逻辑 (NewConversationBatchProcessor)
重装同步使用与普通同步完全相同的批处理器逻辑:
// 创建批处理器(与普通同步相同)
sort_conversation.NewConversationBatchProcessor(sortConversationList, needSyncAllSeqMap, synMaxConversations)
// 批处理器的Run方法逻辑完全一致
func (p *ConversationBatchProcessor) Run(ctx context.Context, handler BatchHandler) {
result := make(map[string][2]int64)
for {
// 获取下一批会话
batch := p.iter.NextTop(p.batchSize)
if len(batch) == 0 {
break
}
// 处理当前批次的会话
for _, conv := range batch {
// 添加普通会话的同步范围
if v, ok := p.needSyncSeqMap[conv.ConversationID]; ok {
result[conv.ConversationID] = v
}
// 添加对应通知会话的同步范围
notificationID := GetNotificationConversationIDByConversationID(conv.ConversationID)
if v, ok := p.needSyncSeqMap[notificationID]; ok {
result[notificationID] = v
}
// 批次达到大小限制时触发处理
if len(result) >= p.batchSize {
p.emitResult(ctx, handler, result)
result = make(map[string][2]int64)
}
}
}
// 处理剩余的会话
if len(result) > 0 {
p.emitResult(ctx, handler, result)
}
}
4.3.6 重装同步的消息处理 (handleMessage)
重装同步使用专门的并发消息同步策略:
func (m *MsgSyncer) handleMessage(ctx context.Context, batchID int, needSyncTopSeqMap map[string][2]int64, isFirst bool) {
ctx = mcontext.WithTriggerIDContext(ctx, stringutil.IntToString(batchID))
reinstalled := m.reinstalled
if reinstalled {
// 【重装模式】使用重装专用的高并发同步方法
_ = m.syncAndTriggerReinstallMsgs(ctx, needSyncTopSeqMap, isFirst, connectPullNums)
if isFirst {
// 第一批处理完成,触发重装同步结束事件
common.DispatchSyncFlag(ctx, constant.AppDataSyncEnd, m.conversationEventQueue)
}
}
// 注意:syncAndTriggerReinstallMsgs使用高并发策略,效率比普通同步更高
// 该方法的详细实现在唤醒同步章节已经分析过,这里使用相同的并发处理逻辑
}
4.3.7 重装完成后的清理工作 (markInstallDone)
重装同步完成后需要标记安装状态并重置标志:
func (m *MsgSyncer) markInstallDone(ctx context.Context) {
// 1. 设置应用SDK版本状态为已安装
if err := m.db.SetAppSDKVersion(ctx, &model_struct.LocalAppSDKVersion{Installed: true}); err != nil {
log.ZError(ctx, "SetAppSDKVersion failed", err)
}
// 2. 重置重装标志,表示重装流程已完成
m.reinstalled = false
}
4.4 重装同步详细流程图
flowchart TD
A["长连接成功 CmdConnSuccesss"] --> B["doConnected 开始"]
B --> C["获取服务端最新序列号 GetMaxSeqReq"]
C --> D["计算需要同步的会话 getNeedSyncConversations"]
D --> E["检测到 reinstalled = true"]
E --> F["分离通知消息和普通消息"]
F --> G["通知消息直接更新到数据库 跳过拉取"]
G --> H["普通消息计算同步范围"]
H --> I["同步并排序会话 SyncAndSortConversations"]
I --> J["触发重装同步开始事件 AppDataSyncBegin"]
J --> K["syncFlag 事件处理"]
K --> L["OnSyncServerStart true"]
L --> M["OnSyncServerProgress 1%"]
M --> N["第一阶段:异步等待基础数据同步"]
N --> O1["IncrSyncJoinGroup 并发等待"]
N --> O2["IncrSyncFriends 并发等待"]
O1 --> P["等待第一阶段完成"]
O2 --> P
P --> Q["OnSyncServerProgress 40%"]
Q --> R["第二阶段:同步等待核心数据"]
R --> S1["IncrSyncConversations 串行"]
R --> S2["SyncAllConUnreadAndCreateNewCon 串行"]
S1 --> T["等待第二阶段完成"]
S2 --> T
T --> U["OnSyncServerProgress 100%"]
U --> V["第三阶段:异步不等待辅助数据"]
V --> W1["SyncLoginUserInfoWithoutNotice"]
V --> W2["SyncAllBlackListWithoutNotice"]
V --> W3["SyncAllFriendApplicationWithoutNotice"]
V --> W4["SyncAllSelfFriendApplicationWithoutNotice"]
V --> W5["SyncAllAdminGroupApplicationWithoutNotice"]
V --> W6["SyncAllSelfGroupApplicationWithoutNotice"]
U --> X["创建批处理器 NewConversationBatchProcessor"]
X --> Y["分批处理会话 每批100个"]
Y --> Z["handleMessage 处理每批"]
Z --> AA["syncAndTriggerReinstallMsgs 高并发同步"]
AA --> BB["errgroup 限制并发数10"]
BB --> CC["分批拉取消息 SplitPullMsgNum=100"]
CC --> DD["pullMsgBySeqRange 并发网络请求"]
DD --> EE["checkMessagesAndGetLastMessage"]
EE --> FF["triggerReinstallConversation"]
EE --> GG["triggerNotification"]
FF --> HH["第一批完成 AppDataSyncEnd"]
GG --> HH
HH --> II["OnSyncServerFinish true"]
II --> JJ["markInstallDone 清理工作"]
JJ --> KK["SetAppSDKVersion Installed=true"]
KK --> LL["reinstalled = false"]
LL --> MM["重装同步完成"]
style A fill:#e1f5fe
style E fill:#fff3e0
style J fill:#f3e5f5
style AA fill:#fff3e0
style MM fill:#e8f5e8
4.5 与其他同步机制的关键区别
| 对比维度 | 唤醒同步 | 普通同步 | 大数据同步 | 重装同步 |
|---|---|---|---|---|
| 触发条件 | 前后台切换 | 网络重连 < 500会话 | 网络重连 ≥ 500会话 | 应用重装 |
| 通知消息 | ✅ 完整同步 | ✅ 完整同步 | ✅ 完整同步 | ❌ 跳过拉取 |
| 会话排序 | ❌ 跳过排序 | ✅ 完整排序 | ✅ 完整排序 | ✅ 完整排序 |
| 分阶段执行 | ❌ 简单同步 | ❌ 简单同步 | ✅ 分阶段同步 | ✅ 三阶段同步 |
| 进度提示 | ❌ 无UI提示 | ✅ 有进度提示 | ✅ 有进度提示 | ✅ 详细进度 |
| 同步策略 | 标准同步 | 标准同步 | 并发同步 | 并发同步 |
| 数据优先级 | 无优先级 | 无优先级 | 无优先级 | ✅ 分优先级 |
| 静默同步 | ❌ 无静默 | ❌ 无静默 | ❌ 无静默 | ✅ 辅助数据静默 |
4.6 优缺点分析
优点:
- 专门针对重装场景优化,数据恢复完整
- 跳过通知消息拉取,减少不必要的网络请求
- 分阶段执行确保核心数据优先
- 静默同步辅助数据,不影响用户体验
- 使用最高效的并发策略,恢复速度快
- 详细的进度提示,用户体验良好
- 完成后自动清理状态,避免重复执行
缺点:
- 执行时间长,用户需要等待
- 资源消耗大,可能影响设备性能
- 网络带宽消耗大
- 复杂度高,容错要求高
设计理念: 重装同步是四种同步机制中最复杂、最全面的数据恢复方案。设计理念是"完整恢复,体验优先"。通过跳过不重要的通知消息、分阶段执行、静默同步等策略,确保用户在重装后能够快速恢复到可用状态,同时最小化对用户体验的影响。它既要保证数据的完整性,又要兼顾恢复速度和用户感知。