一、简介 ICP(Index Condition Pushdown)是在MySQL 5.6版本上推出的查询优化策略,把本来由Server层做的索引条件检查下推给存储引擎层来做,以降低回表和访问存储引擎的次数,提高查询效率。 二、原理 为了理解ICP是如何工作的,我们先了解下没有使用ICP的情况下,MySQL是如何查询的: 存储引擎读取索引记录; 根据索引中的主键值,定位并读取完整的行记录; 存储引擎把记录交给Server层去检测该记录是否满足WHERE条件。 使用ICP的情况下,查询过程如下: 读取索引记录(不是完整的行记录); 判断WHERE条件部分能否用索引中的列来做检查,条件不满足,则处理下一行索引记录; 条件满足,使用索引中的主键去定位并读取完整的行记录(就是所谓的回表); 存储引擎把记录交给Server层,Server层检测该记录是否满足WHERE条件的其余部分。 三、实践 先创建一张表,并插入记录 CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT COMMENT “主键”, name varchar(32) COMMENT “姓名”, city varchar(32) COMMENT “城市”, age int(11) COMMENT “年龄”, primary key(id), key idx_name_city(name, city) )engine=InnoDB default charset=utf8; insert into user(name, city, age) values(“ZhaoDa”, “BeiJing”,… Continue reading MySQL索引下推
MYSQL前缀索引
这里主要介绍 MySQL 的前缀索引。从名字上来看,前缀索引就是指索引的前缀,当然这个索引的存储结构不能是 HASH,HASH 不支持前缀索引。 先看下面这两行示例数据: 你是中国人吗?是的是的是的是的是的是的是的是的是的是的 确定是中国人?是的是的是的是的是的是的是的是的是的是的 这两行数据有一个共同的特点就是前面几个字符不同,后面的所有字符内容都一样。那面对这样的数据,我们该如何建立索引呢? 大致有以下 3 种方法: 拿整个串的数据来做索引 这种方法来的最简单直观,但是会造成索引空间极大的浪费。重复值太多,进而索引中无用数据太多,无论写入或者读取都产生极大资源消耗。 将字符拆开,将一部分做索引 把数据前面几个字符和剩余的部分字符分拆为两个字段 r1_prefix,r1_other,针对字段 r1_prefix 建立索引。如果排除掉表结构更改这块影响,那这种方法无疑是最好的。 把前面 6 个字符截取出来的子串做一个索引 能否不拆分字段,又能避免太多重复值的冗余?我们今天讨论一下前缀索引。 前缀索引 前缀索引就是基于原始索引字段,截取前面指定的字符个数或者字节数来做的索引。 MySQL 基本上大部分存储引擎都支持前缀索引,目前只有字符类型或者二进制类型的字段可以建立前缀索引。比如:CHAR/VARCHAR、TEXT/BLOB、BINARY/VARBINARY。 字符类型基于前缀字符长度,f1(10) 指的前 10 个字符; 二进制类型基于字节大小,f1(10) 指的是前 10 个字节长度; TEXT/BLOB 类型只支持前缀索引,不支持整个字段建索引。 举个简单例子,表 t1 有两个字段,针对字段 r1 有两个索引,一个是基于字段 r1 的普通二级索引,另外一个是基于字段r1的前缀索引。 <localhost|mysql>show create table t1\G *************************** 1. row *************************** Table: t1 Create… Continue reading MYSQL前缀索引
HTTP 队头阻塞(Head-of-Line blocking)
(译)Robin Marx: QUIC 和 HTTP/3 队头阻塞的细节 作者:Robin Marx,原文:Head-of-Line Blocking in QUIC and HTTP/3: The Details,GitHub:rmarx/holblocking-blogpost 您可能已经听说,经过4年的工作,新的 HTTP/3 和 QUIC 协议终于接近正式标准化。预览版现在可以在服务器和浏览器中进行测试。 与 HTTP/2 相比,HTTP/3 有很大的性能改进,这主要是因为它将底层传输协议从 TCP 改为基于 UDP 的 QUIC。在这篇文章中,我们将深入了解其中的一项改进,即消除“队头阻塞”(Head-of-Line blocking, 简写:HOL blocking)问题。这很有用,因为我读过很多关于这实际上意味着什么以及它在现实中有多大帮助的误解。解决队头阻塞也是 HTTP/3 和 QUIC 以及 HTTP/2 背后的主要动机之一,因此它也为协议演进的原因提供了一个极好的视角。 我将首先介绍队头阻塞问题,然后在整个 HTTP 历史中跟踪它的不同形式。我们还将研究它如何与其他系统交互,如优先级和拥塞控制。我们的目标是帮助人们对 HTTP/3 的性能改进做出正确的判断,而这(剧透)可能不像营销材料中所说的那样令人惊讶。 目录: 什么是队头阻塞? HTTP/1.1 的队头阻塞 HTTP/2(基于 TCP)的队头阻塞 HTTP/3(基于 QUIC)的队头阻塞 总结与结论 彩蛋内容: 彩蛋:HTTP/1.1 管道… Continue reading HTTP 队头阻塞(Head-of-Line blocking)
Nagle算法的一些理解
背景 Nagle算法是以他的发明人John Nagle的名字命名的,它用于自动连接许多的小缓冲器消息;这一过程(称为nagling)通过减少必须发送包的个数来增加网络软件系统的效率。 Nagle算法于1984年定义为福特航空和通信公司IP/TCP拥塞控制方法,这使福特经营的最早的专用TCP/IP网络减少拥塞控制,从那以后这一方法得到了广泛应用。Nagle的文档里定义了处理他所谓的小包问题的方法,这种问题指的是应用程序一次产生一字节数据,这样会导致网络由于太多的包而过载(一个常见的情况是发送端的”糊涂窗口综合症(Silly Window Syndrome)”)。从键盘输入的一个字符,占用一个字节,可能在传输上造成41字节的包,其中包括1字节的有用信息和40字节的首部数据。这种情况转变成了4000%的消耗,这样的情况对于轻负载的网络来说还是可以接受的,但是重负载的福特网络就受不了了,它没有必要在经过节点和网关的时候重发,导致包丢失和妨碍传输速度。吞吐量可能会妨碍甚至在一定程度上会导致连接失败。Nagle的算法通常会在TCP程序里添加两行代码,在未确认数据发送的时候让发送器把数据送到缓存里。任何数据随后继续直到得到明显的数据确认或者直到攒到了一定数量的数据了再发包。尽管Nagle的算法解决的问题只是局限于福特网络,然而同样的问题也可能出现在ARPANet。这种方法在包括因特网在内的整个网络里得到了推广,成为了默认的执行方式,尽管在高互动环境下有些时候是不必要的,例如在客户/服务器情形下。在这种情况下,nagling可以通过使用TCP_NODELAY 套接字选项关闭。 算法规则 如果包长度达到MSS,则允许发送 如果包含FIN,则允许发送 如果设置了TCP_NODELAY,则允许发送 未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送 上述条件都未满足,但发生了超时(一般为200ms),则立即发送。 Nagle算法只允许一个未被ACK的包存在于网络,它并不管包的大小,因此它事实上就是一个扩展的停-等协议,只不过它是基于包停-等的,而不是基于字节停-等的。Nagle算法完全由TCP协议的ACK机制决定,这会带来一些问题,比如如果对端ACK回复很快的话,Nagle事实上不会拼接太多的数据包,虽然避免了网络拥塞,网络总体的利用率依然很低。 也就是说,Nagle的算法是控制发送的,发送端在有未确认数据包并且本次发送包较小时,会等待后续数据包,直至超过mss才发送 举个例子,client端调用socket的write操作将一个int型数据(称为A块)写入到网络中,由于此时连接是空闲的(也就是说还没有未被确认的小段),因此这个int型数据会被马上发送到server端,接着,client端又调用write操作写入‘rn’(简称B块),这个时候,A块的ACK没有返回,所以可以认为已经存在了一个未被确认的小段,所以B块没有立即被发送,一直等待A块的ACK收到(大概40ms之后),B块才被发送。 这里还隐藏了一个问题,就是A块数据的ACK为什么40ms之后才收到?这是因为TCP/IP中不仅仅有nagle算法,还有一个TCP确认延迟机制 。当Server端收到数据之后,它并不会马上向client端发送ACK,而是会将ACK的发送延迟一段时间(假设为t),它希望在t时间内server端会向client端发送应答数据,这样ACK就能够和应答数据一起发送,就像是应答数据捎带着ACK过去。在我之前的时间中,t大概就是40ms。这就解释了为什么’rn’(B块)总是在A块之后40ms才发出。 抓包时经常看到PSH,ACK的包,正是这个机制的体现,将ack和数据一并送出 其实就算没有延迟确认,Nagle这种只允许一个未确认包存在的机制,也是利用率很低的,因为完全串行化了,当网络延迟较高时,根本无法最大化利用物理带宽(有空试试Nagle在大带宽,高延迟的网络下的表现) 这两个机制配合在一起也是绝配,一个不回,一个不发,接收端不ack,发送端就不发送下一个小报文,也就造成了这个40ms(tcp delay ack)的延迟问题 tcp_nodelay 禁用的是只是nagle算法,并没有禁用延迟确认 禁用了nagle算法之后,相当于拆散了这对CP,发送端发送时也就不会再等待所有包都ack了;接收端的延迟ack影响并不是很大,只是会一定程度降低tcp 窗口滑动的速度而已
TCP Keepalive机制刨根问底
TCP协议中有长连接和短连接之分。短连接环境下,数据交互完毕后,主动释放连接; 双方建立交互的连接,但是并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会,那么在长时间无数据交互的时间段内,交互双方都有可能出现掉电、死机、异常重启,还是中间路由网络无故断开、NAT超时等各种意外。 当这些意外发生之后,这些TCP连接并未来得及正常释放,那么,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,为了解决这个问题,在传输层可以利用TCP的保活报文来实现,这就有了TCP的Keepalive(保活探测)机制。 Tcp Keepalive存在的作用 探测连接的对端是否存活 在应用交互的过程中,可能存在以下几种情况: 客户端或服务端意外断电,死机,崩溃,重启。 中间网络已经中断,而客户端与服务器并不知道。 利用保活探测功能,可以探知这种对端的意外情况,从而保证在意外发生时,可以释放半打开的TCP、 连接。 防止中间设备因超时删除连接相关的连接表 中间设备如防火墙等,会为经过它的数据报文建立相关的连接信息表,并未其设置一个超时时间的定时器,如果超出预定时间,某连接无任何报文交互的话,中间设备会将该连接信息从表中删除,在删除后,再有应用报文过来时,中间设备将丢弃该报文,从而导致应用出现异常,这个交互的过程大致如下图所示: 这种情况在有防火墙的应用环境下非常常见,这会给某些长时间无数据交互但是又要长时间维持连接的应用(如数据库)带来很大的影响,为了解决这个问题,应用本身或TCP可以通过保活报文来维持中间设备中该连接的信息,(也可以在中间设备上开启长连接属性或调高连接表的释放时间来解决。 常见应用故障场景: 某财务应用,在客户端需要填写大量的表单数据,在客户端与服务器端建立TCP连接后,客户端终端使用者将花费几分钟甚至几十分钟填写表单相关信息,终端使用者终于填好表单所需信息后,点击“提交”按钮,结果,这个时候由于中间设备早已经将这个TCP连接从连接表中删除了,其将直接丢弃这个报文或者给客户端发送RST报文,应用故障产生,这将导致客户端终端使用者所有的工作将需要重新来过,给使用者带来极大的不便和损失。 TCP保活报文交互过程 TCP保活的交互过程大致如下图所示: TCP保活可能带来的问题: 中间设备因大量保活连接,导致其连接表满,网关设备由于保活问题,导致其连接表满,无法新建连接(XX局网闸故障案例)或性能下降严重 正常连接被释放 当连接一端在发送保活探测报文时,中间网络正好由于各种异常(如链路中断、中间设备重启等)而无法将保活探测报文正确转发至对端时,可能会导致探测的一方释放本来正常的连接,但是这种可能情况发生的概率较小, 另外,一般也可以增加保活探测报文发生的次数来减少这种情况发生的概率和影响。 TCP Keepalive协议解读 下面协议解读,基于 RFC1122#TCP Keep-Alives (注意这是协议的解读站在协议的角度) TCP Keepalive 虽不是标准规范,但操作系统一旦实现,默认情况下须为关闭,可以被上层应用开启和关闭。 TCP Keepalive必须在 没有任何数据(包括ACK包)接收之后的周期内才会被发送,允许配置,默认值不能够小于2个小时 不包含数据的ACK段在被TCP发送时没有可靠性保证,意即一旦发送,不确保一定发送成功。系统实现不能对任何特定探针包作死连接对待 规范建议keepalive保活包不应该包含数据,但也可以包含1个无意义的字节,比如0x0。 SEG.SEQ = SND.NXT-1,即TCP保活探测报文序列号将前一个TCP报文序列号减1。SND.NXT = RCV.NXT,即下一次发送正常报文序号等于ACK序列号;总之保活报文不在窗口控制范围内 有一张图,可以很容易说明,但请仔细观察Tcp Keepalive部分: TCP Keepalive 需要注意的点(协议层面) 不太好的TCP堆栈实现,可能会要求保活报文必须携带有1个字节的数据负载 TCP Keepalive应该在服务器端启用,客户端不做任何改动;若单独在客户端启用,若客户端异常崩溃或出现连接故障,存在服务器无限期的为已打开的但已失效的文件描述符消耗资源的严重问题。 但在特殊的NFS文件系统环境下,需要客户端和服务器端都要启用Tcp… Continue reading TCP Keepalive机制刨根问底
浅谈Linux下Socket选项设置
0.前言 TCP/IP协议栈是Linux内核的重要组成部分和网络编程的基石,虽然Linux和BSD有很大的联系,但是对于某些Socket选项和内核操作仍然存在差异,因此文中适用场景均为仅Linux。 《UNIX网络编程》是已故UNIX网络专家W. Richard Stevens博士(1951-1999)、世界著名网络专家Bill Fenner和Andrew M. Rudoff完成的权威著作,该书对网络编程进行全面而深入的阐述,是提高网络编程功力的不二之选。 1.Socket和TCP/IP的关系 “All problems in computer science can be solved by another level of indirection.” 为满足应用层需求,系统对TCP/IP层进行细节屏蔽和抽象,Socket层就相当于TCP/IP和应用层之间的中间层。 常用的socket/bind/accept/connect就是抽象出来的接口,使用它们可以快速进行网络程序开发,可见Socket中间层的重要性。 Socket选项就是为满足用户的定制化需求而生的。我们经常遇到的情况包括地址复用、端口复用、读写超时时间、读写缓冲区大小等。 在Linux的TCP/IP协议栈中包括很多Socket选项,它们会出现在TCP层、IP层、Socket层等,为此在读取和设置socket选项时需要指定level。 如图可以看到Socket层作为中间层以及各层支持的部分Socket选项: 注:可通过man 7 tcp/man 7 ip查看tcp/ip各层Socket选项详细定义和添加内核版本等信息。 2.操作Socket选项的API 读取和设置Socket选项的API包括: getsockopt、setsockopt、fcntl、ioctl等; 其中fcntl和ioctl用来设置socket的阻塞和非阻塞状态。 通过man获得的函数定义: //ioctl函数定义 #include <sys/ioctl.h> int ioctl(int d, int request, …); //fcntl函数定义 #include <unistd.h> #include <fcntl.h> int fcntl(int fd,… Continue reading 浅谈Linux下Socket选项设置
从创建进程到进入main函数,发生了什么?
前几天,读者群里有小伙伴提问:从进程创建后,到底是怎么进入我写的main函数的? 今天这篇文章就来聊聊这个话题。 首先先划定一下这个问题的讨论范围:C/C++语言 这篇文章主要讨论的是操作系统层面上对于进程、线程的创建初始化等行为,而像Python、Java等基于解释器、虚拟机的语言,如何进入到main函数执行,这背后的路径则更长(包含了解释器和虚拟机内部的执行流程),以后有机会再讨论。所以这里就重点关注C/C++这类native语言的main函数是如何进入的。 本文会兼顾叙述Linux和Windows两个主要平台上的详细流程。 创建进程 第一步,创建进程。 在Linux上,我们要启动一个新的进程,一般通过fork + exec系列函数来实现,前者将当前进程“分叉”出一个孪生子进程,后者负责替换这个子进程的执行文件,来执行子进程的新程序文件。 这里的fork、exec系列函数,是操作系统提供给应用程序的API函数,在其内部最终都会通过系统调用,进入操作系统内核,通过内核中的进程管理机制,来完成一个进程的创建。 操作系统内核将负责进程的创建,主要有下面几个工作要做: 创建内核中用于描述进程的数据结构,在Linux上是task_struct 创建新进程的页目录、页表,用于构建新进程的内存地址空间 在Linux内核中,由于历史原因,Linux内核早期并没有线程的概念,而是用任务:task_struct来描述一个程序的执行实例:进程。 在内核中,一个任务对应就是一个task_struct,也就是一个进程,内核的调度单元也是一个个的个task_struct。 后来,多线程的概念兴起,Linux内核为了支持多线程技术,task_struct实际上表示的变成了一个线程,通过将多个task_struct合并为一组(通过该结构内部的组id字段)再来描述一个进程。因此,Linux上的线程,也称为轻量级进程。 系统调用fork的一个重要使命就是要去创建新进程的task_struct结构,创建完成后,进程就拥有了调度单元。随后将开始可以参与调度并有机会获得执行。 加载可执行文件 通过fork成功创建进程后,此时的子进程和父进程相当于一个细胞进行了有丝分裂,两个进程“几乎”是一模一样的。 而要想子进程执行新的程序,在子进程中还需要用到exec系列函数来实现对进程可执行程序的替换。 exec系列函数同样是系统调用的封装,通过调用它们,将进入内核sys_execve来执行真正的工作。 这个工作细节比较多,其中有一个重要的工作就是加载可执行文件到进程空间并对其进行分析,提取出可执行文件的入口地址。 我们使用C、C++等高级语言编写的代码,最终通过编译器会编译生成可执行文件,在Linux上,是ELF格式,在Windows上,称之为PE文件。 无论是ELF文件还是PE文件,在各自的文件头中,都记录了这个可执行文件的指令入口地址,它指示了程序该从哪里开始执行。 这个入口指向哪里,是我们的main函数吗?这里卖一个关子,先来解决在这之前的一个问题:进程创建后,是如何来到这个入口地址的? 不管在Windows还是Linux上,应用线程都会经常在用户空间和内核空间来回穿梭,这可能出现在以下几种情况发生时: 系统调用 中断 异常 从内核返回时,线程是如何知道自己从哪里进来的,该回到应用空间的哪里去继续执行呢? 答案是,在进入内核空间时,线程将自动保存上下文(其实就是一些寄存器的内容,比如指令寄存器EIP)到线程的堆栈上,记录自己从哪里来的,等到从内核返回时,再从堆栈上加载这些信息,回到原来的地方继续执行。 前面提到,子进程是通过sys_execve系统调用进入到内核中的,在后面完成可执行文件的分析后,拿到了ELF文件的入口地址,将会去修改原来保存在堆栈上的上下文信息,将EIP指向ELF文件的入口地址。这样等sys_execve系统调用结束时,返回到用户空间后,就能够直接转到新的程序入口开始执行代码。 所以,一个非常重要的特点是:exec系列函数正常情况下是不会返回的,一旦进入,完成使命后,执行流程就会转向新的可执行文件入口。 另外需要提一下的是,在Linux上,除了ELF文件,还支持一些其他格式的可执行文件,如MS-DOS、COFF 除了二进制的可执行文件,还支持shell脚本,这个情况下将会将脚本解释器程序作为入口来启动 从ELF入口到main函数 上面交代了,一个新的进程,是如何执行到可执行文件的入口地址的。 同时也留了一个问题,这个入口地址是什么?是我们的main函数吗? 这里有一个简单的C程序,运行起来后输出经典的hello world: #include <stdio.h> int main() { printf(“hello, world!\n”); return 0; } 通过gcc编译后,生成了一个ELF可执行文件,通过readelf指令,可以实现对ELF文件的分析,这里可以看到ELF文件的入口地址是0x400430: 随后,我们通过反汇编神器,IDA打开分析这个文件,看一下位于0x400430入口的地方是什么函数? 可以看到,入口地方是一个叫做 _start 的函数,并不是我们的main函数。 在_start的结尾,调用了 __libc_start_main 函数,而这个函数,位于libc.so中。 你可能疑惑,这个函数是哪里冒出来的,我们的代码中并没有用到它呢?… Continue reading 从创建进程到进入main函数,发生了什么?
聊聊TCP连接耗时的那些事儿
在互联网后端日常开发接口的时候中,不管你使用的是C、Java、PHP还是Golang,都避免不了需要调用mysql、redis等组件来获取数据,可能还需要执行一些rpc远程调用,或者再调用一些其它restful api。 在这些调用的底层,基本都是在使用TCP协议进行传输。这是因为在传输层协议中,TCP协议具备可靠的连接,错误重传,拥塞控制等优点,所以目前应用比UDP更广泛一些。 相信你也一定听闻过TCP也存在一些缺点,那就是老生常谈的开销要略大。但是各路技术博客里都在单单说开销大、或者开销小,而少见不给出具体的量化分析。不客气一点,这都是营养不大的废话。经过日常工作的思考之后,我更想弄明白的是,开销到底多大。一条TCP连接的建立需要耗时延迟多少,是多少毫秒,还是多少微秒?能不能有一个哪怕是粗略的量化估计?当然影响TCP耗时的因素有很多,比如网络丢包等等。我今天只分享我在工作实践中遇到的比较高发的各种情况。 一 正常TCP连接建立过程 要想搞清楚TCP连接的建立耗时,我们需要详细了解连接的建立过程。在前文《图解Linux网络包接收过程》中我们介绍了数据包在接收端是怎么被接收的。数据包从发送方出来,经过网络到达接收方的网卡。在接收方网卡将数据包DMA到RingBuffer后,内核经过硬中断、软中断等机制来处理(如果发送的是用户数据的话,最后会发送到socket的接收队列中,并唤醒用户进程)。 在软中断中,当一个包被内核从RingBuffer中摘下来的时候,在内核中是用struct sk_buff结构体来表示的(参见内核代码include/linux/skbuff.h)。其中的data成员是接收到的数据,在协议栈逐层被处理的时候,通过修改指针指向data的不同位置,来找到每一层协议关心的数据。 对于TCP协议包来说,它的Header中有一个重要的字段-flags。如下图: 通过设置不同的标记为,将TCP包分成SYNC、FIN、ACK、RST等类型。客户端通过connect系统调用命令内核发出SYNC、ACK等包来实现和服务器TCP连接的建立。在服务器端,可能会接收许许多多的连接请求,内核还需要借助一些辅助数据结构-半连接队列和全连接队列。我们来看一下整个连接过程: 在这个连接过程中,我们来简单分析一下每一步的耗时 客户端发出SYNC包:客户端一般是通过connect系统调用来发出SYN的,这里牵涉到本机的系统调用和软中断的CPU耗时开销 SYN传到服务器:SYN从客户端网卡被发出,开始“跨过山和大海,也穿过人山人海……”,这是一次长途远距离的网络传输 服务器处理SYN包:内核通过软中断来收包,然后放到半连接队列中,然后再发出SYN/ACK响应。又是CPU耗时开销 SYC/ACK传到客户端:SYC/ACK从服务器端被发出后,同样跨过很多山、可能很多大海来到客户端。又一次长途网络跋涉 客户端处理SYN/ACK:客户端内核收包并处理SYN后,经过几us的CPU处理,接着发出ACK。同样是软中断处理开销 ACK传到服务器:和SYN包,一样,再经过几乎同样远的路,传输一遍。 又一次长途网络跋涉 服务端收到ACK:服务器端内核收到并处理ACK,然后把对应的连接从半连接队列中取出来,然后放到全连接队列中。一次软中断CPU开销 服务器端用户进程唤醒:正在被accpet系统调用阻塞的用户进程被唤醒,然后从全连接队列中取出来已经建立好的连接。一次上下文切换的CPU开销 以上几步操作,可以简单划分为两类: 第一类是内核消耗CPU进行接收、发送或者是处理,包括系统调用、软中断和上下文切换。它们的耗时基本都是几个us左右。具体的分析过程可以参见《一次系统调用开销到底有多大?》、《软中断会吃掉你多少CPU?》、《进程/线程切换会用掉你多少CPU?》这三篇文章。 第二类是网络传输,当包被从一台机器上发出以后,中间要经过各式各样的网线、各种交换机路由器。所以网络传输的耗时相比本机的CPU处理,就要高的多了。根据网络远近一般在几ms~到几百ms不等。。 1ms就等于1000us,因此网络传输耗时比双端的CPU开销要高1000倍左右,甚至更高可能还到100000倍。所以,在正常的TCP连接的建立过程中,一般可以考虑网络延时即可。一个RTT指的是包从一台服务器到另外一台服务器的一个来回的延迟时间。所以从全局来看,TCP连接建立的网络耗时大约需要三次传输,再加上少许的双方CPU开销,总共大约比1.5倍RTT大一点点。不过从客户端视角来看,只要ACK包发出了,内核就认为连接是建立成功了。所以如果在客户端打点统计TCP连接建立耗时的话,只需要两次传输耗时-既1个RTT多一点的时间。(对于服务器端视角来看同理,从SYN包收到开始算,到收到ACK,中间也是一次RTT耗时) 二 TCP连接建立时的异常情况 上一节可以看到在客户端视角,在正常情况下一次TCP连接总的耗时也就就大约是一次网络RTT的耗时。如果所有的事情都这么简单,我想我的这次分享也就没有必要了。事情不一定总是这么美好,总会有意外发生。在某些情况下,可能会导致连接时的网络传输耗时上涨、CPU处理开销增加、甚至是连接失败。现在我们说一下我在线上遇到过的各种沟沟坎坎。 1)客户端connect系统调用耗时失控 正常一个系统调用的耗时也就是几个us(微秒)左右。但是在《追踪将服务器CPU耗光的凶手!》一文中笔者的一台服务器当时遇到一个状况,某次运维同学转达过来说该服务CPU不够用了,需要扩容。当时的服务器监控如下图: 该服务之前一直每秒抗2000左右的qps,CPU的idel一直有70%+。怎么突然就CPU一下就不够用了呢。而且更奇怪的是CPU被打到谷底的那一段时间,负载却并不高(服务器为4核机器,负载3-4是比较正常的)。 后来经过排查以后发现当TCP客户端TIME_WAIT有30000左右,导致可用端口不是特别充足的时候,connect系统调用的CPU开销直接上涨了100多倍,每次耗时达到了2500us(微秒),达到了毫秒级别。 当遇到这种问题的时候,虽然TCP连接建立耗时只增加了2ms左右,整体TCP连接耗时看起来还可接受。但是这里的问题在于这2ms多都是在消耗CPU的周期,所以问题不小。 解决起来也非常简单,办法很多:修改内核参数net.ipv4.ip_local_port_range多预留一些端口号、改用长连接都可以。 2)半/全连接队列满 如果连接建立的过程中,任意一个队列满了,那么客户端发送过来的syn或者ack就会被丢弃。客户端等待很长一段时间无果后,然后会发出TCP Retransmission重传。拿半连接队列举例: 要知道的是上面TCP握手超时重传的时间是秒级别的。也就是说一旦server端的连接队列导致连接建立不成功,那么光建立连接就至少需要秒级以上。而正常的在同机房的情况下只是不到1毫秒的事情,整整高了1000倍左右。尤其是对于给用户提供实时服务的程序来说,用户体验将会受到较大影响。如果连重传也没有握手成功的话,很可能等不及二次重试,这个用户访问直接就超时了。 还有另外一个更坏的情况是,它还有可能会影响其它的用户。假如你使用的是进程/线程池这种模型提供服务,比如php-fpm。我们知道fpm进程是阻塞的,当它响应一个用户请求的时候,该进程是没有办法再响应其它请求的。假如你开了100个进程/线程,而某一段时间内有50个进程/线程卡在和redis或者mysql服务器的握手连接上了(注意:这个时候你的服务器是TCP连接的客户端一方)。这一段时间内相当于你可以用的正常工作的进程/线程只有50个了。而这个50个worker可能根本处理不过来,这时候你的服务可能就会产生拥堵。再持续稍微时间长一点的话,可能就产生雪崩了,整个服务都有可能会受影响。 既然后果有可能这么严重,那么我们如何查看我们手头的服务是否有因为半/全连接队列满的情况发生呢?在客户端,可以抓包查看是否有SYN的TCP Retransmission。如果有偶发的TCP Retransmission,那就说明对应的服务端连接队列可能有问题了。 在服务端的话,查看起来就更方便一些了。netstat -s可查看到当前系统半连接队列满导致的丢包统计,但该数字记录的是总丢包数。你需要再借助watch命令动态监控。如果下面的数字在你监控的过程中变了,那说明当前服务器有因为半连接队列满而产生的丢包。你可能需要加大你的半连接队列的长度了。 $ watch ‘netstat -s | grep LISTEN’ 8 SYNs to LISTEN sockets ignored 对于全连接队列来说呢,查看方法也类似。… Continue reading 聊聊TCP连接耗时的那些事儿
例用lsof命令恢复linux误删除文件(正在使用的文件)
首先我们要知道lsof这个命令是做什么用的,简单的讲就是列出当前打开的程序,进程或者用户所使用的所有文件(是正在使用的) 在这之前我们要搞清楚一个理论知识:那就是句柄(这里我用缓存来理解的,这样更容易理解)及空间释放问题 打个比方:在生产环境中,有维护人员正在用 tail 命令查看一个日志,然后另一个维护人员用rm -rf 命令把这个日志给删除了。但是这时其实你的空间还没有真正的释放,因为为还有用户占用着这个文件,所以在缓存中这个文件还是存在的。那么我们就可以通这个缓存把文件给恢复回来。下面我们开始实验 1.首先我们用下面的命令查看我们的 less /var/log/messages 然后再按我们键盘上的 ctrl + z 先暂停(注意,这时候我们的 less 命令占用着这个文件) less /var/log/messages 复制代码 2.然后我们用 rm -rf /var/log/messages 删除这个文件 rm -rf /var/log/messages 复制代码 3.你可以 ls 看下我们的这个文件已经被删除了,这时候不要惊慌,我们现在来恢复,首先,我们要使用 lsof 命令找到 less 这个程序占用着哪些文件 lsof | grep deleted #过滤出已经被删除的所有文件(这里被删除的意思是程序在占用且被删除),在最后一列找到我们需要恢复的文件 复制代码 4.刚上面的命令的结果如下: $ lsof | grep deleted #过滤出less这个程序所占用的所有文件,在最后一列找到我们需要恢复的文件 rsyslogd 903 root 4w… Continue reading 例用lsof命令恢复linux误删除文件(正在使用的文件)
HTTP/2 简介
注: 以下内容节选自《High Performance Browser Networking》(出版社: O’Reilly,作者: Ilya Grigorik)。 要了解完整版本和相关内容,请访问 hpbn.co。 HTTP/2 可以让我们的应用更快、更简单、更稳定 – 这几词凑到一块是很罕见的!HTTP/2 将很多以前我们在应用中针对 HTTP/1.1 想出来的“歪招儿”一笔勾销,把解决那些问题的方案内置在了传输层中。 不仅如此,它还为我们进一步优化应用和提升性能提供了全新的机会! HTTP/2 的主要目标是通过支持完整的请求与响应复用来减少延迟,通过有效压缩 HTTP 标头字段将协议开销降至最低,同时增加对请求优先级和服务器推送的支持。 为达成这些目标,HTTP/2 还给我们带来了大量其他协议层面的辅助实现,例如新的流控制、错误处理和升级机制。上述几种机制虽然不是全部,但却是最重要的,每一位网络开发者都应该理解并在自己的应用中加以利用。 HTTP/2 没有改动 HTTP 的应用语义。 HTTP 方法、状态代码、URI 和标头字段等核心概念一如往常。 不过,HTTP/2 修改了数据格式化(分帧)以及在客户端与服务器间传输的方式。这两点统帅全局,通过新的分帧层向我们的应用隐藏了所有复杂性。 因此,所有现有的应用都可以不必修改而在新协议下运行。 为什么不是 HTTP/1.2? 为了实现 HTTP 工作组设定的性能目标,HTTP/2 引入了一个新的二进制分帧层,该层无法与之前的 HTTP/1.x 服务器和客户端向后兼容,因此协议的主版本提升到 HTTP/2。 即便如此,除非您在实现网络服务器(或自定义客户端),需要使用原始的 TCP 套接字,否则您很可能注意不到任何区别: 所有新的低级分帧由客户端和服务器为您执行。 可观察到的唯一区别将是性能的提升和请求优先级、流控制与服务器推送等新功能的出现。 SPDY 与 HTTP/2 简史 SPDY 是 Google… Continue reading HTTP/2 简介