Redis önbellekleme sorunlarının ayrıntılı açıklaması

önbellek delinmesi
Önbellek delinmesi, istemci tarafından talep edilen verinin hem önbellekte hem de veritabanında bulunmaması anlamına gelir. Bu durumda önbellek hiçbir zaman etkili olmaz ve bu istekler doğrudan veritabanına ulaşır.
Kötü niyetli bir kullanıcı, var olmayan verilere eşzamanlı erişim sağlamak için çok sayıda iş parçacığı kullanırsa, tüm bu istekler veritabanına ulaşarak veritabanının çökmesine neden olabilir.
Çözüm
Boş nesneleri önbelleğe alma
Fikir: Bir kullanıcı belirli bir kimliği talep ettiğinde ve bu kimlik ne Redis'te ne de veritabanında mevcutsa, bu kimliğe karşılık gelen boş değeri doğrudan Redis'e önbelleriz. Böylece kullanıcı aynı kimliği tekrar talep ettiğinde Redis bir vuruş gerçekleştirir (boş değer vuruşu) ve veritabanına sorgu gönderilmez.
Avantajları: Uygulaması basittir, bakımı kolaydır.
Dezavantajları:

Ek bellek tüketimi (TTL eklenerek çözülebilir).


- Kısa süreli tutarsızlığa neden olabilir (TTL süresini kontrol etmek bu durumu belirli bir ölçüde hafifletebilir): Boş değer önbelleğe alındığında veritabanında ilgili veriyi ayarlarsak, kullanıcı sorgusu boş döner ancak veri aslında veritabanında mevcuttur; bu da tutarsızlık yaratır (veri eklerken önceki boş verinin otomatik olarak üzerine yazılmasıyla çözülebilir).
Bloom filtresi
İstemci ile Redis arasına bir Bloom filtresi katmanı eklenir. Kullanıcı erişim sağladığında, verinin var olup olmadığını belirlemek için Bloom filtresi kullanılır. Veri mevcut değilse istek doğrudan reddedilir; mevcutsa normal süreç işlenir.
Bloom filtresi verinin var olup olmadığını nasıl belirler?

Bloom filtresi basitçe ikili bitleri depolayan bir bayt dizisi olarak anlaşılabilir. Veritabanındaki verinin var olup olmadığını belirlemek gerektiğinde, veri doğrudan Bloom filtresinde saklanmaz; bunun yerine hash algoritması aracılığıyla hash değeri hesaplanır. Ardından bu hash değerleri ikili bitlere dönüştürülerek Bloom filtresinde saklanır. Verinin varlığı değerlendirilirken ilgili konumun 0 veya 1 olduğu kontrol edilir (bu tür bir varlık tespiti olasılıksal bir istatistiktir ve %100 doğru değildir; yani veri yoksa mutlaka var olmadığı anlamına gelmez, bu nedenle yine de delinme riski bulunmaktadır).
Avantajları: Daha az bellek kullanımı, gereksiz anahtarlar yok (ikili).
Dezavantajları:

Uygulaması karmaşıktır.
Yanlış değerlendirme olasılığı vardır (mutlaka doğru değildir).


Boş nesneleri önbelleğe alma Java uygulaması
/**
* Önbellek delinmesi
*
* @param id
* @return
*/
public Shop queryWithPassThrough(Long id) {
String key = CACHE_SHOP_KEY + id;
// 1. Mağaza önbelleğini Redis'ten sorgula
String shopJson = stringRedisTemplate.opsForValue().get(key);
// 2. Var olup olmadığını kontrol et
if (StrUtil.isNotBlank(shopJson)) {
// 3. Varsa doğrudan döndür
return JSONUtil.toBean(shopJson, Shop.class);
}
// Vuruşun boş değer olup olmadığını belirle
if (shopJson != null) {
// hata mesajı döndür
return null;
}
// 4. Mevcut değilse, kimliğe göre veritabanını sorgula
Shop shop = getById(id);
// 5. Mevcut değilse, hata döndür
if (shop == null) {
// boş değeri Redis'e yaz (önbellek delinmesi)
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
// 6. Mevcutsa, Redis'e yaz
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
// 7. Döndür
return shop;
}
Önbellek Çığlaşması
Önbellek Çığlaşması, çok sayıda önbellek anahtarının aynı anda geçersiz hale gelmesi veya Redis hizmetinin aynı anda çevrimdışı olması anlamına gelir. Bu durum, çok sayıda isteğin veritabanına ulaşmasına neden olarak büyük bir baskı yaratır.


Çözüm

Farklı anahtarların TTL değerlerine rastgele sayılar ekleyin (eşzamanlı geçersizleşme sorununu çözmek için): Örneğin, önbellek ısınması sırasında veritabanındaki verilerin toplu olarak önceden önbelleğe aktarılması gerekir. Veriler aynı anda aktarıldığından, bu verilerin TTL değeri de aynı olur. Bu durum, belirli bir noktada verilerin aynı anda süresinin dolmasına ve çığlaşmaya yol açabilir. Bu sorunu çözmek için aktarım sırasında TTL'ye rastgele bir sayı ekleyebiliriz (örneğin, TTL 30±1~5). Böylece bu anahtarların son kullanma süreleri belirli bir zamana yayılır ve aynı anda geçersiz hale gelmeleri önlenerek çığlaşma riski ortadan kaldırılır.
Hizmet kullanılabilirliğini artırmak için Redis kümesi kullanın (Redis kesinti süresini çözmek için): Redis sentinel mekanizması sayesinde, bir makine çöktüğünde sentinel otomatik olarak yerine başka bir makine seçer. Ana ve yedek düğümler verileri senkronize ederek yüksek Redis performansını ve kullanılabilirliğini garanti eder.
Önbellek iş mantığına düşürme ve akım sınırlama stratejisi ekleyin: Hızlı başarısızlık, hizmet reddi ve isteklerin veritabanına yönlendirilmesinin engellenmesi gibi yöntemler uygulanabilir.
İş mantığına çok katmanlı önbellekler ekleyin: Tarayıcılara önbellek eklenebilir (genellikle statik kaynaklar için), ters proxy sunucusu NGINX'e önbellek eklenebilir. NGINX önbellek ıskalamasında Redis'e istek gider, Redis ıskalamasında JVM'e ulaşılır. JVM içinde de yerel bir önbellek oluşturulabilir ve son olarak veritabanına erişilir.
önbellek kırılması
Sıcak anahtar problemi olarak da bilinen önbellek kırılması, yüksek eşzamanlı erişime maruz kalan ve karmaşık önbellek yeniden oluşturma iş mantığına sahip bir anahtarın aniden başarısız olmasıdır. Sayısız istek erişimi, bir anda veritabanı üzerinde büyük bir etki yaratır.
Önbellek yeniden oluşturma: Redis'teki önbelleğin süresi dolduğunda geçersiz hale gelir ve sürenin dolmasının ardından veritabanından yeniden sorgulanıp Redis'e yazılması gerekir. Veritabanından veri sorgulama ve yapılandırma süreci karmaşık olabilir; çoklu tablo birleştirme sorguları vb. gerekebilir ve sonuçlar nihayetinde önbelleğe alınır. Bu iş mantığı uzun sürebilir (onlarca hatta yüzlerce milisaniye). Bu süre zarfında Redis'te önbellek bulunmaz ve gelen istekler veritabanına erişmek üzere ıskalama yapar.


Çözüm
Karşılıklı dışlama kilidi
Bir iş parçacığı isteğinde ıskalama tespit edildiğinde, veritabanı sorgulanmadan önce kilit işlemi gerçekleştirilir ve önbelleğe yazıldıktan sonra kilit serbest bırakılır. Böylece, diğer iş parçacıkları ıskalama yaptığında veritabanını sorgularken karşılıklı dışlama kilidini almaya çalışır. Alma işlemi başarısız olursa, belirli bir süre bekletilir ve ardından tekrar sorgulanır.
Açıkçası, diğer iş parçacıkları verileri ancak önbelleğe yazıldıktan sonra elde edebilir. Tutarlılık garanti edilse de performans nispeten düşüktür ve kilitlenmeye neden olabilir.


Java uygulaması


/**
* Kilidi al
*
* @param key
* @return
*/
private boolean tryLock(String key) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}


/**
* Kilidi serbest bırak
*
* @param key
*/
private void unlock(String key) {
stringRedisTemplate.delete(key);
}


/**
* Karşılıklı dışlama kilidi
*
* @param id
* @return
*/
public Shop queryWithMutex(Long id) {
String key = CACHE_SHOP_KEY + id;
// 1. Mağaza önbelleğini Redis'ten sorgula
String shopJson = stringRedisTemplate.opsForValue().get(key);
// 2. Var olup olmadığını kontrol et
if (StrUtil.isNotBlank(shopJson)) {
// 3. Varsa doğrudan döndür
return JSONUtil.toBean(shopJson, Shop.class);
}
// Vuruşun boş değer olup olmadığını belirle
if (shopJson != null) {
// hata mesajı döndür
return null;
}

// 4. Önbellek yeniden oluşturmayı uygula
// 4.1 Karşılıklı dışlama kilidini al
String lockKey = LOCK_SHOP_KEY + id;
Shop shop = null;
try {
boolean isLock = tryLock(lockKey);
// 4.2 Almanın başarılı olup olmadığını belirle
if (!isLock) {
// 4.3 Başarısız, beklet ve tekrar dene
Thread.sleep(50);
// yinelemeli çağrı
return queryWithMutex(id);
}
// 4.4 Başarılı, kimliğe göre veritabanını sorgula
shop = getById(id);
// yeniden oluşturma gecikmesini simüle et
Thread.sleep(200);
// 5. Mevcut değilse, hata döndür
if (shop == null) {
// boş değeri Redis'e yaz (önbellek delinmesi)
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
// 6. Mevcutsa, Redis'e yaz
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 7. Karşılıklı dışlama kilidini serbest bırak
unlock(lockKey);
}
// 8. Döndür
return shop;
}
JMeter ile test edelim; 1000 istek gönderdiğimizde tüm isteklerin geçtiğini ve veritabanının yalnızca bir kez sorgulandığını görebiliriz.



mantıksal süre sonu
Adından da anlaşılacağı gibi, bu gerçekten bir süre sonu değildir; asla süresi dolmayan bir yapı olarak görülebilir. Verileri Redis'e önbelleğe aldığımızda TTL ayarlamayız. Bunun yerine, verileri depolarken bir son kullanma zamanı alanı ekleriz (TTL değil, geçerli zaman + son kullanma süresi temelinde mantıksal olarak korunan bir zamandır). Böylece herhangi bir iş parçacığı bunu sorguladığında vuruş alır ve yalnızca mantıksal olarak süresinin dolup dolmadığını belirlemesi yeterlidir.
Aşağıdaki şekilde gösterildiği gibi, iş parçacığı 1 önbelleği sorgularken mantıksal zamanın dolduğunu fark ederse, önbelleği yeniden oluşturması ve ardından karşılıklı dışlama kilidini alması gerekir. ) yerine önbellek yeniden oluşturma işlemini kendisi gerçekleştirmez. Önbellek yeniden oluşturma tamamlandıktan sonra kilit serbest bırakılır ve iş parçacığı 1 süresi dolmuş veriyi doğrudan döndürür. Diğer iş parçacıkları da ıskalama yaptığında, karşılıklı dışlama kilidini alamazlarsa doğrudan süresi dolmuş veriyi döndürürler. Performans garanti edilse de tutarlılık garanti edilemez.


Java uygulaması
/**
* Önbellek ısınması
*
* @param id
* @param expireSeconds mantıksal son kullanma süresi
*/
public void saveShop2Redis(Long id, Long expireSeconds) throws InterruptedException {
// 1. Mağaza verilerini sorgula
Shop shop = getById(id);
Thread.sleep(200);
// 2. Mantıksal son kullanma süresini kapsülle
RedisData redisData = new RedisData();
redisData.setData(shop);
redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
// 3. Redis'e yaz
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}

private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

/**
* Mantıksal süre sonu
*
* @param id
* @return
*/
public Shop queryWithLogicalExpire(Long id) {
String key = CACHE_SHOP_KEY + id;
// 1. Mağaza önbelleğini Redis'ten sorgula
String shopJson = stringRedisTemplate.opsForValue().get(key);
// 2. Var olup olmadığını kontrol et
if (StrUtil.isBlank(shopJson)) {
// 3. Iskalama, doğrudan döndür
return null;
}
// 4. Vuruş, önce json'u nesneye serileştirmeyi kaldırmanız gerekir
RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
LocalDateTime expireTime = redisData.getExpireTime();
// 5. Süresinin dolup dolmadığını belirle
if (expireTime.isAfter(LocalDateTime.now())) {
// 5.1 Süresi dolmamışsa, mağaza bilgilerini doğrudan döndür
return shop;
}
// 5.2 Süresi dolmuş, yeniden oluşturulması gerekiyor
// 6. Önbellek yeniden oluşturma
// 6.1 Karşılıklı dışlama kilidini al
String lockKey = LOCK_SHOP_KEY + id;
boolean isLock = tryLock(lockKey);
// 6.2 Kilidin başarıyla alınıp alınmadığını belirle
if (isLock) {
// 6.3 Başarılı, önbellek yeniden oluşturmayı gerçekleştirmek için bağımsız bir iş parçacığı aç
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
// önbelleği yeniden oluştur
this.saveShop2Redis(id, 20L);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// kilidi serbest bırak
unlock(lockKey);
}
});
}
// 6.4 Süresi dolmuş mağaza bilgilerini döndür
return shop;
}

İlgili Makaleler

Daha Fazla Özel Teklifi Keşfedin

  1. Short Message Service (SMS) & Posta Hizmeti

    50.000 e-posta paketi 1,99 USD'den başlayan fiyatlarla, 120 kısa mesaj ise yalnızca 1,00 USD'den başlamaktadır.

phone Bize Ulaşın
Hi, I'm Alibaba Cloud AI Assistant!
I can help with questions and solutions.