news 2026/10/8 2:24:06

K8s实例“长毛”了?从故障隔离到安全“单杀”指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
K8s实例“长毛”了?从故障隔离到安全“单杀”指南

凌晨两点半,监控大屏上的告警像炸了锅一样弹出来。某个负责订单回流的服务实例,健康检查连续失败,日志里写满了非预期的异常退出。如果只是单个实例重启,重启也就罢了,可诡异的是,这个实例的邻居们也开始变得不稳定。群里有人甩了一句:“h46 艾黎老师的冰皮月饼长毛了,差点单杀 h46。”

乍一看,这句话充满了一种互联网黑话式的幽默,但它背后对应的是一个非常真实的云原生运维困境:一个节点的异常,如何像霉菌一样扩散到旁边的实例;一次原本应该“精准单杀”的隔离操作,又为什么差点把整个集群拖下水。所谓“冰皮月饼长毛”,放在容器场景里,就是镜像层、缓存层或数据卷里出现了脏数据、过期镜像或异常日志膨胀;所谓“单杀 h46”,就是对那个故障实例做定点清理和驱逐。这两件事合在一起,其实是每个 K8s 集群运维者都绕不开的必修课:如何在不过度影响全局的前提下,快速定位、隔离并恢复一个故障实例。

这篇文章不是来玩梗的,而是想借这个场景,把容器实例异常后的完整处置链路讲清楚:从发现故障,到确认故障边界,到精准隔离,再到恢复验证。读完这篇文章,你应该能回答这三个问题:为什么一个实例出问题会牵连周围实例?所谓“单杀”到底是怎么操作的?隔离之后又如何确保集群真的恢复健康了?

1. 这篇文章真正要解决的问题

云原生架构给开发带来的自由度是巨大的,但自由度也意味着故障半径更容易被放大。在传统虚拟机时代,一台机器挂了,影响范围相对可控;在容器时代,实例可以快速横向扩展,但同一个节点上的多个 Pod 会共享内核、共享磁盘、共享网络栈,一处资源泄漏,往往会被“相邻住户”感知到。

这里要说的“长毛”,并不是真的霉菌,而是几种典型的实例劣化现象:

  • 镜像层异常:镜像仓库里的某个层在拉取时损坏,或者本地缓存中的镜像与远程 digest 不一致,导致容器启动后行为不可预期。
  • 数据卷膨胀:容器内日志、临时文件、缓存目录无限增长,最终写满节点磁盘,导致同一节点上的其他 Pod 也出现 IO 抖动甚至创建失败。
  • 进程级污染:某个进程 fork 出大量子进程,或者出现活跃连接数暴涨,把 CPU、内存、文件句柄占满,直接影响 Node 上 kubelet 的稳定性。
  • 异常缓存/脏数据:应用层缓存里存了错误内容,逻辑判断走到了非预期分支,不断重试,形成自放大效应。

而“差点单杀 h46”这个描述,恰好指出了一个最常见也最致命的问题:我们总以为把出问题的 Pod 一删了之就结束了,但实际上,如果你没有搞清故障的传播路径,删除动作本身可能引发更大的雪崩。比如,故障实例持有某个分布式锁,你强制删除它,锁没有正常释放,其他实例就会全部阻塞;又比如,故障实例是某个消息分区的唯一消费者,你删掉它,消费位点就会堆积,反压到上游。

所以这篇文章要真正解决的问题,不是“怎么删一个 Pod”,而是:

  1. 如何判断当前故障是单点故障还是群体故障;
  2. 如何在故障隔离时控制爆炸半径;
  3. 如何完成“单杀”操作,并验证集群恢复到稳定状态。

2. 基础概念与核心原理:实例劣化、故障传播与爆炸半径

要理解“单杀 h46”为什么不简单,需要先建立几个概念。

2.1 实例劣化不是“死掉”,而是“半死不活”

在 Kubernetes 里,一个 Pod 有生命周期状态:Pending、Running、Succeeded、Failed、Unknown。但真正让运维头疼的不是 Failed,而是一种“半死不活”的状态:Pod 显示 Running,进程还在,但请求已经超时,健康检查开始不稳定,日志里全是异常。

这种状态很像冰皮月饼放进冰箱后表面开始出现斑点——它看起来还是月饼,但你已经开始怀疑它能不能吃。“长毛”是一个渐进过程,不是瞬间崩溃。容器内应用可能因为内存泄漏、缓存污染、依赖超时,逐步走向不可用。而 K8s 的探针(Liveness、Readiness、Startup)就是用来感知这种劣化的机制。

  • Startup Probe:判断容器是否启动完成,防止应用启动慢导致误杀。
  • Readiness Probe:判断容器是否能够接收流量,失败时 Service 会摘除该 Pod。
  • Liveness Probe:判断容器是否存活,失败时 kubelet 会重启容器。

2.2 故障传播的几种路径

为什么一个实例出问题,会影响旁边的实例?这涉及故障传播路径。

节点级资源争抢

最常见的传播路径是通过宿主机资源。如果实例的日志文件或缓存目录无限增长,把节点磁盘写满,kubelet 的 eviction manager 会开始驱逐节点上的 Pod,所有新调度到该节点的 Pod 都可能失败。这就是“长毛”从单个实例扩散到同节点邻居的过程。

共享依赖的级联失败

如果多个实例共享同一个 Redis、数据库或配置中心,而故障实例的异常重试流量打爆了共享客户端连接池,那么其他正常实例也会在获取连接时超时。这种传播不需要同节点,是逻辑层面的级联。

分布式锁与领导者选举的扰动

故障实例如果是某个分区 consumer 或某类任务的 leader,它的非正常退出会触发重新选举。如果选举机制没有做好防抖和恢复,锁的反复易主会导致写冲突,影响范围从单个服务扩大到多个服务。

2.3 爆炸半径的思维方式

在排查故障时,脑子里始终要有“爆炸半径”这个概念。所谓爆炸半径,是指一个操作可能影响到的业务范围。为了保证全局稳定,任何恢复动作都应该遵循“最小化影响”原则:

  • 先隔离,再恢复;
  • 先摘流量,再重启;
  • 先确认依赖,再删除实例。

“差点单杀 h46”这个说法,本质上是承认了一次隔离动作的冒险性。如果你直接对一个持有重要分布式锁的实例执行kubectl delete pod,表面上是定点清除了一个故障目标,实际上可能触发了锁丢失、主从切换、缓存击穿等一系列连锁反应。

3. 环境准备与前置条件:一套可复现的排查环境

为了让后面的“单杀”操作有的放矢,建议先准备一套能够复现故障现象和隔离动作的实验环境。如果是在生产环境做变更,务必遵循测试环境验证先行、备份和回滚预案齐全的原则。

3.1 环境组件清单

  • Kubernetes 集群:任意发行版均可,本文不绑定具体版本。实验环境建议使用kind或minikube,生产环境请以实际集群版本为准。
  • 命令行工具:kubectl,并配置好目标集群的 kubeconfig。
  • 镜像仓库:用于验证镜像层异常的场景,可使用本地 registry 或任意远程仓库。
  • 监控工具:至少要有基础的指标采集能力,例如 Prometheus + Grafana,或者云厂商自带的监控面板。
  • 命名空间隔离:建议在独立命名空间ops-demo中进行验证,不要直接在默认命名空间或生产命名空间操作。

3.2 准备一个用于复现的服务

我们用一个简单的 Web 服务作为演示对象,它包含两个副本,集群中同时再部署一个同命名空间下的辅助服务,用来模拟“邻居实例”。这里以多副本 Deployment 为例,实际项目中请根据业务形态调整。

# 文件路径:ops-demo.yaml apiVersion: apps/v1 kind: Deployment metadata: name: order-api namespace: ops-demo labels: app: order-api spec: replicas: 2 selector: matchLabels: app: order-api template: metadata: labels: app: order-api spec: containers: - name: order-api image: registry.example.com/library/order-api:v1.2.0 ports: - containerPort: 8080 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /livez port: 8080 initialDelaySeconds: 15 periodSeconds: 20

这个 YAML 本身没有特殊之处,但两个细节值得注意:资源限制和健康探针。没有资源限制的容器,在故障时更容易拖垮节点;没有健康探针的容器,在故障时不会及时被摘除流量。这两个配置是指标“长毛”能被提前发现的前提。

3.3 角色权限准备

在生产环境执行隔离或删除操作时,建议使用最小权限原则。比如 RBAC 中只授权delete特定命名空间下 Pod 的权限,而不是集群范围的*。这样即使操作失误,影响范围也被限制在授权边界内。

4. 核心流程拆解:从发现“长毛”到完成“单杀”

整个处置流程可以拆成六步。下面每一步都会说明操作内容、为什么需要这一步、关键命令是什么。

4.1 第一步:确认故障实例的身份

收到告警后,第一件事不是急着删除,而是确认到底是谁出了问题。用标签选择器找到故障服务对应的所有实例,观察它们的运行状态。

kubectl -n ops-demo get pods -l app=order-api -o wide

这一步能回答:故障是个体的还是群体的?如果两个副本都不正常,那就不是“单杀”能解决的,要考虑 Deployment 配置或依赖服务整体异常;如果只有一个副本异常,才进入定点处理。

4.2 第二步:区分“进程故障”与“节点故障”

实例异常可能来自应用自身,也可能来自宿主机资源争抢。查看节点状态和实例日志,是区分两类故障的基础。

kubectl describe node <node-name> kubectl -n ops-demo logs -l app=order-api --tail=200

如果节点上出现了DiskPressure或MemoryPressure,说明问题可能出在资源层面,需要先看共享目录、镜像层、日志文件的占用情况,而不是直接处理单个 Pod。

4.3 第三步:摘流量,把实例从服务端点中移出

这一步是“单杀”之前的关键缓冲。在删除实例前,先把它的标签从 Service 的端点中摘除,或者将 Readiness 探针改为失败状态,确保新的外部请求不会继续涌入。这样做的好处是,即使后续操作引发连锁反应,外部流量入口已经切到了健康实例。

kubectl -n ops-demo label pod <pod-name> role=quarantine --overwrite

当然,这只是把实例标记为隔离,真正让 Service 不转发流量,需要 Service 的 selector 不匹配role=quarantine。如果 Service selector 是app=order-api,则单独加一个role标签不会生效。更稳妥的方式是直接修改探针配置,或者使用kubectl cordon的节点维度操作,但后者的影响范围更大。这里要强调的是,摘流量动作的核心思想是让实例从负载均衡中退出,而不是立刻终止它。

4.4 第四步:进入实例内部,定位“长毛点”

摘除流量后,实例还有机会被检查。此时可以进入容器内部,查看磁盘占用、缓存目录、进程数和日志增长趋势。

kubectl -n ops-demo exec -it <pod-name> -- sh du -sh /var/log /tmp /app/cache 2>/dev/null ps -ef | wc -l

如果发现/var/log或/tmp目录异常膨胀,说明是日志或临时文件“长毛”;如果发现进程数异常多,说明应用 fork 失控;如果发现缓存目录里有大量非预期文件,说明缓存层被污染。从容器内部拿到证据,比只看到告警数要可靠得多。

4.5 第五步:执行隔离与清理

确认问题点后,有两种处理路径:

路径 A:实例可修复,执行原地清理

如果只是临时文件或缓存膨胀,可以先清理目录,重启容器内的主进程,观察是否恢复。这种方式风险小,但未必能根治问题。

路径 B:实例不可修复,执行重新调度

如果确认实例的镜像层或数据卷已经被污染,更推荐的方式是删除旧 Pod,让 ReplicaSet 重新拉起一个全新实例。下面这个命令就是“单杀”的直观形态:

kubectl -n ops-demo delete pod <pod-name> --grace-period=30

但这里必须强调:直接删除 Pod 不等于完成了单杀。如果故障的根因在镜像层,重新拉起的实例依然会使用同一个异常镜像,等于上岸了又跳进同一条河。所以删除之前,应该先确认镜像 digest 是否异常,必要时先更新 Deployment 中的镜像版本,再触发滚动重建。

4.6 第六步:观察集群状态,确认爆炸半径没有扩大

隔离和清理之后,需要观察几个层面的信号:

  • Pod 是否进入 Running 且 Ready;
  • Service 端点是否恢复正常;
  • 同节点的其他 Pod 是否出现重启或驱逐;
  • 下游依赖的指标是否回归基线。

如果这些信号都正常,这次的“单杀”才算真正完成。

5. 完整示例与代码实现:一次“长毛”实例的定点处置

下面用一个完整的模拟过程,演示从发现异常到恢复验证的所有操作。这里不假定具体的 Kubernetes 版本,命令在主流版本中均可运行。

5.1 模拟故障:制造“长毛”现象

在实验环境中,可以先给某个容器注入大量临时文件,模拟数据卷膨胀。

kubectl -n ops-demo exec -it deployment/order-api -- sh -c \ "mkdir -p /tmp/mold && for i in \$(seq 1 5000); do dd if=/dev/zero of=/tmp/mold/file_\$i bs=1M count=1 2>/dev/null; done"

这个命令会快速创建 5000 个 1MB 大小的临时文件,模拟日志或缓存膨胀。实际生产环境不要这样操作,这里只是为了生成实验现象。

5.2 定位异常实例

kubectl -n ops-demo get pods -l app=order-api -o wide kubectl -n ops-demo describe pod order-api-xxx

正常情况下,你会发现 Pod 的磁盘使用率上升,Readiness 探针开始失败。这时 Pod 虽然还在 Running,但已经被 Service 摘除流量。

5.3 摘流量与隔离标记

用一个带隔离标签的方式,将实例从正常的服务选择逻辑中移出。前提是 Service 的 selector 中包含role: active,而这个异常实例没有该标签。

kubectl -n ops-demo label pod order-api-xxx role=quarantine --overwrite kubectl -n ops-demo get endpoints -l app=order-api

观察 Endpoints 列表,确认该实例的 IP 已经从地址列表中消失。

5.4 进入容器确认污染点

kubectl -n ops-demo exec -it order-api-xxx -- sh du -sh /tmp/mold ls -l /tmp/mold | wc -l

如果确实存在大量临时文件,说明问题根因是应用没有清理临时目录,或者日志轮转配置失效。

5.5 执行清理与删除

由于临时文件已经污染了容器层,原地清理虽然可行,但不够彻底。这里演示“先修镜像,再重建实例”的方式。首先修改 Deployment 镜像版本,例如从v1.2.0改为v1.2.1,然后触发滚动更新:

kubectl -n ops-demo set image deployment/order-api \ order-api=registry.example.com/library/order-api:v1.2.1 kubectl -n ops-demo rollout status deployment/order-api

滚动更新会创建新实例,回收旧实例。此时再观察新实例的运行状态和磁盘占用,确认“长毛点”已经消失。

5.6 清理隔离标签

新实例起来后,旧的隔离标签不再需要,如果命名空间内还有其他残留的隔离实例,可以按需清理。

kubectl -n ops-demo get pods -l role=quarantine --no-headers | \ awk '{print $1}' | xargs -r kubectl -n ops-demo delete pod

这一串命令的意思是把所有带role=quarantine标签的 Pod 删除。执行前务必确认这些 Pod 确实已不需要保留。

6. 运行结果与效果验证

6.1 验证服务健康

执行完滚动更新后,用下面的命令查看 Pod 状态和 Service 端点:

kubectl -n ops-demo get pods -l app=order-api -o wide kubectl -n ops-demo get endpoints -l app=order-api

预期结果:所有副本都处于Running状态,且READY列为1/1;Endpoints 列表中有健康实例的 IP,且不再出现隔离实例的 IP。

6.2 验证节点资源

如果你在 5.1 中使用了临时文件制造故障,此时需要确认节点磁盘占用已回落。

kubectl top nodes kubectl top pods -n ops-demo

预期结果:节点的DISK指标不再处于压力状态,异常 Pod 的磁盘占用恢复正常。

6.3 验证依赖指标

这里的“依赖指标”代指下游 Redis、数据库或消息队列的延迟和错误率。在生产环境中,应通过监控面板观察这些指标是否回落到基线水平。如果这些指标仍然异常,说明故障传播的影响尚未完全消除,需要继续排查。

6.4 如果失败了,先看哪里

滚动更新过程中最常遇到的问题是新实例反复 CrashLoopBackOff。此时按下面的顺序排查:

  1. 先看新实例的日志:kubectl -n ops-demo logs deployment/order-api;
  2. 再看资源限制是否过小:kubectl -n ops-demo describe pod中的 Events;
  3. 确认镜像 tag 是否真实存在:docker pull registry.example.com/library/order-api:v1.2.1或镜像仓库界面;
  4. 确认探针路径是否正确:登录容器内部访问/healthz,看返回码。

7. 常见问题与排查思路

7.1 实例一直在重启,但读不到有效日志

问题现象可能原因排查方式解决方案
Pod 反复 CrashLoopBackOff,kubectl logs无输出容器启动阶段崩溃,日志未持久化查看kubectl describe pod的 Events,检查lastState的 Exit Code先加上terminationMessagePath或持久化日志,再定位启动阶段崩溃原因
日志中出现 “no space left on device”节点磁盘被写满,通常由日志或镜像层异常占用导致df -h和du -sh /var/log /var/lib/docker清理日志轮转配置,删除无用的悬空镜像层,必要时清理未被回收的容器层

7.2 删除一个异常 Pod,反而引发更大的流量超时

问题现象可能原因排查方式解决方案
执行kubectl delete pod后,服务超时率上升该实例持有分布式锁、消息分区消费权或本地缓存;删除后触发反复选举,或缓存击穿查看服务监控曲线,确认删除时间点和指标突变是否吻合先摘流量,再等选举或缓存预热完成;不直接删除,而是先扩容副本数量,再缩容故障实例
同节点其他 Pod 也出现异常故障传播路径来自共享资源争抢查看节点上的DiskPressure/MemoryPressure,确认是否有共享数据卷优先处理资源层问题,不盲删 Pod

8. 最佳实践与工程建议

8.1 让“单杀”成为可重复的日常操作

不要等事故发生时再临时摸索隔离命令。建议团队提前把“异常实例隔离操作手册”固化下来,至少包含以下内容:

  • 故障确认的 check-list;
  • 摘流量的具体方式;
  • 进入容器排查的关键路径;
  • 删除或重建的审批流程;
  • 验证集群恢复的指标清单。

8.2 为实例设置合理的资源限制和探针

没有资源限制的容器,就像是没穿防护服处理污染物,很容易把节点拖垮。建议所有 Deployment 都配置 requests 和 limits,并设置 Readiness Probe 与 Liveness Probe。探针的initialDelaySeconds要参考应用启动时间,太小会导致启动期被误杀,太大则会让故障发现过慢。

8.3 清理镜像层和数据卷的沉淀

“冰皮月饼长毛”总是从第一片霉菌开始。生产环境中,镜像仓库和节点上的悬空镜像层、未回收的数据卷就是潜在的“霉菌培养基”。建议定期执行以下操作:

docker image prune -f docker volume prune -f

注意,docker volume prune会删除所有未被容器使用的数据卷,生产环境中务必先确认哪些数据卷需要保留,再决定是否执行。如果用的是 containerd 或 CRI-O,需要使用对应的清理管理命令,不要盲目套用 Docker 命令。

8.4 使用滚动更新而不是直接删除

在多数场景中,“单杀”一个 Pod 的真正安全姿势是:修改 Deployment 模板,触发滚动更新,让 ReplicaSet 先补一个新实例,再回收旧实例。直接对单个 Pod 执行删除,本质上绕过了滚动更新的保护机制,容易引发可用副本数下降。如果确实需要快速隔离,建议先扩容副本数,再缩容故障实例。

8.5 权限控制与审批

对生产环境 Pod 的 delete、exec、label 操作,应通过 RBAC 和审批流程双重约束。至少要做到:

  • 只对指定命名空间授权删除权限;
  • 高危操作必须记录操作人和操作原因;
  • 变更前保留事件日志,方便事后审计。

8.6 重视“恢复后验证”而不是“恢复动作本身”

很多团队把“Pod 重新变成 Running”当作故障处置结束的标志,但真正的恢复信号是业务指标回到基线。建议在恢复验证文档中定义至少三个可量化的指标,例如:

  • 请求成功率恢复到 XX% 以上;
  • 平均延迟恢复到 XX ms 以内;
  • 节点资源使用率回落到 XX% 以下。

具体数值以业务实际为准,但指标必须是可观测的。

9. 总结与后续学习方向

这次从“h46 艾黎老师的冰皮月饼长毛了 差点单杀 h46”出发,把容器实例异常后的处置链路串了一遍:从判断故障是单点还是群体,到摘流量防止雪崩,再到定位“长毛点”和完成隔离重建,最后验证集群恢复状态。可以这样概括:“单杀”本身不是难点,难点在于杀之前控制爆炸半径,杀之后确认系统真正回到健康状态。

如果你刚开始接触 Kubernetes 运维,建议先在自己的实验集群里制造一次可控的故障,比如用临时文件填满容器磁盘,然后按照本文步骤走一遍隔离、清理、滚动更新、恢复验证的流程。这个过程会比只看文档记忆深刻得多。

如果你已经在负责生产集群,建议把“隔离操作手册”和“恢复验证清单”补充到团队的 Runbook 里。下次再遇到类似“长毛”场景时,就不会因为一句“差点单杀”而在凌晨手忙脚乱了。更值得深入的方向是:理解镜像层和图层的垃圾回收机制,掌握 CRI 层面运行时的磁盘管理,以及在 Service Mesh 或自定义调度策略下的精细化隔离手段。这些能力叠加起来,才能真正把“差点单杀”变成“安全单杀”。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 2:23:59

把研究问题变成问卷:拆解职臣AI问卷设计

一份问卷看起来由许多题目组成&#xff0c;真正决定它是否好用的&#xff0c;却是题目背后的设计顺序。职臣AI的问卷设计页面&#xff0c;把这个过程拆成了几项可见的选择&#xff1a;先写主题和目标&#xff0c;再确定调查对象、题量与题型&#xff0c;最后生成问卷与方案。理…

作者头像 李华
网站建设 2026/10/8 2:23:57

Windows Server 2012 R2 安装 .NET 3.5 卡住?SxS 源文件与 DISM 参数全解析

简介&#xff1a;这份资源面向在 Windows Server 2012 R2 Standard 上部署 .NET Framework 3.5 时反复安装失败的系统管理员与运维人员&#xff0c;核心是提供 SXS 组件源文件&#xff0c;用于在添加角色和功能时指定备用路径&#xff0c;绕过在线更新或镜像源缺失导致的报错。…

作者头像 李华
网站建设 2026/10/8 2:23:14

SSM体育器材租借管理系统:从建库到部署的完整实战指南

简介&#xff1a;一套面向毕业设计场景的SSM体育器材租借管理系统源码包&#xff0c;基于SpringSpringMVCMyBatis框架&#xff0c;采用B/S模式与JSP/JavaWeb技术栈&#xff0c;适合需要完成课程设计或毕设项目的计算机专业学生。系统包含管理员与普通用户双角色功能&#xff0c…

作者头像 李华
网站建设 2026/10/8 2:22:43

国密算法体系及国际对标分析

文章目录 引言 国密算法全景图谱 核心算法技术原理解析 SM1与SM7:非公开分组密码 SM4:高速对称加密引擎 核心参数与基础设计 算法架构溯源与行业定位 SM4非平衡Feistel vs AES-128 SPN 架构核心差异 产业主流格局 SM2:椭圆曲线公钥密码体系 核心曲线参数与安全等级 核心功能…

作者头像 李华
网站建设 2026/10/8 2:22:11

WinForm DevEx控件多Sheet导出方案:EPPlus替代COM实现高性能Excel导出

简介&#xff1a;这是一份面向WinForm开发者&#xff0c;特别是使用DevExpress控件库进行企业级桌面应用开发的技术人员的Excel导出增强方案。资源聚焦解决DevExpress原生导出功能的多项痛点&#xff1a;GridControl无法导出图片与多表头、PivotGridControl自动分组失真等问题&…

作者头像 李华
网站建设 2026/10/8 2:22:05

SpringBoot酒店客房管理系统实战:房态并发与订单状态机设计

简介&#xff1a;本资源为基于SpringBoot的酒店客房管理系统完整源码包&#xff0c;面向Java全栈学习者、课程设计或毕业设计开发者&#xff0c;帮助快速搭建一套前后端分离的酒店业务管理平台。后端采用SpringBoot、MybatisPlus、MySQL、Redis与Shiro权限中间件&#xff0c;提…

作者头像 李华