предисловие
когдаRedis
При использовании в качестве кеша цель состоит в том, чтобы уменьшить частоту доступа к базе данных и уменьшить нагрузку на базу данных, но если некоторые из наших данных не существуют вRedis
Их, тогда запрос до сих пор доставит вас непосредственно в базу данных, и если бы большой запрос на недействительную кэшу одновременно не существует или кэш вредоносных атак на доступ к этим базам данных приведет к тому, как он должен это предотвратить?
Кэш Лавина
Кэш Лавина относится кRedis
Большое количество кешей недействительно одновременно, и если одновременно инициируется большое количество запросов, это приведет к тому, что запрос будет напрямую обращаться к базе данных, что может привести к перегрузке базы данных.
Лавина кеша обычно описывает данные, которые находятся не в кеше, а в базе данных, и запрос уходит в базу данных по истечению времени.
решение
Есть много способов решить проблему лавины кеша, и наиболее часто используемые из них следующие:
- Блокировка для обеспечения однопоточного доступа к кешу. Таким образом, одновременно к базе данных не будет поступать много запросов.
-
key
Время истечения срока действия значения не должно быть одинаковым. Как правило, при инициализации данных прогрева можно использовать произвольное время для сохранения данных в кэше, чтобы гарантировать, что не будет большого количества одновременных аннулирований кэша. - Кэш можно настроить так, чтобы он никогда не истекал, если позволяет память.
разбивка кеша
Разбивка кеша очень похожа на лавину кеша, разница в том, что разбивка кеша обычно относится к одному сбою кеша, и в то же время существует большое количество одновременных запросов, которым необходимо получить доступ к этомуkey
, тем самым вызывая нагрузку на базу данных.
решение
Решение для разбивки кеша очень похоже на решение для лавинного кеша:
- Блокировка для обеспечения однопоточного доступа к кешу. Таким образом, первый запрос доходит до базы данных, он перезаписывает кеш, последующие запросы читают кеш.
- Кэш можно настроить так, чтобы он никогда не истекал, если позволяет память.
проникновение в кеш
Существенная разница между проникновением в кэш и двумя вышеупомянутыми явлениями заключается в том, что данные, к которым осуществляется доступ в это время, находятся не только вRedis
Он не существует в базе данных и не существует в базе данных, поэтому, если параллелизм слишком велик, это приведет к тому, что данные будут постоянно поступать в базу данных, вызывая большую нагрузку на базу данных.
решение
Для проблемы проникновения в кеш блокировка не работает, потому что она сама по себеkey
Его просто не существует, поэтому, даже если количество потоков доступа контролируется, запросы будут продолжать поступать в базу данных.
Проникновение для решения проблем с кешем обычно можно использовать в сочетании со следующей программой:
- Уровень интерфейса проверяет и находит недопустимые
key
Вернуться напрямую. Например, в базе данных используется автоинкремент.id
, то если не целоеid
или отрицательныйid
Вы можете вернуться напрямую, или если вы используете32
немногоuuid
, затем найдитеid
длина не равна32
Биты также могут быть возвращены напрямую. - Кешировать несуществующие данные, вы можете напрямую кэшировать пустые или другие согласованные недействительные
value
. Таким способом лучше всегоkey
Установите короткое время истечения, иначе большое количество несуществующихkey
хранится вRedis
, это также займет много памяти.
Фильтр Блума
Для решения вышеупомянутого проникновения в кеш, давайте подумаем об этом: еслиkey
может обойти1
метод проверки, и в настоящее время существует большое количество несуществующихkey
доступен (например,1
Миллиард или10
100 миллионов), то хранить их все в памяти в это время нереально.
Так есть ли лучшее решение? Это фильтр Блума, который мы представим далее.Фильтр Блума может хранить как можно больше данных на минимально возможном пространстве.
Что такое фильтр Блума
Фильтр Блума (Bloom Filter) сделан Блумом в1970
Годовой предложенный. Это на самом деле очень длинный двоичный вектор (растровое изображение) и серия случайных функций сопоставленных (хэш-функции).
Фильтры Блума можно использовать для определения того, находится ли элемент в коллекции. Его преимущество в том, что эффективность использования памяти и время запроса намного лучше, чем у общего алгоритма, а недостаток в том, что должны быть определенные коэффициенты неправильного распознавания и трудности с удалением.
Битовая карта
Redis
Одной из структур данных является битовая карта, а важной реализацией фильтра Блума является реализация битовой карты, то есть битового массива, и каждая позиция в этом массиве имеет только0
и1
Два состояния, каждое положение занимает только1
байт, из них0
означает, что ни один элемент не существует,1
Указывает, что есть элемент. Как показано на рисунке ниже, является примером простого резинового фильтра (Одинkey
Значение хешируется и побитно, чтобы выяснить, куда оно должно попасть):
хэш-коллизия
Выше мы обнаружили, чтоlonely
иwolf
Он падает в том же положении, что разные значения ключевых значений, полученные после того же феномена хешированного значения, называютсяхэш-коллизия. После того, как произойдет коллизия хэшей, выполняется битовая операция, после чего он обязательно окажется в той же позиции.
Если будет слишком много хеш-коллизий, это повлияет на точность решения, поэтому, чтобы уменьшить количество хеш-коллизий, мы обычно учитываем следующее.2
факторы:
- Увеличьте размер массива растровых изображений (чем больше массив растровых изображений, тем больше памяти он занимает).
- Увеличьте количество раз, когда хеш-функция (та же самая
key
значение проходит1
Функции равны, то после2
Вероятность получения такого же результата при вычислении одной или нескольких хеш-функций, естественно, уменьшится).
Нам нужно рассматривать два вышеуказанных метода комплексно: например, увеличение битового массива будет занимать больше места, а чем больше хеш-вычислений, тем больше места будет потребляться.cpu
Это влияет на окончательное время расчета, поэтому, насколько велик битовый массив и сколько раз хеш-функция должна быть правильно рассчитана, требует специального анализа конкретной ситуации.
2 Особенности фильтров Блума
На картинке ниже прохождение2
Фильтр Блума, полученный второй хэш-функцией, согласно следующему рисунку, мы можем легко увидеть, что если нашRedis
вообще не существует, ноRedis
проходить через2
Две позиции, полученные после второй хеш-функции, уже1
(одинwolf
пройти черезf2
получить, одинNosql
пройти черезf1
, поэтому произошла коллизия хэшей, и поэтому фильтр Блума может ошибаться).
Таким образом, через описанное выше явление, с точки зрения фильтра Блума, мы можем сделать вывод, что фильтр Блума в основном имеет2
Большие возможности:
- Если фильтр Блума определяет, что элемент существует, то этот элементЭто может существовать.
- Если фильтр Блума определяет, что элемент не существует, то этот элементне должно существовать.
И с точки зрения элемента также можно сделать вывод, что2
Большие возможности:
- Фильтр Блума, если элемент действительно существуетдолжно быть оценено как существующее.
- Фильтр Блума, если элемент не существуетможно считать существующим.
PS:Следует отметить, что еслиN
Хеш-функция, вам нужно получитьN
локации1
можно считать существующим, пока существует0
, можно определить, что элемент не существует в фильтре Блума.
fpp
Потому что в фильтре Блума всегда будет ложное срабатывание, потому что коллизий хэшей невозможно избежать на 100%.Эта частота ложных срабатываний называется вероятностью ложных срабатываний фильтра Блума., а именно: Вероятность ложного срабатывания, называемаяfpp
.
При использовании фильтров Блума на практике вы можете определить их самостоятельно.fpp
, а затем вы можете рассчитать, сколько хеш-функций и сколько места битового массива необходимо в соответствии с теорией фильтра Блума. Следует отметить, что этоfpp
нельзя определить как100%
, потому что нет 100% гарантии от коллизий хэшей.
Реализация фильтра Блума (Guava)
существуетGuava
Реализация фильтра Блума предоставляется в пакете, и передается следующееGuava
Опыт применения фильтра Buron:
- вводить
pom
полагаться
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
- Создайте новый тестовый класс
BloomFilterDemo
:
package com.lonely.wolf.note.redis;
import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class GuavaBloomFilter {
private static final int expectedInsertions = 1000000;
public static void main(String[] args) {
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8),expectedInsertions);
List<String> list = new ArrayList<>(expectedInsertions);
for (int i = 0; i < expectedInsertions; i++) {
String uuid = UUID.randomUUID().toString();
bloomFilter.put(uuid);
list.add(uuid);
}
int mightContainNum1 = 0;
NumberFormat percentFormat =NumberFormat.getPercentInstance();
percentFormat.setMaximumFractionDigits(2); //最大小数位数
for (int i=0;i < 500;i++){
String key = list.get(i);
if (bloomFilter.mightContain(key)){
mightContainNum1++;
}
}
System.out.println("【key真实存在的情况】布隆过滤器认为存在的key值数:" + mightContainNum1);
System.out.println("-----------------------分割线---------------------------------");
int mightContainNum2 = 0;
for (int i=0;i < expectedInsertions;i++){
String key = UUID.randomUUID().toString();
if (bloomFilter.mightContain(key)){
mightContainNum2++;
}
}
System.out.println("【key不存在的情况】布隆过滤器认为存在的key值数:" + mightContainNum2);
System.out.println("【key不存在的情况】布隆过滤器的误判率为:" + percentFormat.format((float)mightContainNum2 / expectedInsertions));
}
}
Результат после запуска:
Вывод первой частиmightContainNum1
должно быть сfor
Равно значению в цикле, т.е. совпадение на сто процентов. Это соответствует принципу1
:Если элемент действительно существует, то фильтр Блума должен определить, что он существует..
Частота ложных срабатываний вывода второй части равнаfpp
всегда в3%
вокруг и сfor
Чем больше количество петель, тем теснее3%
. который удовлетворяет принципу2
:Если элемент не существует, то фильтр Блума может определить, что он существует..
это3%
Как возникла частота ложноположительных результатов? Переходим к созданию фильтра Блумаcreate
метод, обнаружил, что fpp по умолчанию0.03
:
для этого значения по умолчанию3%
изfpp
Сколько места нужно для битового массива и сколько раз получается хэш-функция? существуетBloomFilter
Ниже класса есть дваdefault
Метод может получить размер пространства битового массива и количество хеш-функций:
- OptimalnumOfHashfunctions: количество раз, чтобы получить хеш-функцию
- OPTIMALNUMOFBITS: получите размер битового массива
debug
Загляните внутрь:
Полученный результат7298440 bit=0.87M
, затем после5
Хэш-операция. Можно заметить, что занимаемая площадь очень мала,100W
изkey
только что занят0.87M
.
PS:кликните сюдаМожно ввести расчет сайтаbit
Размер массива и количество хеш-функций.
Как убрать фильтр Блума
Фильтр Блума для определения существования элемента заключается в том, чтобы определить, является ли соответствующая позиция1
определить, но если вы хотите удалить элемент, вы не можете напрямую поставить1
изменить на0
Да, потому что в этой позиции могут быть другие элементы, поэтому, если мы хотим поддержать удаление, что нам делать? Самый простой способ — добавить счетчик, т. е. если каждый бит битового массива не существует, он0
, есть несколько элементов для хранения конкретных чисел, а не только1
, то есть проблема, изначально1
Достаточно одной цифры, но если вы хотите сохранить определенные числа, например2
, то вам нужно2
немного, такBuron Filter с счетчиком занимает большую площадь.
Фильтр Блума со счетчиком
Вот пример фильтра Блума со счетчиком:
-
pom
Файл вводит зависимости:
<dependency>
<groupId>com.baqend</groupId>
<artifactId>bloom-filter</artifactId>
<version>1.0.7</version>
</dependency>
- Создайте новый фильтр Блума со счетчиком
CountingBloomFilter
:
package com.lonelyWolf.redis.bloom;
import orestes.bloomfilter.FilterBuilder;
public class CountingBloomFilter {
public static void main(String[] args) {
orestes.bloomfilter.CountingBloomFilter<String> cbf = new FilterBuilder(10000,
0.01).countingBits(8).buildCountingBloomFilter();
cbf.add("zhangsan");
cbf.add("lisi");
cbf.add("wangwu");
System.out.println("是否存在王五:" + cbf.contains("wangwu")); //true
cbf.remove("wangwu");
System.out.println("是否存在王五:" + cbf.contains("wangwu")); //false
}
}
Построить фронт фильтра Блума2
Один из параметров — это ожидаемое количество элементов, а другой —fpp
значение, послеcountingBits
Параметр - размер, занимаемый счетчиком,Вот8
бит, т.е. до255
Повторы, если не проходят, то вот по умолчанию16
Размер бита, то есть разрешить65535
повторения.
Суммировать
В этой статье в основном описывается использованиеRedis
Есть три вида проблем: лавина кеша, поломка кеша и проникновение в кеш. Решение каждой проблемы описывается отдельно, и, наконец, представлено решение проблемы проникновения в кеш: фильтр Блума. Родной фильтр Блума не поддерживает удаление, но можно ввести счетчик для реализации фильтра Блума со счетчиком для реализации функции удаления, в то же время, как было сказано в конце, фильтр Блума со счетчиком займет больше космическая проблема.