恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
eladmin微服务改造实战:从单体拆分到分布式部署全记录
首页
资讯中心
/
eladmin微服务改造实战:从单体拆分到分布式部署全记录
eladmin微服务改造实战:从单体拆分到分布式部署全记录
发布时间:2026/9/28 13:02:27
eladmin这个项目社区里用的人不少Spring Boot 2.x Vue 的单体后台脚手架代码结构清晰RBAC权限模型也够用拿来接私活或者做公司内部后台非常顺手。但项目一旦上了量比如用户量上来了、业务部门多了单体部署的瓶颈就藏不住了。我这段时间正好把我们团队基于 eladmin 做的后台管理系统做了一轮微服务改造顺带扩展了日志、文件、定时任务这几个模块。这篇文章就把整个改造过程、拆分思路、踩坑记录原原本本写出来给同样打算把 eladmin 改造成微服务的朋友做个参考。先说清楚一件事微服务改造不是把大单体拆成几个小单体就算完。拆分只是第一步后面跟着的是注册中心、网关、认证鉴权、远程调用、分布式事务、配置管理、链路追踪这一整套分布式基础设施。这篇文章不打算写成那种从零教学的手册而是围绕 eladmin 这个具体项目讲清楚每步怎么决策、为什么这么选、落地时要注意什么最后附上联调排错的实战经验。如果你正在纠结要不要拆、怎么拆或者拆到一半卡住了这篇应该能帮上忙。1. 改造之前先想清楚这3个问题再动手1.1 eladmin 单体架构的真实痛点在哪eladmin 这个项目我在用它的时候最大的感受是“好用但脆”。好用是因为开箱即用System、Logging、Tools 这些模块划分得很清晰RBAC 权限模型开箱即用配合 MyBatis-Plus 做 CRUD 很顺手。脆是因为部署形态只有一种一个 Spring Boot 应用打成 jar 包扔到一台服务器上跑。当你的业务规模变大这套形态会出几个很实际的问题。第一所有功能挤在一个进程里线上接口偶发卡顿你很难定位到底是哪个模块出了问题。第二发布成本高只是改了一个菜单管理的小Bug也得整个应用重启一遍所有在线用户跟着受影响。第三资源没法独立扩缩容系统管理模块占了大量的内存和 CPU但也只能眼睁睁看着它和消息推送模块挤在一起抢资源。还有一个容易被忽略的痛点eladmin 的代码结构是标准的“包分层”不是模块化——controller、service、mapper 按业务包堆在一起。表面看着整洁但不同业务之间其实没有硬隔离一个 util 类到处引用一个 service 互相注入。这种代码做微服务拆分的时候工作量不小。1.2 拆成微服务到底解决了什么拆分之后上面的痛点确实解决了不少。拿我们自己的例子来说改造后系统管理、日志、文件上传、定时任务被拆成了四个独立服务。日常使用里存在感最强的是文件服务——之前所有文件上传都走主应用的 Tomcat上传高峰期整个应用响应都变慢拆分出去之后即使文件上传再密集系统管理的接口也完全不受影响。这就是所谓的故障隔离和独立扩缩容。开发效率也有变化。之前五个人同时在一个仓库里改代码合并冲突几乎是每天的固定节目。拆完服务之后各个组负责各自的服务仓库后端联调暴露的互相干扰明显少了发布流程也变成了各自服务独立发版不用等着一锅炖。但说句实话微服务不是免费的午餐。它给团队带来了几个新负担分布式事务、跨库查询、远程调用链路变长带来的排查复杂度、还有运维层面的多实例部署。如果你的项目一直很小拆了之后反而会拖慢效率。这道选择题没有标准答案只有适不适合。1.3 什么样的团队和项目适合动这个手我在社区里见过很多被微服务概念“种草”的朋友一上来就想把项目拆了其实挺危险的。结合我自己的经验我认为适合动这个改造至少要满足下面任意两条团队在 4-6 人以上并且多人经常在同一模块上协同开发访问量有明显的波峰波谷或者对某个子系统的稳定性有特殊要求比如文件上传、消息推送业务方对新功能的上线速度要求很快希望按模块独立发版你们已经开始出现“单机扛不住但又不知道横向扩容该加谁”的情况反过来如果你只是一个人维护、日活几千、部署一台 2C4G 的服务器就能跑得很稳的场面那真没必要微服务化。这时候用单体会简单很多。这条判断可能比任何技术选型都重要——很多失败项目不是死在技术上而是死在需求不明确就上微服务。2. 拆分方案eladmin 怎么拆才不踩坑2.1 服务清单与边界划分拆分方案是整个改造的地基。我见过不少团队上来先搭 Nacos、Gateway结果服务边界没想清楚一边拆一边返工。eladmin 拆分第一步不是写代码而是把“业务边界”划出来。我最终采用的方案是把 eladmin 原单体按职责拆成 5 个服务另外配了 3 个基础设施组件具体如下表所示服务名原模块核心职责关键技术点eladmin-systemsystem 模块用户、角色、菜单、部门、字典Spring Security RBAC存量核心eladmin-auth登录/鉴权逻辑登录接口、签发 JWT、刷新 Token独立鉴权服务减少 System 压力eladmin-filetools 中的文件上传部分文件上传、下载、预览支持本地存储 MinIO 兼容 S3eladmin-loglogging 模块操作日志、登录日志、异常日志日志异步落库支持聚合查询eladmin-tasktools 中的定时任务Quartz 任务管理、分布式调度多实例加锁防重Nacos基础设施注册中心 配置中心服务发现、配置统一管理Spring Cloud Gateway基础设施统一入口、路由转发、跨域、鉴权所有请求的第一站Redis基础设施缓存、Token 黑名单、分布式锁幂等、防重复提交需要特别提醒的是我没有把 eladmin 原本的 system 模块直接劈成用户服务和权限服务这是因为 eladmin 的权限模型用户-角色-菜单耦合很深硬拆会导致跨服务 join 查询蔓延。把认证逻辑抽出去、核心权限留在 system 里是维持演进和改造进度平衡的务实之道。这个边界划分方案理论上也可以直接套到若依RuoYi这类相似脚手架的改造里因为它们大部分模块划分思路同源。2.2 共享库还是分库的取舍这是拆分过程中一定会被问到的服务拆了数据库拆不拆实话实说一口作气分库在初期非常不推荐。eladmin 所有表都在一个数据库里而且表之间有大量的外键关联查询——sys_user 关联 sys_rolesys_role 关联 sys_menu日志表还要引用用户表的用户名。如果强行分库这些关联查询百分之百要改成跨服务调用改造量直接翻倍。我们的策略是代码逻辑层面先完全隔离数据库物理层面暂时共享。每个服务拥有自己独立的表前缀和管理边界服务之间禁止直接读取彼此的表所有跨服务数据获取必须走 Feign 接口。比如日志服务需要查用户名就通过 Feign 调用 system 服务的接口而不是直连库。这个做法在数据库层面留了一个“缓拆”的口子等将来真正需要分库的时候由于边界已经清晰了把表搬走并不困难。如果你问我的建议我会说两个月内能上线的业务共享库更稳半年以上的长期项目可以提前规划分库但也不要一步到位。2.3 公共模块抽取的正确姿势eladmin 原代码里的 common 包里堆积了很多东西实体、DTO、工具类、统一返回结构 Result、异常处理、Security 工具类。改造微服务的时候就面临一个问题这些代码是抽到一个 common 包给所有服务引用还是按服务拆分各自维护答案是避免大而全的 common 包。这是我踩过的坑。刚开始我图省事把原来的 eladmin-common 原封不动打成公共依赖让所有服务引用结果没过多久就发现改一个公共包里的方法触发所有服务重新编译打包版本管理乱成一团。后来我重新整理把公共代码分成了三层eladmin-common-base只放最基础的、几乎不会变的。比如统一返回 Result、全局异常枚举、基础工具类DateUtils、StringUtils这些无论怎么做版本演进都不太会变。eladmin-common-security放认证鉴权相关主要是 JWT 工具类、SecurityContextHolder 的解析逻辑只被 auth、system 服务引用。eladmin-common-api放 Feign 接口定义和 DTO数据传输对象作为服务间通信的“契约”。例如 System 服务的用户查询接口就定义在这一层日志服务通过引入 common-api 完成调用。这样切分之后服务间的依赖关系非常清晰谁在改什么一眼就能看出来。如果你不用 eladmin用别的脚手架改造这套“三明治”切法也完全能参考。3. 核心改造实录网关、认证与服务的落地细节3.1 Nacos 注册中心与配置中心的落地改造的第一步是把服务跑起来并且能互相发现。这个环节我用的是 Spring Cloud Alibaba 全家桶核心依赖是 Nacos。选 Nacos 而不是 Eureka ConfigServer 的组合原因很简单注册中心和配置中心二合一社区生态好而且对 Spring Boot 2.x 的兼容性也比较成熟。Nacos 的接入有几个细节值得说。首先要选对版本。Nacos 服务端我用的是 2.x客户端在 pom 里的依赖管理走的是spring-cloud-alibaba-dependencies版本号用 2021.x 这个分支的前提是 Spring Boot 2.x 和 Spring Cloud 2021.x这三个版本必须配套否则 Nacos 客户端启动后会疯狂重试连接还不同步。配置文件这一块我的经验是尽量把公共配置放到 Nacos 的配置中心里统一管理。比如各个服务都需要的数据源配置、Redis 配置、Feign 连接池参数放在 Nacos 的一个共享配置文件中各服务独有的配置端口、服务名留在本地的 bootstrap.yml 里。下面是一个典型的 bootstrap.ymlspring: application: name: eladmin-system cloud: nacos: server-addr: 127.0.0.1:8848 username: nacos password: nacos config: file-extension: yaml namespace: dev shared-configs: ->spring: cloud: gateway: routes: - id: eladmin-auth uri: lb://eladmin-auth predicates: - Path/auth/** - id: eladmin-system uri: lb://eladmin-system predicates: - Path/system/** - id: eladmin-file uri: lb://eladmin-file predicates: - Path/file/** - id: eladmin-log uri: lb://eladmin-log predicates: - Path/log/**真正的关键点在 JWT 鉴权过滤器。eladmin 原本的鉴权思路是客户端每次请求都带上 JWTSpring Security 过滤器链里解析 Token 并把用户 ID 放到 SecurityContext。微服务改造后我改成了“网关统一校验下游服务信任网关”——JWT 的解析统一收敛到网关校验通过之后网关把用户 ID、用户名、租户编码这些信息通过请求头透传给下游服务。这样做有几个明显的好处下游服务不用再依赖 security 相关的公共包省掉了每个服务都维护一套 JWT 密钥的风险而且鉴权逻辑变更时只要改网关一个服务。实现上网关过滤器里解析完 Token用ServerHttpRequest.mutate()加上X-User-ID、X-User-Name请求头然后继续向下游转发。要在白名单里的路径比如登录接口、获取验证码放行不放行的一律校验。这个模式还有个额外收益下游服务拿不到用户 ID 之外的敏感信息减少了 Token 泄露的暴露面。在接口粒度比较粗的权限控制上完全可以配合网关再做一个简单的继续拦截。3.3 认证服务与用户信息服务的拆分协作登录认证这块eladmin 原先的逻辑都在 AuthenticationController 里改造后单独拆成了一个 eladmin-auth 服务。这个服务只干一件事接收登录请求、校验用户名密码、生成 JWT、维护 Token 的刷新。登录接口的流程是这样的auth 服务接收用户名密码调用 system 服务的 Feign 接口验证用户信息并且校验状态认证通过后auth 服务查询用户的角色编码和权限列表把这些信息封装进 JWT 的 Claims 里返回 Token。整个过程中auth 服务不需要直接碰数据库。这个设计你们可以看到有个有趣的取舍权限列表我选择装入 JWT而不是每次请求都去查库。会话无状态的同时带来的问题是权限变更不能立即生效——某个用户角色改了老 Token 在过期前依然持有旧权限。这个问题在后台管理系统中其实是能接受的Token 有效期设短一点比如 2 小时就够了。如果一定要做到实时踢人或权限即时生效就需要引入 Redis 维护 Token 白名单网关每次校验 Token 时去 Redis 查一下状态这也是个成熟方案只是会增加一次网络开销。3.4 分布式事务与数据一致性务实比理想重要微服务改造里最绕不开的话题就是分布式事务。eladmin 原来的代码里事务基本是单库本地事务拆了服务之后原来在一个事务里完成的逻辑很可能跨多个服务。我的建议很直接能不引入分布式事务框架就尽量不要引。Seata 听起来很美AT 模式用起来也方便但上了 Seata 之后服务间的耦合度、运维复杂度、以及业务事务的隔离级别控制都会变得非常麻烦。务实做法一般是这几种。第一种通过接口重试 消息补偿达到最终一致性。比如新增用户之后需要给系统管理员发通知这个发通知操作就不是强一致需求——主流程保存用户成功后发一条 MQ 消息消费端去处理发通知失败了重试几次还不行就进死信队列人工处理。第二种把多个本地写操作通过一次 Feign 调用聚合到同一个服务内部尽量保持本地事务。比如创建订单同时扣库存如果订单和库存拆成了两个服务那就没办法本地事务处理。这种强一致场景除非业务能容忍短时的不一致否则还是建议把这两个模块放在同一个服务里。一句话总结就是微服务之间少谈事务多谈流程少做并行同步多做异步补偿。如果你的业务里强一致场景确实特别多那就要考虑是不是服务边界拆错了。4. 扩展功能改造之后顺手补齐的4个能力4.1 操作日志异步化MQ 解耦和归档eladmin 原来的操作日志是同步写的Controller 层通过 AOP 切面记录日志然后直接 insert 到数据库。量大之后日志表成了性能瓶颈尤其是有开发同事喜欢写批量导出操作一次操作导出几万行日志记录同步等待导致接口响应变慢。微服务拆分之后日志服务变成独立的 eladmin-log。操作日志通过 AOP 在业务服务里捕获捕获完成后把日志数据序列化发送到 Kafka。eladmin-log 服务消费 Kafka 消息异步落库。这个改造上线后再做导出操作响应时间明显提升。这里有个小细节日志消息的 payload 里必须带上 traceId这样在排查日志时才能把一次请求跨服务的完整链路串起来没有 traceId 的日志在微服务环境几乎是废的。另外日志量上来之后我建议对日志表做按月分表设计并且加一条定时任务做归档清理。这条可以在第 5 节的定时任务服务里一起支撑。4.2 文件服务独立化从本地存储到 MinIO 的切换eladmin 的文件上传原先是写在 tools 模块里的支持本地存储和七牛云。我的改造方案是直接独立一个 file 服务而且把存储后端做成了可替换的接口。一开始我用的是纯本地磁盘存储后来发现我们这台服务器磁盘太小了才切成 MinIO 对象存储。file 服务里抽象了一个 StorageStrategy 接口本地存储和 MinIO 各一个实现通过配置文件决定激活哪个。切存储完全不影响业务服务。这个抽象看起来多花了一点时间但实际价值很大。后续团队讨论要不要再切到阿里云 OSS对我来说只是多写一个实现类的事。接口设计也很简单public interface StorageStrategy { String upload(MultipartFile file, String bucket); void delete(String filePath); InputStream download(String filePath); }文件上传接口需要注意的地方是网关层要配置稍大一点的请求体限制默认 1M 肯定不够用需要调大同时文件服务最好再单独设一个 Tomcat 的 maxSwallowSize避免客户端上传一半断掉引发异常。4.3 定时任务服务与分布式锁eladmin 内置了 Quartz管理页面做任务调度很便捷。但微服务化之后如果 task 服务部署了多个实例同一个任务会被多个实例同时触发——这是典型的重复执行问题。我的处理方案有两步。第一步把定时任务代码抽到独立的 eladmin-task 服务Quartz 的调度器还是它负责。第二步给每个任务的方法加上 Redis 分布式锁。锁的 key 用任务名 执行时刻比如精确到分钟value 用实例 IP过期时间 30 秒。拿到锁的任务才执行没拿到锁的直接跳过。这样即使 task 服务水平扩张到三个节点任务也不会重复执行。这块注意一个点Quartz 原生集群模式配置比较复杂而且会频繁查数据库抢占调度锁本身就是瓶颈。用 Redis 锁之后Quartz 集群模式就不需要开了每个节点都是独立的本地调度再靠业务代码锁保证唯一性反而简单可靠。4.4 预留消息推送与 API 对接扩展位扩展这一步我也顺手把原先想做的微信公众号模板消息推送加入了规划。eladmin 的 tools 模块里本来有部分对接微信公众号的基础代码拆分微服务后一般会归到 message 服务或者 file 服务旁边的 notify 服务。微信公众号测试号服务 API 的对接思路其实不复杂核心就是两步拿 access_token然后调用模板消息接口。这个功能在单体时代也能做但微服务拆出来之后消息服务的负载不会再影响主后台推送高峰期也稳定很多。这类偏外部的 API 对接我最大的经验是不要在业务代码里直接硬编码第三方配置放到 Nacos 配置中心统一管联调环境和线上环境直接切换配置就行。5. 联调与启动多服务不是跑起来那么简单5.1 本地 IDEA 多服务启动的正确顺序本地开发用的 IDEA同时启动多个微服务是一个很容易让人崩溃的事。启动顺序不对服务频繁注册失败、端口冲突日志刷屏却也排查不到问题。我的经验是先做“基建三件套”先起 Nacos再起 Redis最后确认 MySQL 连接可用。这三个没问题了再按依赖顺序启动服务——eladmin-system 先起因为它是核心服务其他服务启动过程中可能会调用它的接口然后是 eladmin-auth登录接口要依赖 system 接口、eladmin-file、eladmin-log、eladmin-task。最后再启动网关。网关放最后是有讲究的。所有业务服务都注册到 Nacos 之后网关重启一次就能正确获取完整服务列表避免路由报 503。IDEA 里配置多服务启动可以直接用 Run Dashboard把相关启动类都加到面板里一键顺序启动。但用之前一定要检查一下 VM 参数多服务一起跑默认堆内存很容易爆建议每个服务单独分配-Xms256m -Xmx512m。5.2 联调中的常见报错与排查速查联调阶段有几个报错几乎是每个人都碰过的我整理一个速查表按优先级排查报错现象常见原因排查方法网关转发返回 503目标服务没注册到 Nacos先看 Nacos 服务列表服务名是否一致Feign 调用报 No provider服务消费者没找到服务提供者多半是服务名写错了或者提供实例状态异常请求到下游 401JWT 校验失败或用户信息头没传递排查网关过滤器 Token 解析与透传逻辑接口超时 Read timed outFeign 默认超时时间太短Ribbon 或 Feign 客户端单独配置超时时间Nacos 控制台服务列表显示下线慢服务实例没优雅下线配置 spring.cloud.nacos.discovery.register-enabled 和 shutdown 钩子数据库连接池占满多服务共享库后连接数不够调整 HikariCP 最大连接数上限控制在数据库承载范围Feign 超时这个我单独说下。默认的 Feign 超时很短跨服务调用加上网关一跳很容易就超时了。在配置文件里可以这样调大feign: client: config: default: connectTimeout: 5000 readTimeout: 10000不过超时时间不是越大越好时间设太长会让调用等待阻塞线程一般连接超时 3-5 秒、读取超时 10 秒比较常见。像网关转发也可用类似配置方式调整。5.3 我强烈建议一开始就做的三件事第一件事全链路 TraceId 要尽早接入。微服务环境下排查问题最痛苦的就是日志分散在各服务统一日志收集之前先通过日志框架的 MDC 机制生成一个traceId在网关注入后传递到所有下游服务日志打印时带上它。之后不管日志放 Elasticsearch 还是用 Sleuth Zipkin 都方便得多。第二件事统一返回结构和异常处理器从第一天就定死。eladmin 原本就有 Result 结构和全局异常处理但微服务改造后 Feign 调用失败的异常和 HTTP 状态码之间的转换逻辑要统一否则下游接受到的服务异常格式不一致前端处理起来会非常痛苦。第三件事服务拆分后及时补上接口文档。以前单体时代还能通过看代码接口辅助理解微服务环境下服务多了每个服务的调用关系不写清楚后面接手的人会崩溃。我的习惯是直接引入 Knife4j 聚合所有服务的 OpenAPI 文档网关统一暴露文档入口这样联调和排错都方便很多。6. 改造之后我的几点真实体会这次 eladmin 微服务改造从评估、拆分到上线前后大概花了两个多月。上线后的收益是实打实的文件服务独立后主后台接口的稳定性和响应表现都好了不少日志异步化也解决了高峰期 MySQL 连接数被打满的问题。但说心里话微服务改造确实不是一个轻松的项目它带来的复杂度和运维负担是实实在在的。我个人最大的体会是微服务改造能不能成功根本不取决于你会不会用 Nacos 或 Gateway而取决于你对业务边界的理解和把握。代码能拆表能拆但业务逻辑剪不断理还乱的话后面每一步都会很痛苦。最后再分享一个小技巧。改造过程中每次拆出一个服务都保留一份单体版本在测试环境做同样的回归测试保证数据一致性和权限逻辑完全对得上。这虽然慢一点但能帮你把每次拆分引入的变量控制到最小。微服务的魅力在于独立但改造的过程中最需要的反而是全局的稳。