Production Engineer · PE / Coding

Meta 服务迁移巡检:双端口、未知结果与有界并发

探测旧123与新456端口,输出迁移状态并限制并发,区分端口事实与版本证明。

题目重点:探测旧123与新456端口,输出迁移状态并限制并发,区分端口事实与版本证明。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练每host两个TCP connect probes,端口按约定对应服务版本,但连接成功只证明端口接受连接,真正version需协议健康响应。输入host唯一,代码不扫描未授权目标。
  • 明确open/refused/unknown;超时、DNS失败和路由问题均unknown,不称host必down。示例无自动重试,总结要保留未知项。

解题路线

  1. 旧新都open标both,旧open新refused为old-only,反之new-only,都refused为neither。任一unknown都保留两端详情并归unknown,不能与refused合并。
  2. 每连接with context关闭socket,防数千host句柄泄漏;timeout限制connect等待,但OS DNS解析不受同一connect timeout严格保证,别承诺完整总deadline。
  3. I/O等待适合ThreadPoolExecutor,有界in-flight workers,不一次submit整个巨大文件。完成一个host再提交下一个,流式yield结果,输出顺序按完成不保证文件顺序。
  4. 每host两probe独立记录,巡检间状态可能变,因此不是全网原子快照;记录观测时间并对迁移异常做有预算复查,勿自动重启或关旧版。
  5. 进程适合CPU密集解析/隔离而非简单socket等待,额外启动/通信成本大。线程也有DNS、fd和目标负载上限,不能无限开线程缩短总时长。

手动走一遍

old connect成功、new超时应输出old=open,new=unknown,status=unknown,不能说only-old;两端拒绝仅neither-port-open,主机可能健康但服务未监听。两open需进一步核对进程/版本,可能双实例过渡。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

import socket,errno
from concurrent.futures import ThreadPoolExecutor,wait,FIRST_COMPLETED


def probe_port(host,port,timeout):
    """Connect timeout does not guarantee a deadline for OS DNS resolution."""
    try:
        with socket.create_connection((host,port),timeout=timeout):return 'open'
    except OSError as error:
        return 'refused' if error.errno==errno.ECONNREFUSED else 'unknown'


def migration_status(host,timeout=2,probe=probe_port):
    old=probe(host,123,timeout);new=probe(host,456,timeout)
    if 'unknown' in (old,new):status='unknown'
    elif old=='open' and new=='open':status='both_ports_open'
    elif old=='open':status='old_port_only'
    elif new=='open':status='new_port_only'
    else:status='neither_port_open'
    return host,old,new,status


def iter_migration(hosts,workers=32,timeout=2,probe=probe_port):
    """At most workers hosts in flight; unordered completion; unique input hosts.

    Yields facts, not proof of service version or host liveness. No network retries.
    """
    if workers<=0 or timeout<=0:raise ValueError('positive workers/timeout')
    iterator=iter(hosts)
    with ThreadPoolExecutor(max_workers=workers) as pool:
        pending=set()
        for _ in range(workers):
            try:host=next(iterator)
            except StopIteration:break
            pending.add(pool.submit(migration_status,host,timeout,probe))
        while pending:
            completed,pending=wait(pending,return_when=FIRST_COMPLETED)
            for future in completed:
                yield future.result()
                try:host=next(iterator)
                except StopIteration:continue
                pending.add(pool.submit(migration_status,host,timeout,probe))

复杂度

H host两探测、W并发,理想网络等待总约O(H×平均探测等待/W),但DNS与目标限速影响;in-flight状态O(W)、结果流式,调用方收集全结果才O(H)。超时×2不是OS DNS全局硬截止。

易错点与追问

容易失分的边界

  • 超时直接判断down,隐藏防火墙/网络不确定性。
  • 无界submit和线程数让巡检本身资源耗尽。
  • TCP open当作服务确实新版,无版本响应验证。

继续深挖

  • 若强制每host总deadline,怎样隔离/限制DNS并取消未完成I/O?
  • 重试需指数退避与总预算,怎样避免迁移故障时巡检加剧负载?
✓已复制微信号!