网鱼AI电商助手  ·  面向闲鱼卖家的 AI 铺货与发布自动化 工作日 09:00–18:00  ·  试用与部署 免费

多设备并行,先把设备池管住

设备从一台加到五台,最先出问题的不是速度,是归属:哪个账号在哪台设备上跑、哪个进程占了哪个端口、某台设备坏了任务落到谁身上、几台同时回写台账为什么对不上。这页讲的是设备池的判定门槛、端口锁的分配与回收、任务按服务线而不是按设备切的理由、故障隔离的三级状态,以及加设备到底能解决什么、不能解决什么。

设备池端口锁槽位归属故障隔离并发回写扩容判断
桌面上的平板与手机
Device pool · 一台设备一条服务线,槽位由同一个函数算出

01  /  设备池

设备不是插上就算,先过接入判定

多设备并行的第一个误区,是把“连上了”当成“能用了”。设备管理器里有名字、ADB 设备列表里能看到序列号,都不构成可用。判定一台设备能不能进池,只有三条硬条件,逐条过。

一、存活只认 shell 往返

设备列表是缓存,不是事实。USB 掉线、无线调试断开、adb server 里留下僵尸记录,都会让列表继续显示序列号。可靠的做法是发起一次真实的 shell 往返,拿到返回才算活着。这条判断要写进执行前的检查里,不能靠人眼看列表。

二、uiautomator2 能建会话并 dump 出界面

能连上不等于能读界面。uiautomator2 要能在该设备上建立会话、成功 dump 出当前界面结构,才说明界面读取这一层通了。dump 有重试机制,但重试两次仍失败,这台设备不该进池。dump 在 launcher 屏会失败,需要先试压缩模式再退回普通模式,这个细节放在第 06 节。

三、中文输入至少一条通道可用

三条中文输入通道分别基于 uiautomator2、adbkey 与 adb_input。其中 adb input text 打不出中文,这是实测结论,不是配置问题。所以一台设备至少要有一条第 06 节以外可用的中文输入路径,否则它能点、能读、就是写不了标题,进池没有意义。

三条都过,才进设备池。引擎自检在 3 台设备上各 13 项全过,就是这条门槛的具体形态:不是“跑起来没报错”,是 13 项逐项通过。设备进池时要落一条记录,字段固定:设备标识、ADB 序列号、安卓版本、绑定的 adb server 端口、归属服务线、最近一次自检结果与时间、当前状态(可用、隔离、退役)。这张表是后面所有判断的依据,字段缺一项,排查时就少一条线索。

判定频率要分两档。轻量的一档是每次任务前做一次 shell 往返,几秒钟,用来挡住刚刚掉线的设备;完整的一档是每天一次跑全 13 项自检,用来挡住那些能连上但界面层已经坏掉的设备。只做轻量档,会把“能连不能用”的设备放进队列;只做完整档,会在两次自检之间让掉线设备拖垮一批任务。

还有一类设备要单独说:模拟器。模拟器没有实体 USB,掉线方式不一样,往往是整个进程被杀或端口被回收。判定条件一样,但它的端口与进程绑定关系需要单独维护,不能和真机共用一张端口表。混在一张表里的后果是:模拟器重启后端口被别的进程占走,而配置里还以为它在那儿。

设备端的流程、点击与输入这几层怎么跑,在手机 APP 自动化通道里逐项写过。

02  /  端口锁

一个端口一台设备,锁必须显式

多设备并行最容易踩到的工程约束是这一条:uiautomator2 的一个进程只绑一个 adb server 端口。这句话的含义是,多个设备要真正并行,就不能共用默认端口,必须为每台设备分配独立的 adb server 端口,并且保证同一时刻只有一个进程占用某个端口。

不做这件事的表现很有欺骗性。程序不报错,任务也在跑,只是速度上不去——两台设备的操作被排成了一串,因为它们在抢同一个端口。更危险的一种表现是:进程以为自己控制了 A 设备,实际拿到的是 B 设备的界面,于是点击落在了错误的机器上。这种情况下日志看起来正常,只有对比截图才能发现,而截图往往没人看。

环节做法不做会怎样
端口分配端口号与设备标识在配置里一对一绑定,不用默认端口多设备退化成串行,或互相串台
申请启动时先申请端口,写一份带进程号与时间戳的租约文件两个进程同时认为自己拥有该端口
校验拿到界面后先核对设备身份,再执行第一个动作点击落到别的设备上,且不报错
释放正常退出与异常退出都要释放,租约超时可被回收端口永久被占,只能人工清锁

租约文件比起简单的“有文件就算占用”更可靠,原因是它会过期。进程崩溃时来不及删锁是常态,如果锁只是一面旗子,一次崩溃就会让这台设备永久不可用。写成租约:文件里存进程号、开始时间、设备标识;回收程序看到租约超过阈值就判定失效,回收后再重新分配。阈值不能太短,否则正常的慢任务会被误判;也不能太长,否则崩溃后要等很久。实际取值按单条任务最长耗时上浮一段来定。

端口校验这一步值得单独强调。判断“这台设备是不是我要的那台”,不能只看 adb 序列号——序列号在部分模拟器和部分真机上会重复或被改写。做法是在设备进池时把一条可读的标识写进配置(比如设备名加序列号的组合),每次建会话后读一次,与配置比对,不一致就中止这一条任务并隔离设备。这个检查只花一次界面读取,却能挡住后果最重的一类错误。

端口数量要预留。五台设备不是配五个端口,而是要留出回收与轮换的余量,否则一台设备隔离后备用机接入时会发现端口全被占着。经验做法是按设备上限的一点五倍规划端口段,并在配置里写清楚哪一段留给真机、哪一段留给模拟器,避免两类设备争同一段。

03  /  槽位归属

任务不是平均分,是算出来的

设备变多之后,最自然的想法是“五台设备,任务平分五份”。这个想法会直接撞上平台的节奏约束,因为节奏不是按设备算的。同一份发布计划里,两条通道的槽位归属是算出来的——同一个函数对象算出结果,不是人工在表里标出来的。多设备延伸也是同一条规则:槽位归属由分配函数算出,人工只在异常时干预,不做日常标注。

先摆清楚约束:每日 5 到 10 条,条间隔不小于 30 分钟并带抖动,发布窗口 09:00 到 21:59,单服务线每日 2 条,周上限 12 条。这几条是账号侧的边界。所以加设备不会提高单个账号的日上限,一台设备和五台设备对同一个账号的配额是一样的。加设备改变的是并行度、容错和不同账号之间的隔离度。

先按服务线切,再按设备落

队列的第一步切分单位是服务线,不是设备。每个服务线(对应一个账号)有独立的节奏计数器与独立的发布窗口占用,互不借用配额。切完之后才轮到设备:一条服务线固定一台主设备。固定主设备的理由很实际——同一个账号在两台设备上同时操作,行为特征会立刻变得不一致,登录态、缓存、操作习惯都会露出破绽。

备用设备只在隔离时接管

主设备被隔离,备用设备接管,但接管要带冷却:不在隔离的同一分钟内接手,先跑一次完整自检,通过再接。冷却的作用是避免把“主设备坏了”变成“两台设备一起坏”。接管只是把队列顺延,不补发——顺延掉的那一条进下一个可用槽位,不额外增加当日条数。

负载不按台数平均

五台设备不等于每台两成。分配要看三件事:该服务线当天还剩几个槽位、设备最近一次自检的耗时、设备当前的队列深度。耗时长的设备少分两条,比平均分更接近实际吞吐。平均分的后果是慢设备成为瓶颈,快设备空转,整体窗口利用率反而下降。

槽位编号要唯一且稳定

每个槽位在计划生成时就拿到编号,之后不管由哪台设备执行,编号不变。台账按槽位编号对账,设备换了、通道换了,编号都不换。这是后面能算清“谁漏了”的前提。

分配函数要能解释。上线前拿一份计划跑一次分配,把每个槽位的归属打出来,人工核一遍:同一个账号有没有被分到两台设备、同一台设备有没有同时背两个账号、某台设备是不是连续吃了当天最后几个槽位。三项都过,分配逻辑才算可用。跑不出来就别上,靠人肉排表的系统在设备数超过三台之后一定会乱。

顺延队列是这套分配的缓冲区。它记录“本该发但没发”的槽位,附原因与原始计划时间。队列深度是一个很直接的健康指标:稳定在一两条是正常波动,持续涨到一天的槽位量,说明设备池的吞吐已经跟不上计划,这时候要么减计划,要么加设备,而不是压缩间隔硬塞进去。间隔与窗口的具体定义见定时发布与节奏控制。

04  /  故障隔离

一台坏了,不能拖住整条队列

多设备的价值有一半体现在故障上:单台设备出问题,当天其他槽位照常跑。要做到这一点,得先把“坏了”定义清楚,再把“坏了之后做什么”固定下来,否则现场每次都在临时决定。

设备状态只有三级:可用、隔离、退役。三级之间只有四条转移路径,写死在程序里,不做第五种状态。状态多了,统计会变形,判断会变慢。

触发条件判定动作状态转移
shell 往返连续失败重试一次仍失败即判离线,不无限重试可用 → 隔离
自检某项不通过记录失败项编号,留一份界面快照可用 → 隔离
同批任务中仅该设备集中失败对比同批其他设备结果,排除平台侧原因可用 → 隔离
隔离后自检全过跑完整自检,13 项全过隔离 → 可用
隔离后连续两轮自检不过记录原因与次数,不再自动回池隔离 → 退役

这里要区分两种健康闸,混在一起是常见错误。平台健康闸看的是平台侧状态:出现异常时暂停发布,不硬发,等状态恢复。设备健康闸看的是这台设备自己:只影响该设备上的任务,其他设备照常。把两者做成一个开关,结果是平台稍有波动就全池停摆,或者设备坏了却以为平台好了。两条闸分开,日志里也分开记,复盘时才分得清是哪一类。

隔离之后不做自动重试。这条看着反直觉,但在发布场景里是必须的:重试发布可能造出一条重复商品,重复比缺一条更难处理。正确处理是顺延——该槽位进顺延队列,由备用设备或下一个可用槽位接。重试只允许发生在“读界面失败”这类无副作用的动作上,凡是会改变平台状态的动作,一律不重试。

隔离时要留现场。至少三样:一张界面快照、一次界面结构 dump、一条带时间的失败原因枚举。原因必须是枚举值,不能写句子,否则一周之后就统计不出“这台设备主要因为什么被隔离”。三样东西的价值在于:设备是真坏了还是被某类商品卡住了,看这三次记录就能分开。曾经出现过的情况是某台设备连续被隔离,最后发现是特定类目的图片上传慢,超时被误判成设备故障——有 dump 才看得出来。

最后是恢复的节奏。隔离的设备不要立刻回池,先跑一次自检,通过了再观察一轮任务,两轮都正常才回到正常分配权重。急着回池的代价是同一台设备一天内反复隔离,队列反复顺延,节奏被打乱——这比直接少一台设备更糟。设备数少的时候尤其明显,因为反复隔离会让当天窗口末端堆满顺延任务。

05  /  并发回写

多台同时写账,槽位守恒不能破

设备一多,回写就成了并发问题。两条通道写的是同一本台账,由一个写入模块统一落盘,记录字段是 item_id、标题、描述、价格、时间戳这一套。多台设备同时回写时,如果写入不做并发控制,问题不会以报错的形式出现,只会以“对不上”的形式出现:按槽位对账时四个状态加起来不等于当日槽位数。

交错写入

两行内容在同一位置被同时写,结果是一条记录里混了另一条记录的一部分。表现为台账里出现一条字段都对但组合不合理的记录,肉眼很难发现。解决方式只有一种:整行一次性追加,或者加锁。

丢记录

两个进程读到同一个文件末尾位置,各自追加,后写的覆盖先写的。表现为当天少一条记录,且没有任何痕迹。设备数越多,撞上的概率越高。

状态覆盖

同一条商品先被写成失败,随后被改成成功,两次写入顺序颠倒,最终状态可能是错的。台账只允许追加,改状态靠追加一条新记录,当前状态取最后一条,这样顺序问题不会变成状态问题。

并发控制的具体做法是锁加追加:写入前申请一把针对台账文件的锁,拿到锁之后以追加模式写完整一行,再释放。锁的粒度对整本台账还是分段,取决于并发量;设备数在十台以内,整本加锁的开销可以忽略,简单做法更不容易出错。追求细粒度锁之前先确认并发真的到了那个量级,否则是把复杂度提前引入。

写生产数据前留一份备份,是这套并发机制的配套要求,不是可选项。备份要在改动之前生成,并且立刻读回来验证行数一致——很多所谓的备份从未被恢复过,需要的时候才发现是空的。这个要求在设备多的时候更重要,因为一次并发事故影响的是整天的记录,而不是一条。

还有两道防护跟并发无关但必须一起做:自检进程带标记,带标记的进程尝试写生产数据直接抛错;整轮自检前后对生产文件做哈希比对,不一致就报警。这两条防的是“写错地方”,和并发防的“写坏内容”是两回事。曾经出现过自检数据写进生产台账的事故,根因是路径解析早于环境变量生效,所以数据目录必须用绝对路径写在配置里,不依赖环境变量。台账的字段口径与并发细节,在发布数据台账里有完整说明。

多设备场景下还要注意一件小事:时间戳。不同设备的系统时间可能差几十秒,如果时间戳取设备本地时间,跨零点或跨窗口边界的判断就会飘。做法是统一取主控端时间,设备侧只提供事件顺序,不提供绝对时间。这一条在设备少的时候看不出来,设备一多就会有若干条记录落在窗口边界外,而规则上它们本该在窗口内。

06  /  工程坑

几处能让全池一起停摆的坑

下面这几处是实测踩过的,共同特点是:报错信息指向的地方不是真正的根因,排查时容易在错误的方向上花掉半天。

PIT 01

驱动跑在哪个系统上

WSL 到 Windows 的 TCP 不通,所以 ADB 驱动必须跑在 Windows 侧。把驱动放进 WSL 里,表现出来是设备列表空、连接超时,而设备本身完全正常。

PIT 02

adb server 要显式寻址

不要依赖默认地址与环境变量推断。server 地址写在配置里,连哪个地址、哪个端口都明确。依赖推断的后果是同一台机器上不同进程连到了不同的 server,各自看到一部分设备,谁都不全。

PIT 03

dump 在 launcher 屏会失败

界面结构 dump 在桌面(launcher)屏有较大概率失败。做法是先试压缩模式,失败再退回普通模式。只写一条路径,就会出现“任务卡在打开应用那一步”的现象。

PIT 04

中文打不出来不是编码问题

adb input text 打不出中文,这不是字符集设置能解决的。要用 uiautomator2 那条通道,已在实测中验证可输入中文。把它当成编码问题去调,会一直调不通。

PIT 05

供电不足导致批量掉线

设备数量上去之后,普通 USB 口的电流不够,表现为好几台设备在同一时间段集体掉线。换带独立供电的集线器,并把设备分散到不同的控制器上,比逐台排查有效。

PIT 06

端口占用清理不干净

进程被杀但端口没释放,下一次启动申请不到端口,报的却是“设备未找到”。这就是第 02 节要用租约文件的原因:锁会过期,比人工清锁可靠。

这六个坑有一个共同的处理原则:把判断写成代码里的检查,不要写进值班笔记。设备连不上时,先看 shell 往返、再看 server 地址、再看端口租约,三步走完才知道该修哪里。三步靠人记,换个班就断了。

另外补一条容易被忽略的:设备名与序列号。部分设备的序列号在重新授权后会变,部分模拟器的序列号是固定值,多台模拟器会撞号。配置里如果只用序列号做键,撞号时会出现两台设备抢同一份配置,症状是任务跑到一半行为变了。做法是键用“序列号加设备名”,并在进池时校验唯一性,撞号直接拒绝入池。

入池之后再改设备名,等于换了一个身份,配置里的键与端口绑定都要同步改。漏改的表现是任务报设备未找到,而设备明明还在列表里——这类故障最容易让人在 USB 和数据线上白折腾半天。

07  /  风控边界

设备多,不等于可以多发

多设备最容易被误用的方向,是把它当成多开刷量的工具。这个方向不但无效,还会把账号推到风险里,因为节奏约束是按账号算的,不是按设备算的。

发布侧的约束摆在这里:每日 5 到 10 条,条间隔不小于 30 分钟并带抖动,窗口 09:00 到 21:59,单服务线每日 2 条,周上限 12 条。这些数字在设备增加到五台之后一个都不变。五台设备对同一个账号发送,只会让间隔变小、时间分布变密,正好落在风控最敏感的特征上。

多设备真正的用处

  • 单台故障不影响当天槽位,队列能顺延而不是丢
  • 不同账号在不同设备上,登录态与操作习惯互不串
  • 自检、灰度、新流程验证可以并行,不占生产设备的窗口
  • 备用设备可以在隔离时接管,不需要现场换机
  • 不同类目的操作节奏可以分开调,互不牵制

多设备不解决的问题

  • 单个账号的日上限与周上限
  • 商品同质化:指纹去重、同款链接不超过三条、单链接 3 到 9 张图这些结构规则
  • 类目分散、发布时间分散、标题与主图相似度分散
  • 合规闸门与到点复检的结论,不通过就是不发
  • 平台侧异常时的暂停,硬发只会更糟

合规闸门和发布时刻复检要一起看。闸门在计划阶段跑一次,到点发布前用当下的基线再跑一次,不通过就不发。多设备并行时,这一条尤其重要:如果只在计划阶段跑,几台设备同时执行时基线已经变了,前面几条发出去改变了同类商品的分布,后面几条按旧基线判断就会误放。复检是 fail-closed 的,宁可少发一条。

货品层还有几条规则要用设备池一起守住:商品指纹去重,同一个指纹的链接不超过三条;单链接 3 到 9 张图,少图会被判为低质,多图上传慢会拖窗口;类目按计划分散,不集中在同一类目连续发。这些规则由闸门执行,跟哪台设备执行无关,但设备多的时候更容易被绕过——因为人会觉得“反正还有别的设备,多插一条看不出来”。看不出来的只是当天,看得到的是一周之后整体结构变差。

还有一个很实际的边界:设备与账号的对应关系要固定。今天 A 账号在 1 号机,明天换到 3 号机,这种迁移看起来只是换了台机器,实际上登录环境、机型信息、操作间隔全都变了,是风险最高的一类变化。要做迁移就一次做完,之后固定不动,不要每天换。相关规则在防封控与商品合规检查两页里有更细的口径。

08  /  扩容判断

什么时候该加设备,什么时候不该

加设备是成本决策,不是能力证明。判断依据只有几条,都能量化,不需要凭感觉。

该加的信号一:顺延队列持续变深

顺延队列稳定在一两条是正常波动。连续三天超过当日槽位量的一半,说明吞吐跟不上计划,加设备或减计划二选一。先减计划更省事,因为计划本身可能就超了账号能承受的节奏。

该加的信号二:隔离率上升但不集中

如果隔离集中在同一台设备,是那台设备的问题,换掉就行。如果隔离分散在多台设备,且原因各不相同,说明设备已经跑在极限上,需要扩容而不是修。

该加的信号三:单台任务排队跨过窗口

某台设备当天最后一条任务的开始时间已经接近窗口末端,说明这台设备的容量到顶了。这时候压缩间隔是错的,因为间隔有下限。

不该加的情况

日条数还没到上限、失败集中在合规闸门、没有值守人力、账号本身只有一个。这四种情况加设备只会增加维护面,不增加产出。

上线前把这份清单逐条过一遍,比事后排查省时间。

  • 设备身份唯一序列号加设备名的组合在全池唯一,撞号的设备拒绝入池
  • 端口一对一绑定每台设备有独立 adb server 端口,写在配置里,不用默认端口
  • 锁带过期端口租约有进程号与时间戳,崩溃后可自动回收
  • 三条入池判定shell 往返、界面 dump、中文输入通道,逐条通过才入池
  • 主设备固定一条服务线只对应一台主设备,备用机只在隔离时接管
  • 分配可解释拿一份计划跑出归属表,人工核对无账号串台、无设备连吃末端槽位
  • 三级状态只有可用、隔离、退役,状态转移路径写死在程序里
  • 两种健康闸分开平台侧异常暂停发布,设备侧异常只隔离单台
  • 改状态动作不重试发布类动作失败只顺延,不重试,避免重复商品
  • 并发写入有锁整行追加加锁,写生产前留备份并读回验证行数
  • 绝对路径数据目录用绝对路径写在配置里,不依赖环境变量
  • 时间统一时间戳取主控端时间,不用各设备本地时间

最后一句判断:设备池是运维对象,不是采购清单。买五台设备只要一天,让这五台稳定地按同一套规则跑一个月,靠的是上面这份清单里的每一条都真的在执行。设备数从一台到三台的变化,主要是端口与分配;从三台到十台的变化,主要是并发、隔离与值守。中间没有捷径,只有把每一层都写清楚。

多台设备能提高同一个账号的日发布条数吗?
不能。发布节奏是按服务线算的:每日 5 到 10 条,条间隔不小于 30 分钟并带抖动,窗口 09:00 到 21:59,单服务线每日 2 条,周上限 12 条。这些约束跟设备数量无关。多设备提升的是并行度与容错,不是单个账号的配额。想靠加设备突破节奏下限,只会让时间分布变密,落在风控最敏感的特征上。
为什么必须给每台设备分配独立的 adb server 端口?
因为 uiautomator2 的一个进程只绑一个 adb server 端口。多台设备共用默认端口时,两个进程会互相抢占,表现出来不是报错,而是任务被排成串行、速度上不去,或者拿到别的设备的界面而点击落错机器。做法是端口与设备标识在配置里一对一绑定,启动时申请带租约的端口,退出释放,租约过期可回收。
设备连不上,先查什么?
按三步走:先做一次 shell 往返确认是否真的活着,设备列表里有名字不算;再确认配置里的 adb server 地址是不是显式写的、有没有连到另一个 server;最后看端口租约是不是被上一个崩溃的进程占着。这三步都不看编码问题,因为驱动必须跑在 Windows 侧,WSL 到 Windows 的 TCP 不通,把驱动放进 WSL 会表现为设备列表为空。
一台设备出问题,当天的任务会全停吗?
不会。设备状态分可用、隔离、退役三级,触发隔离的条件是 shell 往返连续失败、自检某项不通过,或同批任务中仅该设备集中失败。隔离之后任务进顺延队列,由备用设备或下一个可用槽位接手,不补发、不额外增加当日条数。平台侧的健康闸是另一条,只有平台异常时才整体暂停。两种闸分开,才不会一台设备坏了导致全池停摆。
发布失败能不能直接重试?
改变平台状态的动作用失败后一律不重试,只顺延。重试发布可能造出一条重复商品,重复比少一条更难处理。允许重试的只有读界面这类无副作用的动作,比如界面 dump 失败后换压缩模式再试一次。隔离的设备也不要立刻回池,先跑完整自检,通过后再观察一轮任务。
多台设备同时回写台账,怎么保证账不花?
两条通道与所有设备写同一本台账,由同一个写入模块落盘,记录字段固定为 item_id、标题、描述、价格、时间戳。并发写入用锁加整行追加,避免交错与丢记录。历史只追加不覆盖,状态变化靠追加新记录。写生产数据前留备份并读回验证行数,自检进程带标记写生产直接抛错。并发问题不会报错,只会在按槽位对账时表现为四个状态加起来不等于当日槽位数。
什么时候才该加设备?
看三条:顺延队列连续三天超过当日槽位量的一半;隔离分散在多台设备且原因各不相同;单台设备当天最后一条任务的开始时间已经接近窗口末端。这三种情况说明吞吐到顶了。反过来,日条数还没到上限、失败集中在合规闸门、没有值守人力、账号只有一个,都不该加设备,加了只是增加维护面。
同一批商品能不能分到多台设备上发?
要小心。商品指纹去重、同款链接不超过三条、单链接 3 到 9 张图、类目与时间分散,这些是结构规则,跟由哪台设备执行无关。分到多台设备上发,容易让同一指纹的链接在短时间里集中出现,结构上立刻变差。设备多的时候人容易觉得多插一条看不出来,看得出来的是一周之后整体结构的变化。

运维视角

设备池不是采购数字,是一份要每天执行的规则

加设备只要一天,让五台设备按同一套规则稳定跑一个月,靠的是判定、锁、分配、隔离、回写这五层都真的在执行。

先算清楚三件事再加机器

一是账号能承受的节奏上限,它决定计划总量;二是单台设备的实际吞吐,它决定要几台;三是值守人力,它决定隔离与顺延能不能当天处理完。三项里任意一项没算清,加设备只是把问题挪到更难排查的地方。

先跑通一条真实发布,再谈铺量

把账号、设备、商品素材交给我们的部署流程,先跑通一条真实发布并留下可核对的日志,确认没问题再谈规模化。