ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

实战:基于SpringBoot+Vue+SpringCloud的智慧食堂微服务系统设计

实战:基于SpringBoot+Vue+SpringCloud的智慧食堂微服务系统设计 1. 项目背景与核心需求分析1.1 机关食堂的痛点与“智慧化”到底要解决什么问题这个项目是我去年带团队给一家机关单位做的智慧食堂后勤管理系统接这个活的时候单位后勤处那边的食堂管理还处在“手工Excel”的阶段每天用餐人数靠各科室报数菜品采购靠厨师经验估算结算窗口还用的是老式刷卡机数据没法汇总。最要命的是机关食堂有严格的用餐时段一到饭点所有人都涌进来经常出现排队半小时、打菜三分钟的情况。所谓的“智慧食堂”不是简单装几台闸机就完事。核心要解决的是三件事第一让职工能提前知道今天吃什么、提前订餐减少现场打菜时间第二让后厨和采购能看到实时订餐数据按需备餐减少浪费第三让财务和后勤领导能看到每一笔消费、每一批食材的去向真正做到账目可追溯。这个系统最终的技术形态采用了SpringBoot Vue SpringCloud的微服务分布式架构而不是传统的单体应用原因后面我会细说。如果你正准备做类似的后勤管理、企业园区或机关单位的进销存/订餐类系统这篇分享会非常有用。我会把需求拆解、技术选型、微服务拆分、关键代码、部署配置以及我踩过的坑全部聊一遍尽量做到你照着思路能复现一个可用的版本。1.2 系统核心功能模块清单先列一下这个系统最终交付的功能清单方便后续对照用户端微信/钉钉内嵌H5 PC Vue后台菜品浏览、按周菜谱预览、在线订餐、取消订餐、个人消费记录、余额充值、取餐码展示。食堂管理端Vue PC管理后台菜品管理、菜谱管理、库存管理、采购计划生成、档口/窗口管理、员工管理、食堂公告管理。后勤财务端订单汇总、营收统计、补贴规则配置、食材成本核算、供应商对账报表。运营与权限基于RBAC的角色权限、操作日志、数据脱敏、多食堂/多组织架构支持。基础能力短信/微信模板消息通知、文件上传菜品图片、视频展示、报表导出、数据大屏。你可能觉得这些模块听起来不复杂但一旦用微服务拆开每个模块之间要处理的数据一致性、接口权限、分布式事务问题就全冒出来了。这里我想强调不要因为项目叫“智慧食堂”就小瞧它机关单位对权限和审计的要求非常严格每个操作都要有日志账户余额变动必须强一致这些都是硬指标。1.3 为什么选择微服务架构而不是单体很多朋友会问一个食堂系统而已单体应用不香吗确实如果只是支撑几百人同时订餐单体完全够用。但当时甲方在招标文件里明确写了“系统须采用微服务分布式架构”而且要求系统要能够横向扩展后续可能会接入其他后勤业务比如车辆管理、访客管理、会议室预约等。如果做成单体后期这些业务全部堆在一个工程里想单独扩容某个模块是不可能的。另外机关单位的IT环境比较特殊开发环境、测试环境、生产环境严格隔离网络策略复杂。用微服务可以把不同模块部署到不同的服务器甚至不同的网段某个服务出问题不会拖垮全部功能。但代价就是架构复杂度指数级上升服务注册发现、配置中心、网关、分布式事务、分布式锁每一块都要搭。我的建议是如果你们团队没做过微服务不要一上来就搞全套。先确定哪些模块必须独立比如订单、支付/余额、库存哪些暂时可以塞进一个基础服务里比如用户、权限、公告后期再拆。我们最终拆成了6个服务这个拆分粒度是踩过坑后调整出来的后面详细讲。2. 技术选型与架构设计2.1 技术栈总览SpringBoot Vue SpringCloud 的组合逻辑直接说结论这套技术栈是当前国内做微服务管理系统最稳妥的组合没有之一。后端以SpringBoot为微服务基础框架每个服务都是一个独立的SpringBoot应用。我们用的SpringBoot 2.7.x没上3.x因为当时SpringCloud Alibaba对3.x的支持还不稳定而且大量老项目依赖的jar在JDK8下是最稳的。这里特别提醒如果你是2025年之后才新开的项目SpringBoot 3.x已经很成熟了可以直接上但一定要先确认SpringCloud、SpringCloud Alibaba、MyBatis-Plus这些中间件的版本兼容矩阵。我们当时吃过高版本的亏后面踩坑部分会提。SpringCloud我们使用的是SpringCloud Alibaba全家桶Nacos作为注册中心和配置中心Sentinel做接口限流和熔断OpenFeign做服务间调用Gateway做统一网关Seata处理分布式事务Redisson做分布式锁。前端就是标准的Vue 3 Vite Element-Plus Pinia Vue Router。可能你会问为什么不选React或者Ant Design原因很简单项目团队里大多数人熟Vue而且Element-Plus的中后台组件对管理系统极其友好表单、表格、弹窗都是一套一套的显著缩短开发周期。这套组合最大的优势是“生态闭环”Nacos负责服务发现Gateway统一入口Sentinel护住流量Seata搞定数据一致前端Vue直接把组件往下堆。对于机关后勤这种“重权限、重报表、重CRUD”的系统效率极高。2.2 微服务拆分与数据库设计我们最终拆了6个微服务服务名职责主要业务表gateway-service网关统一路由、鉴权、转发无auth-service认证授权用户登录、Token签发、权限校验sys_user, sys_role, sys_permissionuser-service员工/职工信息管理组织架构emp_info, dept_infocatering-service核心食堂业务菜品、菜谱、订餐、取餐、评价dish, menu_week, order_info, order_detailinventory-service食材库存、采购、供应商管理ingredients, stock_flow, purchase_order, supplierreport-service统计报表、财务报表、数据大屏接口report_data, finance_summary数据库我们用的是MySQL 8.0每个服务用独立的数据库而不是共用一个库。比如catering_db、inventory_db、auth_db等。这是微服务的一个核心原则数据隔离。如果服务间需要关联查询一律通过OpenFeign调用接口获取而不是直接跨库join。刚开始会觉得很麻烦但好处是后期每个服务可以独立扩展到不同的数据库实例甚至可以针对报表服务挂一个只读从库。这里插一个我们踩过的坑机关单位的数据权限要求按科室隔离。比如某个科长只能看自己科室人员的订餐记录因为涉及员工隐私。所以我在所有核心业务表里都加了dept_id字段查询的时候通过MyBatis-Plus的拦截器自动拼上数据权限条件。这个功能如果你在原生的MP上配置需要自己写拦截器我建议直接用若依微服务Plus的底层改造它的数据权限体系非常成熟。2.3 前端Vue工程结构与路由设计前端工程结构大致如下smart-canteen-web/ ├── src/ │ ├── api/ # 按服务模块封装的请求接口 │ ├── assets/ │ ├── components/ # 通用组件上传、图片预览、分页等 │ ├── layout/ # 后台布局侧边栏、头部、标签页 │ ├── router/ │ │ └── index.js # 路由配置 │ ├── store/ # Pinia状态管理 │ ├── views/ │ │ ├── system/ # 系统管理页面 │ │ ├── canteen/ # 食堂业务页面 │ │ ├── inventory/ # 库存采购页面 │ │ └── report/ # 报表页面 │ ├── utils/ # 请求封装、工具函数 │ ├── App.vue │ └── main.js路由设计这块必须说说动态路由因为不同角色能访问的菜单不一样。我们采用了“前端静态路由后端动态权限菜单”的方式用户登录后从auth-service拉取当前用户的菜单列表然后通过Vue Router的addRoute动态添加。这种实现比把所有路由写在静态表里再根据角色判断要灵活得多尤其适合机关单位经常调整权限的场景。简单示例// 登录后拉取菜单 const menuList await getCurrentUserMenus(); menuList.forEach(menu { const route { path: menu.path, name: menu.name, component: () import(/views/${menu.component}), meta: { title: menu.title, icon: menu.icon } }; router.addRoute(Layout, route); });注意这里有个坑动态路由刷新页面后会丢失因为Pinia和Vue Router的路由表是内存态刷新后要从后端重新拉取。我们解决的办法是在路由守卫router.beforeEach里判断用户信息如果存在就重新拉取菜单并动态添加。2.4 网关、认证与配置中心选型网关我们用的SpringCloud Gateway。一开始想过Zuul但SpringCloud官方已经主推Gateway了它是基于WebFlux的响应式网关性能比Zuul 1.x好得多。主要配置了全局过滤器做JWT的解析和放行把Token中的用户ID解析出来放到请求头里转发给下游服务。流程是这样的前端登录后auth-service签发JWT Token。前端发起请求时在Header带Authorization: Bearer xxx。Gateway的GlobalFilter解析Token校验签名和有效期同时把userId和deptId写入Header例如X-User-Id、X-Dept-Id。下游微服务从Header取用户信息不再重复解析Token。为什么要在网关解析而不是每个服务解析因为如果每个服务都做一遍Token校验代码重复是一方面更重要的是维护成本高换个密钥你得改一堆服务。网关是唯一入口在这里统一处理最合理。配置中心用的Nacos。每个服务的application.yml里只保留最小配置其余全部放到Nacos的配置列表里例如数据库连接串、Redis地址、消息队列Topic、自定义业务参数等。这样改配置不用重新打包部署Nacos支持配置热更新非常方便。但要注意热更新只能对ConfigurationProperties和RefreshScope标注的Bean生效不是所有配置都能热更新这点很多新手容易忽略。3. 核心功能实现与实操细节3.1 菜品管理与图片/视频文件上传MinIOm3u8播放食堂点餐和普通电商不一样用户必须先看到菜品照片才能决定要不要点所以菜品图片是刚需。菜品图片又是典型的“文件上传”场景我直接推荐MinIO没有用阿里云OSS因为机关单位的数据大部分要求内网部署公网OSS不一定合规。MinIO是开源对象存储可以很方便地部署在局域网内兼容S3协议。SpringBoot集成MinIO很简单核心依赖和配置如下dependency groupIdio.minio/groupId artifactIdminio/artifactId version8.4.3/version /dependencyminio: endpoint: http://192.168.10.50:9000 access-key: canteenadmin secret-key: canteen-secret bucket: canteen-image写一个工具类封装上传和获取预签名URLpublic String uploadFile(MultipartFile file) { String fileName UUID.randomUUID().toString() . getExtension(file.getOriginalFilename()); try { minioClient.putObject(PutObjectArgs.builder() .bucket(bucketName) .object(fileName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build()); return fileName; } catch (Exception e) { throw new RuntimeException(上传失败); } }前端展示的时候不要直接拼endpoint桶名文件名作为公开URL。MinIO的桶默认是私有的建议生成有效期的预签名URL返回给前端。之前为了省事把桶设成public结果任何人都能访问而且把MinIO服务的地址暴露到了公网内部审计的时候被批了一顿。正确做法是后端生成URLString url minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(bucketName) .object(fileName) .expiry(3600) .build());还有一个热词是“vue播放m3u8免安装”。这是怎么来的因为食堂菜品除了图片我们还做了每道菜的小视频展示有些甲方喜欢看到厨师做菜的过程。视频我们切流成HLSm3u8但前端如果不做任何处理原生video标签是播不了m3u8的Safari除外。在Chrome下要让video支持m3u8播放最常用的方案是使用hls.js这个库。它不要求额外安装播放器插件纯JS解析m3u8并用Media Source Extensions播放。Vue3中一个简单的封装video refvideoEl controls/video script setup import Hls from hls.js; import { onMounted, ref } from vue; const videoEl ref(null); const src http://your-server/live/dish.m3u8; onMounted(() { if (Hls.isSupported()) { const hls new Hls(); hls.loadSource(src); hls.attachMedia(videoEl.value); } else { videoEl.value.src src; } }); /script这就满足了“免安装”的需求用户打开网页就能看。3.2 订餐与库存扣减的分布式事务先说说一个机关单位食堂订餐的流程用户提交订餐系统要扣减个人账户余额同时要减少一份菜品对应的库存还要生成一条订单记录。这三件事分布在catering-service和inventory-service两个服务里。在单体应用里这个操作可以放在同一个本地事务里一个方法加上Transactional就完事了。但在微服务里跨服务的两个事务没法用本地事务保证。这里我们用了Seata的AT模式也就是自动补偿模式。AT模式的原理是在每个分支事务执行完SQL后Seata会记录数据快照如果后续有分支失败根据快照逆向补偿把之前的修改撤销。引入Seata后服务间的调用只需要在Feign接口加一个注解FeignClient(name inventory-service) public interface InventoryFeignClient { PostMapping(/stock/deduct) boolean deductStock(RequestParam(dishId) Long dishId, RequestParam(count) Integer count); }然后在发起方catering-service的方法上加上GlobalTransactionalGlobalTransactional(rollbackFor Exception.class) Transactional(rollbackFor Exception.class) public void createOrder(CreateOrderDTO dto) { // 1. 生成订单 orderMapper.insert(order); // 2. 调用库存服务扣减库存 inventoryFeignClient.deductStock(dto.getDishId(), 1); // 3. 扣减用户余额在catering-service内 balanceMapper.deductBalance(dto.getUserId(), order.getAmount()); }使用Seata的时候有几个很关键的细节服务必须向Seata Server注册保证每个服务的数据源是由Seata代理过的否则分支事务不会生效。不要在远程调用的方法里开本地事务或者说不要让被调用的方法也套上GlobalTransactional只需要在发起方控制全局事务。Seata AT模式对数据库的隔离级别有要求默认是读已提交但全局事务的隔离级别是“读未提交”的因为要允许中间状态这对库存扣减这种强一致场景是可以接受的。如果你不想引入Seata这么重的框架也可以使用消息队列最终一致性比如用RocketMQ的事务消息。但机关食堂余额扣减和库存扣减之间业务上要求实时一致否则用户刚订完餐库存已经没了后厨也不知道该不该做。所以我们还是选择了强一致方案。3.3 高并发下的分布式锁与缓存设计机关食堂的饭点是“波次并发”中午11:30开放订餐可能同一时间几百上千人同时点击订餐。当时我们压测时发现如果不加锁同一个用户连续点击两次“提交订单”可能出现两条重复订单或者库存扣成负数。处理方式分两层第一层用Redis缓存菜品信息减少对数据库的冲击。第二层用分布式锁控制同一个用户、同一个菜品的订餐并发。Redis缓存很简单我们用的是SpringCache RedisCacheable(cacheNames dish:detail, key #dishId, unless #result null) public Dish getDishById(Long dishId) { return dishMapper.selectById(dishId); }这里要注意缓存击穿问题。如果某道菜特别热门第一次查库后放到缓存里后续请求都走缓存这是没问题的。但如果缓存在瞬间过期恰好又有大量请求进来就会全部打到数据库。解决办法是加“缓存空值”或“逻辑过期”。我们使用的是Caffeine本地缓存Redis两级缓存热点数据会被短期放在本地内存里进一步降低压力。分布式锁我们用的Redisson因为它是Redis官方推荐的Java客户端提供了封装好的RLock可以设置锁的等待时间和自动过期时间避免死锁。典型的订餐逻辑加锁代码RLock lock redissonClient.getLock(lock:order: userId); boolean locked lock.tryLock(3, 5, TimeUnit.SECONDS); if (!locked) { throw new ServiceException(系统繁忙请稍后重试); } try { // 校验是否重复订餐 if (orderService.countTodayByUserId(userId) 1) { throw new ServiceException(今天已经订过餐了); } // 执行下单 } finally { lock.unlock(); }这里有个心得锁的粒度一定要细。一开始我们用了一个全局锁lock:order结果整个订餐服务几乎串行化几百个请求打进来后面的全部超时。后来改成按用户加锁只有同一个用户才会互相等待不同用户之间完全无感。对于库存扣减我们又单独加了按菜品的锁比如lock:dish:42这样多个用户订同一个菜时会排队扣减库存避免库存超卖。3.4 对接微信公众号通知机关单位里很多员工已经习惯用微信公众号接收通知。我们做了微信服务号的模板消息推送员工订餐成功后自动发一条“订餐成功通知”包括取餐时间、取餐窗口和菜品名称。这里提一下“微信公众号测试号服务api对接”这个事。做开发调试时如果你没有正式的微信服务号可以用微信公众平台接口测试账号来练手。测试号能申请到几乎全部接口权限AppID和AppSecret都是测试专用的非常适合联调。对接核心步骤在微信公众平台申请测试号拿到AppID和AppSecret。在auth-service或者单独的通知服务里调用微信官方接口https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappidxxxsecretxxx获取access_token。通过模板消息接口发送通知。这里的坑主要在Token过期时间微信access_token有效期是2小时而且每天调用次数有限。我们做了一个WxTokenService用定时任务每90分钟刷新一次存到Redis里多个服务共享同一个token。模板消息发送的JSON示例{ touser: openid_xxx, template_id: 模板ID, data: { first: { value: 您的订餐已确认, color: #173177 }, keyword1: { value: 红烧排骨套餐 }, keyword2: { value: 2025-05-20 12:00 }, keyword3: { value: 二楼2号窗口 }, remark: { value: 请凭取餐码取餐 } } }记得把员工的微信openid在首次绑定账户时保存下来否则模板消息发送不到人。绑定流程员工在公众号菜单点击“绑定食堂账户”网页授权拿到openid然后输入工号和手机号验证验证通过后把openid关联到用户ID。3.5 打包部署前端后端分离与云端部署构建这块被不少朋友问到过尤其是“vue打包放进springboot中”这种操作。我们的做法是前端和后端完全分离部署Nginx托管前端静态文件后端服务作为独立Java进程跑在服务器上互不干扰。但在内网小规模部署时为了省机器也可以把前端打包后的dist目录放到SpringBoot的resources/static下面让SpringBoot同时承担静态资源服务。不过这只适合单机演示或数据量很小的场景真正的生产环境不建议这样混布。前端构建npm install npm run build构建产物在dist/把里面的文件全部拷到Nginx的html目录Nginx配置示例server { listen 80; server_name canteen.example.local; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }为什么加try_files $uri $uri/ /index.html因为Vue Router用的是history模式刷新一个子路由页面比如/canteen/dish/list如果Nginx不重写到index.html会直接404。这个坑非常常见。后端的每个微服务打包好之后直接执行java -jar auth-service.jar --server.port8081 java -jar user-service.jar --server.port8082 java -jar catering-service.jar --server.port8083 java -jar inventory-service.jar --server.port8084 java -jar report-service.jar --server.port8085如果想用脚本一键启动可以写一个start.sh按顺序启动Nacos、Seata、Redis、MySQL等基础设施再启动各业务服务。机关单位的服务器环境通常比较旧可能会有JDK版本太老的问题。建议用Docker或K8s来隔离运行环境我们最终是用docker-compose把所有中间件和业务服务编排起来各服务通过容器名互相访问具体编排文件就不贴了太长。4. 踩坑记录与排查技巧4.1 SpringBoot版本过高带来的依赖冲突开头我提到SpringBoot版本问题。我们第一版开发时急着用新特性直接选了SpringBoot 3.2.0然后发现SpringCloud Alibaba对应的版本还没适配好Nacos客户端一直报“normally connect”的问题。后来查了官方版本说明才决定整套技术栈降到SpringBoot 2.7.18 SpringCloud 2021.0.9 SpringCloud Alibaba 2021.0.5.0。这个组合对应的Nacos Client是2.2.1Seata是1.6.1。经验是不要盲目追求SpringBoot最新版。微服务是一个组合拳必须保证SpringBoot、SpringCloud、SpringCloud Alibaba、MyBatis-Plus、Redisson这些版本互相兼容。最稳妥的方法是去SpringCloud Alibaba官方看版本发布说明照着他们测过的组合来。4.2 Vue播放m3u8免安装的坑用hls.js播放m3u8时遇到两个问题第一视频流如果来自HTTPS页面浏览器会禁止播放HTTP非加密的视频流也就是Mixed Content问题。当时测试环境用的是http一切正常一上生产环境配了HTTPS播放器直接报错。解决办法是把视频服务也放到HTTPS后面或者让Nginx对视频域名做反向代理并开启SSL。第二m3u8的跨域问题。MinIO或者流媒体服务如果没有配置CORShls.js在加载ts分片时会被浏览器拦截。解决方式是在MinIO桶策略里允许跨域请求{ Version: 2012-10-17, Statement: [ { Effect: Allow, Principal: { AWS: [*] }, Action: [s3:GetObject], Resource: [arn:aws:s3:::canteen-video/*] } ] }再加上MinIO的CORS配置。这个坑不解决你会看到播放器一直转圈但抓包能看到m3u8能请求到ts分片却全部挂了。4.3 微服务间调用与分布式事务的边界Seata虽然能解决跨服务事务但不是所有场景都该上。我们一开始把“生成订单扣库存扣余额发送微信通知”全部放进一个全局事务结果每次订餐要等微信通知返回慢得离谱。后来我把微信通知从全局事务里拆出来用异步消息队列去发。因为通知失败了不影响订餐成功最多用户少收到一条消息从业务上完全能接受。这引出一个原则全局事务里只保留必须强一致的操作通知、日志、报表这类弱一致操作放到异步链路。还有一个常见问题Feign调用的超时设置。默认OpenFeign的读取超时是10秒但如果Seata事务里要等库存服务提交很可能超过10秒。我们当时调大了Feign的超时时间并且设置了重试次数结果因为库存扣减是幂等的重试导致库存被扣了两次。所以重试策略要非常小心只对查询接口重试写操作最好加上幂等控制。我们的做法是让库存服务接收一个幂等键比如订单号加菜品ID库存流水表用唯一索引去重。4.4 常见问题速查表现象原因解决办法Nacos注册不上报“normally connect”版本不兼容或开放端口没开检查9848/8848端口确认版本兼容矩阵Vue刷新404history路由没有配置try_filesNginx加try_files $uri $uri/ /index.htmlm3u8播放黑屏跨域或Mixed Content配置CORS视频服务走HTTPS分布式锁导致接口变慢锁粒度过粗按用户/菜品加锁不要用全局锁Seata事务回滚不生效数据源没有被代理确认seata-datasource-proxy启用用户重复订餐成功缺少唯一约束和锁数据库唯一索引分布式锁双保险微信模板消息发送失败access_token过期定时刷新并放到Redis共享上传文件后图片无法访问MinIO桶策略限制使用预签名URL或配置桶读取策略5. 经验总结与扩展建议5.1 从0到1开发微服务项目的时间线这里给团队排期做个参考。这个项目总体工期4个月前后端4人团队第1个月需求调研、原型确认、技术预研、搭建脚手架。期间遇到了版本兼容问题花了将近一周才定下技术版本组合。第2个月基础服务开发包括auth-service、user-service、网关、Nacos、前端框架搭建。第3个月核心业务开发菜品、订餐、库存、报表前端页面同步开发。第4个月联调、压测、部署、修复问题。前面整体还算顺利最后压测时发现重复订餐和库存超卖的问题临时加分布式锁耽误了几天。如果你们是从单体改造为微服务时间至少乘1.5。微服务并不是“代码拆分”那么简单中间件的搭建和运维就得花不少时间。5.2 后续扩展方向这个系统交付后甲方又提了好几个扩展想法我觉得挺有代表性一是把消息通知扩展到钉钉或企业微信因为机关单位现在也不是只用微信。二是接入智能餐盘和称重结算设备这需要做硬件对接但业务逻辑基本不用动订餐、扣款、菜单数据都会复用。三是把食谱和营养分析结合起来自动计算每餐的卡路里和营养元素这对机关单位的职工健康管理是加分项。四是把采购流程做成自动对账供应商送货单和系统采购单自动匹配大大减轻后勤财务的工作量。技术层面可以考虑把报告服务做成独立的报表大屏用Flask或Node.js单独部署更轻量或者引入ClickHouse做历史数据分析不过目前MySQL加上定时汇总表已经够用了。最后说一点个人体会做这类项目最忌讳“技术过剩”。微服务、分布式事务、分布式锁这些技术在特定场景下确实有效但如果你的业务只有几百个人用、一天几千笔订餐单体应用加数据库水平分表反而更香。这个项目之所以上微服务更多是甲方的架构要求以及未来多业务扩展的考量。如果你的团队是第一次做微服务建议一定先把SpringCloud Alibaba的官方Demo跑通理解Nacos、Seata、Sentinel各自的定位再往业务里套。中间件不能只停留在“会配置”出问题的时候能看懂日志、定位到是哪个环节慢了才是微服务实战的硬功夫。
返回列表