为什么普通部署总像一场赌博?
\n每个开发者至少都经历过一次这样的关键时刻:你按下“部署”按钮,心都提到了嗓子眼。如果新版本搞垮了数据库怎么办?如果用户看到的是 500 错误而不是主页怎么办?
\n普通部署就像没有备用伞的跳伞。要么一切完美运行,要么你整晚盯着日志,试图弄清楚哪里出了问题。但有一种方法可以让这个过程变得安全——Canary 部署(金丝雀发布)。
\n在本文中,我们将探讨如何使用 Ktor 和一个简单的负载均衡器在 Kotlin 中实现 Canary 部署。你将学会如何先将新代码提供给 5% 的用户,然后逐步增加流量。没错,我们将编写真正可用的代码。
\n\n什么是 Canary 部署,为什么你需要它?
\n想象你是一艘船的船长。与其直接驶过暗礁,不如先派一艘小船(金丝雀)探路。如果它通过了,整个船队再前进。如果它沉了,你只损失了一艘船。
\n在 IT 世界中,这看起来是这样的:你首先为一小部分用户部署新版本的服务。如果指标正常,就增加流量。如果不正常,你可以在几秒钟内回滚。
\n优势显而易见:
\n- \n
- 风险最小化 —— 错误只影响 1-5% 的用户。 \n
- 快速反馈 —— 你可以在错误演变成灾难之前发现它们。 \n
- A/B 测试 —— 可以比较新旧版本的性能。 \n
- 零停机 —— 用户不会察觉到切换。 \n
但美中不足的是:你需要能够管理流量、监控指标并自动化流程。Kotlin 凭借其简洁性和协程,非常适合这项任务。
\n\nKotlin 上的 Canary 部署架构
\n让我们构建一个简单但实用的架构。我们将拥有:
\n- \n
- 负载均衡器 —— 决定多少百分比的请求发送到旧版本(stable),多少发送到新版本(canary)。 \n
- 两个服务实例 —— stable 和 canary,均使用 Kotlin 和 Ktor 编写。 \n
- 监控 —— 错误计数和响应时间。 \n
负载均衡器将根据 X-Canary 头部或随机分布做出决策。我们将采用第二种方式——它更接近现实。
编写代码:Kotlin 负载均衡器
\n我们从负载均衡器开始。它将接收请求并将其转发到两个服务之一。为简单起见,我们将使用 Ktor 内置的 HTTP 客户端。
\n\nimport io.ktor.client.*\nimport io.ktor.client.engine.cio.*\nimport io.ktor.client.request.*\nimport io.ktor.client.statement.*\nimport io.ktor.server.application.*\nimport io.ktor.server.engine.*\nimport io.ktor.server.netty.*\nimport io.ktor.server.response.*\nimport io.ktor.server.routing.*\nimport kotlinx.coroutines.runBlocking\nimport kotlin.random.Random\n\n// 配置\nconst val STABLE_URL = "http://localhost:8081"\nconst val CANARY_URL = "http://localhost:8082"\nvar canaryPercent = 5 // 从 5% 的流量开始\n\nfun main() {\n val client = HttpClient(CIO)\n\n embeddedServer(Netty, port = 8080) {\n routing {\n get("/api/data") {\n val useCanary = Random.nextInt(100) < canaryPercent\n val targetUrl = if (useCanary) CANARY_URL else STABLE_URL\n\n val response = runBlocking {\n client.get("$targetUrl/api/data")\n }\n\n call.respondText(\n response.bodyAsText(),\n contentType = response.contentType()\n )\n }\n\n // 用于更改金丝雀百分比的端点\n get("/canary/set/{perc")