Skip to content

字节内转即创一面

这下真是闹麻了,准备了八股也刷了题,完全聊的是实习业务 😵,已挂。

20250729164830_b60360f687038ad993c9505375998d42.png

  1. 自我介绍。

    📌 回答

    我是四月报到的,目前在 Ads Infra 团队做效能工程的实习生。从入职到现在,我负责的业务主要包括算法平台和元数据平台。我们团队基于 OceanCloud 做开发,OceanCloud 是将广告平台视作产品的一个集中管理的解决方案。

    入职前期,我快速熟悉了存量业务并且基于现有数据做指标计算,搭建了资产质量相关的看板,便于算法平台效率可视化。接下来承担了一个比较大的独立模块——特征淘汰,目的是保证算法开发过程中对特征迭代治理的可追溯性,提供任务发起、资产状态打标、收益文档上传、一键拉群和催办等功能,参与了需求分析、前端代码编写、BFF 搭建和 Design Review,目前处于流程自动化的初期探索阶段。

    Q3 季度初没有什么太大的需求时,承接了一些比较日常的开发,包括后端提到的一些接口升级、之前特征淘汰涉及到的公用血缘图组件的抽离。前段时间,主导开发了元数据平台的 AI Prompt,对于不熟悉元数据 DSL 的同学可以实现自然语言转 DSL 的效果,并添加埋点方案,生成率和采纳率能基本达到 80% 左右,目前处于维护和优化阶段。

    整体上来看,我实习期间主要的技术架构涉及到了 React、TypeScript、NodeJS、Monorepo、BFF 和 Garfish。

  2. 介绍一下刚才提到的你主导的特征淘汰的功能开发。

    📌 回答

    特征是有自己的生命周期的,对应着特征生产、特征调研、特征上线(特征推全)、特征下线(特征淘汰)和已经淘汰以后的收益结算阶段。

    之前的特征淘汰也有方案,但是面临许多问题。比如,特征和模型的血缘覆盖关系不强。又比如,特征依赖关系不全。当然,这样一个管理过程没有记录,不好追溯和管控也是方案迁移的一个原因。所以,依托于我们 Academy 算法平台的资产淘汰意义主要是有两点:

    • 对于业务同学,资源下线可以减轻线上服务计算压力和离线存储能力,也就是内存和物理存储的释放。
    • 对于我们来说,我们能提供一套解决方案,帮助业务同学更轻松可控地管理每一次特征淘汰,保证他们可以更好地管控、追溯和优化。
    • 对于整体来看,新的解决方案正在探索试图提供流程模版的复用,对于相似的流程管理可以复用到各个业务;另外即使是相同的资产淘汰标准可能不一样,但都是基于血缘的,血缘实现了复用也解决了之前说到的覆盖关系不强等引起的特征误下线的问题。

    这些就是我理解的业务背景,接下来说一下业务流程。

    每次特征淘汰的时候,淘汰任务发起人在我们平台上注册这样一个任务,圈选需要淘汰的特征产出一份列表,会进入状态确认的阶段。

    对于每个列表里的特征,目前会认为其所在的特征集 owner 作为负责人,所有负责人需要在这里进行状态确认,来确定是否可淘汰以及这次确认的结论要保持多久。期间,可以通过一键拉群和催办帮助发起人催促这样一个流程。

    所有的特征打标结束后,会进入淘汰阶段。业务的同学可以去自己名下做特征的下线工作和后续收益结算。对应我们的平台就是直接点击下一步和对应飞书文档的上传。

    在整个流程进行期间,前端页面会展示需要的按钮,比如一键拉群或催办、过程中对资产列表动态调整的 Button,同时会展示资产视角、上下游视角、血缘视角,更方便地解决了血缘问题,让业务同学能够清晰地了解资产关联的上下游和如何找到上下游。

    当然,现在还处于解决方案的探索优化阶段,目前只是实现了基础的特征打标,后续需求方正在进行自动化以及扩展的探索。

    后续的优化可能会包括这么几点:

    • 现在的数据存在 Hard Coding,需要引入模版并动态读取淘汰对象;
    • 现阶段手动圈选列表,后期可能会引入自动打标任务;流程目标停留在状态打标,实际下线仍然依赖人力,优化后可能要实现任务联动。
    • 目前收益结算依赖业务同学手动计算,后续可能会调用公式,但关系不大,我们只负责文档上传可追踪就好。
  3. 简单问了下流程相关的概念,比如为什么要上传收益文档?

  4. 怎么评估你们这个平台的流程好坏?(这个问题比较抽象啊……)

  5. 怎么去看某个平台的收益的?有什么数据指标吗?

    📌 回答

    • 对于业务的支持:在各个业务场景建立效能数据的指标体系和看板,比如我参与的 Academy 算法平台,我们会搭建看板来显示周期内的数据。比如针对 AI 综合搜索的提问总数或者返回概率、响应时间等,针对一周内的特征生产或者特征集在线加工等的任务新增数量。又比如元数据平台,对于录入的实体都有看板可以展示实体质量的高低(根据实体的字段信息配置,比如某个权限字段如果存在这个实体质量里加 x 分)。

    • 对于研发的支持:这一方面的提效从很多角度来考虑。比如平台工程方面,可以从需求交付的效率上来对比每个周期需求完成的情况,我们还会比较组件的丰富度、对需求的覆盖程度、通用组件实现占比计算得到的 Codeless 率来体现这一点;除了结果指标,在过程指标上我们会考虑实体接入情况,换句话讲就是多少实体采用了我们的元数据方式。

  6. 介绍一下你们平台的一个技术架构。

    📌 回答

    随着存量业务高速发展和快速积累,广告中台的效率收益被稀释,进一步导致业务需求难以被高效支持,同时系统架构和解决方案的重构变的困难。OceanCloud 是一个产品化的平台,能够让中台的能力更加透明和可预期,目标是为了将广告系统抽象为产品,通过平台来提供接入和配置管理的能力,整体架构分为平台应用、平台业务、通用服务(工单能力、权限能力等)和基础服务(GULU、Garfish、Vmok 等)。

    平台在技术栈方面全面采用 React 和 TypeScript 来支持开发,具体来说会借助 EdenX 提供的 Monorepo 代码组织方式和 Garfish 做微前端项目管理。同时,部门内也承担 NodeJS 的 BFF 开发。

  7. BFF 的优缺点。

    📌 回答

    ✅ 能做到多终端架构,能避免一套接口通吃。

    ✅ 可以自行定制数据的剪裁或拼接等逻辑,一定程度上可以优化请求的性能。

    ✅ 职责清晰的情况下,前端逻辑不侵入后端服务,随着业务迭代后端复杂性会降低。

    ✅ 取数用数收敛到一个团队,维护业务的整体复杂度下降。

    ❌ 服务数量可能激增,可能引入性能瓶颈。

    ❌ 前端 BFF 建设可能面临学习成本、运维成本、代码质量等问题。

    ❌ 边界模糊时需要思考很多业务逻辑下沉的问题。

  8. 在你的视角里如何确定 BFF 的职责边界?

    📌 回答

    以我们团队的 BFF 基建为例,我们的长期目标是业务后端只提供领域服务,其余的功能由 BFF 承载,BFF 可以承接通用能力建设,避免重复劳动。整体上会分为业务层、通用服务层、数据层、中间件等,比如登录鉴权等可能只是一个通用服务层里的 decorator,而没有放在后端同学的业务代码中。

    更通用地来讲,可以从很多方面去考虑这个问题。如果从业务逻辑上看,可能后端同学会负责数据、鉴权;如果从前端开发体验上来看,可能就类似于我们的架构,前端对数据的可控性会更高,但是具体的业务逻辑仍然是保持纯粹,让后端同学来维护;如果从团队出发,逻辑变化频繁、紧贴 UI 的场景,更适合放在 BFF,反之如果涉及的业务核心规则更多,就应该收敛到后端服务。

    整体上来说,BFF 职责边界在于让前端更好地消费后端,实际开发中可能还会遇到各种情况而有差异。

  9. BFF 接口整合的时候如何保证接口的稳定性和时延?代码层面有什么想法?具体在 Axios 里怎么配置?

    📌 回答

    接口聚合主要面临两个问题——时延叠加和单点失败,也就是说多个接口串行请求可能会导致整体耗时增加,一个子接口失败可能会导致整个接口聚合失败。

    第一种解决办法就是,用并行替代串行,比如采用 Promise.all,需要注意的是可能需要做限流工作,防止太大的调用压力。

    第二,用数据缓存和预加载,能够降低接口的调用频率,增加稳定性。

    第三种是可以使用超时和重传机制,防止单点失败或者长时间阻塞。

    最后一种方法就是容错兜底了。

    具体代码上,可以通过 timeout 来配置 axios 的超时,并添加拦截器输出日志标明请求慢或者失败的接口信息;可以通过 axios-retry 包来重试请求;也可以自己构造一些类来实现熔断兜底机制。

  10. 重试在前端如何实现?在哪一层去实现?

    📌 回答

    幂等请求推荐在请求工具层做重试,比如 axios 拦截器,可以避免每个请求都手写。

    非幂等的请求、需要有特定的 fallback 或者用户交互,推荐在业务逻辑层做重试,可以实现粒度更细的控制。

  11. 并不是所有的接口都想要重试,怎么实现某个接口要不要重试,怎么判断呢?

    📌 回答

    • 从逻辑上考虑,幂等的 GETOPTIONSHEAD 请求可以重试,静态资源类请求比较可靠但是支持重试,非关键数据接口和副作用比较小的接口也可以重试;对应地,非幂等的 POSTDELETEPUTPATCH 请求和需要用户交互的上传/转账等带有副作用的接口,不能自动重试,应该把控制权交由给用户。
    • 从实现上考虑,axios 等请求库一般可以通过配置来实现重试控制,也可以利用拦截器来做重试的判断和记录;对于自己封装的请求工具,如何判断是否请求,可以从很多方面考虑:请求的路径、useRequest 等传入的 config、响应的状态码、响应头里的 Retry-After 信息等。
    • 另外,还有一些软性条件需要注意。比如依赖重试时间发出的请求,最好将重试时间设置成 指数退避 的模式,避免频繁的重试。
  12. 整个平台都使用了微前端,有什么弊端吗?DX 角度和 UX 角度。

    📌 回答

    🧑‍💻 需要统一技术栈,如果技术栈不统一,维护成本可能高于开发成本。不过,因为团队项目技术栈统一,不存在这方面的问题。

    🧑‍💻 本地开发调试流程比较繁琐,比如测试某个平台的功能,可能需要同时启动 client、server、components 等多个子应用。

    🧑‍💻 公共能力不足,跨项目但是相同的代码可能要多次书写,抽离后又会引入版本控制、运维等新的问题。

    🧑‍💻 重复引用依赖,跨项目之间的公共依赖被重复引用,包体积膨胀。

    🧑‍💻 历史存量做微前端迁移有一定的学习成本。

    🧑‍💻 存在性能瓶颈、内存泄露等问题。

    👦 首屏加载慢,因为子应用是懒加载,可能会引入多个 Bundle、多个运行时,导致资源加载延迟。

    👦 切换子应用可能会导致白屏或闪屏,在旧的子应用卸载和新的子应用挂载的时候,如果没做好过渡处理,页面闪烁或者空白页对用户体验不好。

    👦 子应用之间可能因为 UI 风格等原因,给用户带来体验上的割裂感。

    👦 状态不共享等页面行为的差异,比如用户登录态没有做好统一管理,导致切换子应用之后用户需要重新鉴权。

    👦 性能开销大,每个子应用都有自己独立的框架和生命周期,容易造成内存泄漏或者性能抖动。

  13. 怎么做到微前端样式和变量的隔离?

    📌 回答

    CSS 的隔离方式有很多种:Scoped、CSS Modules、CSS-in-JS、Shadow DOM、BEM、前缀 + sandbox 等。

    而 JS 可以采用 iframe、Proxy、VM 等沙箱方式或者用模块化导出。

    字节内部使用的微前端解决方案 Garfish 也适用上面几种方式。

    JS 方面,Garfish3 使用的是 快照沙箱,子应用加载的时候监听事件,卸载的时候恢复状态,但是由于会影响到主应用和其他子应用的事件,所以不支持多实例的场景;Garfish5 则采用 VM 沙箱 的办法,借助 ProxyPolyfill 伪造环境,修改 get、set 等方法拦截对 window 的读写,另外还重写了 setTimeout 和 setInterval 原生方法,并且在 webStorage 读写过程中用前缀做子应用的区分。

    Garfish 通过将子应用的 style 和 link 标签移动到其挂载节点内部,确保了样式只影响子应用本身,避免了污染宿主和其他子应用。同时,它还对动态添加的 style 进行了拦截处理,代理了对 head 的访问,确保动态样式也被正确隔离。这种做法类似于模拟 Shadow DOM 的效果,但不依赖浏览器的 Shadow DOM 实现,更具兼容性和工程实用性。

  14. 运行时用户的微前端项目加载速度怎么优化?

    📌 回答

    微前端对于用户的影响,可能包括 首屏加载慢切换不流畅公共依赖、样式、资源等重复加载

    • 从主应用角度考虑,可以 异步加载子应用Skeleton 骨架屏资源预加载。主应用首屏只加载必要的资源,其余的异步获取;在资源不足时,渲染 Loading 效果,提升用户体验;对于下一步可能访问的资源,使用 prefetch 或者 preload。
    • 从子应用角度考虑,可以采用 按需加载公共库 external 化CDN 加速 + HTTP2压缩优化 等。对于子应用,可以采用懒加载,而不是每次都加载一整个应用;公共库提取可以保证 React 等库不会重复打包,同时还能解决依赖混乱的问题;剩下的就是比较常见的性能优化手段,比如 gzip、brotli、tree-shaking、webp 代替 jpeg 等。
    • 从微前端框架角度考虑,框架可能会提供 沙箱缓存依赖共享预加载 等功能。比如,Garfish.preloadApp 可以实现资源预加载;Garfish.registerApp 默认开启 cache,能对加载过的子应用作缓存,节省二次渲染开销,避免重复编译和代码逃逸带来的性能和内存上的问题。
    • 最后,如果加载速度仍然不尽人意,可以考虑用户体验的改善。比如 Loading 动画骨架屏渐进式加载异常重试和降级兜底 等。
  15. 微组件。

  16. 多仓项目里公共的依赖版本冲突怎么解决?有什么好处?

    📌 回答

    多仓指的是项目被拆分成多个仓库,比如 project-a、project-b 是·业务项目,shared 是一个通用的共享模块。

    依赖混乱的情况可能出现在每一个项目都用到了外部的第三方库。比如这三个都依赖 Lodash 但各自的版本又都不同。此时,如果 a 还引用了 shared,就可能导致运行时 bug 或者打包体积过大,甚至直接报错。这样的情况,就叫依赖版本冲突。

    💡 统一版本 是最简单直接的方法,所有项目都使用一模一样的依赖,版本一致不容易出错,升级也简单;但是缺点也很明显,需要手动维护,如果很多项目都有这个依赖也就容易产生遗漏了。

    💡 对等依赖,可以在 package.json 中配置 peerDependencies,适合公共库,不会强制安装依赖,而是灵活地将权限交给项目自身,避免版本重复;缺点就是主项目必须有这个依赖,否则会报错。另外,我自身感觉到的一点就是对新手不友好,这种方式调试成本比较高。

    💡 依赖抽离,将依赖抽离出来自己维护一份,可以是 Git 仓库 或者 NPM 私服,这样也是统一版本的一种手段。集中管理可以实现一改多用,但是增加了额外的维护成本,项目耦合度变强了。

    💡 工具,有一些包可以检查是否有依赖重复等。另外,可以通过 npm overrides 强制使用某个版本的依赖。优点是可以实现自动统一版本,不怕手抖;但是可能会依特定的包管理器,需要考虑这方面的兼容性。

  17. 没留在原团队的想法。

  18. 了解过我们的业务吗?

  19. 「反问」业务交流,包括现下在做的和为什么从 React 迁移到了 Vue3 等问题。