Сообщений: 69
Тем: 7
Зарегистрирован: Jan 2015
Репутация:
88
01-15-2015, 06:37 PM
(Сообщение последний раз редактировалось: 01-15-2015, 07:59 PM Johnson.)
Здравствуйте, уважаемые.
Делюсь с вами своей системой сохранения короткой информации для игрока.
Бывают случаи, когда игроку нужно завести дополнительное поле информации, но заводить ради этого поле в таблице 'characters' или вовсе новую таблицу не удобно.
Для таких случаев можно воспользоваться этой системой.
Позволяет сохранить любую короткую информацию (255 символов строки или цифровое значение) в отдельную таблицу по OID игрока и названию-ключу, а так же прочитать её.
Загрузка "ленивая", только при создании; новые переменные создаются в мапе класса и в базе параллельно, ни каких дополнительных подгрузок не производится.
Класс потокобезопасен, использует ConcurrentHashMap.
Подключение класса производится только в L2PcInstance (для L2JServer и его форков), путём создания приватного поля и метода-геттера, а так же добавлением соответствующих строк в store() и restore(). Подробнее в архиве, в файле L2PcInstance.java.txt
Возможные проблемы:
Если в вашей БД поле 'obj_Id' таблицы 'characters' имеет тип, отличный от 'INT(10) UNSIGNED NOT NULL', или они имеют другие названия - то придется в классе подправить строку создания таблицы, либо создать таблицу 'character_variables' с полями 'oid' (как в characters с установкой внешнего ключа на него), 'key' (VARCHAR(50)), 'value' (VARCHAR(255)), 'time' (TIMESTAMP) самим.
Архив с классом и инструкциями по подключению: PlayerVariables.rar
Конструктивная критика, и предложения по дополнению или изменению дизайна класса приветствуются.
Критика вида "да такое есть в L2BlaBlaMegaSuperRulezServer сборке!" не принимаются. Систему подобную я использую очень давно, и конкретно сейчас писал для форка l2jserver.
Johnson получился в результате деления на null. Помогаю с джавой только за булочки с маком.
Сообщений: 2,454
Тем: 53
Зарегистрирован: Apr 2010
Репутация:
19,728
Предложения:
1. Добавить синхронизацию методу load в PlayerVariables, либо сделать loaded атомарной, а при входе в метод делать compareAndSet.
Почему: если из разных потоков вызвать load, то будет очень неприятная ситуация.
2. Не дергать на каждый чих базу данных, а кешировать. Сделать shutdown hook, который при выключении сервера будет все это дело сейвить; либо при выходе игрока из игры.
Почему: протестируйте на 1000 онлайна, при активном использовании данной штуки, будет очень сильное удивление, что сервер "логаит", а база данных вообще отваливается.
m0nster.art - clear client patches, linkz to utils & code.
Гадаю по капче.
Сообщений: 69
Тем: 7
Зарегистрирован: Jan 2015
Репутация:
88
01-15-2015, 07:16 PM
(Сообщение последний раз редактировалось: 01-15-2015, 07:39 PM Johnson.)
В shutdownHook и так есть сохранение персонажей, оно за собой потянет и сохранение переменных, так что смысла отдельно сторить нет.
Про нагрузку, чесно говоря не подумал, не пришло в голову, что туда могут писать так часто.
А вот про синхронизацию забыл, хотя и не уверен в её необходимости. Пакеты в любом случае приходят последовательно, значит и загрузка из двух потоков одновременно не произойдет. Но на всякий случай метод синхронизировал.
Перезалил, спасибо большое за подсказку!
Johnson получился в результате деления на null. Помогаю с джавой только за булочки с маком.
Сообщений: 1,240
Тем: 29
Зарегистрирован: May 2013
Репутация:
2,505
При удалении чара корни остаются. Нужно подчищать, да.
Родился, живу и когда-нибудь умру.
Сообщений: 2,454
Тем: 53
Зарегистрирован: Apr 2010
Репутация:
19,728
Johnson Написал:А вот про синхронизацию забыл, хотя и не уверен в её необходимости. Пакеты в любом случае приходят последовательно, значит и загрузка из двух потоков одновременно не произойдет. Но на всякий случай метод синхронизировал.
Перезалил, спасибо большое за подсказку!
Приходят то они последовательно, а выполняются асинхронно (: Во всяком случае на лыже. Отсюда идут различные интересные вещи, вроде двойного EnterWorld.
В любом случае атомарные операции много шустрее, чем обычный лок, поэтому рекомендую в данном случае заюзать именно их.
m0nster.art - clear client patches, linkz to utils & code.
Гадаю по капче.
Сообщений: 69
Тем: 7
Зарегистрирован: Jan 2015
Репутация:
88
Donatte;384387 Написал:При удалении чара корни остаются. Нужно подчищать, да.
Код: CONSTRAINT `FK_VARS_ON_CHARACTER` FOREIGN KEY (`oid`) REFERENCES `characters` (`obj_Id`) ON UPDATE CASCADE ON DELETE CASCADE
Внешний ключ на каскадное удаление/изменение для того и нужен, чтобы корни подчищать.
Если на таблицу ведут внешние ключи - сначала обработаются их условия изменения.
Johnson получился в результате деления на null. Помогаю с джавой только за булочки с маком.
Сообщений: 561
Тем: 44
Зарегистрирован: Sep 2011
Репутация:
412
Не нужно усложнять.
Загружаем один раз в map при загрузке данных персонажа.
При любой изменении переменной, кроме того что пишем в map, сразу же сохраняем эту переменную в базу.
Так мы упрощаем алгоритм и перестраховываемся от потери данных при неожиданном падении или выключении сервера.
Вот подобное из исходников l2open.
PHP код: <?php
private final FastMap<String, String> user_variables = new FastMap<String, String>().setShared(true);
public void setVar(String name, String value)
{
user_variables.put(name, value);
mysql.set("REPLACE INTO character_variables (obj_id, type, name, value, expire_time) VALUES (?,'user-var',?,?,-1)", _objectId, name, value);
}
public void unsetVar(String name)
{
if(name == null)
return;
if(user_variables.remove(name) != null)
mysql.set("DELETE FROM `character_variables` WHERE `obj_id`=? AND `type`='user-var' AND `name`=? LIMIT 1", _objectId, name);
}
public String getVar(String name)
{
return user_variables.get(name);
}
public boolean getVarB(String name, boolean defaultVal)
{
String var = user_variables.get(name);
if(var == null)
return defaultVal;
return !(var.equals("0") || var.equalsIgnoreCase("false"));
}
public boolean getVarB(String name)
{
String var = user_variables.get(name);
return !(var == null || var.equals("0") || var.equalsIgnoreCase("false"));
}
public FastMap<String, String> getVars()
{
return user_variables;
}
private void loadVariables()
{
ThreadConnection con = null;
FiltredPreparedStatement offline = null;
ResultSet rs = null;
try
{
con = L2DatabaseFactory.getInstance().getConnection();
offline = con.prepareStatement("SELECT * FROM character_variables WHERE obj_id = ?");
offline.setInt(1, _objectId);
rs = offline.executeQuery();
while(rs.next())
{
String name = rs.getString("name");
String value = Strings.stripSlashes(rs.getString("value"));
user_variables.put(name, value);
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
DatabaseUtils.closeDatabaseCSR(con, offline, rs);
}
}
Если нужно именно интенсивно изменять какие то данные то лучше это сделать по другому.
Сообщений: 2,454
Тем: 53
Зарегистрирован: Apr 2010
Репутация:
19,728
flopix Написал:Не нужно усложнять.
Загружаем один раз в map при загрузке данных персонажа.
При любой изменении переменной, кроме того что пишем в map, сразу же сохраняем эту переменную в базу.
Так мы упрощаем алгоритм и перестраховываемся от потери данных при неожиданном падении или выключении сервера.
Сервера ведь каждую минуту падают, особенно с нормальными администраторами... А потом берем тачки с двумя хеонами, овер9000 гб озу, строим БД кластеры для сервера с онлайном 1500.
Л2ж дев такой л2ж.
У меня есть знакомый один, который построил кластер БД на 6ти машинах, для сервера майнкрафт, что бы нормально держать онлайн 1000. Это очень мне напоминает, да.
m0nster.art - clear client patches, linkz to utils & code.
Гадаю по капче.
Сообщений: 561
Тем: 44
Зарегистрирован: Sep 2011
Репутация:
412
Ну все таки эта система была придумана для редко изменяющихся данных, таких как настройки, какие то флаги для сервисов и прочее. Откуда тут быть нагрузке на базу даже при большом онлайне?
Ведь никто не будет в такой системе хранить счетчик выбитой адены или убитых мобов?
Сообщений: 69
Тем: 7
Зарегистрирован: Jan 2015
Репутация:
88
01-15-2015, 08:02 PM
(Сообщение последний раз редактировалось: 01-15-2015, 08:03 PM Johnson.)
Pointer*Rage;384391 Написал:майнкрафт, что бы нормально держать онлайн 1000 Весьма оптимистичный знакомый, да...
Перезалил с атомарниками по совету Pointer*Rage, спасибо большое за подсказки.
Если что-то важное туда писать - то можно и .store() вызвать для надежности.
Добавлено через 1 минуту
flopix Написал:Ну все таки эта система была придумана для редко изменяющихся данных, таких как настройки, какие то флаги для сервисов и прочее. Откуда тут быть нагрузке на базу даже при большом онлайне?
Ведь никто не будет в такой системе хранить счетчик выбитой адены или убитых мобов?
Хотя знаешь, ты не прав. Сейчас вспомнил один из форков l2r, там какую-то статистику писали в них. Премию Дарвина за это, конечно, давать надо. Но л2ж дев такой л2ж дев (с)
Johnson получился в результате деления на null. Помогаю с джавой только за булочки с маком.
|