Проблема производительности чата JAVA MYSQL для 100 пользователей - PullRequest
0 голосов
/ 12 июля 2011

Я пытаюсь разработать клиент-серверное приложение для чата с использованием Java-сервлетов, MySQL (движок innoDB) и сервера Jetty. я проверил код подключения с 100 симулированными пользователями, которые одновременно подключались к серверу с использованием jmeter, но я получил 40 с как среднее время :( для всех из них, чтобы соединиться с минимальным временем, потраченным потоком (2 с), и максимальное время (80 секунд). Моя таблица базы данных соединений имеет следующую структуру: два столбца connect (пользователь, незнакомец) и мой код сервлета показан ниже. Я использую механизм innoDB для блокировки на уровне строк. Я также использовал явную блокировку записи SELECT. ..... ДЛЯ ОБНОВЛЕНИЯ внутри транзакции. Я зацикливаю транзакцию, если она откатывается из-за тупиковой ситуации, пока не выполнится хотя бы один раз. Как только два пользователя подключаются, они обновляют столбец своего незнакомца случайным образом сгенерированным уникальным номером.

Я использую пул соединений c3p0 с открытыми как минимум 100 нитями и пристань с минимальными 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 ]

2 голосов
/ 12 июля 2011

Сколько у вас пользователей?Можете ли вы сначала загрузить их в память и выполнить поиск в памяти?Если вы отделяете свой слой БД от уровня представления, это то, что вы можете изменить, не меняя сервлет (поскольку не должно заботиться, откуда поступают данные)

Если вы используете память Java, это не должно заниматьболее 20 мс на пользователя.


Вот тест, который создает один миллион профилей в памяти, просматривает их и создает записи в чате, которые впоследствии удаляются.Среднее время на операцию составило 640 нс (нано-секунд или миллиардных долей секунды)

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 голос
/ 12 июля 2011

Я не уверен, как вы можете реально ожидать, что кто-нибудь определит, где находятся узкие места, просто взглянув на исходный код.

Чтобы найти узкие места, вы должны запустить свое приложение и нагрузочный тестс прикрепленным профилировщиком, таким как JVisualVM или YourKit или JProfiler .Это скажет вам точно , сколько времени тратится в каждой области кода.

Единственная вещь, которую каждый может реально критиковать, глядя на ваш код, - это базовая архитектура:

  • Почему вы ищете источник данных в каждом doGet ()?
  • Почему вы используете транзакции для не связанных между собой вставок и запросов к базе данных?
  • Является ли использование СУБД для поддержки системы чата действительно лучшей идеей?
1 голос
/ 12 июля 2011

Рассматривали ли вы кеширование чтения и пакетную запись?

0 голосов
/ 12 июля 2011

Если время вашего ответа слишком велико, вам нужно правильно проиндексировать таблицы БД.Исходя из предоставленного вами времени, я буду считать, что это не было сделано. Вам нужно ускорить чтение и запись.

Поиск Планов выполнения и способов их чтения.План выполнения покажет вам, если / когда индексы используются с вашими запросами;если вы выполняете поиск или сканирование и т. д. на столах.используя их, вы можете настроить свой запрос / индексы / таблицы, чтобы они были более оптимальными.

Как уже говорили другие, RDMS не будет вашим лучшим вариантом в крупномасштабных приложениях, но, поскольку вы только начинаете, это должно бытьхорошо, пока вы не узнаете больше.

Научитесь правильно настраивать эти таблицы, и вы должны увидеть, как уменьшается количество взаимоблокировок и время отклика

...