Проектирование систем кеширования: проникновение в кеш, разбивка кеша, анализ решения лавинного кеша

Java

предисловие

При проектировании системы кэширования необходимо учитывать следующие вопросы:Кэш проникает, и при лавинообразном сбое кеша происходит сбой.

проникновение в кеш

Проникновение в кэш относится к запросу фрагмента данных, который не должен существовать.Поскольку кеш пассивно записывается, когда он отсутствует, и по причинам отказоустойчивости, если данные не могут быть найдены на уровне хранилища, они не будут записаны в кеш, что приведет к этим несуществующим данным.Каждый раз, когда данные запрашиваются, они должны идти на уровень хранения для запроса, что теряет смысл кэширования.

При большом трафике БД может зависнуть, если кто-то использует несуществующий ключ для частой атаки на наше приложение, это уязвимость.

решение

Существует множество способов эффективного решения проблемы проникновения в кеш.Наиболее распространенным является использование фильтра Блума для хэширования всех возможных данных в достаточно большое растровое изображение, причем данные, которые не должны существовать, будут использоваться этим растровым изображением. тем самым избегая давления запросов на базовую систему хранения.

Кроме того, есть более простой и грубый способ (им мы и пользуемся): если данные, возвращаемые запросом, пусты (независимо от того, данных не существует или система дает сбой), мы все равно кэшируем пустой результат, но его время истечения будет коротким, до пяти минут.

Кэш Лавина

Cache Avalanger относится к тому же времени срока действия, когда мы устанавливаем кэш, вызывая медленную неудачу в определенное время, запросить все вперед в DB, ​​DB мгновенное давление подавляет.

решение

Лавинный эффект инвалидации кэша оказывает ужасное влияние на базовую систему. Большинство разработчиков систем рассматривают возможность использования блокировок или очередей для обеспечения однопоточной (процессной) записи в кэш, чтобы избежать большого количества одновременных запросов, попадающих в базовую систему хранения во время аннулирования.

Простое решение, представленное здесь, состоит в том, чтобы распределить время истечения срока действия кеша.Например, мы можем добавить случайное значение к исходному времени истечения срока действия, например, 1-5 минут случайным образом, чтобы частота повторения времени истечения срока действия каждого кеша была Сокращение Трудно инициировать событие коллективного отказа.

разбивка кеша

Для некоторых ключей с установленным сроком действия, если к этим ключам можно получить доступ с высокой степенью параллелизма в определенные моменты времени, это очень «горячие» данные. На данный момент есть проблема, которую необходимо рассмотреть: проблема "разбитого" кеша Отличие этого от лавинного кеша в том, что кеш для определенного ключа, а первый для многих ключей .

В момент времени, когда срок действия кеша истекает, именно в этот момент времени существует большое количество одновременных запросов, по которым этот ключ заканчивается, эти запросы обычно находятся в кеше, истечения срока действия загружают данные из серверной БД и отражаются обратно в кеш, на этот раз большой одновременный запрос может быть на мгновение перегружен внутренней БД.

решение

1. Используйте ключ мьютекса

Более распространенной практикой в ​​отрасли является использование мьютекса. Проще говоря, когда кеш недействителен (считается, что извлеченное значение пусто), вместо того, чтобы немедленно загрузить БД, сначала используйте некоторые операции инструмента кеша с возвращаемым значением успешной операции (например, SETNX или Memcache Redis) ADD), чтобы установить ключ мьютекса, когда операция завершается успешно, выполнить операцию загрузки базы данных и сбросить кеш; в противном случае повторите весь метод получения кеша.

SETNX — это сокращение от «SET if Not eXists», то есть он устанавливается только тогда, когда его не существует, и его можно использовать для достижения эффекта блокировки. До Redis 2.6.1 время истечения срока действия setnx не реализовывалось, поэтому вот две версии кода для справки:

//2.6.1前单机版本锁
String get(String key) {
	String value = redis.get(key);
	if (value  == null) {
		if (redis.setnx(key_mutex, "1")) {
			// 3 min timeout to avoid mutex holder crash  
			redis.expire(key_mutex, 3 * 60)  
			        value = db.get(key);
			redis.set(key, value);
			redis.delete(key_mutex);
		} else {
			//其他线程休息50毫秒后重试  
			Thread.sleep(50);
			get(key);
		}
	}
}

Код новой версии:

public String get(key) {
	String value = redis.get(key);
	if (value == null) {
		//代表缓存值过期
		//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
		if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {
			//代表设置成功
			value = db.get(key);
			redis.set(key, value, expire_secs);
			redis.del(key_mutex);
		} else {
			//这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
			sleep(50);
			get(key);
			//重试
		}
	} else {
		return value;
	}
}

код кэша памяти:

if (memcache.get(key) == null) {
	// 3 min timeout to avoid mutex holder crash  
	if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
		value = db.get(key);
		memcache.set(key, value);
		memcache.delete(key_mutex);
	} else {
		sleep(50);
		retry();
	}
}

2. Используйте блокировки мьютексов «заранее»

Установите значение таймаута (timeout1) внутри значения, timeout1 меньше фактического таймаута memcache (timeout2).

Когда таймаут1 считывается из кэша и обнаруживается, что он истек, таймаут1 немедленно продлевается и сбрасывается в кэш. Затем загрузите данные из базы данных и поместите их в кеш.

Псевдокод выглядит следующим образом:

v = memcache.get(key);
if (v == null) {
	if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
		value = db.get(key);
		memcache.set(key, value);
		memcache.delete(key_mutex);
	} else {
		sleep(50);
		retry();
	}
} else {
	if (v.timeout <= now()) {
		if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
			// extend the timeout for other threads  
			v.timeout += 3 * 60 * 1000;
			memcache.set(key, v, KEY_TIMEOUT * 2);
			// load the latest value from db  
			v = db.get(key);
			v.timeout = KEY_TIMEOUT;
			memcache.set(key, value, KEY_TIMEOUT * 2);
			memcache.delete(key_mutex);
		} else {
			sleep(50);
			retry();
		}
	}
}

3. «Никогда не истекает»

«Никогда не истекает» здесь имеет два значения:

(1) Из редиса не устанавливает время истечения, что гарантирует отсутствие проблем с истечением срока действия горячих ключей, то есть "физического" не предвидится.

(2) С функциональной точки зрения, если срок действия не истекает, разве он не статичен? Поэтому мы храним время истечения в значении, соответствующем ключу.Если обнаруживается, что оно вот-вот истечет, кэш строится через фоновый асинхронный поток, то есть «логическое» истечение

С практической точки зрения этот метод очень удобен для производительности, единственный недостаток в том, что при построении кеша другие потоки (потоки, которые не строят кеш) могут обращаться к старым данным, но это все еще терпимо для общего интернета. функции.

String get(final String key) {
	V v = redis.get(key);
	String value = v.getValue();
	long timeout = v.getTimeout();
	if (v.timeout <= System.currentTimeMillis()) {
		// 异步更新后台异常执行  
		threadPool.execute(new Runnable() {
			public void run() {
				String keyMutex = "mutex:" + key;
				if (redis.setnx(keyMutex, "1")) {
					// 3 min timeout to avoid mutex holder crash  
					redis.expire(keyMutex, 3 * 60);
					String dbValue = db.get(key);
					redis.set(key, dbValue);
					redis.delete(keyMutex);
				}
			}
		}
		);
	}
	return value;
}

4. Защита ресурсов

С помощью hystrix от netflix можно изолировать ресурсы для защиты основного пула потоков.Если применить это к построению кеша, то неплохо.

Четыре решения: лучшего нет, есть самое подходящее


Суммировать

Для бизнес-систем это всегда в каждом конкретном анализе конкретных ситуаций.Нет лучшего, есть самое подходящее.

Наконец, общие проблемы с переполнением кеша и потерей данных в системе кеша необходимо анализировать в соответствии с конкретным бизнесом.Обычно мы используем стратегию LRU для борьбы с переполнением и стратегии сохранения RDB и AOF Redis для обеспечения безопасность данных при определенных обстоятельствах.

Для получения дополнительных технических статей, пожалуйста, обратите внимание на публичный аккаунт WeChat: место сбора Java-программистов.