Couchbase: инициализация из статического блока кода занимает больше времени
Я поместил свой код инициализации couchbase в блок статического кода:
static {
initCluster();
bucket = initBucket("graph");
metaBucket = initBucket("meta");
BLACKLIST = new SetObservingCache<String>(() -> getBlackList(), BLACKLIST_REFRESH_INTERVAL_SEC * 1000);
}
Я знаю, что это не очень хорошая практика, но она была очень удобной и служила своей цели, поскольку мне нужно, чтобы этот код выполнялся ровно один раз в многопоточной среде и блокировал все последующие вызовы из других потоков, пока он не будет завершен (черный список был инициализирован).
К моему удивлению, тайм-аут вызова getBlacklist() завершен. Однако при повторном вызове через 2 минуты (вот что ObservingCache
делает), это завершено менее чем за секунду.
Чтобы решить эту проблему, я реорганизовал свой код и сделал запись в черный список ленивой:
public boolean isBlacklisted(String key) {
// BLACKLIST variable should NEVER be touched outside of this context.
assureBlacklistIsPopulated();
return BLACKLIST != null ? BLACKLIST.getItems().contains(key) : false;
}
private void assureBlacklistIsPopulated() {
if (!ENABLE_BLACKLIST) {
return;
}
if (BLACKLIST == null) {
synchronized (CouchConnectionManager.class) {
if (BLACKLIST == null) {
BLACKLIST = new SetObservingCache<String>(() -> getBlackList(), BLACKLIST_REFRESH_INTERVAL_SEC * 1000);
}
}
}
}
Призыв к isBlacklisted()
блокирует все другие потоки, которые пытаются проверить, находится ли запись в черном списке, пока черный список не будет инициализирован. Я не большой поклонник этого решения, потому что оно очень многословно и подвержено ошибкам - можно попытаться читать из BLACKLIST без вызова assureBlacklistIsPopulated()
заранее.
Статические (и не окончательные) поля внутри класса:
private static CouchbaseCluster cluster;
private static Bucket bucket;
private static Bucket metaBucket;
private static SetObservingCache<String> BLACKLIST;
Я не могу понять, почему вызов был успешным, когда он не был частью блока статической инициализации. Есть ли известная уязвимость, связанная с производительностью блока статической инициализации, о которой я не знаю?
РЕДАКТИРОВАТЬ: добавлен код инициализации для каждого запроса
private Bucket initBucket(String bucketName) {
while(true) {
Throwable t = null;
try {
ReportableThread.updateStatus("Initializing bucket " + bucketName);
return cluster.openBucket(bucketName);
} catch(Throwable t1) {
t1.printStackTrace();
t = t1;
}
try {
ReportableThread.updateStatus(String.format("Failed to open bucket: %s reason: %s", bucketName, t));
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void initCluster() {
CouchbaseEnvironment env = DefaultCouchbaseEnvironment
.builder()
.kvTimeout(MINUTE)
.connectTimeout(MINUTE)
.retryStrategy(FailFastRetryStrategy.INSTANCE)
.requestBufferSize(16384 * 2)
.responseBufferSize(16384 * 2)
.build();
while(true) {
ReportableThread.updateStatus("Initializing couchbase cluster");
Throwable t = null;
try {
cluster = CouchbaseCluster.create(env, getServerNodes());
if(cluster != null) {
return;
}
} catch(Throwable t1) {
t1.printStackTrace();
t = t1;
}
try {
ReportableThread.updateStatus(String.format("Failed to create connection to couch %s", t));
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Set<String> getBlackList() {
ReportableThread.updateStatus("Getting black list");
AbstractDocument<?> abstractDoc = get("blacklist", metaBucket, JsonArrayDocument.class);
JsonArrayDocument doc = null;
if (abstractDoc != null && abstractDoc instanceof JsonArrayDocument) {
doc = (JsonArrayDocument)abstractDoc;
} else {
return new HashSet<String>();
}
ReportableThread.updateStatus(String.format("%s: Got %d items | sorting items", new Date(System.currentTimeMillis()).toString(), doc.content().size()));
HashSet<String> ret = new HashSet<String>();
for (Object string : doc.content()) {
if (string != null) {
ret.add(string.toString());
}
}
return ret;
}
1 ответ
1-й: вы делаете двойную проверку идиомы. Это всегда плохо. Поставьте только один if(BLACKLIST==null), и он должен быть внутри синхронизированного.
2-й: ленивый init в порядке, но делайте это в статическом getInstance() и НИКОГДА не открывайте поле BLACKLIST