#网络安全#一行代码引起的协程泄露( 二 )


不出所望依然是过了一两周 , 问题又来了 。。
暂时是最后一次猜想这次是大半夜突然连续出了两三次 , 而且出的频率还特别高 , 迁移了实例后过一两个钟又会有实例协程泄露 。 所以恰好发现 , 所有会造成突然协程泄露的操作 , 都是调用了一个写数据的接口导致的 。 把接口来回看了好几次 , 硬是没看出问题 。 但是我意识到一件事 , 这个接口调用了三次db , 而我设置db查询/写的超时时间是3s , 而上游却过了10s才熔断 , 那么按道理来说应该是9s后会超时 , 感觉到了事情有一点不对 。
所以再一次协程泄露时 , 我暂时没有迁移实例 , 而是爬上了实例查看实例日志 , 发现某一行debug日志并未执行 。 看来问题已经缩小到某几行代码了 。
没错 , 我在开始怀疑gorm框架有问题了 , 看起了源码 。 发现我们实现查找最大值的代码很神奇 , 竟然是拿了rows 。 代码如下 。
乍一眼看 , 貌似没啥问题 , 看一下next的源码 , 在没有下一行数据时 , 不会将rows close掉 , 代码如下 , 代码来自go 1.13.4源码 , 只有中文注释是自己加上的:
 
咦这个不close真的不会有问题吗? 我们再看看这个rows是从哪里拿出来的 , 没错线程池 , 那么不close掉是不是会导致这个线程不会放回去线程池里呢? 我们看看close的代码 , 代码如下:
事实上 , 应该是的 。 所以事情到这里应该是已经解决了 。解决完的代码如下:
但是实际上取max值只会有一个值 , 为何会使用rows而不使用row呢?不得而知 。 因为根据go源码来看如果这么写的话是不需要自己去close掉线程的 。
还有另一个问题是 , 为什么会两周一次呢 , 算一算 , 实例差不多有15台 , 而进行这个查max的操作只有在申请某个配置的时候才会触发 , 而线程池里有10个线程 , 所以在假设请求是均衡的情况下 , 要申请100+次才会开始命中这个问题 。 而且也只有db线程数不够的机器才会出现这种问题 , 再加上这个服务已经相对比较稳定了 , 很久没有怎么加过需求了 , 所以容器不会重启 , 内存不会重置 。 至于GC到底能不能把解决这个问题呢 , 应该是不行的 , 因为解决了只会让你的线程数减少 。
哎 , 真的是菜 , 定位问题都那么难 。 主要还是没啥经验吧 , 所有接口都报错了 , 一开始无从下手 , 直到某天凌晨给报警电话打醒 , 才突发奇想去定位这个问题 。
内存全表存储设计为什么要设计用内存呢?首先 , 表不多 , 其次 , 数据也不太多平均每张表也就5k , 而且 , 由于并不希望上游每次拿数据都需要请求这个服务 , 所以需要扫表 。 基于以上原因 , 该服务是没有用redis做缓存的 , 服务设计如下 。



这样设计的优缺点是什么呢优点:
  1. db压力小 , 数据量不多的情况下扫表问题不大 。
  2. 当上游服务多时 , 实例充当了一个redis , db没有来自上游的请求压力 。
  3. 没有序列化和反序列化的时间复杂度
缺点:
  1. 开发成本高 , go没有泛型 , 为了减少序列化 , 代码写的比较暴力
  2. 更新效率慢 , 取决于数据轮训的时间 , 适用于不需要及时更新的数据
结语【#网络安全#一行代码引起的协程泄露】这个服务是刚进公司的时候学到的 , 没想到会有隐藏问题 , 而且还藏了那么久 。
服务设计是挺不错的 , 就是开发起来特别恶心 。
还是自己太菜了 , 多学点东西吧 。


推荐阅读