2010年6月

浏览器对同一个Server并发连接数限制

HTTP/1.1协议的RFC2616中8.1.4中讲到:




Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. A proxy SHOULD use up to 2*N connections to another server or proxy, where N is the number of simultaneously active users. These guidelines are intended to improve HTTP response times and avoid congestion.



 


使用持久连接的Client不应该维持到同一Server多于2个的连接数,基本上,各个浏览器都依据该规范,实现了自己的限制,如下,引自这里



折叠复制代码




  1. Browser HTTP/1.1 HTTP/1.0

  2. IE 6,7 2 4

  3. IE 8 6 6

  4. Firefox 2 2 8

  5. Firefox 3 6 6

  6. Safari 3,4 4 4

  7. Chrome 1,2 6 ?

  8. Chrome 3 4 4

  9. Opera 4 4




最杯具的莫过于IE6了,严格按照RFC规范来实现,每Server只能并发2个连接,到了IE8就有了改进,增大到了6个,见这里


Six connections per hostfor broadband scenarios instead of two, and a scriptable property, improve performance by allowing parallelization of downloads in Internet Explorer 8. This also increases functionality by ensuring that a request is not blocked to a host if two connections already exist. Websites can optimize downloads based on thewindow.maxConnectionsPerServerproperty.


这个限制,对于普通的Web应用来讲,影响不算太大,毕竟正常的连接请求都会很快有返回结果的


但如果在Web中要是用长连接的话,就可能会有比较大的问题了,尤其是在IE6里面


长连接会一直占用一个连接数,如果你有2个长连接,那就更杯具了,浏览器这时将无法向同这个Server去request了...


解决办法,看起来只有2个:


1. 修改浏览器的设置,IE和Firefox貌似都可以设置,但我们的Web应用总不能要求所有的用户都去修改那个设置吧


2. 把长连接的请求分发到其它的Server上,不要和普通的请求放在同一个Domain下面,但这样ajax就会有跨域的问题了,也许可以试试Jsonp?

Twisted application中TCPClient的使用

一般,在Twisted中使用Application的方式启动程序,是这样做:



折叠复制代码




  1. pop_service = internet.TCPServer(...,...)

  2. popService = service.MultiService()

  3. pop_service.setServiceParent(popService)

  4. application= service.Application('popqueueservice')

  5. popService.setServiceParent(application)




通过setServiceParent来把某一个服务运行在application中


如果程序中我们需要生成很多TCPClient对象,去做其它的很多事情,那么我们不能这么做:



折叠复制代码




  1. op_service = internet.TCPClient(popserver,popport,f)

  2. pop_service.setServiceParent(popService)











这样做是有很大问题的,因为每一个client的请求都被加入到了application中去运行

即使client执行完毕,也不会去释放自己对应的Factory实例和Protocol实例

长时间会造成内存中有很多这样的实例对象,内存不断增加,最后内存溢出...




这样的内存泄露,不太好定位,我之前检查了程序的各个地方,把能释放的资源都给释放了

唯独没有考虑到这个地方,最后使用了meliae工具,才定位到这个地方

然后这么修改的:



折叠复制代码




  1. pop_service.startService()

  2. f.deferred.addCallback(handleEnd,id,pop_service)

  3. 。。。。。。

  4. #在每个client执行完毕后的deffer中调用了

  5. s.stopService()




这样修改后,观察了几天,内存一直都比较稳定

利用meliae来监控python进程的内存占用情况

meliae是一个python进程内存占用监控、分析工具,它的安装需要依赖pyrex


meliae会把某个时刻的内存给dump到一个文件中,然后再对该文件进行分析


当我们的某个python程序占用内存很大,可能有内存泄露发生时,可以使用该工具来进行检测分析


安装和使用用都比较简单,在需要dump内存的地方,写上以下代码即可:



折叠复制代码




  1. from meliae import scanner

  2. scanner.dump_all_objects('/opt/log/dump.txt')




这样,我们就可以把当前的内存Objects都导出到了dump.txt,然后再进行分析



折叠复制代码




  1. from meliae import loader

  2. #加载dump文件

  3. om=loader.load('/opt/log/dump.txt')

  4. #计算各Objects的引用关系

  5. om.compute_parents()

  6. #去掉各对象Instance的_dict_属性

  7. om.collapse_instance_dicts()

  8. #分析内存占用情况

  9. om.summarize()




我的某个python程序分析结果如下:



折叠复制代码




  1. Total 333015 objects, 188 types, Totalsize=52.8MiB (55414199 bytes)

  2. Index Count % Size % Cum Max Kind

  3. 0 10620 3 18096480 32 32 1704 POP3ClientProtocol

  4. 1 133004 39 6290033 11 44 31457 str

  5. 2 10628 3 5866656 10 54 552 Connector

  6. 3 10628 3 5866656 10 65 552 POP3ClientFactory

  7. .......




从上面可以看到,共有333015个对象,占用了52M的内存


其中,共有10620个POP3ClientProtocol对象实例,占用了32%约18M的内存


最大的对象也只占用了1.7K的内存,但由于对象很多,所以最终它占用的内存就很大了


那么,我们就大概知道了内存泄露的地方,就是这个POP3ClientProtocol对象,在使用完成之后,没有释放造成的


我们还可以继续分析某个对象,找出它的引用关系



折叠复制代码




  1. #得到所有的POP3ClientProtocol对象  

  2. p = om.get_all('POP3ClientProtocol')  

  3. #查看第一个对象  

  4. p[0]  

  5. 说明该对象的地址为2803894924,占用了1.7K内存,引用了51个对象,它被1个对象所引用  

  6. >>>POP3ClientProtocol(2803894924 1704B 51refs
     1par)  

  7. #可以查看该对象的所有引用  

  8. p[0].c


  9. >>>[str(3079323384 33B 10647par 'popuserid'), str(2814724096 45B 1par 'fuzimiao2000@sohu.com'), str(3079036128 31B 10624par 'mailnum'), int(165090672 12B 29par '75'),

  10. #查看谁引用了这个对象

  11. p[0].p

  12. >>>[POP3ClientFactory(2803893100 552B 15refs 3par)]



Sohu邮箱上线全程https功能

今日,Sohu邮箱上线了“全程Https”的功能


mail.sohu.com的证书早就申请下来了


其实,技术要做的主要工作是:


如何不让浏览器弹出“不安全”的警告框


由于邮箱中有很多iframe,同时还有很多广告碎片等元素


所以,大部分时间都花在了整理前端的js


同时,修改后台的代码,过滤外域src资源并提供前端所需要的URL


以下是几个要点:



  1. 对于请求mail.sohu.com之外的资源,如CDN上的图片等,使用了反向代理,并在nginx层增加Cache(如果没有Cache,nginx端外域代理并发大时,可能会返回403错误...),参见:配置nginx的cache

  2. 遍历所有的iframe,把src中直接document.write的地方修改为具体的页面,如blank.html,参见:https下的iframe

  3. 对于一些图片广告资源,使用类似//mail.sohu.com/....或/static/images/...的方式,来自适应http和https的访问,参见:web页面中自动切换http/https

  4. 一些浏览器(如:IE)会自动缓存https下的Get请求,只好在url后面加上随机数来防止缓存了


 

实现一个简单的Gmail contextual gadget

关于Gmail contextual gadget是什么,可以参考这里

 

简单的说,Gmail contextual gadget是一个能在Gmail里触发的gadget

 

邮件的Subject,Body,From,To...等包含某个指定的文本或者link时,均可以触发改gadget

 

实现一个Gmail contextual gadget还算比较简单,我们只需要实现Manifest和Gadget.xml即可

 

以下是一个简单的Manifest文件,使用了google.com:HttpLinkExtractor,在Subject和Body中查找满足条件的链接

 


折叠复制代码




































































  1. <?xmlversion="1.0"encoding="UTF-8"?>
  2. <ApplicationManifestxmlns="http://schemas.google.com/ApplicationManifest/2009">
  3.  
  4. <!-- Support info to show in the marketplace & control panel -->
  5. <Support>
  6.  
  7. <!-- URL for application setup as an optional redirect during the install
  8. <Linkrel="setup"href="http://www.tech126.com/gadgets/setup.php?domain=${DOMAIN_NAME}"/>
  9. -->
  10.  
  11. <!-- URL for application configuration, accessed from the app settings page in the control panel
  12. <Linkrel="manage"href="http://www.tech126.com/gadgets/admin.php?domain=${DOMAIN_NAME}"/>
  13. -->
  14.  
  15. <!-- URL explaining how customers get support. -->
  16. <Linkrel="support"href="http://u.sohu.com/help/help1_free.jsp"/>
  17.  
  18. <!-- URL that is displayed to admins during the deletion process, to specify policies such as data retention, how to claim accounts, etc. -->
  19. <!--<Link rel="deletion-policy" href="http://www.tech126.com/google/deletion-policy.php" />-->
  20. </Support>
  21.  
  22. <!-- Name and description pulled from message bundles -->
  23. <Name>Sohu Mail UFile</Name>
  24. <Description>A sohu mail ufile info for Gmail contextual gadgets</Description>
  25.  
  26. <!-- Show this link in Google's universal navigation for all users -->
  27. <Extensionid="navLink"type="link">
  28. <Name>Sohu Mail UFile</Name>
  29. <Url>http://u.sohu.com?from=google&domain=${DOMAIN_NAME}</Url>
  30. </Extension>
  31.  
  32. <!-- Declare our OpenID realm so our app is white listed -->
  33. <Extensionid="realm"type="openIdRealm">
  34. <Url>http://u.sohu.com</Url>
  35. </Extension>
  36.  
  37. <!-- EXTRACTOR -->
  38.   ;
  39. <Extensionid="UFileIdExtractor"type="contextExtractor">
  40. <Name>UFileId</Name>
  41. <Url>google.com:HttpLinkExtractor</Url>
  42. <Paramname="url"value=".*u.sohu.com/download/(d+)/(d+)$"/>
  43. <Triggersref="UFileIdGadget"/>
  44. <Scoperef="emailSubject"/>
  45. <Scoperef="emailBody"/>
  46. <Containername="mail"/>
  47. </Extension>
  48.  
  49. <!-- GADGET -->
  50.  
  51. <Extensionid="UFileIdGadget"type="gadget">
  52. <Name>Sohu Mail UFile contextual gadget</Name>
  53. <Url>http://www.tech126.com/gadgets/sohu_mail_ufile_gadget.xml</Url>
  54. <Containername="mail"/>
  55. </Extension>
  56.  
  57. <!-- SCOPE -->
  58.  
  59. <Scopeid="emailSubject">
  60. <Url>tag:google.com,2010:auth/contextual/extractor/SUBJECT</Url>
  61. <Reason>This application searches the Subject: line of each email for the link "http://u.sohu.com/download".</Reason>
  62. </Scope>
  63.  
  64. <Scopeid="emailBody">
  65. <Url>tag:google.com,2010:auth/contextual/extractor/BODY</Url>
  66. <Reason>This application searches the message body of each email for the link "http://u.sohu.com/download".</Reason>
  67. </Scope>
  68.  
  69. </ApplicationManifest>


 

然后,再调用指定服务器上的gadget.xml去执行相应的操作

 


折叠复制代码





























































  1. <?xmlversion="1.0"encoding="UTF-8"?>
  2. <Module>
  3. <ModuleP refstitle="Sohu Mail UFile Link"
  4. description="The File information"
  5. height="20"
  6. author="yushunzhi"
  7. author_email="yushunzhi@sohu.com"
  8. author_location="Beijing, China">
  9.  
  10. <!-- Declare feature dependencies.
  11. The first one is not specific to Gmail contextual gadgets. -->
  12. <Requirefeature="dynamic-height"/>
  13.  
  14. <!-- The next feature, google.contentmatch, is required for all
  15. Gmail contextual gadgets.
  16. <Param>- specify one or more comma-separated extractor IDs in
  17. a param named "extractors". This line is overridden by the extractor ID
  18. in the manifest, but is still expected to be present. -->
  19. <Requirefeature="google.contentmatch">
  20. <Paramname="extractors">
  21. google.com:HttpLinkExtractor
  22. </Param>
  23. </Require>
  24. </ModulePrefs>
  25.  
  26. <!-- Define the content type and display location. The settings
  27. "html" and "card" are required for all Gmail contextual gadgets. -->
  28. <Contenttype="html"view="card">
  29. <![CDATA[
  30. <head>
  31. <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  32. <style>
  33. html, body {
  34. color:#333333;
  35. font:12px Arial,Helvetica,sans-serif;
  36. margin:0;
  37. padding:0;
  38. </style>
  39. </head>
  40. <script src="http://www.tech126.com/gadgets/jquery-1.4.2.min.js" type="text/javascript"></script>
  41. <script type="text/javascript">
  42.  
  43. (function ()
  44. {
  45. var exp = /u.sohu.com/download/(d+)/(d+)/;
  46. var matches = google.contentmatch.getContentMatches();
  47. var numLoaded = 0;
  48. var exp_match = [];
  49. var url = '', bid = '', fileid = '';
  50. var img_postfix = ['jpg','jpeg','gif','png'];
  51. var matchList = document.createElement('ul');
  52. var listItem, childItem;
  53.  
  54. document.body.appendChild(matchList);
  55.  
  56. for(var i = 0 ; i < matches.length ; ++i)
  57. {
  58. url = matches.url;
  59. exp_match = url.match(exp);
  60. if (!exp_match)
  61. {
  62. continue;
  63. }
  64.  
  65. bid = exp_match[1];
  66. fileid = exp_match[2];
  67.  
  68. $.ajax(
  69. {
  70. url: "http://u.sohu.com/uFileInfo.action",
  71. dataType: 'jsonp',
  72. data: {'fileId':fileid},
  73. jsonp: 'callBack',
  74. success: function(json)
  75. {
  76. json = json || {};
  77. listItem = document.createElement('li');
  78. listItem.innerHTML = "File Url: " + "http://u.sohu.com/download/" + json.fileSe rver + "/" + json.fileId;
  79. childItem = document.createElement('div');
  80. childItem.innerHTML = "File Name: " + json.fileName;
  81. listItem.appendChild(childItem);
  82. childItem = document.createElement('div');
  83. childItem.innerHTML = "File Size: " + json.fileSize;
  84. listItem.appendChild(childItem);
  85. childItem = document.createElement('div');
  86. childItem.innerHTML = "File User: " + json.fileUser;
  87. listItem.appendChild(childItem);
  88. childItem = document.createElement('div');
  89. childItem.innerHTML = "File Type: " + json.fileType;
  90. listItem.appendChild(childItem);
  91. if (img_postfix.indexOf(json.fileType.toLowerCase()) >= 0)
  92. {
  93. childItem = document.createElement('img');
  94. childItem.src = "http://u.sohu.com/file/preview/image/" + json.fileServer + "/" + json.fileId;
  95. listItem.appendChild(childItem);
  96. }
  97. childItem = document.createElement('p');
  98. listItem.appendChild(childItem);
  99. matchList.appendChild(listItem);
  100. },
  101. timeout: 3000
  102. }
  103. );
  104. }
  105. gadgets.window.adjustHeight(200);
  106.  
  107. })();
  108.  
  109. </script>
  110. ]]><I></I>
  111. </Content>
  112. </Module>




这个Gadgets功能很简单,就是判断如果有Sohu U盘发送的文件链接,则后台使用Jsonp去获取该文件的一些信息


如果文件是图片,那么就生成一个缩略图展示出来


实现后,可以到Google marketplace上去创建一个List,最后不要submit激活,如果激活,Googel会收取$100的费用...


然后,添加到自己的Google Apps domain中去测试,在调试过程中,访问mail最好加上nogadgetcach=1的参数,不让其缓存,如:


https://mail.google.com/a/tech126.com/?nogadgetcache=1#


以下是运行这个Gadget后的截图:


小试Mysql存储过程

今天,需要对sohu邮箱用户的过期积分进行清理


过期的积分早已经计算好了,存放在mysql的一个表中


因此,清理的过程逻辑相对比较简单,就是update用户的可用积分,并保留此刻的可用积分作为历史记录


由于都是针对Mysql的操作,所以,就想用Mysql的存储过程来实现


N年之前,我在第一家公司里,曾写过无数的SQL SERVER的存储过程和触发器


MySql的Procedure一直没写过,不过,查了下Mysql的参考手册,还是比较简单的



折叠复制代码




  1. drop procedure if exists pro_ysz;    

  2.   

  3. delimiter //   

  4.   

  5. CREATE PROCEDURE pro_ysz(out rtn int)   

  6. BEGIN  

  7.   

  8.     DECLARE v_userid varchar(50);    

  9.     DECLARE v_totalscore int default 0;   

  10.     DECLARE v_freescore int default 0;   

  11.     DECLARE v_expirescore int default 0;   

  12.     DECLARE v_done int default 0;   

  13.   

  14.     DECLARE cur cursor for select userid,expirescore from ... where ... limit 0,10;   

  15.   

  16.     -- cursor end  

  17.     DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET v_done = 1;    

  18.     -- exception occur  

  19.     DECLARE CONTINUE HANDLER FOR SQLEXCEPTION rollback;   

  20.   

  21.     SET rtn = 0;   

  22.   

  23.     open cur;      

  24.           

  25.     fetch cur into v_userid,v_expirescore;   

  26.        

  27.     while v_done = 0 do   

  28.   

  29.         select ...,... into v_totalscore,v_freescore from ... where ... = v_userid;   

  30.            

  31.         select v_userid,v_totalscore,v_freescore;   

  32.            

  33.         if v_freescore > v_expirescore then  

  34.             set v_freescore = v_freescore - v_expirescore;   

  35.         else  

  36.             set v_freescore = 0;   

  37.         end if;   

  38.   

  39.         insert into ... (...) values(v_userid,v_totalscore,v_freescore,now());   

  40.            

  41.  &nb
    sp;      
    update ... set ... = v_freescore where ... = v_userid;   

  42.         update ... set ... = 1 where ... = v_userid;   

  43.   

  44.         commit;   

  45.            

  46.         -- reset score  

  47.         set v_totalscore = 0;   

  48.         set v_freescore = 0;   

  49.         set rtn = rtn + 1;   

  50.   

  51.         fetch cur into v_userid,v_expirescore;   

  52.            

  53.     end while;   

  54.   

  55.     close cur;    

  56.   

  57.  END  

  58.   

  59. //   

  60.   

  61. delimiter ;  






其中,里面有几个要点:



  1. delimiter //,是设置Mysql statement的分隔符,默认是分号;,所以需要先修改成其它的符号,最后修改回去

  2. out rtn int,设置存储过程的返回参数,使用call pro_ysz(@rtn);调用后,可通过select @rtn;来查看返回值

  3. DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET v_done = 1;当游标到末尾时,设置对应的变量,控制后续的循环

  4. DECLARE CONTINUE HANDLER FOR SQLEXCEPTION rollback;设置执行中有异常时,rollback,并继续后续的执行

  5. select v_userid,v_totalscore,v_freescore;在存储过程中模拟print语句来调试,可以打印出对应的变量值


最后,由一个Shell来执行



折叠复制代码




  1. cdate=`date +%Y%m%d%H%M%S`   

  2. echo "$cdate Starting shell..."   

  3. while [ 1 -lt 2 ]   

  4. do   

  5.         mysql -e 'call pro_ysz(@rtn)' db   

  6.         sleep 5   

  7. done  






 

当Https遇到了Iframe

最近,邮箱准备上全程的https服务


在把所有的页面引用的src全都改到https的路径后,通过https访问仍然会有浏览器的安全警告框


查了很久无果,后来在邱老大的提示下,才发现原来是Iframe导致的


Https中如果有Iframe,如果无src或者src中有document.wirte等,浏览器可能会认为是不安全的


以下是测试结果:



  1. 无src属性,IE6会有警告,IE8,Firefox均正常

  2. src="javascript:document.open();document.close();" ,IE下正常,Firfox下有警告

  3. src="javascript:void(0);",IE6有警告,IE8,Firefox均正常

  4. src="",IE6有警告,IE8,Firefox均正常


现在Mail前端的js中,有很多iframe的src不是指向到具体的页面,而是通过document.open(),document.write()来写入代码


当初,前端开发工程师可能是为了减少连接数,就这么实现的


在Https中这么做是不行的,只好src到一个空的blank.html页面,然后在js中再进行write


如果需要监听某个iframe的load完成事件,则上述办法也不行,只能实现一个真实的html了,里面加上对应的逻辑

最新文章

最近回复

  • feifei435:这两个URI实际是不一样的
  • zsy: git push origin 分支 -f 给力!
  • 冼敏兵:简单易懂,good fit
  • Jack:无需改配置文件,看着累! # gluster volume se...
  • Mr.j:按照你的方法凑效了,折腾死了。。。。
  • zheyemaster:补充一句:我的网站路径:D:\wamp\www ~~菜鸟站长, ...
  • zheyemaster:wamp2.5(apache2.4.9)下局域网访问403错误的...
  • Git中pull对比fetch和merge | 炼似春秋:[…] 首先,我搜索了git pull和git fe...
  • higkoo:总结一下吧, 性能调优示例: gluster volume s...
  • knowaeap:请问一下博主,你维护的openyoudao支持opensuse吗

分类

归档

其它