与100位用户的JAVA MYSQL聊天性能问题

时间:2011-07-12 16:06:59

标签: java mysql performance jdbc transactions

我正在尝试使用java servlet和mysql(innoDB引擎)和jetty服务器开发客户端 - 服务器聊天应用程序。我测试了100个模拟用户使用jmeter立即击中服务器的连接代码,但我得到 40秒作为平均时间:(所有这些用线程连接最短时间(2秒)最大时间(80秒)。我的连接数据库表有两列连接(用户,陌生人)和我的servlet代码如下所示。我使用innoDB引擎进行行级锁定。我还使用了显式写锁定SELECT ...... FOR更新内部事务。如果事务由于死锁而回滚,直到它执行至少一次,我将循环事务。一旦两个用户连接,他们就会用彼此随机生成的唯一编号更新他们的陌生人列。

我正在使用c3p0连接池,最小100个线程打开,jetty最小100个线程。 请帮我识别找到它们所需的瓶颈或工具。

 import java.io.*;
 import java.util.*;
 import java.sql.*;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.naming.*; 
 import javax.sql.*;



public class connect extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res)
throws java.io.IOException {

String unumber=null;
String snumber=null;

String status=null;
InitialContext contxt1=null;
DataSource ds1=null;
Connection conxn1=null;
PreparedStatement stmt1=null;
ResultSet rs1=null;
PreparedStatement stmt2=null;
InitialContext contxt3=null;
DataSource ds3=null;
Connection conxn3=null;
PreparedStatement stmt3=null;
ResultSet rs3=null;
PreparedStatement stmt4=null;
ResultSet rs4=null;
PreparedStatement stmt5=null;
boolean checktransaction = true;


unumber=req.getParameter("number");       // GET THE USER's UNIQUE NUMBER 

try {
contxt1 = new InitialContext();
ds1 =(DataSource)contxt1.lookup("java:comp/env/jdbc/user");
conxn1 = ds1.getConnection(); 

stmt1 = conxn1.prepareStatement("SELECT * FROM profiles WHERE number=?");   //    GETTING USER DATA FROM PROFILE
stmt1.setString(1,unumber);
rs1 = stmt1.executeQuery();

if(rs1.next()) {
res.getWriter().println("user found in PROFILE table.........");
uage=rs1.getString("age");
usex=rs1.getString("sex");
ulocation=rs1.getString("location");
uaslmode=rs1.getString("aslmode");
stmt1.close();
stmt1=null;
conxn1.close();
conxn1 = null;

contxt3 = new InitialContext();
ds3 =(DataSource)contxt3.lookup("java:comp/env/jdbc/chat");

conxn3 = ds3.getConnection(); 
conxn3.setAutoCommit(false);

while(checktransaction) {

  // TRANSACTION STARTS HERE
try {

stmt2 = conxn3.prepareStatement("INSERT INTO "+ulocation+" (user,stranger) VALUES (?,'')");  //  INSERTING RECORD INTO LOCAL CHAT TABLE
stmt2.setString(1,unumber);
stmt2.executeUpdate();

stmt2.close();
stmt2 = null;
res.getWriter().println("inserting row into LOCAL CHAT TABLE........."); 

System.out.println("transaction starting........."+unumber);


stmt3 = conxn3.prepareStatement("SELECT user FROM "+ulocation+" WHERE (stranger='' && user!=?) LIMIT 1 FOR UPDATE");
stmt3.setString(1,unumber);                                            //   SEARCHING FOR STRANGER
  rs3=stmt3.executeQuery();

if (rs3.next()) {                // stranger found   

stmt4 = conxn3.prepareStatement("SELECT stranger FROM "+ulocation+" WHERE user=?");  
stmt4.setString(1,unumber);                      //CHECKING FOR USER STATUS BEFORE CONNECTING TO STRANGER
rs4=stmt4.executeQuery();

if(rs4.next()) {
 status=rs4.getString("stranger");
}
stmt4.close();
stmt4=null;

if(status.equals("")) {           // user status is also null
snumber = rs3.getString("user");

stmt5 = conxn3.prepareStatement("UPDATE "+ulocation+" SET stranger=? WHERE user=?"); // CONNECTING USER AND STRANGER
stmt5.setString(1,snumber);
stmt5.setString(2,unumber);
stmt5.executeUpdate();

stmt5.setString(2,snumber);
stmt5.setString(1,unumber);
stmt5.executeUpdate();

stmt5.close();
stmt5=null;
}
}         // end of stranger found

stmt3.close();
stmt3 = null;

conxn3.commit();     // TRANSACTION ENDING

checktransaction = false;
}  // END OF TRY INSIDE WHILE
catch(java.sql.SQLTransactionRollbackException e) {
System.out.println("transaction restarted......."+unumber);
counttransaction = counttransaction+1;

}
}          //END OF WHILE LOOP    
conxn3.close();                
conxn3 = null;
}        //  END OF USER FOUND IN PROFILE TABLE

}  // end of try

catch(java.sql.SQLException sqlexe) {

try {conxn3.rollback();}
catch(java.sql.SQLException exe) {conxn3=null;}
sqlexe.printStackTrace();
res.getWriter().println("UNABE TO GET CONNECTION FROM POOL!");

}
catch(javax.naming.NamingException namexe) {
namexe.printStackTrace();
res.getWriter().println("DATA SOURCE LOOK UP FAILED!");
}

}
}

4 个答案:

答案 0 :(得分:2)

你有多少用户?你可以先将它们全部加载到内存中并进行内存查找吗? 如果您将DB层与表示层分开,那么您可以在不更改servlet的情况下进行更改(因为它不应该关注数据的来源)

如果您使用Java内存,则每个用户不应超过20毫秒。


这是一个测试,它在内存中创建一百万个配置文件,查找它们并创建聊天条目,稍后将删除。每次操作的平均时间为640 ns(纳秒,或十亿分之一秒)

import java.util.LinkedHashMap;
import java.util.Map;

public class Main {
    public static void main(String... args) {
        UserDB userDB = new UserDB();
        // add 1000,000 users
        for (int i = 0; i < 1000000; i++)
            userDB.addUser(
                    new Profile(i,
                            "user+i",
                            (short) (18 + i % 90),
                            i % 2 == 0 ? Profile.Sex.Male : Profile.Sex.Female,
                            "here", "mode"));
        // lookup a users and add a chat session.
        long start = System.nanoTime();

        int operations = 0;
        for(int i=0;i<userDB.profileCount();i+=2) {
            Profile p0 = userDB.getProfileByNumber(i);
            operations++;
            Profile p1 = userDB.getProfileByNumber(i+1);
            operations++;
            userDB.chatsTo(i, i+1);
            operations++;
        }
        for(int i=0;i<userDB.profileCount();i+=2) {
            userDB.endChat(i);
            operations++;
        }
        long time = System.nanoTime() -start;
        System.out.printf("Average lookup and update time per operation was %d ns%n", time/operations);
    }
}

class UserDB {
    private final Map<Long, Profile> profileMap = new LinkedHashMap<Long, Profile>();
    private final Map<Long, Long> chatsWith = new LinkedHashMap<Long, Long>();

    public void addUser(Profile profile) {
        profileMap.put(profile.number, profile);
    }

    public Profile getProfileByNumber(long number) {
        return profileMap.get(number);
    }

    public void chatsTo(long number1, long number2) {
        chatsWith.put(number1, number2);
        chatsWith.put(number2, number1);
    }

    public void endChat(long number) {
        Long other = chatsWith.get(number);
        if (other == null) return;
        Long number2 = chatsWith.get(other);
        if (number2 != null && number2 == number)
            chatsWith.remove(other);
    }

    public int profileCount() {
        return profileMap.size();
    }
}

class Profile {
    final long number;
    final String name;
    final short age;
    final Sex sex;
    final String location;
    final String aslmode;

    Profile(long number, String name, short age, Sex sex, String location, String aslmode) {
        this.number = number;
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.location = location;
        this.aslmode = aslmode;
    }

    enum Sex {Male, Female}

}

打印

Average lookup and update time per operation was 636 ns

如果你需要这个更快,你可以看看使用Trove4j,在这种情况下可能快两倍。鉴于这可能足够快,我会尽量保持简单。

答案 1 :(得分:1)

您是否考虑过缓存读取和批处理写入?

答案 2 :(得分:1)

我不确定如何通过查看源代码来实际期望任何人确定瓶颈的位置。

要找到瓶颈,您应该使用附加的分析器运行您的应用和负载测试,例如JVisualVMYourKitJProfiler。这将告诉您完全在代码的每个区域花费了多少时间。

任何人都可以通过查看代码来真正批评的是基本架构:

  • 为什么要在每个doGet()上查找DataSource?
  • 为什么要将事务用于看似无关的数据库插入和查询?
  • 首先使用RDBMS备份聊天系统真的是最好的主意吗?

答案 3 :(得分:0)

如果响应时间过长,则需要正确索引数据库表。根据你提供的时间,我会假设没有这样做。你需要加快你的读写速度。

查找执行计划以及如何阅读它们。执行计划将显示索引是否/何时与查询一起使用;如果你在表上进行搜索或扫描等。通过使用这些,您可以调整查询/索引/表格以使其更加优化。

正如其他人所说,RDMS不会是大型应用程序中的最佳选择,但由于你刚刚开始它应该没问题,直到你了解更多。

了解如何正确设置这些表,您应该会看到死锁计数和响应时间下降