在大資料時代,由于網路請求的并發,導致的資料庫的I/O開銷巨大,所以為了緩解資料庫的壓力,快取技術必不可少,而這其中redis基本是服務器的快取服務之一,雖然快取技術很好用,但是也會出現各種各樣的問題,這里就最常見的三種問題進行原理分析和解決,希望能夠給大家帶來幫助,
-
快取穿透:key中對應的快取資料不存在,導致去請求資料庫,造成資料庫的壓力倍增的情況
-
快取擊穿:redis過期后的一瞬間,有大量用戶請求同一個快取資料,導致這些請求都去請求資料庫,造成資料庫壓力倍增的情,針對一個key而言
-
快取雪崩:快取服務器宕機或者大量快取集中某個時間段失效,導致請求全部去到資料庫,造成資料庫壓力倍增的情況,這個是針對多個key而言
一、快取穿透的解決方案
常用方法可以采用布隆過濾器方法進行資料攔截,其次可以還有一種解決思路,就是如果請求的資料為空,將空值也進行快取,就不會發生穿透情況
<?php
class getPrizeList {
/**
* redis實體
* @var \Redis
*/
private $redis;
/**
* @var string
*/
private $redis_key = '|prize_list';
/**
* 過期時間
* @var int
*/
private $expire = 30;
/**
* getPrizeList constructor.
* @param $redis
*/
public function __construct($redis)
{
$this->redis = $redis;
}
/**
* @return array|bool|string
*/
public function fetch()
{
$result = $this->redis->get($this->redis_key);
if(!isset($result)) {
//此處應該進行資料庫查詢...
//如果查詢結果不存在,給其默認空陣列進行快取
$result = [];
$this->redis->set($this->redis_key, $result, $this->expire);
}
return $result;
}
}
二、快取擊穿解決辦法
使用互斥鎖(mutex key),就是一個key過期時,多個請求過來允許其中一個請求去操作資料庫,其他請求等待第一個請求成功回傳結果后再請求,
<?php
class getPrizeList {
/**
* redis實體
* @var \Redis
*/
private $redis;
/**
* @var string
*/
private $redis_key = '|prize_list';
/**
* @var string
*/
private $setnx_key = '|prize_list_setnx';
/**
* 過期時間
* @var int
*/
private $expire = 30;
/**
* getPrizeList constructor.
* @param $redis
*/
public function __construct($redis)
{
$this->redis = $redis;
}
/**
* @return array|bool|string
*/
public function fetch()
{
$result = $this->redis->get($this->redis_key);
if(!isset($result)) {
if($this->redis->setnx($this->setnx_key, 1, $this->expire)) {
//此處應該進行資料庫查詢...
//$result = 資料庫查詢結果;
$this->redis->set($this->redis_key, $result, $this->expire);
$this->redis->del($this->setnx_key); //洗掉互斥鎖
} else {
//其他請求每等待10毫秒重新請求一次
sleep(10);
self::fetch();
}
}
return $result;
}
}
三、快取雪崩的解決辦法
這種情況是因為多個key同時過期導致的資料庫壓力,一種方法可以在key過期時間基礎上增加時間亂數,讓過期時間分散開,減少快取時間過期的重復率另一種方法就是加鎖排隊,這種有點像上面快取擊穿的解決方式,但是這種請求量太大,比如5000個請求過來,4999個都需要等待,這必然是指標不治本,不僅用戶體驗性差,分布式環境下就更加復雜,因此在高并發場景下很少使用最好的解決方法,是使用快取標記,判斷該標記是否過期,過期則去請求資料庫,而快取資料的過期時間要設定的比快取標記的長,這樣當一個請求去操作資料庫的時候,其他請求拿的是上一次快取資料
<?php
class getPrizeList {
/**
* redis實體
* @var \Redis
*/
private $redis;
/**
* @var string
*/
private $redis_key = '|prize_list';
/**
* 快取標記key
* @var string
*/
private $cash_key = '|prize_list_cash';
/**
* 過期時間
* @var int
*/
private $expire = 30;
/**
* getPrizeList constructor.
* @param $redis
*/
public function __construct($redis)
{
$this->redis = $redis;
}
/**
* @return array|bool|string
*/
public function fetch()
{
$cash_result = $this->redis->get($this->cash_key);
$result = $this->redis->get($this->redis_key);
if(!$cash_result) {
$this->redis->set($this->cash_key, 1, $this->expire);
//此處應該進行資料庫查詢...
//$result = 資料庫查詢結果, 并且設定的時間要比cash_key長,這里設定為2倍;
$this->redis->set($this->redis_key, $result, $this->expire * 2);
$this->redis->del($this->cash_key); //洗掉互斥鎖
}
return $result;
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/85758.html
標籤:區塊鏈
上一篇:2020研究生數學建模大賽
