腾讯云数据库MongoDB作为一款基于开源社区MongoDB版本的文档数据库产品,其承载着公司内外包括微信、看点、QQ音乐在内的亿级用户重量级APP产品。在某些场景的使用过程中,用户在客户端请求超时后会不断重试,可能导致服务端大量请求积压,出现恶性循环甚至导致服务雪崩。一般遇到这种情况,后台会自动检测并做服务降级,主动拒绝一部分用户请求,或者重启后端服务等举措来应对。但是这些措施对业务有损,或者不可自行恢复。
本文围绕 MongoDB 原生 maxTimeMS 特性和腾讯云MongoDB的优化,并结合 4.0 版本代码,详细阐述如何巧用 maxTimeMS 服务端超时,来避免服务端请求积压导致雪崩的情形。
背景
业务方在腾讯云MongoDB运营过程中,曾有业务集群出现过:慢请求 -> 客户端断开重试 -> 服务端累积的请求越来越多 -> 服务雪崩 -> 人工重启解决的问题。
过去,为了防止服务雪崩,腾讯云MongoDB应对的解决方案是:在内核中实现了连接状态检测、自适应限流等功能进行过载保护,并开发了外围工具 kill 长时间运行的请求等。但是过载保护本质上会进行服务降级,对业务还是会产生一定影响,而外围工具处理起来也不够及时,甚至可能在节点 hang 住时无法工作,以上解决举措都存在着一定程度的弊端。
为了更好地避免服务雪崩,腾讯云MongoDB建议设置服务端超时,并和客户端超时保持一致。这样在客户端出现超时后,服务端也立刻终止这些“无意义”请求的执行。通过避免服务端资源的无效占用,极大地降低客户端不断重试导致服务雪崩的概率。
MongoDB原生服务端超时原理
当一个用户请求到达 mongos 或者 mongod 时,会生成一个对应的 OperationContext 对象,来记录这个请求从开始到结束期间的完整上下文信息。上下文信息中就包含了后面要介绍的“时间信息”:起始时间,已执行时间,超时时间,以及是否是 kill 状态等。相关成员和接口如下图所示:
其中 checkForInterrupt() 接口的作用就是检测请求是否应该终止,需满足以下 3 个条件之一:
1.实例处于 shutdown 状态;
2.用户使用 killOp 命令,调用 markKilled() 接口,将 OperationContext 标记为了终止状态;
3.用户通过 maxTimeMS 参数给 OperationContext 配置了超时 Deadline,然后检测到 hasDeadlineExpired() 为 true;
一个请求在执行过程中会多次调用 checkForInterrupt() 检测自己是否超时,比如在获取Global ticket,获取资源锁,yield,内部迭代重试等阶段会主动检测并超时退出。
备注1:原生 MongoDB 在 3.7.3 版本通过 SERVER-33473 (https://jira.mongodb.org/browse/SERVER-33473)才支持获取 Global Ticket 时能够主动超时和打断,因此更低版本在 qr/qw 较大,请求排队比较严重时无法及时超时退出;而且在 3.7.3 版本通过 SERVER-32638 (https://jira.mongodb.org/browse/SERVER-32638)才支持获取资源锁时主动超时,因此更低版本在获取互斥锁卡住时也无法及时超时退出。
因此,为了有更好的体验,强烈建议升级到 4.0 或更高版本。
<