背景: 用户利用各种客户端,包括网页,pc客户端,无线客户端上传本地文件到服务器保存;
上传速度作为一个非常重要的关键指标决定了产品体验的好坏;
架构:
1,接入采用开源web server,接入业务逻辑采用类似cgi方式(并发可以非常高);
2,数据存储,业务数据维护采用c++写的守护server;
3,cgi 和 上传server采用简单的内部二进制协议交互,传递文件数据流,这样,上传server完全不
需要关心http,完全不需要关心上传方式...世界甚是清静;
问题:
cgi 是阻塞的,cgi必须向上传server发送数据然后等回包,这一过程是阻塞的;且这一阻塞还挺长
时间的(大概有500ms左右,没办法,后面的逻辑流程长而复杂...),在这一过程中,不能做任何事情,
自然不能接收用户的上传数据;
用户客户端的现象是:上传速度忽上忽下,忽高忽低;
解决:
否定的方案:采用非阻塞,自己搭并发异步io或者多路复用server;可以比作nginx的上传模块,
nginx在等待写磁盘的过程中,可以继续接收用户数据;nginx会很好的处理好用户的socket和磁盘写入
的匹配,保证磁盘写入和用户socket一直是满负荷运转的,且一致的;在用户端看来,上传速度是恒
定的;经过考虑,还是否定了这一方案:
1,搭这个server是费力费时的,处理http协议就够喝一壶了;
2,server耦合度,复杂度都会很大,处理http协议,二进制数据流,种类繁多的上传方式等
业务逻辑处理全集中到一个server中去了,光这一条就直接否定了...这种server根本不具有维护性;
3,最重要的原因是:自己写server处理这种大数据流,很难保证上传速度最大化的(和开源的
web server比起来肯定会差不少),说不定最后出来的结果是:速度是恒定了,但是一个较低速度的恒定;
虽然cgi方案的上传速度忽高忽低,但一平均,也许还快些;那就费力不讨好了;
垫底方案:采用 nginx 做web server,异步数据收发由开发额外的 nginx 扩展模块实现,这一
方案实现起来听起来有点吸引力,架构和现有的架构吻合;只是将 cgi 改成了扩展模块;且nginx扩展
模块用的也还算熟悉,写过几个比较复杂的扩展模块;
但最后还是没采用:
1,写 nginx 扩展模块真的很痛苦,已经写好的几个扩展模块感觉有点摆弄技术...复杂,非常容易
出bug,开始感觉有点意思,后来感觉痛苦了...
2,nginx 扩展模块技术性有点强,在可维护性和可传承上比较差
3,不到万不得已,我不想采用这个方案,最后果然没采用,因为我有了更好的,且简单一万倍的:
采用方案:
问:为什么cgi在阻塞的过程中,客户端的上传也被阻塞了?
答:因为tcp有流量控制,tcp每个连接都有接收缓冲,当这个缓冲满了,发送方(即客户端)就
停止发送数据了,也即被阻塞了;
问:在什么条件下这个接受缓冲区会满?
答:拥有这个socket的进程不执行从这个socket read或者recv走操作系统已经接收到的数据;
问:那如果这个缓冲区如果足够大,那还会阻塞么?
答:假设这个接受缓冲区有1G,那即使进程根本不从socket读任何数据,那发送方发送小于1G大小的
文件数据流,也不会有任何阻塞;
问:1G夸张了点,那这个接收缓冲大小到底多大正好够用且不浪费呢?
答:假设瓶颈在cgi(如果瓶颈在于用户带宽那就没办法了),每次cgi收集到4M收据发送一次,
那最理想的情况是:cgi发送完毕后,再从socket中取数据,刚好一次就能取到需要的4M;
所以,如果这个接收缓冲大小4M左右差不多了;
问:具体实施?
答:见下文...
总结:
I) 在采用复杂方案之前,先冷静下来,等过段时间再回头去看,复杂方案是最佳方案么?有没有更
简洁的方案?
II) 视野开阔点,站高点,从系统层面看面临的问题,首先想想为什么会产生这个问题?然后再根据
具体的原因针对的性的采用针对性的方案;
III)Keep it simple stupid,这个原则永远不过时;
简洁即是美,简洁才能永恒,简洁才能传承;
实战:
1,设置 SOCK_RECVBUF:
最简单的接收缓冲设置大小就是设置 /proc/sys/net/ipv4/tcp_rmem 第二个参数,但最好不要
这样简单粗暴,这样改动会影响到整个系统的默认接收缓冲大小;
最好在进程中调用 setsocketopt(, SOCK_RECVBUF),设置进程本身的监听socket的recv buf size,
注意,这个调用必须在 listen 和 bind 之前调用,否则不起作用;
在我的进程中,我直接设置为 8M,发现没有达到理想的目标,上传速度还是忽高忽低,怀疑没设置
成功,于是在调用 setsocketopt 后,我又调用了 getsocketopt 获取真实的 recv buf size,发现果然
没有 4M,只有 180K 左右;哦,原来这个值还受限于 /proc/sys/net/core/rmem_max 和
/proc/sys/net/ipv4/tcp_rmem 的第三个参数,于是将这两处改成了 16M,再试,OK,输出了:
“last effctive recvbuf=8388608”
2,测试
首先要测试的是tcp的接收缓冲大小设置后,是不是真的和我们设想的结果一致,最佳的办法是
I) tcpdump port 80 -i eth1 > /tmp/yyyy.txt
II) gdb 挂住web server(直接 gdb -p,然后什么都不做)
III)上传文件
测试结果:
一)用户端很明显的看到,上传进度条前进到6-7M左右后,停了下来,和服务器的缓冲大小差不多;
二)打开 tcpdump 输出看到最后几行
tcp 的接收窗口慢慢变小,最后变成了0,也即接收缓冲最后满了
三)可以根据tcpdump输出计算出服务器此socket接收到的数据大概计算 1460*4272 = 6,237,120B
grep "(1460)" /tmp/zzzz.txt | wc -l => 4272
3,退出2步骤中的gdb,用户端的上传速度稳定在 5-6 M 左右...
阅读(1858) | 评论(1) | 转发(0) |