包含关键字 typecho 的文章

在这里插入图片描述

摘要

这两年,跨屏协作在鸿蒙生态里出现得越来越频繁。
从最早的文件互传、多屏办公,到现在的教育课堂、车机联动,设备之间已经不再是“各干各的”。

在游戏领域,这个变化更明显:

  • 一块屏幕已经不够玩
  • 玩家希望多设备一起参与
  • 大屏负责画面,小屏负责操作

但很多开发者一提“跨屏游戏”,第一反应还是投屏、远程控制、镜像显示。
实际上,鸿蒙给的不是投屏方案,而是一整套分布式游戏协作能力

这篇文章就从游戏开发者的真实视角,讲清楚鸿蒙是如何把多设备变成“一个游戏系统”的。

引言

在传统系统里,如果你想做多设备协作游戏,通常意味着:

  • 自己写网络协议
  • 自己做设备发现
  • 自己处理数据一致性
  • 自己兜底各种异常情况

而在 HarmonyOS 里,这些事情被系统层直接兜住了:

  • 设备发现靠软总线
  • 状态同步靠分布式数据
  • UI 跨屏靠 Ability 调度

你要做的事情更偏向游戏逻辑设计本身,而不是重复造轮子。

接下来我们一步一步拆。

什么是鸿蒙里的跨屏游戏协作

跨屏不是投屏

先说一个很重要的点:

鸿蒙的跨屏游戏 ≠ 投屏

投屏的特点是:

  • 一端渲染
  • 另一端只是显示
  • 没有真正的协作逻辑

而鸿蒙的跨屏游戏,更像是:

  • 多设备同时运行
  • 各自承担不同功能
  • 通过系统级分布式能力协同

比如:

  • 手机只负责操作和技能
  • 平板或智慧屏负责主战场渲染
  • 游戏状态在多设备之间自动同步

一个最常见的跨屏游戏形态

手机(控制器)
  │
  │ 操作指令
  ▼
平板 / 智慧屏(主画面)
  │
  │ 游戏状态同步
  ▼
分布式数据中心

支撑跨屏游戏的三大核心能力

分布式软总线:设备能“找到彼此”

在游戏里,你最关心的不是网络协议,而是:

  • 能不能快速发现附近设备
  • 延迟够不够低
  • 掉线能不能感知

鸿蒙的分布式软总线解决的正是这些问题。

你不需要关心设备是:

  • Wi-Fi
  • 蓝牙
  • 局域网
  • 点对点

系统会自动选最优链路。

分布式数据管理:状态天然同步

跨屏游戏最怕的几个问题:

  • 状态不一致
  • 数据打架
  • 玩家看到的画面不同步

鸿蒙提供的分布式 KV 数据,天生适合游戏里的:

  • 玩家位置
  • 血量
  • 技能状态
  • 回合阶段

而且是系统级同步,不是你自己发包。

分布式 UI:屏幕不是绑死的

在鸿蒙里:

  • Ability 可以被拉起到其他设备
  • 游戏不用重新启动
  • 状态不需要你手动迁移

这对游戏来说很重要,因为你可以自由设计:

  • 哪个屏幕显示什么
  • 玩家如何参与
  • 随时切换设备角色

跨屏游戏的整体架构设计

一个可落地的结构示例

┌────────────┐
│ 手机端     │
│ 操作输入   │
│ 技能按钮   │
└─────┬──────┘
      │
      │ 分布式 KV 数据
      ▼
┌────────────┐
│ 平板端     │
│ 游戏主画面 │
│ 渲染逻辑   │
└────────────┘

手机不负责画面,平板不负责输入,各司其职。

实战核心:跨屏游戏状态同步 Demo

创建分布式 KV Store

import distributedData from '@ohos.data.distributedData';

const kvManager = distributedData.createKVManager({
  bundleName: 'com.example.crossgame',
  context: getContext()
});

const store = await kvManager.getKVStore('gameStore', {
  kvStoreType: distributedData.KVStoreType.SINGLE_VERSION,
  securityLevel: distributedData.SecurityLevel.S1
});

这个 store 在多设备之间是共享的。

手机端发送操作指令

// 模拟摇杆方向
async function sendMove(x: number, y: number) {
  await store.put('player_move', JSON.stringify({
    x,
    y,
    time: Date.now()
  }));
}

这里同步的是“操作”,而不是最终坐标。

平板端监听并更新角色

store.on('dataChange', (data) => {
  data.insertedEntries.forEach(entry => {
    if (entry.key === 'player_move') {
      const move = JSON.parse(entry.value as string);
      updatePlayer(move.x, move.y);
    }
  });
});

跨屏 UI:把主画面拉到大屏

从手机拉起平板的游戏界面

import featureAbility from '@ohos.ability.featureAbility';

featureAbility.startAbility({
  want: {
    bundleName: 'com.example.crossgame',
    abilityName: 'GameMainAbility',
    deviceId: 'remoteDeviceId'
  }
});

前提是:

  • 游戏状态已经存在分布式数据中
  • 新设备启动后直接读取即可

为什么这个能力对游戏很重要

你不需要:

  • 手动传进度
  • 重新初始化状态
  • 处理复杂的恢复逻辑

系统已经帮你兜底。

真实应用场景拆解

场景一:手机当手柄,大屏玩游戏

适合类型

  • 派对游戏
  • 本地多人
  • 家庭娱乐

逻辑示例

// 手机端:技能释放
await store.put('skill_cast', {
  skillId: 2,
  playerId: 'p1'
});
// 大屏端:技能响应
store.on('dataChange', (data) => {
  data.insertedEntries.forEach(e => {
    if (e.key === 'skill_cast') {
      castSkill(e.value);
    }
  });
});

场景二:非对称协作游戏

比如:

  • 一个人当指挥
  • 一个人实际操作
// 指挥端下达命令
await store.put('command', {
  type: 'attack',
  target: 'boss'
});

操作端只负责执行,不做决策。

场景三:教育 + 游戏化互动

老师平板控制节奏,学生手机参与。

// 教师端切换关卡
await store.put('game_stage', 'level_2');

学生端监听并同步切换界面。

常见问题 QA

Q1:分布式 KV 会不会太慢?

不会。
它适合的是:

  • 低频状态
  • 操作指令
  • 游戏阶段

高频帧同步需要更底层方案。

Q2:能不能用在竞技类游戏?

可以,但不建议直接用 KV 同步帧数据。
更适合:

  • 操作同步
  • 客户端预测
  • 状态校正

Q3:设备掉线怎么办?

KV 会自动触发变更事件,你可以监听:

  • 玩家退出
  • 状态回收
  • AI 接管

总结

从游戏开发角度看,鸿蒙的跨屏协作并不是噱头,而是一套真正能落地的系统能力

核心就一句话:

多设备在鸿蒙里,不是多个客户端,而是一个分布式游戏系统。

  • 软总线解决连接
  • 分布式数据解决同步
  • Ability 解决跨屏 UI
  • ArkTS 足够把 Demo 跑起来

在这里插入图片描述

摘要

随着智能终端越来越多,应用早就不再只运行在一台设备上。手机、平板、智慧屏、手表之间的协作,已经成了很常见的需求。在这种背景下,多设备任务该怎么分、分到哪台设备执行,就成了开发中绕不开的问题。

在鸿蒙系统中,这个问题并不是靠开发者“手动指定设备”来解决的,而是通过 设备能力感知 + 分布式调度机制 来完成。开发者更多关心的是:
这个任务适合干什么,而不是非要在哪台设备干。

本文会结合鸿蒙系统的分布式能力,介绍多设备任务分配的整体思路,并通过可运行的 Demo 代码,把这个过程完整跑一遍,最后再结合几个真实场景,聊聊它在实际项目中该怎么用。

引言

如果放在以前,一个应用基本只跑在一台手机上,最多考虑前后台切换。但现在不一样了:

  • 手机在你手里
  • 平板在桌子上
  • 智慧屏在客厅
  • 手表戴在手上

用户希望的是:
设备不同,但体验是连着的。

鸿蒙系统的分布式能力,正是为这种场景设计的。它不是简单的“跨设备通信”,而是把 任务、数据、能力 都变成可以在多设备之间流动的资源。

而多设备任务分配,本质上就是一句话:

把合适的任务,交给合适的设备去做。

鸿蒙多设备任务分配的整体思路

先发现设备,再谈分配

在鸿蒙系统中,只要设备在同一个分布式网络里,系统就能自动发现它们。
开发者不需要自己维护“设备表”,也不用关心设备什么时候上线、下线。

系统会帮你感知这些信息:

  • 设备类型(手机、平板、智慧屏)
  • 基本性能情况
  • 是否可信
  • 当前是否可用

你只需要在合适的时机拿到设备列表即可。

任务一定要能拆

多设备任务分配的前提是:
你的业务本身是能拆开的。

比如:

  • 页面展示是一块
  • 数据采集是一块
  • 计算处理是一块

如果一个任务从头到尾全写死在一个 Ability 里,那基本就没法分配了。

系统负责“怎么选设备”

在鸿蒙里,真正“选哪台设备执行”的逻辑,大部分是系统完成的:

  • 当前设备忙不忙
  • 网络情况好不好
  • 设备能力是否匹配
  • 是否更适合本地执行

开发者更多是通过 Ability 启动方式、Service 类型、数据同步方式 来间接影响分配结果。

核心实现方式一:跨设备启动 Ability

适合什么场景

这种方式最常见,适合:

  • 页面展示
  • 功能模块整体迁移
  • 用户可感知的交互任务

比如:
手机负责控制,平板负责显示大屏内容。

Demo:在平板上启动远程 Ability

import distributedDeviceManager from '@ohos.distributedDeviceManager';
import featureAbility from '@ohos.ability.featureAbility';

const BUNDLE_NAME = 'com.example.distributeddemo';

let deviceManager = distributedDeviceManager.createDeviceManager(BUNDLE_NAME);

function startRemotePage() {
  let devices = deviceManager.getTrustedDeviceListSync();

  devices.forEach(device => {
    if (device.deviceType === 2) { // 假设 2 表示平板
      let want = {
        bundleName: BUNDLE_NAME,
        abilityName: 'RemotePageAbility',
        deviceId: device.deviceId
      };
      featureAbility.startAbility(want);
    }
  });
}

代码说明

  • createDeviceManager:创建设备管理器
  • getTrustedDeviceListSync:获取可信设备列表
  • deviceType:用于简单区分设备类型
  • startAbility:指定 deviceId 后,Ability 会在远端设备启动

整个过程不需要你关心远端设备的进程、生命周期,系统会处理。

核心实现方式二:分布式 Service 执行任务

适合什么场景

这种方式更适合:

  • 计算密集型任务
  • 后台处理
  • 不需要 UI 的逻辑

比如:
手机采集数据,交给性能更强的设备做分析。

Demo:连接远端计算 Service

import featureAbility from '@ohos.ability.featureAbility';

function connectRemoteService(remoteDeviceId: string) {
  let want = {
    bundleName: 'com.example.distributeddemo',
    abilityName: 'ComputeServiceAbility',
    deviceId: remoteDeviceId
  };

  featureAbility.connectAbility(want, {
    onConnect(elementName, remote) {
      console.log('远程 Service 已连接');
      remote.sendMessage({
        command: 'startCompute',
        data: [1, 2, 3, 4]
      });
    },
    onDisconnect() {
      console.log('远程 Service 已断开');
    }
  });
}

代码说明

  • Service 在远端设备运行
  • 本地通过 IPC 的方式和远端通信
  • 计算逻辑完全在远端执行
  • 本地只负责发请求、收结果

这种方式非常适合“重计算、轻交互”的任务。

典型应用场景分析与示例

场景一:手机 + 平板的学习展示系统

场景说明

  • 手机负责控制、翻页
  • 平板负责展示课件内容

实现思路

  • 手机发现平板
  • 在平板启动展示 Ability
  • 通过分布式数据同步当前页码
import distributedData from '@ohos.data.distributedData';

async function syncPage(page: number) {
  let kvManager = distributedData.createKVManager();
  let store = await kvManager.getKVStore('pageStore');
  await store.put('current_page', page);
}

平板端监听数据变化,自动刷新页面。

场景二:多设备健康数据分析

场景说明

  • 手表采集心率
  • 手机做基础处理
  • 平板做数据可视化

实现思路

  • 手表同步原始数据
  • 手机过滤、预处理
  • 平板负责展示图表

核心在于:
任务不是“复制”,而是“分工”。

场景三:家庭智慧屏协同控制

场景说明

  • 手机是遥控器
  • 智慧屏负责 UI 展示
  • 计算逻辑放在智慧屏

实现思路

  • 手机只负责发指令
  • 智慧屏 Service 处理业务逻辑
  • 结果同步回手机

这种模式下,手机压力很小,体验反而更流畅。

常见问题 QA

Q1:我能不能指定“一定要某台设备执行”?

不推荐。
鸿蒙的设计思想是 声明需求,而不是指定设备
你可以通过能力需求去“引导”,但不建议写死。

Q2:设备突然下线怎么办?

系统会通知连接断开,
你需要做的只有一件事:
支持本地降级执行或重试。

Q3:分布式任务一定比本地慢吗?

不一定。
当任务本身就不适合本地执行时,
分布式反而更快、更省电。

总结

在鸿蒙系统中,多设备任务分配并不是一套复杂、难以理解的机制,它的核心思想其实很简单:

  • 把任务拆清楚
  • 描述好任务需求
  • 把调度交给系统

只要你在设计阶段考虑好“哪些任务适合分出去”,鸿蒙的分布式能力就能自然地帮你把事情做好。

一句话总结就是:

多设备任务分配,不是设备协作有多复杂,而是你有没有把任务设计清楚。

在这里插入图片描述

摘要

随着 HarmonyOS / OpenHarmony 在手机、平板、智慧屏、车机等多设备上的落地,应用的复杂度正在明显提升。页面不再只是简单展示,而是伴随着网络请求、数据计算、设备协同等大量逻辑。如果这些逻辑处理不当,很容易出现页面卡顿、点击无响应,甚至 Ability 被系统回收的问题。

线程阻塞,已经成为鸿蒙应用开发中最容易踩坑、也最影响体验的问题之一。本文将结合实际开发场景,用尽量口语化的方式,聊一聊在鸿蒙系统中如何系统性地避免线程阻塞,并给出可以直接运行的 Demo 代码。

引言

在早期的应用开发中,很多开发者习惯把逻辑直接写在点击事件里,或者在页面加载时同步读取数据。这种写法在简单页面中问题不大,但在 HarmonyOS 这种强调流畅体验和多设备协同的系统中,很容易暴露问题。

鸿蒙的 UI 是声明式的,系统对主线程(UI 线程)非常敏感。一旦主线程被占用,页面掉帧、动画卡住、操作延迟都会立刻出现。因此,理解哪些操作会阻塞线程,以及如何把这些操作合理地“挪走”,是每个鸿蒙开发者绕不开的一课。

下面我们从原理、工具、代码和真实场景几个角度,完整地拆解这个问题。

为什么线程阻塞在鸿蒙中这么致命

UI 线程到底在忙什么

在 HarmonyOS 中,UI 线程主要负责三件事:

  • ArkUI 页面渲染
  • 用户事件分发(点击、滑动等)
  • Ability 生命周期回调

简单理解就是:只要和“看得见、点得动”有关的事情,几乎都在 UI 线程上完成

一旦你在这里做了耗时操作,比如计算、IO、网络等待,页面就会立刻表现出“卡”的感觉。

常见的阻塞来源

在实际项目中,最容易导致阻塞的操作通常包括:

  • 同步网络请求
  • 文件读写
  • 数据库查询
  • 大量 for 循环计算
  • 人为 sleep 或死循环

这些操作本身不一定是错的,问题在于它们被放在了不该放的线程上

鸿蒙中避免线程阻塞的核心思路

一个总原则

可以把鸿蒙里的线程使用总结成一句话:

UI 线程只处理 UI,其他事情交给异步、线程池或 Worker。

围绕这个原则,系统也提供了多种工具,帮助开发者把任务“分流”。

异步编程是第一道防线

使用 async / await 处理耗时逻辑

在 ArkTS 中,官方推荐优先使用 Promise 和 async / await。它的好处是代码结构清晰,而且不会阻塞 UI 线程。

示例:页面加载网络数据

@Entry
@Component
struct AsyncDemo {
  @State message: string = '加载中...'

  build() {
    Column() {
      Text(this.message)
        .fontSize(20)
        .margin(20)

      Button('重新加载')
        .onClick(() => {
          this.loadData()
        })
    }
  }

  async loadData() {
    this.message = '请求中...'
    let response = await fetch('https://example.com/data')
    let result = await response.text()
    this.message = result
  }
}

代码说明

  • loadData 使用 async 声明,不会阻塞 UI
  • await 只是暂停当前函数执行,不会卡住页面
  • UI 更新完全由状态变化驱动

这是最基础、也是最常用的一种防阻塞方式。

TaskPool:处理计算和 IO 的利器

什么时候该用 TaskPool

当你遇到下面这些情况时,TaskPool 几乎是必选项:

  • 大量计算
  • 批量数据处理
  • 文件压缩、解析

可运行 Demo 示例

import taskpool from '@ohos.taskpool'

@Concurrent
function calculateSum(count: number): number {
  let sum = 0
  for (let i = 0; i < count; i++) {
    sum += i
  }
  return sum
}

@Entry
@Component
struct TaskPoolDemo {
  @State result: string = '等待计算'

  build() {
    Column() {
      Text(this.result)
        .fontSize(18)
        .margin(20)

      Button('开始计算')
        .onClick(() => {
          this.startTask()
        })
    }
  }

  startTask() {
    this.result = '计算中...'
    taskpool.execute(calculateSum, 1000000).then(res => {
      this.result = `结果是:${res}`
    })
  }
}

代码说明

  • @Concurrent 表示该函数可以并发执行
  • TaskPool 自动管理线程,不需要开发者手动创建线程
  • UI 线程只负责接收结果和更新状态

在真实项目中,使用 TaskPool 往往能立刻解决页面卡顿问题。

Worker:长期后台任务的选择

Worker 的使用场景

如果任务具有下面这些特点,就更适合使用 Worker:

  • 长时间运行
  • 需要持续处理数据
  • 与 UI 强隔离

比如日志分析、音视频处理、复杂解析等。

示例:使用 Worker 处理数据

主线程代码

let worker = new Worker('workers/data_worker.ts')

worker.postMessage({ action: 'start' })

worker.onmessage = (e) => {
  console.log('收到结果:', e.data)
}

Worker 线程代码

onmessage = function (e) {
  if (e.data.action === 'start') {
    let result = 0
    for (let i = 0; i < 500000; i++) {
      result += i
    }
    postMessage(result)
  }
}

代码说明

  • Worker 与 UI 线程完全独立
  • 即使计算时间较长,也不会影响页面交互
  • 通过消息机制进行通信

结合实际场景的应用示例

场景一:列表页面加载大量数据

问题:

  • 首次进入页面时一次性处理全部数据
  • 页面明显卡顿

解决思路:

  • 网络请求使用 async
  • 数据整理放入 TaskPool
async loadList() {
  let data = await fetchData()
  taskpool.execute(processData, data).then(list => {
    this.list = list
  })
}

场景二:文件导入与解析

问题:

  • 文件较大
  • 解析过程耗时

解决思路:

  • Worker 负责解析
  • UI 只显示进度
worker.postMessage({ filePath })

场景三:复杂计算驱动 UI 更新

问题:

  • 计算逻辑和 UI 耦合

解决思路:

  • 计算完全放到 TaskPool
  • UI 只订阅结果

QA 环节

Q:async / await 会不会阻塞线程?
A:不会,它只是让出执行权,不会卡住 UI 线程。

Q:TaskPool 和 Worker 怎么选?
A:短期、一次性的任务优先 TaskPool,长期或持续任务用 Worker。

Q:能不能在生命周期里做耗时操作?
A:不建议,生命周期函数应尽量轻量。

总结

线程阻塞并不是某一个 API 的问题,而是设计问题。在 HarmonyOS 中,系统已经为我们准备好了异步模型、TaskPool 和 Worker,只要遵循“UI 线程只做 UI”的原则,大多数卡顿问题都可以提前避免。

在真实项目中,提前做好任务拆分、线程规划,比后期排查卡顿要省心得多。这也是鸿蒙开发从“能跑”到“跑得顺”的一个重要分水岭。

本文引用了45岁老架构师尼恩的技术分享,有修订和重新排版。

1、引言

分布式IM聊天系统中,IM消息怎么做到不丢、不重、还按顺序到达?这个问题,涉及到IM系统的两个核心:1)消息不能丢(可靠性):比如用户点了发送,不能因为服务宕机或网络抖动,消息石沉大海。比如地铁隧道、电梯间,网络断了又连,消息不能卡住不动(要确保弱网也能用)。2)顺序不能乱(有序性):比如“在吗?” 回成 “吗在?”,群聊时间线错乱,体验直接崩盘。这二大痛点,是IM聊天系统架构的命门所在。下面是一张IM消息从发出到接收的关键路径:
图片

2、系列文章

为了更好以进行内容呈现,本文拆分两了上下两篇。本文是2篇文章中的第 1 篇:《如何保障分布式IM聊天系统的消息有序性(即消息不乱)》(☜ 本文)《如何保障分布式IM聊天系统的消息可靠性(即消息不丢)》(稍后发布..)本篇主要总结和分享分布式IM聊天系统架构中关于消息有序性的设计和实践。

3、传统技术方案的瓶颈,怎么破?

早期做消息有序,很多人第一反应是搞个“全局发号器”——所有消息排一队,挨个编号再发。理想很丰满,现实很骨感:高并发下一拥而上抢号,发号器直接被打满;更致命的是,它一旦宕机,全链路雪崩。这就像春运火车站只开一个售票窗——再快也撑不过三分钟。所以,我们必须换思路:不搞大一统,而是分片独立发号,让每个“窗口”自给自足,互不干扰。

4、痛点拆解:为什么消息会乱?

我们先还原一个真实场景: 想象一下你和朋友聊天:你说:“1 吃饭了吗?”他回:“2 刚吃完。”你又说:“3 吃啥呢?”结果对方手机上显示成:“3  吃啥呢?” → “1 吃饭了吗?” → “2 刚吃完。”这不是 bug,是分布式系统的常态。三条消息走不同服务节点、经不同网络路径,到达时间完全不可控,最终呈现顺序错乱。会乱 问题本质是什么?一个要“串行等”,一个想“并发冲”,天然冲突。这时候有人会说:那我加个全局排序服务不就行了?可以,但代价太大——一个中心节点最多撑几万 QPS,面对百万群聊、亿级用户,还没上线就已过载。所以,全局有序不是解,而是枷锁。我们要的不是“天下大同”,而是“各聊各的别乱就行”。

5、最终方案:分而治之 + 局部有序

真正的突破口在于:我们根本不需要全局有序,只需要“会话内有序”。你和张三的聊天记录不能乱,但你和李四的聊天跟王五的完全无关——何必放一起排序?这就引出了经典策略:分而治之 + 局部有序。具体怎么做?两步走稳: 第一步 - 业务分区: 哈希分片,锁定归属用 sessionId 做一致性哈希,确保同一个会话的所有消息始终路由到同一个处理节点。按“会话ID”做哈希,算出该消息该由哪个节点处理。同一会话 → 哈希值一样 → 路由到同一台机器 → 所有消息串行处理,天然避免跨节点乱序。这样一来,单个会话内的消息在服务端就是串行处理的,天然不会乱。 第二步 - 局部序号:独立发号,局部递增每个会话独立维护一个计数器,每来一条消息就+1,作为它的“官方序号”。每个会话,可以配一个独立计数器(比如 Redis 的 INCR),每来一条消息就+1,生成唯一 SEQ。客户端不管什么时候收到消息,只认这个序号,按序号从小到大排列展示。这个 SEQ 就是这条消息的“官方身份证号”,客户端只认这个,不看接收时间。这就像电影院检票——你可以早到晚到,但座位按票号定。哪怕后排观众先进场,也不会坐到前排去。PS:IM消息ID生成相关的文章可详细阅读以下资料:《IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)》《IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)》《IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略》《IM消息ID技术专题(四):深度解密美团的分布式ID生成算法》《IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现》《IM消息ID技术专题(六):深度解密滴滴的高性能ID生成器(Tinyid)》《IM消息ID技术专题(七):深度解密vivo的自研分布式ID服务(鲁班)》

6、实践落地(核心片段伪代码)

1)服务端分片路由逻辑:来看关键实现:如何把消息精准投递给“对的人”。String sessionId = msg.getSessionId();//这里是伪代码,实际代码以mq 的负载均衡机制为准int nodeIndex = Math.abs(sessionId.hashCode()) % clusterNodeCount; //这里写个伪代码,代表mq  主从复制ClusterNode targetNode = clusterNodes.get(nodeIndex);targetNode.sendMsg(msg);核心就一句:基于会话 ID 哈希取模,固定路由。从此,每个会话都有了自己的“专属服务通道”,不再受其他会话影响。2)服务端序号分配逻辑:接下来,给每条消息发“通行证”:long msgSeq = redis.incr("msg_seq_" + sessionId);msg.setSeq(msgSeq);msg.setUniqueKey(sessionId + "_" + msgSeq);这里用了 Redis 的 INCR,保证同一个会话下的 SEQ 绝对递增,且线程安全。同时用 sessionId_seq 作为唯一键,既能幂等去重,也能防止重试导致消息重复入库。实战提示:如果你的 Redis 是集群模式,记得确保同一个会话的 key 落在同一 slot,否则 INCR 可能跨节点失效。3)客户端排序逻辑:最后一步,客户端收尾:别急着渲染,先排好队。//这里是伪代码, 先排序List<Msg> sortedMsgs = msgList.stream()    .sorted(Comparator.comparingLong(Msg::getSeq))    .collect(Collectors.toList());//这里是伪代码, 再渲染renderMsgList(sortedMsgs);无论消息以什么顺序到达,统统按 seq 升序排列后再上屏。哪怕第100条先到,第1条后到,也能正确归位。这也是为什么我们强调“客户端必须信任服务端 SEQ”——它是唯一真相源。

7、方案总结:放弃全局有序,换高可用与高性能

总结一下,这套方案的核心思想就一句话:不要为“假需求”买单——我们不需要全局有序,只需要业务上有意义的有序。你看微信、钉钉、飞书,哪一个是把全平台消息排成一条队列的?没有。它们都选择了“会话级隔离 + 局部有序”的设计,这才是工业级系统的通用解法。背后的分布式哲学也很清晰:
图片

最终换来的是:1)高并发支持(水平扩展);2)高可用(无单点);3)强一致体验(用户无感知)。这正是中高级开发者必须掌握的权衡思维:不是技术做不到,而是要不要做。有时候,“不做全局有序”,反而是最正确的选择。

8、 IM消息有序性架构的核心流程总结

最后,一张图串起全流程:
图片
从发起到渲染,全程围绕“会话隔离”和“局部发号”展开。每一个环节都在为同一个目标服务:在分布式环境下,低成本实现用户可感知的“顺序正确”。

—— 下篇《如何保障分布式IM聊天系统的消息可靠性(即消息不丢)》稍后发布,敬请期待 ——

9、参考资料

[1] 什么是IM聊天系统的可靠性?
[2] 什么是IM聊天系统的消息时序一致性?
[3] 微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)
[4] 马蜂窝旅游网的IM系统架构演进之路
[5] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等
[6] 从新手到专家:如何设计一套亿级消息量的分布式IM系统
[7] 企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等
[8] 融云技术分享:全面揭秘亿级IM消息的可靠投递机制
[9] 阿里IM技术分享(四):闲鱼亿级IM消息系统的可靠投递优化实践
[10] 阿里IM技术分享(八):深度解密钉钉即时消息服务DTIM的技术设计
[11] 基于实践:一套百万消息量小规模IM系统技术要点总结
[12] 一套分布式IM即时通讯系统的技术选型和架构设计
[13] 转转平台IM系统架构设计与实践(一):整体架构设计
[14] 移动端弱网优化专题(一):通俗易懂,理解移动网络的“弱”和“慢”
[15] 移动端弱网优化专题(二):史上最全移动弱网络优化方法总结
[16] Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?
[17] 从客户端的角度来谈谈移动端IM的消息可靠性和送达机制
[18] IM消息送达保证机制实现(一):保证在线实时消息的可靠投递
[19] 移动端IM中大规模群消息的推送如何保证效率、实时性?
[20] 如何保证IM实时消息的“时序性”与“一致性”?
[21] 一个低成本确保IM消息时序的方法探讨

即时通讯技术学习:

(本文已同步发布于:http://www.52im.net/thread-4887-1-1.html

在当今的数字时代,只需输入一句描述,如“一只穿着宇航服的猫在月球上喝咖啡,电影感光影”,几秒钟后,屏幕上便会呈现出一张惊艳的图像。Midjourney、Stable Diffusion 等 AI 绘画工具的出现,仿佛让“神笔马良”的故事成为了现实。

但这背后究竟是魔法,还是科技?

在那个神秘的进度条背后,AI 究竟在进行怎样的操作?它的“大脑”里是否真的住着一位不知疲倦的画手,拿着画笔在白纸上从零开始创作?

本文将抛开复杂的专业术语,以通俗易懂的方式拆解这一神奇过程。真相或许比想象中更有趣——AI 绘画,本质上是一场大型的“脑补”游戏。


第一部分:画布的真相——它居然不是空白的!

谈及绘画,人们的第一反应通常是:在一张干净的白纸上构图、打草稿、上色。

然而,AI 的创作方式截然不同。它的起点并非空白,而是一片混沌。

如果能深入 AI 的后台一探究竟,会发现当它准备开始工作时,面前的“画布”呈现出如下形态:

这是一张密密麻麻、杂乱无章的噪点图,在技术上被称为“纯噪声”

在人类眼中,这或许只是毫无意义的混乱。但在 AI 眼中,这里隐藏着无限可能。AI 作画的本质,并非“无中生有”,而是“从混乱中建立秩序”。它不是在做加法(往白纸上添加内容),而是在做减法(去除不需要的噪点)。


第二部分:AI 的特殊技能——“脑补大师”是怎样炼成的?

面对这样一屏毫无头绪的雪花,AI 如何知道该从何处下手?这得益于它在投入使用前经历的魔鬼训练。

在尚未掌握绘画技能之前,AI 分析了数十亿张人类世界的图片。其学习方式颇为独特,堪称一位“破坏与重建狂魔”

训练过程中,研究人员会向 AI 展示一张清晰的照片(例如一只小狗),随后逐步向照片中添加“沙子”(噪点),使照片逐渐变得模糊,直至完全变为一张无法辨认的雪花屏。

AI 的任务便是学习如何“倒放”这一过程——即凭经验将这张雪花屏还原成最初的那只小狗。

经过亿万次此类练习,AI 练就了一双“火眼金睛”,成为了世界上顶尖的“去噪专家”。面对任何混乱的图像,它的第一反应便是:“这太乱了,需要将其清理干净。”


第三部分:关键时刻——面对一片雪花,AI 怎么下第一笔?

这是整个生成过程中最为神奇的环节。

当用户输入指令:“画一只猫”,AI 面对着手中那张杂乱无章的雪花屏,内心或许是崩溃的:“这里哪里有猫?这全是噪点。”

此时,奇迹发生了。这个过程类似于人们童年时常玩的游戏——“在云朵里找形状”

想象一下,躺在草地上注视着天上杂乱无章的云团发呆。此时,若有人提示:“嘿,你看那片云,像不像一只猫?”

一旦接受了这一设定,大脑便会开始强行“脑补”。越看越觉得:“左边那团突出的云确实有点像猫耳朵,中间那块暗影有点像猫身子……”

AI 画画的第一步,正是这种强制的“幻视”。

当用户输入“猫”作为提示词,便相当于给了 AI 一个强烈的暗示。它被迫在那堆毫无意义的噪点中寻找“猫”的蛛丝马迹。

它会审视那些随机排列的像素点,强行联想:“虽然目前很乱,但如果非要说的话,中间这几个黑点凑在一起,相较于角落里的白点,更有潜力发展成一个猫鼻子。”

于是,AI 迈出了极其微小的第一步:它并未直接画出猫鼻子,而只是将那些像素的颜色,朝着“猫”的方向轻轻推了一把。


第四部分:见证奇迹——从模糊到清晰的循环

这一步迈出后,画布看起来依然是一团糟。但 AI 绘画并非一步到位,它更像是一位手持橡皮擦和雕刻刀的雕塑家,一点一点将作品“磨”出来。

这个过程在软件中通常被称为“步数”(Steps)。

  • 第 1 步: 对着雪花屏强行脑补,画面依然混沌,但已显现出极其微弱的趋势。
  • 第 10 步: AI 认为“猫”的形象越来越确定,下手逐渐加重,画面中出现了一个模糊的影子,能隐约辨识出动物的轮廓。
  • 第 20 步: 轮廓日益清晰,AI 开始雕琢细节:“此处应有毛发,彼处应是眼睛的反光。”
  • 第 30 步: 大功告成!噪点被清理干净,光影、质感完美呈现,一只栩栩如生的猫诞生了。

这就是为什么 AI 生成图片需要几秒钟的时间,因为它在后台快速地进行了数十次“观察-脑补-修正”的循环。


第五部分:灵魂拷问——为什么每次生成的图片都不一样?

人们可能会发现,使用相同的提示词和模型设置,点击两次生成,AI 给出的图片却是完全不同的。既然是机器,为何结果不稳定?

这正是 AI 绘画的迷人之处,其原因主要有二:

1. 起跑线不同(蝴蝶效应)

还记得最初那张“雪花屏”吗?每次点击生成按钮,AI 面对的那张雪花屏都是电脑随机新生成的。

世界上没有两片相同的树叶,也没有两张相同的噪点图。

也许这一次,初始噪点的左上角偶然多出了几个黑点,AI 便觉得:“此处适合画一只黑猫”;下一次,中间的噪点偏黄一点,AI 便觉得:“这次画只橘猫更合理”。

初始状态的极其微小差别,经过数十步的放大,最终导致了结果的巨大不同。这就是 AI 世界的“蝴蝶效应”。

2. “猫”是一个范围,不是一个点

在 AI 的庞大数据库里,“猫”并非一张固定的标准证件照,而是一个巨大的概念库。

提示词只是将 AI 推向了“猫”的领地,但具体落在领地里的哪个位置——是波斯猫还是狸花猫,是躺姿还是坐姿——充满了随机性。除非使用非常精确的语言进行限制,否则 AI 很乐意在“猫”的领地里随机探索。


结语

综上所述,AI 绘画并没有自主意识,它其实并不懂什么是艺术,也不懂什么是猫。

它只是一个阅图无数、拥有超强计算能力的“去噪机器”,一个有着严重强迫症的“脑补大师”。

但正是这种纯粹的数学计算,加上一点点随机的运气,为人类带来了近乎无限的创造力。下次当再次按下生成按钮时,不妨想象一下 AI 在后台对着一堆雪花屏努力“脑补”的样子,这或许正是科技的可爱之处。

本文由mdnice多平台发布

前言:一场技术与激情的双向奔赴

当 2025 年秋季的第一片梧桐叶飘落在交大校园时,一场关于人工智能未来的探索正在悄然展开。这不仅是技术的传授,更是认知的革新——从被动使用AI工具到主动创造智能体,从理论认知到工程实践。上海交通大学“AI赋能智汇高校实训营”正是这样一座桥梁,连接着学术前沿与产业实践,也连接着青年学子与AI的未来。

实训营概况速览

  • 时间: 2025年秋季学期
  • 地点: 上海交通大学(闵行校区)
  • 参与规模: 超过300名交大学子
  • 核心目标: 从零掌握大模型本地部署与微调全流程
  • 特色亮点: 国内首个全面基于NPU生态的大模型实训课程

能力提升三维度评估

同学们的“高光时刻”数据

  1. 参与度爆表

    • 课程满意度评分:4.8/5.0
    • 课后代码提交率:92%
    • 平均每人完成3.2个微调实验
    • 累计GPU/NPU计算时长:超过5,000小时
  2. 成果展示墙

    • 37个创意微调项目诞生
    • 12个项目进入 AI 社区“优秀案例库”
    • 最受欢迎应用方向:科研助手、创意写作、代码生成

技术实践全记录:从环境搭建到模型部署

环境配置篇:跨越“第一道门槛”

挑战场景还原:

“老师,torch_npu导入报错了!”
“镜像选择哪一个是正确的?”
——这是开课时最频繁的问题

我们的解决方案:

# 标准化环境配置流程(最终优化版)
# 1. 镜像选择黄金法则
PyTorch (openeuler-python3.10-pytorch2.1.0-openmind0.9.0) 
# 理由:Python3.10兼容性最佳,torch2.1.0与NPU适配最稳定

# 2. 依赖安装“避坑指南”
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
pip install torch==2.5.1 torch_npu numpy==1.26.4 transformers==4.52.4
# 关键发现:transformers 4.52.4对中文多模态支持最优

# 3. 环境校验“三连击”
python -c "import torch; import torch_npu; import vllm_ascend"
# 绿色√出现时,教室里响起的掌声至今难忘

教学反思:

  • 提前准备的“常见错误对照表”将问题解决时间缩短70%
  • “三人小组互助制”让基础较弱的同学也能跟上进度
  • 最受欢迎的教学创新:环境配置“闯关游戏”式教程

模型部署实战:见证“Hello World”时刻

技术路线演进:

Week 1: 基础文本模型 (Qwen2.5-3B)
Week 2: 视觉语言模型 (Qwen2.5-VL-3B)
Week 3: 国产多模态 (InternVL3.5-1B)

代码实践精华:

# 从“复杂难懂”到“一键部署”的蜕变

# 初版(学生普遍反映配置复杂)
# vllm serve /path/to/model --port 8000 --max-model-len 16384 ...

# 优化版(封装为simple_deploy.py)
from deployment_kit import ModelDeployer
deployer = ModelDeployer(model_name="Qwen2.5-VL-3B")
deployer.launch(port=8000, api_type="openai")

# 效果:部署时间从平均30分钟缩短至5分钟

互动环节亮点:

  • “模型对话接龙”:每组微调一个特色模型,串联成创意故事
  • “Bug排查大赛”:最快解决部署问题的组获得算力奖励
  • 最惊艳的学生作品:《红楼梦》风格的多模态对话模型

微调实操:让模型拥有“交大基因”

LoRA微调实战案例:

# 交大校史知识注入配置(student_project_01)
model_name: Qwen2.5-7B
dataset: sjtu_history_qa.json  # 学生自建的校史问答对
lora_config:
  r: 16
  alpha: 32
  target_modules: ["q_proj", "v_proj"]
training_args:
  num_epochs: 3
  per_device_train_batch_size: 4
  learning_rate: 2e-4

训练成果展示:

微调前:
问:上海交通大学何时成立?
答:交通大学是一所历史悠久的高校...

微调后:
问:上海交通大学何时成立?
答:上海交通大学前身为1896年创立的南洋公学,1921年定名为交通大学...
问:钱学森图书馆在哪里?
答:位于上海交通大学闵行校区,是为纪念校友钱学森而建...

技术突破点:

  1. 显存优化:QLoRA+梯度检查点,7B模型在24G NPU上可训练
  2. 数据质量:学生创新的“三阶段数据清洗法”
  3. 评估体系:自动化的ROUGE-L+BERTScore双指标评估

社区生态共建:AI 平台深度合作

AI 特色功能实践

功能模块使用频次学生评价亮点
模型库一键下载287次“比HuggingFace快5倍”
在线Notebook156次“随时随地继续实验”
模型市场分享42次“看到自己的模型被别人使用很有成就感”

优秀学生项目孵化

项目1:SJTU-CodePal

  • 团队:计算机系3名学生
  • 技术:基于DeepSeek-Coder微调
  • 特色:理解交大课程代码规范(如CS1101实验要求)
  • 成果:被《程序设计基础》课程组采纳为辅助工具

项目2:医工交叉文献助手

  • 团队:医学院+电院跨学科团队
  • 技术:Qwen2.5-VL微调
  • 特色:解析医学影像+文献摘要
  • 成果:在生物医学工程实验室实际部署

总结

当钱学森图书馆的灯光照亮同学们调试代码的身影,当东下院的键盘声敲响AI时代的序曲,我们深切感受到:教育最美的模样,就是点燃学生眼中的光。那些为环境配置而紧锁的眉头,那些看到模型成功响应时绽放的笑容,那些跨学科碰撞出的思想火花——这些瞬间汇聚成了2025年秋天最温暖的记忆。

感谢每一位参与其中的交大学子,你们的热情与创造力是这趟旅程最宝贵的风景。感谢所有支持单位提供的资源保障。人工智能的未来属于青年,而你们,正站在创造未来的起点上。

路虽远,行则将至;事虽难,做则必成。

LLM幻觉问题至今没有根治方案。RAG能缓解一部分,但成本高、架构复杂,而且只适用于有外部知识源的场景。而对于模型"应该知道但经常搞错"的那类问题,比如历史事件的时间线、人物履历的细节,RAG帮不上什么忙。

Chain-of-Verification(CoVe)的思路是既然模型会在生成时犯错,那就让它生成完之后再检查一遍自己的输出,把能发现的错误纠正掉,然后再给用户看。

听起来像是废话?关键在于"怎么检查"。

直接让模型审视自己刚写的东西,它大概率会坚持原有立场,这是确认偏差在作祟。CoVe的核心贡献是发现了一个绕过这个陷阱的方法:验证时必须把原始输出藏起来,让模型在"失忆"状态下重新回答事实性问题,然后用这些独立验证的答案去校对初稿。

某种意义上,这是给LLM装上了"系统2"思维:快思考先出初稿,慢思考再做验证。

工作流程:起草、规划、验证、修复

CoVe不是什么新的模型架构,它是一种提示编排模式,它把生成过程拆成四步:先让模型写初稿(这时候幻觉随便来),然后让它针对自己的初稿生成一组事实核查问题,接着独立回答这些问题,最后用验证过的事实重写原文。

Factored:真正的关键

前面说的四步里,第一、二、四步都是常规提示工程,用思维链就能搞定,而第三步才是核心。

让LLM一边看着自己写的东西一边验证,会有一个问题,这个在学术上管这叫"sycophancy",也就是说模型会顺着自己的话往下说。草稿就在上下文窗口里摆着,概率分布会被带偏,模型倾向于认同自己刚编出来的东西。

所以最简单的解决办法是把上下文剥掉。

CoVe论文里发现,回答验证问题时必须把原始草稿藏起来。举个例子:如果你问"根据这个草稿,X是不是在1998年发生的?"模型八成会点头同意自己。但如果你只问"X是什么时候发生的?"它就得老老实实从训练权重里检索答案,没有偏差可言。

隔离验证问题就是逼模型去查自己的知识库,而不是复读自己刚说过的话。

代码实现

下面是CoVe流程的Python实现,封装成一个类。注意第三步里的CRITICAL注释,那就是Factored验证的精髓。

 classChainOfVerification:  
    def__init__(self, llm):  
        self.llm=llm  

    defrun(self, query):  
        # Step 1: Baseline Generation
        # Let the model hallucinate freely here.
        draft_prompt=f"Question: {query}\nAnswer:"  
        draft=self.llm.generate(draft_prompt)  
        print(f"--- DRAFT ---\n{draft}\n")  

        # Step 2: Plan Verifications
        # Ask the model to identify what needs checking.
        plan_prompt=f"""  
        Context: {query}  
        Draft: {draft}  
        Task: Create a list of 3-5 verification questions to check the facts   
        in the draft. Output ONLY the questions.  
        """  
        plan_text=self.llm.generate(plan_prompt)  
        questions=self.parse_questions(plan_text)
        print(f"--- QUESTIONS ---\n{questions}\n")  

        # Step 3: Factored Verification (The Key Step)
        verification_results= []  
        forqinquestions:  
            # CRITICAL: Do NOT include 'draft' in this prompt context.
            # We want the raw model weights to answer this, uninfluenced by the previous lie.
            verify_prompt=f"Question: {q}\nAnswer:"  
              
            # Low temperature is crucial here for factual retrieval
            answer=self.llm.generate(verify_prompt, temperature=0)  
            verification_results.append((q, answer))  

        # Step 4: Final Synthesis
        # Now we bring it all together.
        verification_context=self.format_pairs(verification_results)  
        synthesis_prompt=f"""  
        Original Query: {query}  
        Draft Response: {draft}  
          
        Verification Data:  
        {verification_context}  
          
        Task: Rewrite the Draft Response to be fully accurate.   
        Remove any details contradicted by the Verification Data.  
        """  
        final_response=self.llm.generate(synthesis_prompt)  
          
        returnfinal_response  

    defparse_questions(self, text):  
        return [line.strip() forlineintext.split('\n') if'?'inline]  

    defformat_pairs(self, pairs):  
         return"\n".join([f"Q: {q}\nA: {a}"forq, ainpairs])

CoVe和RAG该怎么选?

每次聊到CoVe,总有人问:为什么不直接用RAG?

两者解决的是不同问题。

RAG适用于模型根本不可能知道答案的场景,比如你公司Q3的销售数据。CoVe适用于模型理论上应该知道、但可能搞混或偷懒的场景,比如按时间顺序列出纽约市历任市长。

而且研究表明两者可以混用:先用CoVe验证RAG检索回来的文档是否真的相关,再决定要不要用。代价是成本翻倍,但在医疗、法律这种高风险场景下,还是可行的。

从Vibe Coding到系统2代理

关注2026年初Agentic爆发的人,大概都听过"Ralph Wiggum"技术这个梗。

名字来自《辛普森一家》里那个喊着"我在帮忙!"却啥也没干成的角色。这技术的核心就是把LLM塞进一个while循环,让它反复尝试直到单元测试通过。暴力验证,Token消耗会爆表但最后确实能撞出正确答案。虽然听起来很好笑,实际上还挺管用。

工具增强版CoVe

opencode、OpenDevin、Windsurf这些现代自主代理已经在用"工具增强"版本的CoVe了。

它们不再只是问自己"这代码对不对",而是直接动手:先写代码,然后在沙盒里跑npm test或linter,读stderr输出,根据真实报错来修。

这就把CoVe的验证环节从概率猜测变成了确定性判断。

2026年的新拓扑:分支验证

最前沿的做法已经不是简单的线性循环了。是分支。

分支拓扑下,代理不是失败了就重试一次。它会同时提出三个修复方案,在三个隔离容器里并行跑,哪个能让构建变绿就提交哪个。

验证的消耗

这是2026年工程实践必须面对问题

Vibe Coding走系统1路线:快、便宜、但有20%左右的幻觉率,做原型够用。系统2代理反过来:慢、Token成本翻10倍、但可靠性过硬,生产环境离不开。

也就是说是拿计算资源换安心,当业务从聊天机器人升级到自主工程师,这笔成本不是能不能接受的问题,而是必须付的保险费——除非你想承担"Ralph Wiggum式"的风险,比如AI自己把数据库删了。

总结

CoVe的代价很明确:延迟。

生成初稿、生成问题、并行验证、综合重写,整套流程跑下来,Token消耗和响应时间基本翻四倍。对于实时聊天场景,这个延迟可能难以接受。但换个角度看,异步报告生成、代码审查、自动邮件起草这类任务,多等几秒换来输出可信度的大幅提升,这笔账怎么算都划算。

更值得关注的是CoVe带来的转变:过去几年,行业把大量精力投入到"如何让模型生成得更好"上——更大的参数、更多的数据、更精细的对齐。CoVe指向了另一个方向:与其追求一次生成就完美,不如承认模型会犯错,然后在架构层面把纠错机制build进去。

这和软件工程的演进路径很像。早期写代码追求一次写对,后来发现测试驱动开发、持续集成、灰度发布这些"验证优先"的实践才是规模化的正确姿势。

CoVe不会是终点,我们未来大概率会看到更多CoVe与RAG、外部工具、多模型交叉验证的组合方案。

https://avoid.overfit.cn/post/1f3da2d8396d44c6bab8bfea80405cb6

作者:Digvijay Mahapatra

在这里插入图片描述

摘要

这两年,跨屏协作在鸿蒙生态里出现得越来越频繁。
从最早的文件互传、多屏办公,到现在的教育课堂、车机联动,设备之间已经不再是“各干各的”。

在游戏领域,这个变化更明显:

  • 一块屏幕已经不够玩
  • 玩家希望多设备一起参与
  • 大屏负责画面,小屏负责操作

但很多开发者一提“跨屏游戏”,第一反应还是投屏、远程控制、镜像显示。
实际上,鸿蒙给的不是投屏方案,而是一整套分布式游戏协作能力

这篇文章就从游戏开发者的真实视角,讲清楚鸿蒙是如何把多设备变成“一个游戏系统”的。

引言

在传统系统里,如果你想做多设备协作游戏,通常意味着:

  • 自己写网络协议
  • 自己做设备发现
  • 自己处理数据一致性
  • 自己兜底各种异常情况

而在 HarmonyOS 里,这些事情被系统层直接兜住了:

  • 设备发现靠软总线
  • 状态同步靠分布式数据
  • UI 跨屏靠 Ability 调度

你要做的事情更偏向游戏逻辑设计本身,而不是重复造轮子。

接下来我们一步一步拆。

什么是鸿蒙里的跨屏游戏协作

跨屏不是投屏

先说一个很重要的点:

鸿蒙的跨屏游戏 ≠ 投屏

投屏的特点是:

  • 一端渲染
  • 另一端只是显示
  • 没有真正的协作逻辑

而鸿蒙的跨屏游戏,更像是:

  • 多设备同时运行
  • 各自承担不同功能
  • 通过系统级分布式能力协同

比如:

  • 手机只负责操作和技能
  • 平板或智慧屏负责主战场渲染
  • 游戏状态在多设备之间自动同步

一个最常见的跨屏游戏形态

手机(控制器)
  │
  │ 操作指令
  ▼
平板 / 智慧屏(主画面)
  │
  │ 游戏状态同步
  ▼
分布式数据中心

支撑跨屏游戏的三大核心能力

分布式软总线:设备能“找到彼此”

在游戏里,你最关心的不是网络协议,而是:

  • 能不能快速发现附近设备
  • 延迟够不够低
  • 掉线能不能感知

鸿蒙的分布式软总线解决的正是这些问题。

你不需要关心设备是:

  • Wi-Fi
  • 蓝牙
  • 局域网
  • 点对点

系统会自动选最优链路。

分布式数据管理:状态天然同步

跨屏游戏最怕的几个问题:

  • 状态不一致
  • 数据打架
  • 玩家看到的画面不同步

鸿蒙提供的分布式 KV 数据,天生适合游戏里的:

  • 玩家位置
  • 血量
  • 技能状态
  • 回合阶段

而且是系统级同步,不是你自己发包。

分布式 UI:屏幕不是绑死的

在鸿蒙里:

  • Ability 可以被拉起到其他设备
  • 游戏不用重新启动
  • 状态不需要你手动迁移

这对游戏来说很重要,因为你可以自由设计:

  • 哪个屏幕显示什么
  • 玩家如何参与
  • 随时切换设备角色

跨屏游戏的整体架构设计

一个可落地的结构示例

┌────────────┐
│ 手机端     │
│ 操作输入   │
│ 技能按钮   │
└─────┬──────┘
      │
      │ 分布式 KV 数据
      ▼
┌────────────┐
│ 平板端     │
│ 游戏主画面 │
│ 渲染逻辑   │
└────────────┘

手机不负责画面,平板不负责输入,各司其职。

实战核心:跨屏游戏状态同步 Demo

创建分布式 KV Store

import distributedData from '@ohos.data.distributedData';

const kvManager = distributedData.createKVManager({
  bundleName: 'com.example.crossgame',
  context: getContext()
});

const store = await kvManager.getKVStore('gameStore', {
  kvStoreType: distributedData.KVStoreType.SINGLE_VERSION,
  securityLevel: distributedData.SecurityLevel.S1
});

这个 store 在多设备之间是共享的。

手机端发送操作指令

// 模拟摇杆方向
async function sendMove(x: number, y: number) {
  await store.put('player_move', JSON.stringify({
    x,
    y,
    time: Date.now()
  }));
}

这里同步的是“操作”,而不是最终坐标。

平板端监听并更新角色

store.on('dataChange', (data) => {
  data.insertedEntries.forEach(entry => {
    if (entry.key === 'player_move') {
      const move = JSON.parse(entry.value as string);
      updatePlayer(move.x, move.y);
    }
  });
});

跨屏 UI:把主画面拉到大屏

从手机拉起平板的游戏界面

import featureAbility from '@ohos.ability.featureAbility';

featureAbility.startAbility({
  want: {
    bundleName: 'com.example.crossgame',
    abilityName: 'GameMainAbility',
    deviceId: 'remoteDeviceId'
  }
});

前提是:

  • 游戏状态已经存在分布式数据中
  • 新设备启动后直接读取即可

为什么这个能力对游戏很重要

你不需要:

  • 手动传进度
  • 重新初始化状态
  • 处理复杂的恢复逻辑

系统已经帮你兜底。

真实应用场景拆解

场景一:手机当手柄,大屏玩游戏

适合类型

  • 派对游戏
  • 本地多人
  • 家庭娱乐

逻辑示例

// 手机端:技能释放
await store.put('skill_cast', {
  skillId: 2,
  playerId: 'p1'
});
// 大屏端:技能响应
store.on('dataChange', (data) => {
  data.insertedEntries.forEach(e => {
    if (e.key === 'skill_cast') {
      castSkill(e.value);
    }
  });
});

场景二:非对称协作游戏

比如:

  • 一个人当指挥
  • 一个人实际操作
// 指挥端下达命令
await store.put('command', {
  type: 'attack',
  target: 'boss'
});

操作端只负责执行,不做决策。

场景三:教育 + 游戏化互动

老师平板控制节奏,学生手机参与。

// 教师端切换关卡
await store.put('game_stage', 'level_2');

学生端监听并同步切换界面。

常见问题 QA

Q1:分布式 KV 会不会太慢?

不会。
它适合的是:

  • 低频状态
  • 操作指令
  • 游戏阶段

高频帧同步需要更底层方案。

Q2:能不能用在竞技类游戏?

可以,但不建议直接用 KV 同步帧数据。
更适合:

  • 操作同步
  • 客户端预测
  • 状态校正

Q3:设备掉线怎么办?

KV 会自动触发变更事件,你可以监听:

  • 玩家退出
  • 状态回收
  • AI 接管

总结

从游戏开发角度看,鸿蒙的跨屏协作并不是噱头,而是一套真正能落地的系统能力

核心就一句话:

多设备在鸿蒙里,不是多个客户端,而是一个分布式游戏系统。

  • 软总线解决连接
  • 分布式数据解决同步
  • Ability 解决跨屏 UI
  • ArkTS 足够把 Demo 跑起来

在这里插入图片描述

摘要

随着智能终端越来越多,应用早就不再只运行在一台设备上。手机、平板、智慧屏、手表之间的协作,已经成了很常见的需求。在这种背景下,多设备任务该怎么分、分到哪台设备执行,就成了开发中绕不开的问题。

在鸿蒙系统中,这个问题并不是靠开发者“手动指定设备”来解决的,而是通过 设备能力感知 + 分布式调度机制 来完成。开发者更多关心的是:
这个任务适合干什么,而不是非要在哪台设备干。

本文会结合鸿蒙系统的分布式能力,介绍多设备任务分配的整体思路,并通过可运行的 Demo 代码,把这个过程完整跑一遍,最后再结合几个真实场景,聊聊它在实际项目中该怎么用。

引言

如果放在以前,一个应用基本只跑在一台手机上,最多考虑前后台切换。但现在不一样了:

  • 手机在你手里
  • 平板在桌子上
  • 智慧屏在客厅
  • 手表戴在手上

用户希望的是:
设备不同,但体验是连着的。

鸿蒙系统的分布式能力,正是为这种场景设计的。它不是简单的“跨设备通信”,而是把 任务、数据、能力 都变成可以在多设备之间流动的资源。

而多设备任务分配,本质上就是一句话:

把合适的任务,交给合适的设备去做。

鸿蒙多设备任务分配的整体思路

先发现设备,再谈分配

在鸿蒙系统中,只要设备在同一个分布式网络里,系统就能自动发现它们。
开发者不需要自己维护“设备表”,也不用关心设备什么时候上线、下线。

系统会帮你感知这些信息:

  • 设备类型(手机、平板、智慧屏)
  • 基本性能情况
  • 是否可信
  • 当前是否可用

你只需要在合适的时机拿到设备列表即可。

任务一定要能拆

多设备任务分配的前提是:
你的业务本身是能拆开的。

比如:

  • 页面展示是一块
  • 数据采集是一块
  • 计算处理是一块

如果一个任务从头到尾全写死在一个 Ability 里,那基本就没法分配了。

系统负责“怎么选设备”

在鸿蒙里,真正“选哪台设备执行”的逻辑,大部分是系统完成的:

  • 当前设备忙不忙
  • 网络情况好不好
  • 设备能力是否匹配
  • 是否更适合本地执行

开发者更多是通过 Ability 启动方式、Service 类型、数据同步方式 来间接影响分配结果。

核心实现方式一:跨设备启动 Ability

适合什么场景

这种方式最常见,适合:

  • 页面展示
  • 功能模块整体迁移
  • 用户可感知的交互任务

比如:
手机负责控制,平板负责显示大屏内容。

Demo:在平板上启动远程 Ability

import distributedDeviceManager from '@ohos.distributedDeviceManager';
import featureAbility from '@ohos.ability.featureAbility';

const BUNDLE_NAME = 'com.example.distributeddemo';

let deviceManager = distributedDeviceManager.createDeviceManager(BUNDLE_NAME);

function startRemotePage() {
  let devices = deviceManager.getTrustedDeviceListSync();

  devices.forEach(device => {
    if (device.deviceType === 2) { // 假设 2 表示平板
      let want = {
        bundleName: BUNDLE_NAME,
        abilityName: 'RemotePageAbility',
        deviceId: device.deviceId
      };
      featureAbility.startAbility(want);
    }
  });
}

代码说明

  • createDeviceManager:创建设备管理器
  • getTrustedDeviceListSync:获取可信设备列表
  • deviceType:用于简单区分设备类型
  • startAbility:指定 deviceId 后,Ability 会在远端设备启动

整个过程不需要你关心远端设备的进程、生命周期,系统会处理。

核心实现方式二:分布式 Service 执行任务

适合什么场景

这种方式更适合:

  • 计算密集型任务
  • 后台处理
  • 不需要 UI 的逻辑

比如:
手机采集数据,交给性能更强的设备做分析。

Demo:连接远端计算 Service

import featureAbility from '@ohos.ability.featureAbility';

function connectRemoteService(remoteDeviceId: string) {
  let want = {
    bundleName: 'com.example.distributeddemo',
    abilityName: 'ComputeServiceAbility',
    deviceId: remoteDeviceId
  };

  featureAbility.connectAbility(want, {
    onConnect(elementName, remote) {
      console.log('远程 Service 已连接');
      remote.sendMessage({
        command: 'startCompute',
        data: [1, 2, 3, 4]
      });
    },
    onDisconnect() {
      console.log('远程 Service 已断开');
    }
  });
}

代码说明

  • Service 在远端设备运行
  • 本地通过 IPC 的方式和远端通信
  • 计算逻辑完全在远端执行
  • 本地只负责发请求、收结果

这种方式非常适合“重计算、轻交互”的任务。

典型应用场景分析与示例

场景一:手机 + 平板的学习展示系统

场景说明

  • 手机负责控制、翻页
  • 平板负责展示课件内容

实现思路

  • 手机发现平板
  • 在平板启动展示 Ability
  • 通过分布式数据同步当前页码
import distributedData from '@ohos.data.distributedData';

async function syncPage(page: number) {
  let kvManager = distributedData.createKVManager();
  let store = await kvManager.getKVStore('pageStore');
  await store.put('current_page', page);
}

平板端监听数据变化,自动刷新页面。

场景二:多设备健康数据分析

场景说明

  • 手表采集心率
  • 手机做基础处理
  • 平板做数据可视化

实现思路

  • 手表同步原始数据
  • 手机过滤、预处理
  • 平板负责展示图表

核心在于:
任务不是“复制”,而是“分工”。

场景三:家庭智慧屏协同控制

场景说明

  • 手机是遥控器
  • 智慧屏负责 UI 展示
  • 计算逻辑放在智慧屏

实现思路

  • 手机只负责发指令
  • 智慧屏 Service 处理业务逻辑
  • 结果同步回手机

这种模式下,手机压力很小,体验反而更流畅。

常见问题 QA

Q1:我能不能指定“一定要某台设备执行”?

不推荐。
鸿蒙的设计思想是 声明需求,而不是指定设备
你可以通过能力需求去“引导”,但不建议写死。

Q2:设备突然下线怎么办?

系统会通知连接断开,
你需要做的只有一件事:
支持本地降级执行或重试。

Q3:分布式任务一定比本地慢吗?

不一定。
当任务本身就不适合本地执行时,
分布式反而更快、更省电。

总结

在鸿蒙系统中,多设备任务分配并不是一套复杂、难以理解的机制,它的核心思想其实很简单:

  • 把任务拆清楚
  • 描述好任务需求
  • 把调度交给系统

只要你在设计阶段考虑好“哪些任务适合分出去”,鸿蒙的分布式能力就能自然地帮你把事情做好。

一句话总结就是:

多设备任务分配,不是设备协作有多复杂,而是你有没有把任务设计清楚。

在这里插入图片描述

摘要

随着 HarmonyOS / OpenHarmony 在手机、平板、智慧屏、车机等多设备上的落地,应用的复杂度正在明显提升。页面不再只是简单展示,而是伴随着网络请求、数据计算、设备协同等大量逻辑。如果这些逻辑处理不当,很容易出现页面卡顿、点击无响应,甚至 Ability 被系统回收的问题。

线程阻塞,已经成为鸿蒙应用开发中最容易踩坑、也最影响体验的问题之一。本文将结合实际开发场景,用尽量口语化的方式,聊一聊在鸿蒙系统中如何系统性地避免线程阻塞,并给出可以直接运行的 Demo 代码。

引言

在早期的应用开发中,很多开发者习惯把逻辑直接写在点击事件里,或者在页面加载时同步读取数据。这种写法在简单页面中问题不大,但在 HarmonyOS 这种强调流畅体验和多设备协同的系统中,很容易暴露问题。

鸿蒙的 UI 是声明式的,系统对主线程(UI 线程)非常敏感。一旦主线程被占用,页面掉帧、动画卡住、操作延迟都会立刻出现。因此,理解哪些操作会阻塞线程,以及如何把这些操作合理地“挪走”,是每个鸿蒙开发者绕不开的一课。

下面我们从原理、工具、代码和真实场景几个角度,完整地拆解这个问题。

为什么线程阻塞在鸿蒙中这么致命

UI 线程到底在忙什么

在 HarmonyOS 中,UI 线程主要负责三件事:

  • ArkUI 页面渲染
  • 用户事件分发(点击、滑动等)
  • Ability 生命周期回调

简单理解就是:只要和“看得见、点得动”有关的事情,几乎都在 UI 线程上完成

一旦你在这里做了耗时操作,比如计算、IO、网络等待,页面就会立刻表现出“卡”的感觉。

常见的阻塞来源

在实际项目中,最容易导致阻塞的操作通常包括:

  • 同步网络请求
  • 文件读写
  • 数据库查询
  • 大量 for 循环计算
  • 人为 sleep 或死循环

这些操作本身不一定是错的,问题在于它们被放在了不该放的线程上

鸿蒙中避免线程阻塞的核心思路

一个总原则

可以把鸿蒙里的线程使用总结成一句话:

UI 线程只处理 UI,其他事情交给异步、线程池或 Worker。

围绕这个原则,系统也提供了多种工具,帮助开发者把任务“分流”。

异步编程是第一道防线

使用 async / await 处理耗时逻辑

在 ArkTS 中,官方推荐优先使用 Promise 和 async / await。它的好处是代码结构清晰,而且不会阻塞 UI 线程。

示例:页面加载网络数据

@Entry
@Component
struct AsyncDemo {
  @State message: string = '加载中...'

  build() {
    Column() {
      Text(this.message)
        .fontSize(20)
        .margin(20)

      Button('重新加载')
        .onClick(() => {
          this.loadData()
        })
    }
  }

  async loadData() {
    this.message = '请求中...'
    let response = await fetch('https://example.com/data')
    let result = await response.text()
    this.message = result
  }
}

代码说明

  • loadData 使用 async 声明,不会阻塞 UI
  • await 只是暂停当前函数执行,不会卡住页面
  • UI 更新完全由状态变化驱动

这是最基础、也是最常用的一种防阻塞方式。

TaskPool:处理计算和 IO 的利器

什么时候该用 TaskPool

当你遇到下面这些情况时,TaskPool 几乎是必选项:

  • 大量计算
  • 批量数据处理
  • 文件压缩、解析

可运行 Demo 示例

import taskpool from '@ohos.taskpool'

@Concurrent
function calculateSum(count: number): number {
  let sum = 0
  for (let i = 0; i < count; i++) {
    sum += i
  }
  return sum
}

@Entry
@Component
struct TaskPoolDemo {
  @State result: string = '等待计算'

  build() {
    Column() {
      Text(this.result)
        .fontSize(18)
        .margin(20)

      Button('开始计算')
        .onClick(() => {
          this.startTask()
        })
    }
  }

  startTask() {
    this.result = '计算中...'
    taskpool.execute(calculateSum, 1000000).then(res => {
      this.result = `结果是:${res}`
    })
  }
}

代码说明

  • @Concurrent 表示该函数可以并发执行
  • TaskPool 自动管理线程,不需要开发者手动创建线程
  • UI 线程只负责接收结果和更新状态

在真实项目中,使用 TaskPool 往往能立刻解决页面卡顿问题。

Worker:长期后台任务的选择

Worker 的使用场景

如果任务具有下面这些特点,就更适合使用 Worker:

  • 长时间运行
  • 需要持续处理数据
  • 与 UI 强隔离

比如日志分析、音视频处理、复杂解析等。

示例:使用 Worker 处理数据

主线程代码

let worker = new Worker('workers/data_worker.ts')

worker.postMessage({ action: 'start' })

worker.onmessage = (e) => {
  console.log('收到结果:', e.data)
}

Worker 线程代码

onmessage = function (e) {
  if (e.data.action === 'start') {
    let result = 0
    for (let i = 0; i < 500000; i++) {
      result += i
    }
    postMessage(result)
  }
}

代码说明

  • Worker 与 UI 线程完全独立
  • 即使计算时间较长,也不会影响页面交互
  • 通过消息机制进行通信

结合实际场景的应用示例

场景一:列表页面加载大量数据

问题:

  • 首次进入页面时一次性处理全部数据
  • 页面明显卡顿

解决思路:

  • 网络请求使用 async
  • 数据整理放入 TaskPool
async loadList() {
  let data = await fetchData()
  taskpool.execute(processData, data).then(list => {
    this.list = list
  })
}

场景二:文件导入与解析

问题:

  • 文件较大
  • 解析过程耗时

解决思路:

  • Worker 负责解析
  • UI 只显示进度
worker.postMessage({ filePath })

场景三:复杂计算驱动 UI 更新

问题:

  • 计算逻辑和 UI 耦合

解决思路:

  • 计算完全放到 TaskPool
  • UI 只订阅结果

QA 环节

Q:async / await 会不会阻塞线程?
A:不会,它只是让出执行权,不会卡住 UI 线程。

Q:TaskPool 和 Worker 怎么选?
A:短期、一次性的任务优先 TaskPool,长期或持续任务用 Worker。

Q:能不能在生命周期里做耗时操作?
A:不建议,生命周期函数应尽量轻量。

总结

线程阻塞并不是某一个 API 的问题,而是设计问题。在 HarmonyOS 中,系统已经为我们准备好了异步模型、TaskPool 和 Worker,只要遵循“UI 线程只做 UI”的原则,大多数卡顿问题都可以提前避免。

在真实项目中,提前做好任务拆分、线程规划,比后期排查卡顿要省心得多。这也是鸿蒙开发从“能跑”到“跑得顺”的一个重要分水岭。

本文引用了45岁老架构师尼恩的技术分享,有修订和重新排版。

1、引言

分布式IM聊天系统中,IM消息怎么做到不丢、不重、还按顺序到达?这个问题,涉及到IM系统的两个核心:1)消息不能丢(可靠性):比如用户点了发送,不能因为服务宕机或网络抖动,消息石沉大海。比如地铁隧道、电梯间,网络断了又连,消息不能卡住不动(要确保弱网也能用)。2)顺序不能乱(有序性):比如“在吗?” 回成 “吗在?”,群聊时间线错乱,体验直接崩盘。这二大痛点,是IM聊天系统架构的命门所在。下面是一张IM消息从发出到接收的关键路径:
图片

2、系列文章

为了更好以进行内容呈现,本文拆分两了上下两篇。本文是2篇文章中的第 1 篇:《如何保障分布式IM聊天系统的消息有序性(即消息不乱)》(☜ 本文)《如何保障分布式IM聊天系统的消息可靠性(即消息不丢)》(稍后发布..)本篇主要总结和分享分布式IM聊天系统架构中关于消息有序性的设计和实践。

3、传统技术方案的瓶颈,怎么破?

早期做消息有序,很多人第一反应是搞个“全局发号器”——所有消息排一队,挨个编号再发。理想很丰满,现实很骨感:高并发下一拥而上抢号,发号器直接被打满;更致命的是,它一旦宕机,全链路雪崩。这就像春运火车站只开一个售票窗——再快也撑不过三分钟。所以,我们必须换思路:不搞大一统,而是分片独立发号,让每个“窗口”自给自足,互不干扰。

4、痛点拆解:为什么消息会乱?

我们先还原一个真实场景: 想象一下你和朋友聊天:你说:“1 吃饭了吗?”他回:“2 刚吃完。”你又说:“3 吃啥呢?”结果对方手机上显示成:“3  吃啥呢?” → “1 吃饭了吗?” → “2 刚吃完。”这不是 bug,是分布式系统的常态。三条消息走不同服务节点、经不同网络路径,到达时间完全不可控,最终呈现顺序错乱。会乱 问题本质是什么?一个要“串行等”,一个想“并发冲”,天然冲突。这时候有人会说:那我加个全局排序服务不就行了?可以,但代价太大——一个中心节点最多撑几万 QPS,面对百万群聊、亿级用户,还没上线就已过载。所以,全局有序不是解,而是枷锁。我们要的不是“天下大同”,而是“各聊各的别乱就行”。

5、最终方案:分而治之 + 局部有序

真正的突破口在于:我们根本不需要全局有序,只需要“会话内有序”。你和张三的聊天记录不能乱,但你和李四的聊天跟王五的完全无关——何必放一起排序?这就引出了经典策略:分而治之 + 局部有序。具体怎么做?两步走稳: 第一步 - 业务分区: 哈希分片,锁定归属用 sessionId 做一致性哈希,确保同一个会话的所有消息始终路由到同一个处理节点。按“会话ID”做哈希,算出该消息该由哪个节点处理。同一会话 → 哈希值一样 → 路由到同一台机器 → 所有消息串行处理,天然避免跨节点乱序。这样一来,单个会话内的消息在服务端就是串行处理的,天然不会乱。 第二步 - 局部序号:独立发号,局部递增每个会话独立维护一个计数器,每来一条消息就+1,作为它的“官方序号”。每个会话,可以配一个独立计数器(比如 Redis 的 INCR),每来一条消息就+1,生成唯一 SEQ。客户端不管什么时候收到消息,只认这个序号,按序号从小到大排列展示。这个 SEQ 就是这条消息的“官方身份证号”,客户端只认这个,不看接收时间。这就像电影院检票——你可以早到晚到,但座位按票号定。哪怕后排观众先进场,也不会坐到前排去。PS:IM消息ID生成相关的文章可详细阅读以下资料:《IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)》《IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)》《IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略》《IM消息ID技术专题(四):深度解密美团的分布式ID生成算法》《IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现》《IM消息ID技术专题(六):深度解密滴滴的高性能ID生成器(Tinyid)》《IM消息ID技术专题(七):深度解密vivo的自研分布式ID服务(鲁班)》

6、实践落地(核心片段伪代码)

1)服务端分片路由逻辑:来看关键实现:如何把消息精准投递给“对的人”。String sessionId = msg.getSessionId();//这里是伪代码,实际代码以mq 的负载均衡机制为准int nodeIndex = Math.abs(sessionId.hashCode()) % clusterNodeCount; //这里写个伪代码,代表mq  主从复制ClusterNode targetNode = clusterNodes.get(nodeIndex);targetNode.sendMsg(msg);核心就一句:基于会话 ID 哈希取模,固定路由。从此,每个会话都有了自己的“专属服务通道”,不再受其他会话影响。2)服务端序号分配逻辑:接下来,给每条消息发“通行证”:long msgSeq = redis.incr("msg_seq_" + sessionId);msg.setSeq(msgSeq);msg.setUniqueKey(sessionId + "_" + msgSeq);这里用了 Redis 的 INCR,保证同一个会话下的 SEQ 绝对递增,且线程安全。同时用 sessionId_seq 作为唯一键,既能幂等去重,也能防止重试导致消息重复入库。实战提示:如果你的 Redis 是集群模式,记得确保同一个会话的 key 落在同一 slot,否则 INCR 可能跨节点失效。3)客户端排序逻辑:最后一步,客户端收尾:别急着渲染,先排好队。//这里是伪代码, 先排序List<Msg> sortedMsgs = msgList.stream()    .sorted(Comparator.comparingLong(Msg::getSeq))    .collect(Collectors.toList());//这里是伪代码, 再渲染renderMsgList(sortedMsgs);无论消息以什么顺序到达,统统按 seq 升序排列后再上屏。哪怕第100条先到,第1条后到,也能正确归位。这也是为什么我们强调“客户端必须信任服务端 SEQ”——它是唯一真相源。

7、方案总结:放弃全局有序,换高可用与高性能

总结一下,这套方案的核心思想就一句话:不要为“假需求”买单——我们不需要全局有序,只需要业务上有意义的有序。你看微信、钉钉、飞书,哪一个是把全平台消息排成一条队列的?没有。它们都选择了“会话级隔离 + 局部有序”的设计,这才是工业级系统的通用解法。背后的分布式哲学也很清晰:
图片

最终换来的是:1)高并发支持(水平扩展);2)高可用(无单点);3)强一致体验(用户无感知)。这正是中高级开发者必须掌握的权衡思维:不是技术做不到,而是要不要做。有时候,“不做全局有序”,反而是最正确的选择。

8、 IM消息有序性架构的核心流程总结

最后,一张图串起全流程:
图片
从发起到渲染,全程围绕“会话隔离”和“局部发号”展开。每一个环节都在为同一个目标服务:在分布式环境下,低成本实现用户可感知的“顺序正确”。

—— 下篇《如何保障分布式IM聊天系统的消息可靠性(即消息不丢)》稍后发布,敬请期待 ——

9、参考资料

[1] 什么是IM聊天系统的可靠性?
[2] 什么是IM聊天系统的消息时序一致性?
[3] 微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)
[4] 马蜂窝旅游网的IM系统架构演进之路
[5] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等
[6] 从新手到专家:如何设计一套亿级消息量的分布式IM系统
[7] 企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等
[8] 融云技术分享:全面揭秘亿级IM消息的可靠投递机制
[9] 阿里IM技术分享(四):闲鱼亿级IM消息系统的可靠投递优化实践
[10] 阿里IM技术分享(八):深度解密钉钉即时消息服务DTIM的技术设计
[11] 基于实践:一套百万消息量小规模IM系统技术要点总结
[12] 一套分布式IM即时通讯系统的技术选型和架构设计
[13] 转转平台IM系统架构设计与实践(一):整体架构设计
[14] 移动端弱网优化专题(一):通俗易懂,理解移动网络的“弱”和“慢”
[15] 移动端弱网优化专题(二):史上最全移动弱网络优化方法总结
[16] Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?
[17] 从客户端的角度来谈谈移动端IM的消息可靠性和送达机制
[18] IM消息送达保证机制实现(一):保证在线实时消息的可靠投递
[19] 移动端IM中大规模群消息的推送如何保证效率、实时性?
[20] 如何保证IM实时消息的“时序性”与“一致性”?
[21] 一个低成本确保IM消息时序的方法探讨

即时通讯技术学习:

(本文已同步发布于:http://www.52im.net/thread-4887-1-1.html

CVE-2024-3400 Palo Alto Networks PAN-OS命令注入漏洞

Real World CTF 6th Router4 writeup

Exploiting File Writes in Hardened Node.js Environments

CVE-2024-41592 vigor 栈溢出漏洞分析

CVE-2025-0282 Ivanti Connect Secure VPN 栈溢出漏洞分析

CVE-2025-36463 Sudo_chroot Elevation of Privilege 漏洞分析

CVE-2025-32023 Redis 漏洞分析


过去两年间,科技行业的领军者 —— 包括英特尔(Intel)、超威半导体(AMD)、高通(Qualcomm)等芯片巨头,以及软件架构巨头微软(Microsoft)—— 都在不遗余力地推广 “AI PC” 概念,试图推动一轮大规模硬件更新周期。然而,戴尔(Dell)近期坦诚承认:对绝大多数消费者而言,人工智能尚不足以成为购买新电脑的动力,甚至可能产生反效果。据《PC 玩家》(PCGamer)报道,戴尔已意识到,行业内铺天盖地的 “AI PC” 营销操作,与终端用户的实际需求之间存在巨大脱节。
戴尔方面表示,尽管公司仍致力于集成神经处理单元(Neural Processing Units, NPUs)并强化设备端推理能力,但市场实证表明,将 AI 定位为核心卖点 “未能有效刺激销量”。对普通消费者(而非科技爱好者)而言,AI 不仅无法激发购买热情,反而常引发他们对数据隐私安全实际用途模糊性的疑虑。戴尔的观察显示,理性消费者仍将决策锚定在传统实用指标上:价格、性能、电池续航与可靠性
与行业执着于 “每秒万亿次运算(TOPS)” 和专属 “Copilot 键” 不同,用户更看重设备的耐用性、运行流畅度和实际价值。这一现象凸显出当前消费级 AI 领域的关键短板:缺乏一款能打动用户的 “杀手级应用(Killer App)”,导致用户不愿为 “理论上的好处” 升级硬件。正因如此,在 2026 年国际消费电子展(CES 2026)上,戴尔发布的 XPS 系列笔记本调整了宣传方向,将重点转向便携性与耐用性。为提升该系列的高端定位,戴尔还采取了大胆的品牌策略:机身标识用 “XPS” 取代原有的 “Dell” logo,且宣传材料中刻意弱化了 AI 功能的提及。
尽管承认营销方向与用户需求存在偏差,戴尔仍强调,AI 仍是未来硬件更新周期的重要长期驱动力。行业共识认为,AI 必须从单纯的 “热门概念” 转变为无缝、可感知的实用工具,而实现这一转变的关键在于构建更完善的软件生态,而非简单堆砌硬件参数。戴尔的坦诚,实则揭开了行业内的 “公开秘密”:2024 至 2026 年间,尽管 NPU 的设备渗透率不断提升,但对普通办公和网页浏览等常规场景的体验提升微乎其微
当 Windows Copilot 等功能仍依赖云端连接,且设备端生成式 AI 能力表现平平之时,消费者自然会回归理性,对比屏幕画质、设备续航等实际参数。AI PC 并非虚幻概念,其发展路径类似早期 5G 手机 —— 必须先搭建好硬件基础,后续才能出现足以证明其价值的软件应用。除非人工智能能进化为类似 Wi-Fi 那样 “无形却不可或缺” 的基础设施,否则过度宣传反而可能引发消费者的抵触情绪,让他们觉得自己是在为冗余功能支付溢价。

苹果与谷歌已正式确认,下一代 Siri 将采用 Gemini 模型与 Google Cloud,双方达成一项为期多年的合作。
此前,苹果一直为 Siri 使用自研 AI 模型,但在性能上与 GPT、Gemini 甚至 Copilot 相比都显得力不从心。
如今,苹果与谷歌开启了多年合作。作为合作的一部分,未来版本的 Siri 将基于 Gemini 模型 运行。
此外,苹果的 Foundation Models(基础模型) 将以谷歌的 Gemini 为底层,并部署在谷歌云平台上。

谷歌在一份新闻稿中表示:

“这些模型将为未来的 Apple Intelligence 功能提供支持,包括将于今年推出的更具个性化的 Siri。”

苹果方面称:

“经过审慎评估,苹果认为谷歌的 AI 技术为 Apple Foundation Models 提供了最强大的基础,并对其将为苹果用户带来的创新体验感到兴奋。”

苹果强调,Apple Intelligence 将在苹果设备与 Private Cloud Compute(私有云计算) 上运行,公司长期坚持的隐私承诺 不会受到任何影响

Apple Intelligence 的发展历程一直充满波折

Siri 问世至今已超过十年。尽管它曾是最优秀的 “个人助手” 之一,但如今的大型语言模型(LLMs)在能力上已远超苹果的实现。
在 2024 年的 WWDC 上,苹果宣布正在开发 Apple Intelligence,其中也包括一个更具 AI 能力的 Siri,例如支持个人上下文理解与屏幕内容感知。
然而,这些功能一再延期。
部分功能最终上线,但用户抱怨 Apple Intelligence 的准确性不佳,在执行复杂指令时经常失败。
尽管苹果的 AI 体验本应以 “安全” 和 “隐私友好” 为卖点,但最终呈现的初始体验却远未达到革命性的高度。
如今,苹果与谷歌合作,借助 Gemini 升级 Siri 体验。但这是否能实现苹果曾经承诺的那种革命性突破?只有时间能给出答案。

新一轮网络攻击正借助ValleyRAT_S2 恶意软件悄然入侵各类组织机构,该病毒可实现长期潜伏,并窃取目标机构的敏感金融数据。
ValleyRAT_S2 是 ValleyRAT 恶意软件家族的第二阶段载荷,基于 C++ 语言开发。一旦侵入目标网络,它就会以完整远程访问木马(RAT)的形态运行,让攻击者获得对受感染系统的高度控制权,同时搭建起稳定的数据外传通道。
当前,该攻击活动的主要传播途径为伪造的中文办公软件、破解版程序,以及伪装成人工智能表格生成工具的植马安装包。
在诸多攻击案例中,恶意软件会通过DLL 侧载技术实现植入:攻击者诱导一个带有合法数字签名的应用程序,加载看似正常的恶意动态链接库文件,例如命名为steam_api64.dll的恶意模块。

网络安全团队 APOPHiS 通过追踪多起相关攻击事件,确认ValleyRAT_S2 是此类入侵活动的核心第二阶段后门程序

除此之外,该恶意软件还会通过鱼叉式钓鱼邮件附件,以及被劫持的软件更新渠道进行传播。

恶意文档或压缩包会将载荷文件释放至系统临时文件夹等路径,例如:

C:\Users\Admin\AppData\Local\Temp\AI自动化办公表格制作生成工具安装包\steam_api64.dll

攻击的第一阶段以躲避安全检测为核心目标,而 ValleyRAT_S2 则会接管后续操作,负责长期驻留控制、系统信息探查、凭证窃取及金融数据收集等关键任务。

ValleyRAT_S2 激活后,会对系统进程、文件系统及注册表项展开扫描,随后通过自定义 TCP 协议,连接预先写入代码的命令与控制(C&C)服务器,例如27.124.3.175:14852。该病毒具备文件上传下载、执行 Shell 命令、注入恶意载荷、记录键盘输入等多种功能,

这些特性使其能够精准窃取网银账户凭证、支付交易数据及内部财务文档。

持久化机制与监控守护功能

ValleyRAT_S2 的一大高危特性,是其多层级持久化设计与监控守护机制,这让它能够在系统重启或手动清理后依然存活。

恶意软件会先将相关文件释放到用户的临时文件夹(Temp)和应用数据文件夹(AppData)中,创建%TEMP%\target.pid这类进程标识文件,同时在%APPDATA%\Promotions\Temp.aps路径下生成配置文件。

它还会利用 COM 接口调用 Windows 任务计划程序,实现开机自启动;同时会在注册表启动项中写入备份路径,作为双重保障。

该病毒的一个关键特征是会生成一个名为monitor.bat的批处理脚本,以此构建监控守护循环。

这个脚本会从target.pid文件中读取恶意主程序的进程 ID,持续检查主程序是否处于运行状态,一旦发现主程序被终止,就会自动静默重启。

以下是该脚本的简化版本:
@echo off
set "PIDFile=%TEMP%\target.pid"
set /p pid=<"%PIDFile%"
del "%PIDFile%"
:check
tasklist /fi "PID eq %pid%" | findstr >nul
if errorlevel 1 (
  cscript //nologo "%TEMP%\watch.vbs"
  exit
)
timeout /t 15 >nul
goto check
这一守护循环,能让 ValleyRAT_S2 在主进程被安全工具或管理员终止后快速恢复。此外,该恶意软件还结合了结构化异常处理、沙箱环境检测,以及进程注入技术 —— 将自身注入Telegra.exeWhatsApp.exe等具有可信名称的进程中,以此实现隐蔽且稳固的持久化驻留。
对于防御方而言,这意味着单纯终止恶意进程无法实现彻底清除;想要根除该病毒,必须同时针对计划任务、批处理与 VBS 监控脚本、释放的恶意文件及后门进程等所有相关组件,开展一体化清除操作。

Moxa 的工业以太网交换机中被发现存在一个严重安全漏洞,可能威胁工业控制系统(OT)网络的完整性。该漏洞编号为 CVE-2023-38408,CVSS 评分高达 9.8(高危),意味着需要立即修复
问题出在设备所使用的 OpenSSH 组件 中,具体影响 OpenSSH ssh-agent 在 9.3p2 之前版本中的 PKCS#11 功能
该漏洞源于一个 “不可靠的搜索路径”。如果 ssh-agent 被转发到攻击者控制的系统,就可能导致远程代码执行(RCE)。像 /usr/lib 这样的目录中的代码并不一定安全,而该漏洞的出现是因为之前的问题 CVE-2016-10009 的修复并不彻底。
由于漏洞严重性极高,官方强烈建议用户立即采取修复措施以降低风险。

受影响的产品系列

Moxa 已确认两个主要产品系列受到影响:

1. EDS 系列

型号包括:EDS-G4000、EDS-4008、EDS-4009、EDS-4012、EDS-4014、EDS-G4008、EDS-G4012、EDS-G4014

受影响固件:4.1 及更早版本

2. RKS 系列

型号包括:RKS-G4000、RKS-G4028、RKS-G4028-L3

受影响固件:5.0 及更早版本

修复措施

Moxa 已发布安全补丁,但暂未提供公开下载。用户必须联系 Moxa 技术支持以获取补丁文件。
  • EDS 系列: 请求安全补丁 v4.1.58
  • RKS 系列: 请求安全补丁 v5.0.4

临时缓解方案

对于无法马上进行固件更新的组织,Moxa 建议采用纵深防御策略降低风险:
  • 限制访问: 使用防火墙或访问控制列表(ACL)仅允许可信 IP 通信;通过 VLAN 隔离工业网络。
  • 减少暴露面: 确保设备不直接暴露在互联网上,并关闭未使用的端口。
  • 加强认证: 启用多因素认证(MFA)和基于角色的访问控制(RBAC)。
  • 安全通信: 使用 VPN 或 SSH 等加密协议进行远程访问,并仅限授权人员使用。
  • 监控: 部署异常检测以发现未授权活动,并定期审查审计日志。