По колено в JVM куче, или на пороге потери данных |
||
МЕНЮ Главная страница Поиск Регистрация на сайте Помощь проекту Архив новостей ТЕМЫ Новости ИИ Голосовой помощник Разработка ИИГородские сумасшедшие ИИ в медицине ИИ проекты Искусственные нейросети Искусственный интеллект Слежка за людьми Угроза ИИ ИИ теория Внедрение ИИКомпьютерные науки Машинное обуч. (Ошибки) Машинное обучение Машинный перевод Нейронные сети начинающим Психология ИИ Реализация ИИ Реализация нейросетей Создание беспилотных авто Трезво про ИИ Философия ИИ Big data Работа разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика
Генетические алгоритмы Капсульные нейросети Основы нейронных сетей Распознавание лиц Распознавание образов Распознавание речи Творчество ИИ Техническое зрение Чат-боты Авторизация |
2022-03-12 14:25 Спойлер: здесь мы будем программно вытаскивать данные из JVM дампа кучи. Контекст и предыстория Смоделируем ситуацию: у вас есть приложение на JVM (без разница, будь то Kotlin, Java или Scala), а еще у вас есть уверенность в себе и немного не хватает ответственности. В пачке с приложением, а именно сервером, идет несколько стандартных штук - база данных, небольшой http слой, в общем, все стандартно. Ах да, забыл упомянуть - мы хостим майнкрафт сервер с огромной самописной модификацией, которая меняет почти все нюансы геймплея. Не стоит фокусироваться на майнкрафте - просто представим, что у нас есть набор моделей, которые довольно часто меняются - игрок получает уровень, тратит свободные очки опыта, создает королевство, уходит из него и бла-бла-бла. Мне кажется, любой уважающий себя программист на +- хобби проекте захочет навернуть нереально крутое самописное решение, чем, собственно говоря, я и занялся. Модель данных У нас есть несколько типов сущностей, примерно по 20 штук на каждого игрока, которые хранятся в базе данных и активно меняются в рантайме. Соответственно, нужно эти данные еще и сохранять в базу данных, а то после перезапуска сервера они все пропадут. Сохранять в базу данных после каждого изменения - отвратительная затея при таких часто меняющихся данных, поверьте, в ногу стрелял, состояния синхронизировал. Было принято решение написать своего рода read - write модель, которая отлично себе живет в рантайме, ее быстро запрашивать из главного игрового потока и менять, соответственно, тоже. А в отдельном потоке периодически сохранять все это дело в базу данных, чтобы игроки не чувствовали лагов. На словах все отлично, да и на деле все тоже было отлично, пока на сервере одновременно не оказалось примерно 20 игроков. Казалось бы, 20 параллельных юзеров - фигня, а не нагрузка. Так и есть, но главный фактор мы не учли, главный разработчик - наивный самоуверенный лох. Короче говоря, в отдельном потоке для сохранений в базу не было проверок на исключения, и наш `while true delay persist` цикл успешно сдох вместе с потоком. И где-то тут у разработчика (в данном случае меня) перед глазами возникает именно вот тот мерзкий кусок кода, который он сам написал, сразу всплывают все подводные камни - короче говоря, все проясняется, ты бьешь себя по лбу, сетуешь как ты раньше это не учел... Хьюстон, у нас проблемы Конец лирической части. Где данные, Лебовски? Очевидно, в JVM приложении - в куче. В целом, появляется очевидный план - сделать дамп, взять из него все нужные сущности, запихнуть их в базу данных. Ну и не забыть исправить сохранение в коде, конечно же. Однако "взять данные из дампа" - не самая тривиальная задача. Большинство утилит, а именно
Далее. Имеем дело с java 17, то есть большинство тул(например, jhat и jmap) могут иметь неполный функционал. Например, мне не удалось запустить jhat сервер для запуска OQL(Object Query Language) для дампа с 17 жавы, как бы я не старался. Object Query Language - аля sql для объектно ориентированных структур данных, его можно использовать для индексированного дампа. Да, я потратил 3 часа, пытаясь пропатчить jhat под 17 жаву. Не пытайтесь, затея так себе. После некоторых потуг, кстати, начинаешь замечать, что дамп довольно велик и могуч - в зависимости от масштабов приложения, он может быть от гигабайта до очень большого количества гигабайтов. А ведь тужимся мы не на выделенном сервере / виртуалке, мы тужимся на своей локальной машине, соответственно нам нужно этот дамп скачать. Пользователи Intellij Idea: не пытайтесь скачать дамп больше 4 гигабайта "перетаскиванием" с удаленного хоста в локальную папку (Remote host window). Идея сдохнет и продолжит что-то качать даже после полного скачивания, насколько много она будет скачивать - не знаю. Пока я ждал, она скачала что-то в 5 раз превышающее размером дамп. Используйте scp или что-нибудь понадежнее. Итак, просуммируем наши наработки:
Мне пришло, но потом я вспомнил, что проблему нужно решить оперативно, а так бы точно начал бы писать. На этом этапе я уже смирился, что вытаскивать данные придется программно - вариантов оставалось все меньше, а этот хоть комфортный и привычный, да и контроля над ситуацией много. Начал искать готовые решения. В идеале - найти библиотеку / скомпилировать среду разработки / скомпилировать утилиту вроде JVisualVM для того, чтобы пихнуть ее себе в classpath и заиспользовать все, что мне нужно для работы с дампом. Очевидно, компилировать целый Eclipse или NetBeans - вообще не хочется. Заварил чай, начал листать гугл, наткнулся на статью https://cuprak.info/2018/11/12/exploring-java-heap-dumps/ И это был свет небесный на мою грешную голову - там, по сути, была инструкция и готовый проект, в котором есть небольшие примеры, как загрузить дамп и получить из него инстансы. 8 чудо света, короче говоря. В целом, дальше все было довольно таки просто - склонить проект, перенести на котлин (стараюсь избегать Java всеми силами, уж очень тяжко на ней писать после котлина) и дописать под свои нужды. Алгоритм очень простой:
А теперь, наконец то, код. // Загружаем дамп в память(второй аргумент - индекс сегмента, по дефолту 0, // что делает - не знаю, спасибо, что еще не пришлось узнавать val heap: Heap = HeapFactory.createHeap(File(path), 0) println("Loaded heap dump") // Получаем объект для работы с инстансами класса по полному пути класса val strClass: JavaClass = heap.getClassFor("ru.kingdomrp.krp.persistence.model.SkillPathModel") println("We got class, now will compute instances") // А теперь долгий этап. Если большой дамп - заваривайте кофе, // гуляйте с собакой, ложитесь спать. Для 7 гигабайт дампа на макбуке 20 года - 15 минут минимум. val instances: MutableList<Instance> = strClass.instances as MutableList<Instance> // Мы получили список инстансов - репрезентаций сущностей в куче. Теперь нужно считать данные val instance: Instance = instances.first() // Получаем примитив: val intFieldValue = instance.getValueOfField("primitiveField123") as Int // Получаем объект: val objectField: Instance = instance.getValueOfField("status") as Instance // Снова получаем объект. Если это enum, например, то можем просто получить ordinal // А из ординала уже получить реальный енам val enumFieldOrdinal = objectField.getValueOfField("ordinal") as Int Должен признаться. Я не осилил вытащить строковые данные из кучи. Час времени и пара вырванных волос и я понял, что не очень то и хотелось. Что же это, будущему поколению все готовенькое? Нет уж, сами разбирайтесь. Из полученных объектов, примитивов и тд собираете вашу модель, сериализуете ее, как хотите, заливаете в базу, веселитесь, радуетесь. В целом то говоря, все. На этой части снова начинается лирическая часть, так сказать, небольшая комедия. Подготовив все данные, забекапил все что можно было забекапить, я остановил сервер. И как только я это сделал, у меня опять было то самое состояние, когда перед глазами появляется код, все проясняется... Короче говоря, перед остановкой игрового сервера мы в главном потоке приложения синхронно пишем в базу все, что еще не сохранилось. То есть все данные, которые я часов 7 вытягивал из дампа оказались для подстраховки, которая, конечно, понадобилась, но буквально изменить пару сущностей. Заключение Это было чудесное путешествие в мир нерешенных проблем, полное стресса и чудес, которых становится все меньше и меньше с течением времени. Программист становится опытнее, наступает на все меньшее количество граблей, и приключения вроде этого рано или поздно покидают его жизнь. Мне кажется, иногда полезно аккуратно подложить себе пару свиней, граблей, выкопать пару ям с пиками и так далее. Очень освежающий опыт, который позволяет снова почувствовать себя неопытным новичком, для которого каждая задача, за которую он берется - приключение и огромное испытание. Ну и уж если ты такой большой любитель лирики, что дошел до этого места, то может тебе захочется поиграть в средневековый майнкрафт? :) Ссылки https://github.com/enchantinggg4/java-heap-adventures https://cuprak.info/2018/11/12/exploring-java-heap-dumps https://mvnrepository.com/artifact/org.netbeans.modules/org-netbeans-lib-profiler/RELEASE802 https://discord.gg/H5WEnQk5rX - discord сервер вышеупомянутого майнкрафт проекта Источник: m.vk.com Комментарии: |
|