发布新日志

  • C#对Java

    2007-09-08 14:13:19

     

     

    C#对Java

    • 作者:佚名    来源:不详    

    华山论剑:C#对Java
    (仙人掌工作室 2001年06月29日 17:01)

    C#(C-Sharp)是Microsoft的新编程语言,被誉为“C/C++家族中第一种面向组件的语言”。然而,不管它自己宣称的是什么,许多人认为C#更像是Java的一种克隆,或者是Microsoft用来替代Java的产品。事实是否是这样的呢?

      本文的比较结果表明,C#不止是Java的同胞那么简单。如果你是一个Java开发者,想要学习C#或者了解更多有关C#的知识,那么本文就是你必须把最初10分钟投入于其中的所在。

    一、C#、C++和Java

    C#的语言规范由Microsoft的Anders Hejlsberg与Scott Wiltamuth编写。在当前Microsoft天花乱坠的宣传中,对C#和C++、Java作一番比较总是很有趣的。考虑到当前IT媒体的舆论倾向,如果你早就知道C#更接近Java而不是C++,事情也不值得大惊小怪。对于刚刚加入这场讨论的读者,下面的表1让你自己作出判断。显然,结论应该是:Java和C#虽然不是孪生子,但C#最主要的特色却更接近Java而不是C++。

    表1:比较C#、C++和Java最重要的功能
    功能 C# C++ Java
    继承 允许继承单个类,允许实现多个接口 允许从多个类继承 允许继承单个类,允许实现多个接口
    接口实现 通过“interface”关键词 通过抽象类 通过“interface”关键词
    内存管理 由运行时环境管理,使用垃圾收集器 需要手工管理 由运行时环境管理,使用垃圾收集器
    指针 支持,但只在很少使用的非安全模式下才支持。通常以引用取代指针 支持,一种很常用的功能。 完全不支持。代之以引用。
    源代码编译后的形式 .NET中间语言(IL) 可执行代码 字节码
    单一的公共基类
    异常处理 异常处理 返回错误 异常处理。

    了解表1总结的重要语言功能之后,请继续往下阅读,了解C#和Java的一些重要区别。

    二、语言规范的比较

    2.1、简单数据类型

    简单数据类型(Primitive)在C#中称为值类型,C#预定义的简单数据类型比Java多。例如,C#有unit,即无符号整数。表2列出了所有C#的预定义数据类型:

    表2:C#中的值类型
    类型 说明
    object 所有类型的最终极的基类
    string 字符串类型;字符串是一个Unicode字符的序列
    sbyte 8位带符号整数
    short 16位带符号整数
    int 32位带符号整数
    long 64位带符号整数
    byte 8位无符号整数
    ushort 16位无符号整数
    uint 32位无符号整数
    ulong 64位无符号整数
    float 单精度浮点数类型
    double 双精度浮点数类型
    bool 布尔类型;bool值或者是true,或者是false
    char 字符类型;一个char值即是一个Unicode字符
    decimal 有28位有效数字的高精度小数类型

    2.2、常量

    忘掉Java中的static final修饰符。在C#中,常量可以用const关键词声明。

    public const int x = 55;

    此外,C#的设计者还增加了readonly关键词。如果编译器编译时未能确定常量值,你可以使用readonly关键词。readonly域只能通过初始化器或类的构造函数设置。

    2.3、公用类的入口点

    在Java中,公用类的入口点是一个名为main的公用静态方法。main方法的参数是String对象数组,它没有返回值。在C#中,main方法变成了公用静态方法Main(大写的M),Main方法的参数也是一个String对象数组,而且也没有返回值,如下面的原型声明所示:

    public static void Main(String[] args)

    但是,C#的Main方法不局限于此。如果不向Main方法传递任何参数,你可以使用上述Main方法的一个重载版本,即不带参数列表的版本。也就是说,下面的Main方法也是一个合法的入口点:

    public static void Main()

    另外,如果你认为有必要的话,Main方法还可以返回一个int。例如,下面代码中的Main方法返回1:

    using System;
    public class Hello {
    public static int Main() {
    Console.WriteLine("Done");
    return 1;
    }
    }

    与此相对,在Java中重载main方法是不合法的。

    2.4、switch语句

    在Java中,switch语句只能处理整数。但C#中的switch语句不同,它还能够处理字符变量。请考虑下面用switch语句处理字符串变量的C#代码:

    using System;
    public class Hello {
    public static void Main(String[] args) {
    switch (args[0]) {
    case "老板":
    Console.WriteLine("早上好!我们随时准备为您效劳!");
    break;
    case "雇员":
    Console.WriteLine("早上好!你可以开始工作了!");
    break;
    default:
    Console.WriteLine("早上好!祝你好运!");
    break;
    }
    }
    }

    与Java中的switch不同,C#的switch语句要求每一个case块或者在块的末尾提供一个break语句,或者用goto转到switch内的其他case标签。

    2.5、foreach语句

    foreach语句枚举集合中的各个元素,为集合中的每一个元素执行一次代码块。请参见下面的例子。

    using System;
    public class Hello {
    public static void Main(String[] args) {
    foreach (String arg in args)
    Console.WriteLine(arg);
    }
    }

    如果在运行这个执行文件的时候指定了参数,比如“Hello Peter Kevin Richard”,则程序的输出将是下面几行文字:

    Peter
    Kevin
    Richard

    2.6、C#没有>>>移位操作符

    C#支持uint和ulong之类的无符号变量类型。因此,在C#中,右移操作符(即“>>”)对于无符号变量类型和带符号变量类型(比如int和long)的处理方式不同。右移uint和ulong丢弃低位并把空出的高位设置为零;但对于int和long类型的变量,“>>”操作符丢弃低位,同时,只有当变量值是正数时,“>>”才把空出的高位设置成零;如果“>>”操作的是一个负数,空出的高位被设置成为1。

    Java中不存在无符号的变量类型。因此,我们用“>>>”操作符在右移时引入负号位;否则,使用“>>”操作符。

    2.7、goto关键词

    Java不用goto关键词。在C#中,goto允许你转到指定的标签。不过,C#以特别谨慎的态度对待goto,比如它不允许goto转入到语句块的内部。在Java中,你可以用带标签的语句加上break或continue取代C#中的goto。

    2.8、声明数组

    在Java中,数组的声明方法非常灵活,实际上有许多种声明方法都属于合法的方法。例如,下面的几行代码是等价的:

    int[] x = { 0, 1, 2, 3 };
    int x[] = { 0, 1, 2, 3 };

    但在C#中,只有第一行代码合法,[]不能放到变量名字之后。

    2.9、包

    在C#中,包(Package)被称为名称空间。把名称空间引入C#程序的关键词是“using”。例如,“using System;”这个语句引入了System名称空间

    然而,与Java不同的是,C#允许为名称空间或者名称空间中的类指定别名:

    using TheConsole = System.Console;
    public class Hello {
    public static void Main() {
    TheConsole.WriteLine("使用别名");
    }
    }

    虽然从概念上看,Java的包类似于.NET的名称空间。然而,两者的实现方式不同。在Java中,包的名字同时也是实际存在的实体,它决定了放置.java文件的目录结构。在C#中,物理的包和逻辑的名称之间是完全分离的,也就是说,名称空间的名字不会对物理的打包方式产生任何影响。在C#中,每一个源代码文件可以从属于多个名称空间,而且它可以容纳多个公共类。

    .NET中包的实体称为程序集(Assembly)。每一个程序集包含一个manifest结构。manifest列举程序集所包含的文件,控制哪些类型和资源被显露到程序集之外,并把对这些类型和资源的引用映射到包含这些类型与资源的文件。程序集是自包含的,一个程序集可以放置到单一的文件之内,也可以分割成多个文件。.NET的这种封装机制解决了DLL文件所面临的问题,即臭名昭著的DLL Hell问题。

    2.10、默认包

    在Java中,java.lang包是默认的包,它无需显式导入就已经自动包含。例如,要把一些文本输出到控制台,你可以使用下面的代码:

    System.out.println("Hello world from Java");

    C#中不存在默认的包。如果要向控制台输出文本,你使用System名称空间Console对象的WriteLine方法。但是,你必须显式导入所有的类。代码如下:

    using System;
    public class Hello {
    public static void Main() {
    Console.WriteLine("Hello world from C#");
    }
    }

    2.11、面向对象

    Java和C#都是完全面向对象的语言。在面向对象编程的三大原则方面,这两种语言接近得不能再接近。

    • 继承:这两种语言都支持类的单一继承,但类可以实现多个接口。所有类都从一个公共的基类继承。
    • 封装与可见性:无论是在Java还是C#中,你都可以决定类成员是否可见。除了C#的internal访问修饰符之外,两者的可见性机制非常相似。
    • 多态性:Java和C#都支持某些形式的多态性机制,且两者实现方法非常类似。

    2.12、可访问性

    类的每个成员都有特定类型的可访问性。C#中的访问修饰符与Java中的基本对应,但多出了一个internal。简而言之,C#有5种类型的可访问性,如下所示:

    • public:成员可以从任何代码访问。
    • protected:成员只能从派生类访问。
    • internal:成员只能从同一程序集的内部访问。
    • protected internal:成员只能从同一程序集内的派生类访问。
    • private:成员只能在当前类的内部访问。

    2.13、派生类

    在Java中,我们用关键词“extends”实现继承。C#采用了C++的类派生语法。例如,下面的代码显示了如何派生父类Control从而创建出新类Button:

    public class Button: Control { . . }

    2.14、最终类

    由于C#中不存在final关键词,如果想要某个类不再被派生,你可以使用sealed关键词,如下例所示:

    sealed class FinalClass { . . }

    2.15、接口

    接口这个概念在C#和Java中非常相似。接口的关键词是interface,一个接口可以扩展一个或者多个其他接口。按照惯例,接口的名字以大写字母“I”开头。下面的代码是C#接口的一个例子,它与Java中的接口完全一样:

    interface IShape { void Draw(); }

    扩展接口的语法与扩展类的语法一样。例如,下例的IRectangularShape接口扩展IShape接口(即,从IShape接口派生出IRectangularShape接口)。

    interface IRectangularShape: IShape { int GetWidth(); }

    如果你从两个或者两个以上的接口派生,父接口的名字列表用逗号分隔,如下面的代码所示:

    interface INewInterface: IParent1, IParent2 { }

    然而,与Java不同,C#中的接口不能包含域(Field)。

    另外还要注意,在C#中,接口内的所有方法默认都是公用方法。在Java中,方法声明可以带有public修饰符(即使这并非必要),但在C#中,显式为接口的方法指定public修饰符是非法的。例如,下面的C#接口将产生一个编译错误。

    interface IShape { public void Draw(); }

    2.16、is和as操作符

    C#中的is操作符与Java中的instanceof操作符一样,两者都可以用来测试某个对象的实例是否属于特定的类型。在Java中没有与C#中的as操作符等价的操作符。as操作符与is操作符非常相似,但它更富有“进取心”:如果类型正确的话,as操作符会尝试把被测试的对象引用转换成目标类型;否则,它把变量引用设置成null。

    为正确理解as操作符,首先请考虑下面这个例子中is操作符的运用。这个例子包含一个IShape接口,以及两个实现了IShape接口的类Rectangle和Circle。

    using System;
    interface IShape {
    void draw();
    }
    public class Rectangle: IShape {
    public void draw() {
    }
    public int GetWidth() {
    return 6;
    }
    }
    public class Circle: IShape {
    public void draw() {
    }
    public int GetRadius() {
    return 5;
    }
    }
    public class LetsDraw {
    public static void Main(String[] args) {
    IShape shape = null;
    if (args[0] == "rectangle") {
    shape = new Rectangle();
    }
    else if (args[0] == "circle") {
    shape = new Circle();
    }
    if (shape is Rectangle) {
    Rectangle rectangle = (Rectangle) shape;
    Console.WriteLine("Width : " + rectangle.GetWidth());
    }
    if (shape is Circle) {
    Circle circle = (Circle) shape;
    Console.WriteLine("Radius : " + circle.GetRadius());
    }
    }
    }

    编译好代码之后,用户可以输入“rectangle”或者“circle”作为Main方法的参数。如果用户输入的是“circle”,则shape被实例化成为一个Circle类型的对象;反之,如果用户输入的是“rectangle”,则shape被实例化成为Rectangle类型的对象。随后,程序用is操作符测试shape的变量类型:如果shape是一个矩形,则shape被转换成为Rectangle对象,我们调用它的GetWidth方法;如果shape是一个圆,则shape被转换成为一个Circle对象,我们调用它的GetRadius方法。

    如果使用as操作符,则上述代码可以改成如下形式:

    using System;
    interface IShape {
    void draw();
    }
    public class Rectangle: IShape {
    public void draw() {
    }
    public int GetWidth() {
    return 6;
    }
    }
    public class Circle: IShape {
    public void draw() {
    }
    public int GetRadius() {
    return 5;
    }
    }
    public class LetsDraw {
    public static void Main(String[] args) {
    IShape shape = null;
    if (args[0] == "rectangle") {
    shape = new Rectangle();
    }
    else if (args[0] == "circle") {
    shape = new Circle();
    }
    Rectangle rectangle = shape as Rectangle;
    if (rectangle != null) {
    Console.WriteLine("Width : " + rectangle.GetWidth());
    }
    else {
    Circle circle = shape as Circle;
    if (circle != null)
    Console.WriteLine("Radius : " + circle.GetRadius());
    }

    }
    }

    在上面代码的粗体部分中,我们在没有测试shape对象类型的情况下,就用as操作符把shape转换成Rectangle类型的对象。如果shape正好是一个Rectangle,则shape被转换成为Rectangle类型的对象并保存到rectangle变量,然后我们调用它的GetWidth方法。如果这种转换失败,则我们进行第二次尝试。这一次,shape被转换成为Circle类型的对象并保存到circle变量。如果shape确实是一个Circle对象,则circle现在引用了一个Circle对象,我们调用它的GetRadius方法。

    2.17、库

    C#没有自己的类库。但是,C#共享了.NET的类库。当然,.NET类库也可以用于其他.NET语言,比如VB.NET或者Jscrīpt.NET。值得一提的是StringBuilder类,它是对String类的补充。StringBuilder类与Java的StringBuffer类非常相似。

    2.18、垃圾收集

    C++已经让我们认识到手工管理内存是多么缺乏效率和浪费时间。当你在C++中创建了一个对象,你就必须手工地拆除这个对象。代码越复杂,这个任务也越困难。Java用垃圾收集器来解决这个问题,由垃圾收集器搜集不再使用的对象并释放内存。C#同样采用了这种方法。应该说,如果你也在开发一种新的OOP语言,追随这条道路是一种非常自然的选择。C#仍旧保留了C++的内存手工管理方法,它适合在速度极端重要的场合使用,而在Java中这是不允许的。

    2.19、异常处理

    如果你听说C#使用与Java相似的异常处理机制,你不会为此而惊讶,对吧?在C#中,所有的异常都从一个名为Exception的类派生(听起来很熟悉?)另外,正如在Java中一样,你还有熟悉的try和catch语句。Exception类属于.NET System名称空间的一部分。

    三、Java没有的功能

    C#出生在Java成熟之后,因此,C#拥有一些Java(目前)还没有的绝妙功能也就不足为奇。

    3.1、枚举器

    枚举器即enum类型(Enumerator,或称为计数器),它是一个相关常量的集合。精确地说,enum类型声明为一组相关的符号常量定义了一个类型名字。例如,你可以创建一个名为Fruit(水果)的枚举器,把它作为一个变量值的类型使用,从而把变量可能的取值范围限制为枚举器中出现的值。

    public class Demo {
    public enum Fruit {
    Apple, Banana, Cherry, Durian
    }
    public void Process(Fruit fruit) {
    switch (fruit) {
    case Fruit.Apple:
    ...
    break;
    case Fruit.Banana:
    ...
    break;
    case Fruit.Cherry:
    ...
    break;
    case Fruit.Durian:
    ...
    break;
    }
    }
    }

    在上例的Process方法中,虽然你可以用int作为myVar变量的类型,但是,使用枚举器Fruit之后,变量的取值范围限制到了Applet、Banana、Cherry和Durian这几个值之内。与int相比,enum的可读性更好,自我说明能力更强。

    3.2、结构

    结构(Struct)与类很相似。然而,类是作为一种引用类型在堆中创建,而结构是一种值类型,它存储在栈中或者是嵌入式的。因此,只要谨慎运用,结构要比类快。结构可以实现接口,可以象类一样拥有成员,但结构不支持继承。

    然而,简单地用结构来取代类可能导致惨重损失。这是因为,结构是以值的方式传递,由于这种传递方式要把值复制到新的位置,所以传递一个“肥胖的”结构需要较大的开销。而对于类,传递的时候只需传递它的引用。

    下面是一个结构的例子。注意它与类非常相似,只要把单词“struct”替换成“class”,你就得到了一个类。

    struct Point {
    public int x, y;
    public Point(int x, int y) {
    this.x = x;
    this.y = y;
    }
    }

    3.3、属性

    C#类除了可以拥有域(Field)之外,它还可以拥有属性(Property)。属性是一个与类或对象关联的命名的特征。属性是域的一种自然扩展――两者都是有类型、有名字的类成员。然而,和域不同的是,属性不表示存储位置;相反,属性拥有存取器(accessor),存取器定义了读取或者写入属性值时必须执行的代码。因此,属性提供了一种把动作和读取、写入对象属性值的操作关联起来的机制,而且它们允许属性值通过计算得到。

    在C#中,属性通过属性声明语法定义。属性声明语法的第一部分与域声明很相似,第二部分包括一个set过程和/或一个get过程。例如,在下面的例子中,PropertyDemo类定义了一个Prop属性。

    public class PropertyDemo {
    private string prop;
    public string Prop {
    get {
    return prop;
    }
    set {
    prop = value;
    }
    }
    }

    如果属性既允许读取也允许写入,如PropertyDemo类的Prop属性,则它同时拥有get和set存取过程。当我们读取属性的值时,get存取过程被调用;当我们写入属性值时,set存取过程被调用。在set存取过程中,属性的新值在一个隐含的value参数中给出。

    与读取和写入域的方法一样,属性也可以用同样的语法读取和写入。例如,下面的代码实例化了一个PropertyDemo类,然后写入、读取它的Prop属性。

    PropertyDemo pd = new PropertyDemo();
    pd.Prop = "123"; // set
    string s = pd.Prop; // get

    3.4、以引用方式传递简单数据类型的参数

    在Java中,当你把一个简单数据类型的值作为参数传递给方法时,参数总是以值的方式传递――即,系统将为被调用的方法创建一个参数值的副本。在C#中,你可以用引用的方式传递一个简单数据类型的值。此时,被调用的方法将直接使用传递给它的那个值――也就是说,如果在被调用方法内部修改了参数的值,则原来的变量值也随之改变。

    在C#中以引用方式传递值时,我们使用ref关键词。例如,如果编译并运行下面的代码,你将在控制台上看到输出结果16。注意i值被传递给ProcessNumber之后是如何被改变的。

    using System;
    public class PassByReference {
    public static void Main(String[] args) {
    int i = 8;
    ProcessNumber(ref i);
    Console.WriteLine(i);
    }
    public static void ProcessNumber(ref int j) {
    j = 16;
    }
    }

    C#中还有一个允许以引用方式传递参数的关键词out,它与ref相似。但是,使用out时,作为参数传递的变量在传递之前不必具有已知的值。在上例中,如果整数i在传递给ProcessNumber方法之前没有初始化,则代码将出错。如果用out来取代ref,你就可以传递一个未经初始化的值,如下面这个修改后的例子所示。

    using System;
    public class PassByReference {
    public static void Main(String[] args) {
    int i;
    ProcessNumber(out i);
    Console.WriteLine(i);
    }
    public static void ProcessNumber(out int j) {
    j = 16;
    }
    }

    经过修改之后,虽然i值在传递给ProcessNumber方法之前没有初始化,但PassByReference类能够顺利通过编译。

    3.5、C#保留了指针

    对于那些觉得自己能够恰到好处地运用指针并乐意手工进行内存管理的开发者来说,在C#中,他们仍旧可以用既不安全也不容易使用的“古老的”指针来提高程序的性能。C#提供了支持“不安全”(unsafe)代码的能力,这种代码能够直接操作指针,能够“固定”对象以便临时地阻止垃圾收集器移动对象。无论从开发者还是用户的眼光来看,这种对“不安全”代码的支持其实是一种安全功能。“不安全”的代码必须用unsafe关键词显式地标明,因此开发者不可能在无意之中使用“不安全”的代码。同时,C#编译器又和执行引擎协作,保证了“不安全”的代码不能伪装成为安全代码。

    using System;
    class UsePointer {
    unsafe static void PointerDemo(byte[] arr) {
    .
    .
    }
    }

    C#中的unsafe代码适合在下列情形下使用:当速度极端重要时,或者当对象需要与现有的软件(比如COM对象或者DLL形式的C代码)交互时。

    3.6、代理

    代理(delegate)可以看作C++或者其他语言中的函数指针。然而,与函数指针不同的是,C#中的代理是面向对象的、类型安全的、可靠的。而且,函数指针只能用来引用静态函数,但代理既能够引用静态方法,也能够引用实例方法。代理用来封装可调用方法。你可以在类里面编写方法并在该方法上创建代理,此后这个代理就可以被传递到第二个方法。这样,第二个方法就可以调用第一个方法。

    代理是从公共基类System.Delegate派生的引用类型。定义和使用代理包括三个步骤:声明,创建实例,调用。代理用delegate声明语法声明。例如,一个不需要参数且没有返回值的代理可以用如下代码声明:

    delegate void TheDelegate();

    创建代理实例的语法是:使用new关键词,并引用一个实例或类方法,该方法必须符合代理指定的特征。一旦创建了代理的实例,我们就可以用调用方法的语法调用它。

    3.7、包装和解除包装

    在面向对象的编程语言中,我们通常使用的是对象。但为了提高速度,C#也提供了简单数据类型。因此,C#程序既包含一大堆的对象,又有大量的值。在这种环境下,让这两者协同工作始终是一个不可回避的问题,你必须要有一种让引用和值进行通信的方法。

    在C#以及.NET运行时环境中,这个“通信”问题通过包装(Boxing)和解除包装(Unboxing)解决。包装是一种让值类型看起来象引用类型的处理过程。当一个值类型(简单数据类型)被用于一个要求或者可以使用对象的场合时,包装操作自动进行。包装一个value-type值的步骤包括:分配一个对象实例,然后把value-type值复制到对象实例。

    解除包装所执行的动作与包装相反,它把一个引用类型转换成值类型。解除包装操作的步骤包括:首先检查并确认对象实例确实是给定value-type的一个经过包装的值,然后从对象实例复制出值。

    Java对该问题的处理方式略有不同。Java为每一种简单数据类型提供了一个对应的类封装器。例如,用Integer类封装int类型,用Byte类封装byte类型。

    【结束语】本文为你比较了C#和Java。这两种语言很相似,然而,说C#是Java的克隆或许已经大大地言过其实。面向对象、中间语言这类概念并不是什么新东西。如果你准备设计一种面向对象的新语言,而且它必须在一个受管理的安全环境内运行,你难道不会搞出与C#差不多的东西吗?

  • apache+tomcat+mysql 负载平衡和集群『转载』

    2007-09-07 12:56:11

    摘自 http://www.wujianrong.com/archives/2007/08/apachetomcatmysql.html

    前言:
    公司开发了一个网站,估计最高在线人数是3万,并发人数最多100人。开发的网站是否能否承受这个压力,如何确保网站的负荷没有问题,经过研究决定如下:
    (1) 采用负载平衡和集群技术,初步机构采用Apache+Tomcat的机群技术。
    (2) 采用压力测试工具,测试压力。工具是Loadrunner。
    硬件环境搭建:
    为了能够进行压力测试,需要搭建一个环境。刚开始时,测试在公司局域网内进行,但很快发现了一个问题,即一个脚本的压力测试结果每次都不一样,并且差别很大。原来是受公司网络的影响,于是决定搭建一个完全隔离的局域网测试。搭建后的局域网配置如下:
    (1) 网络速度:100M
    (2) 三台服务器:
    负载服务器 :操作系统windows2003,
    Tomcat服务器:操作系统windows2000 Professional
    数据库服务器:操作系统windows2000 Professional
    三台机器的cpu 2.4 G, 内存 1G。
    软件环境搭建:
    软件的版本如下:
    Apache 版本:2.054,
    Tomcat5.0.30,
    mysql :4.1.14.
    JDK1.5
    压力测试工具:Loadrunner7.8。

    负载平衡方案如下:
    一台机器(操作系统2003)安装apache,作为负载服务器,并安装tomcat作为一个worker;一个单独安装tomcat,作为第二个worker;剩下的一台单独作为数据库服务器。
    Apache和tomcat的负载平衡采用JK1.2.14(没有采用2.0,主要是2.0不再维护了)。
    集群方案:
    采用Tomcat本身的集群方案。在server.xml配置。
    压力测试问题:
    压力测试后,发现了一些问题,现一一列出来:
    (1)采用Tocmat集群后,速度变得很慢。因为集群后,要进行session复制,导致速度较慢。Tomcatd的复制,目前不支持 application 复制。复制的作用,主要用来容错的,即一台机器有故障后,apache可以把请求自动转发到另外一个机器。在容错和速度的考虑上,我们最终选择速度,去掉 了Tomcat集群。
    (2) 操作系统最大并发用户的限制:
    为了采用网站的压力,我们开始的时候,仅测试Tomcat的最大负载数。 Tomcat服务器安装的操作系统是windows2000 Professional。当我们用压力测试工具,并发测试时,发现只要超过15个并发用户,会经常出现无法连接服务器的情况。经过研究,发现是操作系统 的问题:windows2000 Professional 支持的并发访问用户有限,默认的好像是15个。于是我们把操作系统全部采用windows2003 server版本。
    (3) 数据库连接池的问题:
    测试数据库连接性能时,发现数据库连接速度很慢。每增加一些用户,连接性能就差了很多。我们采用的数据库连接池是DBCP,默认的初始化为50个,应该不 会很慢吧。查询数据库的连接数,发现初始化,只初始化一个连接。并发增加一个用户时,程序就会重新创建一个连接,导致连接很慢。原因就在这里了。如何解决 呢?偶尔在JDK1.4下的Tomcat5.0.30下执行数据库连接压力测试,发现速度很快,程序创建数据库连接的速度也是很快的。看来JDK1.5的 JDBC驱动程序有问题。于是我们修改 JDK的版本为1.4.


     

    (4) C3P0和DBCP
    C3P0是Hibernate3.0默认的自带数据库连接池,DBCP是Apache开发的数据库连接池。我们对这两种连接池进行压力测试对比,发现在并发300个用户以下时,DBCP比C3P0平均时间快1秒左右。但在并发400个用户时,两者差不多。


     

    速度上虽然DBCP比C3P0快些,但是有BUG:当DBCP建立的数据库连接,因为某种原因断掉后,DBCP将不会再重新创建新的连接,导致必须重新启动Tomcat才能解决问题。DBCP的BUG使我们决定采用C3P0作为数据库连接池。
    调整后的方案:
    操作系统Windows2003 server版本
    JDK1.4
    Tomcat 5.0.30
    数据库连接池C3P0
    仅采用负载平衡,不采用集群。
    软件的配置:
    Apache配置:主要配置httpd.conf和新增加的文件workers.properties
    Httpd.conf:
    #一个连接的最大请求数量
    MaxKeepAliveRequests 10000
    #NT环境,只能配置这个参数来提供性能
    <IfModule mpm_winnt.c>
    #每个进程的线程数,最大1920。NT只启动父子两个进程,不能设置启动多个进程
    ThreadsPerChild 1900
    每个子进程能够处理的最大请求数
    MaxRequestsPerChild 10000
    </IfModule>


     

    # 加载mod_jk
    #
    LoadModule jk_module modules/mod_jk.so
    #
    # 配置mod_jk
    #
    JkWorkersFile conf/workers.properties
    JkLogFile logs/mod_jk.log
    JkLogLevel info
    #请求分发,对jsp文件,.do等动态请求交由tomcat处理
    DocumentRoot "C:/Apache/htdocs"
    JkMount /*.jsp loadbalancer
    JkMount /*.do loadbalancer
    JkMount /servlet/* loadbalancer
    #关掉主机Lookup,如果为on,很影响性能,可以有10多秒钟的延迟。
    HostnameLookups Off
    #缓存配置
    LoadModule cache_module modules/mod_cache.so
    LoadModule disk_cache_module modules/mod_disk_cache.so
    LoadModule mem_cache_module modules/mod_mem_cache.so


     

    <IfModule mod_cache.c>
    CacheForceCompletion 100
    CacheDefaultExpire 3600
    CacheMaxExpire 86400
    CacheLastModifiedFactor 0.1

    <IfModule mod_disk_cache.c>
    CacheEnable disk /
    CacheRoot c:/cacheroot
    CacheSize 327680
    CacheDirLength 4
    CacheDirLevels 5
    CacheGcInterval 4
    </IfModule>
    <IfModule mod_mem_cache.c>
    CacheEnable mem /
    MCacheSize 8192
    MCacheMaxObjectCount 10000
    MCacheMinObjectSize 1
    MCacheMaxObjectSize 51200
    </IfModule>
    </IfModule>
    worker. Properties文件
    #
    # workers.properties ,可以参考
    http://jakarta.apache.org/tomcat/connectors-doc/config/workers.html
    # In Unix, we use forward slashes:
    ps=


     

    # list the workers by name


     

    worker.list=tomcat1, tomcat2, loadbalancer


     

    # ------------------------
    # First tomcat server
    # ------------------------
    worker.tomcat1.port=8009
    worker.tomcat1.host=localhost
    worker.tomcat1.type=ajp13


     

    # Specify the size of the open connection cache.
    #worker.tomcat1.cachesize


     

    #
    # Specifies the load balance factor when used with
    # a load balancing worker.
    # Note:
    # ----> lbfactor must be > 0
    # ----> Low lbfactor means less work done by the worker.
    worker.tomcat1.lbfactor=900


     

    # ------------------------
    # Second tomcat server
    # ------------------------
    worker.tomcat1.port=8009
    worker.tomcat1.host=202.88.8.101
    worker.tomcat1.type=ajp13

     

    # Specify the size of the open connection cache.
    #worker.tomcat1.cachesize


    #
    # Specifies the load balance factor when used with
    # a load balancing worker.
    # Note:
    # ----> lbfactor must be > 0
    # ----> Low lbfactor means less work done by the worker.
    worker.tomcat1.lbfactor=2000


    # ------------------------
    # Load Balancer worker
    # ------------------------

     

    #
    # The loadbalancer (type lb) worker performs weighted round-robin
    # load balancing with sticky sessions.
    # Note:
    # ----> If a worker dies, the load balancer will check its state
    # once in a while. Until then all work is redirected to peer
    # worker.
    worker.loadbalancer.type=lb
    worker.loadbalancer.balanced_workers=tomcat1,tomcat2

     

    #
    # END workers.properties
    #

    Tomcat1配置:
    <!--配置server.xml
    去掉8080端口,即注释掉如下代码:-->
    <Connector
    port="8080" maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
    enableLookups="false" redirectPort="8443" acceptCount="100"
    debug="0" connectionTimeout="20000"
    disableUploadTimeout="true" />


    <!--配置8009端口如下:-->
    <Connector port="8009"
    maxThreads="500" minSpareThreads="400" maxSpareThreads="450"
    enableLookups="false" redirectPort="8443" debug="0"
    protocol="AJP/1.3" />
    <!--配置引擎-->
    <Engine name="Catalina" defaultHost="localhost" debug="0" jvmRoute="tomcat1">

     

    启动内存配置,开发configure tomcat程序即可配置:
    Initial memory pool: 200 M
    Maxinum memory pool:300M
    Tomcat2配置:
    配置和tomcat1差不多,需要改动的地方如下:
    <!--配置引擎-->
    <Engine name="Catalina" defaultHost="localhost" debug="0" jvmRoute="tomcat2">

     

    启动内存配置,开发configure tomcat程序即可配置:
    Initial memory pool: 512 M
    Maxinum memory pool:768M
    Mysql配置:
    Server类型:Dedicated MySQL Server Machine
    Database usage:Transational Database Only
    并发连接数量:Online Transaction Processing(OLTP)
    字符集:UTF8
    数据库连接池的配置:
    我们采用的是spring 框架,配置如下:
    <property name="hibernateProperties">
    <props>
    <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
    <prop key="hibernate.connection.driver_class">com.mysql.jdbc.Driver</prop>
    <prop key="hibernate.connection.url">jdbc:mysql://202.88.1.103/db</prop>
    <prop key="hibernate.connection.username">sa</prop>
    <prop key="hibernate.connection.password"></prop>

     

    <prop key="hibernate.show_sql">false</prop>
    <prop key="hibernate.use_sql_comments">false</prop>

     

    <prop key="hibernate.cglib.use_reflection_optimizer">true</prop>
    <prop key="hibernate.max_fetch_depth">2</prop>

     

    <prop key="hibernate.c3p0.max_size">200</prop>
    <prop key="hibernate.c3p0.min_size">5</prop>
    <prop key="hibernate.c3p0.timeout">12000</prop>
    <prop key="hibernate.c3p0.max_statements">50</prop>
    <prop key="hibernate.c3p0.acquire_increment">1</prop>
    </props>
    </property>
    其他的没有额外配置。
    LoadRunner 常见问题:
    (1)sofeware caused connction:这种情况,一般是脚本有问题,或者loadrunner有问题。解决方法:重新启动机器,或者重新录制脚本,估计是loadrunner的bug。
    (2)cannot connect to server:无法连接到服务器。这种情况是服务器的配置有问题,服务器无法承受过多的并发连接了。需要优化服务器的配置,
    如操作系统采用windows 2003 server,
    优化tomcat配置:maxThreads="500" minSpareThreads="400" maxSpareThreads="450"。但是tomcat 最多支持500个并发访问
    优化apache配置:
    ThreadsPerChild 1900
    MaxRequestsPerChild 10000
    其他的错误如:
    Action.c(10): Error -27791: Server has shut down the connection prematurely
    HTTP Status-Code=503 (Service Temporarily Unavailable)
    一般都是由于服务器配置不够好引起的,按照问题(2)处理,如果仍旧不行,需要优化硬件和调整程序了。
    Apache问题:
    (1) File does not exist: C:/Apache/htdocs/favicon.ico:
    这个问题是apache,htdocs目录没有favicon.ico文件引起的,该文件是网站的图标,仅在firefox,myIE等浏览器出现。
    (2) 图片无法显示:
    配置apache后,却无法显示图片。
    解决方法:把程序的图片,按照程序结构copy到apache的htdocs目录下。
    (3) 无法处理请求:
    当我们输入 ***.do 命令后,apache确返回错误信息,而连接tomcat却没有问题。原因是没有把.do命令转发给tomcat处理。解决方法如下:
    在apache配置文件中配置如下内容:
    DocumentRoot "C:/Apache/htdocs"
    JkMount /*.jsp loadbalancer
    JkMount /*.do loadbalancer



    总结:
    网站的压力测试,涉及的知识面挺广的,不仅要熟悉压力测试工具,还要知道如何配置和优化应用服务器和数据库,并且需要知道如何优化网络、操作系统、硬件系统。
    测试中不仅要善于发现问题,要知道如何解决。最重要的一点,要有良好的测试方法。刚开始测试时,可以从最简单的测试脚本入手,不需要太复杂的脚本,这样便 于发现问题。如我们刚开始时,就从一个简单的下载登陆界面的脚本入手,测试一个tomcat的压力负载。一个简单的获取登陆的脚本,帮助我们优化了 tomcat的配置;后来再测试数据库连接,也是一个简单的数据库连接脚本,帮助我们优化了数据库连接池;然后利用这些简单的脚本,测试apache的负 载平衡,优化了apache配置。最后运行复杂的脚本,模拟多种角色的用户在不同时间下的处理,以测试网站压力负载。

Open Toolbar