1. Предпосылки
Эта статья была написана после того, как на прошлой неделе в техническом салоне прослушали выступление iQIYI по кэшированию Java. Давайте кратко представим разработку iQIYI java cache road.
Видно, что фигура разбита на несколько этапов:- Первый этап: синхронизация данных плюс redis
Данные синхронизируются с Redis через очередь сообщений, а затем приложение Java напрямую извлекает кеш. Преимущества этого этапа: благодаря используемому распределенному кешу данные обновляются быстро. Недостатки также более очевидны: в зависимости от стабильности Redis, когда Redis зависает, вся система кэширования становится недоступной, что приводит к лавинному кэшированию и попаданию всех запросов в БД.
- Второй и третий этап: JavaMap в кеш Guava
На этом этапе внутрипроцессный кеш используется в качестве кеша первого уровня, а Redis — в качестве кеша второго уровня. Преимущества: Не зависит от внешних систем, другие системы все еще могут использоваться, если они зависают. Недостаток: внутрипроцессный кеш не может обновляться в режиме реального времени, как распределенный кеш. Из-за ограниченной памяти java размер кеша должен быть установлен, и тогда некоторые кеши будут устранены, и возникнет проблема с частотой попаданий.
- Этап 4: Обновление кэша гуавы
Чтобы решить вышеуказанные проблемы, можно использовать Guava Cache для установки времени обновления после записи и обновления. Решил проблему не обновления все время, но все равно не решил обновление в реальном времени.
- Этап 5: Асинхронное обновление внешнего кеша
Этот этап расширяет Guava Cache и использует Redis в качестве механизма уведомления очереди сообщений, чтобы уведомить другие Java-приложения об обновлении.
Вот краткое введение в пять этапов разработки кеша iQIYI.Конечно, есть и другие оптимизации, такие как настройка GC, проникновение в кеш и некоторые оптимизации для покрытия кеша. Заинтересованные студенты могут подписаться на официальный аккаунт и связаться со мной для связи.
Первобытное общество - Чаку
Выше — эволюционная линия iQIYI, но в общем процессе разработки у всех первый шаг — это вообще не редис, а непосредственно проверка библиотеки.
Когда трафик небольшой, удобнее всего проверить базу данных или прочитать файл, и это может полностью удовлетворить наши бизнес-требования.
Древнее общество — HashMap
Когда наше приложение имеет определенный объем трафика или база данных запрашивается очень часто, мы можем использовать HashMap или ConcurrentHashMap, которые поставляются с нашим java. Мы можем написать это в коде:
public class CustomerService {
private HashMap<String,String> hashMap = new HashMap<>();
private CustomerMapper customerMapper;
public String getCustomer(String name){
String customer = hashMap.get(name);
if ( customer == null){
customer = customerMapper.get(name);
hashMap.put(name,customer);
}
return customer;
}
}
Однако при этом возникает проблема: HashMap не может выполнять удаление данных, а память будет неограниченно расти, поэтому hashMap скоро будет удален. Конечно, это не значит, что он совсем бесполезен, как и не все в нашем древнем обществе устарело, например, традиционные добродетели нашей знаменитой китайской семьи вне времени, точно так же этот hashMap можно использовать в определенных сценариях как кеш, когда механизм исключения не требуется, например, мы используем отражение, если мы каждый раз будем искать метод и поле через отражение, производительность будет неэффективной.В это время мы используем HashMap для его кэширования, и производительность может быть улучшены много.
Современное общество - LRUHashMap
Проблемы, которые ставили нас в тупик в древнем обществе, не могут быть устранены данными, что приведет к бесконечному расширению нашей памяти, что для нас заведомо неприемлемо. Некоторые люди говорят, что я удалю некоторые данные, что неправильно, но как это удалить? Случайное удаление? Конечно нет, просто представьте, что вы только что загрузили A в кеш, и он будет устранен в следующий раз, когда вы захотите получить к нему доступ, и он снова будет обращаться к нашей базе данных.Так зачем нам его кешировать?
Поэтому умные люди придумали несколько алгоритмов отсева.Вот три распространённых FIFO,LRU,LFU(есть ещё ARC,MRU желающие могут поискать сами):
- FIFO: первый вошел, первый вышел, в этом алгоритме исключения первый, кто войдет в кеш, будет исключен первым. Это самое простое, но это приведет к очень низкому проценту попаданий. Представьте, что если у нас есть данные с высокой частотой доступа, к которым сначала обращаются все данные, а к тем, которые не очень высокие, обращаются позже, то наши первые данные будут обращаться, но его частота доступа очень высока, чтобы выжать.
- LRU: наименее недавно используемый алгоритм. В этом алгоритме избегаются вышеуказанные проблемы.Каждый раз, когда к данным обращаются, они будут помещены в конец нашей команды.Если нам нужно удалить данные, нам нужно только устранить главу команды. Но с этим еще есть проблема, если есть данные, к которым обращаются 10 000 раз за первые 59 минут часа (видно, что это данные хотспота), а в следующую минуту данные не обращаются , но есть и другие обращения к данным, это приведет к тому, что наши горячие данные отсеются.
- LFU: Наименее часто используемый в последнее время. В этом алгоритме все вышеперечисленное оптимизируется, используя дополнительное пространство для записи частоты использования каждых данных, а затем выбирая самую низкую частоту для исключения. Это позволяет избежать проблемы, связанной с тем, что LRU не может обрабатывать периоды времени.
Выше перечислены три стратегии исключения.Для этих трех стоимость реализации выше, чем у другой, и тот же процент попаданий также лучше, чем у другой. Вообще говоря, решение, которое мы выбираем, может быть посередине, то есть стоимость реализации не слишком высока, а показатель попаданий тоже хорош. Мы можем завершить простой LRUMap, унаследовав LinkedHashMap и переопределив метод removeEldestEntry.
class LRUMap extends LinkedHashMap {
private final int max;
private Object lock;
public LRUMap(int max, Object lock) {
//无需扩容
super((int) (max * 1.4f), 0.75f, true);
this.max = max;
this.lock = lock;
}
/**
* 重写LinkedHashMap的removeEldestEntry方法即可
* 在Put的时候判断,如果为true,就会删除最老的
* @param eldest
* @return
*/
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > max;
}
public Object getValue(Object key) {
synchronized (lock) {
return get(key);
}
}
public void putValue(Object key, Object value) {
synchronized (lock) {
put(key, value);
}
}
public boolean removeValue(Object key) {
synchronized (lock) {
return remove(key) != null;
}
}
public boolean removeAll(){
clear();
return true;
}
}
Связанный список записей (объектов, используемых для размещения ключа и значения) поддерживается в LinkedHashMap. При каждом получении или установке вставленная новая запись или запрошенная старая запись будут помещены в конец нашего связанного списка. Можно заметить, что в методе построения размер набора намеренно установлен равным max * 1,4.В следующем методе removeEldestEntry требуется исключить только size>max, так что наша карта никогда не сможет достичь логики расширения, переписав LinkedHashMap, несколько простых методов, которые мы реализовали в нашем LruMap.
Современное общество - тайник гуавы
LRUMap был придуман в современном обществе для устранения кешированных данных, но есть несколько проблем:
- Конкуренция замков серьезная.Вы можете видеть, что в моем коде Lock является глобальной блокировкой.На уровне методов, когда количество вызовов велико, производительность неизбежно будет ниже.
- Срок действия не поддерживается
- Автообновление не поддерживается
Поэтому большие ребята из Google не смогли сдержать эти проблемы и изобрели кеш Guava, В кеше Guava вы можете легко использовать его, как следующий код:
public static void main(String[] args) throws ExecutionException {
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
//写之后30ms过期
.expireAfterWrite(30L, TimeUnit.MILLISECONDS)
//访问之后30ms过期
.expireAfterAccess(30L, TimeUnit.MILLISECONDS)
//20ms之后刷新
.refreshAfterWrite(20L, TimeUnit.MILLISECONDS)
//开启weakKey key 当启动垃圾回收时,该缓存也被回收
.weakKeys()
.build(createCacheLoader());
System.out.println(cache.get("hello"));
cache.put("hello1", "我是hello1");
System.out.println(cache.get("hello1"));
cache.put("hello1", "我是hello2");
System.out.println(cache.get("hello1"));
}
public static com.google.common.cache.CacheLoader<String, String> createCacheLoader() {
return new com.google.common.cache.CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return key;
}
};
}
Я объясню, как кэш гуавы решает несколько проблем LRUMap по принципу кэша гуавы.
блокировка конфликта
Кэш Guava использует идею, аналогичную ConcurrentHashMap, блокировки сегментов, и каждый сегмент отвечает за свое собственное устранение. В Гуаве сегментация выполняется по определенному алгоритму. Здесь следует отметить, что если сегментов слишком мало, конкуренция все равно будет жесткой. Если сегментов слишком много, легко произойдет случайное исключение. То есть, чтобы каждый данные имеют эксклюзивный сегмент, и каждый сегмент будет обрабатывать процесс исключения сам по себе, поэтому будет происходить случайное исключение. В кеше гуавы для расчета сегментации используется следующий код.
int segmentShift = 0;
int segmentCount = 1;
while (segmentCount < concurrencyLevel && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
++segmentShift;
segmentCount <<= 1;
}
Приведенный выше segmentCount — это наш окончательный счетчик сегментов, который гарантирует, что в каждом сегменте будет не менее 10 записей. Если параметр concurrencyLevel не установлен, значение по умолчанию будет 4, а конечное количество сегментов будет не более 4. Например, если наш размер 100, он будет разбит на 4 сегмента, и максимальный размер каждого сегмент 25. В кэше гуавы операция записи заблокирована напрямую.Для операции чтения, если срок действия прочитанных данных не истек и они были загружены, нет необходимости блокировать их.Если они не прочитаны, они будут снова заблокированы на некоторое время. требуется загрузка кеша, то есть через настроенный нами CacheLoader, то, что я настраиваю здесь, это возврат ключа напрямую, а в бизнесе он обычно настроен на запрос из базы данных. Как показано ниже:
Дата истечения срока годности
По сравнению с двумя времен истечения с двумя истечениями по сравнению с Lрумап, один написан для того, как долго указывается, как долго, как долго после прочтения доходности. Это очень интересно, в кэше Гуавы, для записи с истекшим сроком действия не сроки истек (то есть фоновая нить была подметающей), но благодаря эксплуатации чтения и записи преимущества должны избегать фона Global Lock, выполняется при сканировании потока. Посмотрите на следующий код:
public static void main(String[] args) throws ExecutionException, InterruptedException {
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
//写之后5s过期
.expireAfterWrite(5, TimeUnit.MILLISECONDS)
.concurrencyLevel(1)
.build();
cache.put("hello1", "我是hello1");
cache.put("hello2", "我是hello2");
cache.put("hello3", "我是hello3");
cache.put("hello4", "我是hello4");
//至少睡眠5ms
Thread.sleep(5);
System.out.println(cache.size());
cache.put("hello5", "我是hello5");
System.out.println(cache.size());
}
输出:
4
1
Из этого результата мы знаем, что обработка экспирации выполняется только во время пут. Особое внимание: я установил максимальный сегмент равным 1 для concurrencyLevel(1) выше, иначе этот экспериментальный эффект не возникнет.Как упоминалось в предыдущем разделе, мы используем единицы сегмента для обработки истечения срока действия. В каждом сегменте поддерживается две очереди:
final Queue<ReferenceEntry<K, V>> writeQueue;
final Queue<ReferenceEntry<K, V>> accessQueue;
writeQueue поддерживает очередь записи, начало очереди представляет данные, записанные раньше, а конец очереди представляет данные, записанные поздно. AccessQueue поддерживает очередь доступа, которая, как и LRU, используется для устранения времени доступа.Если сегмент превышает максимальную емкость, такую как 25, о которых мы упоминали выше, после ее превышения первый элемент очереди accessQueue будет обработано.
void expireEntries(long now) {
drainRecencyQueue();
ReferenceEntry<K, V> e;
while ((e = writeQueue.peek()) != null && map.isExpired(e, now)) {
if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
throw new AssertionError();
}
}
while ((e = accessQueue.peek()) != null && map.isExpired(e, now)) {
if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
throw new AssertionError();
}
}
}
Выше приведен процесс обработки просроченных записей в кеше guava.Он будет выполнять операцию просмотра двух очередей одновременно и удалять их, если срок их действия истечет. Как правило, записи с истекшим сроком действия могут обрабатываться до и после нашей операции размещения, или когда данные считываются и обнаруживается, что срок их действия истек, а затем обрабатывается истечение срока действия всего сегмента, или он вызывается, когда выполняется операция LockedGetOrLoad для второе чтение.
void evictEntries(ReferenceEntry<K, V> newest) {
///... 省略无用代码
while (totalWeight > maxSegmentWeight) {
ReferenceEntry<K, V> e = getNextEvictable();
if (!removeEntry(e, e.getHash(), RemovalCause.SIZE)) {
throw new AssertionError();
}
}
}
/**
**返回accessQueue的entry
**/
ReferenceEntry<K, V> getNextEvictable() {
for (ReferenceEntry<K, V> e : accessQueue) {
int weight = e.getValueReference().getWeight();
if (weight > 0) {
return e;
}
}
throw new AssertionError();
}
Выше приведен код, когда мы исключили запись.Вы можете видеть, что доступ к accessQueue осуществляется для удаления головы очереди. Стратегия вытеснения обычно вызывается при изменении элементов в сегменте, таких как операции вставки, операции обновления и операции загрузки данных.
Автоматическое обновление
Операцию автоматического обновления относительно просто реализовать в кеше гуавы, непосредственно через запрос определить, соответствует ли он условиям обновления и обновить.
Другие особенности
В кеше Guava есть и другие функции:
фантомная ссылка
В кэше Guava и ключ, и значение могут быть установлены для виртуальной ссылки, а в сегменте есть две очереди ссылок:
final @Nullable ReferenceQueue<K> keyReferenceQueue;
final @Nullable ReferenceQueue<V> valueReferenceQueue;
Эти две очереди используются для записи переработанных ссылок, и каждая очередь записывает хэш каждой переработанной Записи, чтобы предыдущая Запись могла быть удалена через хеш-значение в этой очереди после перезаписи.
удалить слушателя
В кэше гуавы, когда какие-то данные удаляются, но вы не знаете, просрочены ли они, или выселены, или потому что виртуальный эталонный объект переработан? В настоящее время вы можете вызвать этот метод removeListener (прослушиватель RemovalListener), чтобы добавить прослушиватель для отслеживания удаления данных, регистрации или какой-либо другой обработки, которую можно использовать для анализа удаления данных.
В RemovalCause записываются все причины выселения: удалено пользователем, заменено пользователем, срок действия истек, сбор выселения, выселение из-за размера.
Резюме кэша гуавы
После внимательного прочтения исходного кода кэша guava, это на самом деле карта LRU с хорошей производительностью и богатым API. На этом также основана разработка кеша iQIYI.Благодаря вторичной разработке кеша guava он может обновлять кеш между службами приложений Java.
Навстречу будущему - кофеин
Функция кэша гуавы действительно очень мощная и удовлетворяет потребности большинства людей, но, по сути, это слой инкапсуляции LRU, поэтому он затмевается многими другими лучшими алгоритмами исключения. Кэш кофеина реализует W-TinyLFU (вариант алгоритма LFU+LRU). Вот сравнение показателей эффективности различных алгоритмов:
Среди них Optimal — самый идеальный показатель попадания, а LRU действительно младший брат по сравнению с другими алгоритмами. И наш W-TinyLFU наиболее близок к идеальной частоте попаданий. Конечно, не только кофеин по частоте попаданий лучше, чем кеш гуавы, но и кеш гуавы с точки зрения пропускной способности чтения и записи.
В это время вы, должно быть, задаетесь вопросом, почему кофеин такой классный? Не волнуйтесь, я расскажу вам медленно ниже.
W-TinyLFU
Выше было сказано, что такое традиционный LFU. До тех пор, пока распределение вероятностей шаблонов доступа к данным в LFU остается постоянным с течением времени, его частота попаданий может стать очень высокой. Здесь я все же возьму iQIYI в качестве примера.Например,выходит новая дорама.Мы используем LFU для кэширования.К этой новой дораме обращались сотни миллионов раз за последние несколько дней,и эта частота посещений также фиксируется в нашем ЛФУ сотни миллионов раз. Но новые дорамы всегда будут устаревшими.Например, первые несколько серий этой новой дорамы через месяц на самом деле устарели, но количество его посещений действительно слишком велико, а другие дорамы вообще не могут устранить эту новую дораму, так что вот Там являются ограничениями в этом режиме. Таким образом, появились различные варианты LFU, затухающие в зависимости от периода времени или частоты за недавний период времени. Тот же LFU также использует дополнительное пространство для записи частоты каждого доступа к данным, даже если данные не находятся в кеше, их необходимо записывать, поэтому дополнительное пространство, которое необходимо поддерживать, очень велико.
Вы можете представить, что мы создаем хэш-карту для этого пространства обслуживания. Каждый элемент данных будет существовать в этой хэш-карте. Когда объем данных очень велик, эта хэш-карта также будет очень большой.
Возвращаясь к LRU, наш LRU не так уж и бесполезен, LRU отлично справляется с ситуацией всплеска трафика, потому что ему не нужно накапливать частоту данных.
Итак, W-TinyLFU сочетает в себе LRU и LFU, а также некоторые особенности других алгоритмов.
запись частоты
Первое, о чем нужно поговорить, это проблема записи частоты, цель, которую мы хотим достичь, это использовать ограниченное пространство для записи частоты доступа, которая меняется во времени. Мы используем Count-Min Sketch в W-TinyLFU для записи частоты посещений, что также является вариантом фильтра Блума. Как показано ниже:
Если нам нужно записать значение, то нам нужно хэшировать его с помощью нескольких алгоритмов хеширования, а затем добавить к записи соответствующего алгоритма хеширования 1. Зачем нам нужно несколько алгоритмов хеширования? Так как это алгоритм сжатия, конфликты обязательно возникнут.Например, мы создаем массив Long и вычисляем хэш-позицию каждого данных. Например, Чжан Сан и Ли Си могут иметь одинаковое хеш-значение. Например, если они оба равны 1, позиция Long[1] увеличит соответствующую частоту. Чжан Сан посещает 10 000 раз, а Ли Си посещает один раз. Long[1] 1] Эта позиция равна 10 001. Если вы возьмете показатель посещаемости Ли Си, это будет 10 001. Однако имя Ли Си было посещено только один раз. Чтобы решить эту проблему, можно использовать множественный алгоритм хеширования. следует понимать как понятие длинного [][] двумерного массива.Например, в первом алгоритме Чжан Сан и Ли Си конфликтуют, а во втором и третьем велика вероятность того, что конфликта нет, такие как Алгоритм имеет вероятность конфликта около 1%, а вероятность конфликта между четырьмя алгоритмами составляет 1% в четвертой степени. В этом режиме, когда мы берем частоту посещений Li Si, мы берем количество посещений Li Si с самой низкой частотой среди всех алгоритмов. Значит, его зовут Граф-Мин Скетч.Вот сравнение с предыдущим, вот простой пример: если хэш-карта записывает эту частоту, если у меня есть 100 данных, то этот хэш-карта должен хранить 100 частот доступа к этим данным. Даже если емкость моего кеша равна 1, из-за правил Lfu я все равно должен записывать частоту доступа этих 100 штук данных. Если есть больше данных, у меня больше записей.
В Count-Min Sketch позвольте мне прямо рассказать о реализации в caffeine (в классе FrequencySketch), если размер вашего кеша равен 100, он сгенерирует длинный массив, размер которого является ближайшей степенью от 2 до 100. , то есть 128 . И этот массив будет записывать нашу частоту обращения. В кофеине максимальная частота его правила равна 15, двоичный бит 15 равен 1111, всего 4 бита, а тип Long — 64 бита. Таким образом, каждый тип Long может поставить 16 алгоритмов, но кофеин этого не делает, используются только четыре алгоритма хеширования, каждый тип Long делится на четыре сегмента, и каждый сегмент хранит частоты четырех алгоритмов. Преимущество этого заключается в том, что конфликт хэшей может быть дополнительно уменьшен, и исходный хеш размером 128 становится 128X4.
Структура лонга выглядит следующим образом:
Наши 4 сегмента разделены на A, B, C, D, которые я буду называть позже. А четыре алгоритма в каждом сегменте я ему называю s1, s2, s3, s4. Вот пример: что мне делать, если я хочу добавить цифровую частоту с доступом к 50? Здесь мы используем size=100 в качестве примера.- Сначала определите, в каком сегменте находится хеш 50. С помощью хеш & 3 должно быть получено число меньше 4. Предполагая, что хэш & 3 = 0, он находится в сегменте A.
- Используйте другие хэш-алгоритмы, чтобы снова хэшировать 50 хэшей, чтобы получить позицию длинного массива. Предположим, что алгоритм s1 получает 1, алгоритм s2 получает 3, алгоритм s3 получает 4 и алгоритм s4 получает 0.
- Затем добавьте +1 к позиции s1 в сегменте A long[1], добавьте 1 к 1As1 для краткости, затем добавьте 1 к 3As2, добавьте 1 к 4As3 и добавьте 1 к 0As4.
В это время некоторые люди зададутся вопросом, не слишком ли мала максимальная частота 15? Это не имеет значения.В этом алгоритме, например, размер равен 100. Если он увеличится глобально в 1000 раз, он будет глобально делиться на 2 до распада.После распада он может продолжать увеличиваться.Этот алгоритм был доказал в документе W-TinyLFU, что его можно лучше адаптировать Частота посещений за период времени.
Чтение и запись производительности
В кэше гуавы мы сказали, что его операции чтения и записи смешаны с обработкой времени истечения срока действия, то есть вы также можете выполнять операцию исключения в операции размещения, поэтому его производительность чтения и записи будет в определенной степени затронута. Вы можете видеть изображение выше. Кофеин разрывает кеш гуавы при операциях чтения и записи. В основном потому, что в кофеине работа с этими событиями осуществляется через асинхронную операцию, и он отправляет события в очередь.Структура данных очереди здесь RingBuffer.Если вам непонятно, вы можете прочитать эту статью.Высокопроизводительный деструктор очередей без блокировки, о котором вы должны знать. Затем передайте ForkJoinPool.commonPool() по умолчанию или настройте пул потоков самостоятельно, выполните операцию извлечения из очереди, а затем выполните последующие операции исключения и истечения срока действия.
Конечно, есть разные очереди на чтение и на запись, в кофеине считается, что чтения кэша намного больше, чем записи, поэтому для операций записи все потоки совместно используют Ringbuffer.
Для операций чтения чаще, чем операций записи, чтобы еще больше снизить конкуренцию, каждый поток оснащен кольцевым буфером:
Политика удаления данных
Все данные в кофеине находятся в ConcurrentHashMap, который отличается от кеша гуавы, кеш гуавы реализует структуру, аналогичную ConcurrentHashMap сам по себе. В кофеине упоминаются три записи.LRUочередь:
-
Очередь Эдема: В кофеине оговорено, что она может составлять только %1 от емкости кеша, если size=100, то эффективный размер этой очереди равен 1. Вновь поступившие данные записываются в эту очередь, чтобы предотвратить устранение всплесков трафика из-за отсутствия частоты доступа ранее. Например, когда запускается новая драма, в начале фактически нет частоты доступа, чтобы предотвратить ее удаление другими кэшами после того, как она выйдет в сеть и присоединится к этой области. Иденский район, самый благоустроенный и благоустроенный район, здесь трудно исключить другими данными.
-
Очередь на пробацию: она называется очередью на пробацию.В этой очереди это означает, что ваши данные относительно холодные и скоро будут удалены. Эффективный размер равен размеру минус eden минус защищенный.
-
Защищенная очередь: в этой очереди вы можете быть уверены, что вы не будете временно исключены, но не волнуйтесь, если в очереди пробации нет данных или защищенные данные заполнены, вы также столкнетесь с неловкой ситуацией исключения. Конечно, если вы хотите стать этой очередью, вам нужно один раз получить доступ к испытательному сроку, и она будет повышена до защищенной очереди. Этот эффективный размер равен (размер минус eden) X 80%. Если размер = 100, будет 79.
Три очереди связаны следующим образом:
- Все новые данные попадают в Eden.
- Иден заполнен и исключен на испытательный срок.
- Если к одному из данных обращаются в испытательном сроке, данные обновляются до защищенных.
- Если Protected заполнен, он будет по-прежнему понижен до Probation.
Когда данные будут ликвидированы, они будут исключены из Пробации, а глава очереди данных в этой очереди будет называться жертвой.Эта голова очереди должна попасть первой.Согласно алгоритму LRU очереди , то он должен быть Ликвидирован, но здесь его можно назвать только жертвой.Эта очередь является испытательной очередью, а это значит, что он вот-вот будет казнен. Здесь хвост очереди называется кандидатом, также называемым злоумышленником. Здесь жертва будет делать ПК с злоумышленником, и следующие суждения делаются на основе данных о частоте, записанных в нашем скетче Count-Min:
- Если нападающий больше жертвы, жертва уничтожается напрямую.
- Если атакующий
Он считает, что установка порога разогрева приведет к более высокому общему количеству попаданий. - В остальных случаях случайная ликвидация.
как пользоваться
Для игроков, которые знакомы с Guava, если вы беспокоитесь о стоимости переключения, вам следует слишком сильно беспокоиться API-интерфейс Caffeine опирается на API-интерфейс Guava, и вы можете обнаружить, что он в основном такой же.
public static void main(String[] args) {
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.SECONDS)
.expireAfterAccess(1,TimeUnit.SECONDS)
.maximumSize(10)
.build();
cache.put("hello","hello");
}
Кстати, все больше и больше фреймворков с открытым исходным кодом отказались от кеша Guava, например, Spring5. В бизнесе я сам сравнил кеш гуавы и кофеин и, наконец, выбрал кофеин, который также имеет хорошие результаты в Интернете. Так что не волнуйтесь, что кофеин незрелый и его никто не использует.
Наконец
В этой статье в основном рассказывается о пути кэширования iQiyi и истории развития локального кэша (с древних времен до будущего), а также об основных принципах реализации каждого кэша. Конечно, недостаточно хорошо использовать кеш, например, как синхронно обновляется локальный кеш после изменений в других местах, распределенный кеш, многоуровневый кеш и так далее. Позже будет написан специальный раздел, в котором рассказывается, как правильно использовать этот кеш. Что касается принципов кеша Guava и кофеина, нам также понадобится время, чтобы написать анализ исходного кода этих двух.Если вам интересно, вы можете подписаться на официальный аккаунт, чтобы как можно скорее проверить обновленную статью.
Наконец, эта статья была включена в JGrowing, всеобъемлющий и отличный маршрут изучения Java, совместно созданный сообществом.Если вы хотите участвовать в обслуживании проектов с открытым исходным кодом, вы можете создать его вместе.Адрес github:GitHub.com/Java растет…Пожалуйста, дайте мне маленькую звезду.
Если вы считаете, что в этой статье есть статьи для вас, вы можете подписаться на мой технический паблик Ваше внимание и пересылка - самая большая поддержка для меня, O(∩_∩)O