項目背景:
之前在做項目的時候,由於是采用微服務架構,所有服務通信使用 Redis 作為數據交互層。需求是不同類型的數據存儲在不同的數據庫中,不同的數據庫就需要動態切換 Redis 數據庫。
Redis 默認有 16 個數據庫,index(值范圍:0~15),默認 index=0。切換數據庫命令為:select index。
但是呢,flask_redis 卻沒有這個功能。為什么會沒有呢?因為 redis-py 就沒有。flask_redis 是基於 redis-py 進行二次封裝的庫,redis-py 為了保證 Redis 實例的線程安全,沒有實現 SELECT 指令。如果有不明白的歡迎扣扣裙詢問哦(++六零六九一六八三一)
Redis
客戶端實例可以安全地在線程間共享。從內部實現來說,只有在命令執行時才獲取連接實例,完成后直接返回連接池,命令永不修改客戶端實例的狀態。但是,有一點需要注意:SELECT
命令。SELECT
命令允許切換當前連接使用的數據庫。新的數據庫保持被選中狀態,直到選中另一個數據庫或連接關閉。這會導致在返回連接池時,連接可能指定了別的數據庫。因此,redis-py
沒有在客戶端實例中實現 SELECT 命令。如果要在同一個應用中使用多個 Redis
數據庫,應該給第一個數據庫創建獨立的客戶端實例(可能也需要獨立的連接池)。
flask_redis 既然不能動態切換數據庫,那我們就從根源入手——使用前都重新連接並指定數據庫。
感覺是挺暴力的,其實,這是官方建議的做法。自己動手豐衣足食,接下來我們就來封裝一個支持動態切換數據庫的 Redis,需要滿足用戶在使用的時候無感知。
廢話不多說,直接上代碼:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import copy
import datetime
from redis import StrictRedis, ConnectionPool
class RedisLib(object):
def __init__(self, redis_db, redis_url, blacklist=None):
self.redis = None
self.redis_db = redis_db
self.redis_url = redis_url
self.blacklist = blacklist
self.blacklist_data = None
def select(self, db):
url = '%s/%s' % (self.redis_url, db.split('db')[1])
pool = ConnectionPool.from_url(url=url, decode_responses=True)
self.redis = StrictRedis(connection_pool=pool)
def push_redis(self, db, data):
def handle_data():
self.blacklist_data = [value for value in map(
lambda index: data.pop(index) if data.get(index) else None, self.blacklist)]
key = '%s:%s' % (self.redis_db[db], data['id'])
for k, v in data.items():
self.redis.hset(key, k, v.strftime("%Y-%m-%d %H:%M:%S") if isinstance(
v, datetime.datetime) else (v if v else ''))
self.select(db)
if isinstance(data, list):
for obj in data:
data = copy.deepcopy(obj.__dict__)
handle_data()
else:
data = copy.deepcopy(data.__dict__)
handle_data()
def pull_redis(self, db, _id=None):
self.select(db)
key = '%s:%s' % (self.redis_db[db], _id if _id else '')
if _id is None:
data = self.redis.dbsize()
elif _id == 'key':
data = self.redis.keys()
elif _id == '*':
data = [self.redis.hgetall(key) for key in self.redis.keys()]
else:
data = self.redis.hgetall(key)
return data
def __del__(self):
self.redis.connection_pool.disconnect()
以上代碼就是實現了新增數據、獲取數據、切換數據庫的功能,在實例化的時候需要進行配置。雖然可以使用了,但是並不友好,因為我每次都得需要指定數據庫,那有沒有什么方法可以連數據庫指定都不用呢?
當然有的啦!
那就是再寫一個[狗頭]哈哈!
class HandleQueue(RedisLib):
def __init__(self):
self.redis_db = {
'db0': None,
'db1': 'oss:aop:user',
'db9': 'oss:aop:role'
}
self.redis_url = 'redis://127.0.0.1:6379'
# Flask app config from redis url
# from app import app
# self.redis_url = app.config['REDIS_URL']
self.blacklist = ['_sa_instance_state', 'version', 'status']
RedisLib.__init__(self, self.redis_db, self.redis_url, self.blacklist)
def set_user_data(self, data):
return self.push_redis('db1', data)
def get_user_data(self, user_id=None):
return self.pull_redis('db1', user_id)
def set_role_data(self, data):
return self.push_redis('db9', data)
def get_role_data(self, dict_id=None):
return self.pull_redis('db9', dict_id)
if __name__ == '__main__':
handle_queue = HandleQueue()
return_value = handle_queue.get_user_data(1)
print(return_value)
運行結果:
{'username': '極客點兒', 'role_id': '2', 'phone': '', 'account': 'GeekDot', 'mail': '', 'update_time': '2020-09-22 15:52:32', 'create_time': '2020-08-13 16:51:51', 'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDA4NDc1NTIsImlhdCI6MTYwMDc2MTE1MiwiaXNzIjoiMjF2aWFuZXQiLCJkYXRhIjp7ImlkIjoxfX0.fEAfuRC3tvA-qv5j6Xjbqu9W8ksY4PZwYwFoI3ST3xU', 'number': '', 'id': '1'}
通過類繼承把切換數據庫等基本功都保留,然后通過構造函數配置數據庫,最后用到哪些數據和數據庫就寫一個很簡單的方法即可。
因為我們是 Flask 項目,所以要使用 Flask 的配置文件,方法如下,也很簡單。
# Flask app config from redis url # from app import app # self.redis_url = app.config['REDIS_URL']
因為項目組中用的是 ORM 所以 push_redis 現在只能接受 ORM 對象。如果想支持其他對象,可以自己改寫,也簡單。
最后別忘了使用析構函數將數據庫關閉!以上為部分代碼,需要源碼的可以來扣扣裙,群里有免費的源碼和python學習資料。
