需求,图片上传,需要浏览完后就在指定位置显示图片,支持所有浏览器。
分析,不能只用JS完成,不可能支持所有浏览器,所以只能用后台技术。
JSP:
<dl class="l UbLeft"> <dt><a href=""><img src="${basePrefix}/images/meh.jpg" id="photo1-img" width="108" height="105" /></a><input type="hidden" id="photo1-val" name="url1" value=""/></dt> <dd><input type="file" id="photo1" class="Dina l" name="userPic" onchange="uploadUserPicMore(this)"/><a href="javascript:void(0)" onclick="delUpload(1)"><f:message key="member_jsp.jsp.photo.uploadPhoto.jsp.delete" /></a></dd> </dl> js: [html] view plaincopy function uploadUserPicMore(file){ var id = $(file).attr('id'); $("#loading").ajaxStart(function(){ $(this).show(); }).ajaxComplete(function(){ $(this).hide(); }); $.ajaxFileUpload( { url:'/uploadUserPic.html', secureuri:false, fileElementId:id, dataType: 'json', data:{}, success: function (data) { $("#"+id+"-img").attr("src",data.url); $("#"+id+"-val").val(data.url); }, error: function (data, status, e) { alert(e); } } ); return false; } |
21世纪是云的世纪, 大规模云网已经出现了,而且在未来几年内会得到高速发展,从而使得基于云的系统也会越来越多。如果要开发一款高性能的云系统,服务器
性能测试是一个必不可少的环节。今天,就来介绍一款新一代服务器性能测试工具Gatling。
一,什么是Gatling
Gatling是一款基于Scala 开发的高性能服务器性能测试工具,它主要用于对服务器进行负载等测试,并分析和测量服务器的各种性能指标。Gatling主要用于测量基于HTTP的服务器,比如Web应用程序,RESTful服务等,除此之外它拥有以下特点:
支持Akka Actors 和 Async IO,从而能达到很高的性能
支持实时生成Html动态轻量报表,从而使报表更易阅读和进行数据分析
支持DSL脚本,从而使测试脚本更易开发与维护
支持录制并生成测试脚本,从而可以方便的生成测试脚本
支持导入HAR(Http Archive)并生成测试脚本
支持Maven,Eclipse,IntelliJ等,以便于开发
支持Jenkins,以便于进行持续集成
支持插件,从而可以扩展其功能,比如可以扩展对其他协议的支持
开源免费
Gatling适用的场景包括:测试需求经常改变,测试脚本需要经常维护;测试环境的客户机性能不强,但又希望发挥硬件的极限性能;能对测试脚本进行很好的版本管理,并通过CI进行持续的性能测试;希望测试结果轻量易读等。
相关厂商内容
QCon上海技术训练营:OSGi、GitHub、Scrum深度培训,10月29-31日与您相约,了解详情! 2013年10月26日QClub大连站:大连
软件开发者大会 QCon上海2013“团队文化”专题:构建持续前进的团队文化、价值观,工具与体系 GitHub中国上海Drinkup活动,就在QCon上海2013前夜,贝尼酒吧 QCon上海2013“游戏服务器实践”专题:游戏服务器运维经验、架构分享、性能策略
二,Gatling与JMeter
JMeter是目前使用最为广泛的服务器性能测试工具之一,它最大的特点就是拥有一套简单易用的GUI,但它最大的缺点也是由于简单易用导致它某些方面的不足,比如测试脚本(XML)不容易维护等。Gatling正是针对JMeter的劣势做了大量改进,因此相较于 JMeter,Gatling拥有以下优势:
在并发性能方面,Gatling使用了Akka Actors和Async IO, 而JMeter则采用了一个用户使用一个线程的方式 ,一旦并发线程过多,性能就急速下降,很难充分发挥硬件的能力。虽然两个工具都是基于JVM的,但是Actors模型的性能在高并发的情况下性能大大优于Threads,从而使得Gatling在更少的内存和CPU的情况下可以提供同样的测试能力,降低了测试成本。图1和图2分别展现了二者在并发性能方面的表现。
图1,JMeter 2.8
图2,Gatling 1.3.2
图片,测试环境和测试脚本参见:https://github.com/excilys/gatling/wiki/Benchmarks
其中图1和图2分别是JMeter和Gatling在300个用户并发下的测试结果。可以很明显的看出,JMeter的并发量在300上下波动,最高达到400,最低达到200,而Gatling几乎稳定在300。由此可见Gatling性能的稳定性。
在测试脚本方面,Gatling是Scala代码,而JMeter主要是XML代码。Gatling基于一套开源的Gatling DSL API,所以它的功能很容易扩展,也不需要使用者精通Scala语言。DSL的使用也更容易编写出简明,易读性和维护性高的代码,而且还可以使用版本工具进行更有效的管理。因为性能测试应该属于系统发布流程中必不可少的一个步骤,所以测试脚本应该和系统代码一样使用版本工具进行统一管理。
在报表系统上,Gatling提供了一套轻量并且十分友好的Html报表系统,使得用户可以更为快速而方便地查看和分析数据,相反,JMeter的报表系统却十分笨重,且使用也不方便。
三,如何在项目中使用Gatling
对于Gatling这样一个全新的服务器性能测试工具,是否能将它很好的运用到项目中并发挥其优势,这个是一个困扰测试决策者的问题。下面我将结合在一个真实项目中使用和部署Gatling的经验来解答这个问题。
搭建测试环境
在一个大型的Web项目中,测试环境的搭建是项目测试工作开始的第一步,也是最为关键的一步,因为测试环境直接影响到测试成本和测试结果。由于这个项目对于性能的要求并不是很高,我们经过讨论和分析,决定选取虚拟机作为测试平台。这就意味着被测系统以及测试客户端可以使用的硬件资源比如CPU和内存十分有限,因此需要测试工具能充分使用有限的资源发挥最大的性能。
进行负载测试
为了快速实现测试脚本,我首先选择了使用Gatling录制功能进行脚本录制,成功录制以后会在指定的“Output folder”目录下面生成你指定“Class Name”为名字的脚本,见图3。
图3,Gatling Recorder
根据图3的配置,录制好的脚本存放在/Users/twer/work/gatling/user-files/simulations/Common/MyRecordedSimulation.scala。生成的部分脚本代码如下:
class MyRecordedSimulation extends Simulation { val httpProtocol = http .baseURL("http:// :10.17.7.3") .acceptHeader("image/png,image/*;q=0.8,*/*;q=0.5") .acceptEncodingHeader("gzip, deflate") .acceptLanguageHeader("en-US,en;q=0.5") .connection("keep-alive") .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:22.0) Gecko/20100101 Firefox/22.0") val headers_1 = Map( """Accept""" -> """text/html,application/xhtml+xml,application/xml;q=0.9, */*;q=0.8""", """If-None-Match""" -> """"a3ef335152d5532e2297bd8ad288f3f9"""") |
录制代码段1
.exec(http("request_21") .get("""/customer/images/new?app_dialog=true&dialog=true""") .headers(headers_18)) .pause(6) .exec(http("request_22") .get("""/customer/images?_=1374400463122""") .headers(headers_19)) .pause(165 milliseconds) .exec(http("request_23") .get("""/Users/twer/work/gatling/user-files/simulations/testdata/test1. png""") .headers(headers_20) .check(status.is(500))) .pause(2) .exec(http("request_24") .get("""/customer/images?view=list""")) .pause(86 milliseconds) …… setUp(scn.inject(atOnce(1 user))).protocols(httpProtocol) |
录制代码段2
录制出来的脚本拥有很多局限性:
只支持1个用户
没有检测点
没有逻辑分层
因此,它并不能用于真正的性能测试。对于这样的原始代码,我们需要进行大量的重构,使代码拥有很好的可读性和可维护性。
首先我们要进行分层处理:
对于录制代码段1,需要建立一个Header类来管理所有HTTP Header,这里使用“Headers.scala”,在录制代码段1中只给出了“headers_1”,实际的脚本包含了大量的Header。
对于录制代码段2,需要将测试场景和测试控制分开,每一个测试场景使用一个文件来保存,代码段2所示的场景使用“UploadImageScenario.scala”来保存。主控脚本也需要分离出来存入“MainSimulation.scala”,通过调用不同的测试场景的脚本,从而可以复用HTTP的配置选项,比如:
val httpProtocol = http
.baseURL("http:// :10.17.7.3")
其次,我们还需要增加多用户的支持:
多用户数据的读入,其中“user_credentials.csv”存储的就是用户名和密码
.feed(csv("user_credentials.csv")) .exec(http("request_login") .post("""/customer/login""") .param("""username""", """${username}""") .param("""password", "${password}""") |
设置多用户的值。由于我们使用的是虚拟机,所以经过测试,确定为400用户并发。
setUp(LoginScenario.loginScn.inject(ramp(400 users) over(60 seconds))).
protocols(httpProtocol)
最后,我们还要增加检测点,使用check,find,status等函数进行检测,下面的代码检测了用户登出的时候HTTP Response Status是否为302:
exec(http("request_logout")
.get(("""/customer/logout""")
.headers(headers_logout)
.check(status.is(302)))
当然,如果测试人员熟悉Gatling DSL API,我们也可以不用录制代码再进行重构,而是直接设计测试系统并进行测试案例的开发。
项目采取了敏捷方法进行开发,所以系统的一些功能在开发过程中会出现频繁改动,导致测试场景和测试脚本也会随之发生改变,因此,测试脚本的可读性和可维护性对于我们来说就非常重要。当某个功能改变之后,使用Gatling脚本就能十分方便的进行阅读和重构。比如对于添加user的功能,第一版只需要能添加user即可(见添加user代码1),而在下一版中,则要求在添加user时可以选择该user具有那些权限(见添加user代码2),代码如下:
.exec(http("request_add_user") .post("""/customer/users""") .headers(headers_user) .param("""utf8""", """?""") .param("""user[username]""", """user2""") .param("""user[email]""", """user@gmail.com""") .param("""user[password]""", """user2""") .param("""user[password_confirmation]""", """user2""") |
添加user代码1
.exec(http("request_add_user") .post("""/customer/users""") .headers(headers_user) .param("""utf8""", """?""") .param("""user[username]""", """user2""") .param("""user[email]""", """user@gmail.com""") .param("""user[password]""", """user2""") .param("""user[password_confirmation]""", """user2""") .param("""user[plugins][]""", """customer_dashboard""") .param("""user[plugins][]""", """customer_files""") .param("""user[plugins][]""", """customer_images""") .param("""user[plugins][]""", """customer_pages""")) |
添加user代码2
项目发布后,若项目功能发生改变,我们也可以使用Gatling进行持续的性能回归测试,保证系统性能不会因为某次修改导致非预期的降低。如果降低了,就要进行及时的调查,修复或者是调整,保证性能一直在预期的可控范围内。
测试报表
Gatling测试报表基于HTML,并且在测试过程中业已生成,所以打开速度很快。而且,当把鼠标移动到不同数据轴上时,都会有弹出框显示这个点上详细的测试数据信息。这种动态显示数据的方式非常方便查看和分析数据。考虑到项目真实数据的不便,我将通过Gatling官方网站给出的示例报表进行说明。
Gatling的报表分为两类:GLOBAL和DETAILS,其中GLOBAL主要是请求相关的统计数据,比如每秒请求数,请求成功与失败数等;其中DETAILS主要是请求时间相关的统计数据,比如请求响应时间,请求响应延迟时间等。
图4 每秒请求数
当鼠标放到图中任何一个点的时候,对应时间点上请求的详细数据就会以图中白色的弹出框的方式进行显示。在下面的请求响应延迟时间图里面也有同样的功能。
图5 请求响应延迟时间
3,与CI的集成
项目的CI系统选用的是Jenkins,因为Jenkins有Gatling的插件,所以通过这个插件可以在Jenkins上直接查看Gatling的测试结果,如图6所示。
图6 Jenkins Gatling插件
我们还把生成的报表存档到每个Build里面,这样就可以在每个Build中获得那次测试的所有报表。
更多类型的测试
其他类型的HTTP服务器性能测试,比如瞬间压力测试,耐久性测试等,都十分适合使用Gatling。
四,未来的Gatling
Gatling发布的时间虽然不长,但凭借其优良的性能,DSL模式的脚本,轻量友好的报表系统在众多服务器性能测试工具中脱颖而出。在2013年5月发布的ThoughtWorks技术雷达中,Gatling被列入了ADOPT,并在一些ThoughtWorks项目中得到了实际的运用。不过,Gatling还是存在一些问题,比如不支持分布式模型;默认只支持HTTP,对于其他协议需要自己动手进行扩展;报表种类也不是很丰富 。倘若Gatling 能在这几方面有所突破,那么它必将成为新一代服务器性能测试工具中的杀手锏。
如果你是一个JS高手的话可以在WebDriver 中直接执行JS代码来提升效率,一般用到执行js的场景主要分一下两种:
在页面加载的时候执行JS
在某个已经定位了的元素上执行js
Demos: package org.coderinfo.demo; import org.openqa.selenium.By; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; public class ExecuteJSOnPageLoading { private static final String URL = "file:///C:/user/Desktop/Selenium/jsdemo.html"; public static void main(String[] args) { WebDriver driver = new ChromeDriver(); // create a chrome driver driver.manage().window().maximize(); // max size the chrome window driver.get(URL); // open URL with the chrome browser try { Thread.sleep(2000); // wait for web loading } catch (InterruptedException e) { e.printStackTrace(); } // Execute JavaScript on page loading ((JavascriptExecutor)driver).executeScript("alert(\"Hello,World!\")"); ele.click(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } driver.quit(); // close webdriver } } package org.coderinfo.demo; import org.openqa.selenium.By; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; public class ExecuteJSOnWebElement { private static final String URL = "file:///C:/user/Desktop/Selenium/jsdemo.html"; public static void main(String[] args) { WebDriver driver = new ChromeDriver(); // create a chrome driver driver.manage().window().maximize(); // max size the chrome window driver.get(URL); // open URL with the chrome browser try { Thread.sleep(2000); // wait for web loading } catch (InterruptedException e) { e.printStackTrace(); } /* * Execute JavaScript on web element */ WebElement ele = driver.findElement(By.id("js")); // locate web element ((JavascriptExecutor) driver) .executeScript( "arguments[0].onclick=function(){alert('js has been execute!');}", ele); // add js on the web element ele.click(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } driver.quit(); // close webdriver } }
|
这里是用来测试的jsdemo.html的源码:
<!DOCTYPE html> <html> <head> <title>JavaScript Demo</title> </head> <body> <form action="#" method="get"> <input type="button" id="js" value="TestExecuteJS"/> </form> </body> </html> |
相关文章:
Rational AppScan 工作原理
Rational AppScan(简称 AppScan)其实是一个产品家族,包括众多的应用安全扫描产品,从开发阶段的源代码扫描的 AppScan source edition,到针对 Web 应用进行快速扫描的 AppScan standard edition,以及进行安全管理和汇总整合的 AppScan enterprise Edition 等。我们经常说的 AppScan 就是指的桌面版本的 AppScan,即 AppScan standard edition。其安装在 Windows 操作系统上,可以对网站等 Web 应用进行自动化的应用安全扫描和测试。
来张 AppScan 的截图,用图表说话,更明确。
图 1. AppScan 标准版界面
请注意右上角,单击“扫描”下面的小三角,可以出现如下的三个选型“继续完全扫描”、“继续仅探索”、“继续仅测试”,有木有?什么意思?理解了这个地方,就理解了 AppScan 的工作原理,我们慢慢展开:
还没有正式开始安全测试之前,所以先不管“继续”,直接来讨论“完全扫描”,“仅探索”,“仅测试”三个名词:
AppScan 三个核心要素
AppScan 是对网站等 Web 应用进行安全攻击来检查网站是否存在安全漏洞;既然是攻击,需要有明确的攻击对象吧,比如北约现在的对象就是卡扎菲上校还有他的军队。对网站来说,一个网站存在的页面,可能成千上万。每个页面也都可能存在多个字段(参数),比如一个登陆界面,至少要输入用户名和密码吧,这就是一个页面存在两个字段,你提交了用户名密码等登陆信息,网站总要有地方接受并且检查是否正确吧,这就可能存在一个新的检查页面。这里的每个页面的每个参数都可能存在安全漏洞,所有都是被攻击对象,都需要来检查。
这就存在一个问题,我们来负责来检查一个网站的安全性,这个网站有多少个页面,有多少个参数,页面之间如何跳转,我们可能并不明确,如何知道这些信息?看起来很复杂,盘根错节;那就更需要找到那个线索,提纲挈领;想一想,访问一个网站的时候,我们需要知道的最重要的信息是哪个?网站主页地址吧?从网站地址开始,很多其他频道,其他页面都可以链接过去,对不对,那么可不可以有种技术,告诉了它网站的入口地址,然后它“顺藤摸瓜”,找出其他的网页和页面参数?OK,这就是“爬虫”技术,具体说,是“网站爬虫”,其利用了网页的请求都是用 http 协议发送的,发送和返回的内容都是统一的语言 HTML,那么对 HTML 语言进行分析,找到里面的参数和链接,纪录并继续发送之,最终,找到了这个网站的众多的页面和目录。这个能力 AppScan 就提供了,这里的术语叫“探索”,explorer,就是去发现,去分析,了解未知的,并记录之。
在使用 AppScan 的时候,要配置的第一个就是要检查的网站的地址,配置了以后,AppScan 就会利用“探索”技术去发现这个网站存在多少个目录,多少个页面,页面中有哪些参数等,简单说,了解了你的网站的结构。
“探索”了解了,测试的目标和范围就大致确定了,然后呢,利用“军火库”,发送导弹,进行安全攻击,这个过程就是“测试”;针对发现的每个页面的每个参数,进行安全检查,检查的弹药就来自 AppScan 的扫描规则库,其类似杀毒软件的病毒库,具体可以检查的安全攻击类型都在里面做好了,我们去使用即可。
那么什么是“完全测试呢”,完全测试就是把上面的两个步骤整合起来,“探索”+“测试”;在安全测试过程中,可以先只进行探索,不进行测试,目的是了解被测的网站结构,评估范围;然后选择“继续仅测试”,只对前面探索过的页面进行测试,不对新发现的页面进行测试。“完全测试”就是把两个步骤结合在一起,一边探索,一边测试。
AppScan 工作原理小结如下:
通过搜索(爬行)发现整个 Web 应用结构
根据分析,发送修改的 HTTP Request 进行攻击尝试(扫描规则库)
通过对于 Respone 的分析验证是否存在安全漏洞
图 2. AppScan 扫描原理:扫描规则库 + 爬行 + 测试
步骤 1:探索(又叫爬行,爬网)
图 3. 探索(爬网,爬行)
步骤 2:测试(针对找到的页面,生成测试,进行安全攻击)
图 4. 针对探索发现的页面和参数,进行安全测试
所以,简言之,AppScan 的核心是提供一个扫描规则库,然后利用自动化的“探索”技术得到众多的页面和页面参数,进而对这些页面和页面参数进行安全性测试。“扫描规则库”,“探索”,“测试”就构成了 AppScan 的核心三要素。而在安全扫描过程中,如何进行优化,就要结合这三个要素,看哪些部分需要优化,应该如何优化。
AppScan 结果文件
同时,对于 AppScan 标准版来说,扫描的配置和结果信息都保存为后缀名为 Scan 文件,Scan 文件里面主要包括的内容如下:
扫描配置信息:扫描配置信息,如扫描的目标网站地址,录制的登陆过程脚本等,选择的扫描设置等都保存在 Scan 文件中。
所有访问到页面信息:针对每个发现的页面,即使没有进行测试,在探索过程也会访问该页面并纪录 http request/response 信息;所以如果探索的页面访问的时候返回的页面内容比较多,页面比较大,那么即使只做了探索根本没有扫描,整个 Scan 文件也会很大。
测试阶段,记录测试成功的测试变体和页面访问信息:针对每个页面都会发送多次测试(测试变体),每次测试都会有 Request/response 信息,这些信息如果测试通过,即发现了一个安全问题,则会把该测试变体对应得 request/response 都会纪录下来,保存在 .scan 文件中;由于 AppScan 的扫描测试用例库全面,对于每种安全威胁漏洞,都会发送多个安全测试变体(Variant)进行测试,比如对于 XSS 问题,AppScan 发送了 100 个变体,其中 30 个执行失败,70 个变体执行成功,则会纪录 70 次执行成功的具体变体信息,以及每个变体对应的 Request/Response 信息。这就是一个很大的数据量。这些信息保存以后,就可以在不连接在网站的情况下进行结果分析,快速显示当时测试的页面快照等。
我们以http://demo.testfire.net/bank/customize.aspx 为例,如下就有 74 个变体都发现了 Customize 页面的 Lang 参数存在跨站点脚本执行(XSS)类型的安全漏洞:
图 5. 测试变体显示
所以针对 AppScan 标准版来说,由于需要保存的信息比较多,结果文件是会比较大的,最根本的方法还是有针对性地进行扫描和测试,使用排除页面等排除冗余页面,把一个大的系统分解为多个小的扫描任务等。
好的,了解了 AppScan 的原理,我们就结合原来讨论下为什么扫描大型网站时候可能遇到问题了。
大型网站技术特点分析
AppScan 扫描的对象是网站等 Web 应用,而网站规模的大小和使用的技术,都需要针对性的进行扫描设置,我们遇到的很多问题,都是在扫描规模比较大的网站时候遇到的,如一个网站页面数目超过 2000 个,需要执行的扫描用例是 50,000 个,在扫描这样的网站时候,默认情况下 AppScan 的扫描 scan 文件可能超过 100M 了,扫描效率就可能比较慢,需要长时间的扫描运行时间。
下面,我们就来分析大型网站中存在的一些可能影响 AppScan 扫描的技术特点。
网站页面多,页面参数多,则 AppScan 需要发送的测试用例多
什么叫大型网站,顾名思义,网站规模大,提供内容多;具体说是页面很多,内容很全。比如 www.sina.com.cn,比如 http://music.10086.cn/,网站中都有多个频道,包括上万个页面。而且除了页面多,可能还有一个特点 --- 页面参数多,即要填写的地方多,和用户的交互多;比如一个网站如果都是静态页面(.html、.jpg 等),没有让用户输入的地方,那么可以利用,可以作为攻击点的地方也就不多。如果页面到处都是有输入有查询,要求用户来参与的,你输入的越多,可能泄露的信息也越多,可能被别人利用的攻击点也就越多,所以和页面参数也是有关系的。
AppScan 产生测试用例的时候,也是根据每个参数来产生的,简单说,如果一个参数,对应了 200 个安全攻击测试用例,那么一个登陆界面至少就对应 400 个了,为什么?登陆界面至少有用户名(username)和密码(password)两个字段吧?每个字段 200 个攻击用例。
这个简单吧,还可以更复杂:如果遇到下面的两个地址,那要扫描多少次呢?
http://www.Test.com/focus/satisfy/file.jsp?id=1 http://www.Test.com/focus/satisfy/file.jsp?id=2 |
上面的两个地址有类似的,“?”号以前的 URL 地址完全一样,“?”号后面带的参数不同,这种可以认为是重复页面,那么对于重复页面,是否要重复测试呢?
这取决于“冗余路径设置”,默认的是最多测试 5 次;即,这种类型 URL 出现的前 5 次,那么就是要测试 1000 个攻击用例了。
如果再继续修改下:遇到下面的 URL 呢
http://www.Test.com/focus/satisfy/file.jsp?id=&Item=open http://www.Test.com/focus/satisfy/file.jsp?id=2&Item=close |
每个 URL 里面都有 2 个参数,测试的次数就更多了。想象下,如果这个网页里面的参数如果是 10 个,或者更多的呢?比如很多网站提交注册信息的时候,要填写的栏位就很多,要进行的安全测试用例也就随之不断增加…
这是网站规模的影响,还有一个问题,就出在“每个参数,发送 200 个安全测试用例”这个假设上。这个假设的前提来源于哪里?来源于我们选择的扫描规则库。即你关心那些安全威胁,这个需要在测试策略里面选择。同样来参照杀毒软件,你会用杀毒软件来查找一些专用的病毒吗,比如 CIH、木马;应用安全扫描也是一样的道理,如果有明确的安全指标或者安全规则范围,那么就选择之。这些可能来源于企业的规范,来源于政府的法律法规。就要根据你的理解,在这里选择。
图 6. 选择测试策略
在实际工作中,我们也很难在最开始的阶段,就把扫描规范制定下来,按照项目经理们的口头禅“渐进明细”,“滚动式规划”,在实践中,更多时候也是摸着石头过河,选择了一个扫描策略,然后根据结果分析,看是否需要调整,不断优化。比如选择默认的“缺省值”扫描策略,对网站进行扫描,发现其“敏感信息”里面会去检查页面上是否含有 Email 地址,是否含有信用卡号码等,如果我们觉得这些信息,显示在页面上是正常的业务需要(比如这样的链接:<a href=mailto:admin@www.test.com>有问题请联系admin@www.test.com</a>),我们就可以取消掉这些规则,所以扫描规则也很大程度上影响着我们的扫描效率。
网站采用多种混合的技术,需要不同的扫描设置
一些大型网站,往往是一个统一的入口,在里面提供不同的内容,而这些内容可能来源于不同的技术。如我们熟悉的门户网站,里面就有“财经”、“体育”、“娱乐”等多个频道;每个频道的内容,可能是采用不同的技术,对应不同的服务器。如一个网站的“论坛”频道,就有很多类似的页面:
http://www.Test.com/bbs/showthread.php?id=1 Http://www.Test.com/bbs/showthread.php?id=2 Http://www.Test.com/bbs/showthread.php?id=3 |
这里的 showthread.php 页面存在多次,每次都是参数值不同,访问后发现这些页面除了文本内容外,其他的页面结构等都相同,则这些页面只需要选择几个典型的扫描即可,没有必要全部扫描。
而同时,在另外的一些频道,存在另外类型的页面:
http://www.Test.com/default.aspx?content=inside_community.htm http://www.Test.com/default.aspx?content=inside_press.htm http://www.Test.com/default.aspx?content=inside_executives.htm |
这些动态页面,也是网址相同,参数相同,但是具有不同的参数值,访问时候发现每种类型的参数值都指向了完全不同的页面,则需要每种参数值都要测试到。这种情况经常存在跳转页面中。
而这两个频道中,第一种情况,可以选择典型的页面扫描之,而第二种情况则需要进行完全的扫描,每种参数值都需要考虑到。这就需要不同的扫描设置。
同时,可能大家也注意到了,第一种情况下的是 php 页面,而第二种情况下的则是 aspx 页面,对应不同的开发技术,这也可能需要不同的扫描设置。
所以,总结下,AppScan 的扫描受到如下因素的影响:
网站规模(页面个数,页面参数)
扫描策略的选择
扫描设置
而对于大型的网站,我们经常需要从几个方面来优化配置
选择合适的,最小化的扫描规则
分解扫描任务,把一个大的扫描任务分解为多个小的扫描任务
根据页面特点,设置可以过滤的类似页面(冗余页面)
1.打开EXtended Log
Log告诉了我们一切,默认的Log是standard Log,这时远远不够的.我们要extended log,打开路径为runtime settings-->log-->extended log.把parameter substitution和data returned byserver和advanced trace大家根据需要勾选吧.
2.log文件位置,特别是controll执行后,怎么看log。这里一一说明一下:
(1)vgen的runtime settings设置:在vgen中,我们必须写输出函数输出信息,将我们所想要了解的信息用函数输出,主要有这么几个函数输出信息:lr_output_message,lr_error_message,lr_log_message。这些函数请参阅 help-->function reference.
其次,我们要在runtime settings中设置,勾选always send messages,具体的做法是:runtime settings--->log-->always send messages,这样我们才能写出Log,在我们的脚本所在的文件夹中,有两个文件很重要,mdrv.log.txt和output.txt文件,lr_log_message只会把信息输到mdrv.log文件中,而lr_output_message则会写进以上两个文件。
(2)controller的runtime settings设置:在controller我们也要设置runtime settings,这样才能在场景运行后查看相应
日志,而且每个用户组的runtime settings都有设置。设置的方法是:在controller的design标签页中,右下角的部分有runtime settings按钮,我们点击它,设置的方法与在vgen中一样的。很多朋友都会想知道多次迭代,参数是否正确的导入了呢,我们依旧查看log,我们在执行结束后,查看结果目录的Log文件夹,如果是负载生成器运行的话,则在tmp目录。
在Windows环境下,日志文件output.txt保存在脚本目录中;在UNIX环境下,保存在标准输出中。 【Vuser】——【Run Time Settings】——【General】——【Log】 1、【Enable logging】启动日志功能;(建议运行场景进行负载 测试时关闭此项) 2、【Send messages only when an error occurs】仅发送出错时的日志,可设置缓存大小(默认1KB); 3、【Always send message】发送所有日志; 4、【Standard log】标准日志,脚本运行时发送函数信息; 【Extended log】扩展日志: 5、【Parameter substitution】脚本运行时,在【Replay log】显示参数信息、参数值; 6、【Data returned by server】记录服务器返回的所有数据; 7、【Advanced trace】多用于脚本调试,记录VU在运行期间发送的所有函数信息。 |
========================================
【Replay log】显示的日志颜色:
1、红色:错误信息;
2、橙色:迭代信息;
3、蓝色:事件信息;
4、黑色:输出信息;
5、绿色:字符信息。
========================================
输出函数:
1、lr_log_message() // 输出信息,并记录到 output.txt 中 2、lr_output_message() // 输出信息,不记录到日志文件中 3、lr_message() // 输出信息,不记录到日志文件中 4、lr_error_message() // 输出信息,不记录到日志文件中 |
1.Java中finalize()的作用一主要是清理那些对象(并非使用new)获得了一块“特殊”的内存区域。程序员可以用finalize()来操作。 程序员都了解初始化的重要性,但常常会忘记同样也重要的清理
工作。毕竟,谁需要清理一个int呢?但在使用程序库时,把一个对象用完后就“弃之不顾”的做法并非总是安全的。当然,
Java有垃圾回收器负责回收无用对象占据的内存资源。但也有特殊情况:假定你的对象(并非使用new)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所以它不知道该如何释放该对象的这块“特殊”内存区域,为了应对这种情况,java允许在类中定义一个名为finalize()的方法。它的工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储孔家,将首先调用其finalize()的方法。并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以要是你打算用finalize(),就能在垃圾回收时刻做一些重要的清理工作。注意这里的finalize()并不是C++里的析构.在C++中,对象一定会被销毁,而在Java里的对象却并非总是被垃圾回收(1.对象可能不被垃圾回收;2.垃圾回收并并不等于“析构”)。
2.垃圾回收只与内存有关。也就是说,使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾回收有关的任何行为来说(尤其是finalize()方法),它们也必须同内存及其回收有关。但这是否意味着要是对象中含有其他对象,finalize()就应该明确释放那些对象呢?不,无论对象是如何创建的,垃圾回收器都会负责释放对象占据的所有内存。这就将对finalize()的需求限制到一种特殊情况,即通过某种创建对象方式以外的方式为对象分配了存储空间。不过,java中一切皆为对象,那这种特殊情况是怎么回事呢?由于在分配内存时可能采用了类似C语言中的做法,而非java中的通常做法。这种情况主要发生在使用“本地方法”的情况下,本地方法是一种在Java中调用非Java代码的方式。在非java代码中,也许会调用C的malloc()函数系列来分配存储空间,而且除非了free()函数
3.垃圾回收如何工作
(1)“引用记数(reference counting)”是一种简单但速度很慢的垃圾回收技术。每个对象都含有一个引用记数器,当有引用连接至对象时,引用计数加1。当引用离开作用域或被置为null时,引用计数减1。虽然管理引用记数的开销不大,但需要在整个程序生命周期中持续地开销。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为0时,就释放其占用的空间。这种方法有个缺陷,如果对象之间存在循环引用,可能会出现“对象应该被回收,但引用计数却不为零”的情况。对垃圾回收器而言,定位这样存在交互引用的对象组所需的工作量极大。引用记数常用来说明垃圾收集的工作方式,似乎从未被应用于任何一种Java虚拟机实现中。
(2) 在一些更快的模式中,垃圾回收器并非基于引用记数技术。它们依据的思想是:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。由此,如果你从堆栈和静态存储区开始,遍历所有的引用,就能找到所有“活”的对象。对于发现的每个引用,你必须追踪它所引用的对象,然后是此对象包含的所有引用,如此反复进行,直到“根源于堆栈和静态存储区的引用”所形成的网络全部被访问为止。你所访问过的对象必须都是“活”的。注意,这就解决了“存在交互引用的整体对象”的问题,这些对象根本不会被发现,因此也就被自动回收了。
(3)在这种方式下,Java虚拟机将采用一种“自适应”的垃圾回收技术。至于如何处理找到的存活对象,取决于不同的Java虚拟机实现。有一种作法名为“停止——复制”(stop-and-copy)。这意味着,先暂停程序的运行,(所以它不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按前述方法简单、直接地分配新空间了。
“标记——清扫”所依据的思路同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活对象,就会给对象设一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清除动作才会开始。在清处过程中,没有标记的对象将被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,垃圾回收器要是希望得到连续空间的话,就得重新整理剩下的对象。
“停止——复制”的意思是这种垃圾回收方式不是在后台进行的;相反,垃圾回收动作发生的同时,程序将会被暂停。在Sun 公司的文档中你会发现,许多参考文献将垃圾回收视为低优先级的后台进程,但事实上垃圾回收器并非以这种方式实现——至少Sun公司早期版本的Java虚拟机中并非如此。当可用内存数量较低时,Sun版中的垃圾回收器才会被激活,同样,“标记——清扫”工作也必须在程序暂停的情况下才能进行。
如前文所述,这里讨论的Java虚拟机,内存分配单位是较大的“块”。如果对象较大,它会占用单独的块。严格来说,“停止——复制”要求你在释放旧有对象之前,必须先把所有存活对象从旧堆复制到新堆,这将导致大量内存复制行为。有了块之后,垃圾回收器在回收的时候就可以往废弃的块里拷贝对象了。每个块都用相应的“代数(generation count)”记录它是否还存活。通常,如果块在某处被引用,其代数会增加;垃圾回收器将对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清除动作——大型对象仍然不会被复制(只是其代数会增加),内含小型对象的那些块则被复制并整理。Java虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到“标记——清扫”方式;同样, Java虚拟机会注意“标记——清扫”的效果,要是堆空间出现很多碎片,就会切换回“停止——复制”方式。这就是“自适应”技术。你可以给它个罗嗦的称呼:“自适应的、分代的、停止——复制、标记——清扫”式垃圾回收器。
Java虚拟机中有许多附加技术用以提升速度。尤其是与加载器操作有关的,被称为“即时”(Just-In-Time,JIT)编译的技术。这种技术可以把程序全部或部分翻译成本地机器码(这本来是Java虚拟机的工作),程序运行速度因此得以提升。当需要装载某个类(通常是在你为该类创建第一个对象)时,编译器会先找到其 .class 文件,然后将该类的字节码装入内存。此时,有两种方案可供选择。一种是就让即时编译器编译所有代码。但这种做法有两个缺陷:这种加载动作散落在整个程序生命周期内,累加起来要花更多时间;并且会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多),这将导致页面调度,从而降低程序速度。另一种做法称为“惰性编译(lazy uation)”,意思是即时编译器只在必要的时候才编译代码。这样,从不会被执行的代码也许就压根不会被JIT所编译。新版JDK中的Java HotSpot技术就采用了类似方法,代码每次被执行的时候都会做一些优化,所以执行的次数越多,它的速度就越快。
/: 根目录,一般根目录下只存放目录,不要存放文件,/etc、/bin、/dev、/lib、/sbin应该和根目录放置在一个分区中
/bin:/usr/bin: 可执行二进制文件的目录,如常用的命令ls、tar、mv、cat等。
/boot: 放置linux系统启动时用到的一些文件。/boot/vmlinuz为linux的内核文件,以及/boot/gurb。建议单独分区,分区大小100M即可
/dev: 存放linux系统下的设备文件,访问该目录下某个文件,相当于访问某个设备,常用的是挂载光驱mount /dev/cdrom /mnt。
/etc: 系统配置文件存放的目录,不建议在此目录下存放可执行文件,重要的配置文件有/etc/inittab、/etc/fstab、/etc/init.d、/etc/X11、/etc/sysconfig、/etc/xinetd.d修改配置文件之前记得备份。注:/etc/X11存放与x windows有关的设置。
/home: 系统默认的用户家目录,新增用户账号时,用户的家目录都存放在此目录下,~表示当前用户的家目录,~test表示用户
test的家目录。建议单独分区,并设置较大的磁盘空间,方便用户存放数据
/lib:/usr/lib:/usr/local/lib: 系统使用的函数库的目录,程序在执行过程中,需要调用一些额外的参数时需要函数库的协助,比较重要的目录为/lib/modules。
/lost+fount: 系统异常产生错误时,会将一些遗失的片段放置于此目录下,通常这个目录会自动出现在装置目录下。如加载硬盘于/disk 中,此目录下就会自动产生目录/disk/lost+found
/mnt:/media: 光盘默认挂载点,通常光盘挂载于/mnt/cdrom下,也不一定,可以选择任意位置进行挂载。
/opt: 给主机额外安装软件所摆放的目录。如:FC4使用的Fedora 社群开发软件,如果想要自行安装新的KDE 桌面软件,可以将该软件安装在该目录下。以前的 Linux 系统中,习惯放置在 /usr/local 目录下
/proc: 此目录的数据都在内存中,如系统核心,外部设备,网络状态,由于数据都存放于内存中,所以不占用磁盘空间,比较重要的目录有/proc/cpuinfo、/proc/interrupts、/proc/dma、/proc/ioports、/proc/net/*等
/root: 系统管理员root的家目录,系统第一个启动的分区为/,所以最好将/root和/放置在一个分区下。
/sbin:/usr/sbin:/usr/local/sbin: 放置系统管理员使用的可执行命令,如fdisk、shutdown、mount等。与/bin不同的是,这几个目录是给系统管理员root使用的命令,一般用户只能"查看"而不能设置和使用。
/tmp: 一般用户或正在执行的程序临时存放文件的目录,任何人都可以访问,重要数据不可放置在此目录下
/srv: 服务启动之后需要访问的数据目录,如www服务需要访问的网页数据存放在/srv/www内
/usr: 应用程序存放目录,/usr/bin 存放应用程序, /usr/share 存放共享数据,/usr/lib 存放不能直接运行的,却是许多程序运行所必需的一些函数库文件。/usr/local:存放软件升级包。/usr/share/doc: 系统说明文件存放目录。/usr/share/man: 程序说明文件存放目录,使用 man ls时会查询/usr/share/man/man1/ls.1.gz的内容建议单独分区,设置较大的磁盘空间
/var: 放置系统执行过程中经常变化的文件,如随时更改的
日志文件 /var/log,/var/log/message: 所有的登录文件存放目录,/var/spool/mail: 邮件存放的目录, /var/run: 程序或服务启动后,其PID存放在该目录下。建议单独分区,设置较大的磁盘空间
------------------------------------------
/dev: 目录
dev是设备(device)的英文缩写。/dev这个目录对所有的用户都十分重要。因为在这个目录中包含了所有Linux系统中使用的外部设备。但是这里并不是放的外部设备的驱动程序,这一点和
windows,dos操作系统不一样。它实际上是一个访问这些外部设备的端口。我们可以非常方便地去访问这些外部设备,和访问一个文件,一个目录没有任何区别。
Linux沿袭Unix的风格,将所有设备认成是一个文件。
设备文件分为两种:块设备文件(b)和字符设备文件(c)
设备文件一般存放在/dev目录下,对常见设备文件作如下说明:
/dev/hd[a-t]:IDE设备
/dev/sd[a-z]:SCSI设备
/dev/fd[0-7]:标准软驱
/dev/md[0-31]:软raid设备
/dev/loop[0-7]:本地回环设备
/dev/ram[0-15]:内存
/dev/null:无限数据接收设备,相当于黑洞
/dev/zero:无限零资源
/dev/tty[0-63]:虚拟终端
/dev/ttyS[0-3]:串口
/dev/lp[0-3]:并口
/dev/console:控制台
/dev/fb[0-31]:framebuffer
/dev/cdrom => /dev/hdc
/dev/modem => /dev/ttyS[0-9]
/dev/pilot => /dev/ttyS[0-9]
/dev/random:随机数设备
/dev/urandom:随机数设备
(PS:随机数设备,后面我会再写篇博客总结一下)
/dev目录下的节点是怎么创建的?
devf或者udev会自动帮你创建得。
kobject是sysfs文件系统的基础,udev通过监测、检测sysfs来获取新创建的设备的。
------------------------------------------
/etc: 目录
包含很多文件.许多网络配置文件也在/etc 中.
/etc/rc or /etc/rc.d or /etc/rc*.d 启动、或改变运行级时运行的scripts或scripts的目录.
/etc/passwd
用户数据库,其中的域给出了用户名、真实姓名、家目录、加密的口令和用户的其他信息.
/etc/fstab
启动时mount -a命令(在/etc/rc 或等效的启动文件中)自动mount的文件系统列表. Linux下,也包括用swapon -a启用的swap区的信息.
/etc/group
类似/etc/passwd ,但说明的不是用户而是组.
/etc/inittab
init 的配置文件.
/etc/issue
getty 在登录提示符前的输出信息.通常包括系统的一段短说明或欢迎信息.内容由系统管理员确定.
/etc/motd
Message Of The Day,成功登录后自动输出.内容由系统管理员确定.经常用于通告信息,如计划关机时间的警告.
/etc/mtab
当前安装的文件系统列表.由scripts初始化,并由mount 命令自动更新.需要一个当前安装的文件系统的列表时使用,例如df 命令.
/etc/shadow
在安装了影子口令软件的系统上的影子口令文件.影子口令文件将/etc/passwd 文件中的加密口令移动到/etc/shadow 中,而后者只对root可读.这使破译口令更困难.
/etc/login.defs
login 命令的配置文件.
/etc/printcap
类似/etc/termcap ,但针对打印机.语法不同.
/etc/profile , /etc/csh.login , /etc/csh.cshrc
登录或启动时Bourne或C shells执行的文件.这允许系统管理员为所有用户建立全局缺省环境.
/etc/securetty
确认安全终端,即哪个终端允许root登录.一般只列出虚拟控制台,这样就不可能(至少很困难)通过modem或网络闯入系统并得到超级用户特权.
/etc/shells
列出可信任的shell.chsh 命令允许用户在本文件指定范围内改变登录shell.提供一台机器FTP服务的服务进程ftpd 检查用户shell是否列在 /etc/shells 文件中,如果不是将不允许该用户登录.
/etc/sysconfig
网络配置相关目录
--------------------------------
/proc: 目录
档名 文件内容
/proc/cmdline 加载 kernel 时所下达的相关参数!查阅此文件,可了解系统是如何启动的!
/proc/cpuinfo 本机的 CPU 的相关资讯,包含时脉、类型与运算功能等
/proc/devices 这个文件记录了系统各个主要装置的主要装置代号,与 mknod 有关呢!
/proc/filesystems 目前系统已经加载的文件系统罗!
/proc/interrupts 目前系统上面的 IRQ 分配状态。
/proc/ioports 目前系统上面各个装置所配置的 I/O 位址。
/proc/kcore 这个就是内存的大小啦!好大对吧!但是不要读他啦!
/proc/loadavg 还记得 top 以及 uptime 吧?没错!上头的三个平均数值就是记录在此!
/proc/meminfo 使用 free 列出的内存资讯,嘿嘿!在这里也能够查阅到!
/proc/modules 目前我们的 Linux 已经加载的模块列表,也可以想成是驱动程序啦!
/proc/mounts 系统已经挂载的数据,就是用 mount 这个命令呼叫出来的数据啦!
/proc/swaps 到底系统挂加载的内存在哪里?呵呵!使用掉的 partition 就记录在此啦!
/proc/partitions 使用 fdisk -l 会出现目前所有的 partition 吧?在这个文件当中也有纪录喔!
/proc/pci 在 PCI 汇流排上面,每个装置的详细情况!可用 lspci 来查阅!
/proc/uptime 就是用 uptime 的时候,会出现的资讯啦!
/proc/version 核心的版本,就是用 uname -a 显示的内容啦!
/proc/bus/* 一些汇流排的装置,还有 U盘 的装置也记录在此喔!
------------------------------------------
/usr: 目录
/usr 文件系统经常很大,因为所有程序安装在这里. /usr 里的所有文件一般来自Linux distribution;本地安装的程序和其他东西在/usr/local 下.这样可能在升级新版系统或新distribution时无须重新安装全部程序.
/usr/etc 存放设置文件
/usr/games 存放游戏和教学文件
/usr/include 存放C开发工具的头文件
/usr/share 存放结构独立的数据
/usr/bin
几乎所有用户命令.有些命令在/bin 或/usr/local/bin 中.
/usr/sbin
根文件系统不必要的系统管理命令,例如多数服务程序.
/usr/share/man , /usr/share/info , /usr/share/doc
手册页、GNU信息文档和各种其他文档文件.
/usr/include
C编程语言的头文件.为了一致性这实际上应该在/usr/lib 下,但传统上支持这个名字.
/usr/lib
程序或子系统的不变的数据文件,包括一些site-wide配置文件.名字lib来源于库(library); 编程的原始库存在/usr/lib 里.
/usr/local
本地安装的软件和其他文件放在这里.
/usr/src 存放程序的源代码
------------------------------------------
/var: 目录
/var 包括系统一般运行时要改变的数据.每个系统是特定的,即不通过网络与其他计算机共享.
/var/catman
当要求格式化时的man页的cache.man页的源文件一般存在/usr/man/man* 中;有些man页可能有预格式化的版本,存在/usr/man/cat* 中.而其他的man页在第一次看时需要格式化,格式化完的版本存在/var/man 中,这样其他人再看相同的页时就无须等待格式化了. (/var/catman 经常被清除,就象清除临时目录一样.)
/var/lib
系统正常运行时要改变的文件.
/var/local
/usr/local 中安装的程序的可变数据(即系统管理员安装的程序).注意,如果必要,即使本地安装的程序也会使用其他/var 目录,例如/var/lock .
/var/lock
锁定文件.许多程序遵循在/var/lock 中产生一个锁定文件的约定,以支持他们正在使用某个特定的设备或文件.其他程序注意到这个锁定文件,将不试图使用这个设备或文件.
/var/log
各种程序的Log文件,特别是login (/var/log/wtmp log所有到系统的登录和注销) 和syslog (/var/log/messages 里存储所有核心和系统程序信息. /var/log 里的文件经常不确定地增长,应该定期清除.
/var/run
保存到下次引导前有效的关于系统的信息文件.例如, /var/run/utmp 包含当前登录的用户的信息.
/var/spool
mail, news, 打印队列和其他队列工作的目录.每个不同的spool在/var/spool 下有自己的子目录,例如,用户的邮箱在/var/spool/mail 中.
/var/tmp
比/tmp 允许的大或需要存在较长时间的临时文件. (虽然系统管理员可能不允许/var/tmp 有很旧的文件.)
比较重要的目录
在 Linux 系统中,有几个目录是特别需要注意的,以下提供几个需要注意的目录,以及预设相关的用途:
/etc: 这个目录相当重要,如前所述,你的开机与系统数据文件均在这个目录之下,因此当这个目录被破坏,那你的系统大概也就差不多该死掉了!而在往后的文件中,你会发现我们常常使用这个目录下的 /etc/rc.d/init.d 这个子目录,因为这个 init.d 子目录是开启一些 Linux 系统服务的 scripts (可以想成是批次檔 )的地方。而在 /etc/rc.d/rc.local 这个文件是开机的执行档。
/bin, /sbin, /usr/bin, /usr/sbin: 这是系统预设的执行文件的放置目录,例如 root 常常使用的 userconf, netconf, perl, gcc, c++ 等等的数据都放在这几个目录中,所以如果你在提示字符下找不到某个执行档时,可以在这四个目录中查一查!
/bin, /usr/bin 是给系统使用者使用的指令,
/sbin, /usr/sbin 则是给系统管理员使用的指令!
/usr/local: 这是系统预设的让你安装你后来升级的套件的目录。例如,当你发现有更新的 Web 套件(如 Apache )可以安装,而你又不想以 rpm 的方式升级你的套件,则你可以将 apache 这个套件安装在 /usr/local 底下。安装在这里有个好处,因为目前大家的系统都是差不多的,所以如果你的系统要让别人接管的话,也比较容易上手呀!也比较容易找的到数据喔!因此,如果你有需要的话,通常我都会将 /usr/local/bin 这个路径加到我的 path 中。
/home: 这个是系统将有账号的人口的家目录设置的地方。
/var: 这个路径就重要了!不论是登入、各类服务的问题发生时的记录、以及常态性的服务记录等等的记录目录,所以当你的系统有问题时,就需要来这个目录记录的文件数据中察看问题的所在啰!而 mail 的预设放置也是在这里,所以他是很重要的
/usr/share/man, /usr/local/man: 这两个目录为放置各类套件说明档的地方,例如你如果执行 man man,则系统会自动去找这两个目录下的所有说明文件
文件种类:
谈完了文件格式之后,再来谈谈所谓的文件种类吧!我们在刚刚的属性介绍中提到了最前面的标志 ( d 或 - ) 可以代表目录或文件,那就是不同的文件种类啦!Linux 的文件种类主要有底下
这几种:
正规文件( regular file ):就是一般类型的文件,在由 ls –al 所显示出来的属性方面,第一个属性为 [ - ]。另外,依照文件的内容,又大略可以分为两种文件种类:
纯文字文件(ascii) :这是 Unix 系统中最多的一种啰,几乎只要我们可以用来做为设定的文件都属于这一种;
二进制文件(binary) :通常执行档除了 scripts (文字型批次文件)之外,就是这一种文件格式;
目录 (directory):就是目录!第一个属性为 [ d ];
连结档 (link):就是类似 Windows 底下的快捷方式啦!第一个属性为 [ l ];
设备档 (device):与系统周边相关的一些文件,通常都集中在 /dev 这个目录之下!通常又分为两种:
区块 (block) 设备档 :就是一些储存数据,以提供系统存取的接口设备,简单的说就是硬盘啦!例如你的一号硬盘的代码是 /dev/hda1 等等的文件啦!第一个属性为 [ b ];
字符 (character) 设备档 :亦即是一些串行端口的接口设备,例如键盘、鼠标等等!第一个属性为 [ c ]。
Linux 的文件系统( inode ):
在 Linux 系统当中,每个文件不止有文件的内容数据,还包括文件的种种属性,例如:所属群组、所属使用者、能否执行、文件建立时间、文件特殊属性等等。我们将每个文件的内容分为两个部分来储存,一个是文件的属性,另一个则是文件的内容。
为了应付这两个不同的咚咚,所以 ext2 规划出 inode 与 Block 来分别储存文件的属性( 放在 inode 当中 )与文件的内容( 放置在 Block area 当中 )。
当我们要将一个 partition 格式化( format )为 ext2 时,就必须要指定 inode 与 Block 的大小才行,也就是说,当 partition 被格式化为 ext2 的文件系统时,他一定会有 inode table 与 block area 这两个区域。
Block 已经在前面说过了,他是数据储存的最小单位。
那么 inode 是什么?!简单的说, Block 是记录『文件内容数据』的区域,至于 inode 则是记录『该文件的相关属性,以及文件内容放置在哪一个 Block 之内』的信息。简单的说, inode 除了记录文件的属性外,同时还必须要具有指向( pointer )的功能,亦即指向文件内容放置的区块之中,好让操作系统可以正确的去取得文件的内容啊
该文件的拥有者与群组(owner/group);
该文件的存取模式;
该文件的类型;
该文件的建立日期(ctime)、最近一次的读取时间(atime)、最近修改的时间 (mtime);
该文件的容量;
定义文件特性的旗标(flag),如 SetUID...;
该文件真正内容的指向 (pointer);
我们定位到元素之后80%的目的都是要操作这个
web元素,所以Web元素的操作也是非常重要的,这里介绍WebDriver几种主要的操作方法:
click :点击当前的元素
sendKeys :在当前的web元素上模拟键盘的操作
clear : 清除当前元素的内容,前提是当前元素可以接收内容的话
下面直接上代码了:
package org.coderinfo.demo; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; public class OperatElement { private static final String URL = "file:///C:/user/Desktop/Selenium/operate.html"; public static void main(String[] args) { WebDriver driver = new ChromeDriver(); // create a chrome driver driver.manage().window().maximize(); // max size the chrome window driver.get(URL); // open URL with the chrome browser try { Thread.sleep(2000); // wait for page loading } catch (InterruptedException e) { e.printStackTrace(); } driver.findElement(By.id("UserName")).sendKeys("coderinfo"); // Get input element and input some words try { Thread.sleep(3000); //wait 3s } catch (InterruptedException e) { e.printStackTrace(); } driver.findElement(By.id("UserName")).clear(); // Get input element and clear it's content try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } driver.findElement(By.id("UserName")).sendKeys("coderinfo"); // Get input element and input some words driver.findElement(By.id("UserEmail")).sendKeys("coderinfo"); // Get input element and input some words try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } driver.findElement(By.xpath("//input[@type='reset']")).click(); // Get reset button and click it try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } driver.quit(); // close webdriver } } |
<!DOCTYPE html> <html> <head> <title>Operate Element</title> </head> <body> <h3>Operate Element</h3> <form class="form-h"> <input type="text" class="in" id="UserName" /><br /> <input type="text" class="in" id="UserEmail" /><br /> <input type="submit" class="in" /> <input type="reset" class="in" /> </form> </body> </html> |
相关
文章:
关于性能优化这是一个比较大的话题,在《由12306.cn谈谈网站性能技术》中我从业务和设计上说过一些可用的技术以及那些技术的优缺点,今天,想从一些技术细节上谈谈性能优化,主要是一些代码级别的技术和方法。本文的东西是我的一些经验和知识,并不一定全对,希望大家指正和补充。
在开始这篇
文章之前,大家可以移步去看一下以前发表的《代码优化概要》,这篇文章基本上告诉你——要进行优化,先得找到性能瓶颈!但是在讲如何定位系统性能瓶劲之前,请让我讲一下系统性能的定义和
测试,因为没有这两件事,后面的定位和优化无从谈起。
一、系统性能定义
让我们先来说说什么是系统性能。这个定义非常关键,如果我们不清楚什么是系统性能,那么我们将无法定位之。我见过很多朋友会觉得这很容易,但是仔细一问,其实他们并没有一个比较系统的方法,所以,在这里我想告诉大家如何系统地来定位性能。总体来说,系统性能就是两个事:
Throughput吞吐量。也就是每秒钟可以处理的请求数,任务数。
Latency系统延迟。也就是系统在处理一个请求或一个任务时的延迟。
一般来说,一个系统的性能受到这两个条件的约束,缺一不可。比如,我的系统可以顶得住一百万的并发,但是系统的延迟是2分钟以上,那么,这个一百万的负载毫无意义。系统延迟很短,但是吞吐量很低,同样没有意义。所以,一个好的系统的
性能测试必然受到这两个条件的同时作用。有经验的朋友一定知道,这两个东西的一些关系:
Throughput越大,Latency会越差。因为请求量过大,系统太繁忙,所以响应速度自然会低。
Latency越好,能支持的Throughput就会越高。因为Latency短说明处理速度快,于是就可以处理更多的请求。
二、系统性能测试
经过上述的说明,我们知道要测试系统的性能,需要我们收集系统的Throughput和Latency这两个值。
首先,需要定义Latency这个值,比如说,对于网站系统响应时间必需是5秒以内(对于某些实时系统可能需要定义的更短,比如5ms以内,这个更根据不同的业务来定义)
其次,开发性能测试工具,一个工具用来制造高强度的Throughput,另一个工具用来测量Latency。对于第一个工具,你可以参考一下“十个免费的Web压力测试工具”,关于如何测量Latency,你可以在代码中测量,但是这样会影响程序的执行,而且只能测试到程序内部的Latency,真正的Latency是整个系统都算上,包括
操作系统和网络的延时,你可以使用Wireshark来抓网络包来测量。这两个工具具体怎么做,这个还请大家自己思考去了。
最后,开始性能测试。你需要不断地提升测试的Throughput,然后观察系统的负载情况,如果系统顶得住,那就观察Latency的值。这样,你就可以找到系统的最大负载,并且你可以知道系统的响应延时是多少。
再多说一些
关于Latency,如果吞吐量很少,这个值估计会非常稳定,当吞吐量越来越大时,系统的Latency会出现非常剧烈的抖动,所以,我们在测量Latency的时候,我们需要注意到Latency的分布,也就是说,有百分之几的在我们允许的范围,有百分之几的超出了,有百分之几的完全不可接受。也许,平均下来的Latency达标了,但是其中仅有50%的达到了我们可接受的范围。那也没有意义。
关于性能测试,我们还需要定义一个时间段。比如:在某个吞吐量上持续15分钟。因为当负载到达的时候,系统会变得不稳定,当过了一两分钟后,系统才会稳定。另外,也有可能是,你的系统在这个负载下前几分钟还表现正常,然后就不稳定了,甚至垮了。所以,需要这么一段时间。这个值,我们叫做峰值极限。
性能测试还需要做Soak
Test,也就是在某个吞吐量下,系统可以持续跑一周甚至更长。这个值,我们叫做系统的正常运行的负载极限。
性能测试有很多很复要的东西,比如:burst test等。这里不能一一详述,这里只说了一些和性能调优相关的东西。总之,性能测试是一细活和累活。
三、定位性能瓶颈
有了上面的铺垫,我们就可以测试到到系统的性能了,再调优之前,我们先来说说如何找到性能的瓶颈。我见过很多朋友会觉得这很容易,但是仔细一问,其实他们并没有一个比较系统的方法。
3.1查看操作系统负载
首先,当我们系统有问题的时候,我们不要急于去调查我们代码,这个毫无意义。我们首要需要看的是操作系统的报告。看看操作系统的CPU利用率,看看内存使用率,看看操作系统的IO,还有网络的IO,网络链接数,等等。Windows下的perfmon是一个很不错的工具,Linux下也有很多相关的命令和工具,比如:SystemTap,LatencyTOP,vmstat,sar,iostat,top,tcpdump等等。通过观察这些数据,我们就可以知道我们的软件的性能基本上出在哪里。比如:
1)先看CPU利用率,如果CPU利用率不高,但是系统的Throughput和Latency上不去了,这说明我们的程序并没有忙于计算,而是忙于别的一些事,比如IO。(另外,CPU的利用率还要看内核态的和用户态的,内核态的一上去了,整个系统的性能就下来了。而对于多核CPU来说,CPU 0是相当关键的,如果CPU 0的负载高,那么会影响其它核的性能,因为CPU各核间是需要有调度的,这靠CPU0完成)
2)然后,我们可以看一下IO大不大,IO和CPU一般是反着来的,CPU利用率高则IO不大,IO大则CPU就小。关于IO,我们要看三个事,一个是磁盘文件IO,一个是驱动程序的IO(如:网卡),一个是内存换页率。这三个事都会影响系统性能。
3)然后,查看一下网络带宽使用情况,在Linux下,你可以使用iftop,iptraf,ntop,tcpdump这些命令来查看。或是用Wireshark来查看。
4)如果CPU不高,IO不高,内存使用不高,网络带宽使用不高。但是系统的性能上不去。这说明你的程序有问题,比如,你的程序被阻塞了。可能是因为等那个锁,可能是因为等某个资源,或者是在切换上下文。
通过了解操作系统的性能,我们才知道性能的问题,比如:带宽不够,内存不够,TCP缓冲区不够,等等,很多时候,不需要调整程序的,只需要调整一下硬件或操作系统的配置就可以了。
#p#
3.2使用Profiler测试
接下来,我们需要使用性能检测工具,也就是使用某个Profiler来差看一下我们程序的运行性能。如:Java的JProfiler/TPTP/CodePro Profiler,GNU的gprof,IBM的PurifyPlus,Intel的VTune,AMD的CodeAnalyst,还有Linux下的OProfile/perf,后面两个可以让你对你的代码优化到CPU的微指令级别,如果你关心CPU的L1/L2的缓存调优,那么你需要考虑一下使用VTune。使用这些Profiler工具,可以让你程序中各个模块函数甚至指令的很多东西,如:运行的时间,调用的次数,CPU的利用率,等等。这些东西对我们来说非常有用。
我们重点观察运行时间最多,调用次数最多的那些函数和指令。这里注意一下,对于调用次数多但是时间很短的函数,你可能只需要轻微优化一下,你的性能就上去了(比如:某函数一秒种被调用100万次,你想想如果你让这个函数提高0.01毫秒的时间,这会给你带来多大的性能)
使用Profiler有个问题我们需要注意一下,因为Profiler会让你的程序运行的性能变低,像PurifyPlus这样的工具会在你的代码中插入很多代码,会导致你的程序运行效率变低,从而没发测试出在高吞吐量下的系统的性能,对此,一般有两个方法来定位系统瓶颈:
1)在你的代码中自己做统计,使用微秒级的计时器和函数调用计算器,每隔10秒把统计log到文件中。
2)分段注释你的代码块,让一些函数空转,做Hard Code的Mock,然后再测试一下系统的Throughput和Latency是否有质的变化,如果有,那么被注释的函数就是性能瓶颈,再在这个函数体内注释代码,直到找到最耗性能的语句。
最后再说一点,对于性能测试,不同的Throughput会出现不同的测试结果,不同的测试数据也会有不同的测试结果。所以,用于性能测试的数据非常重要,性能测试中,我们需要观测试不同Throughput的结果。
四、常见的系统瓶颈
下面这些东西是我所经历过的一些问题,也许并不全,也许并不对,大家可以补充指正,我纯属抛砖引玉。关于系统架构方面的性能调优,大家可移步看一下《由12306.cn谈谈网站性能技术》,关于Web方面的一些性能调优的东西,大家可以看看《Web开发中需要了解的东西》一文中的性能一章。我在这里就不再说设计和架构上的东西了。
一般来说,性能优化也就是下面的几个策略:
用空间换时间。各种cache如CPU L1/L2/RAM到硬盘,都是用空间来换时间的策略。这样策略基本上是把计算的过程一步一步的保存或缓存下来,这样就不用每次用的时候都要再计算一遍,比如数据缓冲,CDN,等。这样的策略还表现为冗余数据,比如数据镜象,负载均衡什么的。
用时间换空间。有时候,少量的空间可能性能会更好,比如网络传输,如果有一些压缩数据的算法(如前些天说的“Huffman编码压缩算法”和“rsync的核心算法”),这样的算法其实很耗时,但是因为瓶颈在网络传输,所以用时间来换空间反而能省时间。
简化代码。最高效的程序就是不执行任何代码的程序,所以,代码越少性能就越高。关于代码级优化的技术大学里的教科书有很多示例了。如:减少循环的层数,减少递归,在循环中少声明变量,少做分配和释放内存的操作,尽量把循环体内的表达式抽到循环外,条件表达的中的多个条件判断的次序,尽量在程序启动时把一些东西准备好,注意函数调用的开销(栈上开销),注意面向对象语言中临时对象的开销,小心使用异常(不要用异常来检查一些可接受可忽略并经常发生的错误),等等,这连东西需要我们非常了解编程语言和常用的库。
并行处理。如果CPU只有一个核,你要玩多进程,多线程,对于计算密集型的软件会反而更慢(因为操作系统调度和切换开销很大),CPU的核多了才能真正体现出多进程多线程的优势。并行处理需要我们的程序有Scalability,不能水平或垂直扩展的程序无法进行并行处理。从架构上来说,这表再为——是否可以做到不改代码只是加加机器就可以完成性能提升?
总之,根据2:8原则来说,20%的代码耗了你80%的性能,找到那20%的代码,你就可以优化那80%的性能。下面的一些东西都是我的一些经验,我只例举了一些最有价值的性能调优的的方法,供你参考,也欢迎补充。
4.1算法调优
算法非常重要,好的算法会有更好的性能。举几个我经历过的项目的例子,大家可以感觉一下。
一个是过滤算法。系统需要对收到的请求做过滤,我们把可以被filter in/out的东西配置在了一个文件中,原有的过滤算法是遍历过滤配置,后来,我们找到了一种方法可以对这个过滤配置进行排序,这样就可以用二分折半的方法来过滤,系统性能增加了50%。
一个是哈希算法。计算哈希算法的函数并不高效,一方面是计算太费时,另一方面是碰撞太高,碰撞高了就跟单向链表一个性能(可参看Hash Collision DoS 问题)。我们知道,算法都是和需要处理的数据很有关系的,就算是被大家所嘲笑的“冒泡排序”在某些情况下(大多数数据是排好序的)其效率会高于所有的排序算法。哈希算法也一样,广为人知的哈希算法都是用英文字典做测试,但是我们的业务在数据有其特殊性,所以,对于还需要根据自己的数据来挑选适合的哈希算法。对于我以前的一个项目,公司内某牛人给我发来了一个哈希算法,结果让我们的系统性能上升了150%。(关于各种哈希算法,你一定要看看StackExchange上的这篇关于各种hash算法的文章)
分而治之和预处理。以前有一个程序为了生成月报表,每次都需要计算很长的时间,有时候需要花将近一整天的时间。于是我们把我们找到了一种方法可以把这个算法发成增量式的,也就是说我每天都把当天的数据计算好了后和前一天的报表合并,这样可以大大的节省计算时间,每天的数据计算量只需要20分钟,但是如果我要算整个月的,系统则需要10个小时以上(SQL语句在大数据量面前性能成级数性下降)。这种分而治之的思路在大数据面前对性能有很帮助,就像merge排序一样。SQL语句和数据库的性能优化也是这一策略,如:使用嵌套式的Select而不是笛卡尔积的Select,使用视图,等等。
4.2代码调优
字符串操作。这是最费系统性能的事了,无论是strcpy,strcat还是strlen,最需要注意的是字符串子串匹配。所以,能用整型最好用整型。举几个例子,第一个例子是N年前做银行的时候,我的同事喜欢把日期存成字符串(如:2012-05-29 08:30:02),我勒个去,一个select where between语句相当耗时。另一个例子是,我以前有个同事把一些状态码用字符串来处理,他的理由是,这样可以在界面上直接显示,后来性能调优的时候,我把这些状态码全改成整型,然后用位操作查状态,因为有一个每秒钟被调用了150K次的函数里面有三处需要检查状态,经过改善以后,整个系统的性能上升了30%左右。还有一个例子是,我以前从事的某个产品编程规范中有一条是要在每个函数中把函数名定义出来,如:const char fname[]=”functionName()”,这是为了好打日志,但是为什么不声明成static类型的呢?
多线程调优。有人说,thread is evil,这个对于系统性能在某些时候是个问题。因为多线程瓶颈就在于互斥和同步的锁上,以及线程上下文切换的成本,怎么样的少用锁或不用锁是根本(比如:多版本并发控制(MVCC)在分布式系统中的应用中说的乐观锁可以解决性能问题),此外,还有读写锁也可以解决大多数是读操作的并发的性能问题。这里多说一点在C++中,我们可能会使用线程安全的智能指针AutoPtr或是别的一些容器,只要是线程安全的,其不管三七二十一都要上锁,上锁是个成本很高的操作,使用AutoPtr会让我们的系统性能下降得很快,如果你可以保证不会有线程并发问题,那么你应该不要用AutoPtr。我记得我上次我们同事去掉智能指针的引用计数,让系统性能提升了50%以上。对于Java对象的引用计数,如果我猜的没错的话,到处都是锁,所以,Java的性能问题一直是个问题。另外,线程不是越多越好,线程间的调度和上下文切换也是很夸张的事,尽可能的在一个线程里干,尽可能的不要同步线程。这会让你有很多的性能。
内存分配。不要小看程序的内存分配。malloc/realloc/calloc这样的系统调非常耗时,尤其是当内存出现碎片的时候。我以前的公司出过这样一个问题——在用户的站点上,我们的程序有一天不响应了,用GDB跟进去一看,系统hang在了malloc操作上,20秒都没有返回,重启一些系统就好了。这就是内存碎片的问题。这就是为什么很多人抱怨STL有严重的内存碎片的问题,因为太多的小内存的分配释放了。有很多人会以为用内存池可以解决这个问题,但是实际上他们只是重新发明了Runtime-C或操作系统的内存管理机制,完全于事无补。当然解决内存碎片的问题还是通过内存池,具体来说是一系列不同尺寸的内存池(这个留给大家自己去思考)。当然,少进行动态内存分配是最好的。说到内存池就需要说一下池化技术。比如线程池,连接池等。池化技术对于一些短作业来说(如http服务)相当相当的有效。这项技术可以减少链接建立,线程创建的开销,从而提高性能。
异步操作。我们知道Unix下的文件操作是有block和non-block的方式的,像有些系统调用也是block式的,如:Socket下的select,Windows下的WaitforObject之类的,如果我们的程序是同步操作,那么会非常影响性能,我们可以改成异步的,但是改成异步的方式会让你的程序变复杂。异步方式一般要通过队列,要注间队列的性能问题,另外,异步下的状态通知通常是个问题,比如消息事件通知方式,有callback方式,等,这些方式同样可能会影响你的性能。但是通常来说,异步操作会让性能的吞吐率有很大提升(Throughput),但是会牺牲系统的响应时间(latency)。这需要业务上支持。
语言和代码库。我们要熟悉语言以及所使用的函数库或类库的性能。比如:STL中的很多容器分配了内存后,那怕你删除元素,内存也不会回收,其会造成内存泄露的假像,并可能造成内存碎片问题。再如,STL某些容器的size()==0和empty()是不一样的,因为,size()是O(n)复杂度,empty()是O(1)的复杂度,这个要小心。Java中的JVM调优需要使用的这些参数:-Xms-Xmx-Xmn-XX:SurvivorRatio-XX:MaxTenuringThreshold,还需要注意JVM的GC,GC的霸气大家都知道,尤其是full GC(还整理内存碎片),他就像“恐龙特级克赛号”一样,他运行的时候,整个世界的时间都停止了。
4.3网络调优
关于网络调优,尤其是TCP Tuning(你可以以这两个关键词在网上找到很多文章),这里面有很多很多东西可以说。看看Linux下TCP/IP的那么多参数就知道了(顺便说一下,你也许不喜欢Linux,但是你不能否认Linux给我们了很多可以进行内核调优的权力)。强烈建议大家看看《TCP/IP详解卷1:协议》这本书。我在这里只讲一些概念上的东西。
A)TCP调优
我们知道TCP链接是有很多开销的,一个是会占用文件描述符,另一个是会开缓存,一般来说一个系统可以支持的TCP链接数是有限的,我们需要清楚地认识到TCP链接对系统的开销是很大的。正是因为TCP是耗资源的,所以,很多攻击都是让你系统上出现大量的TCP链接,把你的系统资源耗尽。比如著名的SYNC Flood攻击。所以,我们要注意配置KeepAlive参数,这个参数的意思是定义一个时间,如果链接上没有数据传输,系统会在这个时间发一个包,如果没有收到回应,那么TCP就认为链接断了,然后就会把链接关闭,这样可以回收系统资源开销。(注:HTTP层上也有KeepAlive参数)对于像HTTP这样的短链接,设置一个1-2分钟的keepalive非常重要。这可以在一定程度上防止DoS攻击。有下面几个参数(下面这些参数的值仅供参考):
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_keepalive_intvl = 20
net.ipv4.tcp_fin_timeout = 30
对于TCP的TIME_WAIT这个状态,主动关闭的一方进入TIME_WAIT状态,TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),默认为4分钟,TIME_WAIT状态下的资源不能回收。有大量的TIME_WAIT链接的情况一般是在HTTP服务器上。对此,有两个参数需要注意,
net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_tw_recycle=1
前者表示重用TIME_WAIT,后者表示回收TIME_WAIT的资源。
TCP还有一个重要的概念叫RWIN(TCP Receive Window Size),这个东西的意思是,我一个TCP链接在没有向Sender发出ack时可以接收到的最大的数据包。为什么这个很重要?因为如果Sender没有收到Receiver发过来ack,Sender就会停止发送数据并会等一段时间,如果超时,那么就会重传。这就是为什么TCP链接是可靠链接的原因。重传还不是最严重的,如果有丢包发生的话,TCP的带宽使用率会马上受到影响(会盲目减半),再丢包,再减半,然后如果不丢包了,就逐步恢复。相关参数如下:
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
一般来说,理论上的RWIN应该设置成:吞吐量*回路时间。Sender端的buffer应该和RWIN有一样的大小,因为Sender端发送完数据后要等Receiver端确认,如果网络延时很大,buffer过小了,确认的次数就会多,于是性能就不高,对网络的利用率也就不高了。也就是说,对于延迟大的网络,我们需要大的buffer,这样可以少一点ack,多一些数据,对于响应快一点的网络,可以少一些buffer。因为,如果有丢包(没有收到ack),buffer过大可能会有问题,因为这会让TCP重传所有的数据,反而影响网络性能。(当然,网络差的情况下,就别玩什么高性能了)所以,高性能的网络重要的是要让网络丢包率非常非常地小(基本上是用在LAN里),如果网络基本是可信的,这样用大一点的buffer会有更好的网络传输性能(来来回回太多太影响性能了)。
另外,我们想一想,如果网络质量非常好,基本不丢包,而业务上我们不怕偶尔丢几个包,如果是这样的话,那么,我们为什么不用速度更快的UDP呢?你想过这个问题了吗?
B)UDP调优
说到UDP的调优,有一些事我想重点说一样,那就是MTU——最大传输单元(其实这对TCP也一样,因为这是链路层上的东西)。所谓最大传输单元,你可以想像成是公路上的公交车,假设一个公交车可以最多坐70人,带宽就像是公路的车道数一样,如果一条路上最多可以容下100辆公交车,那意味着我最多可以运送7000人,但是如果公交车坐不满,比如平均每辆车只有20人,那么我只运送了2000人,于是我公路资源(带宽资源)就被浪费了。所以,我们对于一个UDP的包,我们要尽量地让他大到MTU的最大尺寸再往网络上传,这样可以最大化带宽利用率。对于这个MTU,以太网是1500字节,光纤是4352字节,802.11无线网是7981。但是,当我们用TCP/UDP发包的时候,我们的有效负载Payload要低于这个值,因为IP协议会加上20个字节,UDP会加上8个字节(TCP加的更多),所以,一般来说,你的一个UDP包的最大应该是1500-8-20=1472,这是你的数据的大小。当然,如果你用光纤的话,这个值就可以更大一些。(顺便说一下,对于某些NB的千光以态网网卡来说,在网卡上,网卡硬件如果发现你的包的大小超过了MTU,其会帮你做fragment,到了目标端又会帮你做重组,这就不需要你在程序中处理了)
再多说一下,使用Socket编程的时候,你可以使用setsockopt() 设置SO_SNDBUF/SO_RCVBUF的大小,TTL和KeepAlive这些关键的设置,当然,还有很多,具体你可以查看一下Socket的手册。
最后说一点,UDP还有一个最大的好处是multi-cast多播,这个技术对于你需要在内网里通知多台结点时非常方便和高效。而且,多播这种技术对于机会的水平扩展(需要增加机器来侦听多播信息)也很有利。
C)网卡调优
对于网卡,我们也是可以调优的,这对于千兆以及网网卡非常必要,在Linux下,我们可以用ifconfig查看网上的统计信息,如果我们看到overrun上有数据,我们就可能需要调整一下txqueuelen的尺寸(一般默认为1000),我们可以调大一些,如:ifconfig eth0 txqueuelen 5000。Linux下还有一个命令叫:ethtool可以用于设置网卡的缓冲区大小。在Windows下,我们可以在网卡适配器中的高级选项卡中调整相关的参数(如:Receive Buffers, Transmit Buffer等,不同的网卡有不同的参数)。把Buffer调大对于需要大数据量的网络传输非常有效。
D)其它网络性能
关于多路复用技术,也就是用一个线程来管理所有的TCP链接,有三个系统调用要重点注意:一个是select,这个系统调用只支持上限1024个链接,第二个是poll,其可以突破1024的限制,但是select和poll本质上是使用的轮询机制,轮询机制在链接多的时候性能很差,因主是O(n)的算法,所以,epoll出现了,epoll是操作系统内核支持的,仅当在链接活跃时,操作系统才会callback,这是由操作系统通知触发的,但其只有Linux Kernel 2.6以后才支持(准确说是2.5.44中引入的),当然,如果所有的链接都是活跃的,过多的使用epoll_ctl可能会比轮询的方式还影响性能,不过影响的不大。
另外,关于一些和DNS Lookup的系统调用要小心,比如:gethostbyaddr/gethostbyname,这个函数可能会相当的费时,因为其要到网络上去找域名,因为DNS的递归查询,会导致严重超时,而又不能通过设置什么参数来设置time out,对此你可以通过配置hosts文件来加快速度,或是自己在内存中管理对应表,在程序启动时查好,而不要在运行时每次都查。另外,在多线程下面,gethostbyname会一个更严重的问题,就是如果有一个线程的gethostbyname发生阻塞,其它线程都会在gethostbyname处发生阻塞,这个比较变态,要小心。(你可以试试GNU的gethostbyname_r(),这个的性能要好一些)这种到网上找信息的东西很多,比如,如果你的Linux使用了NIS,或是NFS,某些用户或文件相关的系统调用就很慢,所以要小心。
#p#
4.4系统调优
A)I/O模型
前面说到过select/poll/epoll这三个系统调用,我们都知道,Unix/Linux下把所有的设备都当成文件来进行I/O,所以,那三个操作更应该算是I/O相关的系统调用。说到I/O模型,这对于我们的I/O性能相当重要,我们知道,Unix/Linux经典的I/O方式是(关于Linux下的I/O模型,大家可以读一下这篇文章《使用异步I/O大大提高性能》):
第一种,同步阻塞式I/O,这个不说了。
第二种,同步无阻塞方式。其通过fctnl设置O_NONBLOCK来完成。
第三种,对于select/poll/epoll这三个是I/O不阻塞,但是在事件上阻塞,算是:I/O异步,事件同步的调用。
第四种,AIO方式。这种I/O模型是一种处理与I/O并行的模型。I/O请求会立即返回,说明请求已经成功发起了。在后台完成I/O操作时,向应用程序发起通知,通知有两种方式:一种是产生一个信号,另一种是执行一个基于线程的回调函数来完成这次I/O处理过程。
第四种因为没有任何的阻塞,无论是I/O上,还是事件通知上,所以,其可以让你充分地利用CPU,比起第二种同步无阻塞好处就是,第二种要你一遍一遍地去轮询。Nginx之所所以高效,是其使用了epoll和AIO的方式来进行I/O的。
再说一下Windows下的I/O模型,
a)一个是WriteFile系统调用,这个系统调用可以是同步阻塞的,也可以是同步无阻塞的,关于看文件是不是以Overlapped打开的。关于同步无阻塞,需要设置其最后一个参数Overlapped,微软叫Overlapped I/O,你需要WaitForSingleObject才能知道有没有写完成。这个系统调用的性能可想而知。
b)另一个叫WriteFileEx的系统调用,其可以实现异步I/O,并可以让你传入一个callback函数,等I/O结束后回调之,但是这个回调的过程Windows是把callback函数放到了APC(Asynchronous Procedure Calls)的队列中,然后,只用当应用程序当前线程成为可被通知状态(Alterable)时,才会被回调。只有当你的线程使用了这几个函数时WaitForSingleObjectEx,WaitForMultipleObjectsEx, MsgWaitForMultipleObjectsEx,SignalObjectAndWait 和SleepEx,线程才会成为Alterable状态。可见,这个模型,还是有wait,所以性能也不高。
c)然后是IOCP–IO Completion Port,IOCP会把I/O的结果放在一个队列中,但是,侦听这个队列的不是主线程,而是专门来干这个事的一个或多个线程去干(老的平台要你自己创建线程,新的平台是你可以创建一个线程池)。IOCP是一个线程池模型。这个和Linux下的AIO模型比较相似,但是实现方式和使用方式完全不一样。
当然,真正提高I/O性能方式是把和外设的I/O的次数降到最低,最好没有,所以,对于读来说,内存cache通常可以从质上提升性能,因为内存比外设快太多了。对于写来说,cache住要写的数据,少写几次,但是cache带来的问题就是实时性的问题,也就是latency会变大,我们需要在写的次数上和相应上做权衡。
B)多核CPU调优
关于CPU的多核技术,我们知道,CPU0是很关键的,如果0号CPU被用得过狠的话,别的CPU性能也会下降,因为CPU0是有调整功能的,所以,我们不能任由操作系统负载均衡,因为我们自己更了解自己的程序,所以,我们可以手动地为其分配CPU核,而不会过多地占用CPU0,或是让我们关键进程和一堆别的进程挤在一起。
对于Windows来说,我们可以通过“任务管理器”中的“进程”而中右键菜单中的“设置相关性……”(Set Affinity…)来设置并限制这个进程能被运行在哪些核上。
对于Linux来说,可以使用taskset命令来设置(你可以通过安装schedutils来安装这个命令:apt-get install schedutils)
多核CPU还有一个技术叫NUMA技术(Non-Uniform Memory Access)。传统的多核运算是使用SMP(Symmetric Multi-Processor )模式,多个处理器共享一个集中的存储器和I/O总线。于是就会出现一致存储器访问的问题,一致性通常意味着性能问题。NUMA模式下,处理器被划分成多个node,每个node有自己的本地存储器空间。关于NUMA的一些技术细节,你可以查看一下这篇文章《Linux的NUMA技术》,在Linux下,对NUMA调优的命令是:numactl 。如下面的命令:(指定命令“myprogram arg1 arg2”运行在node 0上,其内存分配在node 0 和1上)
numactl --cpubind=0 --membind=0,1 myprogram arg1 arg2
当然,上面这个命令并不好,因为内存跨越了两个node,这非常不好。最好的方式是只让程序访问和自己运行一样的node,如:
$ numactl --membind 1 --cpunodebind 1 --localalloc myapplication
C)文件系统调优
关于文件系统,因为文件系统也是有cache的,所以,为了让文件系统有最大的性能。首要的事情就是分配足够大的内存,这个非常关键,在Linux下可以使用free命令来查看 free/used/buffers/cached,理想来说,buffers和cached应该有40%左右。然后是一个快速的硬盘控制器,SCSI会好很多。最快的是Intel SSD固态硬盘,速度超快,但是写次数有限。
接下来,我们就可以调优文件系统配置了,对于Linux的Ext3/4来说,几乎在所有情况下都有所帮助的一个参数是关闭文件系统访问时间,在/etc/fstab下看看你的文件系统有没有noatime参数(一般来说应该有),还有一个是dealloc,它可以让系统在最后时刻决定写入文件发生时使用哪个块,可优化这个写入程序。还要注间一下三种日志模式:data=journal、data=ordered和data=writeback。默认设置data=ordered提供性能和防护之间的最佳平衡。
当然,对于这些来说,ext4的默认设置基本上是最佳优化了。
这里介绍一个Linux下的查看I/O的命令——iotop,可以让你看到各进程的磁盘读写的负载情况。
其它还有一些关于NFS、XFS的调优,大家可以上google搜索一些相关优化的文章看看。关于各文件系统,大家可以看一下这篇文章——《Linux日志文件系统及性能分析》。
4.5数据库调优
数据库调优并不是我的强项,我就仅用我非常有限的知识说上一些吧。注意,下面的这些东西并不一定正确,因为在不同的业务场景,不同的数据库设计下可能会得到完全相反的结论,所以,我仅在这里做一些一般性的说明,具体问题还要具体分析。
A)数据库引擎调优
我对数据库引擎不是熟,但是有几个事情我觉得是一定要去了解的。
数据库的锁的方式。这个非常非常地重要。并发情况下,锁是非常非常影响性能的。各种隔离级别,行锁,表锁,页锁,读写锁,事务锁,以及各种写优先还是读优先机制。性能最高的是不要锁,所以,分库分表,冗余数据,减少一致性事务处理,可以有效地提高性能。NoSQL就是牺牲了一致性和事务处理,并冗余数据,从而达到了分布式和高性能。
数据库的存储机制。不但要搞清楚各种类型字段是怎么存储的,更重要的是数据库的数据存储方式,是怎么分区的,是怎么管理的,比如Oracle的数据文件,表空间,段,等等。了解清楚这个机制可以减轻很多的I/O负载。比如:MySQL下使用show engines;可以看到各种存储引擎的支持。不同的存储引擎有不同的侧重点,针对不同的业务或数据库设计会让你有不同的性能。
数据库的分布式策略。最简单的就是复制或镜像,需要了解分布式的一致性算法,或是主主同步,主从同步。通过了解这种技术的机理可以做到数据库级别的水平扩展。
B)SQL语句优化
关于SQL语句的优化,首先也是要使用工具,比如:MySQL SQL Query Analyzer,Oracle SQL Performance Analyzer,或是微软SQL Query Analyzer,基本上来说,所有的RMDB都会有这样的工具,来让你查看你的应用中的SQL的性能问题。 还可以使用explain来看看SQL语句最终Execution Plan会是什么样的。
还有一点很重要,数据库的各种操作需要大量的内存,所以服务器的内存要够,优其应对那些多表查询的SQL语句,那是相当的耗内存。
下面我根据我有限的数据库SQL的知识说几个会有性能问题的SQL:
全表检索。比如:select * from user where lastname = “xxxx”,这样的SQL语句基本上是全表查找,线性复杂度O(n),记录数越多,性能也越差(如:100条记录的查找要50ms,一百万条记录需要5分钟)。对于这种情况,我们可以有两种方法提高性能:一种方法是分表,把记录数降下来,另一种方法是建索引(为lastname建索引)。索引就像是key-value的数据结构一样,key就是where后面的字段,value就是物理行号,对索引的搜索复杂度是基本上是O(log(n)) ——用B-Tree实现索引(如:100条记录的查找要50ms,一百万条记录需要100ms)。
索引。对于索引字段,最好不要在字段上做计算、类型转换、函数、空值判断、字段连接操作,这些操作都会破坏索引原本的性能。当然,索引一般都出现在Where或是Order by字句中,所以对Where和Order by子句中的子段最好不要进行计算操作,或是加上什么NOT之类的,或是使用什么函数。
多表查询。关系型数据库最多的操作就是多表查询,多表查询主要有三个关键字,EXISTS,IN和JOIN(关于各种join,可以参看图解SQL的Join一文)。基本来说,现代的数据引擎对SQL语句优化得都挺好的,JOIN和IN/EXISTS在结果上有些不同,但性能基本上都差不多。有人说,EXISTS的性能要好于IN,IN的性能要好于JOIN,我各人觉得,这个还要看你的数据、schema和SQL语句的复杂度,对于一般的简单的情况来说,都差不多,所以千万不要使用过多的嵌套,千万不要让你的SQL太复杂,宁可使用几个简单的SQL也不要使用一个巨大无比的嵌套N级的SQL。还有人说,如果两个表的数据量差不多,Exists的性能可能会高于In,In可能会高于Join,如果这两个表一大一小,那么子查询中,Exists用大表,In则用小表。这个,我没有验证过,放在这里让大家讨论吧。另,有一篇关于SQL Server的文章大家可以看看《IN vs JOIN vs EXISTS》
JOIN操作。有人说,Join表的顺序会影响性能,只要Join的结果集是一样,性能和join的次序无关。因为后台的数据库引擎会帮我们优化的。Join有三种实现算法,嵌套循环,排序归并,和Hash式的Join。(MySQL只支持第一种)
(1)嵌套循环,就好像是我们常见的多重嵌套循环。注意,前面的索引说过,数据库的索引查找算法用的是B-Tree,这是O(log(n))的算法,所以,整个算法复法度应该是O(log(n)) * O(log(m))这样的。
(2)Hash式的Join,主要解决嵌套循环的O(log(n))的复杂,使用一个临时的hash表来标记。
(3)排序归并,意思是两个表按照查询字段排好序,然后再合并。当然,索引字段一般是排好序的。
还是那句话,具体要看什么样的数据,什么样的SQL语句,你才知道用哪种方法是最好的。
部分结果集。我们知道MySQL里的Limit关键字,Oracle里的rownum,SQL Server里的Top都是在限制前几条的返回结果。这给了我们数据库引擎很多可以调优的空间。一般来说,返回top n的记录数据需要我们使用order by,注意在这里我们需要为order by的字段建立索引。有了被建索引的order by后,会让我们的select语句的性能不会被记录数的所影响。使用这个技术,一般来说我们前台会以分页方式来显现数据,Mysql用的是OFFSET,SQL Server用的是FETCH NEXT,这种Fetch的方式其实并不好是线性复杂度,所以,如果我们能够知道order by字段的第二页的起始值,我们就可以在where语句里直接使用>=的表达式来select,这种技术叫seek,而不是fetch,seek的性能比fetch要高很多。
字符串。正如我前面所说的,字符串操作对性能上有非常大的恶梦,所以,能用数据的情况就用数字,比如:时间,工号,等。
全文检索。千万不要用Like之类的东西来做全文检索,如果要玩全文检索,可以尝试使用Sphinx。
其它。
(1)不要select *,而是明确指出各个字段,如果有多个表,一定要在字段名前加上表名,不要让引擎去算。
(2)不要用Having,因为其要遍历所有的记录。性能差得不能再差。
(3)尽可能地使用UNION ALL 取代UNION。
(4)索引过多,insert和delete就会越慢。而update如果update多数索引,也会慢,但是如果只update一个,则只会影响一个索引表。
先写这么多,欢迎大家指正补充。
前一段时间做一个转发工具压力
测试,只是提供IP和端口,下面贴出来与大家分享,不足之处还请指正:
整个脚本写法很简单,大体来说,分三个步骤:
步骤1:建立到服务器端连接
rc = lrs_create_socket("socket0", "TCP", "LocalHost=0", "RemoteHost=127.0.0.1:8808", LrsLastArg);
注:rc=0则表示建立通讯成功
步骤2:发送报文和接收报文
lrs_send("socket0","buf0", LrsLastArg);//往socket0发送buf0的数据
lrs_receive ("socket0","buf1",LrsLastArg); //将socket0发送返回的数据存放到buf1中
步骤3:关闭连接
lrs_close_socket("socket0");//关闭
到此为止,socket通讯的单次的发送、接收报文基本没有什么问题了,完整源码如下:
#define _EOF '#' #include "lrs.h" Action() { char *recvbuf; int recvlen=0; int rc; lr_start_transaction("Trans_socket");//事务 lrs_set_recv_timeout (60,0);//接收超时时间 lr_start_transaction("Conn_socket"); //RemoteHost处填入被测程序所在服务器IP rc = lrs_create_socket("socket0", "TCP", "LocalHost=0", "RemoteHost=127.0.0.1:8808", LrsLastArg); lr_output_message("rc=%d",rc); if (rc != 0 ) { lr_end_transaction("Conn_socket", LR_FAIL); lr_end_transaction ("Trans_socket", LR_FAIL); return 0; } lr_end_transaction("Conn_socket", LR_PASS); //判断socket是否链接成功的事务 lr_rendezvous("集合点"); lrs_send("socket0","buf0", LrsLastArg); lrs_receive ("socket0","buf1",LrsLastArg); lrs_get_last_received_buffer ("socket0",&recvbuf,&recvlen); //判断报文长度是否正确 if(recvlen==304) lr_end_transaction("Trans_socket", LR_PASS); else lr_end_transaction ("Trans_socket", LR_FAIL); //判断返回信息的长度是否正确,recvlen处填入预期返回信息的长度 lrs_close_socket("socket0"); return 0; } |
data.ws 是报文部分, buf0 100 ,100是指报文的长度,\x表示是16进制
报文内容验证,待分享