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

Другие вопросы по тегам