totobobo 运动版(supercool) 口罩简单测评

13 年冬买了第一个 totobobo 的口罩,用起来非常爽,完爆 3M,为此我专门写了一篇博客,后来到了 14 年夏天,大热天的带着口罩走路不大舒服(这个问题现在来看是世界性难题没有一个口罩能彻底解决),像下面这样

于是对比了下几种比较适合在夏天戴的,最终买了 totobobo 的运动款(supercool),像下面这个样子

可以看到跟普通 totobobo 不一样的地方是它需要口吸鼻出,跟游泳一样。买回来之后发现 HEPA 滤网跟贴合它的两个塑料片之间有很大的间隙

这也就意味着过滤的性能可能大打折扣,因为脏空气不经过滤网直接进呼吸系统了。为此特意跟官方的工作人员交流下此事的意见,答复是这是正常的,不会影响过滤效果,如果实在需要贴合的话,可以用热水重新塑形,于是我就用热水把两边的重新塑形了一遍,发现效果还是有的,确实贴紧了。接下来该到路上走两步了,于是每天带着 supercool 走着上下班(单程 3 公里左右),虽然我游泳能适应口吸鼻出的情况,但是在马路上也这么搞,还真有些不大习惯,好在一周之后基本适应。奇怪的是,这段时间的空气质量并不好,但回去肉眼观察到 HEPA 几乎还是白白的,如果是之前戴的非 supercool 的 totobobo,一天就开始发灰了,本着科学的态度,继续带了接近三周,最终的结论是:连续带了一个月,HEPA 的颜色如上图第二章所示,也就是说,这一个月我人肉替它吸了不少烟尘雾霾 ;-(

以上实践证明,supercool 在我这儿的效果非常差。除此之外,由于北方干燥气候加上嘴吸气,所以不管空气好不好,呼吸一段时间时间后嗓子都会明显感受到发干发痒,这是我后来抛弃 supercool 的第二个原因。

根据科学测评(1, 2),totobobo 的舒适性没话说,但是实际的过滤效果在众多的口罩型号中只能说可以接受,大概是中等稍稍偏上的水平,综合考虑的话,还是 3M 的更加靠谱些。

去年底,带了一年多的口罩(上第一张图)遗失在了欧美汇的某个角落里,于是现在我又开始消耗家里之前屯了一大盒子物美价廉的 3M 9501 了 ;-)

stdio 的 buffer 问题

下面会涉及到一些底层的函数库以及系统调用,不想看过程的直接跳到最后看结论好了。
一段代码,通过 tail -f 看打的 log,发现很长时间都没有输出,然后突然一下子输出了好多条,猜想可能跟 buffer 之类的有关系。这个问题其实很早就遇到过,最初以为是什么 bug,直到看到自己写的代码也出现类似的现象之后才决定看看是怎么回事。
先来看看下面这一小段代码。

$ cat demo1.py
import time, sys
for i in range(50):
  sys.stdout.write("test")
  time.sleep(0.2)

$ python demo1.py
testtesttesttesttesttes……ttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest$
可以看到,这堆 test 字符串是等了若干秒之后一下子输出的。

如果我们把 sys.stdout.write("test") 改为 sys.stdout.write("test\n") 即加上换行符号,或者使用 print 函数来输出,发现现象不一样了:
$ cat demo2.py
import time, sys
for i in range(50):
  sys.stdout.write("test\n")
  time.sleep(0.2)

$ python demo2.py
test
test
test
test
test

$ cat demo3.py
import time, sys
for i in range(50):
  print "test"
  time.sleep(0.2)

发现不管是 demo2 还是 demo3,屏幕上均以平均 0.2s 的频率输出 test 字符。
把 demo3 的 print "test" 换成 print "test",(结尾加一个半角逗号)再看看是什么现象。
再用 python3 的 print("test") 试试,尝试加上 end 参数比如,print("test", end="\n"), print("test", end="\t"),print("test", end="") 再试试有什么不同的结果。

再来看一个 demo:
$ cat demo4.py
import time, sys
for i in range(50):
  sys.stdout.write("test")
  sys.stdout.flush()
  time.sleep(0.2)

加上 sys.stdout.flush() 看看跟上面的比有什么不同的效果。

最后一个,代码是 demo3.py,但是运行的方式不同:
$ python demo3.py > output
注意实时观察 output 文件的大小,发现并没有随时间而增大,而是 demo3.py 运行结束了之后才变化的。


上面就是之前遇到的一些现象,这里面涉及到其实是 UNIX 下面的 STDIO buffer 问题。下面会深入现象揭开本质,没时间的看最后的结论即可。

IOS C 标准定义了一套叫标准 I/O 的库,也叫 buffered I/O,这套库被包括 UNIX 在内的系统所实现,包括我们日常使用的众多发行版本。而大家熟知的 open, read, write, lseek, close 这些 I/O 系统调用函数则是 POSIX 定义的,他们通常称为 unbuffered I/O,就是为了跟标准 I/O 库作出区分。这些底层的系统调用函数,大多都是围绕 fd 展开,而标准 I/O 则是围绕着 STREAM 展开,标准 I/O 库其实可以理解为对系统 I/O 函数的封装,因为标准 I/O 库最终还是要调用对应的这些系统 I/O 函数,可以通过 fileno(FILE *FP) 获取到 STREAM 对应的 fd。
为什么说标准 I/O 库是 buffered I/O 了,因为他会自动的帮你处理 buffer 分配以及 I/O chunks 的选择,这样就不再需要为选择 block size 而操心了,这个在使用系统 I/O 调用的时候无法避免,比如 read/write 都需要考虑 buffer 地址以及读取写入的 buffer size,通常你需要在调用 read 时候定义一个 buffer size 的宏:
# define BUFFSIZE 4096

buffered I/O 的主要目的就是为了降低 read/write 这类的系统调用以及自动的为程序分配 buffer。但是他分为了下面三种类似的 buffering:
1. full buffer,当标准 I/O buffer 满了时候发生一次 flush 操作,可以调用 fflush() 来完成,他将 buffer 里面的数据 flush 到内核缓冲区中。
2. line buffer,遇到换行符(一般就是 "\n") 也就是写完一行的时候发生一次 flush,
3. unbuffered,有多少读写多少。

Linux 一般是这样实现的:
1. stderr 是 unbuffered,这会让错误信息及时的出现。
2. stdin/stdout stream 如果不跟终端相关联,比如 pipe,redirect,fopen 打开的文件,则是 full buffer;如果跟终端相关联,则是 line buffer
上面这两条规则其实就是速度跟系统之间的一个 tradoff,很好理解。

可以通过 setbuf/setvbuf 来修改 buffer 的模式,具体的使用方式 man 2,需要注意的是,这两个函数要在 stream 打开之后其余 I/O 操作之前调用,让然,如果你需要做一些特殊的事情,完全可以在昨晚某些 I/O 操作之后再调用,比如下面要举的第二个 demo。setvbuf 比 setbuf 有更大的优势,比如可以修改 buffer 的大小等等。

关于 STREM 对应的 buffer 类型,其大小可以通过这段代码来做一个验证,比如我的机器的几个 buffer size 都是 8KB。

而 int fflush(FILE *fp) 这个函数就是解决我们上面问题的核心了,该函数会将当前 STREAM 中的数据 flush 到内核缓冲区,如果 fp 是 NULL,则 stdout 流被 flush 一次。准确的说,fflush 只能用于输出流,而不能用于输入流,具体的原因见这里
这里的一个 demo 很好的解释了 fflush/setvbuf 做的事情,尝试把 setvbuf 中的 size_t size 参数从原先的 1024 调小到 20 试试看。
很明显,通过这种 buffer 的方式,把一部分的写先 buffer 起来然后统一调用一次系统调用,可以大量的减少 user space 跟 kernel space 之间的切换。

可能会有人想到 fsync 这个系统调用,它跟 fflush 做的事情好像是一样的,其实仔细辨别的,二者做的事情根本不在一个平面上
fflush(FILE *stream) 作用的是 FILE*,对于 stdout 来说,他是将标准 IO 流的 buffer 从用户空间 flush 到内核缓存中。这也是调用 exit 要做的事情。
fsync(int fd) 控制的是何时将 data&metadata 从内核缓冲区 flush 到磁盘中,他的传入参数是一个 fd。对 fsync 来说,FILE* 是透明的也就是所他并不知道 FILE* 的存在,一个是在 user space 一个是在 kernel space。

所以,如果我们不想有 full/line buffer 而是尽可能快的获取到输出流的话,就需要通过调用 fflush(stdout) 指明。

上面解释的仅仅是 C 的,对于 Python 而言,底层调用的东西几乎一样,Python 它自己通过 C 实现了 fflush(),具体的代码可以看这里。其实不单单是 fflush,不少包括 read/write 在内的底层调用 Python 都是用 C 实现的。
对用到 Python 的 fflush 则是 sys.stdout.flush()。

不管是 fflush() 还是 sys.stdout.flush(),都需要对立即返回的 stdout 手动的调用,比较麻烦。所幸的,上面提到的 setvbuf 就可以直接帮我们做这件事,在 stream 打开后调用 setvbuf() 即可,其 mode 参数可以选择下面三种:
1. _IOLBF,line buffer
2. _IOFBF, full buffer
3. _IONBF,no buffer
要完全禁用的话按照下面这种方式调用:
setvbuf(stdout, 0, _INNBF, 0);

对应到 python 的,至少还有下面的几种方式可以避免此类问题:
1. 直接关闭 stdout 的 buffer,类似 setvbuf:
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

2. 有个比较 ugly 的方式,把输出流改到 stderr 上,这样不管什么时候都能保证尽快的输出。

3. 直接脚本的时候加上 -u 参数。但是需要注意下,xreadlines(), readlines() 包含一个内部 buffer,不受 -u 影响,因此如果通过 stdin 来遍历会出现问题,可以看看这两个连接提供的内容(1, 2)。

4. 将其 stream 关联到 pseudo terminal(pty) 上,script 可以做这事情的:
script -q -c "command1" /dev/null | command2
或者通过 socat 这个工具实现,

再来看个跟 pipe 相关的问题, 这个命令常常回车之后没有反应:
$ tail -f logfile | grep "foo" | awk {print $1}

tail 的 stdout buffer 默认会做 full buffer,由于加上了 -f,表示会调用 fflush() 对输出流进行 flush,所以 tail -f 这部分没什么问题。关键在 grep 的 stdout buffer,因此它存在一个 8KB stdout buffer,要等该 buffer 满了之后 awk 才会接收到数据。awk 的 stdout buffer 跟终端相关联,所有默认是 line buffer。怎么解决这个问题了,其实 grep 提供了 –line-buffered 这个选项来做 line buffer,这会比 full buffer 快的多:
tail -f logfile | grep –line-buffered  "foo" | awk {print $1}

除了 grep,sed 有对应的 -u(–unbuffered),awk(我们默认的是 mawk) 有 -W 选项,tcpdump 有 -l 选项来将 full buffer 变成 line 或者 no buffer。

不仅仅是 stdin/stdout/stderr 有 buffer 问题,pipe 同样有 buffer 的问题,相关的文档可以看这里(1, 2)。

上面的方式都涉及到了具体的函数调用,修改参数的不具有普遍原理,对于普通用户来说,不大可能这么操作。其实 coreutils 已经给我们提供了一个叫 stdbuf 的工具。expect 还提供了一个叫 unbuffer 的工具,通过它可以将输出流的 buffer 给禁止掉,另外,在 pipe 的应用中,可能会出现一些问题,具体的 man 一下。因此,上面的问题可以更具有普遍性:
tail -f logfile | stdbuf -oL grep "foo" | awk {print $1}

看到这里最上面的几个问题现在应该非常容易回答了。

ref:
http://www.pixelbeat.org/programming/stdio_buffering/
http://stackoverflow.com/questions/16780908/understanding-the-need-of-fflush-and-problems-associated-with-it
http://stackoverflow.com/questions/23334147/misunderstand-line-buffer-in-unix

http://mywiki.wooledge.org/BashFAQ/009

大陆地区的 DNS 劫持

撇开方院士的那个不谈,这种方式在大陆的流行情况我仅仅是听说,收到过我们客服的反馈而已,至于这个具体有多么的「生动」,我还真没遇到过(正常情况下,如果你的所有的流量都走 proxy,是不会遇到那种浏览器右下角弹广告的情况的,所以建议有能力的用户不管在哪里访问网络,都请主动走 proxy)。
直到我们开始启用了某个第三方的服务监控,才发现这个比例还不小,数据还是蛮有意思的,我找了两个列子,基本上一个 nslook 就能看出问题所在了。


这事要根本解决没强大的背景搞不定啊。

另外,这事对业务有没有影响,肯定是有的,但是有多大,还是要业务,比如面向最终用户的,一个访问了就是投诉过来了,但是如果是类似我们的这类看总体指标的业务,其实目前来看只要不是一个地区的大规模的劫持,还是能容忍的。

HTTP 资源的同步

breed 为 yum, deb, rsync 的都很好做,直接通过 cobbler 内置的 repo 同步很快就可以完成,即使没有 cobbler,一个脚本也能搞定,比如我们线上 Ubuntu 12.04 的就是通过 debmirror 的方式同步完成的。
但是对于 HTTP 的方式,我找了一圈没有找到很好的方式,最初我是使用的 wget,发现效率太低,后来有推友推荐使用 lftp,使用之后发现果真是半个神器,虽然达不到 rsync 那样的智能的增量,但是对于更新的不是那么频繁的资源的同步也基本能够满足要求了。
最初是写了一个 expect 脚本上去跑,后来发现还有 non-interactive 的方式,-c/-f 都可以,放到 cron 里面确实很方便:
$ cat  lftp.txt
open http://maven.twttr.com
mirror -c -n  –parallel=10 ./ ~/resources/maven
quit

$ lftp -f lftp.txt

除了上面这个问题还有个几年内无法突破的问题,方院士,自己的 PC 遇到,服务器自然也会遇到,同步到本地一是出于上面的考虑,另外一点还是为了节省带宽以及其他的机器资源消耗。目前专门有一台 VPS 供我们线上使用,在线上搞了一个 proxy,实现的方式太多了(shadowsocks + polipo),具体的就不说了,提供 http, https 的方式,基本能满足目前的需求了。

通过 tcpcopy(pf_ring) 对 BCM 5719 小包做的多组 benchmark

tcpcopy 在文档化、用户参与方式方面有很大的提升空间这个问题在之前已经专门说过。最终,在我们自己阅读代码的情况下,结合 pf_ring,坚持跑通了整个流程,用其对目前 BCM 5719 型号的网卡做了多组对比,结论见结尾。
使用 tcpcopy 做 benchmark,务必确定 tcpcopy 语法使用的正确性, 尽管互联网上绝大多数的文档以及官方文档都写的含糊不清。
比如,我们之前把过滤条件 -F "tcp and dst port 80" 写成了 -F "tcp and src port 80",造成的结果是在错误的基础上得出了一些奇葩的数据以及无法解释的现象,比如到 target server 的流量特别小,并且及其的不稳定,得到的 pps 也是非常的不稳定大到 400kpps/s,小到 20k  甚至 0。

这里列举我们测试环境使用的一组配置。
所有机器都是 RedHat 6.2 2.6.32-279.el6.x86_64,Tcpcopy 1.0 版本,PF_RING 6.0.1,BCM 5719,双网卡 bonding。另外,我们单台生产机器的流量都比较大,并且绝大多数都是 100B 以下的小包,所以对系统的要求还是比较高的。
为了避免后续的误解,先确定几个用语:
1. online server(OS): 即我们线上生产环境的机器
2. target server(TS): 是我们想做 benchmark 的那台机器,以测试其究竟能支撑多大的量
3. assistant server(AS): 是安装了 intercept 的机器
4. mirror server(MS): 通过 tcpcopy 的 mirror 工具把 OS 的流量复制到该台机器上

我们一共做了三大组 benchmark,每组下面包含若干小的 benchmark,包括:
1. tcpcopy + intercept
2. tcpcopy + intercept + tcpcopy mirror,加一个 mirror 是为了减少对 OS 的负载,这样 tcpcopy 在 MS 上运行
3. tcpcopy + intercept + 交换机 mirror,更直接的方式复制线上流量,保证对 OS 没有干扰,并且能保证 100% 全量复制 OS 的流量

Continue reading