Redis之基礎知識總結

Redis之基礎知識總結

一、支持的基本的數據類型

1.1. 五大數據類型

Redis有5個基本數據結構,stringlisthashsetzset。它們是日常開發中使用頻率非常高應用最爲廣泛的數據結構,

  • String類型

    1. Stringredis最基本的類型,一個key對應一個value,sring類型是二進制安全的。redisstring可以包含任何數據。Sring類型是Redis最基本的數據類型,一個鍵最大能存儲512MB

    2. Redis的字符串是動態字符串,是可以修改的字符串,內部結構實現上類似於JavaArrayList,採用預分配冗餘空間的方式來減少內存的頻繁分配。

    3. 內部爲當前字符串實際分配的空間capacity一般要高於實際字符串長度len。當字符串長度小於1M時,擴容都是加倍現有的空間,如果超過1M,擴容時一次只會多擴1M的空間。

    4. 針對String的一些操作:

      127.0.0.1:6379> set name zhangsan  # 初始化字符串
      OK
      127.0.0.1:6379> get name           # 獲取字符串
      "zhangsan"
      127.0.0.1:6379> strlen name        # 字符串長度
      (integer) 8
      127.0.0.1:6379> getrange name 2 4  # 獲取子串 key值和開始和結束位置
      "ang"
      127.0.0.1:6379> setrange name 2 6666  # 覆蓋子串  將會把子串 6666 從2位置開始覆蓋
      (integer) 8
      127.0.0.1:6379> get name              # 覆蓋之後的子串
      "zh6666an" 
      127.0.0.1:6379> append name 123456789    # 追加子串
      (integer) 17
      127.0.0.1:6379> get name                 # 追加之後的子串
      "zh6666an123456789"
      127.0.0.1:6379>
      
    5. 將作爲計數器使用

      127.0.0.1:6379> set index 10                       # 設置 index 值爲 10
      OK
      127.0.0.1:6379> get index
      "10"
      127.0.0.1:6379> incrby index 1                     # 增加一
      (integer) 11
      127.0.0.1:6379> get index                          # 增加一後爲11
      "11"
      127.0.0.1:6379> decrby index 1                     # 減去一
      (integer) 10
      127.0.0.1:6379> get index                          # 減去一位10
      "10"
      127.0.0.1:6379> incr index                         # 加一
      (integer) 11
      127.0.0.1:6379> decr index                         # 減一
      (integer) 10
      127.0.0.1:6379> 
      
    6. 過期刪除操作

        127.0.0.1:6379> expire index 5                     # 設置過期時間
        (integer) 1              # 1 成功      0 key不存在
        127.0.0.1:6379> ttl index                          # 獲取還有多久過期
        (integer) -2             # -2 表示變量不存在, -1 表示設置過期時間  返回正常正數爲還有多少秒過期
        127.0.0.1:6379> del name                           # 刪除
        (integer) 1              # 刪除成功返回1
        127.0.0.1:6379> get name                           # 獲取   
        (nil)  # b變量不存在
        127.0.0.1:6379> 
        ```
      
      
  • list類型

    1. Redis將列表數據結構命名爲list而不是array,是因爲列表的存儲結構用的是鏈表而不是數組,而且鏈表還是雙向鏈表。因爲它是鏈表,所以隨機定位性能較弱,首尾插入刪除性能較優。如果list的列表長度很長,使用時我們一定要關注鏈表相關操作的時間複雜度。

    2. 負下標 鏈表元素的位置使用自然數0,1,2,....n-1表示,還可以使用負數-1,-2,...-n來表示,-1表示「倒數第一」,-2表示「倒數第二」,那麼-n就表示第一個元素,對應的下標爲0

    3. 隊列/堆棧 鏈表可以從表頭和表尾追加和移除元素,結合使用rpush/rpop/lpush/lpop四條指令,可以將鏈表作爲隊列或堆棧使用,左向右向進行都可以

    4. list的一些操作:

      在日常應用中,列表常用來作爲異步隊列來使用

      127.0.0.1:6379> rpush code go java  # 將值value插入到列表key的表尾,當且僅當key存在並且是一個列表。
      (integer) 2
      127.0.0.1:6379> rpush code python php c c++
      (integer) 6
      127.0.0.1:6379> rpop code    #  移除並返回列表key的尾元素
      "c++"
      127.0.0.1:6379> rpop code
      "c"
      127.0.0.1:6379> lpop code    #  移除並返回列表key的頭元素
      "go"
      127.0.0.1:6379> lpop code
      "java"
      127.0.0.1:6379> lrange code 0 -1   # 查看列表中的元素  0 開始位置, -1爲最後一個位置
      1) "python"
      2) "php"
      127.0.0.1:6379> lpush code 12345   # 往表頭插入數據
      (integer) 3
      127.0.0.1:6379> lrange code 0 -1   # 將12345 插入到了表頭
      1) "12345"
      2) "python"
      3) "php"
      127.0.0.1:6379> lpush code java1 java2   # 插入多個值
      (integer) 6
      127.0.0.1:6379> lrange code 0 -1
      1) "java2"
      2) "java1"
      3) "rust"
      4) "12345"
      5) "python"
      6) "php"
      127.0.0.1:6379> llen code    # 獲取列表的長度
      (integer) 6
      127.0.0.1:6379> lindex code 2   # 訪問位置2的數據
      "rust"
      127.0.0.1:6379> lset code 1 js   # 修改位置1的數據
      OK
      127.0.0.1:6379> lrange code 0 -1
      1) "java2"
      2) "js"
      3) "rust"
      4) "12345"
      5) "python"
      6) "php"
      127.0.0.1:6379> linsert code after js js1   # 在js數據之後插入數據
      (integer) 7
      127.0.0.1:6379> lrange code 0 -1
      1) "java2"
      2) "js"
      3) "js1"
      4) "rust"
      5) "12345"
      6) "python"
      7) "php"
      127.0.0.1:6379> linsert code before js js2  # 在js數據之前插入數據
      (integer) 8
      127.0.0.1:6379> lrange code 0 -1
      1) "java2"
      2) "js2"
      3) "js"
      4) "js1"
      5) "rust"
      6) "12345"
      7) "python"
      8) "php"
      127.0.0.1:6379> lrem code 1 js   # 元素刪除
      (integer) 1
      127.0.0.1:6379> lrange code 0 -1
      1) "java2"
      2) "js2"
      3) "js1"
      4) "rust"
      5) "12345"
      6) "python"
      7) "php"
      127.0.0.1:6379> ltrim code -4 -1   # 截取定長數據, 其他部分將被捨去
      OK
      127.0.0.1:6379> lrange code 0 -1
      1) "rust"
      2) "12345"
      3) "python"
      4) "php"
      127.0.0.1:6379> 
      
      
    5. 快速列表

      如果再深入一點,你會發現Redis底層存儲的還不是一個簡單的linkedlist,而是稱之爲快速鏈表quicklist的一個結構。首先在列表元素較少的情況下會使用一塊連續的內存存儲,這個結構是ziplist,也即是壓縮列表。它將所有的元素緊挨着一起存儲,分配的是一塊連續的內存。當數據量比較多的時候纔會改成quicklist。因爲普通的鏈表需要的附加指針空間太大,會比較浪費空間。比如這個列表裏存的只是int類型的數據,結構上還需要兩個額外的指針prevnext。所以Redis將鏈表和ziplist結合起來組成了quicklist。也就是將多個ziplist使用雙向指針串起來使用。這樣既滿足了快速的插入刪除性能,又不會出現太大的空間冗餘。

  • hash類型

    1. 哈希等價於Java語言的HashMap或者是Python語言的dict,在實現結構上它使用二維結構,第一維是數組,第二維是鏈表,hash的內容keyvalue存放在鏈表中,數組裏存放的是鏈表的頭指針。通過key查找元素時,先計算keyhashcode,然後用hashcode對數組的長度進行取模定位到鏈表的表頭,再對鏈表進行遍歷獲取到相應的value值,鏈表的作用就是用來將產生了「hash碰撞」的元素串起來。Java語言開發者會感到非常熟悉,因爲這樣的結構和HashMap是沒有區別的。哈希的第一維數組的長度也是2^n

    2. 一些常規操作:

         127.0.0.1:6379> hset code name zhangsan  # 增加一個元素
           (integer) 1
           127.0.0.1:6379> hmset code lisi threeClass wangwu fourClass  # 增加多個元素
           OK
           127.0.0.1:6379> hget code name   # 通過key獲取值
           "zhangsan"
           127.0.0.1:6379> hgetall code     # 獲取所有的值
           1) "name"
           2) "zhangsan"
           3) "lisi"
           4) "threeClass"
           5) "wangwu"
           6) "fourClass"
           127.0.0.1:6379> hkeys code       # 獲取所有的key
           1) "name"
           2) "lisi"
           3) "wangwu"
           127.0.0.1:6379> hvals code       # 獲取所有的value
           1) "zhangsan"
           2) "threeClass"
           3) "fourClass"
           127.0.0.1:6379> hdel code name   # 獲取key 爲name的數據
           (integer) 1
           127.0.0.1:6379> hgetall code
           1) "lisi"
           2) "threeClass"
           3) "wangwu"
           4) "fourClass"
           127.0.0.1:6379> hdel code lisi wangwu   # 刪除多個
           (integer) 2
           127.0.0.1:6379> hgetall code
           (empty list or set)
           127.0.0.1:6379> hexists code name       # 查看name存在, 存在返回1  不存在返回0
           (integer) 1
           127.0.0.1:6379> hexists code naaa
           (integer) 0
           
           127.0.0.1:6379> hset code name lisi    # 計數器,使用,不能使字符串
           (integer) 1
           127.0.0.1:6379> hincrby code name 1    # 加一操作,這樣會報錯
           (error) ERR hash value is not an integer
           127.0.0.1:6379> hset code name 1       # 將name值設置爲1
           (integer) 0
           127.0.0.1:6379> hincrby code name 1    # 加一操作之後就成爲 2
           (integer) 2
           127.0.0.1:6379> hgetall code
           1) "name"
           2) "2"
           127.0.0.1:6379> hdel code name         # 刪除元素   
           (integer) 1
           127.0.0.1:6379> hexists code name      # 是否存在    存在1   不存在0
           (integer) 0
           127.0.0.1:6379> 
        ```
      
      3. **擴容** 當`hash`內部的元素比較擁擠時(`hash`碰撞比較頻繁),就需要進行擴容。擴容需要申請新的兩倍大小的數組,然後將所有的鍵值對重新分配到新的數組下標對應的鏈表中(`rehash`)。如果`hash`結構很大,比如有上百萬個鍵值對,那麼一次完整`rehash`的過程就會耗時很長。這對於單線程的`Redis`裏來說有點壓力山大。所以`Redis`採用了漸進式`rehash`的方案。它會同時保留兩個新舊`hash`結構,在後續的定時任務以及`hash`結構的讀寫指令中將舊結構的元素逐漸遷移到新的結構中。這樣就可以避免因擴容導致的線程卡頓現象。
      
      4. **縮容** `Redis``hash`結構不但有擴容還有縮容,從這一點出發,它要比`Java``HashMap`要厲害一些,`Java``HashMap`只有擴容。縮容的原理和擴容是一致的,只不過新的數組大小要比舊數組小一倍。
      
      
      
  • set類型

    1. Java程序員都知道HashSet的內部實現使用的是HashMap,只不過所有的value都指向同一個對象。Redisset結構也是一樣,它的內部也使用hash結構,所有的value都指向同一個內部值。

    2. 一些常用的方法:

      127.0.0.1:6379> sadd index name sex age height   # 添加元素
      (integer) 4
      127.0.0.1:6379> smembers index    # 列出所有元素
      1) "sex"
      2) "name"
      3) "height"
      4) "age" 
      127.0.0.1:6379> scard index    # 統計元素
      (integer) 4
      127.0.0.1:6379> srandmember index   # 隨機獲取某一元素
      "age"
      127.0.0.1:6379> srem index sex  # 刪除元素
      (integer) 1
      127.0.0.1:6379> spop index      # 隨機刪除一個元素
      "height"
      127.0.0.1:6379> sismember index sex    # 檢驗某一元素是否存在,0 不存在   1存在
      (integer) 0
      127.0.0.1:6379> sismember index name
      (integer) 1
      127.0.0.1:6379> 
      
      
  • sortset類型

    1. SortedSet(zset)Redis提供的一個非常特別的數據結構,一方面它等價於Java的數據結構Map,可以給每一個元素value賦予一個權重score,另一方面它又類似於TreeSet,內部的元素會按照權重score進行排序,可以得到每個元素的名次,還可以通過score的範圍來獲取元素的列表。

    2. zset底層實現使用了兩個數據結構,第一個是hash,第二個是跳躍列表,hash的作用就是關聯元素value和權重score,保障元素value的唯一性,可以通過元素value找到相應的score值。跳躍列表的目的在於給元素value排序,根據score的範圍獲取元素列表。

    3. 一些常用操作:

      127.0.0.1:6379> zadd info 14 age     # 增加一元素
      (integer) 1
      127.0.0.1:6379> zadd info 15 name 17 height   # 增加多個元素
      (integer) 2
      127.0.0.1:6379> zcard info                    # 獲取元素個數
      (integer) 3
      127.0.0.1:6379> zrem info name                # 刪除元素
      (integer) 1
      127.0.0.1:6379> zadd info 5 sex               
      (integer) 1
      127.0.0.1:6379> zincrby info 1 sex            # 加一操作
      "6"
      127.0.0.1:6379> zincrby info 3 sex            # 加三操作
      "9"
      127.0.0.1:6379> zrange info 0 -1              # 獲取所有元素
      1) "lisi"
      2) "sex"
      3) "age"
      4) "height"
      127.0.0.1:6379> zrank info sex     # 指定元素的正向排名
      (integer) 1
      127.0.0.1:6379> zrevrank info sex  # zrevrank指令獲取指定元素的反向排名[倒數第一名] 
      (integer) 2
      127.0.0.1:6379> zscore info sex    # 獲取元素的權重
      "9"
      127.0.0.1:6379> zrange info 0 -1 withscores   # 攜帶權重正向輸出
      1) "lisi"
      2) "7"
      3) "sex"
      4) "9"
      5) "age"
      6) "14"
      7) "height"
      8) "17"
      127.0.0.1:6379> zrevrange info 0 -1 withscores  # 攜帶權重反向輸出
      1) "height"
      2) "17"
      3) "age"
      4) "14"
      5) "sex"
      6) "9"
      7) "lisi"
      8) "7"
      127.0.0.1:6379> zrangebyscore info -inf +inf withscores   # 通過zrangebyscore指令指定score範圍獲取對應的元素列表   -inf 負無窮    +inf 正無窮
      1) "lisi"
      2) "7"
      3) "sex"
      4) "9"
      5) "age"
      6) "14"
      7) "height"
      8) "17"
      127.0.0.1:6379> zrevrangebyscore info +inf -inf withscores  # 通過zrevrangebyscore指令獲取倒排元素列表
      1) "height"
      2) "17"
      3) "age"
      4) "14"
      5) "sex"
      6) "9"
      7) "lisi"
      8) "7"
      127.0.0.1:6379> 
      

二、簡述機制原理

Redis使用的是NIO中的多路IO複用的機制,能夠非常好的支持併發,從而保持線程安全。

Redis單線程,也就是底層採用一個線程維護多個不同的客戶端io操作。

但是NIO在不同的操作系統上實現的方式有所不同,在我們windows操作系統使用select實現輪訓時間複雜度是爲o(n),而且還存在空輪訓的情況,效率非常低, 其次是默認對我們輪訓的數據有一定限制,所以支持上萬的tcp連接是非常難。

linux操作系統採用epoll實現事件驅動回調,不會存在空輪訓的情況,只對活躍的 socket連接實現主動回調這樣在性能上有大大的提升,所以時間複雜度是爲o(1)

nginxredis都能夠非常高支持高併發,最終都是linux中的IO多路複用機制epoll, Redis底層採用nio epoll實現。

三、使用場景

  • Token令牌的生成
  • 短信驗證碼Code
  • 緩存查詢數據
  • 網頁計數器
  • 分佈式鎖
  • 延遲操作

四、 SpringBoot整合

4.1. 依賴
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 需要引入pools,否則會報類無法找到 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

4.2. 配置文件
# Redis服務器地址
spring.redis.host=192.168.252.135
# Redis數據庫索引
spring.redis.database=0
# Redis密碼
spring.redis.password=123456
# 端口
spring.redis.port=6379
# 連接池最大連接數,負值無限制
spring.redis.lettuce.pool.max-active=8
# 連接池最大阻塞等待時間 負值無限制
spring.redis.lettuce.pool.max-wait=-1ms
# 關機超時時間
spring.redis.lettuce.shutdown-timeout=100ms
# 最小空閒連接數
spring.redis.lettuce.pool.min-idle=0
4.3. 使用例子
@RestController
public class RedisController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @GetMapping("/getData")
    public String load(@RequestParam("name") String name) {
        stringRedisTemplate.opsForValue().set("name", name, TimeUnit.SECONDS.toSeconds(60));
        return "success";
    }

}
4.4. Redis客戶端區別
  • Jedis:是RedisJava實現客戶端,提供了比較全面的Redis命令的支持,

  • Redisson:實現了分佈式和可擴展的Java數據結構。

  • Lettuce:高級Redis客戶端,用於線程安全同步,異步和響應使用,支持集羣,Sentinel,管道和編碼器。

總體來說:優先使用Lettuce,如果需要分佈式鎖,分佈式集合等分佈式的高級特性,添加Redisson結合使用,因爲Redisson本身對字符串的操作支持很差。

文章第二部分的Redis數據類型參考文章地址:https://juejin.im/post/5b53ee7e5188251aaa2d2e16

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章