KeepGoing


  • 首页

  • 归档

Mongodb Cursor Not Found

发表于 2019-02-22 | 分类于 技术
  • 在个人的私有项目实践中,使用了腾讯云的 cmongo (base on mongodb v3.2),在端详日志的时候,发现偶尔会出现下述问题:
    • Cursor not found (namespace: ‘db_name.collection_name’, id: cursor_id).
    • 频率大概是 3次/天

依赖信息

  • mongodb (cmongo: v3.2), 在腾讯云上以副本集方式部署

  • python 模拟业务代码一份

问题分析

  • google 关键词

    • 关键词: mongodb cursor not found
    • link: MongoDB - Error: getMore command failed: Cursor not found
    • 概要: 大部分描述是由于 timeout 导致的,timeout 默认时间大概是 10 分钟
    • 现象分析: 由于模拟的业务代码里是 delete 操作而且从数据量上分析,要 cursor 超过 10 分钟的概率非常小,暂时摒弃从 timeout 上分析的路径
    • 附上一个 (v 3.6+) 相关 Jira 链接 Session cache refresh can erroneously kill cursors that are still in use,大概讲了 cache refresh 导致 cursor 被清掉了, 这个挺靠谱的
  • 下载源码 mongodb (v3.2) 分析

    1. 搜索 “cursor not found” 关键字
      • 找到对应的源码文件在 cluster_cursor_manager.cpp 中,由 “cursorNotFoundStatus” 定义。这个错误可以由 killCursor,detachCursor_inlock,checkOutCursor等函数抛出,从该源文件看,该类型错误可能由 集群关闭 / cursor没有注册成功 / cursor处于 kill pending(如: timeout) 等原因状态导致
    2. 由于腾讯云的监控显示在 cursor not found 出现的时候集群状态是正常的, 所以由于集群关闭导致的可能性不大
    3. 从 cluster_cursor_manager.cpp 中可以看出 cursor 是由 cursor_manager 管理的, 在 cursor_manager.cpp 文件中可以找到 timeoutCursors,eraseCursor,invalidateAll 等相关方法
    4. 全局搜索与 cursor_manager 相关的 invalidateAll 方法,可以在 index_catalog.cpp 中找到相关的代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      Status IndexCatalog::_dropIndex(OperationContext* txn, IndexCatalogEntry* entry) {
      // ...
      // TODO only kill cursors that are actually using the index rather than everything on this
      // collection.
      if (entry->isReady(txn)) {
      _collection->getCursorManager()->invalidateAll(
      false, str::stream() << "index '" << indexName << "' dropped");
      }
      // ...
      }
    5. 由上述代码以及注释可以发现如果删除一个 collection 的 index 的话,会同时 invalidateAll cursors,可以尝试通过该手段去验证是否会报 cursor not found 错误

  • 复现方法

    • 写一段代码获取某 collection 的 curosr, 迭代该 cursor, 并在每次迭代中 sleep 1 秒
    • 在另外的 session 中开一个 mongo shell,并在其中创建然后删除该 collection 下的 index
    • 可以发现我们的模拟代码会抛出 cursor not found 的错误

总结

  • 虽然能够复现出 cursor not found 的问题,但是跟实际上的日志错误还是有一定差别的,实际的日志错误是带有 namespace 等信息的,但是在我们的复现路径中并不能看见如此信息。
  • 从代码实现以及注释上看,导致实际的 cursor not found 的原因极可能由于 curosr manager 对 cursor 的 “粗粒度” 管理导致的
  • 至于最后的解决方案: 可以通过 try catch retry 的方式去对实际业务代码进行相对应的改造修复

General Cache Layer Base On Kong

发表于 2018-12-22 | 分类于 技术
  • 在接 RESTful api 接口开发实践过程中, 我们常常需要借助一些缓存策略去提高接口的响应速度以提升用户体验。此外,恰当的缓存策略也有利于降低后方数据库承载的压力。当然,缓存维护也是需要一定成本的,在数据更新的时候我们也需要同时使得缓存更新/失效。在设计开发缓存层的时候,我们应尽量减少对业务层的侵入,设计尽可能通用的缓存层。

  • 本示例将基于 Kong CE (https://konghq.com/) 从原理上改造设计一个业务通用的 L7 缓存插件, 参考源于 kong-plugin-response-cache (https://github.com/wshirey/kong-plugin-response-cache) 。

  • 注意⚠️: Kong EE 版本是带 Proxy Cache 的, 具体实现方法不详, 有需要的可以自行体验。

系统结构

  • 说明: 在系统结构图中我们可以看到,由于 Kong (proxy) 的介入,我们可以把通用服务(如: 缓存/认证/日志等)与业务服务剥离开, 使得服务职责/功能趋向单一化。我们即将改造设计的缓存层也是以插件的形式运行在代理层的。

缓存层考量点

缓存内容

  • cache key:
  • http method (GET/POST…)
  • http uri (/api)
  • http header (Host…)
  • query string (page=xxx)
  • http body ({“name”: “wilder”})
  • cache value
  • http response
  • 说明: 按需组织所需要的 cache key (如: http_uri:qs_k1=qs_v1)并将对应的http response 写入到 redis 缓存中

  • 注意⚠️: 要对cache_key进行排序,不然可能会导致大量缓存击穿以及缓存层被快速占满

失效触发机制

  • 在 kong plugin schema 中定义相关的标志位, 如果该请求满足对应的 “REST” 条件(如: http_method in [POST/DELETE/PATCH/PUT] AND uri = “/api”) 则触发对应的失效机制

  • 失效方案设计成在 redis 中删除符合特定 PATTERN 条件的 keys, 该 PATTERN 需要在 plugin schema 中定义

  • 在写入缓存的时候, 设置对应的 expire time,超时自动失效

部署方案(Base On Kubernetes)

kong (kong proxy)

  • 以 DaemonSet/Deployment 的方式将 kong proxy 部署到集群的每个节点上。 在 kong proxy 上层挂在指定端口的 NodePort,将该 Service 挂载到 Load Balancer 后端

  • 注意⚠️: 如果需要用 Deployment 在每个节点部署一个或一个以上的 Pod,应该需要自定义调度策略的 ( 这里可以思考一下,有什么业务场景需要在每个节点上都部署一个 proxy )

kong admin

  • 以 Deployment 方式部署 vpc/cluster 内访问的 kong admin 即可, 该 admin 接口提供管理服务/路由/插件等能力。

  • 注意⚠️: 除非本地开发环境,该 admin 接口万不能部署到公网环境上

kong admin gui

  • 以 Deployment 的方式部署 vpc/cluster 内访问的 pantsel/konga (kong admin gui), 该服务以图形化的形式提供 kong admin 接口管理能力。

  • 注意⚠️: 目前版本(DATE: 2018.12.22)只支持 0.14.x 版本的 kong admin, 1.0.0 版本的 kong 有太多的 break changes(如: service schema changed), 所以就目前情况而言,社区版的 GUI 暂不支持 1.0.0 的 kong, 如需要 1.0 的 admin gui, 可以尝试使用 Kong EE

效果

缓存击穿效果

  • 说明: 如上所示,缓存击穿的时候首次请求耗时为 5054 ms

缓存命中效果

  • 说明: 如上所示,缓存命中的时候首次请求耗时为 51 ms

关联接口导致缓存失效说明

  • 该通用缓存的设计可以达到用户在调用特定 api (如: POST /api/xxx) 的时候使得之前产生特定 pattern 的缓存的 key 批量失效。 下图为失效后再次命中缓存的例子, 请注意此次缓存命中中标红的字段 “total” 已经是 23 了:

Container Network Hangs

发表于 2018-10-29 | 分类于 技术

项目实践过程中,我们用 Celery 解耦邮箱邮件的解读与分析,将其构建成 Docker 镜像以容器的方式运行在公有云的 k8s 的集群中。

环境

  • python 3.6, celery 4.1.0, kombu 4.1.0, redis 2.10.6
  • k8s 版本 1.4.6, Docker version 1.12.6, OS ubuntu 1604

现象

  • Celery beat 正常工作, redis 任务持续堆积
  • Worker 在某个时间点之后不再消费任务队列的任务, 没有任何错误日志输出
  • Redis工作正常, 集群中的其他容器服务正常

分析

Google celery worker hangs

  • Celery worker hangs without any error

netstat -n

  • 说明: 在容器内执行上述命令, 其中 993 端口是 imap 服务常用连接端口。

strace -p pid

  • 注意: 因为项目运行的容器非特权容器, strace 等命令无法在容器内执行的。需要到该 pod 被调度到的节点上执行。 查找节点上对应的进程, 详情参考 docker inspect。
  • 说明: strace结果表明程序阻塞在 “read(22, …” 上, 22 是文件描述符的标示

lsof -i

  • 说明: ID 22 的文件描述符对应打开的连接是 imap 的服务连接。由此推测程序应该是在 read 22 的时候由于没有设置超时时间被阻塞了。此外, 上图中 imap 的服务器为 gmail 的服务器,国内访问存在一定的障碍,可以进一步加强佐证。

验证

tcpdump -i eth0 tcp -w out.pcap

  • 容器内执行上述抓包命令, 与此同时执行telnet tk-in-f108.1e100.net imaps
  • 把 out.pcap 文件拷贝到本地,用 wireshark 进行分析
  • 上图表明, 容器与该 google imap server 连接相对不稳定,在握手阶段发生多次重传以及重复的 ack 。进一步加强佐证。

简单修复

  • 为了不阻塞其余的任务,在 imap 的 socket 连接中加上 timeout 超时时间限制。

Wilder Chen

3 日志
1 分类
10 标签
© 2019 Wilder Chen
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4