Redis 必知必會

liuqing_hu發表於2019-03-13

原文是公司小夥伴內部分享的,後續會續上擴散問題相關資料,歡迎小夥伴在評論區裡補充資料。

Redis 必知必會

一、redis需要掌握的知識點

  • 架構:單執行緒
  • 資料型別及其適用場景:5種
  • 命令的熟悉度(http://doc.redisfans.com/index.html
  • 慢查詢分析
  • pipeline的使用
  • redis與lua指令碼的使用
  • redis持久化:rdb && aof區別及各自特點
  • redis複製
  • redis記憶體怎麼管理:記憶體使用統計,記憶體回收策略,記憶體優化等
  • redis叢集

二、redis常見應用場景 && 一些注意的地方

排行榜,計數器,社交網路,訊息佇列等

場景1: 遍歷一個set || zset || hash 匹配某個pattern的所有元素。

/**
 * 給使用者發放獎勵
 */
public function runSendReward()
{
    $it = null;
    $this->_redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_RETRY);
    while ($arr_keys = $this->_redis->getRealConnect()->hScan(self::KEY_INVITE_PUPIL_REWARD, $it)) {
        foreach ($arr_keys as $memberId => $time) {
            if ($this->_redis->hExists(self::KEY_INVITE_SEND_REWARD, $memberId)) {
                continue;
            }
            $validPupils = $this->getPupils($memberId);
            $reward = $this->getRewardsByRule($validPupils);
            $reward = $reward['reward'];
            $ret = $this->sendActReward($memberId, $validPupils, $reward);
            if (false == $ret) {
                echo "Faild : member_id : {$memberId} \r\n";
            }
        }
    }
}

2、增量式迭代命令:

SCAN, HSCAN, SSCAN, ZSCAN

優點:

從完整遍歷開始直到完整遍歷結束期間, 一直存在於資料集內的所有元素都會被完整遍歷返回; 這意味著, 如果有一個元素, 它從遍歷開始直到遍歷結束期間都存在於被遍歷的資料集當中, 那麼 SCAN 命令總會在某次迭代中將這個元素返回給使用者。

缺點:

1、同一個元素可能會被返回多次。 處理重複元素的工作交由應用程式負責, 比如說, 可以考慮將迭代返回的元素僅僅用於可以安全地重複執行多次的操作上。

2、如果一個元素是在迭代過程中被新增到資料集的, 又或者是在迭代過程中從資料集中被刪除的, 那麼這個元素可能會被返回, 也可能不會, 這是未定義的(undefined)。

注意:

1、遍歷的時候處理重複出現的元素。

場景2: 注意分片 && 加鎖

public function withdraw($memberId, $amount = '')
{
    if (empty($amount)) {
        $this->_err = self::$ERR_WITHDRAW_ERROR;
        return false;
    }

    //商城限制最高提現額度
    $maxWithdrawOnce = self::MAX_WITHDRAW_ONCE;
    if ($amount > $maxWithdrawOnce) {
        $leftAmount = bcsub($amount, $maxWithdrawOnce);
    } else {
        $leftAmount = 0;
    }

    $oLock = new Lock($this->_redis); //注意一定要枷鎖
    $keyLock = $this->_getKeyWithdrawLock($memberId);
    if (!$oLock->acquire($keyLock, 0, 10)) {
        $this->_err = self::$ERR_WITHDRAW_DIFF_TIME;
        return false;
    }
    $params = [];
    $result = Servbox()->Mall_Order()->addOrderScene($memberId, $params);
    if (!$result) {
        $err              = Servbox()->Mall_Order()->getErrMsg();
        $debug['err_msg'] = $err;
        RegBox()->Log()->info("[actMS3] withdraw error . data: " . json_encode($debug));
        $this->_err = $err;
        return false;
    }

    //儲存提現記錄,扣除餘額
    $keyAmount = $this->_getKeyAmount($memberId);
    $this->_redis->hSet($keyAmount, $memberId, bcmul($leftAmount, Model\Balance::UNIT_SCALE));
    $this->_setKeyExpire($keyAmount);
    $oLock->release($keyLock);

    return true;
}

//使用者餘額key
private function _getKeyAmount($memberId)
{
    $mod = $memberId % self::SLICE_NUM; 
    return sprintf(self::$KEY_ACT_AMOUNT, $mod);
}

場景3: 排行榜 && 佇列

/**
 * @desc 新增中獎資訊到列表
 * @param int $memberId
 * @param int $coinRewardNum
 */
private function _addTopList($memberId, $coinRewardNum)
{
    $key = $this->_getTopListKey();
    $statExpireDay = (int)$this->_conf['stat_expire_day'];
    if ($this->_redis->exists($key)) {
        $this->_redis->zIncrBy($key, $coinRewardNum, $memberId);
    } else {
        $this->_redis->zAdd($key, $coinRewardNum, $memberId);
    }
    $this->_redis->expire($key, self::DAY_SECONDS * $statExpireDay);
}

/**
 *  獲取排行榜
 * @return array
 */
public function getTopList($num = 10)
{
    $key = $this->_getTopListKey();
    $list = $this->_redis->zRevRangeByScore($key,
        '+inf',
        '-inf',
        ['limit' => [0, $num + 5], 'withscores' => true]);

    $data = [];
    if ($list) {
        foreach ($list as $key => $val) {
            $nickname = $this->_container->Member()->getMemberInfoByField($key, 'nickname');
            if (!$nickname) continue;
            if (count($data) == $num) break;
            $temp['member_id'] = 'A'.$this->_hideMemberId($key);
            $temp['coin'] = $val;
            $temp['nickname'] = $nickname;
            $data [] = $temp;
        }
    }
    return $data;
}

三、redis常見問題
1、先讀快取還是先寫資料庫

2、快取更新策略(使用場景、一致性、維護成本)

  • LRU/LFU/FIFO 演算法剔除
  • 超時剔除
  • 主動更新(訊息系統或其他方式通知)

3、快取穿透解決

快取空物件(場景:資料頻繁變化實時性高; 缺點:更多的空間,短時間內有不一致的情況-可以利用訊息系統主動清除)
布隆過濾器攔截(場景:資料相對固定實時性低)

4、快取雪崩優化

  • 保證快取層高可用
  • 降級限流
  • 提前演練

5、熱點key

重建熱點key

  • 通過互斥鎖只允許一個執行緒重建

  • 快過期的時候後臺指令碼自動延續快取時間

    熱點key尋找

  • facebook的redis-faina

  • 通過客戶端,代理,monitor命令,機器抓包來尋查詢熱點key

    熱點key解決辦法

  • 拆分複雜資料結構(如使用的hash則可以考慮對hash進行拆分)

  • 遷移熱點key到效能好的機器上

  • 本地快取(更新時通過釋出訂閱機制處理redis本地快取不一致)

6、bigkey(胃寒:資料傾斜,超時足額,)

要求:

  • 字串型別:不能超過10kb
  • 非字串型別:元素個數不能過多(一般小於10000個)

發現及刪除注意事項:

  • 利用scan類命令漸進式刪除,防止出現redis阻塞。

四、擴散問題:
redis為啥單執行緒模型會達到每秒萬級別的處理能力?

  • redis 分散式鎖怎麼實現
  • redis 快取怎麼監控?怎麼告警
  • redis中 的虛擬記憶體
  • master節點雙機熱備 && sentinel
  • Redis的回收策略
  • redis的優點有哪些
  • 高可用&&叢集(一共就兩種做法:1 主從+哨兵 2 redis-cluster/codis)
  • redis常見效能問題優化
  • redis是否可以完全替換memeche?
  • redis多執行緒的話為啥和memcache多執行緒相比差別不大?
  • redis協議
  • redis 的記憶體分配有幾種方法?
  • 如何解決redis高併發客戶端頻繁connect timeout? 怎麼解決這個問題?
  • redis踩過的坑有哪些?(從大公司的ppt裡找)
  • 引起redis阻塞的有哪些命令?
  • TcpListenOverflows報警解決過程
  • Codis原理
  • Codis叢集的搭建與使用(https://www.cnblogs.com/xuanzhi201111/p/44...
  • redis怎麼持久化? 比如存到redis裡的資料,怎麼存到mysql庫裡
  • 客戶端連結超時
  • redis的tcp-backlog 出現的問題; tcp 3次握手中的 accept queue佇列跟這個有什麼關係?
  • 客戶端連線數過大
  • redis記憶體陡增,客戶端出現oom
  • 客戶端週期性超時
  • 什麼是複製快取區?
  • redis怎麼重啟,關閉
  • redis 不同db之間鍵名可以重複嗎? db有啥優缺點?
  • redis連線池怎麼做?(https://www.jianshu.com/p/2639549bedc8)怎麼檢視redis連線池中建立的連結?(https://www.u3v3.com/ar/1346
  • redis事物的原理是啥? redis事物有像mysql的隔離級別嗎? redis 支援事物回滾嗎?如果一個事務中的某個命令執行出錯,Redis會怎樣處理呢?如果客戶端在使用 MULTI 開啟了一個事務之後,卻因為斷線而沒有成功執行 EXEC ,那麼事務中的所有命令都不會被執行?如果採用aof持久化,假如redis執行事務的過程中,程式被殺掉了,事物中的命令會部分成功嗎?
  • WATCH命令
  • redis如何批量刪除符合某個規則的key?
  • hgetall 的欄位數過多會有什麼影響?
  • redis 能對hash中的欄位加過期時間嗎?
  • redis原子操作命令有哪些?
  • redis悲觀鎖
  • redis 刪除單個列表、集合、有序集合或雜湊表型別的 key ,時間複雜度為O(M)
  • redis 分頁查詢怎麼解決?
  • Redis 高負載下的中斷優化?

相關文章