由一个 LB 错误配置导致的 site issue 引发的思考

有个内部员工报了一个 issue, 支付成功之后, 弹出支付成功, 可是还能看到 "支付" 按钮. 过一段时间刷新页面, 还仍旧能看到那个"支付"按钮. 找到对应的开发团队, 开发团队检查之后, 发现整体趋势没有看到任何问题, 特地检查他这一笔, 发现有个事件通知的消息没有消费成功. 到底哪里出问题了? 诊断这种问题从架构/设计/实现技术角度有没有更好的办法?


开发团队仔细检查这笔交易, 发现支付成功之后, 有个支付成功的异步消息需要被某个 service 应用A 去消费, 应用A 消费这个消息的时候, 需要调用下游某个应用B, 可是调用应用B 的时候, 应用B 返回了 403, 导致整个事件没有被成功消费. 索性有不断的重试机制, 过了一段使用后这个事件被成功消费了.

那么问题变成了: 为什么一开始应用B 返回 403, 后来又调用成功了呢?

在应用A 的日志里, 能看到应用B 返回了 403, 但是日志里面没有抛出任何 error/Exception, 这导致应用A 里很难过滤并聚集这种错误类型. 同时整个处理的 Transaction 虽然失败了, 没有消费成功, 可是这个业务却没有报任何错误. 这种吃掉异常的错误做法导致有问题很难被发现.

在应用A 的日志里, 虽然有些 403, 但是没有给出更多信息. 通常我们的应用都会返回一个 unique 的 logID, 它能唯一定位整个 site 的唯一条业务的日志, 虽然这个 403 在 header 里返回了这个 logID, 但是却没有打印出来, 导致不能很快定位到有问题的下游日志. 所以, 我们推荐在平时返回 2xx 的时候, 这些 logID 可以不打印日志, 但是一旦非 2xx, 打印出请求的 header & payload 以及 response 的 header & payload 非常重要.

尝试在已知的下游应用的日志里面搜索某些特定 ID 去查找服务端的处理日志, 不断的扩大范围, 总是找不到. 这个时候就想在客户端应用A 这边是不是通过抓 tcp 包的形式, 能抓到具体的 request/response. 通常这些下游应用的 endpoint 都可以通过某些管理页面暴露出来, 可是这个 servcie 调用竟然不遵循常规的协议, 没有暴露出来. 只能尝试通过查看网络连接状态的方式去模糊匹配. 由于这个到下游的连接并不多, 也不是长连接(keep-alive) 导致几乎找不到一个模糊匹配的. 最后通过查看Java 的 DNS 的 history 信息, 模糊匹配到一条记录. 这里我们发现几个问题: 1) 如果请求不是特别少, 使用长连接(keep-alive) 没有什么坏处; 2) 遵循常规的协议, 方便大家按照统一的思路去查问题.

通过对找到的这个 endpoint 的 IP 地址抓包, 我们找到了几条返回 403 请求的 response, response header 里面的 logID 帮助我们很快定位到了下游处理这个请求的日志. 非常奇特的是: 这个服务端的 server 竟然是另外一个 service 的server. 也就是说我们使用的 endpoint 是对的, 不过这个 endpoint 的 LB 后面错误挂了另外一个 service 的 server 去处理这个请求. 所幸的是这个是 http 的请求, 不是 https, 我们抓包很容易就能看到具体的 request/response. 如果是 https, 那么就导致要通过代码注入的方式去观察更多日志了.

到此, 如何导致了这个问题, 从 LB 错配之后基本搞明白. 就是 LB 后边错挂了一个 server, 导致如果命中这个 server 那么就有问题, 否则就是好的. 因为是处理异步事件, 不成功会继续尝试, 只要不是一直命中这个错误的 server, 总归会成功.

获得

  1. 有任何错误, 请一定不要吃掉, 请及时报告, 写到日志里面标记出来;
  2. 微服务的环境里如果有调用非 2xx 的response, 最好把 request 的 header & payload 以及 response 的 header & payload 都打印出来, 方便查看原因或重试;
  3. 如果有共同的规范暴露一些信息, 请遵循特定的规范, 它方便拍错.
  4. 大量的微服务, 一定要有办法通过某种方式把日志串起来, 比如通过 logID;
  5. 暴露最近查询的 DNS 非常有必要;
  6. 可以设置某些开关, 打印出错的更多信息.

标签: none

添加新评论