前言
在传统的 Java 后端(如早期 Tomcat)中,一个请求通常占用一个线程。如果并发量大,线程池会迅速枯竭,内存也会因为线程过多而爆表。
作为 Kotlin 世界的亲儿子,Ktor 从诞生的第一天起就是完全基于协程的。 这种架构让它在极少的线程资源下,就能处理海量的并发连接。今天我们就来拆解 Ktor 后端的并发模型。
一、 Ktor 的“无丝分裂”:非阻塞 IO
Ktor 默认使用 Netty 或 CIO (Coroutine I/O) 引擎。
- 传统的阻塞 IO:线程等数据(白干活,占着茅坑不拉屎)。
- Ktor 的非阻塞 IO:数据没来?协程直接挂起(Suspend),线程立刻去处理别的请求。等数据来了,协程恢复。
二、 实战:处理耗时操作的正确姿势
这是很多移动端转后端的开发最容易踩的坑。
❌ 错误示范:在 Route 里写阻塞代码
get("/slow") {
Thread.sleep(5000) // 警告!这会阻塞 Netty 的事件循环线程
call.respondText("Done")
}
✅ 正确示范:利用 Dispatchers.IO
get("/slow") {
val result = withContext(Dispatchers.IO) {
// 执行耗时计算、大文件读取或数据库同步查询
performHeavyTask()
}
call.respond(result)
}
三、 并发冲突与协程安全
当你在后端处理全局状态(如在线人数、内存缓存)时,必须注意协程安全。
- 不要使用
HashMap:多个协程同时写会导致数据损坏。 - 使用
Atomic或Mutex:
val mutex = Mutex()
var counter = 0
post("/increment") {
mutex.withLock {
counter++ // 确保原子性
}
call.respond(counter)
}
四、 给十年老兵的架构建议
- Context 传播:在后端,协程上下文(CoroutineContext)常用于传递 TraceId。这对于在海量日志中追踪某一个用户的请求全链路至关重要。
- 超时控制:后端 API 必须有强烈的超时意识。利用
withTimeout包装每一个外部调用,防止上游挂掉导致你自己也被拖死。 - 结构化并发:Ktor 的每一个
ApplicationCall都有自己的协程作用域。这意味着当客户端断开连接时,该请求启动的所有子协程都会被自动取消,避免资源浪费。
结语
协程不是魔法,但它让后端的资源利用率达到了极致。理解了挂起与恢复,你就掌握了高性能后端的钥匙。
下一篇,我们将进入持久化领域 —— Exposed ORM 实战:在 Kotlin 中优雅地操作数据库。