GWT – отличный фреймворк. Я — Java-разработчик, и мне доводилось работать с тонкими клиентами с использованием JSP, JSF и GWT. Про JSP говорить особо нечего, технология сейчас практически вымерла, а вот в JSF-е пришлось повариться пару лет на двух проектах, и впечатления, мягко говоря, не из лучших: мешанина JSTL, HTML, JavaScript и прочего доставляет несказанное “удовольствие”, доходящее до экстаза в моменты разбора непонятного поведения какой-нибудь сложной страницы. Да, в примерах все выглядит аккуратно и просто, но реальная жизнь не такая, и JSF-страницы проекта даже среднего размера и, вроде бы, с грамотным неторопливым подходом при проектировании, с использованием шаблонов, все равно начинает “попахивать”, особенно в части читабельности. В GWT все достаточно аккуратно, ведь пишем на родном языке Java, пусть и в урезанном варианте, но того что есть более чем достаточно.
Если говорить о том, что в GWT на клиента грузятся огромные файлы JavaScript-а, то тут мнение мое такое. В GWT при первом входе на клиента грузится логика, пусть и много, но лишь один раз. После загрузки между клиентом и сервером ходят только чистые данные бизнес-логики. Конкретно у меня первая загрузка 3МБ, затем AJAX-запросы/ответы, тут уж зависит от того, что у вас за объемы данных. В JSF-е же, при входе на любую страницу в браузер идут и данные и описание интерфейса, в котором они отображаются. В большинстве случаев размер описания интерфейса (html-а) во много раз больше размера самих данных. Чтобы показать на клиенте 1КБ данных (несколько строчек таблицы, например), вам придется передать в браузер 15-30 кБ html-а (считаем, что изображения и скрипты закешированы). А ведь данных на странице обычно гораздо больше: заголовки, меню, различные блоки и т.д. Фактически, достаточно насыщенная данными и функционалом страница обычно весит не менее 100-200 кБ. GWT же для отображения такой же страницы заберет с сервера 1 кБ данных и всё. Если пользователи постоянные и подолгу находятся на сайте, то потеря первичных 3МБ компенсируется за полчаса. С учетом этого, GWT идеально подходит для реализации рабочих мест, а вот для обычных сайтов он может быть не так хорош.
В любом случае, это лишь вступление для того, чтобы немного “окунуться” в рассматриваемую тему, а статья не о сравнении технологий, а о том, как подглядывание за данными, которые ходят между браузером и сервером, может помочь в решении проблем и в оптимизации клиент-серверного взаимодействия. На момент написания, у нас использовался уже очень сильно устаревший GWT 1.5, а для передачи DTO столь же старая и уже не поддерживаемая библиотека Gilead. У меня в планах обновление до последней версии GWT и отказ от Gilead в пользу GWT-шного RequestFactory. Независимо от технологий, инструментарий и методы отладки остаются теми же. Я использовал FireFox с установленным плагином FireBug.
Как мы нашли утечку памяти
Однажды мы решили обновить используемые технологии – GWT, Gilead, JBoss, Hibernate. Заменили библиотеки, развернули сервер, перекомпилировали проект. Запустили, оттестировали, и решили выходить на промышленную эксплуатацию. Обновили сервер, ждем. Первые отзывы – работает быстрее. Через час все начало “подвисать”, через 15 минут “встало колом”, еще чуть позже сервер упал по OutOfMemory. Начали судорожно пытаться разобраться в проблеме, перезапускали сервер каждый час в это время. Опять же, статья не совсем об этом, кончилось тем, что утечку обнаружить так и не удалось, и мы откатились на старую версию. Я долго пытался анализировать дампы, делать нагрузочное тестирование, но так и не нашел источник проблемы.
Спустя месяц, тестировщик стал жаловаться на то, что один из журналов в Системе долго открывается (ему в свою очередь на это пожаловались пользователи). У меня эта проблема не воспроизводилась, я попросил его установить FireFox и FireBug и посмотреть, что там происходит. Оказалось, что при открытии журнала на клиента грузится пакет размером около 4-х мегабайт (!!!), для того, чтобы отобразить табличку из десятка записей. Источник проблемы был сразу найден – на сервере, при выборке данных для отображения, не было установлено ограничение на количество записей, считываемых из БД, и на клиента грузилось все содержимое таблицы БД. У меня в тестовой базе разработчика в таблице было полсотни записей, а у тестировщика был свежий дамп промышленной базы с тысячами записей в этой таблице. Исправление проблемы – добавление одной строчки кода.
Я думаю, что причина “падения” сервера при обновлении технологий была именно в этом журнале. Почему старые технологии справлялись? Видимо, в них меньше использовалось кеширование данных (косвенно подтверждается отзывами о том, что новые технологии работали быстрее). К сожалению, фейл настолько сильно ударил по нашим пользователям (и конечно же по нам, разработчикам и внедренцам), что следующая попытка обновления JBoss была выполнена только через год, касательно же GWT и Gilead официальных планов вообще нет. Вот так вот забытая строчка кода может порушить грандиозные планы, украсть сотни человекочасов работы, и добавить седых волос множеству людей.
Как сэкономить сетевой трафик и память
Не поленитесь, и потратьте пару часов на то, чтобы просмотреть в FireBug что передается на сервер и обратно, можно обнаружить интересные вещи. Конкретный пример: клиент (браузер) периодически запрашивает с сервера наличие уведомлений. По сути, надо передать на сервер данные о том, кто запросил, в ответ получить список. У нас на клиенте хранится объект User, который содержит в себе немало информации о нем, в том числе различные списочные данные. Так вот, метод, запрашивающий данные с сервера был реализован просто – передается объект User целиком. Точно также реализованы и очень многие другие методы. Посмотрев под отладкой, я увидел, что размер сериализованного в строку и передаваемого на сервер User-а 15кБ. А ведь на сервере в кеше сессии пользователя есть копия этого объекта, а для получения запрашиваемых методом данных вообще достаточно идентификатора пользователя. Если заменить в методе объект User на его ID, размер запроса можно уменьшить с 15кБ до 50-100 байт, а то и вовсе вызывать метод без параметров, а данные о пользователе брать из текущей сессии.
Казалось, что для современной сетевой инфраструктуры 15кБ? Пыль! Но не следует забывать, что запросов много, и пользователь в системе не один. К тому же, кроме сетевых ресурсов, на сервере расходуются ресурсы процессора и памяти на то, чтобы преобразовать полученные 15кБ в Java-объект User, из которого будет гордо вытащен ID для запроса, а все остальное будет оставлено сборщику мусора.
Как я делал нагрузочное тестирование
Когда у нас стал падать сервер при обновлении технологий, одной из попыток обнаружения источника проблемы было проведение нагрузочного тестирования. Быстрый поиск и просмотр бесплатных утилит и программных библиотек не помог в создании среды тестирования, поэтому я решил попробовать написать что-то простейшее самостоятельно. Всё, что отсылает на сервер GWT, – это HTTP-запросы, а значит можно попробовать сэмулировать их стандартными средствами языка Java. Только вот, что именно передавать в запросах? Опять же, на помощь пришел FireBug. Заходим в клиентскую часть, выполняем любые действия, в FireBug-е забираем содержимое POST-запросов. Самописная утилита для нагрузочного тестирования получилась совсем небольшой и вполне функциональной. Некий объект Пользователь выполняет повторяющийся набор действий, т.е. отправку HTTP-запросов. В разных потоках запускаются несколько десятков/сотен таких объектов и дают достойную нагрузку на сервер. Прямо сейчас тестирую под нагрузкой Систему для проверки после перехода на JBoss 7: ресурсы процессора используются сервером на 90%, за ночь съелось 500МБ памяти, при входе в клиентскую часть ощутимая «заторможенность», а это значит, что нагрузка хорошая!
Заключение
Всё вышеописанное относится не только к GWT, но и к любому тонкому клиенту: смотрите под отладкой что ходит между сервером и клиентом. Это не только может дать указания на узкие/проблемные места, но и помочь в лучшем понимании сути используемых технологий.
Автор: bobzer