健康检查
健康检查的职责很单一:把已经注册到 addHealthChecks() 的检查项执行一遍,再把聚合结果暴露成 HTTP 响应。
在 Web 层有两种挂载方式:
useHealthChecks(path):按中间件方式挂载mapHealthChecks(pattern):按终结点方式挂载
最小示例
cangjie
import soulsoft_web_hosting.*
import soulsoft_web_healthchecks.*
import soulsoft_extensions_injection.*
import soulsoft_extensions_healthchecks.*
main(args: Array<String>): Int64 {
let builder = WebHost.createBuilder(args)
// 注册健康检查服务和检查项
builder.services.addHealthChecks()
.addCheck("self", {
HealthCheckResult.healthy()
})
let app = builder.build()
// 暴露 /health 端点
app.useHealthChecks("/health")
app.run()
return 0
}默认行为是:
Healthy->200Degraded->200Unhealthy->503- 响应体默认写入状态名,例如
Healthy - 默认附加防缓存响应头
两种挂载方式
useHealthChecks(path)
中间件方式适合直接按固定路径暴露健康检查。
cangjie
import soulsoft_web_hosting.*
import soulsoft_web_healthchecks.*
import soulsoft_extensions_injection.*
import soulsoft_extensions_healthchecks.*
let builder = WebHost.createBuilder(args)
builder.services.addHealthChecks()
.addCheck("db", {
HealthCheckResult.healthy()
})
let app = builder.build()
// 精确匹配 /health,允许尾部 /
app.useHealthChecks("/health")这里有两个实现细节要记住:
- 它是按路径精确匹配的,不会把
/health/details当成/health - 路径比较时会忽略结尾的
/,所以/health/也能命中
mapHealthChecks(pattern)
终结点方式适合需要和路由体系、授权、CORS 等能力一起使用的场景。
cangjie
import soulsoft_web_hosting.*
import soulsoft_web_routing.*
import soulsoft_web_healthchecks.*
import soulsoft_extensions_injection.*
import soulsoft_extensions_healthchecks.*
let builder = WebHost.createBuilder(args)
builder.services.addRouting()
builder.services.addHealthChecks()
.addCheck("self", {
HealthCheckResult.healthy()
})
let app = builder.build()
// 先启用路由
app.useRouting()
// 注册健康检查终结点
app.mapHealthChecks("/health")mapHealthChecks(...) 返回的是 EndpointConventionBuilder,所以可以继续挂终结点约定。
cangjie
app.mapHealthChecks("/health")
.requireCors("ops")过滤检查项
HealthCheckOptions.predicate 决定本次请求要执行哪些检查项。
按名称过滤
cangjie
app.useHealthChecks("/health/db") { options =>
// 只运行名为 db 的检查项
options.predicate = { registration =>
registration.name == "db"
}
}按标签过滤
cangjie
builder.services.addHealthChecks()
.addCheck("db", ["critical"], {
HealthCheckResult.healthy()
})
.addCheck("cache", ["cache"], {
HealthCheckResult.degraded()
})
app.useHealthChecks("/health/critical") { options =>
// 只运行带 critical 标签的检查项
options.predicate = { registration =>
var matched = false
for (tag in registration.tags) {
if (tag == "critical") {
matched = true
}
}
matched
}
}自定义响应
自定义状态码映射
cangjie
import soulsoft_extensions_healthchecks.*
app.useHealthChecks("/health") { options =>
// 让 Degraded 也返回 503
options.resultStatusCodes[HealthStatus.Degraded] = UInt16(503)
}自定义响应体
cangjie
app.useHealthChecks("/health/detail") { options =>
options.responseWriter = { context, report =>
context.response.contentType = "application/json"
context.response.write("{\"status\":\"${report.status}\"}")
}
}默认 responseWriter 会:
- 设置
Content-Type: text/plain - 直接写出
Healthy、Degraded或Unhealthy
控制缓存头
默认会写入这些防缓存头:
Cache-Control: no-store, no-cachePragma: no-cacheExpires: Thu, 01 Jan 1970 00:00:00 GMT
如果你明确允许缓存响应,可以关闭它:
cangjie
app.useHealthChecks("/health/cache") { options =>
options.allowCachingResponses = true
}使用自定义检查类型
如果检查逻辑本身需要依赖注入,更合适的方式是实现 IHealthCheck:
cangjie
import soulsoft_web_hosting.*
import soulsoft_web_healthchecks.*
import soulsoft_extensions_injection.*
import soulsoft_extensions_healthchecks.*
public class DatabaseHealthCheck <: IHealthCheck {
public init() {
}
public func check(context: HealthCheckContext): HealthCheckResult {
return HealthCheckResult.healthy()
}
}
let builder = WebHost.createBuilder(args)
builder.services.addHealthChecks()
.addCheck<DatabaseHealthCheck>("database")
let app = builder.build()
app.useHealthChecks("/health")时序图
下面的时序图描述的是一次健康检查请求的执行过程。中间件方式和终结点方式的核心执行逻辑相同,都是进入 HealthCheckMiddleware:
执行流程
执行时只要抓住五步:
- 入口先把请求导向
HealthCheckMiddleware - 中间件调用
IHealthCheckService.checkHealth(predicate) predicate决定哪些检查项参与本次聚合- 聚合状态按
resultStatusCodes映射成 HTTP 状态码 - 最后写缓存头和响应体
可以把默认结果理解成下面这样:
| 聚合状态 | 默认 HTTP 状态码 | 默认响应体 |
|---|---|---|
Healthy | 200 | Healthy |
Degraded | 200 | Degraded |
Unhealthy | 503 | Unhealthy |
一个推荐写法
如果你的目标是“同时暴露存活检查和就绪检查”,推荐直接按标签拆两个端点:
cangjie
import soulsoft_web_hosting.*
import soulsoft_web_healthchecks.*
import soulsoft_extensions_injection.*
import soulsoft_extensions_healthchecks.*
let builder = WebHost.createBuilder(args)
builder.services.addHealthChecks()
.addCheck("self", ["live"], {
HealthCheckResult.healthy()
})
.addCheck("database", ["ready"], {
HealthCheckResult.healthy()
})
let app = builder.build()
// 存活检查:只确认进程自身可响应
app.useHealthChecks("/health/live") { options =>
options.predicate = { registration =>
var matched = false
for (tag in registration.tags) {
if (tag == "live") {
matched = true
}
}
matched
}
}
// 就绪检查:确认依赖也已经准备好
app.useHealthChecks("/health/ready") { options =>
options.predicate = { registration =>
var matched = false
for (tag in registration.tags) {
if (tag == "ready") {
matched = true
}
}
matched
}
}这个模式的好处很直接:
- 存活与就绪的语义分开
- 各端点只执行自己关心的检查项
- 状态码和响应体仍然走统一的健康检查中间件逻辑