中小团队需要一个资源微服务

我这边是把所有资源密集型的操作从核心服务里拆了出来,单独起了一个微服务。做这个决定基于两个考虑:一个是系统稳定性,一个是微服务定位。

从稳定性的视角看

核心服务承载的是系统最关键的业务流程,审批、数据查询、业务流转这些。一旦核心服务不可用,整个系统就瘫了。

把资源密集型操作放在核心服务里,风险集中在三个方面。

线程池被占满。 一个PDF解析任务跑两三分钟,Tomcat默认的工作线程数是200。看起来一个任务只占了一个线程,影响不大。问题在于这个线程被锁住了两三分钟,相当于正常请求的几千倍。5个这样的任务并发来,5个线程被占住几分钟,期间如果正常请求量大一点,剩余的195个线程可能不够用,业务接口开始排队。

内存被吃光。 用PDFBox解析一个100MB的PDF,在内存里要展开好几倍。几个并发一来,堆内存直接打满,频繁Full GC,接口响应变得不稳定。严重的情况下直接OOM,核心服务整个进程挂掉,所有业务全停。

CPU被打满。 文本提取、规则匹配、图片处理这些操作是CPU密集型的。跑起来CPU占用率拉高,核心服务的正常请求响应时间跟着上升。

核心服务挂了,影响的是所有业务。 不是说PDF解析失败了没关系,而是这个PDF解析操作可能让你的审批接口、查询接口、业务流转接口全部不可用。一个辅助性的功能,把核心业务拉下水了。

可能有人会想:用异步线程处理不就行了?异步线程解决不了这个问题。异步线程仍然跑在同一个JVM里,内存和CPU还是同一份资源。PDF解析吃掉的内存不会因为你放到异步线程就消失了。OOM的时候整个进程一起挂,核心业务照样全停。

放在网关里也不行。网关是所有流量的入口,比核心服务还脆弱。一个大文件上传把网关线程占住了,所有服务的入口流量都受影响。

拆到独立的资源微服务之后,即使这个服务OOM了,核心服务完全不受影响。最多是某个解析任务失败了,重试一次就行。核心业务的可用性有了保障。

从服务定位的视角看

微服务拆分有个定位问题:每个服务存在的理由是什么?

大多数团队按业务领域拆。订单服务、用户服务、财务服务,每个服务对应一块业务。这个思路没问题,但它只覆盖了一个维度。

还有另一个维度值得考虑:按资源消耗特征拆。

资源微服务的定位是收口所有重IO、重CPU、重内存的操作,跟具体业务无关。它的划分维度不是业务领域,而是资源消耗特征。

看我们项目里实际放在这个服务里的功能:

  • 邮件拉取和附件解析。运营那边的需求,需要通过IMAP协议拉取企业邮箱里的邮件,解析PDF附件。IMAP是长连接,附件解析吃内存
  • PDF文档识别。物业那边的需求,需要对业主账单做OCR识别。PDFBox解析吃CPU和内存,调大模型做识别还有网络等待
  • 大文件上传到第三方系统。财务那边的需求,需要把几百MB的文件通过multipart形式传到对方系统。HTTP连接要保持30分钟
  • 银行回单解析。也是财务的,PDF文本提取加规则匹配加按关键字分页打包,CPU和IO双密集

这些功能分属运营、物业、财务三个不同的业务领域。如果按业务领域拆,它们应该分别待在各自的业务服务里。但它们有一个共同特征:执行时间长、内存占用高、CPU占用高、HTTP连接占用时间长。

把它们按资源特征聚合到一个服务里,每个服务的职责边界就清晰了:核心服务只处理轻量级的业务逻辑,快进快出;资源微服务专门处理重资源操作,允许慢、允许占资源。

代码结构上大致是这样:

Java 复制代码
// 邮件拉取
MailFetchHandler.fetchUnread()

// 银行回单解析:提取文本、按规则分页、打包成ZIP、上传
ReceiptSplitHandler.splitAndPackage()

// 文件上传到第三方系统
ResourceUploader.upload()

// PDF文档识别
DocRecognizeService.recognize()

// 账单OCR识别任务
BillOcrService.createTask()

这些方法各自的业务语义不同,但运行时的资源消耗特征是一样的:耗时长、吃资源多。

拆出来之后的配置差异

拆分之后有个直接的好处:两个服务可以用完全不同的配置,互不干扰。

资源微服务的JVM堆内存可以配到4G甚至8G,因为它要在内存里处理大文件。核心服务2G就够了,配太大反而GC停顿时间长。

Tomcat的超时配置也不一样。资源微服务需要支持500MB文件上传,HTTP连接超时得设到30分钟。核心服务的接口超时设在几秒就行,超过几秒的请求大概率已经有问题了。文件大小限制也是,资源微服务放开到500MB,核心服务保持默认的1MB。

这两套配置放在同一个服务里是矛盾的。你不可能给核心服务设30分钟的HTTP超时,那样一个慢请求会占住线程半小时。你也不可能给核心服务配8G堆内存来应对偶尔出现的大文件解析,平时都是浪费。

独立扩缩容也是一个实际的好处。月底银行回单集中处理的时候,只需要给资源微服务加机器或者加副本,核心服务不用动。

哪些操作应该放到资源微服务

给一个判断标准,遇到新需求的时候可以对照着看:

操作特征 典型场景 放核心服务的风险 建议
执行时间超过10秒 文件上传、大文件解析 占线程,拖慢核心接口 拆到资源服务
内存占用超过100MB PDF解析、Excel大文件导入 OOM风险,核心服务可能挂 拆到资源服务
CPU密集型计算 图片处理、文本提取、规则批量匹配 核心接口响应变慢 拆到资源服务
长连接或长轮询 IMAP邮件拉取、大模型API调用 占连接池资源 拆到资源服务
需要特殊超时配置 大文件上传(需要30分钟超时) 和核心服务的短超时配置矛盾 拆到资源服务
偶发但不可控的负载 第三方回调触发的批量解析 突发负载冲击核心服务 拆到资源服务
毫秒级稳定执行 CRUD、数据校验、状态流转 无风险 留在核心服务

小结

稳定性和定位,看起来是两个独立的理由,实际上指向同一个结论。微服务拆分不只有按业务领域拆这一条路,按资源消耗特征拆也是一个有效的维度。这跟之前聊过的出口入口网关微服务是同一个思路:网关收口所有第三方对接,资源微服务收口所有重资源操作。每个服务有明确的定位,系统就不容易乱。

这里多说一点。服务边界的划分、哪些操作放哪个服务里,这些决策如果你是团队的技术负责人或者架构师,是需要你来定的,不能任由团队成员自由发挥。如果没人管这件事,开发人员遇到一个新需求,最自然的做法就是往手头的业务服务里加代码。一个人加一点,一个人加一点,时间一长核心服务就变成了什么都干的大杂烩,稳定性风险在不知不觉中积累。等到某天出了故障才回头拆,成本比一开始就规划好要大得多。技术负责人应该在项目初期就把这个规矩定下来:资源密集型的操作统一走资源微服务,不往核心服务里放。规矩定得越早,后面越省心。

希望这篇内容可以帮到你。


最近在知乎出了秒杀专栏,感兴趣的可以订阅一下。至于知识星球的,可以搜:

  • 老码头的技术浮生录

它是一个能实际帮你解决难题的星球。有问题的,找知心的Sam哥,支持无限次语音一对一解决你遇到的难题。另外后续我新写的所有对外的付费专栏,在星球内都是免费的,且可以拿到所有源代码。

我的知乎账号:

  • SamDeepThinking
相关推荐
两万五千个小时2 小时前
为什么你的 Agent 读了文件,却好像什么都没读到?
人工智能·程序员·架构
超梦dasgg3 小时前
Spring AI 智能航空助手项目实战
java·人工智能·后端·spring·ai编程
lifewange3 小时前
如何设计一个 RESTful API
后端·http·restful
非优秀程序员3 小时前
智能体的构成--深入探讨Anthropic、OpenAI、Perplexity和LangChain究竟在构建什么。
人工智能·架构·开源
安德鲁20223 小时前
Spring Boot + Undertow 全栈架构深度剖析时序图
后端
码事漫谈3 小时前
AI 正在重塑职场:有人乘风破浪,有人悄然掉队
后端
码点滴3 小时前
从“失忆症“到“数智分身“:Hermes Agent 如何重塑你的 AI 交互体验?
人工智能·架构·prompt·ai编程·hermes
狗哥哥3 小时前
面包屑自动推导的算法设计:从“最短路径匹配”到工程可落地
算法·架构
用户97436970725283 小时前
5分钟搭建企业级实时消息推送系统
后端·websocket