Chinaunix首页 | 论坛 | 博客
  • 博客访问: 483393
  • 博文数量: 78
  • 博客积分: 995
  • 博客等级: 准尉
  • 技术积分: 1462
  • 用 户 组: 普通用户
  • 注册时间: 2011-11-15 20:22
个人简介

技术中沉思的时候最快乐,问题得到完美解决的时候最有成就感!

文章分类

全部博文(78)

文章存档

2013年(39)

2012年(37)

2011年(2)

分类: LINUX

2012-06-11 17:36:20

     背景:
     用户利用各种客户端,包括网页,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 左右...
     

阅读(1676) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

qq2000zhong2012-06-28 18:13:27