Posted in

若依Go前端分离部署踩坑大全(Nginx+Vue3+Go API联调终极配置模板)

第一章:若依Go版前端分离架构全景概览

若依Go版采用标准的前后端完全分离架构,后端基于 Gin 框架构建 RESTful API 服务,前端独立部署于 Nginx 或 Vite 开发服务器,通过跨域配置或反向代理实现通信。该架构解耦清晰、职责分明,支持前端团队并行开发与灰度发布,也便于后端进行微服务演进。

核心组件构成

  • 后端服务ruoyi-go-server,提供统一认证(JWT)、权限控制(RBAC)、系统监控(Prometheus + Grafana 接入点)及业务模块接口;
  • 前端项目ruoyi-go-ui,基于 Vue 3 + TypeScript + Element Plus 构建,使用 Pinia 管理状态,路由懒加载提升首屏性能;
  • 通信协议:所有接口遵循 application/json 格式,错误响应统一为 { "code": 500, "msg": "xxx", "data": null } 结构;
  • 部署方式:前端静态资源打包后置于 Nginx 的 html/ 目录,后端二进制可执行文件通过 systemd 托管运行。

关键配置示例

启动前端开发环境时,需在 ruoyi-go-ui/.env.development 中指定代理目标:

# .env.development
VUE_APP_BASE_API = '/dev-api'

并在 vue.config.js 中配置代理规则,避免开发期 CORS 问题:

// vue.config.js
module.exports = {
  devServer: {
    proxy: {
      '/dev-api': {
        target: 'http://localhost:8080', // 对应 ruoyi-go-server 默认端口
        changeOrigin: true,
        pathRewrite: { '^/dev-api': '' } // 剔除前缀,后端直接接收 /login 等路径
      }
    }
  }
}

架构交互流程简表

阶段 前端行为 后端响应要点
用户登录 POST /dev-api/login 提交凭证 成功返回 JWT token 及用户权限菜单
权限校验 每次请求携带 Authorization: Bearer <token> 中间件解析 token 并注入用户上下文
路由守卫 基于后端返回的菜单动态生成路由 /system/user 等路径受 @RequiresPermissions 注解保护

该架构天然支持 CI/CD 流水线:前端可独立构建镜像,后端通过 Swagger 自动生成 OpenAPI 文档,二者契约通过 api-contract.json 文件协同维护。

第二章:Nginx反向代理与跨域治理实战

2.1 Nginx核心配置模型与若依Go API路由映射原理

Nginx以事件驱动 + 多进程模型承载HTTP请求分发,其location块通过前缀匹配、正则捕获与内部重写,精准将路径委托至后端服务。

若依Go后端路由机制

基于gin.Engine构建,采用树形路由结构(radix tree),支持动态参数(如/api/sys/user/:id)与通配符(/*path),所有API统一注册在/api/前缀下。

Nginx与Go路由协同示例

location ^~ /api/ {
    proxy_pass http://go-backend/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

^~ 表示前缀优先级最高,避免正则干扰;proxy_pass末尾的/确保路径不重复拼接(如/api/sys/userhttp://go-backend/sys/user)。

配置项 作用
proxy_redirect 重写后端返回的Location
proxy_buffering 控制响应缓冲策略
graph TD
    A[客户端请求 /api/sys/user/123] --> B[Nginx location ^~ /api/]
    B --> C[重写为 /sys/user/123]
    C --> D[转发至 gin 路由树]
    D --> E[匹配 /sys/user/:id 处理器]

2.2 静态资源托管策略:Vue3构建产物的最优目录结构与缓存控制

Vue 3 默认构建输出 dist/ 目录,但生产环境需精细化组织静态资源以提升 CDN 分发效率与浏览器缓存命中率。

最佳目录结构实践

  • dist/index.html(带完整哈希的入口)
  • dist/assets/js/[name].[hash:8].js(代码分割)
  • dist/assets/css/[name].[contenthash:8].css
  • dist/assets/img/[name].[contenthash:6].png

关键缓存策略配置(vite.config.ts)

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        assetFileNames: 'assets/[name].[contenthash:6].[ext]',
        chunkFileNames: 'assets/js/[name].[hash:8].js',
        entryFileNames: 'assets/js/[name].[hash:8].js'
      }
    }
  }
})

contenthash 基于文件内容生成,确保内容不变则哈希不变;hash 用于入口/分块,避免因依赖变更导致无关 JS 缓存失效。[ext] 保留原始扩展名,便于 MIME 类型识别。

HTTP 缓存头建议

资源类型 Cache-Control
HTML no-cache, must-revalidate
JS/CSS/IMG public, max-age=31536000
graph TD
  A[用户请求 index.html] --> B[CDN 返回未缓存HTML]
  B --> C[解析JS/CSS路径含contenthash]
  C --> D[命中长期缓存资源]

2.3 WebSocket长连接穿透配置:解决若依权限模块实时通知失效问题

若依(RuoYi)默认使用内嵌Tomcat,其WebSocket端点在Nginx反向代理后易因超时或头部缺失导致连接中断,致使权限变更、菜单刷新等实时通知丢失。

关键代理配置项

  • proxy_http_version 1.1:启用HTTP/1.1以支持长连接
  • proxy_set_header Upgrade $http_upgrade:透传Upgrade头
  • proxy_set_header Connection "upgrade":显式声明升级协议

Nginx WebSocket透传配置

location /ws/ {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 86400;  # 防止空闲断连
}

proxy_read_timeout 86400 延长读超时至24小时,避免Nginx主动关闭空闲WebSocket连接;$http_upgrade 动态捕获客户端Upgrade请求头,确保协议升级流程完整。

若依前端连接逻辑适配

客户端行为 配置要点
连接URL wss://domain/ws/notify
心跳间隔 stompClient.heartbeat.outgoing = 10000
重连策略 指数退避(1s→2s→4s…)
graph TD
    A[前端Stomp over WebSocket] --> B[Nginx代理]
    B --> C{Upgrade头匹配?}
    C -->|是| D[透传至Spring Boot WebSocketEndpoint]
    C -->|否| E[降级为HTTP轮询]

2.4 HTTPS双向认证与X-Forwarded-For头透传:保障Go后端获取真实客户端IP

在反向代理(如Nginx、Traefik)后部署Go服务时,r.RemoteAddr仅返回代理IP。需结合TLS双向认证与可信头透传确保身份与来源双重可信。

双向认证校验客户端证书

// 启用ClientAuth并提取CN作为可信标识
tlsConfig := &tls.Config{
    ClientAuth: tls.RequireAndVerifyClientCert,
    ClientCAs:  caPool,
}

RequireAndVerifyClientCert强制验证客户端证书有效性;caPool为预加载的根CA证书池,确保终端设备身份合法。

安全解析X-Forwarded-For

clientIP := r.Header.Get("X-Forwarded-For")
if clientIP != "" {
    ips := strings.Split(clientIP, ",")
    realIP = strings.TrimSpace(ips[0]) // 仅取最左(最外层代理添加)
}

必须限制信任链长度,仅接受来自已知代理的X-Forwarded-For头,避免伪造。

代理信任白名单对照表

代理组件 配置示例 是否默认信任
Nginx proxy_set_header X-Forwarded-For $remote_addr; 否(需显式配置)
Traefik forwardedHeaders.trustedIPs = ["10.0.0.0/8"] 是(需声明CIDR)

请求链路可信传递流程

graph TD
    A[Client] -->|mTLS + XFF| B[Nginx]
    B -->|verified cert + trusted XFF| C[Go Server]
    C --> D[IP: 203.0.113.5, CN: device-7a2f]

2.5 Nginx日志精细化采集:对接若依Go审计日志体系的字段对齐实践

为实现Nginx访问日志与若依Go版审计日志(ruoyi-gateway)语义级对齐,需重构日志格式并建立字段映射关系。

字段对齐核心映射表

Nginx $var 若依审计字段 说明
$remote_addr clientIp 支持X-Forwarded-For透传
$request_time costTime 单位:毫秒(需乘1000)
$http_x_trace_id traceId 全链路追踪标识

日志格式重定义(nginx.conf)

log_format audit_json escape=json 
  '{'
    '"clientIp":"$remote_addr",'
    '"uri":"$uri",'
    '"method":"$request_method",'
    '"status":$status,'
    '"costTime":$request_time,'
    '"traceId":"$http_x_trace_id",'
    '"userAgent":"$http_user_agent"'
  '}';
access_log /var/log/nginx/audit.log audit_json;

逻辑说明:escape=json 防止双引号/斜杠破坏JSON结构;$request_time 原生单位为秒,若依后端期望毫秒,需在Fluent Bit或Logstash中做乘1000转换。

数据同步机制

graph TD
  A[Nginx access.log] --> B[Fluent Bit]
  B --> C{JSON解析 & 字段增强}
  C --> D[若依审计网关 /api/audit/log]

第三章:Vue3前端工程化联调深度适配

3.1 vite.config.ts生产环境代理失效根因分析与build模式下API前缀动态注入方案

Vite 的 server.proxy 仅在开发服务器(vite dev)中生效,build 模式下完全忽略——这是代理失效的根本原因:代理是 Dev Server 特性,非构建时重写机制

为什么代理不参与 build?

  • proxy 基于 connect + http-proxy-middleware,运行在 Node.js 开发服务进程中;
  • vite build 是纯静态产物生成过程,无运行时服务上下文。

动态注入 API 基础路径的推荐方案

// vite.config.ts
export default defineConfig(({ mode }) => {
  const API_BASE = mode === 'production' 
    ? '/api' // 生产走 Nginx 反向代理或 CDN 路由
    : '/dev-api'; // 开发期保留 proxy 前缀,与 vite.config.ts 中 proxy 匹配

  return {
    define: {
      __API_BASE__: JSON.stringify(API_BASE),
    },
  };
});

此处通过 define 注入全局常量,使前端请求逻辑可感知环境差异。__API_BASE__ 在构建时被静态替换,零运行时开销。

请求封装层适配示例

环境 实际请求地址 代理目标(dev) 构建后资源位置
dev /dev-api/users http://localhost:3000/users
prod /api/users 静态文件中已硬编码 /api
graph TD
  A[fetch('/users')] --> B{__API_BASE__}
  B -->|dev| C[/dev-api/users]
  B -->|prod| D[/api/users]
  C --> E[Dev Server Proxy]
  D --> F[Nginx /api → Backend]

3.2 若依RBAC权限指令(v-auth)在Vue3 Composition API中的重载实现

为适配 Vue3 的响应式系统与 Composition API,v-auth 指令需脱离 Options API 的 directives 选项,转为通过 app.directive() 显式注册,并支持 ref/computed 权限标识的动态响应。

指令重载核心逻辑

// src/directives/auth.ts
import { Directive, App, Ref, ComputedRef } from 'vue'
import { usePermissionStore } from '@/store/modules/permission'

const authDirective: Directive = {
  mounted(el, binding) {
    const permissionStore = usePermissionStore()
    const requiredPerms = Array.isArray(binding.value) 
      ? binding.value 
      : [binding.value]

    // 支持 ref/computed 动态权限判断
    const check = () => {
      const has = requiredPerms.some(p => 
        typeof p === 'string' 
          ? permissionStore.hasPerm(p) 
          : p instanceof Function && p()
      )
      el.style.display = has ? '' : 'none'
    }

    // 响应式监听(自动追踪 ref/computed 依赖)
    if (binding.value?.$el || binding.value?.effect) {
      // 兼容 computed/ref:利用 watchEffect 自动追踪
      import('@/utils/watch').then(({ watchEffect }) => {
        watchEffect(check)
      })
    } else {
      check()
    }
  }
}

export function setupAuthDirective(app: App) {
  app.directive('auth', authDirective)
}

逻辑分析mounted 钩子中调用 check() 判断权限,关键在于 watchEffect 自动追踪 binding.value 中的响应式依赖(如 computed(() => user.role)),避免手动 watchrequiredPerms 支持字符串、函数或混合数组,提升灵活性。

权限判定策略对比

策略类型 示例值 响应式支持 适用场景
字符串常量 'sys:user:add' ❌(静态) 路由级粗粒度控制
计算属性 computed(() => route.meta.auth) 动态路由元信息
函数表达式 () => user.isAdmin && isDevEnv() 复杂业务规则

数据同步机制

  • 权限变更时,permissionStore 触发 hasPerm 缓存失效;
  • 所有 v-auth 元素通过 watchEffect 自动重执行 check()
  • 无需手动 $forceUpdate,完全响应式闭环。
graph TD
  A[权限变更] --> B[permissionStore.notify()]
  B --> C[watchEffect 重新运行]
  C --> D[调用 hasPerm]
  D --> E[更新 DOM display]

3.3 前端Token无感刷新机制:Axios拦截器与Go JWT续期接口协同设计

核心挑战

用户会话持续期间,Access Token 过期(通常15–30分钟)而 Refresh Token 仍有效(7天)。需在请求失败前静默续期,避免白屏或强制跳转登录。

Axios 请求拦截器设计

// 自动注入最新 Access Token
axios.interceptors.request.use(config => {
  const token = localStorage.getItem('access_token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

逻辑分析:每次请求前读取本地 access_token;若存在则注入 Authorization 头。注意:此阶段不校验有效期,交由响应拦截器统一兜底。

响应拦截器触发续期

axios.interceptors.response.use(
  res => res,
  async error => {
    const originalRequest = error.config;
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      const refresh = localStorage.getItem('refresh_token');
      const { data } = await axios.post('/api/v1/auth/refresh', { refresh });
      localStorage.setItem('access_token', data.access_token);
      return axios(originalRequest); // 重发原请求
    }
    throw error;
  }
);

逻辑分析:捕获 401 错误且非重试请求时,调用 Go 后端 /auth/refresh 接口;成功后更新本地 access_token 并重放原请求。_retry 标志防止无限循环。

Go JWT 续期接口关键约束

字段 类型 说明
refresh_token string 必填,签名验证 + Redis 黑名单检查
user_id uint64 从 Refresh Token payload 解析,用于生成新 Access Token
access_token string 新签发的短时效 JWT(含 exp, iat, sub

协同流程图

graph TD
  A[前端发起请求] --> B{携带 access_token?}
  B -->|是| C[Go 验证签名与 exp]
  B -->|否| D[401 返回]
  C -->|过期| E[调用 /auth/refresh]
  C -->|有效| F[正常响应]
  E --> G[验证 refresh_token + 黑名单]
  G -->|有效| H[签发新 access_token]
  H --> I[返回新 token]
  I --> J[前端更新 localStorage 并重试]

第四章:Go后端API服务高可用部署关键配置

4.1 若依Go版gin中间件链路:CORB、CSP、CSRF防护与Nginx安全头联动配置

在 Gin 框架中,安全中间件需协同防御多层攻击面。CORB(Cross-Origin Read Blocking)由浏览器强制执行,无需服务端代码,但需确保响应 Content-Type 准确(如 text/html; charset=utf-8),避免 MIME 类型混淆。

CSP 策略精细化控制

func CSPMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Header("Content-Security-Policy",
            "default-src 'self'; script-src 'self' 'unsafe-inline' cdn.example.com; frame-ancestors 'none'")
        c.Next()
    }
}

该策略禁止外域脚本执行与 iframe 嵌入,'unsafe-inline' 仅限开发期临时保留;生产环境应替换为 nonce 或 hash。

Nginx 与 Gin 安全头协同表

头字段 Gin 中间件设置 Nginx 配置位置 职责侧重
X-Content-Type-Options ✅(nosniff ✅(add_header 防 MIME 嗅探
Strict-Transport-Security ❌(应由 Nginx 终止 TLS 后注入) ✅(max-age=31536000 强制 HTTPS

CSRF 防护链路

// 使用 Gin 的 csrf 包(github.com/utrack/gin-csrf)
r := gin.Default()
r.Use(csrf.Middleware(csrf.Options{
    Secret:   []byte("your-32-byte-secret"),
    ErrorFunc: func(c *gin.Context) { c.AbortWithStatus(403) },
}))

Secret 必须为 32 字节随机密钥,错误处理直接终止请求,避免泄露 token 存在性。

graph TD
    A[客户端请求] --> B{Nginx}
    B -->|注入 HSTS/CORP/XSS-Protection| C[Gin 应用]
    C --> D[CSP/CSRF/CORB 元数据校验]
    D --> E[业务 Handler]

4.2 Go服务健康检查端点暴露规范:适配Nginx upstream主动探活与K8s readinessProbe

统一健康端点设计原则

应暴露单一 /healthz 端点,返回结构化 JSON,同时满足 Nginx health_check(HTTP status + body match)与 K8s readinessProbe(HTTP status + optional httpGet.path)双重语义。

基础实现示例

func healthzHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK) // K8s要求200;Nginx默认接受2xx/3xx
    json.NewEncoder(w).Encode(map[string]string{
        "status":  "ok",
        "version": "v1.12.3",
        "uptime":  time.Since(startTime).String(),
    })
}

逻辑分析:http.StatusOK 是 K8s readinessProbe 默认成功阈值;Nginx upstream 主动健康检查依赖 HTTP 状态码+可选响应体正则匹配(如 "status":"ok"),因此响应体需稳定、无动态字段(如时间戳应控制精度)。

探活参数对齐对照表

组件 关键参数 推荐值 说明
Nginx upstream health_check interval=3 fails=2 passes=2 interval=3s 避免瞬时抖动误判
K8s readinessProbe initialDelaySeconds: 5, periodSeconds: 3 failureThreshold: 3 与 Nginx 失败容忍保持一致

探活协同流程

graph TD
    A[Nginx upstream] -->|GET /healthz| B(Go服务)
    C[K8s kubelet] -->|GET /healthz| B
    B --> D{状态码200 ∧ body包含\"status\":\"ok\"}
    D -->|true| E[标记为healthy]
    D -->|false| F[隔离实例]

4.3 文件上传路径与Nginx client_max_body_size协同调优:突破若依附件模块10MB限制

若依默认使用 Spring Boot 内置 Tomcat,spring.servlet.multipart.max-file-size=10MB 仅是应用层限制,真实瓶颈常在 Nginx 反向代理层。

Nginx 层关键配置

# /etc/nginx/conf.d/ruoyi.conf
client_max_body_size 50M;  # 必须 ≥ 应用层上限,且需全局生效(http/server/location 均可)
client_body_buffer_size 16K;

client_max_body_size 控制客户端请求体最大尺寸;若未显式设置,默认为 1M,直接拦截超限请求,返回 413。client_body_buffer_size 影响内存缓冲策略,过小会频繁写临时文件。

若依后端同步调整

# ruoyi-admin/src/main/resources/application.yml
spring:
  servlet:
    multipart:
      max-file-size: 50MB
      max-request-size: 50MB

调优验证要点

  • ✅ 修改后需 nginx -t && systemctl reload nginx
  • ✅ 重启 Spring Boot 应用(JVM 参数无需调整)
  • ❌ 忽略 Nginx 配置将导致 413 错误早于 Spring 解析
组件 默认值 推荐值 作用域
Nginx 1M 50M 反向代理入口
Spring Boot 10MB 50MB Servlet 容器层
若依前端 10MB 50MB maxSize 校验

4.4 Go日志输出格式标准化:与Nginx access_log时间戳、请求ID(X-Request-ID)全链路对齐

为实现前后端日志时间轴对齐与请求追踪闭环,Go服务需严格遵循Nginx access_log 的时间格式与上下文字段规范。

标准化时间戳格式

Nginx默认使用 strftime 格式 %d/%b/%Y:%H:%M:%S %z(如 12/Jan/2024:15:30:45 +0800)。Go中需禁用time.RFC3339,改用自定义layout:

const nginxTimeLayout = "02/Jan/2006:15:04:05 -0700"
logEntry := fmt.Sprintf(`%s - - [%s] "%s %s %s" %d %d "-" "%s" "%s"`,
    clientIP,
    time.Now().Format(nginxTimeLayout), // 精确对齐Nginx时区与格式
    method, path, proto, statusCode, bodySize, userAgent, reqID)

nginxTimeLayout02/Jan/2006:15:04:05 -0700 是Go时间参考值(Unix epoch +1s),-0700 表示时区偏移,确保与Nginx log_format$time_local 语义一致;time.Now() 必须使用本地时区(非UTC),否则时区错位将导致毫秒级偏差。

请求ID注入与透传

  • Nginx需配置 proxy_set_header X-Request-ID $request_id;(启用$request_id模块)
  • Go中间件须从X-Request-ID读取并注入日志上下文
字段 Nginx变量 Go日志字段 对齐要求
时间戳 $time_local time.Now() 同layout+同zone
请求ID $request_id r.Header.Get("X-Request-ID") 非空且透传不重写

全链路日志关联流程

graph TD
    A[Nginx access_log] -->|含$request_id + $time_local| B(Go HTTP Handler)
    B --> C[结构化日志输出]
    C --> D[ELK/Splunk按request_id+timestamp聚合]

第五章:踩坑复盘与企业级部署Checklist

生产环境镜像体积失控导致滚动更新超时

某金融客户在K8s集群中部署Spring Boot应用时,因Dockerfile未清理构建缓存和临时依赖,最终镜像达1.8GB。滚动更新过程中,节点拉取镜像耗时超过300秒(默认progressDeadlineSeconds),触发ReplicaSet回滚。修复方案采用多阶段构建+--no-cache强制刷新基础层,并引入dive工具分析镜像层冗余,最终压缩至217MB,更新成功率从68%提升至99.97%。

ConfigMap热更新未生效引发配置漂移

运维团队将数据库连接池参数通过ConfigMap挂载为文件,但未在Deployment中设置subPath或使用volumeMounts.propagation: HostToContainer。当ConfigMap更新后,容器内文件未同步变更,导致部分Pod仍使用旧的maxActive=10配置,而新Pod启用maxActive=50,引发连接数突增与DB负载不均。解决方案是改用envFrom.configMapRef注入环境变量,并配合kubectl rollout restart强制重建。

企业级部署关键检查项

检查维度 必检项 验证方式
网络策略 是否启用NetworkPolicy限制Pod间通信 kubectl get networkpolicy
安全上下文 Pod是否禁用privileged、运行非root用户 kubectl describe pod
资源约束 limits/requests是否按SLA设定且无过度预留 kubectl top nodes/pods
日志落盘 容器日志是否重定向至/var/log并配置logrotate ls -l /var/log/app/
健康探针 liveness/readiness探针路径与超时阈值合理性 kubectl get pod -o yaml

Prometheus指标采集漏报排查路径

flowchart TD
    A[Alertmanager触发HTTP 5xx激增告警] --> B{确认是否全量Pod异常?}
    B -->|是| C[检查Ingress Controller日志]
    B -->|否| D[定位具体Pod IP]
    D --> E[exec进入容器执行curl -v http://localhost:8080/actuator/health]
    E --> F[发现TLS握手失败]
    F --> G[核查Service的targetPort与Pod容器端口映射是否一致]
    G --> H[修正service.yaml中port/targetPort为8443]

TLS证书自动轮换失效根因

使用cert-manager签发Let’s Encrypt证书时,Ingress资源未声明kubernetes.io/tls-acme: "true"注解,且ClusterIssuer未绑定到对应命名空间。导致证书到期后未触发ACME挑战,Nginx Ingress返回SSL_ERROR_BAD_CERT_DOMAIN。补救措施包括:① 添加cert-manager.io/cluster-issuer: letsencrypt-prod注解;② 在Ingress中显式引用tls[0].secretName;③ 通过kubectl get certificates,orders,challenges -A逐层验证签发链状态。

敏感配置硬编码泄露事件还原

某次CI流水线失败日志中意外输出了.env文件内容,暴露数据库密码。根本原因为GitLab CI脚本中误用echo $DB_PASSWORD而非echo "***",且.gitlab-ci.yml未启用artifacts:exclude过滤敏感文件。后续强制要求所有凭证通过GitLab CI Variables加密注入,并在流水线启动阶段执行grep -r 'password\|secret' . --include="*.yml" || true进行静态扫描拦截。

多可用区调度失衡导致单点故障

AWS EKS集群跨us-east-1a/1b/1c部署,但StatefulSet未配置topologySpreadConstraints,70% Pod被调度至1a区。当该AZ网络抖动时,ZooKeeper集群失去多数派,Kafka生产者批量超时。修复后添加如下约束:

topologySpreadConstraints:
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels: app.kubernetes.io/name: kafka-broker

从 Consensus 到容错,持续探索分布式系统的本质。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注