1. 程式人生 > >基於Python使用Redis的一些想法和建議

基於Python使用Redis的一些想法和建議

1關於Redis使用的一點想法

1.1進行快取前,需考慮

(1)該資料屬於短暫保留,例如只保留三天、七天或者一個月,此時建議採用快取;

(2)該資料在某一個時間段請求量很大,此時建議採用快取;

(3)隨著使用者使用,資料不斷變化,更新操作比較頻繁,此時建議採用快取;

(4)如果資料量不大,且和應用效能提升不大,資料需要長久保留,此時不建議採用Redis進行快取,直接使用MySQL等關係型資料庫儲存即可;

(5)如果資料量很大,但是過了一段時間後,該資料幾乎沒有什麼價值,此時建議採用快取,並設定過期時間和定時清理該資料的指令碼,這樣處理可以減輕儲存空間,也便於優化系統的資料庫層。

1.2進行快取後,需考慮

(1)快取該key在極端情況下,佔用系統記憶體會有多大?或者說儲存的記錄大概會達到什麼數量級?

(2)快取該key,過期時間是否方便設定?如果不方便設定,是否可以隔段時間考慮轉存到MySQL等關係型資料庫中,從而清理快取,釋放記憶體空間。

(3)快取該key,思考一下手動刪除快取資料的指令碼如何編寫,快取的所有資料,如何區分出有價值的資料進行保留,無價值的便利用指令碼進行自動化刪除。

(4)使用快取後,要思考選擇恰當的資料結構來完成程式碼構建。因為一個適合的資料結構不僅使得程式碼變得更加優雅,後期維護也很方便。

1.3快取使用一段時間後

如果發現某一個key佔用記憶體很大,超出預料,提供的優化建議:

(1)分析該key的具體實際功能,和目前的需求,看能否在後續快取資料時,新增過期時間設定;

(2)考慮在取出快取資料的時候,能否轉存到MySQL等關係型資料庫,如果能夠轉存成功,則在此處可以進行立即執行刪除該條快取資料的方法;後續取資料時,可採用先查詢Redis資料庫,未查到再次查詢一下MySQL等關係型資料庫;

(3)依據已經快取的資料,看能夠依據資料中的欄位或者相關屬性對已經快取的資料進行過濾查詢,把那些不重要的資料通過指令碼進行手動刪除處理。

2編寫Redis資料庫層規範建議

2.1選擇適合的redis客戶端

例如,定義了以下兩個客戶端:

複製程式碼

# -*- coding: utf-8 -*-"

from django.conf import settings
import redis

redis_db_client = redis.StrictRedis(
        host=settings.REDIS['redis_db_host'],
        port=settings.REDIS['redis_db_port'],
        db=settings.REDIS['redis_db_db'],
        socket_connect_timeout=4,
        socket_timeout=2,
        decode_responses=True,
    )

redis_hot_client = redis.StrictRedis(
        host=settings.REDIS['redis_hot_host'],
        port=settings.REDIS['redis_hot_port'],
        db=settings.REDIS['redis_hot_db'],
        socket_connect_timeout=2,
        socket_timeout=2,
        decode_responses=False,

    )

複製程式碼

    此時,可以依據key的設計和作用,選擇合適的客戶端來操作。其中不同的客戶端對應的埠和具體資料庫不同,以上客戶端定義僅作參考。(PS:以上定義是基於Django框架的配置檔案來使用,其它Python框架也可以類似定義)

2.2規範化定義key的名稱並初始化

在定義redis操作key的名稱建議採用大寫字母加下劃線組成;在初始化key物件時,最好能夠設定過期時間。

Key定義的類示例:

複製程式碼

class RedisKey:
    """RedisKey類物件"""
    def __init__(self, prefix, ex=None):
        self.prefix = prefix
        self.ex = ex

    # 生成key值
    def __call__(self, key):
        return self.prefix + str(key)

複製程式碼

則定義一個key的示例:

USER_PULL_URL = RedisKey(prefix='user_pull_url:', ex=8 * 60 * 60)

其中引數prefix為key儲存在Redis資料中具體的鍵名,可以通過呼叫__call___方法來為key新增字尾,例如user_pull_url:1808表示一個鍵名;ex為該key物件定義的過期時間設定,等到具體編寫該key的新增操作時,呼叫引數ex來設定key的具體過期時間。

其中key存放的檔案,要依據選擇的客戶端存放在指定的檔案中,這樣方便檢視和管理。

2.3 選擇合適的資料結構

Redis資料庫包含String(字串)、Hash(雜湊表)、List(列表)、Set(集合)、SortedSet(有序集合)五種資料結構。下面簡單介紹一下這五種資料結構的特性:

  • String(字串):新增資料時,採用key-value格式進行儲存。key是定義的鍵名,在上面(2)中已有說明。value是具體要儲存的資料,該資料的型別是String型別。遇到的需求中,例如需要儲存某使用者的線上時長,可以採用key_user_id組成鍵名,具體時長儲存在value中,此時可選擇String資料結構來儲存,比較方便。
  • Hash(雜湊表):新增資料時,採用key-value格式來進行儲存,不過這裡的value表示一張雜湊表。可以這樣做比喻,key比作關係型資料庫中的表名,value儲存該關係型表中的所有行的資料記錄。
  • List(列表):新增資料時,採用key-value格式來進行儲存,不過這裡的value表示一個具體的列表。該列表的功能可類似C++資料結構列表一樣,有出表操作,計算列表長度操作,依據下標獲取某個元素的功能,獲取指定區間內的列表元素,從列表頭部插入元素或者尾部插入元素等。
  • Set(集合):新增資料時,採用key-value格式來進行儲存,這裡的value儲存和Hash(雜湊表)儲存型別,而Set(集合)區別在於在新增資料記錄時,會自動過濾掉相同值得記錄,如果插入多條記錄相同得資料,在Set(集合)儲存得value中只會找到一條記錄。
  • SortedSet(有序集合):新增資料時,採用key-value格式來進行儲存。儲存的方式實現功能和Set(集合)基本相同,但是其唯一突出的特點就是在存資料的時候要儲存元素值對應的score值,也就是依據score值的大小來對value中元素進行排序儲存。後續有查詢操作時,可以很方便的返回value中元素的排序序列。

2.4 規範化定義操作方法

每一個服務層,建議單獨建立一個cache.py檔案,專門用於存放操作Redis資料庫層的方法,此類的功能可以類比models.py檔案。

每一個key使用其鍵名建立符合程式碼規範的類名,然後在該key對應的類裡面,定義操作的redis_client和redis_key,最後通過cliet和key定義相關資料的新增、修改、查詢和刪除的方法。

最後,最重要的一點建議:該cache.py中定義的操作方法建議只在該服務層中被其它類中方法體呼叫。這樣的好處,可以讓我們對於該key在以後的資料管理上有可控的預估操作,也使得程式碼呼叫變得更加規範。

此處給出一個示例:

定義一個key:

鍵名初始化:

USER_SESSION = RedisKey(prefix='user_session:', ex=4 * 60 * 60)

選擇客戶端:

redis_hot_client

在cache.py中定義的類和相關操作方法:

複製程式碼

class CacheUserSession:   # 類名和key的名稱對應

    """
    原始資料型別: dict
    儲存資料型別: bytes
    資料說明:將user_dict序列化成二進位制資料,存入Redis中
    """
    db = redis_hot_client
    key_prefix = rediskey.USER_SESSION

    @classmethod
    def get(cls, user_id: IdInt):
        key = cls.key_prefix(user_id)    # key的具體初始化值
        user_dict = cls.db.get(key)
        if user_dict:
            user_dict = pickle.loads(user_dict)
        return user_dict

    @classmethod
    def delete(cls, user_id: IdInt):    # key的刪除方法定義
        key = cls.key_prefix(user_id)
        cls.db.delete(key)
        logger.info("Delete UserSession:{}".format(user_id))

    @classmethod
    def set(cls, user_id: IdInt, user_dict: dict):
        key = cls.key_prefix(user_id)
        data = pickle.dumps(user_dict)
        cls.db.set(key, data, ex=cls.key_prefix.ex)   # 注意設定過期時間
        logger.info("Set UserSession:{}, {}".format(user_id, user_dict)) 

複製程式碼

此處,給出一個很好用的儲存資料的途徑。通過以下途徑,可以把一個字典格式的資料當作字串存入Redis資料庫,取出後可以重新解析出字典格式。通過該途徑,可以較好的把Redis當作關係型資料庫儲存一樣,一條資料記錄可以儲存多個屬性的值。

例如,一個學生,包含學號、姓名、性別、年級、專業等屬性。如果採用關係型資料庫存,以學號作為主鍵,其它屬性作為列名進行設計,通過學號即可查詢出該學生的所有資訊。但是,一個學生的多個屬性資訊如何儲存到Redis資料庫中,並且取出來能夠很好的使用呢?

採用的策略:學號唯一,可作為key值,姓名、性別、年級、專業聯合成一個字典,在存入Redis資料中之前先轉換為指定結構的字串格式,取出後再解析成字典格式。這樣操作就能很好的解決這個需求。

為什麼要轉換為字串格式存入Redis資料庫呢?因為不管什麼資料結構的資料存入到Redis資料庫中後,它取出都是字串格式。

下面請看具體程式碼示例(具體cache.py中CacheStudent類實現程式碼不給出噢):

複製程式碼

import json

value_data = json.dumps({
  name:  "xiaoming",
  gender:  "male",
  grade:  "2014",
  profession:  "軟體工程",

})    # 通過json模組把字典轉換為指定格式的字串

CacheStudent.set(student_id,  value_data)  # 通過定義好的寫入方法,把指定的學生資料存入
 
student_data = json.loads(CacheStudent.get(student_id))  # 取出指定學號學生的資料後,使用json模組的loads方法解析該字串變成字典格式

print(studnet_data["name"])   # 列印該學生的姓名資訊
print(studnet_data["gender"])
print(studnet_data["grade"])
print(studnet_data["profession"])

複製程式碼

看到上述的實現,是不是發現Redis儲存可以當作關係型資料儲存來用?

2.5 開始愉快的呼叫之旅

此處呼叫,可適當選擇時機何時進行資料刪除操作。比如取出後,可以選擇把最終儲存的資料轉存到MySQL資料庫中,然後呼叫cache.py中的刪除方法,對該資料記錄進行刪除操作。