MVC使得业务逻辑与显示相分离,可以使代码更加的规范与清晰,下面是我自己总结的一般包的一些命名,可能不太规范,希望对大家有帮助!
就是对java的工程进行分层,封装。
三层:
1、数据访问层①(com.mipo.dao)(包以dao结尾)
专门用于操作数据库。(对数据库中的表进行,增,删,改,查)
2、业务逻辑层(包以biz结尾)(包以entity(model)结尾)
主要处理我们项目当中的业务逻辑
(需要有控制器和实体对象来支撑)
控制器②(com.mipo.biz)
实体③(com.mipo.model)
3、表现层④(com.mipo.test)(
Test.java测试类)
给客户看的,供客户操作的。
作为通用类(基础类)的包
通用类⑤(com.mipo.common)
注意:①:com.mipo.model,封装的实体对象(类名一般为表名),类中将 属性定义为私有的,定义一个共公的无参的构造方法,定义一个全 部属性为参数的构造方法,再为每个属性定义getXX,setXX方法;
②:com.mipo.dao,创建实体类对应的数据库操作接口,执行一般的 数据库操作,如增删改,注意抛出异常,接口名 I+表名+dao
例:public int add(BookInfo entity)throw SQLException();
在本包中实现定义的接口,类名 表名+dao+impl
例:public class BookInfo extends BaseDao implements IBookInfodao
③:com.mipo.common,通用类的包;如建立数据库连接,以及建立 增删改方法,供其他类调用
④:com.mipo.view(test),给客户看的界面,供客户操作
⑤:com.mipo.control,控制器
操作:
需要使用到接口(interface)
我们使用util(common)作为通用类(基础类)的包
包以dao结尾,就是数据访问层
包以biz结尾, 就是控制器
包以entity(model)结尾,就是实体层
表现层:
Test.java测试类
过程:
第一步:在common包中创建数据库操作通用类
第二步:根据数据库中的表,创建对应的实体类(创建在entity包中)
第三步:创建实体类对应的数据库操作接口,并创建实现类实现接口(dao)
第四步:创建业务逻辑接口和对应的实现类(biz)
过socket进行通信的分布式应用是比较常见的,包括winsocket和websocket,LR支持这两个协议。
在使用LR回放socket协议的脚本时,可能会经常需要获取服务器返回的数据,语句格式如下:
lrs_create_socket("socket0", "TCP", "RemoteHost=1.2.3.4:12345", LrsLastArg); lrs_send("socket0", "buf0", LrsLastArg); lrs_receive("socket0", "buf1", LrsLastArg); |
很明显,socket0是通信所用套接字,buf0是LR将要发送给服务器的内容(来自用户行为),buf1则接收服务器返回。
我们经常需要通过提取buf1中的内容,并适当的加一些断定语言,来判断这个通信操作有没有获取期望的数据。经常使用函数lrs_get_last_received_buffer,如下:
lrs_get_last_received_buffer("socket0", &ActualBuffer, &NumberOfBytes);
这样就把lrs_receive接收到的数据保存在了内存中(指针ActualBuffer指向的一个有LR自动动态分配的内存块),并返回数据长度(保存在NumberOfBytes中)。
然后就可以像使用用户自定义的数组一样使用该缓冲区,如ActualBuffer[0]。需要注意的是,使用完成后需要手动释放ActualBuffer指向的内存,通过调用lrs_free_buffer。
下面是一个简单示例,在处理服务器返回数据时,我们将其作为char数组处理,经过验证这是没问题的。
完整代码:
vuser_init.c #include "lrs.h" vuser_init() { lrs_startup(257); return 0; } Action.c |
/********************************************************************* * Created by Mercury Interactive Windows Sockets Recorder * * Created on: Tue Nov 12 14:51:33 *********************************************************************/ #include "lrs.h" Action() { int NumberOfBytes=0; char *ActualBuffer; unsigned char num1,num2,num3,num4; unsigned int jifen =0; int dj =0 ; char * tmp; int i=0; lrs_create_socket("socket0", "TCP", "RemoteHost=10.15.107.12:12345", LrsLastArg); lrs_send("socket0", "buf0", LrsLastArg); lrs_receive("socket0", "buf1", LrsLastArg); /* Get the last received buffer and its size. */ lrs_get_last_received_buffer("socket0", &ActualBuffer, &NumberOfBytes); //7B 9C 0B 00 00 0F 00 02 6C 00 00 00 08 00 90 5F 01 00 0D 00 00 00,从第偏移量17取数,直到14,作为积分;偏移量18为等级。 lr_output_message("The last buffer's size is:%d/n", NumberOfBytes); num1 = ActualBuffer[17]; //最高位 num2 = ActualBuffer[16]; num3 = ActualBuffer[15]; num4 = ActualBuffer[14]; //最低位 lr_output_message("num1 = %d,num2 = %d,num3 = %d,num4 = %u\n",num1,num2,num3,num4); jifen = (num1 / 16 ) * 268435456 + (num1 % 16)* 16777216 + (num2 / 16)* 1048576 +(num2 % 16) * 65536 +(num3 / 16) * 4096 + (num3 % 16 ) * 256 +(num4 / 16)* 16 + (num4 % 16) * 1; //注意00 90 5F 01是16进制格式,其中01(num1)是最高位。 dj=(int)ActualBuffer[18]; lrs_free_buffer(ActualBuffer); lr_output_message("jifen = %d\n",jifen ); lr_output_message("dj = %d\n",dj ); lrs_close_socket("socket0"); return 0; } vuser_end.c [cpp] view plaincopy #include "lrs.h" vuser_end() { lrs_cleanup(); return 0; } |
UI自动化
测试,首要考虑的是我们所选用的测试工具或框架对测试程序的支持如何。而这个支持,则主要是通过对控件的识别和操作来体现的;但是,不管一个测试工具或者框架对测试程序的支持程序如何,它执行测试程序时最终都是以屏幕的绝对坐标来定位执行的,尽管我们平时都能听到很多人在说,尽量避免用坐标。
尽量避免用坐标和最终通过坐标来识别,这个看起来有点冲突,但是却又不会冲突,是不是有点类似太极的感觉了。
坐标,通常分为两类:绝对坐标和相对坐标。
1. 通常我们所说的坐标都是基于屏幕左上角的绝对坐标。测试工具和我们人的操作最终也都是通过这个坐标来进行测试程序操作的,只不过有的我们知道,有的我们不知道,仅此而已。
通俗的讲:我们人手动来点击屏幕的时候,只是在点击的位置<x,y>发送一个屏幕点击事件,这个屏幕点击事件通过windows库定位到当前活动的窗体,再对这个窗体的一个具体位置传递点击事件,从而获得响应。这个过程,在我们的感官和需求中,我们只需要直接对活动窗体的进行操作的结果,至于定位,who care?
自动化测试本质上就是来模拟人的行为的操作,所以实现过程大致类似。它首先通过我们在代码中编写的id、text、index、class等来定位到我们要操作的控件,然后再读取这个控件的x、y属性来发送点击事件。但是,在使用层面,我们只看到了通过id来点击,至于其他获取坐标这些,leave them alone。
2. 说到相对坐标,这个就稍微有点复杂。目前已知的相对方式有相对左上、左下、右上、右下、中上、中下、居中;并且根据是否随着父窗口变化而变化,又可分类为:不扩展、同步扩展(和父窗口一起放大/缩小)、按比例扩展(父窗口扩大3倍,该对象也扩大3倍)。
举个例子来说,屏幕上有一个记事本窗口“记事本.txt”,它的坐标绝对坐标是400,600,这个记事本的开始菜单栏,它的坐标是450,700。如果假设记事本窗口的坐标为<x,y>,那么这个开始菜单栏就可以描述为:相对于窗口"记事本.txt"的坐标为50,100,绝对坐标就可以表示为<x+50,y+100>;再假设菜单栏中的编辑按钮绝对坐标为500,700,那么它就可以描述为:相对于菜单栏的坐标为50,0,绝对坐标就可以表示为<x+50+50,y+100+0>。这样的话,这个窗口上的所有坐标就都可以用一个坐标来维护了,如果窗口位置发生变化,我们也只需要修改一个最上层父窗口的坐标就可以动态适配所有按钮的坐标了。
上面就是相对的解释。其实说白了,所谓相对坐标,就是一种优化的坐标计算方式,可以让我们用尽量少的改动去适应更多的变化。
至于它的相对方式和扩展方式,则就只是其中的一些计算方式,在这里就不一一举例了。
那么,了解了上面的这些,我们可以做些什么呢?
平时在开展自动化测试时,总是会遇到一些不能识别的自定义控件,尤其是app类型程序。那么这时候我们就可以通过上面的坐标适配来完善解决一下。
下面,我来简单说一个基于坐标的控件识别的简单实现思路:
1. 实现思想:通过虚拟截图的方式来提供一个快速定位虚拟控件的坐标系,并对齐赋予一些额外的识别参数;嵌入到其他测试工具中直接使用。
2. 首先,通过调用windows api或者其他截图程序,对测试程序的全窗口截一个半透明的截图(可以按比例缩放);
然后,获取测试程序的坐标,并监控这个截图上的拖拽事件来计算控件的坐标系,并写入xml文件;
其次,将这个xml文件导入到测试工具的对象库中进行使用。这里需要注意,有的测试工具不支持外部自定义对象,所以需要做一些转换。
3. 代码:以后再传吧。这块东西最好做成可视化的,比较易于操作
安全性测试都有什么?简单的就包括跳过权限验证啊,修改提交信息啊,复杂的呢,就有sql盲注、跨站点脚本等等。这些咱们暂时不一一细表,只说说我们为什么要进行安全性测试。、
其实网上关于安全性测试的资料并不是非常多,即使有人关注已只是很浅显的谈到部门安全性因素。当然,据我了解部分大公司都有自己的安全性测试团队,这部分
工作并不由测试人员进行。
胡扯了两句,今天我们来聊聊为什么进行安全性测试,或者说,安全性到底会引起哪些问题、后果。
第一,提到安全。我们一个产品一个网站最需要加强安全防范的就是
数据库。那么如果缺少了安全性测试,在高手的sql盲注下,你的数据库就会逐步展现在黑客的面前,无论是数据库类型、表结构、字段名或是详细的用户信息,都有无数种手段可以让人“一览无余”。
第二,就是权限。网站一般都规定了什么样的用户可以做什么事。比如版主可以修改所有人的帖子,而你普通用户只能编辑自己的帖子,同样游客只能看大家的帖子。这就是简单的权限。如果少了安全性保证,那么就容易有人跳出权限做他不该做的事情。
简单举个小例子,一个登录模块,让你输入用户名密码。我们会老老实实的输入我们的用户名密码,比如“风落几番”-“password”。如果我们刻意的去绕过登录认证呢?
猜想一下这个sql,单说用户名,开发人员很可能会这样去数据库里对比:
Select count(id) from sys_user where username=‘XXX’
当然可能更复杂,咱们就用这个说。如果我们在输入框里输入一段特殊的字符会如何?
’or‘1=1
这是段神奇的字符,因为这样这个sql就变成:
Select count(id) from sys_user where username=‘’or‘1=1’
好吧,我们就跳过了用户名的验证。。。
说的好基础和无聊的感觉,其实这就是安全性的一部分。
接着说第三,就是修改提交数据信息。曾经我们公司做过一个关于在线支付的商城,在安全性测试过程中,我发现通过抓包抓到的提交价格,经过修改再发包可以通过。简单来说就是本来100块钱买的东西,我抓包修改为1块就能成功购买。这就成为了一个巨大的隐患。
再说第四,类似跨站脚本的安全隐患。这方面网上资料很多,具体过程呢就像这样:
1.HTML注入。所有HTML注入范例只是注入一个JavaScript弹出式的警告框:alert(1)。
2.做坏事。如果您觉得警告框还不够刺激,当受害者点击了一个被注入了HTML代码的页面链接时攻击者能作的各种的恶意事情。
3.诱捕受害者,可能会redirect到另一个钓鱼网站之类的,使其蒙受损失。
录:
二、Web前端性能优化
三、应用服务器性能优化(重点)
四、存储性能优化
PS:本文为《大型网站技术架构 & 核心原理与案例分析(李智慧 著)》一书的
读书笔记 // =======================================================================================
网站性能测试
一、不同视角下的网站性能
1、用户视角:直观视觉感受
2、开发人员视角:响应延迟、吞吐量、并发处理能力等
3、运维人员视角:基础设置资源利用率
二、性能测试指标
1、响应时间:请求从发出到处理完接收的时间
2、并发数:同时处理请求的数量,即同时提交请求的用户数
3、吞吐量:单位时间内处理请求的数量
*:随着并发数增大:系统吞吐量先逐渐增加到极限,之后反而下降;系统响应时间先是小幅上升,当吞吐量达到极限后快速上升
4、性能计数器:服务器监控指标,如CPU、内存、磁盘IO、网络IO
三、性能测试(压测)方法
*:不断增加系统访问压力(并发请求数),以获取系统性能指标数据
四、性能测试(压测)报告
*:要能够反应压测的系统性能曲线规律,阅读者能评估系统性能是否能满足需求
// ===========================================================
Web前端性能优化
一、浏览器访问优化
1、减少http请求:每次独立的http请求的通信和服务开销都很昂贵,可通过合并CSS、JS、图片等方式减少http请求数
2、使用浏览器缓存:通过设置http头的Cache-Control和Expires属性设定浏览器缓存,将CSS、JS、图片等较少变更的资源缓存下来
3、启用压缩:文件压缩可减少通讯传输的数据量,文本压缩率可达80%以上,但压缩解压会增加计算压力(权衡)
4、CSS渲染放最上面,JS功能脚本放最下面:使用户视觉感受先已经看到页面
二、CND加速
*:部署在网络运营商机房,用户请求路由的第一条就到达CND服务器,有效降低请求时间
三、反向代理
*:部署在网站机房内,用户请求先到达反向代理服务器,有3个主要功能
1、加速Web请求:通过配置缓存功能来实现
2、安全:在用户请求和应用服务器间建立一个屏障
3、负载均衡:均匀分发请求到应用服务器
// ===========================================================
应用服务器性能优化
一、分布式缓存(memcache)
*:网站性能优化第一定律:优先考虑缓存
1、缓存的基本原理:本质为内存Hash表
*:数据以Key、Value对形式存储在内存Hash表中。通过Hash(Key)得到HashCode,即Value对应内存的位置
2、合理使用缓存
*:不适合缓存的数据:频繁修改(写导致缓存中的数据失效)、没有热点、一致性要求高(缓存设有失效时间,这段时间内可能有脏数据)
*:缓存预热:缓存刚起来时为空数据,最好在使用前预加载数据库数据
*:缓存雪崩:当缓存服务器崩溃时,所有请求会落到数据库导致数据库宕机。好的方法是使用分布式缓存服务器提高缓存可用性
*:缓存穿透:不正确或者恶意的请求可能落在某个不存在的Key导致频繁读数据库,一个简单对策将不存在的Key也缓存起来
3、分布式缓存架构
*:一种是以JBossCache为代表的,每个缓存服务器数据相同,需同步更新的分布式缓存(很少用)
*:一种是以Memcache为代表的,每个缓存服务器数据部相同,之间不需要通信的分布式缓存。应用程序通过一致性Hash等路由算法选择具体的缓存服务器
4、Memcache的特点
*:简单的通行协议:TCP的,一套基于简单文本的自定义协议(一个命令关键字+一组命令操作数,如get <key>)
*:丰富的客户端程序:几乎支持所有主流语言(因为通信协议简单)
*:高性能的网络通信:基于Libevent,提供稳定的长连接
*:高效的内存管理::简单固定的内存空间分配,slab_class=>slab=>chunk
*:互不通信的集群架构:客户端路由算法一致性Hash更成为数据存储伸缩性架构的范例
二、异步操作
*:使用消息队列将调用异步化,可改善网站的扩展性
*:消息队列:用户请求发送给消息队列后立即返回,再由消费队列的消费者进程将消息异步写入数据库,具有很好的削峰作用
三、使用集群
*:利用集群解决高并发问题,前端用负载均衡技术将请求均匀分发到多台服务器上(不单单局限在应用服务器)
四、代码优化
1、多线程
*:线程的优点:比进程更轻量,占用更少系统资源,切换代价更小
*:使用多线程的2个主要原因:IO阻塞(阻塞时可以调用其他线程处理)和多CPU(最大限度使用CPU)
*:线程数估算公式:启动线程数 = [ 任务执行时间 / ( 任务执行时间 - IO等待时间 ) ] * CPU核数
*:线程安全问题实质:多线程并发对某块内存进行修改操作(对象、内存、文件、数据库等)
*:线程安全问题解决思路:对象设计为无状态,使用局部变量,并发访问加锁等
2、资源复用
*:开销较大的系统资源:数据库连接,网络Socket连接,线程,复杂对象等
*:资源复用的2个方法:单例模式和对象池,都可以防止不必要的创建和销毁操作
3、数据结构和算法
*:灵活组合数据结构和算法优化程序执行复杂度,如Hash等
// ===========================================================
存储性能优化
一、机械硬盘和固态硬盘
1、机械硬盘:每次访问数据都需要移动磁头臂(物理运动),故数据连续访问和随机访问性能表现差别大
2、固态硬盘:没有机械装置,数据存储于硅晶体中,有更好的性能。可靠性,性价比还有待提升,但逐步取代机械硬盘是迟早的事
二、B+树和LSM树
1、由于机械硬盘具有快速顺序读写,慢速随机读写的特性,故应用程序选择存储结构和算法极为重要
2、B+树是一种专门针对磁盘存储而优化的N叉排序树,目前数据库多采用两级索引,树的层次最多三层
3、LSM树可以看为一个N阶合并树,数据写操作都在内存中进行,目前许多NoSQL都采用LSM树作为主要数据结构
三、RAID和HDFS
1、RAID,即廉价磁盘冗余阵列,主要是为了改善磁盘访问延迟,增加磁盘可用性和容错能力(数据在多块磁盘并发读写和数据备份)
2、HDFS,即分布式文件系统,Hadoop的文件系统,系统在整个存储集群的多台服务器上进行数据并发读写和备份
使用GroboUtils进行简单并发
单元测试,实现测试和监控和单个线程执行的控制,这里展示简单案例的测试过程:
1、建立要测试的线程TestRunnable1
/** * */ package com.dtsz.groboTest; import net.sourceforge.groboutils.junit.v1.TestRunnable; /** * @author xiaoli * */ public class TestRunnable1 extends TestRunnable { private int i; private long sleepTime; public TestRunnable1(int i ,long sleepTime) { super(); this.i = i; this.sleepTime = sleepTime; } /* (non-Javadoc) * @see net.sourceforge.groboutils.junit.v1.TestRunnable#runTest() */ @Override public void runTest() throws Throwable { // TODO Auto-generated method stub System.out.println(i+"线程正在跑…………"); this.delay(sleepTime); System.out.println(i+"线程正要走完…………"); } } |
2、建立监控的线程,每个监控对应一个线程,也可以一个监控监控整个测试过程,需要传入监控的线程对象。
/** * */ package com.dtsz.groboTest; import net.sourceforge.groboutils.junit.v1.TestMonitorRunnable; import net.sourceforge.groboutils.junit.v1.TestRunnable; /** * @author xiaoli * */ public class TestMonitorRunnable1 extends TestMonitorRunnable { private int i; private TestRunnable t; public TestMonitorRunnable1(int i,TestRunnable t) { super(); this.i = i; this.t = t; } /* (non-Javadoc) * @see net.sourceforge.groboutils.junit.v1.TestMonitorRunnable#runMonitor() */ @Override public void runMonitor() throws Throwable { System.out.println(i+"线程监控正在跑…………状态:"+t.isDone()); } } |
3、建立主测试类进行并发单元测试,这里只有简单数据打印,具体情况传入数据进行测试,比如Web项目中需要在setUp()中部署好相关的环境等:
/** * */ package com.dtsz.groboTest; import junit.framework.TestCase; import net.sourceforge.groboutils.junit.v1.MultiThreadedTestRunner; import net.sourceforge.groboutils.junit.v1.TestMonitorRunnable; import net.sourceforge.groboutils.junit.v1.TestRunnable; import org.junit.Test; /** * @author xiaoli * */ public class MainTest1 extends TestCase{ @Override protected void setUp() throws Exception { // TODO Auto-generated method stub super.setUp(); System.out.println("setUp()数据准备"); } @Override protected void tearDown() throws Exception { // TODO Auto-generated method stub super.tearDown(); System.out.println("tearDown()结束"); } @Test public void test1() throws Throwable { int count = 2; long time = 0; TestRunnable[] tr = new TestRunnable[count]; TestMonitorRunnable [] trm = new TestMonitorRunnable[count]; for(int i = 0;i<count;i++) { TestRunnable1 t = new TestRunnable1(i,(i+1)*time); TestMonitorRunnable1 m = new TestMonitorRunnable1(i,t); tr[i] = t; trm[i] = m; } MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(tr,trm); //没有在该时间内完成的线程将会被杀掉 mttr.runTestRunnables(); } } |
测试结果:监控器每隔几个毫秒会进行实时运行,知道整个单元测试结束:
setUp()数据准备
1线程监控正在跑…………状态:false
0线程监控正在跑…………状态:false
0线程正在跑…………
1线程正在跑…………
1线程监控正在跑…………状态:false
0线程监控正在跑…………状态:false
0线程正要走完…………
1线程正要走完…………
1线程监控正在跑…………状态:false
0线程监控正在跑…………状态:false
1线程监控正在跑…………状态:false
0线程监控正在跑…………状态:false
1线程监控正在跑…………状态:false
0线程监控正在跑…………状态:false
1线程监控正在跑…………状态:false
0线程监控正在跑…………状态:true
1线程监控正在跑…………状态:true
tearDown()结束
具体功能可以由这个引申出来。
相关文章:
注入往往是应用程序缺少对输入进行安全性检查所引起的,攻击者把一些包含指令的数据发送给解释器,解释器会把收到的数据转换成指令执行。常见的注入包括
SQL注入,OS Shell,LDAP,Xpath,Hibernate等等,而其中SQL注入尤为常见。这种攻击所造成的后果往往很大,一般整个
数据库的信息都能被读取或篡改,通过SQL注入,攻击者甚至能够获得更多的包括管理员的权限。
先来说说sql注入漏洞是怎么产生的,或者说对于一个程序开发人员应该怎么防范SQL注入的吧。
SQL注入往往是在编写包含用户输入的动态数据库查询时产生的,但其实防范SQL注入的方法非常简单。程序员只不再写动态查询,或防止用户输入包含能够破坏查询逻辑的恶意SQL语句,就能够防范SQL注入。当然,我作为一个
测试人员说起来很容易,做起来就难了。
我本人只会java,所以后边我就用Java代码作为示例: <font face="宋体" color="#000000">String query ="SELECT account_balance FROM user_data WHERE user_name =" + request.getParameter("customerName"); try { Statement statement = connection.createStatement( …); ResultSet results = Statement.executeQuery(query); }</font> |
在以上代码中,我们可以看到并未对变量customerName做验证,customerName的值可以直接附在query语句的后面传送到数据库执行,那么攻击者可以将任意的sql语句注入。
上边说到怎么产生的,我们接着说怎么样才能让sql注入的漏洞避免。本来计划把这一段放在如何测试之后再介绍,但是貌似先介绍出来更方便理解。
防范方法一 :参数化查询
参数化查询是所有开发人员在做数据库查询时首先需要
学习的,参数化查询迫使所有开发者首先要定义好所有的SQL代码,然后再将每个参数逐个传入,这种编码风格就能够让数据库辨明代码和数据。
参数化查询能够确保攻击者无法改变查询的内容,在下面修正过的例子中,如果攻击者输入了UsrID是“’or ‘1 ‘=’1”,参数化查询会去查找一个完全满足名字为‘or ‘1 ‘=’ 1的用户。
对于不同编程语言,有一些不同的建议:
Java——使用带绑定变量的PreparedStatement();
其他的:。。。不好意思,真不会
示例:
<font face="宋体" color="#000000">String custname = request.getParameter("customerName"); String query ="SELECT account_balance FROM user_data WHERE user_name= ?"; PreparedStatement pstmt = connection.prepareStatement(query); Pstmt.setString1,custname(); ResultSet results = pstmt.executeQuery();</font> |
防范方法二:存储过程
存储过程和参数化查询的作用是一样的,唯一的不同在于存储过程是预先定义并存放在数据库中,从而被应用程序调用的。
Java存储过程示例:
String custname = request.getParameter("customerName"); try { CallableStatement cs = connection.prepareCall("call sp_getAccountBalance(?)}"); cs.setString(1,custname); Result results = cs.executeQuery(); }catch(SQLException se){ //error handling } |
Netperf是一种网络性能的测量工具,主要针对基于TCP 或UDP的传输。Netperf根据应用的不同,可以进行不同模式的网络
性能测试,即批量数据传输(bulk data transfer)模式和请求/应答(request/reponse)模式。Netperf测试结果所反映的是两个系统之间发送和接受数据的速度和效率。
Netperf工具是基于C/S模式的。
server端是netserver,用来侦听来自client端的连接,client 端是netperf,用来向server发起网络测试。在client与server之间,首先建立一个控制连接,传递有关测试配置的信息,以及测试的结 果;在控制连接建立并传递了测试配置信息以后,client与server之间会再建立一个测试连接,用来来回传递着特殊的流量模式,以测试网络的性能。
使用场景:一些通信产品的网络环境测试
测量网络性能的五项指标是:
可用性(availability)
响应时间(response time)
网络利用率(network utilization)
网络吞吐量(network throughput)
网络带宽容量(network bandwidth capacity)
ava语言的一个优点就是取消了指针的概念,但也导致了许多程序员在编程中常常忽略了对象与引用的区别,特别是先学c、c++后学java的程序员。并且由于Java不能通过简单的赋值来解决对象复制的问题,在开发过程中,也常常要要应用clone()方法来复制对象。比如函数参数类型是自定义的类时,此时便是引用传递而不是值传递。以下是一个小例子:
public class A { public String name; } public class testClone { public void changeA(A a){ a.name="b"; } public void changInt(int i){ i=i*2+100; } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub testClone test=new testClone(); A a=new A(); a.name="a"; System.out.println("before change : a.name="+a.name); test.changeA(a); System.out.println("after change : a.name="+a.name); int i=1; System.out.println("before change : i="+i); test.changInt(i); System.out.println("after change : i="+i); } } |
此时输出的结果是:
before change : a.name=a after change : a.name=b before change : i=1 after change : i=1 |
从这个例子知道Java对对象和基本的数据类型的处理是不一样的。在Java中用对象的作为入口参数的传递则缺省为"引用传递",也就是说仅仅传递了对象的一个"引用",这个"引用"的概念同C语言中的指针引用是一样的。当函数体内部对输入变量改变时,实质上就是在对这个对象的直接操作。
除了在函数传值的时候是"引用传递",在任何用"="向对象变量赋值的时候都是"引用传递",如:
A a1=new A(); A a2=new A(); a1.name="a1"; a2=a1; a2.name="a2"; System.out.println("a1.name="+a1.name); System.out.println("a2.name="+a2.name); |
此时输出的结果是:
a1.name=a2
a2.name=a2
如果我们要用a2保存a1对象的数据,但又不希望a2对象数据被改变时不影响到a1。实现clone()方法是其一种最简单,也是最高效的手段。
下面我们来实现A的clone方法
public class A implements Cloneable { public String name; public Object clone() { A o = null; try { o = (A) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return o; } } |
首先要实现Cloneable接口,然后在重载clone方法,最后在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调用了java.lang.Object类的clone()方法。
A a1=new A(); A a2=new A(); a1.name="a1"; a2=a1; a2.name="a2"; System.out.println("a1.name="+a1.name); System.out.println("a2.name="+a2.name); |
此时输出的结果是:
a1.name=a1
a2.name=a2
当Class A成员变量类型是java的基本类型时(外加String类型),只要实现如上简单的clone(称影子clone)就可以。但是如果Class A成员变量是数组或复杂类型时,就必须实现深度clone。
public class A implements Cloneable { public String name[]; public A(){ name=new String[2]; } public Object clone() { A o = null; try { o = (A) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return o; } } |
测试代码
A a1=new A(); A a2=new A(); a1.name[0]="a"; a1.name[1]="1"; a2=(A)a1.clone(); a2.name[0]="b"; a2.name[1]="1"; System.out.println("a1.name="+a1.name); System.out.println("a1.name="+a1.name[0]+a1.name[1]); System.out.println("a2.name="+a2.name); System.out.println("a2.name="+a2.name[0]+a2.name[1]); |
输出结果:
a1.name=[Ljava.lang.String;@757aef
a1.name=b1
a2.name=[Ljava.lang.String;@757aef
a2.name=b1
看到了吧,a1.name,a2.name的hash值都是@757aef,也就是说影子clone对name数组只是clone他们的地址!解决该办法是进行深度clone。
public Object clone() { A o = null; try { o = (A) super.clone(); o.name=(String[])name.clone();//其实也很简单^_^ } catch (CloneNotSupportedException e) { e.printStackTrace(); } return o; } |
此时输出结果是:
a1.name=[Ljava.lang.String;@757aef
a1.name=a1
a2.name=[Ljava.lang.String;@d9f9c3
a2.name=b1
需要注意的是Class A存在更为复杂的成员变量时,如Vector等存储对象地址的容器时,就必须clone彻底。
public class A implements Cloneable { public String name[]; public Vector<B> claB; public A(){ name=new String[2]; claB=new Vector<B>(); } public Object clone() { A o = null; try { o = (A) super.clone(); o.name==(String[])name.clone();//深度clone o.claB=new Vector<B>();//将clone进行到底 for(int i=0;i<claB.size();i++){ B temp=(B)claB.get(i).clone();//当然Class B也要实现相应clone方法 o.claB.add(temp); } } catch (CloneNotSupportedException e) { e.printStackTrace(); } return o; } } |
SQL语句在
数据库中处理过程是怎样的呢?执行顺序呢?在回答这个问题前,我们先来回顾一下:在ORACLE数据库架构下,SQL语句由用户进程产生,然后传到相对应的服务端进程,之后由服务器进程执行该SQL语句,如果是SELECT语句,服务器进程还需要将执行结果回传给用户进程。
SQL语句的执行过程一般如下:
解析(PARSE)—— 绑定(BIND)——执行(EXECUTE)——提取(FETCH 只有SELECT才需要这步)
解析
服务器进程接收到一个SQL语句时,首先要将其转换成执行这个SQL语句的最有效步骤,这些步骤被称为执行计划。
Step 1:检查共享池中是否有之前解析相同的SQL语句后所存储的SQL文本、解析树和执行计划。如果能从共享池的缓存库中找到之前解析过生成的执行计划,则SQL语句则不需要再次解析,便可以直接由库缓存得到之前所产生的执行计划,从而直接跳到绑定或执行阶段,这种解析称作软解析。
但是如果在共享池的库缓存中找不到对应的执行计划,则必须继续解析SQL、生成执行计划,这种解析称作硬解析
在缓存池解析过的SQL,会有一个对应的哈希值与之对应,你可以通过V$SQL视图来查询,请看下面一个例子:
SQL>SELECT * FROM SCOTT.DEPT WHERE DEPTNO =10; SQL>SELECT * FROM SCOTT.DEPT WHERE DEPTNO =20; SQL> SELECT HASH_VALUE , ADDRESS, EXECUTIONS ,SQL_TEXT FROM V$SQL WHERE SQL_TEXT LIKE 'SELECT * FROM SCOTT.DEPT WHERE DEPTNO%' ; HASH_VALUE ADDRESS EXECUTIONS SQL_TEXT ---------- -------- ---------- -------------------------------------------------------------------------------- 442836625 27EE4B7C 1 SELECT * FROM SCOTT.DEPT WHERE DEPTNO =20 4215405494 27EEA3BC 1 SELECT * FROM SCOTT.DEPT WHERE DEPTNO =10 |
下面我们先清空共享池缓存的执行计划,然后使用绑定变量,查看执行计划的变换
SQL> ALTER SYSTEM FLUSH SHARED_POOL; System altered SQL> VARIABLE deptno NUMBER; SQL> EXECUTE :deptno := 10; PL/SQL procedure successfully completed deptno --------- 10 SQL> SELECT * FROM SCOTT.DEPT WHERE DEPTNO=:deptno; DEPTNO DNAME LOC ------ -------------- ------------- 10 ACCOUNTING NEW YORK SQL> EXECUTE :deptno :=20; PL/SQL procedure successfully completed deptno --------- 20 SQL> SELECT * FROM SCOTT.DEPT WHERE DEPTNO=:deptno; DEPTNO DNAME LOC ------ -------------- ------------- 20 RESEARCH DALLAS SQL> SELECT HASH_VALUE , ADDRESS, EXECUTIONS ,SQL_TEXT 2 FROM V$SQL 3 WHERE SQL_TEXT LIKE ' SELECT * FROM SCOTT.DEPT WHERE DEPTNO%'; HASH_VALUE ADDRESS EXECUTIONS SQL_TEXT ---------- -------- ---------- -------------------------------------------------------------------------------- 3669302979 27D2BA1C 2 SELECT * FROM SCOTT.DEPT WHERE DEPTNO=:deptno |
Step 2:语法分析,分析SQL语句的语法是否符合规范,衡量语句中各表达式的意义
Step 3:检查是否存在语义错误和权限。语义分析,检查语句中设计的所有数据库对象是否存在,且用户有相应的权限。
Step 4:视图转换和表达式转换 将涉及视图的查询语句转换为相应的对基表查询语句。将复杂表达式转化较为简单的等效连接表达式。
Step 5:决定最佳执行计划。优化器会生成多个执行计划,在按统计信息带入,找出执行成本最小的执行计划,作为执行此SQL语句的执行计划
Step 6:将SQL文本、解析树、执行计划缓存到库缓存,存放地址以及SQL语句的哈希值。
绑定
如果SQL语句中使用了绑定变量,扫描绑定变量的声明,给绑定变量赋值。则此时将变量值带入执行计划。
执行
此阶段按照执行计划执行SQL,产生执行结果。不同类型的SQL语句,执行过程也不同。
SELECT查询
检查所需的数据块是否已经在缓冲区缓存中,如果已经在缓冲区缓存中,直接读取器内容即可。这种读取方式称为逻辑读取。如果所需数据不在缓冲区缓存中,则服务器进程需要先扫描数据块,读取相应数据块到缓冲区缓存,这种读取方式称为物理读。和逻辑读相比较,它更加耗费CPU和IO资源。
修改操作(INSERT、UPDATE、DELETE)
Step 1:检查所需的数据库是否已经被读取到缓冲区缓存中。如果已经存在缓冲区缓存,则执行Step 3
Step 2:若所需的数据库并不在缓冲区缓存中,则服务器将数据块从数据文件读取到缓冲区缓存中
Step 3:对想要修改的表取得的数据行锁定(Row Exclusive Lock),之后对所需要修改的数据行取得独占锁
Step 4:将撤销数据的Redo记录复制到日志缓冲区,产生数据行的撤销数据,将数据行修改的Redo记录复制到日志缓冲区,修改数据行。
Step 5: 产生数据修改的撤销数据
Step 6:复制数据修改的Redo记录到日志缓冲区
Step 7:修改数据行的内容,如果之前的缓冲为干净缓冲,则此时将变为脏缓冲。
提取
提取只有SELECT查询语句才有的步骤。获取查询的记录行,必要的时候对查询结果排序。