2018年12月

  • 从一个 Bug 讲起
  • 我看到的乱象
  • 我推荐的解决方案

一个奇怪的 Bug

  • 网盘的上传服务
    Windows 下 IE, Chrome 浏览器下正常

Linux 下 Firefox, Chrome 浏览器下正常
Windows 下的 Firefox 不正常, 上传失败。

如何定位Bug

  • 首先查看浏览器的控制台,观察是否有错误(都没有)
  • 用 Wireshark 截获请求包,发现失败的案例服务端返 回412(通常为200)
  • 对比 HTTP Request,发现失败案例的 HTTP 头有 “Content-length”,而其他情况下为 “Content- Length”
  • 用 telnet 发送不同的请求来验证结论
  • 确认原因是服务端对 Content-Length 的 大小写敏感,与标准不符,造成了这个 Bug
  • 通知服务端开发人员尽快修复

好Bug/坏Bug

  • 能再现的 Bug 就是好 Bug
  • 如果不能再现,能拿到异常栈或者详细 日志的也是好Bug
  • 对于坏 Bug, 他的最大用处就是督促你补 充日志

另一个奇怪的Bug

  • 从公司访问网站有12s延迟
  • 直接 ping 响应很快
  • 从其他网络来源访问没有问题
  • 监控没有问题, 也没有客户来抱怨此事

先猜猜看

  • 公司路由出问题了?
    重启一下路由试试看?

但为什么只有访问我们自己网站会出事?

  • 人品问题?
    做程序员还是唯物一点比较好
  • 不管,反正没人抱怨这事
    99% 的用户会遇到问题时不会报告

分析

  • tcpdump抓包
    客户端: 发了6个 SYN 包,到最后一个才收到 SYN/ACK

服务端: 确实收到了6个 SYN 包, 而且确实前面5 个 SYN 包都没有回复
从其他网络访问,确实在首个 SYN 包之后返回

  • 结论: 问题跟路由/线路无关,确实是服务端的问题
  • 再重新分析tcpdump的记录,发现前SYN 包带了 timestamp, 第6个没带
    尝试关掉客户端 TCP timestamp, 瞬间响应

尝试关掉服务端 TCP timestamp, 瞬间响应

  • 结论: Bug 与 timestamp 相关,但仍然无法 解释为什么其他网络没问题

解决方案A

  • 关掉服务端的 TCP timestamp
    TCP timestamp 会影响传输速度,但影 响不大
  • 疑问仍然存在,为什么服务器不响应某 些 timestamp 包, 有时候又能正常响应
  • 看内核源码 (CTO 亲自操刀)
    入口机器有大量的反向代理,端口经常不够用,所以开 启了TIME_WAIT 端口重用

开启了 TIME_WAIT 端口重用后,服务端要求同一个IP的 SYN 包 timestamp 必须是顺序的
办公网络是内网, 并且对我们官网访问很频繁,导致故障发生

  • 看来找到真实的原因了, 有没有更好地解决方案?

解决方案B

  • nginx 高版本已经支持 keep-alive
  • 升级 nginx, 启用 keep-alive, 降低端口占用
  • 关闭端口重用,并加强端口数的监控和 报警

我所看到的乱象

  • 用寻找 workaround 代替解决问题
    换个浏览器吧

重启, 清 cookie, 清 cache, 重新登录
现场被破坏得干干净净
对于那些不易再现的 Bug, 就损失了一次修 复的机会

  • 尝试用试错法解决所有问题
    升级依赖包, 升级插件, ...

随便猜一个原因,改两句代码,然后 重新测试

  • 缺少反省
    寻找到 workaround 便认为问题解决了

没有用测试固化 Bug, 容易产生回归Bug
一个 bug 可能在多处出现,没有尝试搜索其他有 Bug 的地方
没有通过补充日志等手段来降低日后定位Bug的难度

  • 海恩法则: 每一起严重事故的背后,必然 有29次轻微事故和300起未遂先兆以及 1000起事故隐患

我们推荐的方法

  • 出了问题不要靠猜
    浏览器

服务端日志
抓包工具

浏览器看什么?

  • 是否有 JS 错误?
  • 网络请求是否发出?
  • 发出的请求是否正确: URL, 方法, 参数, Accept, Cookie
  • 期望的返回值是什么?
  • Request-Id

Request-Id

  • 对每次请求产生一个唯一的Id
  • 该 Id 以 HTTP Response Header 的方式 发送到客户端
  • 可以在 nginx 层面实现或者在业务逻辑层 面实现

服务端日志看什么?

  • 四要素:
    时间: 开始时间,总耗时

谁: 用户Id, Session-Id, Request-Id
做什么:URL, 方法,XHR?, format, 参数(注意保护密码)
结果是什么?

  • 对于其他服务的请求
    邮件,短信,其他服务...

记录请求详情和耗时

但是经常没有足够的日志

wireshark tcpdump

  • 抓包工具
  • wireshark: 有界面
  • tcpdump: sudo tcpdump -n -s 4096 -w 1.log port 80

其他工具

  • strace/dtruss: 系统调用跟踪工具
  • lsof: 列出文件打开情况
  • valgrind: 查内存泄露
  • ltrace: 查询库调用

总结

  • 我们推崇通过一种系统的方法来分析问题,寻找问题的根源
  • 我们反对只靠试错法来解决问题
  • 能再现的 Bug 是好 Bug, 如果不能再现,也要拿到 对应的网络请求和日志
  • 如果这次解决不了 Bug, 那么就改善你的日志,确 保下次 Bug 出现的时候能解决他

摘自https://github.com/lidaobing

特点:

1.有序元素
2.每次从中间值开始查

工作原理

1234……99100

假使有一列1~100的元素,我随机想一个元素,让你猜。你的目标是以最小的次数猜到这个数字。你每次猜测后,我都会说大了、小了或对了。

1.假使你从1开始猜,每次猜测只能排除一个数字,如果我想的是100,你得猜一百次。
2.二分查找,从50开始猜测。你猜50,我说小了,说明数字在50~100之间。你猜75,我说小了,又排除一半数据。这样你便可以在7次就能找到准确的数据!因为每次都会排除很多数据。(这也解释了,为什么二分查找算法只适用于有序元素)

问题:1~1024这列元素,需要猜几次呢?

答案是10次,$\log_2{1024}$,为什么呢?因为在使用二分查找时,每次排除一半元素,直到最后只剩下一个元素,所以二分查找最多需要$\log_2{n}$步。
这里普及一下数学知识,对数运算是幂运算的逆运算,也就是说$\2^10$=1024 <=> $\log_2{1024}$=10。

代码展现形式

<?php
//二分查找法
function binSearch($arr,$search){
    $height=count($arr)-1;
    $low=0;
    while($low<=$height){
        //由于php取商是有小数的,所以向下取整,不过也可不加,数组也会取整
        $mid=floor(($low+$height)/2);//获取中间数
        if($arr[$mid]==$search){
            return $mid;//返回
        }elseif($arr[$mid]<$search){//当中间值小于所查值时,则$mid左边的值都小于$search,此时要将$mid赋值给$low
            $low=$mid+1;
        }elseif($arr[$mid]>$search){//中间值大于所查值,则$mid右边的所有值都大于$search,此时要将$mid赋值给$height
            $height=$mid-1;
        }
    }
    return "查找失败";
} 
//二分查找递归实现
function binSearch2($arr,$low,$height,$k){
    if($low<=$height){
        $mid=floor(($low+$height)/2);//获取中间数
        if($arr[$mid]==$k){
            return $mid;
        }elseif($arr[$mid]<$k){
            return binSearch2($arr,$mid+1,$height,$k);
        }elseif($arr[$mid]>$k){
            return binSearch2($arr,$low,$mid-1,$k);
        }
    }
    return -1;
}
 
//顺序查找
function seqSearch($arr,$k){
    foreach($arr as $key=>$val){
        if($val==$k){
            return $key;
        }
    }
    return -1;
}

那么二分查找法的时间复杂度是多少呢?

假设检查一个元素需要1毫秒。使用简单查找时,必须检查100个元素,因此需要100毫秒才能查找完毕。使用大O表示法,这个运行时间怎么表示呢?,时间为O(n)。
二分查找则为O($\log_2{n}$),即O (log n )。