經驗拾憶(純手工)=> Redis與Python操作Redis語法對比解析

前言

R: 代表 redis-cli
P: 代表 python的redis

準備

pip install redis
pool = redis.ConnectionPool(host='39.107.86.223', port=6379, db=1)
redis = redis.Redis(connection_pool=pool)
redis.所有命令
下面命令所有命令我都省略了, 有和Python內置函數衝突的我會加上 redis.

全局命令

  • dbsize(返回key的數量)

       R: dbsize
       P: print(redis.dbsize())
  • exists(是否存在某key)

       R: exists name
       P: exists('name')
       
  • keys(列出所有key,可使用通配符)

       R: keys na*
       P: keys('na*')
       注:時間複雜度爲 O(n)
  • scan (對應keys,迭代取出所有keys)

       R: scan 0 match '*' count 4       
       P: keys_iter = redis.scan_iter(match='*', count=4)  
       注:這種scan,API後面也會有, 所以我全部放在最後的結束語中講
  • info (查看資源信息)

       R: info            # 也可以填參數  info memory     info cpu 等
       P: redis.info()    # redis.info('CPU')     redis.info('MEMORY')
  • type (列出類型)

       R: type name
       P: redis.type('name')   # type和python的衝突了,所以這裏我寫全了    
       redis中類型有:  none string list    set zset     hash
    

過期時間

  • expire(設置)

       R: expire name 秒數
       P: expire('name',  秒數)
  • ttl(查詢)

       R: ttl name     
       P: ttl('name')
       
       # 返回剩餘過期時間值
       # 返回值爲 -2 則代表 無此 key
       # 返回值爲 -1 則代表 有此 key , 但未設置過期時間
       
  • persist(刪除)

       R: persist name
       P: persist('name')
    
  • 自增,自減

       incr incrby     加上一個整數
           R: incr age   或 incrby age 1
           P: incr age 1 或 incrby age 1        # python實現 的 incr 被 重定向爲 incrby,所以用哪個都行
           
       decr decrby     減去一個整數
           同上
       
       incrbyfloat     加減一個浮點數
           同上
    

字符串相關操作

  • set 設置值

       R: set name lin
       P: redis.set('name', 'lin') 
    
       set選項(原子操作)
           nx(設置默認值)
               R: set name lin nx    
               P: redis.set('name', 'lin', nx=True)
                   注: nx 代表key不存在纔會將值設置成功, 類似python dict的 setdefault,即給key設置默認值
               
           xx(更新值)
               R: set name Tom xx
               P: redis.set('name', 'lin', xx=True)
                   注: xx 代表key存在纔會將值更新成功。 如果key不存在, 則更新失敗。
  • get 獲取值

       R: get name
       P: redis.get('name')
       注:通過py redis客戶端的 get取出的都是 字節二進制類型, 所以需要手動轉爲對應類型
           前面提到的 incr decr 等, 操作返回結果直接就是 int, 而非 字節類型
    
  • mset 批量設置

       R: mset name lin age 18
       p: redis.mset( {'name': 'lin', 'age': 18} )
  • mget 批量獲取

       R: mget name age
       p: redis.mget('name', 'age')    # 返回值爲 字節類型的 列表
  • getset 設置新值並返回舊值

       R: getset name zhang
       P: print( redis.getset('name', 'zhang') )
  • append 字符串追加拼接

       R: append name abc
       P: redis.append('name', 'abc')
  • strlen 獲取字符串長度

       R: strlen name
       P: print( redis.strlen('name') )
       注: 與編程語言的普遍API不同的是, strlen返回的字符串 長度是 字符對應編碼的長度。。。。
           中文 utf-8 佔 3個字節
  • getrange 字符串切片 (從0開始,前閉後閉)

       R: getrange name 1 2
       P: redis.getrange('name', 1, 2)
  • setrange 字符串按索引賦值(覆蓋)

       R: setrange name 0 abc     # 把第0個位置開始, 逐個覆蓋賦值爲 abc, 多餘的不變
       P: redis.setrange('name', 0, 'abc')
  • del 刪除鍵值

       R: del k1 k2
       P: redis.delete(k1, k2)

Hash相關操作(可對應爲 文檔-屬性-值)

  • hset 設置 1條文檔,1個屬性-值

       R: hset user name lin
       P: redis.hset('user', 'name', 'lin')
  • hget 獲取 1條文檔,1個屬性

       R: hget user name
       P: print(redis.hget('user', 'name'))        
  • hmset 設置 1條文檔, 多個屬性-值

       R: hmset user name lin age 18
       P: redis.hmset('user', {'user': 'lin', 'age': 18})
  • hmget 獲取 1條文檔, 多個屬性-值

       R: hmget user name age
       P: print(redis.hmget('user', 'name', 'age'))
  • hkeys 獲取所有 key

       R: hkeys user
       P: print(redis.hkeys('user'))
  • hvals 獲取所有 values

       R: hvals user
       P: print(redis.hvals('user'))
  • hgetall 獲取 一條文檔,所有屬性值(慎用,見下一條API)

       R: hgetall user                    # 返回爲列表, 偶數索引爲key,奇數索引爲vaLue(從0開始)
       P: print(redis.hgetall('user'))    # 返回爲 dict格式
       
       注: hgetall 會將所有key-value取出來,所以數據量龐大可能會造成性能影響。
           大批量數據在python是怎麼處理來着???????
           沒錯,就是迭代器,當然python的redis模塊已爲我們封裝好一個API,hscan_iter, 見一條API
  • hscan (hash迭代,大體上可代替 hgetall使用)

       R: hscan user 0 match * count 200        # 按遊標,按數量取
           # 0代表遊標從頭開始
           # match是關鍵字 
           # * 是key的通配符
           # count 是一次接待的條數
       P: result_iter = redis.hscan_iter('user', match= 'na*', count=2)    
           # python的 cursor參數沒有,是因爲源碼中被固定設置爲 0了, 其他參數解釋同上
           # 返回結果爲可迭代對象,可遍歷取出。
  • hexists 檢測是否存在某key

       R: hexists user name1                   # 存在返回 1,不存在返回 0
       P: print(redis.hexists('user', 'name')) # 存在返回True
  • hlen 統計獲取一個文檔,所有屬性的 總數

       R: hlen user
       P: print(redis.hlen('user'))        
    

List相關操作

  • lpush (左壓棧)

       R: lpush list1 1 2 3
       P: redis.lpush('list1', 1,2,3)
  • rpush (右壓棧,同左壓棧,略)

  • lpop (左彈棧)

       R: lpop list2
       P: print(redis.lpop('list2'))
  • rpop (右彈棧,同左彈棧,略)

  • blpop (左阻塞彈棧,列表爲空時,就阻塞了)

       R: blpop list2 1000      # 1000爲過期時間爲1000秒,1000秒後自動解除阻塞,有值加入也會解除阻塞
       P: redis.blpop('list2', timeout=1000)
  • brpop (右阻塞彈棧,同左阻塞彈棧,略)

  • linsert ( 在指定 值 的 前後 插入值)

       R: linsert list2 before Tom jerry                # 在Tom前插入 jerry, before代表之前
       P: redis.linsert('list2', 'after', 'b', 'Tom')   # 在b的後面插入Tom,  after代表之後
  • lset (按索引賦值, 注意索引不要越界)

       R:lset list2 4 zhang
       P: redis.lset('list2', 4, 'zhang')
  • lindex (按索引取值, 索引可正可負)

       R: lindex list2 -3
       P: print(redis.lindex('list2', 3))
  • llen (獲取列表元素個數)

       R: llen list2
       P: print(redis.llen('list2'))    
  • ltrim (注意:在原數據上切片,不返回值。)

       R: ltrim list2 3 10                     # 保留 索引 3-10 的列表數據,其他都刪除
       P: print(redis.ltrim('list2', 2, -1))   # 索引前閉後閉,可正可負
  • lrem (刪除指定值)

       R: lrem list2 0 Tom    
           # 0 這個位置的參數代表刪除值的個數
               # 0 代表全部刪除, 刪除全部Tom值
               # 正數代表 從左到右 刪除n個。  eg:  lrem list2 5 Tom    即爲 從左到右 刪除5個Tom值
               # 負數代表 從右到左 刪除n個。  eg:  lrem list2 -5 Tom   即爲 從右到左 刪除5個Tom值
       P: print(redis.lrem('list2', -5, 1))    # 解釋同上
  • lrange(遍歷,正負索引都可,前閉後閉)

       R: lrange list1 0 -1 
       P: print(redis.lrange('list2', 0, -1))

Set相關操作

  • sadd (插入元素)

       R: sadd set1 1 2 3
       P: redis.sadd('set1', *[1,2,3])
  • srem (刪除指定值的元素)

       R: srem set1 Tom
       P: redis.srem('set1', 'Tom')
  • scard (獲取集合中元素個數)

       R: scard set1
       P: redis.scard('set1')
  • sismember (判斷某元素是否在集合)

       R: sismember set1 Tom
       P: redis.sismember('set1', 'Tom')
  • srandmember (隨機取出集合指定個數元素)

       “”“類似py的 random.choices,注意有s”“
       R: srandmember set1 2             # 從集合隨機中取出2個元素
       P: redis.srandmember('set1', 2)
  • smembers (取出集合中所有元素)

       R: smembers set1
       P: redis.smembers('set1')
       注: 同 hgetall, 如果一次性取出,可能會出問題,所以需要迭代獲取,見下 sscan
  • sscan (遊標/迭代取出集合所有元素)

       R: sscan set1 0 match * count 200
       P: result_iter = redis.sscan_iter('set1', match='*', count=200)    # 遍歷迭代
  • sdiff (差集)

       R: sdiff sset1 sset2
       P: print(redis.sdiff('sset1', 'sset2'))
  • sinter(交集)

       R: sinter sset1 sset2
       P: print(redis.sinter('sset1', 'sset2'))
  • sunion (並集)

       R: sunion sset1 sset2
       P: print(redis.sunion('sset1', 'sset2'))

zset 有序集合相關操作

  • zadd (有序插入)

       R: zadd zset 100 Tom 90 Jerry    #  100是權重,Tom是數據值, 注意redis-cli  權重在前,值在後
       P: redis.zadd('zset', {'Tom': 100, 'Jerry': 90})    # 注意,py語法,權重作爲字典的value
       注特別注意:
           zadd的默認機制是同值,不同權重時,會更新值的權重
           eg: 上面再插入一條 Tom, 不過這次的權重是 50 ( zadd zset 50 Tom),則Tom的權重會更新爲50
           
       這時就延申出2個參數,(應該還記得前面講的 set的 nx 和 xx參數吧,沒錯 zadd也有)
       nx: (不存在才更新(添加), 存在則更新(添加) 失敗)
           R: zadd zset nx 1000 Tom            
           P: redis.zadd('zset',{'Tom': 1000}, nx=True)    
           注:
               如果Tom這個值之前存在,則這個1000就不會被更新了
               若不存在,則就會新創建,並把這個1000設置成功
               
       nx:(存在才更新(添加),  不存在則更新(添加) 失敗)
           R: zadd zset xx 1000 Tom            
           P:redis.zadd('zset',{'Tom': 1000}, xx=True)    
           注:
               如果Tom這個值之前存在,則1000纔會更新成功
               如果不存在,比如 {'張三':500}, 張三本來就不存在,用了xx, 他不會被添加進來,更何談更新
  • zrange (遍歷)

       R: zrange zset 0 -1
       P: print(redis.zrange('zset', 0, -1))    # 返回值爲列表
    
       withscores 參數(把權重也帶出來返回):
           R: zrange zset 0 -1 withscores    # 注意,  返回時 奇數位 是值, 偶數位是權重
           P: print(redis.zrange('zset', 0, -1, withscores=True))  # 返回列表嵌套元組,[(值,權重)]
  • zrevrange (逆序-降序,遍歷)

       這條API就是多了 "rev" 三個字母,   reversed單詞 熟悉把, python內置逆序高階函數。。就是那個意思
       操作同zrange,略
  • zrangebyscore (根據權重來遍歷)

       R: zrangebyscore zset 40 99 limit 1 3   # 查出權重在40-99之內的數據,並從第1條開始,返回3條
           # 40-99都是閉區間, 要想變成開區間這樣寫即可   (40 (99
       P: print(redis.zrangebyscore('zset', 40, 99, start=1, num=3))
  • zrevrangebyscore (根據權重來 逆序遍歷)

       操作同 zrangebyscore, 略
       這API設計的,還不如,直接弄成一條命令,然後加一個逆序參數,吐槽!!!!
  • zrem (刪除某值)

       R: zrem zset Tom        # 刪除Tom這個值
       P: print(redis.zrem('zset','Tom'))
  • zremrangebyscore (刪除 權重 範圍內的值)

       R: zremrangebyscore zset 70 90        # 把權重在70-90分的所有數據刪除
       P: redis.zremrangebyscore('zset', 70, 90)
  • zremrangebyrank (刪除 索引 範圍內的值)

       R: zremrangebyrank zset 0 -1    # 刪除所有值 ( 0到-1的索引就代表所有值啦!)
       P: redis.zremrangebyrank('zset', 0, -1)    # redis的API風格真的。。。沒辦法python也無奈同名
  • zcard (獲取有序集合的 所有 元素個數)

       R: zcard zset
       P: print(redis.zcard('zset'))
  • zcount (統計有序集合的 某權重範圍的 元素個數)

       R: zcount zset 10 69         # 同樣默認閉區間, ( 可改爲開區間
       P: print(redis.zcount('zset',50, 69))  
  • zrank (獲取某元素的索引)

       R: zrank zset Jerry       # 不用猜,索引肯定從0開始
       P: print(redis.zrank('zset', 'Jerry'))
  • zrevrank (逆序 獲取某元素的索引)

       逆序獲取索引,比如最後一個,索引就是0
       具體操作,同 zrank, 略
  • zscore (獲取某元素對應的權重)

       R: zscore zset Jerry
       P: print(redis.zscore('zset', 'Jerry'))
  • zscan (迭代方式和返回 所有元素及其權重)

       """
           嗯?似曾相識燕歸來? 
           前面說過的 scan hsacn sscan 還有接下來要說的 zscan 都是一個樣子的,都是爲了應對大數據來迭代處理
           python版的redis給了我們一個簡化函數,那就是 _iter結尾的, eg: hscan_iter()
           這種 _iter結尾的函數,不用我們來傳遊標cursor參數, 爲啥呢??
               一. 因爲python有生成器-迭代器機制阿!(當然 _iter等函數的源碼就是用yield爲我們實現的)
               二. cursor遊標不易於管理
       """
       R: zscan zset 0 match * count 5 
       P: zset_iter = redis.zscan_iter('zset', match='*', count=5)   # 同理返回可迭代對象
       注:還要說明一下:
           match參數:  
               過濾查詢數據(其實過濾完了,數據量小了也沒必要用scan了,此參數主要用在"hscan"之類的)
               "因此match參數可不寫",  "match='*' 和 不傳是一個效果的。"
           count參數: 
               Py源碼解釋   ``count`` allows for hint the minimum number of returns
               意思就是:   這個參數是一次迭代"最少"取5個",但不管怎麼說,最終還是會取出全部數據!!

Redis兩種持久化的方式

  • 生成RDB文件 (三種方法)

       """RDB機制就是 觸發生成RDB文件,將Redis數據以二進制形式寫入其中, 觸發方式有如下三種"""
       RDB基本配置:
           vi /etc/redis/redis.conf
               dbfilename dump.rdb    # 配置RDB文件名
               dir /var/lib/redis     # 配置RDB文件存放目錄 (ll 命令查看 dump.rdb是否爲最新時間)
               appendonly no          # 若爲yes, 會優先按照aof文件來恢復,或不恢復
       上述配置,可在下面三種方法實現的時候,自動觸發生成RDB文件。並在redis啓動時恢復RDB文件
  • 觸發方式1:save (阻塞)

       R: save
       P: redis.save()
  • 觸發方式2:bgsave (開fork進程,異步,非阻塞)

       R: bgsave
       P: redis.bgsave()
  • 觸發方式3:自動動態生成RDB文件(配置文件)

       在上面RDB基本配置基礎上,追加如下配置
       vi /etc/redis/redis.conf
           save 100 10    # 100秒鐘改變10條數據就會,自動生成RDB文件
  • RDB缺點

       大數據耗時,RDB文件寫入影響IO性能。宕機數據不可控
    
  • 生成AOF文件(三種方法)

       """AOF機制就是 每執行一條命令,都會記錄到緩衝區,在根據某種策略刷新到AOF文件中,策略有如下三種"""
       AOF基本配置:
           vi /etc/redis/redis.conf
               appendonly yes    # 開關,先要打開
               appendfilename "appendonly.aof"    # AOF文件名
               dir /var/lib/redis     # AOF文件目錄(和RDB是一樣的)
           
  • 刷新策略1:always

       always 即緩衝區有一條命令,就會刷新追加到AOF文件中 (安全可靠,耗IO)
  • 刷新策略2:everysec (默認)

       everysec 即每過1秒 就會把緩衝區的命令 刷新追加到AOF文件中
               如果就在這一秒鐘宕機,那麼數據就丟失了。。。(1秒不可控)
  • 刷新策略3:no

       no 即 什麼時候刷新,全聽操作系統自己的 (完全不可控)
    
  • AOF重寫機制 (兩種方法,異步)

  • 重寫清潔過程:

       如上可知,越來越多的命令會追加到AOF中,其中可能會有一些類似
           一、鍵值覆蓋: 
                   set name tom
                   set name jerry
           二、超時時間過期
           三、多條插入(可用一條命令代替)
       如上無用命令,會讓AOF文件變得繁雜。
       可通過 AOF重寫策略優化來達到化簡,提高恢復速度等。
  • 重寫原理(查找資料 + 個人理解):

       一、 開fork子進程 新弄一份AOF文件,它的任務就是把當前redis中的數據重新按照上面的
           ”重寫清潔過程“ 捋一遍,並記錄到這個新AOF文件中
       二、 此時主進程可以正常接受用戶的請求及修改,(這時可能子進程AOF,和數據庫內容不一致,往下看)
       三、 其實---第一條開fork的時候,順便也開了一份內存空間A(名爲重寫緩衝區) 用來平行記錄 用戶新請求的命令
       四、 當子進程AOF重寫完事後, 會把上面 空間A中 中的數據命令追加到 AOF中(類似斷點複製)
       五、 新AOF替代 舊的AOF
       
       打個比方(針對於 二、三、四):
           就是,你給我一個任務,我正做着,你又給我很多任務,我當然忙不過來
           那這樣,你先拿個清單記錄下來,一會等我忙完了,咱們對接一下就好了)
  • 重寫方式1:bgrewriteaof

       R: bgrewriteaof
       P: redis.bgrewriteaof()
  • 重寫方式2:配置文件實現自動重寫

       在上面AOF基本配置的基礎上,追加如下配置
       vi /etc/redis/redis.conf
           appendfsync everysec    # 就是上面說的三種策略,選一種  always no
           auto-aof-rewrite-min-size 64mb     # 當AOF文件超過64mb就會自動重寫
           auto-aof-rewrite-percentage 100    # 100爲增長率, 每一次的限制大小是之前的100%,也就是二倍
    
           no-appendfsync-on-rewrite yes     # yes 就是不把 “重寫緩衝區” 的內容 刷新到 磁盤
           注意這個參數:
               這就是針對上面 ’重寫原理‘ 中的第三條 中的 內存空間A(重寫緩衝區)
               如果這個 重寫緩衝區 不刷新持久化到磁盤中, 要是宕機了,那麼這個緩衝區的數據就會丟失。
               丟失多少呢?  據悉(linux中 最多最多 會丟失 30秒的數據)
               
               如果你將其 設置爲 no,那麼 重寫緩衝區 就會像 前面講的 原始AOF一樣地 刷新持久化到硬盤中。
               但是你想想, 如果 重寫緩衝區  和 原始AOF 都做持久化刷新
                   那麼 它們就會 競爭 IO,性能必定大打折扣,特殊情況下,還可能 堵塞。
                   
               so, 要性能(設爲yes), 要數據完整安全(設爲no), 自己選....

結束語

本文主要寫了關於 redis 以及 python操作redis的語法對比詳細解釋!!
python的redis API 也是非常夠意思了,函數名幾乎完全還原 原生Redis!!

語法部分印象比較深刻的就是 "redis的 scan家族函數" 以及 "python的 scan_iter"家族函數:
    上面陸陸續續講了那麼多數據結構,都有它們各自的"遍歷所有數據的操作"
    但對於大量數據的情況下, 這些遍歷函數就都變成渣渣了, 可能會造成"OOM(內存溢出)等情況"
    這時 redis 機智的爲我們 提供了一些列 "scan家族函數" , 當然這些函數是都需要遊標控制的。
    "遊標cursor"是比較頭疼的東西, 因此 python本着 人性化的思想:
        將 "scan家族函數" 封裝爲 "scan_iter家族函數", 讓我們省去了遊標的操作,可以愉快編程!
    那我就列出全部大家族 以及 對應 原始遍歷函數:
        原始遍歷    redis    python
        keys       scan     scan_iter
        hgetall    hscan    hscan_iter
        smembers   sscan    sscan_iter
        zrange     zscan    zscan_iter
        沿着這個對應規律,之前我發現一件事情:
            爲什麼 "list 的 lrange 沒有對應的 lscan?"
        我像zz一樣還去ov查了一遍, 居然還看到一位外國朋友和我有一樣的疑問。。。
        解答者的一句話,我直接就清醒了, "Instead, you should use LRANGE to iterate the list"
            由於順着規律,思維定勢,卻忘記了 "lrange本身就可以帶索引來迭代 "   lrange list1 0 n
            
        這時我突然又想起 zrange不也是和 lrange語法一樣麼???
        爲何 zrange單獨設立了一個 zscan, 而 list卻沒???
            (查了一下好像是list底層性能之類的原因,我也沒願意繼續看了。。。)
    
    scan 與 iter家族函數,各自的數據結構章節都有寫, 並且在"zset"那節的 "zscan"那裏做出了詳細的分析
END
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章