TCP queue 的一些问题

先来回顾下三次握手里面涉及到的问题:
1. 当 client 通过 connect 向 server 发出 SYN 包时,client 会维护一个 socket 等待队列,而 server 会维护一个 SYN 队列
2. 此时进入半链接的状态,如果 socket 等待队列满了,server 则会丢弃,而 client 也会由此返回 connection time out;只要是 client 没有收到 SYN+ACK,3s 之后,client 会再次发送,如果依然没有收到,9s 之后会继续发送
3. 半连接 syn 队列的长度为 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)  决定
4. 当 server 收到 client 的 SYN 包后,会返回 SYN, ACK 的包加以确认,client 的 TCP 协议栈会唤醒 socket 等待队列,发出 connect 调用
5. client 返回 ACK 的包后,server 会进入一个新的叫 accept 的队列,该队列的长度为 min(backlog, somaxconn),默认情况下,somaxconn 的值为 128,表示最多有 129 的 ESTAB 的连接等待 accept(),而 backlog 的值则由 int listen(int sockfd, int backlog) 中的第二个参数指定,listen 里面的 backlog 的含义请看这里。需要注意的是,一些 Linux 的发型版本可能存在对 somaxcon 错误 truncating 方式
6. 当 accept 队列满了之后,即使 client 继续向 server 发送 ACK 的包,也会不被相应,此时,server 通过 /proc/sys/net/ipv4/tcp_abort_on_overflow 来决定如何返回,0 表示直接丢丢弃该 ACK,1 表示发送 RST 通知 client;相应的,client 则会分别返回 read timeout 或者 connection reset by peer。上面说的只是些理论,如果服务器不及时的调用 accept(),当 queue 满了之后,服务器并不会按照理论所述,不再对 SYN 进行应答,返回 ETIMEDOUT。根据这篇文档的描述,实际情况并非如此,服务器会随机的忽略收到的 SYN,建立起来的连接数可以无限的增加,只不过客户端会遇到延时以及超时的情况。

可以看到,整个 TCP stack 有如下的两个 queue:
1. 一个是 half open(syn queue) queue(max(tcp_max_syn_backlog, 64)),用来保存 SYN_SENT 以及 SYN_RECV 的信息。
2. 另外一个是 accept queue(min(somaxconn, backlog)),保存 ESTAB 的状态,但是调用 accept()。

注意,之前我对 Recv-Q/Send-Q 的理解有些误差,使用 ss 获取到的 Recv-Q/Send-Q 在 LISTEN 状态以及非 LISTEN 状态所表达的含义是不同的。从 tcp_diag.c 源码中可以看到二者的区别:

LISTEN 状态: Recv-Q 表示的当前等待服务端调用 accept 完成三次握手的 listen backlog 数值,也就是说,当客户端通过 connect() 去连接正在 listen() 的服务端时,这些连接会一直处于这个 queue 里面直到被服务端 accept();Send-Q 表示的则是最大的 listen backlog 数值,这就就是上面提到的 min(backlog, somaxconn) 的值。
其余状态: 非 LISTEN 状态之前理解的没有问题。Recv-Q 表示 receive queue 中的 bytes 数量;Send-Q 表示 send queue 中的 bytes 数值。

要理解上面总结的这些,可以参见下这两个案例(1, 2)。 

通过 "SYNs to LISTEN sockets dropped" 以及 "times the listen queue of a socket overflowed" 这两个 netstat -s 获取到的 TCP 状态,可以很快的发现系统存在的一些问题。
任何一个包含 "dropped" 或者 "overflowed" 并且数值一直居高不下的 metric 从字面含义理解来看,都不是一个好现象。

对于 Nginx 来说,backlog 的默认值为 511,这个可以通过 ss/netstat 的 Send-Q 确认:
State      Recv-Q Send-Q        Local Address:Port          Peer Address:Port
LISTEN     0      511                       *:80                       *:*     

可以通过适当的增大 nginx 的 backlog 以及 somaxconn 来增大队列:
listen 80 backlog=1638

上面说了这么多,其实就是为了引入下面这个问题。
我们线上一个基于 Netty 的代码,3.5.12 的版本,监控显示 "times the listen queue of a socket overflowed" 常年居高不下,动辄几十 K,通过 ss,我们发现其 backlog 的值只有 50:
Recv-Q Send-Q           Local Address:Port               Peer Address:Port   
0      50                           *:6928                          *:*        users:(("java",454409,196))

g 了一下,发现这个版本复用了 Java 默认的 50 这个值。将其增加到 1024 测试,监控曲线一下子降低到了 0。

除了上面这些,还有一个比较基础的 net.core.netdev_max_backlog,如果内核接受包的速度大于被 userspace 处理的速度,该值定义了可以在接口输入最大的的包数量。

chartbeat 分享了两篇很精彩的文档,其中涉及到了 queue 的一些问题。
Lessons learned tuning TCP and Nginx in EC2 1
Lessons learned tuning TCP and Nginx in EC2 2

ref:
http://madalanarayana.wordpress.com/2014/04/13/learnings-on-tcp-syn/

Redhat & Ubuntu systemtap 安装及使用

原本以为是个比较简单的活儿,后来忙了一个晚上加半个白天差不多五六个小时的时间才搞定,主要时间都花在 Ubuntu 上了,归结起来是 Ubuntu 在商业化技术支持方面跟 Redhat 比还是有不少差距的。不管是哪个发行版本,默认都不会安装 debuginfo 的包,所以最重要的是搞定这几个包。
Redhat 的安装教程直接参照官方的说明
RedHat 默认的 Subscription 里面的 Base-Channel(Red Hat Enterprise Linux Server (v. 6 for 64-bit x86_64) 只包含 kernel-debuginfo-common 这个包,但是不包含 kernel-debuginfo 这个包,后来才发现在 Subscription 里面增加一个 RHEL Server Debuginfo (v.6 for x86_64) channel 就能找到后者了。当时没注意,于是 kernel-debuginfo-common 使用的是官方的 repo,而 kernel-debuginfo 则是用的是 CentOS 的 repo,理论上来说,应该没什么大区别,但是实践发现还是有问题的,比如 Build Date 不通,会导致 "build-id mismatch" 的问题:
# stap test.stp
ERROR: Build-id mismatch: "kernel" vs. "vmlinux" byte 0 (0x3d vs 0xfe) address
0xffffffff81508160 rc 0
Warning: /usr/bin/staprun exited with status: 1
Pass 5: run failed.  Try again with another '–vp 00001' option.

可以通过下面的方式验证:
# rpm -qif /boot/vmlinuz-2.6.32-279.el6.x86_64 /usr/lib/debug/lib/modules/2.6.32-279.el6.x86_64/vmlinux

或者:
# rpm -qi kernel-debuginfo-2.6.32-279.el6.x86_64
Name        : kernel-debuginfo             Relocations: (not relocatable)
Version     : 2.6.32                            Vendor: CentOS
Release     : 279.el6                       Build Date: Fri 22 Jun 2012
09:31:57 PM CST
Install Date: Wed 21 May 2014 01:26:13 AM CST      Build Host:
c6b9.bsys.dev.centos.org
Group       : Development/Debug             Source RPM:
kernel-2.6.32-279.el6.src.rpm
Size        : 1521941269                       License: GPLv2
Signature   : RSA/SHA1, Mon 25 Jun 2012 06:12:09 AM CST, Key ID
0946fca2c105b9de
Packager    : CentOS BuildSystem <http://bugs.centos.org>
URL         : http://www.kernel.org/
Summary     : Debug information for package kernel
Description :
This package provides debug information for package kernel.
This is required to use SystemTap with kernel-2.6.32-279.el6.x86_64.

# rpm -qi kernel-debuginfo-common-x86_64-2.6.32-279.el6.x86_64
Name        : kernel-debuginfo-common-x86_64  Relocations: (not relocatable)
Version     : 2.6.32                            Vendor: Red Hat, Inc.
Release     : 279.el6                       Build Date: Thu 14 Jun 2012
08:20:19 AM CST
Install Date: Wed 21 May 2014 12:34:47 AM CST      Build Host:
x86-008.build.bos.redhat.com
Group       : Development/Debug             Source RPM:
kernel-2.6.32-279.el6.src.rpm
Size        : 180674047                        License: GPLv2
Signature   : RSA/8, Thu 14 Jun 2012 08:49:26 AM CST, Key ID 199e2f91fd431d51
Packager    : Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla>
URL         : http://www.kernel.org/
Summary     : Kernel source files used by kernel-debuginfo packages
Description :
This package is required by kernel-debuginfo subpackages.
It provides the kernel source files common to all builds.

总之,保证下面几个包的完全出自同一个 repo,对应的 $(uname -r) 一样就可以了。
kernel-2.6.32-279.el6.x86_64
kernel-debuginfo-common-x86_64-2.6.32-279.el6.x86_64
kernel-debuginfo-2.6.32-279.el6.x86_64

更新之后:
# rpm -qif /boot/vmlinuz-2.6.32-279.el6.x86_64 /usr/lib/debug/lib/modules/2.6.32-279.el6.x86_64/vmlinux
Name        : kernel                       Relocations: (not relocatable)
Version     : 2.6.32                            Vendor: Red Hat, Inc.
Release     : 279.el6                       Build Date: Thu 14 Jun 2012
08:20:19 AM CST
Install Date: Mon 25 Feb 2013 11:25:11 AM CST      Build Host:
x86-008.build.bos.redhat.com
Group       : System Environment/Kernel     Source RPM:
kernel-2.6.32-279.el6.src.rpm
Size        : 119491893                        License: GPLv2
Signature   : RSA/8, Thu 14 Jun 2012 08:44:50 AM CST, Key ID 199e2f91fd431d51
Packager    : Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla>
URL         : http://www.kernel.org/
Summary     : The Linux kernel
Description :
The kernel package contains the Linux kernel (vmlinuz), the core of any
Linux operating system.  The kernel handles the basic functions
of the operating system: memory allocation, process allocation, device
input and output, etc.
Name        : kernel-debuginfo             Relocations: (not relocatable)
Version     : 2.6.32                            Vendor: Red Hat, Inc.
Release     : 279.el6                       Build Date: Thu 14 Jun 2012
08:20:19 AM CST
Install Date: Wed 21 May 2014 01:35:13 AM CST      Build Host:
x86-008.build.bos.redhat.com
Group       : Development/Debug             Source RPM:
kernel-2.6.32-279.el6.src.rpm
Size        : 1521941269                       License: GPLv2
Signature   : RSA/8, Thu 14 Jun 2012 08:48:56 AM CST, Key ID 199e2f91fd431d51
Packager    : Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla>
URL         : http://www.kernel.org/
Summary     : Debug information for package kernel
Description :
This package provides debug information for package kernel.
This is required to use SystemTap with kernel-2.6.32-279.el6.x86_64.

然后通过默认的 repo,按部就班的安装 systemtap 就可以了。确认下 /lib/modules/2.6.34.7/build 是 ln 到 /usr/src/kernels/2.6.32-279.el6.x86_64/。

Ubuntu 的可以参照这篇文档来做,对于 10.04 2.6.32-38-server 的机器来说,依然还是可行的。
添加 repo,安装对应的 linux-image-$(uname -r)-dbgsym:
codename=$(lsb_release -c | awk  '{print $2}')
sudo tee /etc/apt/sources.list.d/ddebs.list << EOF
deb http://ddebs.ubuntu.com/ ${codename}      main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-security main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-updates  main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-proposed main restricted universe multiverse
EOF

sudo apt-key adv –keyserver keyserver.ubuntu.com –recv-keys ECDCAD72428D7C01
sudo apt-get update
sudo apt-get install linux-image-$(uname -r)-dbgsym

不过当一切都安装好了之后,发现 kernel 的版本号并不是真正对应的,默认机器跑的是 2.6.32-38.85,新安装的 debuginfo 是 2.6.32-38.83:
root@unode10-plog-db1:~# dpkg -l | grep linux-image
ii  linux-image-2.6.32-38-server        2.6.32-38.85                                      Linux kernel image for version 2.6.32 on x86
ii  linux-image-2.6.32-38-server-dbgsym 2.6.32-38.83                                      Linux kernel debug image for version 2.6.32

所以把 linux-image 也换了,直接跟 2.6.32-38.83 统一,ftp.ubuntu.com 上找到对应的 linux-image-2.6.32-38-server_2.6.32-38.83_amd64.deb,dpkg 安装就好了。

安装只是个开始,用来发现诊断问题才是真正开始把工具给利用起来。Dtrace 的作者 Brendan 11 年的时候写了篇使用心得,撇开一些主观的情绪因素,上面遇到的一些问题还是值得参考的。
Systemtap 的使用可以参考官方的两篇文档,一篇入门,一篇相当于 manual,其实这些都能直接 man 到。

然后就是根据需求 google 各种 stp 脚本了,跑出来的结果如果不熟悉 kernel function & syscall 的话,需要花些时间理解下,否则没法分析问题了。
如果需要在 production 机器上做,直接安装 systemtap-runtime,在同等配置的机器上把 ko 文件做好丢到 production 上跑就好了。后来偶然的机会发现了一篇不错的中文安装文档

5 月故障总结(post-mortem)

14 年 5 月,友盟的几个核心产品由于各种因素的影响,对外表现了一些不稳定的现象,我作为最主要的当事人之一,有不可逃避的责任,为了给开发者一个交代,我总结了这么一篇 post-mortem。目前对外发布的版本出于迎合普通工程师的口味,我们的运营阉割了部分内容,除此之外,我们还做了一个给纯小白用户看的时间轴图片,更加的简介明了,说是《友盟万兆网络升级日记》并不能概括这段事件发生的事情。下面这个是我写的一个未经删改的 post-mortem,透露的内容还是很多的,同时技术性也更强。该版本基本的思路是参照 github, cloudflare 的 postmortem 套路。


上个月(2014 年 5 月)的 13 日至 27 日,包括我们统计分析、在线参数在内的服务出现了间断性不稳定的情况,给热爱我们产品与服务的开发者带了很大的不便,为此我们深表歉意与自责。

未经缜密计划安排的停机维护时间对我们来说在任何时间段都是不被允许的,作为一名全程参与整个事件的工程师,我非常乐意给我们的开发者解释一下造成我们这一段时间服务不稳定的原因,为此我们做了什么以避免今后类似的事件再次发生。

背景

2011 年 7 月我们开始在全国最好的数据中心之一部署上架了我们的第一台服务器以及交换机,当时的网络架构还是简单的 2960G trunk 模式。

转眼,到了 2012 年第三季度,我们这套简易的方案在处理内部流量时出现了比较吃力的问题,于是我们在 2012 年年底完成了我们三层网络架构的升级,有兴趣的可以看看我们工程师当时总结该次升级的博客(1, 2, 3, 4)。

在接下来的一年多时间里面,我们的业务出现了爆发性的增长,带宽也由最初的几十兆飙升了目前的数 G,在 2013 年年底,我们决定对我们部署着统计分析等服务的核心数据中心再一次进行升级,这一次,我们将由原先的 3750X 三层堆叠架构升级到 Cisco 最新最强大的万兆 Nexus 752 架构以支持更高的吞吐、更低的延时以及更好的服务器网卡冗余。

2014 年年初到 3 月底的这段期间,我们的工程师马不停蹄的进行着大量的前期准备调研工作,包括我们目前线网的梳理确认、综合布线、网络设备的上架调试、新老网的设备互联以最大程度的减小停机时间。

进入 4 月以来,我们又紧锣密鼓的讨论制定了各种潜在风险以及应对措施,回滚计划、网络以及业务连通性监控确认等方案。最终决定,一共分为 4 次常规性的迁移,将数百台服务器由老网迁移到新网,1 次互联网入口/出口流量以及网关的迁移,前后历时一个月。

4 月 03 日晚上 08:00,我们的内网由于延时过高,导致了我们实时处理的时间过长,触发了 Storm 的 timeout 机制,最终导致了我们的实时处理延迟了 10 小时,这也是第一次向我们的开发者发出由于网络原因造成数据延时的通知。为了加快进度,我们决定将原先定于 5 月初的第一次升级提前到 4 月末进行。

4 月 24 日凌晨,我们进行了第一批服务器的迁移,在我们开发默契配合下,完美的完成了此次迁移。在后续的观察中,我们发现新老网(3750X – Nexus 7000)之间的 4G 链路带宽利用率可能会比较高,于是我们紧急调度了我们供应商的资源,在 4 月 28 日将这条关键的链路扩容到了 8G 以提高更大的带宽。

4 月 29 日、5 月 8 日,我们又分批顺利地完成了第二次、第三次服务器的迁移。

Continue reading

1000 天的日日夜夜

11 年的 7 月 10 号来到这里,一周之后的晚上,跟我们 CTO 以及另外一个 TL 一起开始了我们第一个正式 IDC  的「破土动工」,机器是不会说谎的,下图是我们第一台交换机的信息。

May 22 2014 是我们第一台线上机器跑到 1000d 的日子,这是目前我们仅有的三台跑着 CentOS 5.6 2.6.18-238.el5xen 的机器;另外,也是三台仅有的跑着 xen 的机器,当年曾经穷的把 LVS 放在这上面跑。如果不是 208.5d 的 bug,估计同一时段会有很多的机器跑到 1000d,现在同时期上的那批机器不少都跑到了 600、700d。

三年时间发生了很多事,很多有趣的事,不少悲伤的事,没有永远的朋友也没有永远的敌人,只有共同的利益。