redis精進 - String類型的使用和應用場景

redis - String類型的使用和應用場景

最近在精進學習Redis,邊學邊寫
php

先贊後讀,養成習慣redis

1、String類型內存使用說明

  • Redis 的字符串是動態字符串
  • 採用預分配冗餘空間方式來減小內存的頻繁分配
  • 內容空間通常要高於實際字符串長度 len
    • 當字符串長度小於 1M 時,擴容都是加倍現有的空間,
    • 若是超過 1M,擴容時一次只會多擴 1M 的空間。
  • 須要注意的是字符串最大長度爲 512M。

2、String類型經常使用命令:

set

SET expire60 "will expire in a minute" EX 60 # 設置值60秒後過時
複製代碼

多存多取

local_redis:0> mset name1 boy name2 girl name3 unknown
"OK"

local_redis:0> mget name1 name2 name3
 1)  "boy"
 2)  "girl"
 3)  "unknown"
複製代碼

過時

setex name 5 codehole  # 5s 後過時,等價於 set+expire
複製代碼

分佈式鎖

setnx name codehole  # 若是 name 不存在就執行 set 建立
複製代碼

計數

自增是有範圍的,它的範圍是 signed long 的最大最小值,超過了這個值,Redis 會報錯。shell

> set codehole 9223372036854775807 # Long.Max
OK
 > incr codehole
(error) ERR increment or decrement would overflow
複製代碼

3、String類型經常使用的場景

一、驗證碼:常常在一些網站進行登陸、註冊、獲取驗證碼等操做的時候,都會收到一些驗證碼,而且說10min後失效。數據庫

set phone_num code ex 600後端

用手機號做爲key,驗證碼做爲值,超時6min。這樣當你輸入好驗證碼,提交的時候,後臺就能夠了先get phone_num,再比較你的輸入和數據庫裏面存的值,從而完成身份的驗證。緩存

二、緩存功能:String字符串是最經常使用的數據類型,不只僅是redis,各個語言都是最基本類型,所以,利用redis做爲緩存,配合其它數據庫做爲存儲層,利用redis支持高併發的特色,能夠大大加快系統的讀寫速度、以及下降後端數據庫的壓力。bash

三、計數器:許多系統都會使用redis做爲系統的實時計數器,能夠快速實現計數和查詢的功能。並且最終的數據結果能夠按照特定的時間落地到數據庫或者其它存儲介質當中進行永久保存。併發

四、bitmap位圖:可普遍用於,簽到、活躍、打卡等場景統計。灰常好用dom

五、分佈式鎖 確保分佈式鎖可用,咱們至少要確保鎖的實現同時知足如下四個條件:分佈式

  • 資源佔用:互斥性。在任意時刻,只有一個客戶端能持有鎖。
  • 生命週期:不會發生死鎖。即便有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證後續其餘客戶端能加鎖。
  • 多redis:具備容錯性。只要大部分的Redis節點正常運行,客戶端就能夠加鎖和解鎖。
  • 同一人加鎖解鎖:解鈴還須繫鈴人。加鎖和解鎖必須是同一個客戶端,客戶端本身不能把別人加的鎖給解了

PHP具體實現:

class RedLock {
    private $retryDelay;    // 重試間隔
    private $retryCount;    // 重試次數
    private $clockDriftFactor = 0.01;
    private $quorum;
    private $servers = array();
    private $instances = array();
    function __construct(array $servers, $retryDelay = 200, $retryCount = 3) {
        $this->servers = $servers;
        $this->retryDelay = $retryDelay;
        $this->retryCount = $retryCount;
        $this->quorum  = min(count($servers), (count($servers) / 2 + 1));
    }

    public function lock($resource, $ttl) {
        $this->initInstances();
        $token = uniqid();
        $retry = $this->retryCount;

        do {
            $n = 0;
            $startTime = microtime( true ) * 1000;
            foreach ($this->instances as $instance) {
                if ($this->lockInstance($instance, $resource, $token, $ttl)) {
                    $n++;
                }
            }

            // 將 2 毫秒添加到漂移中,以考慮 Redis 過時精度,即 1 毫秒,加上小型 TTL 的 1 毫秒最小漂移。
            $drift = ( $ttl * $this->clockDriftFactor ) + 2;
            $validityTime = $ttl - ( microtime( true ) * 1000 - $startTime ) - $drift;
            if ($n >= $this->quorum && $validityTime > 0) {
                return [
                    'validity' => $validityTime,
                    'resource' => $resource,
                    'token'    => $token,
                ];
            } else {
                foreach ( $this->instances as $instance ) {
                    $this->unlockInstance( $instance, $resource, $token );
                }
            }
            // Wait a random delay before to retry
            $delay = mt_rand( floor( $this->retryDelay / 2 ), $this->retryDelay );
            usleep( $delay * 1000 );
            $retry--;
        } while ($retry > 0);
        return false;
    }

    public function unlock(array $lock) {
        $this->initInstances();
        $resource = $lock['resource'];
        $token    = $lock['token'];
        foreach ($this->instances as $instance) {
            $this->unlockInstance($instance, $resource, $token);
        }
    }

    private function initInstances() {
        if (empty($this->instances)) {
            foreach ($this->servers as $server) {
                list($host, $port, $timeout) = $server;
                $redis = new \Redis();
                $redis->connect($host, $port, $timeout);
                $this->instances[] = $redis;
            }
        }
    }

    private function lockInstance($instance, $resource, $token, $ttl) {
        return $instance->set($resource, $token, ['NX', 'PX' => $ttl]);
    }

    private function unlockInstance($instance, $resource, $token) {
        // 不但實現了 同一人加鎖解鎖;並且若是解鎖不成功就回滾可以保證 資源的佔用
        $script = ' if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end ';
        return $instance->eval($script, [$resource, $token], 1);
    }
}

$servers = [
    ['127.0.0.1', 6379, 0.01],
];
$redLock = new RedLock($servers);
$i2Count = 0;echo '<pre>';

while ( $i2Count < 10 ) {
    $lock = $redLock->lock('test', 10000);
    if ($lock) {
        print_r($lock);
        $ret2Unlock = $redLock->unlock( $lock );

        // !$ret2Unlock 則回滾全部操做
    } else {
        print "Lock not acquired\n";
    }

    $i2Count++;
}
複製代碼

4、最後

因爲篇幅問題,此文章留下了問題

  • 分佈式鎖實現思路,方案討論
  • bitmap位圖妙用

今明補上

參考閱讀:

  • 《Redis深度歷險》 -- 老錢
  • 《Redis 5設計與源碼分析》 -- 陳雷 等編著
  • 《Redis實戰》-- [美]Josiah L. Carlson
相關文章
相關標籤/搜索