06-21-2015, 10:08 PM
Пару месяцев назад подключал ehcache к проекту и первое, что его использовало - html cache, что собственно, и постится.
Никаких секретов и нечто крутого тут нет. Любой человек, который может читать документацию - реализует это все очень легко. К тому же ehcache интуитивно понятный.
На счет собственных ощущений: какого-либо снижения скорости работы, я не увидел (при том, что у меня над ним висит еще и шаблонизатор); можно в полной мере оффхипить (если данных много и они тяжелые); персистент сейв на диск кеша (с помощью чего данные не теряются при перезагрузках), но она показалась мне немного странной, т.к. данные как-то странно синхронизируются с диском; различные виды кешей; выгрузки данных и куча всего. Одним словом - удобно.
Никаких секретов и нечто крутого тут нет. Любой человек, который может читать документацию - реализует это все очень легко. К тому же ehcache интуитивно понятный.
Caches
Код:
package ru.catssoftware.gameserver.cache;
import lombok.Getter;
import net.sf.ehcache.CacheManager;
/**
* @author PointerRage
*
*/
public class Caches {
@Getter(lazy=true) private final static CacheManager manager = CacheManager.create();
}
Reloadable
Код:
package fork2.loaders;
/**
* @author PointerRage
*
*/
public interface Reloadable {
void reload();
}
HtmCache
Код:
package ru.catssoftware.gameserver.cache;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.concurrent.TimeUnit;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration.Strategy;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
import ru.catssoftware.gameserver.Config;
import ru.catssoftware.gameserver.ThreadPoolManager;
import ru.catssoftware.gameserver.model.actor.instance.L2PcInstance;
import ru.catssoftware.gameserver.model.quest.Quest;
import ru.catssoftware.gameserver.network.component.Lang;
import ru.catssoftware.gameserver.util.Util;
import fork2.loaders.Reloadable;
import fork2.startup.Singleton;
import fork2.startup.Startup;
/**
* @author PointerRage
*
*/
@Startup("IdFactory")
@Singleton
@Slf4j
public class HtmCache implements Reloadable {
@Getter(lazy=true)
private final static HtmCache instance = new HtmCache();
private final static Charset charser = Charset.forName("UTF-8");
private final static String OLD_QUEST_FOLDER = "data/scripts/";
private final static String QUEST_FOLDER = "data/html/quests/";
private HtmCache() {
for(int i = 0; i < Lang.values().length; i++) {
CacheConfiguration conf = new CacheConfiguration(Lang.values()[i].name().concat("-html"), 1500)
.eternal(false)
.timeToLiveSeconds(TimeUnit.HOURS.toSeconds(1))
.timeToIdleSeconds(TimeUnit.MINUTES.toSeconds(30))
.persistence(new PersistenceConfiguration().strategy(Strategy.NONE))
.diskExpiryThreadIntervalSeconds(0)
.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU);
Caches.getManager().addCache(new Cache(conf));
}
ThreadPoolManager.getInstance().executeGeneral(() -> reload());
}
private Cache getCache(Lang lang) throws IllegalStateException, ClassCastException {
return Caches.getManager().getCache(lang.name().concat("-html"));
}
private String compactHtml(String html) {
html = html.replace(" ", " ").replace("\r","");
html = html.replace("< ", "<").replace(" >", ">");
html = html.replace("\n\n", "\n").replace(">\n", ">").replace("\n<", "<");
html = html.replace("<!--if","\n<!--if");
return html.trim();
}
private String getHtm(Lang lang, String file) {
Cache cache;
try {
cache = getCache(lang);
} catch(IllegalStateException | ClassCastException e) {
log.error("Cache for {} not found!", lang.name());
return String.format("cache for %s not found!", lang.name());
}
Element element;
try {
element = cache.get(file);
} catch(IllegalStateException | CacheException e) {
return null;
}
if(element == null || element.getObjectValue() == null)
return null;
return element.getObjectValue().toString();
}
private boolean loadElement(Lang lang, String file) {
String tag = lang.getPathName();
int inx = file.lastIndexOf('/');
File f = new File(file.substring(0, inx).concat(tag).concat(file.substring(inx)));
boolean shared = false;
if(!f.exists() || !f.isFile()) {
f = new File(file);
shared = true;
if(!f.exists() || !f.isFile())
return false;
}
byte[] bytes;
try {
bytes = Files.readAllBytes(f.toPath());
} catch (IOException e) {
log.error("Failed to load data: {}", f);
return false;
}
String data = compactHtml(new String(bytes, charser));
if(!shared) {
Cache cache = getCache(lang);
cache.put(new Element(file, data));
} else {
for(Lang l : Lang.values()) {
Cache cache = getCache(l);
cache.put(new Element(file, data));
}
}
return true;
}
public String getHtm(String file, L2PcInstance player)
{
return getHtm(file, player, true);
}
public String getHtm(String file, L2PcInstance player, boolean force) {
if (player == null) {
log.error("Request HTML 'getHtml(file, player)', with param player == null!", new RuntimeException());
return "player is null, error!";
}
if(player.isGM())
player.sendMessage("File: {}", file);
return getHtm(file, player.getLang(), force);
}
public String getHtm(String file, Lang lang, boolean force) {
file = file.replace("//", "/");
String html = getHtm(lang, file);
if (html == null) {
if(loadElement(lang, file))
return getHtm(lang, file);
if (lang.isRU()) {
if(loadElement(Lang.EN, file))
return getHtm(Lang.EN, file);
} else {
if(loadElement(Lang.RU, file))
return getHtm(Lang.RU, file);
}
}
if (html == null && !force) {
html = "<html>";
html += lang == Lang.RU ? "HTML <font color=LEVEL>'" + file + "'</font> не существует." : "HTML <font color=LEVEL>'" + file + "'</font> not found.";
html += "</html>";
}
return html;
}
public String getHtmForce(String file, L2PcInstance player) {
return getHtm(file, player);
}
public String getQuestHtm(String fileName, Quest quest, L2PcInstance player) {
String questFolder = null;
if(quest.getScriptFile() != null)
questFolder = Util.getRelativePath(Config.DATAPACK_ROOT, new File(quest.getScriptFile()).getParentFile());
else {
questFolder = QUEST_FOLDER.concat(quest.getName()).concat("/");
String data = getHtm(questFolder, player, true);
if(data == null) {
questFolder = OLD_QUEST_FOLDER;
if(quest.getQuestIntId() < 0 || quest.getQuestIntId() > 2000)
questFolder += "custom/";
else
questFolder += "quests/";
questFolder += quest.getName();
}
}
return getHtm(questFolder.concat("/").concat(fileName), player, false);
}
public boolean htmExists(String file) {
File f;
for(Lang lang : Lang.values()) {
String tag = lang.isRU() ? "/ru" : "/en";
int inx = file.lastIndexOf('/');
f = new File(file.substring(0, inx).concat(tag).concat(file.substring(inx)));
if(!f.exists() || !f.isFile())
continue;
return true;
}
f = new File(file);
if(!f.exists() || !f.isFile())
return false;
return true;
}
@Override
public void reload() {
for(Lang lang : Lang.values()) {
String name = lang.name().concat("-html");
Cache cache = Caches.getManager().getCache(name);
log.info("Cache {} with {} elements cleared!", name, cache.getSize());
cache.removeAll();
}
}
public String[] getInfo() {
String[] nfo = new String[Lang.values().length];
for(int i = 0; i < Lang.values().length; i++) {
String name = Lang.values()[i].name().concat("-html");
Cache cache = Caches.getManager().getCache(name);
nfo[i] = String.format("Cache %s have %d elements", name, cache.getSize());
}
return nfo;
}
}
Lang
Код:
package ru.catssoftware.gameserver.network.component;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public enum Lang {
RU("ru.properties", new ConcurrentHashMap<String, String>()) {
@Override
public String getPathName() {
return "/ru".intern();
}
@Override
String getErrorMessage() {
return "Сообщения не существует".intern();
}
},
EN("en.properties", new ConcurrentHashMap<String, String>()) {
@Override
public String getPathName() {
return "/en".intern();
}
@Override
String getErrorMessage() {
return "Message not found.".intern();
}
};
private final static Logger log = LoggerFactory.getLogger(Lang.class);
private final String filename;
private final ConcurrentHashMap<String, String> messages;
Lang(String filename, ConcurrentHashMap<String, String> messages)
{
this.filename = filename;
this.messages = messages;
}
public abstract String getPathName();
abstract String getErrorMessage();
public String getMessage(String name) {
String message = messages.get(name);
if (message == null) {
message = getErrorMessage();
log.error("Custom message '{}' not found for language: {}", name, name());
}
return message;
}
public String getMessage(String name, Object... values) {
String message = getMessage(name);
if (values == null || values.length == 0)
return message;
return String.format(message, values);
}
public void addMessage(String name, String value) {
messages.put(name, value);
}
public String getFilenameMessages() {
return filename;
}
public boolean isRU() {
return this == RU;
}
public void clearCustomMessage() {
messages.clear();
}
}
На счет собственных ощущений: какого-либо снижения скорости работы, я не увидел (при том, что у меня над ним висит еще и шаблонизатор); можно в полной мере оффхипить (если данных много и они тяжелые); персистент сейв на диск кеша (с помощью чего данные не теряются при перезагрузках), но она показалась мне немного странной, т.к. данные как-то странно синхронизируются с диском; различные виды кешей; выгрузки данных и куча всего. Одним словом - удобно.
FAQ
Q: шта такое @Singletone/@Startup?
A: это мои аннотации от системы старта сервера; можно просто их удалить.
Q: шта такое @Getter, @Slf4j и другое из lombok пакага?7
A: фреймворк кодогенерации, очень рекомендую; бережет кучу времени (lombok), так же есть поддержка для IDEA, Eclipse, NetBeans.
A: это мои аннотации от системы старта сервера; можно просто их удалить.
Q: шта такое @Getter, @Slf4j и другое из lombok пакага?7
A: фреймворк кодогенерации, очень рекомендую; бережет кучу времени (lombok), так же есть поддержка для IDEA, Eclipse, NetBeans.
m0nster.art - clear client patches, linkz to utils & code.
Гадаю по капче.
Гадаю по капче.