Gganjiasync
b9e55f81创建于 10 天前历史提交

Junco 运维手册

[TOC]

一、内置运维端点

启用 junco-web 后,框架内置以下端点。

端点 成功状态 成功响应 用途
GET /-/live 200 UP 进程存活检查
GET /-/ready 200 READY 流量接入检查
GET /-/metrics 200 Prometheus text 指标抓取

/-/ready 在应用启动完成后返回 READY。当应用准备关闭时,框架会先把 readiness 标记为不可用,再等待 http.shutdown.readiness-drain-ms,用于给 Kubernetes Service 摘流时间。

二、Kubernetes 探针示例

livenessProbe:
  httpGet:
    path: /-/live
    port: 9090
  initialDelaySeconds: 10
  periodSeconds: 10

readinessProbe:
  httpGet:
    path: /-/ready
    port: 9090
  initialDelaySeconds: 5
  periodSeconds: 5
  failureThreshold: 2

优雅停机建议配置:

http:
  shutdown:
    readiness-drain-ms: 10000

readiness-drain-ms 应大于 readiness 探针周期,常见取值为 1000030000 毫秒。

Web 过载与连接保护建议配置:

http:
  business:
    queue-capacity: 1024
  request-read-timeout-ms: 15000
  idle-timeout-ms: 60000
  • http.business.queue-capacity:业务线程池等待队列容量,队列满后返回 503。
  • http.request-read-timeout-ms:单个请求从请求头到完整 body 的最大读取时间,超时返回 408。
  • http.idle-timeout-ms:Keep-Alive 连接的最大空闲时间,超时后关闭连接。

健康检查使用后台快照,避免探针请求线程直接访问数据库等外部依赖:

health:
  interval-ms: 3000
  timeout-ms: 1000

jdbc:
  health:
    enabled: true

redis:
  health:
    enabled: true

nats:
  health:
    enabled: true
  • health.interval-ms:后台健康快照刷新周期。
  • health.timeout-ms:单轮健康检查的总超时时间;异常或超时统一按 DOWN 处理。
  • jdbc.health.enabled:检查所有已配置数据源,默认关闭。
  • redis.health.enabled:通过 Redis PING 检查连接,默认关闭。
  • nats.health.enabled:检查 NATS 连接状态;首次检查会触发惰性建连,默认关闭。

依赖健康检查必须显式开启,避免只使用部分能力的应用因为未使用的外部依赖影响 readiness。应用关闭后,NATS 健康检查不会重新创建已经销毁的连接。

Gateway 转发客户端支持按应用容量调整线程、超时和连接上限:

gateway:
  routes-location: classpath:router.yaml
  response:
    max-bytes: 10485760
  http:
    event-loop-threads: 2
    connect-timeout-ms: 1000
    response-timeout-ms: 3000
    pool:
      acquire-timeout-ms: 1000
      max-total: 200
      max-per-route: 50
  websocket:
    event-loop-threads: 2
    connect-timeout-ms: 3000
    handshake-timeout-ms: 5000
    max-connections: 4096
    max-frame-bytes: 65536
    max-pending-frames: 64
  • gateway.routes-location:路由配置文件位置;本地文件可使用 classpath:router.yaml,Kubernetes 挂载文件可使用 file: 路径。
  • gateway.response.max-bytes:普通 HTTP 下游响应体上限;SSE 数据保持流式转发,不做全量聚合。
  • gateway.http.event-loop-threads:HTTP 下游客户端的 Netty EventLoop 线程数。
  • gateway.http.connect-timeout-ms:建立下游 HTTP 连接的超时时间。
  • gateway.http.response-timeout-ms:等待下游响应头及普通 HTTP 响应体的超时时间;SSE 收到响应头后不再使用该读超时,由客户端断开或下游结束关闭流。
  • gateway.http.pool.acquire-timeout-ms:从 HTTP 连接池取得连接许可的等待时间。
  • gateway.http.pool.max-total:整个网关实例允许的 HTTP 下游并发连接上限。
  • gateway.http.pool.max-per-route:单个下游地址允许的 HTTP 并发连接上限。
  • gateway.websocket.event-loop-threads:WebSocket 下游客户端的 Netty EventLoop 线程数。
  • gateway.websocket.connect-timeout-ms:建立下游 WebSocket TCP 连接的超时时间。
  • gateway.websocket.handshake-timeout-ms:WebSocket 下游握手及关闭握手的等待时间。
  • gateway.websocket.max-connections:整个网关实例允许的 WebSocket 并发连接上限。
  • gateway.websocket.max-frame-bytes:单个 WebSocket 帧的最大字节数。
  • gateway.websocket.max-pending-frames:下游握手已完成但上游握手尚未完成时,允许暂存的最大帧数;达到上限会关闭连接。

以上数值必须为正整数,配置非法时应用启动失败。连接上限是单个网关实例的限制,Kubernetes 多副本容量需要按副本数合并评估。

Gateway 命中路由后只在 Web 业务线程中完成路由匹配、过滤器和请求发起。连接许可、下游响应头和普通响应体均通过 Future 异步等待,完成后切回入站 Netty EventLoop 写出;客户端提前断开会取消尚未完成的下游请求。WebSocket 转发会根据对端可写状态暂停或恢复来源端读取,避免慢连接造成无界堆积。

普通 Controller 采用同步返回模型,参数解析、业务调用、拦截器、异常处理和响应写出保持在一条清晰的调用链中。Gateway 转发仍使用内部 Future 异步等待下游响应,但该实现细节不会暴露给 Controller。

流式端点建议同时配置连接数和单连接积压上限:

http:
  sse:
    max-connections: 4096
    max-pending-bytes: 1048576
  websocket:
    max-connections: 4096
    max-pending-messages: 256

SSE 待写字节或 WebSocket 待处理消息达到上限时,框架会主动关闭慢连接,避免单连接持续占用内存。

Schedule 的触发线程与业务执行线程相互隔离,分布式锁使用随机令牌并在任务运行期间续租:

schedule:
  execution-threads: 4
  default-lock-at-most-for-ms: 300000
  default-timeout-ms: 0
  drain-timeout-ms: 15000

任务超时会中断业务执行,但只有任务线程真正退出后才释放锁。服务停机时先停止触发与续租,再等待执行线程在 drain-timeout-ms 内完成,避免 Redis 连接池先关闭后仍执行解锁。

内存缓存可按 region 限制条目数,默认每个 region 最多 10000 条:

cache:
  memory:
    max-entries-per-region: 10000

达到上限时优先清理过期条目,否则淘汰最早写入的条目。Redis 延时队列通过 Lua 原子地将到期元素从 ZSET 搬到 ready list,支持多实例并发轮询。

JDBC 默认事务传播行为仍为 REQUIRED。需要独立提交或回滚时使用 @JuncoTransactional(propagation = TransactionPropagation.REQUIRES_NEW)timeoutSeconds 从事务开始时计算总截止时间,每条 SQL 只获得剩余时间;截止后事务标记为 rollback-only,提交前也会再次检查。

三、外部配置和优先级

Junco 默认从 classpath 加载 application.propertiesapplication.ymlapplication.yaml

业务配置 Bean 可以继续在注解中保留默认文件名,便于通过文件名反查绑定关系:

@JuncoProperties(prefix = "path", path = "classpath:path.yaml")
public class PathProperties {
}

这里的 path.yamlPathProperties 的默认配置源。application.yml、导入文件、JVM 参数或环境变量中相同 path 前缀的配置都会覆盖默认值,也可以补充默认文件中不存在的字段。

默认文件本身不是必需资源时,可以显式增加 optional: 前缀:

@JuncoProperties(prefix = "path", path = "optional:classpath:path.yaml")
public class PathProperties {
}

该文件不存在时,框架仅使用全局配置源绑定;全局配置源中也没有 path 配置时,容器注册一个无参构造的默认 Bean。文件存在但格式错误时仍会启动失败。

条件化 Bean 注册

@ConditionalOnBean 可以标记 @JuncoManaged 类或 @JuncoBean 方法,仅在容器中存在全部指定类型或名称的 Bean 时注册:

@ConditionalOnProperty 可以标记 @JuncoConfig 配置类、@JuncoManaged 类或 @JuncoBean 方法。未指定 havingValue 时,配置存在且值不为 false 即匹配:

@ConditionalOnMissingBean 用于 @JuncoManaged 类或 @JuncoBean 方法,可以按类型或 Bean 名提供缺省实现。类型和名称都未指定时,默认使用当前类或方法返回类型:

条件判断基于已注册的 BeanDefinition 和单例 Bean,不会提前实例化 Bean。多个条件同时出现时需要全部满足;只依赖配置的实现会先注册,再判断 @ConditionalOnMissingBean 默认实现是否需要退让。

应用主配置可以通过 junco.config.import 导入 ConfigMap 挂载文件:

junco:
  config:
    import:
      - optional:file:/etc/junco/path.yaml

导入项按声明顺序加载,后面的文件覆盖前面的同名 key。支持 classpath:file:;默认情况下文件不存在会导致应用启动失败,增加 optional: 前缀后仅在文件不存在时跳过,文件存在但格式错误时仍会启动失败。

junco.config.import 适合在应用配置中明确记录外部配置依赖。需要在不修改应用配置的情况下替换整份启动配置时,仍可使用环境变量:

JUNCO_CONFIG_LOCATION=file:/etc/junco/application.yml

配置较多时,使用复数配置项声明多个文件:

JUNCO_CONFIG_LOCATIONS=file:/etc/junco/base.yml,file:/etc/junco/jdbc.yml,file:/etc/junco/redis.yml

多个文件按从左到右的顺序加载,后面的文件覆盖前面的同名 key。JUNCO_CONFIG_LOCATION 保留为单文件兼容入口;环境变量未配置时,也可以使用 JVM 参数:

java -Djunco.config.locations=file:/etc/junco/base.yml,file:/etc/junco/redis.yml -jar app.jar

单复数配置项的选择顺序为:

JUNCO_CONFIG_LOCATIONS
> JUNCO_CONFIG_LOCATION
> -Djunco.config.locations
> -Djunco.config.location

配置值的覆盖优先级从低到高为:

@JuncoProperties.path 默认配置
< classpath application.properties
< classpath application.yml
< classpath application.yaml
< 外部配置文件(按声明顺序,后文件优先)
< junco.config.import(按声明顺序,后文件优先)
< 模块专用配置(如 gateway.routes-location)
< JVM -D 参数
< 环境变量
< 程序显式追加的 PropertySource

@JuncoProperties.path 只参与对应 Bean 的绑定,因此它的优先级低于所有全局配置源。上表按装载顺序列出全局配置源时,可以将它理解为该 Bean 的最前置默认值,而不是独立的全局 PropertySource

除带 optional: 的配置路径外,显式配置的文件不存在、格式错误或不是 .properties.yml.yaml 时,应用启动失败,不会忽略错误继续运行。

Kubernetes ConfigMap 示例:

apiVersion: v1
kind: ConfigMap
metadata:
  name: junco-app-config
data:
  base.yml: |
    http:
      port: 9090
  redis.yml: |
    redis:
      address: redis://redis:6379
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: junco-app
spec:
  template:
    spec:
      containers:
        - name: junco-app
          image: example/junco-app:latest
          env:
            - name: JUNCO_CONFIG_LOCATIONS
              value: file:/etc/junco/base.yml,file:/etc/junco/redis.yml
          volumeMounts:
            - name: config
              mountPath: /etc/junco
              readOnly: true
      volumes:
        - name: config
          configMap:
            name: junco-app-config

单个 @JuncoProperties 默认文件的覆盖示例:

apiVersion: v1
kind: ConfigMap
metadata:
  name: junco-path-config
data:
  path.yaml: |
    path:
      base-url: https://api.example.com
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: junco-app
spec:
  template:
    spec:
      containers:
        - name: junco-app
          image: example/junco-app:latest
          volumeMounts:
            - name: path-config
              mountPath: /etc/junco
              readOnly: true
      volumes:
        - name: path-config
          configMap:
            name: junco-path-config

该 Deployment 与前面的 optional:file:/etc/junco/path.yaml 配合使用:本地未挂载文件时使用 classpath 默认值,Kubernetes 中挂载后自动覆盖 PathProperties

Gateway 路由可以保存在独立文件中。应用主配置通过 gateway.routes-location 声明文件位置,框架会在启用 Gateway 时自动导入;本地开发配置示例:

gateway:
  routes-location: classpath:router.yaml

Kubernetes 中将路由文件挂载到容器后,在主配置中直接指向挂载路径:

gateway:
  routes-location: file:/etc/junco/router.yaml

gateway.routes-location 未配置时不会额外导入路由文件,此时仍可直接在主配置中声明 gateway.routes。路由文件不存在、格式错误或无法读取时,应用启动失败。

当前外部配置只在应用启动时加载,ConfigMap 内容变化后需要通过 RollingUpdate 重建 Pod 才会重新绑定配置。

应用启动时会输出一条配置源顺序日志,例如:

Junco config sources (low -> high): [application.yml, file:/etc/junco/base.yml, systemProperties, systemEnvironment]

业务代码需要定位单个配置项时,可以注入 Environment 并调用 Environment#explain

PropertyResolution resolution = environment.explain("jdbc.url");
log.info("source={}, overriddenSources={}, placeholderResolved={}, decoded={}",
        resolution.getSource(), resolution.getOverriddenSources(),
        resolution.isPlaceholderResolved(), resolution.isDecoded());

诊断结果只包含标准化 key、最终来源、被覆盖来源以及占位符和解码状态,不包含原始值或最终值。框架不提供配置诊断 HTTP 端点,避免数据库地址、密码等配置被意外暴露。

四、Prometheus 接入

应用配置:

app:
  name: junco-demo

metrics:
  enabled: true
  jvm:
    enabled: true
  tags:
    application: ${app.name}
    env: prod

Prometheus 抓取配置:

scrape_configs:
  - job_name: junco-demo
    metrics_path: /-/metrics
    static_configs:
      - targets:
          - junco-demo:9090

指标清单详见 metrics.md

五、Grafana 看板

Junco 的 JVM、系统、HTTP 指标使用 Micrometer 命名,优先复用 Spring Boot Actuator Prometheus 看板。

常用面板建议:

  • JVM 内存:jvm_memory_used_bytes
  • JVM GC:jvm_gc_pause_seconds_*
  • Web 业务 HTTP QPS:http_server_requests_seconds_count,不包含 RPC Provider 内部入口。
  • Web 业务 HTTP 延迟:http_server_requests_seconds_sum / http_server_requests_seconds_count
  • RPC Provider 调用结果:junco_rpc_server_requests_total
  • RPC Provider 调用延迟:junco_rpc_server_request_duration_seconds_*
  • 进程 CPU:process_cpu_usage
  • 系统 CPU:system_cpu_usage
  • 日志级别:logback_events_total
  • Web 线程:junco_web_threads_busyjunco_web_threads_currentjunco_web_threads_config_max

六、健康检查排查

/-/live 失败

/-/live 失败一般表示进程不可达或监听端口异常。

检查顺序:

  1. 确认容器或进程仍在运行。
  2. 确认 http.port 与探针端口一致。
  3. 查看启动日志中 Web server 是否成功监听端口。
  4. 确认容器网络、Service、Ingress 或安全组未拦截。

/-/ready 返回 503

/-/ready 返回 503 表示当前不应接收流量。

常见原因:

  • 应用正在关闭,框架已经进入 readiness drain。
  • 注册的 HealthContributor 返回非 UP。
  • JDBC 健康检查失败,例如数据库不可达或 validation query 超时。

检查顺序:

  1. 查看应用日志中是否有 shutdown、health、jdbc 相关日志。
  2. 检查数据库、Redis、NATS 等依赖是否可达。
  3. 检查健康检查配置,例如 jdbc.health.enabledjdbc.health.validation-query

七、指标排查

/-/metrics 返回 404

确认配置:

metrics:
  enabled: true

metrics.enabled=false 时,框架不会暴露 /-/metrics

指标缺少 application 标签

确认已配置公共标签:

metrics:
  tags:
    application: ${app.name}

同时确认 ${app.name} 能解析为非空字符串。空 key 或空 value 会被忽略。

JVM 指标缺失

确认:

metrics:
  jvm:
    enabled: true

关闭 metrics.jvm.enabled 后,jvm_memory_used_bytesprocess_cpu_usagesystem_cpu_usage 等 JVM 和系统指标不会注册。

HTTP 指标路由不符合预期

HTTP 指标的 route 标签应使用路由模板,而不是原始 URL。
例如 /api/users/{id},不要记录为 /api/users/1001,避免高基数标签压垮 Prometheus。

八、故障排查手册

请求大量 400

优先检查:

  • Content-Type 是否正确。
  • JSON body 是否符合接口入参结构。
  • @PathParam@QueryParam@HeaderParam 是否能完成类型转换。
  • multipart 请求是否带了正确的 boundary。

请求大量 404

优先检查:

  • Controller 是否被 @JuncoScan 扫描到。
  • 类和方法上的 @Path 拼接后是否符合预期。
  • HTTP 方法注解是否正确,例如 @GET@POST
  • 是否误把 /-/live/-/ready/-/metrics 放进业务网关重写规则。

请求大量 500

优先检查:

  • 业务异常栈。
  • 全局 @ExceptionHandler 是否覆盖了预期异常。
  • 返回对象是否能被 JSON 序列化。
  • 依赖服务、数据库、缓存是否可用。

RPC 调用失败

优先检查:

  • servicemethodparameterTypes 是否和 provider 暴露的方法一致。
  • provider 是否启用了 @RpcService 并被扫描。
  • 请求体是否超过 rpc.request.max-bytes
  • byte[] 内联字段是否超过 rpc.binary.inline.max-bytes
  • 客户端重试和熔断配置是否符合预期。
  • 熔断状态按 RPC service 隔离,一个下游服务熔断不会阻断其他服务。

九、生产使用建议

正式生产使用前建议至少完成以下检查。

  • 所有服务配置 metrics.tags.application
  • Kubernetes 配置 liveness 和 readiness 探针。
  • Prometheus 成功抓取 /-/metrics
  • Grafana 有 JVM、HTTP、线程、日志、RPC 或缓存核心面板。
  • 业务异常统一通过 @ExceptionHandler 输出稳定结构。
  • 依赖数据库的服务启用 JDBC 健康检查。
  • 压测确认 http_server_requests_seconds_* 与 Web 业务负载一致,junco_rpc_server_* 与 RPC Provider 负载一致,junco_web_threads_* 与两者共同占用的执行资源一致。