Redis學習日記(一) : Redis基礎知識

目錄

一、redis是什麼?

二、redis的特性

三、reidis使用場景

四、redis的啓動方式

五 、redis的API使用

1 . 通用命令

   a 、通用命令

   b、數據結構和內部編碼

  c、單線程

2 . Redis API的理解和使用

a . 字符串

b . hash

c . list

d . set(集合)

e . zset(有序集合)

六、java客戶端(jedis)使用redis


一、redis是什麼?

1 .  開源框架; 2 . 多種數據結構; 3. 基於鍵值對的存儲服務系統; 4. 高性能、功能豐富。

 

二、redis的特性

1. 速度快:

         最關鍵的就是直接存儲在計算機內存中; 第二使用的線程模型是單線程模型; 第三redis使用c語言編寫(50000行左右)。

2. 持久化:

         數據雖然都是存儲在內存中的,但是redis會異步的將數據存儲到硬盤中。

         操作系統也有這種特性,在計算機藍屏是會將內存數據dump到硬盤,下次開機時從硬盤重新讀取到內存。

3. 支持多種數據結構:

        基於鍵值對的存儲,key使用string保存,value有5種基礎的數據結構:(1)String;(2)有序列鏈表List;(3)無序列集合set;(4)無序散列表;(5)有序排序集合zset

4. 支持多種編程語言:

        redis協議爲簡單的tcp協議,所以他支持非常多的客戶端語言

5. 功能豐富:

       註冊、訂閱實現消息隊列; 支持事物; lua腳本; pipeline

6. 簡單:

      這裏的簡單從片面上說最初的代碼長度才23000行,現在的不止了;當然它的簡單還指它不依賴其他外部庫; 同時它的線程模型都是單線程,所以無論服務端還是客戶端都方便開發。

7. 主從複製:

     redis提供了主服務器和從服務器的模式,主從服務器間進行同步數據。

8. 高可用,分佈式:

    redis2.8 之後提供了redis-sentinel 支持高可用;

    redis3.0之後提供了redis-cluster 支持分佈式;

 

三、reidis使用場景

   1 . 簡單的消息隊列 (利用redis 的註冊和訂閱服務)

   2 . 分佈式鎖, session共享池, 計數器(利用redis單線程的唯一性);

   3 . 緩存中間件(適配服務與數據庫之間的訪問量);

四、redis的啓動方式

1 .  安裝redis    參考官方文檔

$ wget http://download.redis.io/releases/redis-5.0.0.tar.gz
$ tar xzf redis-5.0.0.tar.gz
$ cd redis-5.0.0
$ make

2 . redis src下的可執行文件:

  • redis-server                   Redis服務器
  • redis-cli                         Redis命令行客戶端
  • redis-benchmark           Redis性能測試工具
  • redis-check-aof             AOF文件修改工具(持久化方式恢復之一)
  • redis-check-dump         RDB文件檢查工具(持久化方式恢復之二)
  • redis-sentinel                Sentinel服務器(2.8之後)

3 . 啓動方式

   (a) 直接啓動redis-server , 採用默認配置(直接就是redis-server);

   (b) 動態參數配置,可指配端口號等(如 redis-server --port 6380);

   (c) 採用配置文件啓動,也是最常用的啓動方式(生產環境下推薦,如 redis-server conf/redis6380.conf, 即用同目錄下的conf下的redis6380.conf配置啓動redis服務);

4 . 連接方式

     (1) redis-cli (直接連接)

     (2)redis-cli -h 127.0.0.1 -p 6380 (連接本地6380端口的redis)

5. 關閉redis

     (1)  redis-cli -a 密碼  -h 127.0.0.1 -p 6379 shutdown

 

五 、redis的API使用

 

1 . 通用命令

   a 、通用命令

                ①  keys : 得到redis中所有的key, 一般結合正則表達式來使用,即 keys 【pattern】, 如keys * 得到所有的key,keys he? 得到以he開頭的第三個字符任意的三位字符的key,它的時間複雜度爲O(n),所以線上環境不推薦使用。

                ②  dbsize :得到redis中key的個數。它的時間複雜度爲O(1),原因是redis中維護了這個計數器,不是遍歷得到的。

                ③  exists 【key】:判斷key在redis中是否存在,存在爲1,不存在爲0,時間複雜度爲O(1)

                ④  del 【key ...】 : 刪除key,刪除成功爲1,key不存在爲0,時間複雜度爲O(1)

                ⑤  expire 【key】【seconds】:設置key的過期時間(秒級別),時間複雜度爲O(1);

                     ttl 【key】:查看這個key剩餘多久的過期時間,不過期的key返回 -1,key不存在爲 -2,時間複雜度爲O(1);

                     persist 【key】 : 清除這個key的過期時間,這個key就不過期,時間複雜度爲O(1);

                ⑥  type 【key】:返回這個key的類型,有string,set, zset, list,hash,null(key不存在時),時間複雜度爲O(1)

                ⑦ info memory : 查看redis 佔用內存等信息。

   b、數據結構和內部編碼

                               這裏上兩張圖,圖一是redis的數據結構和內部編碼結構,通過這個圖我們知道在外看來hash結構在內部實際有兩種內部編碼格式,一個是hashtable,另一個是ziplist。那這個的好處是什麼?學過java的可以聯想面向接口編程,對內擴展,對外統一原則,什麼時候用什麼內部編碼就相當於到底選擇用時間換空間還是用空間換時間,因爲不同的內部編碼會有不同的空間存儲方式和不同的時間效率。

          圖二是redis內部的一個數據結構叫redisObject,這裏舉出了幾個關鍵的屬性,第一個屬性就是數據類型,第二個是編碼方式。

  c、單線程

       redis在處理客戶端的命令是採用的是單線程模型,用一個線程處理多個客戶端的請求命令

      那爲什麼redis的處理速度很快呢? 其實也就是它的存儲介質是內存(最關鍵),第二是它採用的是非堵塞式的處理請求,第三因爲是單線程所以避免了線程間的數據切換和競爭。

     因爲單線程,所以如果單條命令處理時間過長的話會造成堵塞,因此redis要避免長命令,如keys, flushall等。

 

2 . Redis API的理解和使用

a . 字符串

          ①  結構和命令

           key value 結構,key都是字符串,value有上圖說的5種類型,雖說value存的是字符串(實際存儲的都是二進制),但是value可以存儲多種,如字符串(world),數字(1, 2.2), 序列化數據(如json數據)等。

          get, set:專門針對字符串的命令,得到和設置key的值。

          incr, decr,incrby,decrby:第一個是自增1(incr count),第二個爲自減1(decr count),第三個是自增自定義的數(incrby count 2),第四個是自減自定義的數(decrby count 2)

          setnx:key不存在則設置值爲value,存在則失敗。(n爲null的意思,setnx可以理解爲爲add操作)

          set key value xx : key存在則設置value覆蓋原來的值,不存在則失敗。(set key value xx 可以理解爲update操作)

          mget key1 key2  key3 :批量得到多個key的值,原子操作,O(n),效率比多次獲取快了 n-1 次網絡時間。

          mset key1 value1 key2 value2 key3 value3 :批量設置多個值,O(n)。

 

          ②  內部編碼

          String 內部編碼使用以下三種類型:

               i):int  ---》 8個字節的長整形

               ii): embstr  ----》 小於等於39個字節的字符串

               iii):raw   ----》 大於39個字節的字符串

           Redis 根據String 長度自動決定編碼類型

           查看內部編碼類型可用  object encoding key 來查看

 

          ③   快速實戰

          記錄網站每個用戶頁面的訪問量:incr userid:pageView     ---->(key用userid + 頁面名稱的字符串來記錄)

          緩存視頻網站的熱門視頻信息: set vid vedioInfo  (key用vid表示,值是視頻的二進制序列化文件)

          ④   查漏補缺

           getset key value: 設置key爲value,返回oldvalue。

           append key value: 追加value

           strlen key : 計算key的長度,O(1),和dbsize類似,有專門存長度的屬性。

           incrbyfloat key value: 給浮點數自增自定義的數(incrbyfloat count 1.2)

           getrange key a b: 得到key中value的第a個到第b個字符,從0開始算。

           setrange key position newValue : 將position位置的value用newValue設置。

 

b . hash

          ①  特點,結構

          相當於Mapmap,在一箇中還有一個map,一個hash也可以理解成數據庫一張表的一行數據,一行數據有很多屬性和值,但是每個hash中的屬性可以不同。

             這裏的user:1 , user:2 就是一個hash

          ②  命令

          所有命令以h開頭

         hget,hset,hdel:和string的操作一致,如 hget key field ,hset key field value,hdel key field

         hexists,hlen :判斷是否存在字段和判斷hash有多少個字段, 如hexists key field, hlen key 時間複雜度爲O(n) (這裏不知道怎麼獲取hash中某個字段的長度

         hmget,hmset : 一次返回 / 設置一個hash中的多個field的值 , 如hmget key field1 field2 field3, hmset key field1 value1 field2 value2 field3 value3 ,時間複雜度 O(n)

         hincrby key field count : 給key中field增加count值, 這個count可以是負數(一個命令代替了string的incr,decr,incrby,decrby)

         hgetall key : 返回key的所有field和value, O(n),在hash field和value很多的時候要注意,可以用hmget代替

         hvals key,hkeys key : 得到key的所有value值, 得到所有key的field值。

          ③   快速實戰

          記錄每個用戶個人主頁的訪問量: hincrby user:1 pageView 1  (這個和上面直接用string記錄count不一樣的是這是用一個user的一個pageView字段來記錄訪問量,key爲user:1, field爲頁面名,每次添加量爲1)

          ④   查漏補缺

          hsetnx key field, hincrbyfloat key field float : 用法和string用法一致。

c . list

          ①  特點

               有序(遍歷的時候按照插入順序打出),可以重複,左右兩邊都可以插入彈出。

               這裏放一張list內部編碼爲linkedlist 結構的圖

          ②  命令API (從增刪改查4個方面來講述)

                  增加

               rpush key value1  value2  value3 :從右邊插入value,最後結果就是 value1,value2, value3, 時間複雜度爲O(n)

               lpush key value1  value2  value3 : 從左邊插入value,最後結果是value3,value2, value1,時間複雜度爲O(n)

               linsert key before | after value newValue :在value before | after 插入newValue,時間複雜度爲O(n)

                 刪除

               lpop key : 彈出左邊的第一個值,O(1)

               rpop key : 彈出右邊的第一個值,O(1)

               lrem key count value : 刪除key中count數量的value值,count>0 時從左到右刪,count<0 時從右到左刪count絕對值的數,=0 時刪除所有value值。

               ltrim key start end : 保留 [start, end]  的值,O(n),適合對大表的修建,比如每次保留90%

                 查找

               lrange key start end : 查找  [start, end] 範圍內的數, 時間複雜度O(n),-1爲最後一位數,[0, -1] 即查找所有的值

               lindex key index : 查找下標爲index的值,時間複雜度O(n), lindex key -2 , 即查找倒數第二個數

               llen key : 獲取list的長度, 時間複雜度爲O(1)

                 修改

               lset key index newValue : 修改下標爲index的值爲newValue,時間複雜度爲O(n)

          ③   快速實戰

             時間軸微博展示: 微博關注的人發了新微博展示在時間軸最前面(用列表的lpush),並且得到範圍內的數據做分頁(用range)

          ④   查漏補缺

             blpop,brpop key timeout:阻塞彈出,等待 timeout時間去彈出,超出timeout則失敗,timeout=0爲永不等待。

                       

d . set(集合)

          ①  特點 

               無序,不重複,支持集合間的操作

          ②  集合內API

               sadd key element : 新增元素,element 可以爲多個, 時間複雜度和添加的元素相關

               srem key element: 刪除元素,element可以爲多個,時間複雜度和添加的元素相關

               scard key : 得到key中的元素個數,O(1)

               sismember key element :判斷element是否在集合中。

               srandmember key count : 隨機得到集合中count個數的值,就是隨機查詢,不刪除。

               smembers key : 得到集合中的所有元素, 返回結果無序,小心,集合中的數據太大時會造成redis堵塞。在生產環境可以用sscan代替。

              spop key : 隨機彈出一個元素 。 實戰中可以用來當做抽獎系統選中獎用戶,所有用戶是一個集合,隨機彈出中獎用戶。

          ③  集合間API

              sdiff set1 set2 : 差集,set1 - set2

              sinner set1 set2 : 交集,set1  set2 相同元素的集合

              sunion set1 set2 : 並集,set1 + set2

              sdiffstore | sinnerstore | sunionstore newSet set1 set2 :將set1 set2 的集合在進行交,並,差之後存儲到一個newSet的集合。 

          ④   實戰

               抽象池中選中獎用戶(spop)。

               微博或者qq的共同好友,共同關注等(sinner).

         

e . zset(有序集合)

          ①  特點,結構和命令

               用分值(store)來進行排序。

         

 

          ②  重要API

              命令基本都以 ‘Z’ 開頭

              zadd  key score element: 添加元素,score element 可以有多對,O(logN)

              zrem key element : 刪除元素,  score element 可以有多對

              zscore key element :得到element的score 

              zincrby key count element : 給element的score增加count值(count可以爲負)

              zcard key :得到集合中的個數。 O(1)

              zrank | zrevrank key element: 得到element在集合中的排名,前者按照分值從小到大排名,後者按照分值從大到小排名。

              zrange key start end [withscores] : 按照排名(從小到大遞增)得到範圍內的元素(後面的withscores表示分值是不是一起打印) , 把zrange 換成zrevrange 則是按照從大到小遞減來排名取值。

              zrangebyscore start end [withscores] :按照分值排序(從小到大)得到分值在start到end內的元素。

              zcount key scoreStart scoreEnd : 按照分值得到start到end之間的元素個數。 O(logN + m)  ==> N爲集合中的元素個數,m爲start到end間的個數

             zremrangebyrank key start end : 按照排名排序刪除排名在start到end之間的值。

             zremrangebystore key start end : 按照分數排序刪除分值在start到end之間的值。

          ③   快速實戰

                 可以應用於排行榜,音樂排行榜,點贊數榜等。最核心的是zset中的score保存的內容,可以爲音樂聽歌次數,時間戳用於按時間排序等。

 

    

六、java客戶端(jedis)使用redis

 

   redis-cli 是redis的官方客戶端,jedis是java調用redis的客戶端(本質還是tcp連接,用的是socket,對外是jedis對象)

 

這裏有兩種使用jedis的方法。

1 . jedis直連的方式,每個連接jedis都是一個tcp連接。                              2 . 連接池的方式,通過連接池管理jedis 

        

 

下面給出簡單的使用方法。

使用前先引入jedis的maven依賴

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

package com.utils;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class JedisUtil {

    private static ThreadLocal<JedisPool> jedisPoolLocal = new ThreadLocal<>();

    private static JedisPool jedisPool;

    static {
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        jedisPool = new JedisPool(poolConfig, "你的ip", 6380);
        jedisPoolLocal.set(jedisPool);
    }

    /**
     * 使用jedis訪問redis得到數據,適用於長期,少量的連接
     * @param key
     * @return
     */
    public static String getByJedis(String key) {
        Jedis jedis = null;
        String result = null;
        try {
            jedis = new Jedis("你的ip", 6380);
            result = jedis.get(key);
        } catch (Exception e) {
          e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }


    /**
     * 通過連接池得到jedis,jedis獲取value,對於資源的管理更加合理
     * @param key
     * @return
     */
    public static String getByJedisPool (String key) {
        Jedis jedis = null;
        String result = null;
        try {
            jedis = jedisPoolLocal.get().getResource();
            result = jedis.get(key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                // close操作先判斷是否存在連接池,存在的話代表jedis是從pool裏獲取的,則歸還jedis,否則關閉連接。
                jedis.close(); 
            }
            return result;
        }
    }

    public static ThreadLocal<JedisPool> getJedisPoolLocal () {
        jedisPoolLocal.set(jedisPool);
        return jedisPoolLocal;
    }

}

這裏插播一條(很重要):因爲我是連接阿里雲的服務器(ip 爲A),在客戶端(ip爲B)連接的時候要把阿里雲的保護組開放,而且要把redis的redis.conf的bind屬性註釋掉(bind 指的是你redis服務器本身接受請求的ip即B,但是經過測試發現bind寫成B時,redis服務啓動不了,寫成本地服務器的ip 即A時,啓動了B連不上,當時想到了可不可以綁定兩個,我測試了好像不行,但是redis的示例配置redis.conf說是可以的,我最後是把bind屬性註釋掉或者寫成bind 0.0.0.0就能正常啓動並且B能連上A的redis,寫的有問題的話多請大佬指教)。當時寫了bind 0.0.0.0 ,然後第二天就收到阿里雲的緊急警告。。

因爲這個服務器就是自己學習用的,也就沒什麼處理這個警告,不過還是查了下阿里有建議的修復方案。

 

 

接下來的  小工具,持久化  請看最詳細的redis學習日記(二):redis的小工具及持久化

 

 

 

 

 

 

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