资讯动态

B站缓存合并工具:m4s与微服务架构实战

发布时间:2026/10/14 16:35:04 来源:尧图企业网站定制
如果你也习惯把B站缓存到手机里通勤看一定见过这种画面换到电脑上想接着看打开缓存目录里面躺着一堆 video.m4s、audio.m4s、entry.json有的还带 danmaku.xml拖进播放器要么黑屏要么只有声音要么干脆识别不了。我之前为这事折腾了好几个晚上后来干脆写了一套全平台B站缓存合并服务用户只需要上传缓存文件夹后端自动识别每个分P的音视频轨调用 ffmpeg 抽取并合并最后吐出一个能直接播放的 mp4。服务最开始是个 Java 单体为了同时服务 Windows 客户端、macOS 脚本、Web 端和手机端又把后端拆成了网关加四个微服务。这篇文章不打算讲太多花哨的概念而是围绕这个工具把 m4s 合并的原理、Java 侧调 ffmpeg 的方式、异步任务设计以及真正在项目里用得上的微服务知识点挨个捋一遍。想学 Spring Cloud 但一直被网上的“全家桶教程”劝退的人写过单体后端正准备拆服务的人以及纯粹想做个缓存合并小工具的人都能在里面找到能直接用的东西。1. 项目全貌与拆分思路1.1 缓存合并到底在解决什么问题B站客户端的缓存目录在不同版本里长得不太一样但核心结构非常相似。以常见的安卓缓存为例路径通常是 Android/data/tv.danmaku.bili/download/ 下面的某个视频编号目录里面按清晰度分目录再往里就是音视频文件和描述信息download/{视频编号}/ ├── entry.json // 视频信息标题、分P、清晰度描述 ├── danmaku.xml // 弹幕文件 └── 1/ // 清晰度目录编号 ├── video.m4s // 视频轨DASH 分片格式 └── audio.m4s // 音频轨有些老版本缓存还会出现一串 .blv 分段文件这时候就不是简单的“音视频分离”而是要把 N 个分段先按顺序拼起来才能进入下一步。用户的需求说简单也简单把这一堆碎片变成能双击播放的单个文件。但真做起来坑全在细节里文件名不一定带后缀、m4s 文件头有偏移、不同清晰度目录命名规则不一样、分P多还会嵌套子目录。正因为这些细节太啰嗦做成后端服务才有价值——客户端只负责上传缓存目录解析判断全部集中在服务端修一个版本所有人跟着受益。所谓“全平台”本质不是写一套跨平台桌面程序而是把合并能力收敛成一套 HTTP API。Windows 客户端可以上传macOS 脚本可以调用Web 页面可以预览手机端可以提交任务所有端共用同一份逻辑。1.2 为什么从单体走到微服务这个项目最早确实是一个单体应用一个 Spring Boot 工程controller、service、mapper 全堆在里面。数据量小的时候完全够用问题出在合并任务开始多起来之后。第一个矛盾是负载模型完全不一样。合并任务要拉起 ffmpeg 进程处理几分钟的视频时 CPU、磁盘 IO、内存都被占满而 API 查询查任务状态、查文件列表是典型的低负载短请求。两者塞在同一个进程里一旦有人并发提交几个合并任务整个服务的接口延迟都会变得不可接受登录请求跟着遭殃。第二个矛盾是扩容维度不同。文件服务和任务服务面对的压力来源不同文件服务要处理上传下载瓶颈在带宽和 IO任务服务要跑 ffmpeg瓶颈在 CPU。如果捆在一起想单独加任务服务的实例就得连文件服务一起多部署一份成本直接翻倍。第三个原因是多端接入需要统一的出口。Windows、macOS、Web、手机端如果直接连接各个服务客户端要维护多套地址加一个网关之后所有端只认 /api/xxx 这一套路径内部怎么拆跟调用方无关。当然拆服务也带来了实打实的代价调试链路变长、部署步骤变多、出问题要跨服务查。所以我在项目里把所有中间件和业务服务都用 Docker Compose 编排好本地和生产保持一致这才把复杂度压住。1.3 服务边界怎么划我最终划分成四个业务服务加一个网关每个服务的定位非常明确服务职责代表接口gateway-service统一入口、路由转发、JWT 鉴权/api/v1/**auth-service登录、注册、Token 签发/api/auth/loginfile-service上传下载、缓存包解析、对象存储对接/api/file/uploadtask-service任务状态机、ffmpeg 合并、进度推送/api/task/submit文件服务和任务服务是核心auth 和 gateway 是标准配套。没有拆独立的消息通知服务因为合并进度本来就在 task-service 里产生直接通过 WebSocket 推送最简单没必要为了“微服务数量多”而额外拆一层。另外要说明的是这个项目没有引入分布式事务。合并任务涉及的操作都在 task-service 自己的数据库里任务表、文件记录表、结果表属于本地事务能解决的问题。跨服务最多是 file-service 返回一个文件元数据就算后续失败补偿成本也很低。很多人一上微服务就想 Seata其实大多数业务场景根本用不到。2. 缓存识别与合并的核心流程2.1 解析缓存目录文件名别太相信解析缓存目录的第一原则是文件名只是参考不能作为唯一依据。我遇到过缓存包里 video.m4s 被某些系统改名成 video.m4s.mp4 的情况也见过完全不带扩展名的文件所以判断文件类型必须靠二进制头。m4s 属于 ISO BMFF 格式家族通常文件开头会有 ftyp box。判断逻辑可以写成读取文件前 16 字节如果包含 ftyp 且后面跟着合法 box 长度就按 m4s 处理否则继续尝试当成无头裸流。这套逻辑虽然简单但能挡住大部分异常文件。还有一个非常容易踩的坑多P视频的排序。entry.json 里通常能拿到分P数据和序号一定要按这个序号排序而不是按文件名排序。因为文件可能叫 01.m4s、10.m4s也可能叫 1.m4s、2.m4s字符串排序会把 10 排到 2 前面最后合并出来的视频顺序完全错乱。解析描述信息时也别硬编字段名。B站客户端的 entry.json 和 videoInfo.json 在不同版本里字段命名有差异正确做法是做一个容错解析层先用完整路径优先取标题取不到再层层回退所有拿到的字符串都要做空值处理。这个工具面向真实用户用户手里的缓存包来自各种版本的客户端兼容性直接决定工具能不能用。2.2 解决 m4s 偏移这个头号坑缓存下来的 m4s 文件很多不是一个标准 MP4而是一段“描述信息 裸流”的复合结构。常见的情况是文件开头有一段 ASCII 数字字符串表示真正的音视频数据从第几个字节开始。我处理过的文件里这个偏移标记通常是 9 个字节比如 000000245含义是整个文件前 245 字节是给容器识别的描述性信息真正的视频数据从第 245 字节开始。也有的文件偏移为 0说明它本身就是标准的分片 MP4可以直接交给 ffmpeg。如果把带偏移的文件直接扔给 ffmpeg会遇到各种奇怪错误有的报 Invalid data有的读到一半掐掉有的能出画面但花屏。工程上最稳的做法就是统一探测偏移按偏移切出真正的流数据再交 ffmpeg 处理public static long detectM4sOffset(Path path) throws IOException { byte[] head new byte[16]; try (InputStream is Files.newInputStream(path)) { int read is.read(head); if (read 9) { return 0L; } } String prefix new String(head, 0, 9, StandardCharsets.US_ASCII); if (prefix.chars().allMatch(c - c 0 c 9)) { return Long.parseLong(prefix); } return 0L; }拿到偏移量之后用 RandomAccessFile 或 NIO 从偏移位置开始读取写入临时文件作为后续 ffmpeg 的输入。这个探测逻辑不是官方标准是我在大量样本上试出来的通用方案建议在代码里留着兜底分支——如果探测不到合规偏移就按照标准 MP4 去处理而不是直接报错。2.3 ffmpeg 转封装与最终合并命令选型核心合并流程分三步。第一步把视频轨 m4s 转封装成临时 mp4第二步把音频轨 m4s 转封装成临时 mp4第三步把两个临时文件合并成最终文件。第一、二步的命令形式ffmpeg -y -i video_input.m4s -c copy -fflags genpts -avoid_negative_ts make_zero video_tmp.mp4 ffmpeg -y -i audio_input.m4s -c copy -avoid_negative_ts make_zero audio_tmp.mp4第三步合并ffmpeg -y -i video_tmp.mp4 -i audio_tmp.mp4 -map 0:v:0 -map 1:a:0 -c copy -movflags faststart output.mp4全程用 -c copy也就是只转封装不重新编码。这样速度极快画质无损CPU 占用只发生在 mux/demux 阶段。如果中途遇到时间戳异常可以再叠加 -fflags genpts 重新生成时间戳或者用 -avoid_negative_ts make_zero 把负时间戳归零。为什么输出要加 -movflags faststart这个参数会把 moov 元数据挪到文件头部否则播放器需要把整个文件下载完才能开始播放。对于 Web 端在线预览特别重要本地播放虽然没有硬性要求但加上没有任何副作用还能提高快进快退的响应速度。如果遇到个别视频转封装失败我的兜底方案是转为重新编码视频走 libx264 加 veryfast preset音频走 aac。代价是慢一点、画质有轻微损耗但至少能出结果。这个方案优先级放最后因为绝大多数正常缓存包走 copy 路线就足够了。2.4 异步任务与进程管理合并一个视频动辄几十秒到几分钟这个过程绝对不能放在 HTTP 请求里同步等待。流程应该是客户端提交缓存包 → 服务端创建任务并返回 taskId → 工作线程异步处理 → 客户端通过轮询或 WebSocket 拿进度。异步化之后紧接着就是并发控制。一台机器同时跑太多 ffmpeg 子进程内存和 CPU 会直接被打爆。我的做法是用 Java 的信号量 Semaphore 限制本机同时执行的合并任务数比如单实例最大并发 2任务服务部署了 3 个实例全局就是 6 个并发完全可控。ffmpeg 进程本身也需要保护。ProcessBuilder 启动子进程后waitFor 必须加超时比如 5 分钟超时之后 destroyForcibly 强制杀掉防止异常文件把进程挂死在那里。日志方面把 ffmpeg 的错误输出流重定向到统一日志文件合并失败时能直接看到是编码问题、文件损坏问题还是磁盘空间不足排查效率能提升一个量级。3. 微服务骨架与关键组件选型3.1 Nacos 注册配置中心微服务之间互相发现依赖注册中心我选 Nacos 而不是 Eureka主要因为它除了注册发现还带配置中心一个组件解决两个问题。注册发现解决的是“实例地址漂移”。task-service 扩容到两个实例之后网关调用方不需要感知实例 IPNacos 会自动把请求分发到健康的实例上实例下线也会自动摘除。对于文件服务、任务服务这种随时可能扩缩容的服务这个能力是刚需。配置中心解决的是“改配置不重启”。ffmpeg 的安装路径、对象存储的 bucket 名称、合并队列名、单机并发上限这些值全部收进配置中心。上线之后发现并发设大了直接在控制台改配置服务通过 RefreshScope 自动刷新不用重新打包重新部署。这个能力做上之后就被频繁使用强烈建议所有微服务方案都配上。需要注意一个坑Nacos 2.x 版本客户端和服务端之间有 gRPC 长连接除了 8848 端口还会用到 9848 端口。Docker 部署时如果网络没放通这个端口服务会反复注册失败但日志里不报错非常隐蔽。我第一次部署时就卡在这里花了大半天排查网络。3.2 网关路由与统一鉴权网关是微服务的门面所有外部请求先进网关再转发到下游服务。路由配置基于路径匹配spring: cloud: gateway: routes: - id: auth uri: lb://auth-service predicates: - Path/api/auth/** - id: file uri: lb://file-service predicates: - Path/api/file/** - id: task uri: lb://task-service predicates: - Path/api/task/**lb:// 前缀表示按服务名从 Nacos 拉取实例列表做负载均衡。鉴权逻辑放在网关的全局过滤器里取 Header 里的 Authorization解析 JWT 得到用户信息然后把 userId 放进转发请求的 Header 传给下游。这样做的好处是下游服务完全感知不到用户体系接口只需要关心业务逻辑。登录接口、注册接口这些不需要登录态的接口通过白名单放行。网关里的过滤器不要做重业务操作比如查数据库、调远程接口否则会拖慢所有请求。JWT 解析是纯本地计算适合放在网关如果要查用户状态应该只在必要时由 auth-service 提供单独接口。3.3 OpenFeign 与超时控制task-service 在处理任务时需要知道文件服务那边上传的文件信息这个跨服务调用用 OpenFeign 最直接。定义一个 FeignClient 接口声明方法、路径、参数类型Spring 会自动生成实现类。Feign 默认的超时配置偏短连接超时通常没问题但读取超时要特别小心。文件服务返回的可能是大文件元数据甚至涉及对象存储的预签名地址生成耗时可能到秒级。如果读到默认的 5 秒超时任务服务就会频繁报 SocketTimeout。我把读取超时调到了 30 秒并加了 Feign 拦截器打印每个接口的耗时和参数排查慢接口时非常好用。跨服务调用最怕问题出现在半路日志却各自记各自的。我的方案是在网关生成一个 traceId通过 MDC 传到所有线程再透传给下游服务每个服务的日志配置都带上 %X{traceId}最终用这个 ID 把一条完整调用链串起来。后面排查问题基本不用猜直接按 traceId 在日志系统里搜一遍就知道请求走到哪一步断了。3.4 MQ 异步削峰与任务状态机合并任务的提交入口并发量可能很高尤其是周末晚高峰几百个用户同时提交缓存包。如果请求直接打到 task-service 同步处理服务瞬间被拖垮。RabbitMQ 在这里的作用就是削峰任务提交后先写入数据库状态设为 PENDING然后发送一条消息到 merge.task.queue消费者按自己的处理能力一个一个拿任务。消费者处理前必须先做幂等检查。消息队列虽然能做到基本不丢消息但无法保证不重复消费端要有对应的保护机制。我的做法是处理前先查任务状态只有 PENDING 状态才允许执行执行前再通过 CAS 把状态改成 RUNNING改不成功说明任务已经被别的消费者处理了直接丢弃这条消息。任务状态机定义成 PENDING → RUNNING → SUCCESS / FAILED外加一个 CANCELLED。所有状态变更走条件更新public boolean casToRunning(Long taskId) { return taskMapper.casStatus(taskId, TaskStatus.PENDING, TaskStatus.RUNNING) 1; }SQL 里带上当前状态条件这样并发和重试都不会把状态打乱。消费失败要有重试机制。我用的是手动 ACK 加重试计数业务处理抛异常时不确认消息让队列重新投递重试超过 3 次消息转到死信队列由人工或者定时任务介入。简单的失败场景比如网络抖动造成临时文件上传失败第二次重试往往就成功了复杂场景再走死信兜底。3.5 Redis 的两个角色缓存与锁Redis 在这个项目里有两个完全不同的角色。第一个角色是缓存任务进度。任务执行过程中的进度数据不适合高频写数据库因为每秒可能更新好几次。我的做法是任务开始后把 step、progress、message 实时写入 Redis 的 hash 结构客户端轮询进度接口时优先读缓存任务结束时才把最终状态和进度一次性落库数据库的写压力瞬间降下来。第二个角色是分布式锁。防止同一个缓存包被重复提交合并典型的场景是用户手抖点了两次“提交”或者客户端弱网时重试导致请求发了多遍。锁的 key 用文件包的唯一标识生成比如 MD5RLock lock redissonClient.getLock(merge:submit: fileMd5); if (!lock.tryLock(3, 10, TimeUnit.SECONDS)) { throw new BizException(提交过于频繁请稍后重试); }Redisson 的锁默认带看门狗续期逻辑持有锁的线程没执行完会自动续期不用担心任务超过锁的过期时间导致锁提前释放。我在提交场景里故意把锁的持有范围控制在“创建任务 发消息”这个瞬间而不是锁住整个合并任务这样锁的占用时间极短不会因为业务变慢而拖垮后续请求。4. 核心代码片段可直接抄作业4.1 m4s 偏移探测方法前面的代码已经给了核心实现。这里再补充一个细节探测偏移时最好只读前 16 字节不要读整个文件因为缓存目录里可能躺着几百 MB 的大文件全部读入内存没有意义。用 try-with-resources 确保流关闭方法返回 long 类型因为偏移量可能超过 int 范围。探测结果要做缓存。同一个缓存包里的 m4s 文件偏移量通常是固定的可以在解析目录时把每个文件的偏移量算好放进内存 Map避免后面合并阶段重复读取文件头。这个优化对多P视频效果明显几十个文件循环读取文件头也是有开销的。4.2 ffmpeg 进程调用封装调用 ffmpeg 一定要用 ProcessBuilder 传 List 参数不要用 String 拼接命令ListString command new ArrayList(); command.add(ffmpeg); command.add(-y); command.add(-i); command.add(videoInput.toString()); command.add(-i); command.add(audioInput.toString()); command.add(-map); command.add(0:v:0); command.add(-map); command.add(1:a:0); command.add(-c); command.add(copy); command.add(-movflags); command.add(faststart); command.add(output.toString()); ProcessBuilder pb new ProcessBuilder(command); pb.redirectErrorStream(true); Process process pb.start(); if (!process.waitFor(5, TimeUnit.MINUTES)) { process.destroyForcibly(); throw new MergeTimeoutException(ffmpeg 执行超过 5 分钟); }进程启动后要立刻把标准错误合并到标准输出否则错误信息积压在缓冲区进程可能因为缓冲区写满而阻塞。waitFor 带超时更是必备防止 ffmpeg 被异常文件挂到天荒地老。还有一个细节ffmpeg 的 -y 参数必须加上否则输出文件已存在时会交互式询问是否覆盖而 Process 没有交互输入进程就卡死了。这个坑我踩过合并任务第二次运行时莫名其妙停住查了很久才发现是覆盖确认。4.3 任务状态机与条件更新状态机字段定义成枚举还是整型都可以我偏好枚举加数据库整型映射。关键是所有状态变更必须走条件更新而不是先查再改。先查再改在高并发下是典型的竞态条件两个线程同时读到 PENDING然后同时改成 RUNNING业务逻辑就会重复执行。Update(UPDATE merge_task SET status #{target}, update_time NOW() WHERE id #{taskId} AND status #{expect}) int casStatus(Param(taskId) Long taskId, Param(expect) Integer expect, Param(target) Integer target);执行任务前用 casToRunning任务完成时用 casToSuccess任一步失败就抛出异常走重试逻辑。4.4 分布式锁与提交幂等提交接口的幂等不能只靠数据库唯一索引因为任务还没创建出来数据库根本挡不住并发。用 Redisson 分布式锁保护提交瞬间是最直接的方案锁 key 设计成合并前文件包的 MD5 值确保同一份文件包只能创建一个任务。锁的粒度要合适太小挡不住重试太大把不相关的合并请求也串起来了。我的 key 只包含文件包 MD5不包含用户 ID同一文件包无论谁提交都只合并一次这符合业务预期。如果想把锁粒度和用户隔离可以拼上 userId但那样做会允许同一个包被多个用户各合并一次需要根据实际产品逻辑权衡。5. 部署落地与排查实录5.1 Docker Compose 快速拉起整套环境微服务最大的部署痛点就是依赖多。MySQL、Redis、RabbitMQ、Nacos、MinIO再加上四个业务服务手工部署一次能折腾小半天。Docker Compose 在这里的价值不是炫技而是把整套环境定义成代码本地、测试、生产三个环境用同一套编排文件彻底消灭“在我电脑上是好的”这类问题。编排时注意依赖顺序。业务服务不能光靠 depends_on 硬等中间件启动因为容器启动不等于服务就绪。我用 healthcheck 配合 condition: service_healthy确保 RabbitMQ 真正能接受连接之后再启动 task-serviceservices: task-service: image: registry/example/task-service:latest depends_on: rabbitmq: condition: service_healthy nacos: condition: service_healthy environment: SPRING_PROFILES_ACTIVE: prod NACOS_ADDR: nacos:8848MinIO 默认账号密码要改掉并且备份策略提前考虑。合并产物是用户数据万一 bucket 被误删整个服务就失去意义了。5.2 常见问题合并后音画不同步这是整个项目里被反馈最多的一个问题现象是画面和声音错位有的快有的慢。大部分情况是因为音视频轨的时间基准不一致。DASH 分片本来就是为流媒体设计的音频轨和视频轨的时间戳起始点可能不同直接合并时如果不做处理就会错位。我的解决方法是在第一步转封装时给两个轨都加 -avoid_negative_ts make_zero把负时间戳归零再进入合并阶段。如果个别文件仍然不同步额外试 -itsoffset 微调音频延迟。这个参数是手动的需要根据实际偏差反复尝试我一般建议用户先重新下载缓存不要只靠命令硬调。因为遇到这种情况多半是原本缓存的文件就有问题工具能覆盖大部分正常场景损坏的文件没有义务硬修。5.3 常见问题大文件上传超时用户传一个几 GB 的缓存包小文件测试全都正常大文件总是传一半断掉。排查下来有三层问题网关超时、Web 容器请求体限制、中间代理的连接超时。Nginx 的 client_max_body_size 默认只有 1MB不调大必挂。网关层的 Spring Cloud Gateway 也要调整 httpclient 的 response-timeout。但最根本的解法是分片上传客户端把文件切成 5MB 一片逐片上传服务端接收完成后做合并校验。这样单请求的数据量很小任何一层超时都不容易触发还能实现断点续传。服务端合并分片时要严格校验分片序号和大小防止客户端漏传或者乱序。我用的是分片上传的标准模式先初始化上传会话拿到 uploadId再逐片上传最后调用合并接口。上传中和合并中都用 Redis 缓存已收到的分片序号支持断点续传弱网环境下比整包上传可靠得多。5.4 常见问题微服务调用超时与链路排查task-service 调 file-service 获取文件元数据时偶发 SocketTimeout。这种问题在单体应用里几乎不存在拆成微服务后就变成了家常便饭。排查第一步永远不是看代码而是先确认服务实例是否健康。去 Nacos 控制台看 file-service 实例是否在线再直接 curl 实例的健康检查接口判断是网络问题还是服务本身慢。第二步看 GC 日志如果老年代满导致频繁 Full GC响应时间会突然飙升表现就是偶发超时。第三步看 Docker 网络容器间通信走的是自定义 bridge 网络DNS 解析配置不对也会造成超时。最关键的还是日志链路。我在网关里用 Filter 生成 traceId写入 MDC再通过 OpenFeign 的 RequestInterceptor 把 traceId 放进 Header下游服务从 Header 里取出来继续传。这样一条合并请求从提交到完成所有日志都带着同一个 ID随便去日志系统搜一下就能看到每一步耗时排查效率提升非常明显。5.5 常见问题任务积压与子进程内存控制合并任务积压的根因大概率不是队列容量不够而是消费者处理能力不行或者子进程失控。一个非常隐蔽的问题是 Java 服务内存看着不高但整机内存被吃光了。原因在于 ffmpeg 是外部进程它占的内存不属于 JVM。如果并发控制没做好几十个 ffmpeg 同时跑起来16G 内存的机器也能瞬间被打爆。我加 Semaphore 限制本机并发之后这个问题彻底消失。观察手段ps aux | grep ffmpeg 看子进程数量top 看整机负载。如果同时存在几十个 ffmpeg 进程说明 Semaphore 没生效或者代码路径漏了并发控制。这时候优先检查是不是所有调用 ffmpeg 的地方都走了同一个并发控制入口而不是又加一个信号量。6. 复盘清单6.1 真正用上的知识清单这个项目过了一遍微服务的方方面面我给出一份“真正落过地”的知识点对照和网上那些面经里的微服务八股有很大区别知识点在这个项目里的实际落点服务注册与发现Nacos实例上下线自动摘除配置中心ffmpeg 路径、队列名、并发上限动态调整网关路由与统一鉴权Spring Cloud Gateway JWT下游无感服务间调用OpenFeign超时配置和日志透传异步削峰RabbitMQ 合并任务队列消息幂等与重试状态机 CAS 手动 ACK 死信队列Redis 缓存与分布式锁进度缓存、提交幂等防重对象存储MinIO 保存合并产物日志链路traceId 跨服务透传容器化Docker Compose 本地与生产一致没用到的东西也明确一下分布式事务用不上因为业务模型不需要跨库强一致分布式调度平台用不上因为没有定时任务需求服务网格更是纯负担。如果你正在规划微服务先问自己的业务有没有“不同服务扩容维度不同”“负载模型冲突明显”这类真实拆分的理由再动手拆。6.2 如果重新做一次我会改掉哪些设计第一个改变一开始就把缓存版本解析做成策略接口。不同版本的 B站客户端缓存结构差异极大旧 blv 分段和新 m4s 是两套解析逻辑清晰度目录编号的含义也不同。我最初为了赶进度把解析逻辑写成了一个庞大的 switch-case后面每遇到一个新样本就加一个分支代码越来越难维护。重做的话我会把解析器做成接口每类版本一个实现类通过工厂方法加载新增版本只需新增类不动老逻辑。第二个改变合并任务改成 worker 池模式。现在每个任务临时创建一个线程去执行虽然用信号量控制住了并发但线程创建销毁的开销在高峰期还是有一定浪费。改成固定数量的 worker 常驻线程池任务从队列进来由 worker 统一调度对机器资源的利用率会更高。第三个改变接口文档从第一天就自动生成。服务拆分成四个之后Web 端、Windows 端、macOS 端分别对接文档不同步会消耗掉大量沟通时间。用 OpenAPI 规范管理接口定义自动生成客户端 SDK新接入端可以直接拿现成的代码用而不是对着文档手写请求体。6.3 经验收尾这类工具叫“神器”其实背后没有多少黑科技真正值钱的是把版本差异、文件偏移、任务状态这些脏活处理干净。微服务也差不多拆不是目的让每个服务都能被独立扩展、独立维护才是目的。回到合并这件事我现在手机里缓存了几十集视频想导出时直接打包上传到服务端几分钟后收到一条下载链接整个过程不需要装额外软件不用手动拖动文件更不用处理那些格式问题。作为一个后端工具能做到这一步我觉得就是它存在的意义了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑