BGRadiusVoip version 5.1 build 167 from 14.06.2011 11:40:22
При большой нагрузке встаёт колом.
Начинаем разбираться:
- В innotop висят такие запросы:
Код:
SELECT zone, SUM(round_session_time) FROM log_session_22_201110 WHERE lid=2 GROUP BY zone
Руками такой запрос выполняется за 5-6 секунд. Много.
По jstack с помощью декомпилера определяем, что потоки блокируются на:
Код:
synchronized (this.itemZoneTrafficCache)
, пока один поток обновляет кэш трафиков по зонам для логина (необходимый, как я понимаю, для тарификации):
Код:
Calendar cacheTime = (Calendar)this.loginsWithZoneTrafInit.get(Integer.valueOf(loginId));
if ((cacheTime == null) || (TimeUtils.monthsDelta(time, cacheTime) != 0) ||
(time.getTimeInMillis() - cacheTime.getTimeInMillis() > 300000L))
{
try
{
String tableName = ServerUtils.getModuleMonthTableName("log_session", time.getTime(), this.mid);
if (!ServerUtils.tableExists(con, tableName)) break label261;
String query = "SELECT zone, SUM(round_session_time) FROM " + tableName +
" WHERE lid=? GROUP BY zone";
PreparedStatement ps = con.prepareStatement(query);
ps.setInt(1, loginId);
synchronized (this.itemZoneTrafficCache)
{
Map traffic = new HashMap();
ResultSet rs = ps.executeQuery();
while (rs.next())
{
traffic.put(Integer.valueOf(rs.getInt(1)), Integer.valueOf(rs.getInt(2)));
}
ps.close();
this.itemZoneTrafficCache.setZoneTraffic(Integer.valueOf(loginId), traffic);
}
this.loginsWithZoneTrafInit.put(Integer.valueOf(loginId), time);
}
catch (Exception e)
{
e.printStackTrace();
}
}
else
{
this.loginsWithZoneTrafInit.put(Integer.valueOf(loginId), time);
}
Казалось бы, почему так часто обновляется кэш из базы - ведь судя по коду он должен обновляться, только если последние 5 минут (300000L ms) по нашему логину вообще не было сессий.
(Тут я, если честно, вообще не понял, зачем нужны эти 5 минут, если кэш актуализируется на лету при добавлении новой сессии, ну да ладно) Смотрим, откуда берется time:
Код:
Calendar time = new GregorianCalendar();
int sessionTime = request.getIntAttribute(-1, 46, Integer.valueOf(0)).intValue();
time.add(13, -sessionTime);
session.setStartTime(time);
session.setSessionTime(sessionTime);
session.setItemZoneTrafficCache(initZoneTraffics(login.getId(), con, session.getStartTime()));
Т.е. time в initZoneTraffics - время старта сессии, а не текущее или время окончания сессии.
Т.е. сессии длительностью > 5 минут заведомо попадают мимо кэша, кидают в базу 6-секундный запрос и объявляют кэш устаревшим, пока следующая сессия не обновит time для кэша на более актуальный. Поскольку сессии обрабатываются в многопоточной среде, при этом лочится куча следующих сессий, пока наконец одной не удастся установить нормальное время для кэша. Затем всё повторяется снова. По факту у меня, встав колом один раз, сервер в таком состоянии остаётся очень надолго:
Вложение:
graph_image.png [ 22.29 КБ | Просмотров: 2263 ]
Почему такое происходит только у меня:
Мы решили собирать статистику по операторам, для чего завели отдельный экземпляр модуля VoiceIP со своим радиусом.
В итоге имеем большую нагрузку и всего 2 логина voiceip, на которые всё валится.
Видимо когда эти проблемы с кэшем размазаны на множество логинов, они так себя не проявляют.
Уменьшил количество потоков обработки до 30 - всё то же самое.
Вот как-то так.
Может стоит убрать эти 5 минут? Или брать time - текущим временем?