Redis作為一款高效能的快取資料庫,為許多應用提供了快速的資料存取和儲存能力。然而,在使用Redis時,我們不可避免地會面對一些常見的問題,如快取雪崩、快取穿透和快取擊穿。本文將深入探討這些問題的本質,以及針對這些問題的解決方案。
在某個時間點,快取中的大量資料同時過期失效。
Redis宕機。
因以上兩點導致大量請求直接打到資料庫,從而引發資料庫壓力激增,甚至崩潰的現象。
將 redis 中的 key 設定為永不過期,或者TTL過期時間間隔開
import redis.clients.jedis.Jedis;
public class RedisExpirationDemo {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// Key for caching
String key = "my_data_key";
String value = "cached_value";
int randomExpiration = (int) (Math.random() * 60) + 1; // Random value between 1 and 60 seconds
jedis.setex(key, randomExpiration, value);//設定過期時間
jedis.set(hotKey, hotValue);//永不過期
// Retrieving data
String cachedValue = jedis.get(key);
System.out.println("Cached Value: " + cachedValue);
// Closing the connection
jedis.close();
}
}
使用 redis 快取叢集,實現主從叢集高可用
ehcache本地快取 + redis 快取
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import redis.clients.jedis.Jedis;
public class EhcacheRedisDemo {
public static void main(String[] args) {
// Configure ehcache
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();
cacheManager.init();
Cache<String, String> localCache = cacheManager.createCache("localCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class));
// Configure Redis
Jedis jedis = new Jedis("localhost", 6379);
String key = "data_key";
String value = "cached_value";
// Check if data is in local cache
String cachedValue = localCache.get(key);
if (cachedValue != null) {
System.out.println("Value from local cache: " + cachedValue);
} else {
// Retrieve data from Redis and cache it locally
cachedValue = jedis.get(key);
if (cachedValue != null) {
System.out.println("Value from Redis: " + cachedValue);
localCache.put(key, cachedValue);
} else {
System.out.println("Data not found.");
}
}
// Closing connections
jedis.close();
cacheManager.close();
}
}
限流降級
限流降級需要結合其他工具和框架來實現,比如 Sentinel、Hystrix 等。
快取穿透指的是惡意或者非法的請求,其請求的資料在快取和資料庫中均不存在,由於大量的請求導致直接打到資料庫,造成資料庫負載過大。
使用布隆過濾器:布隆過濾器是一種資料結構,用於快速判斷一個元素是否存在於集合中。部署在Redis的前面,去攔截資料,減少對Redis的衝擊,將所有可能的查詢值都加入布隆過濾器,當一個查詢請求到來時,先經過布隆過濾器判斷是否存在於快取中,避免不必要的資料庫查詢。
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import redis.clients.jedis.Jedis;
public class BloomFilterDemo {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// Create and populate a Bloom Filter
int expectedInsertions = 1000;
double falsePositiveRate = 0.01;
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(), expectedInsertions, falsePositiveRate);
String key1 = "data_key_1";
String key2 = "data_key_2";
String key3 = "data_key_3";
bloomFilter.put(key1);
bloomFilter.put(key2);
// Check if a key exists in the Bloom Filter before querying the database
String queryKey = key3;
if (bloomFilter.mightContain(queryKey)) {
String cachedValue = jedis.get(queryKey);
if (cachedValue != null) {
System.out.println("Cached Value: " + cachedValue);
} else {
System.out.println("Data not found in cache.");
}
} else {
System.out.println("Data not found in Bloom Filter.");
}
// Closing the connection
jedis.close();
}
}
快取空值:如果某個查詢的結果在資料庫中確實不存在,也將這個空結果快取起來,但設定一個較短的過期時間,防止攻擊者頻繁請求同一不存在的資料。
import redis.clients.jedis.Jedis;
public class CacheEmptyValueDemo {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
String emptyKey = "empty_key";
String emptyValue = "EMPTY";
// Cache an empty value with a short expiration time
jedis.setex(emptyKey, 10, emptyValue);
// Check if the key exists in the cache before querying the database
String queryKey = "nonexistent_key";
String cachedValue = jedis.get(queryKey);
if (cachedValue != null) {
if (cachedValue.equals(emptyValue)) {
System.out.println("Data does not exist in the database.");
} else {
System.out.println("Cached Value: " + cachedValue);
}
} else {
System.out.println("Data not found in cache.");
}
// Closing the connection
jedis.close();
}
}
非法請求限制
對非法的IP或賬號進行請求限制。
異常引數校驗,如id=-1、引數空值。
快取擊穿指的是一個查詢請求針對一個在資料庫中存在的資料,但由於該資料在某一時刻過期失效,導致請求直接打到資料庫,引發資料庫負載激增。
import redis.clients.jedis.Jedis;
public class HotDataNeverExpireDemo {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
String hotKey = "hot_data_key";
String hotValue = "hot_cached_value";
// Set the hot key with no expiration
jedis.set(hotKey, hotValue);
// Retrieving hot data
String hotCachedValue = jedis.get(hotKey);
System.out.println("Hot Cached Value: " + hotCachedValue);
// Closing the connection
jedis.close();
}
}
import redis.clients.jedis.Jedis;
public class MutexLockDemo {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
String mutexKey = "mutex_key";
String mutexValue = "locked";
// Try to acquire the lock
Long lockResult = jedis.setnx(mutexKey, mutexValue);
if (lockResult == 1) {
// Lock acquired, perform data regeneration here
System.out.println("Lock acquired. Generating cache data...");
// Simulating regeneration process
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Release the lock
jedis.del(mutexKey);
System.out.println("Lock released.");
} else {
System.out.println("Lock not acquired. Another thread is regenerating cache data.");
}
// Closing the connection
jedis.close();
}
}
import redis.clients.jedis.Jedis;
import java.util.concurrent.CompletableFuture;
public class AsyncCacheUpdateDemo {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
String key = "data_key";
String value = "cached_value";
// Set initial cache
jedis.setex(key, 60, value);
// Simulate data update
CompletableFuture<Void> updateFuture = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(3000); // Simulate time-consuming update
String updatedValue = "updated_value";
jedis.setex(key, 60, updatedValue);
System.out.println("Cache updated asynchronously.");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// Do other work while waiting for the update
System.out.println("Performing other work while waiting for cache update...");
// Wait for the update to complete
updateFuture.join();
// Retrieve updated value
String updatedCachedValue = jedis.get(key);
System.out.println("Updated Cached Value: " + updatedCachedValue);
// Closing the connection
jedis.close();
}
}
在使用Redis時,快取雪崩、快取穿透和快取擊穿是常見的問題,但通過合理的設定快取策略、使用資料結構和鎖機制,以及採用非同步更新等方法,可以有效地減少甚至避免這些問題的發生。因此,在入門Redis後,不應因為這些問題而輕易放棄,而是應當深入瞭解並採取相應的解決方案,以充分發揮Redis在提升應用效能方面的優勢。
作者:伊力程式設計
路過別錯過,點個關注,謝謝支援