TCP链接主动关闭不发fin包奇怪行为分析
问题描述:
多隆同学在做网络框架的时候,发现一条tcp链接在close的时候,对端会收到econnrest,而不是正常的fin包. 通过抓包发现close系统调用的时候,我端发出rst报文, 而不是正常的fin。这个问题比较有意思,我们来演示下:
Read more…
问题描述:
多隆同学在做网络框架的时候,发现一条tcp链接在close的时候,对端会收到econnrest,而不是正常的fin包. 通过抓包发现close系统调用的时候,我端发出rst报文, 而不是正常的fin。这个问题比较有意思,我们来演示下:
Read more…
前天有同学在玩erlang gen_tcp的时候碰到了点小麻烦,描述如下:
比如说连接到baidu.com,发个http请求,然后马上接收数据,发现接收出错,wireshark抓包发现数据都有往返发送,比较郁闷。
我把问题演示下:
$ erl Erlang R14B03 (erts-5.8.4) [source] [64-bit] [smp:16:16] [rq:16] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.8.4 (abort with ^G) 1> {ok,Sock} = gen_tcp:connect("baidu.com", 80, []). {ok,#Port<0.582>} 2> gen_tcp:send(Sock, "GET / HTTP/1.1\r\n\r\n"). ok 3> gen_tcp:recv(Sock,0). {error,einval}
这个问题的根源在于gen_tcp默认的{active,true},也就是说当gen_tcp收到网络包的时候,默认是把报文发送给它的宿主进程。而gen_tcp:recv是用户主动去拉数据,这二个模式是互斥的。
我们来看下代码otp/erts/emulator/drivers/common/inet_drv.c:7462
case TCP_REQ_RECV: { .. if (desc->inet.active || (len != 8)) return ctl_error(EINVAL, rbuf, rsize); ..
那就解释为什么 gen_tcp:recv(Sock,0)返回错误码{error,einval}。
同时我们来验证下,报文是以消息的方式发送的。
4> flush(). Shell got {tcp,#Port<0.582>, "HTTP/1.1 400 Bad Request\r\nDate: Fri, 01 Jul 2011 03:51:25 GMT\r\nServer: Apache\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n127\r\n<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<HTML><HEAD>\n<TITLE>400 Bad Request</TITLE>\n</HEAD><BODY>\n<H1>Bad Request</H1>\nYour browser sent a request that this server could not understand.<P>\nclient sent HTTP/1.1 request without hostname (see RFC2616 section 14.23): /<P>\n</BODY></HTML>\n\r\n0\r\n\r\n"} ok 5>
搞清楚了问题,那解决方案很简单,connect的时候把active模式设成{active,false}.
再来演示下:
$ erl Erlang R14B03 (erts-5.8.4) [source] [64-bit] [smp:16:16] [rq:16] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.8.4 (abort with ^G) 1> {ok,Sock} = gen_tcp:connect("baidu.com", 80, [{active,false}]). {ok,#Port<0.582>} 2> gen_tcp:send(Sock, "GET / HTTP/1.1\r\n\r\n"). ok 3> gen_tcp:recv(Sock,0). {ok,"HTTP/1.1 400 Bad Request\r\nDate: Fri, 01 Jul 2011 05:25:15 GMT\r\nServer: Apache\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n127\r\n<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<HTML><HEAD>\n<TITLE>400 Bad Request</TITLE>\n</HEAD><BODY>\n<H1>Bad Request</H1>\nYour browser sent a request that this server could not understand.<P>\nclient sent HTTP/1.1 request without hostname (see RFC2616 section 14.23): /<P>\n</BODY></HTML>\n\r\n0\r\n\r\n"} 4>
搞定!
玩得开心!
昨天有同学在使用sysbench时候遇到了点小麻烦:
$ sysbench --test=oltp --oltp-table-size=100000000 --oltp-read-only=off --init-rng=on --num-threads=16 --max-requests=0 --oltp-dist-type=uniform --max-time=1800 --mysql-user=root --db-driver=mysql --mysql-table-engine=innodb --oltp-test-mode=simple prepare sysbench 0.4.12: multi-threaded system evaluation benchmark FATAL: unable to connect to MySQL server, aborting... FATAL: error 1049: Unknown database 'sbtest' FATAL: failed to connect to database server! ...
错误提示说:mysql连接不上, sbtest库没找到。
首先确认mysql是正常的…
$ mysql -u root Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 91 Server version: 5.1.48-debug-log Source distribution Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql> use sbtest; ERROR 1049 (42000): Unknown database 'sbtest'
但是库 sbtest确实不存在。
通过查看sysbench-0.4.12/sysbench/drivers/mysql/drv_mysql.c:400行
DEBUG("mysql_real_connect(%p, \"%s\", \"%s\", \"%s\", \"%s\", %u, \"%s\", %s)", con, host, args.user, args.password, args.db, args.port, args.socket, (MYSQL_VERSION_ID >= 50000) ? "CLIENT_MULTI_STATEMENTS" : "0" ); if (!mysql_real_connect(con, host, args.user, args.password, args.db, args.port, args.socket, #if MYSQL_VERSION_ID >= 50000 CLIENT_MULTI_STATEMENTS) #else 0) #endif
我们可以看到sysbench在连接的时候需要先连接到sbtest库,但是库不存在,所以出现问题。
解决问题的方法很简单:
在mysql的shell下运行:
create database sbtest;
搞定。
小结:开源软件总是有点小问题,自己动手丰衣足食!
玩得开心!
ulimit最初设计是用来限制进程对资源的使用情况的,因为早期的系统系统资源包括内存,CPU都是非常有限的,系统要保持公平,就要限制大家的使用,以达到一个相对公平的环境。以下是典型的机器默认的限制情况:
$ ulimit -a core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 204800 max locked memory (kbytes, -l) 32 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 10240 cpu time (seconds, -t) unlimited max user processes (-u) 204800 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited
但是很多年过去了,情况发生变化了,硬件在过去的时间里面发展的非常迅猛,一个拥有几十个核心的,上百G内存的机器差不多也是白菜价格了。但是软件的限制还是没怎么发生变化,导致一系列使用的问题。其中很重要的文件句柄使用限制尤为明显。
特别是类似web服务器,数据库程序等需要大量的文件句柄,一旦开太小,比如默认(1024),在句柄使用完毕的时候,系统就频繁出现emfile错误
,这时候系统很容易陷入不可用。但是如果设定太大了,又会有这样的副作用。很多服务器程序是事件派遣的,比如说用epoll,程序在启动的时候通常会根据最大的文件句柄数来预留内部的slot,比如说Erlang一个slot貌似要占用几K的资源,如果你设定文件句柄数目太大,就可能无端的浪费了几百M内存。所以要正视这个问题,设定一个合适的值。
通常我们是在shell下用来ulimit -n NNNN来设定新开的进程的文件句柄的限制,但是在一个生产环境下会有如下麻烦:
$ ulimit -n 22222222 -bash: ulimit: open files: cannot modify limit: Operation not permitted $ sudo ulimit -n 22222222 sudo: ulimit: command not found
JulyClyde(julyclyde@gmail.com)同学介绍解释了这个问题:
shell里不能直接更改,是因为登录的时候pam已经从limits.conf中设置了上限,ulimit命令只能在低于上限的范围内发挥了。
这时候我们通常需要修改/etc/security/limits.conf
# 确认包含下面的内容: * soft nofile NNNNN * hard nofile NNNNN
修改后,重现登录shell, 用ulimit -Hn和ulimit -Sn确认修改已生效.
另外淘宝雕梁说:
在linux kernel 2.6.25之前通过ulimit -n(setrlimit(RLIMIT_NOFILE))设置每个进程的最大打开文件句柄数不能超过NR_OPEN (1024*1024),也就是100多w(除非重新编译内核),而在25之后,内核导出了一个sys接口可以修改这个最大值(/proc/sys/fs /nr_open).具体的changelog在这里
直接在你自己的程序里面绕开文件句柄的限制。
祝大家玩得开心!
Linux的高速缓存pagecache对性能的影响至关重要,但是实际系统中我们的利用率如何呢,特别是具体到每个设备的利用情况。
我们知道IO请求由vfs发起,经过pagecache缓存,挡不住的就落实到io设备去,那么统计这个利用率就很简单。 我们只要知道挡不住的IO的比例就好了。
我写了个systemtap脚本来解决这个问题:
Read more…
Linux系统很重要的一个性能提升点就是它的Pagecache, 因为内存比IO快太多了,所以大家都想进办法来利用这个cache。 文件系统也不例外,为了达到高性能,文件读取通常采用预读来预测用户的行为,把用户可能需要的数据预先读取到cache去,达到高性能的目的。
Linux各个发行版readahead的实现差异很大,我们这里重点讨论2.6.18, RHEL 5U4发行版的行为.文件预读的实现主要在mm/readahead.c中,代码才603行。 预读的流程大概是这样的,用户需要文件页面的时候入口函数do_generic_mapping_read会委托page_cache_readahead来进行处理。它首先判断用户的IO是顺序的还是随机的,如果是随机的就没啥好预读. 如果是顺序的话,那么预读算法会根据用户上一次读取的页面的使用情况评估出预读的窗口,决定要读多少页面。读页面的模块会先检查要读取页面在pagecache里面是否已经存在,如果不存在的话就需要发起IO请求,读取相应的页面。还有个路径就是在文件mmap缺页的时候filemap_nopage调用do_page_cache_readahead进行预读, 不过这个路径在通常的环境里概率不高.
这个预读的关键参数有3个: 用户的req_size, 预读算法评估出来的nr_to_read,以及实际上IO读取的页面数actual。
接下来我们就是要查看系统是如何运作的,所以我首先写了个systemtap脚本叫做ratop.stp来获取这些数据:
Read more…
Leveldb是一个google实现的非常高效的kv数据库,目前的版本1.2能够支持billion级别的数据量了。 在这个数量级别下还有着非常高的性能,主要归功于它的良好的设计。特别是LSM算法。
那么数据库最怕的的随机IO他是如何解决的呢?
先说随机写,它的写都是先记录到日志文件去的,在日志文件满之前只是简单的更新memtable,那么就把随机写转化成了顺序写。在日志满了后,把日志里面的数据排序写成sst表同时和之前的sst进行合并,这个动作也是顺序读和写。大家都知道传统磁盘raid的顺序读写吞吐量是很大的,100M左右是没有问题。在写日志文件的时候,用到是buffer IO,也就是说如果操作系统有足够的内存,这个读写全部由操作系统缓冲,效果非常好。即使是sync写模式,也是以数据累计到4K为一个单位写的,所以效率高。
那么随机读呢?这个它解决不了。但是ssd盘最擅长随机读了。这个硬件很自然的解决了这个问题。
所以leveldb的绝配是ssd盘的raid.
Read more…
Recent Comments