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() блокирует все другие потоки, которые пытаются проверить, внесена ли запись в черный список, пока черный список не будет инициализирован. Я не большой поклонник этого решения, потому что оно очень подробное и подвержено ошибкам - можно попытаться прочитать из ЧЕРНОГО СПИСКА, не вызывая заранее 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;
}

person KidCrippler    schedule 29.11.2015    source источник
comment
вы можете поделиться своим кодом initCluster, initBucket и getBlackList()?   -  person Simon Baslé    schedule 30.11.2015
comment
@ SimonBaslé Добавлен код инициализации по вашему запросу   -  person KidCrippler    schedule 02.12.2015


Ответы (1)


1-й: вы используете идиому «перепроверьте». Это всегда плохо. Поместите только один if (BLACKLIST == null), и он должен быть внутри synchronized.

2-й: ленивая инициализация - это нормально, но делайте это в статическом getInstance () и НИКОГДА не открывайте поле BLACKLIST.

person user2023577    schedule 07.02.2016
comment
10x за ответ. Что плохого в идиоме двойной проверки? Во-вторых, я не помню, чтобы выставлял поле ЧЕРНЫЙ СПИСОК. Можете ли вы ответить на мой первоначальный вопрос - почему код в статическом блоке бесконечно медленнее? - person KidCrippler; 07.02.2016
comment
Я не знаю. Вы профилировали это? Статический блок запускается во время загрузки класса, что означает, что во время выполнения кода произойдет много ленивой загрузки классов. Если вы запустите позже, основная загрузка классов была сделана во время загрузки и в более ранние моменты. Это все, что я могу догадаться. Вы можете подтвердить эту гипотезу, изучив подробный журнал класса (см. Параметры Java), перешагнув вызов в отладчике. - person user2023577; 08.02.2016