摘要:在通常情況下,即使只更新配置中的一個(gè)標(biāo)志,也會(huì)導(dǎo)致更新后的配置文件被強(qiáng)制推送至所有服務(wù)器,收到更新的服務(wù)器可能需要重新載入配置甚至可能還要重啟應(yīng)用程序服務(wù)器。將被包裹函數(shù)的一些有用的元數(shù)據(jù)復(fù)制給配置處理器。
上一篇文章:Python--Redis實(shí)戰(zhàn):第五章:使用Redis構(gòu)建支持程序:第3節(jié):查找IP所屬城市以及國家
隨著我們越來越多地使用Redis以及其他服務(wù),如何存儲各項(xiàng)服務(wù)的配置信息將變成一個(gè)棘手的問題:對于一個(gè)Redis服務(wù)器、一個(gè)數(shù)據(jù)庫服務(wù)器以及一個(gè)Web服務(wù)器來說,存儲它們的配置信息并不困難;但如果我們使用了一個(gè)擁有好幾個(gè)從服務(wù)器的Redis主服務(wù)器,或者為不同的應(yīng)用程序設(shè)置了不同的Redis服務(wù)器,甚至為數(shù)據(jù)庫也設(shè)置了主服務(wù)器和從服務(wù)器的話,那么存儲這些服務(wù)器的配置信息將變成一件讓人頭痛的事情。
用于連接其他服務(wù)器以及服務(wù)器的配置信息一般都是以配置文件的形式存儲在硬盤里面,每當(dāng)機(jī)器下線、網(wǎng)絡(luò)連接斷開或者某些需要連接其他服務(wù)器的情況出現(xiàn)時(shí),程序通常需要一次性地對不同服務(wù)器中的多個(gè)配置文件進(jìn)行更新。而這一節(jié)要介紹的就是如何將大部分配置信息從文件轉(zhuǎn)移到Redis里面,使得應(yīng)用程序可以自己完成絕大部分配置工作。
使用Redis存儲配置信息為了展示配置管理方面的難題是多么的常見,來看一個(gè)非常簡單的配置例子:假設(shè)現(xiàn)在我們要用一個(gè)標(biāo)志flag來表示web服務(wù)器是否正在進(jìn)行維護(hù),如果服務(wù)器正在進(jìn)行維護(hù),那么它就不應(yīng)該發(fā)送數(shù)據(jù)庫請求,而是應(yīng)該向訪問們返回一條簡短的【抱歉,我們正在進(jìn)行維護(hù),請稍后重試】的信息;相反,如果服務(wù)器并沒有進(jìn)行維護(hù),那么它就應(yīng)該按照既定的程序來運(yùn)行。
在通常情況下,即使只更新配置中的一個(gè)標(biāo)志,也會(huì)導(dǎo)致更新后的配置文件被強(qiáng)制推送至所有Web服務(wù)器,收到更新的服務(wù)器可能需要重新載入配置、甚至可能還要重啟應(yīng)用程序服務(wù)器。
與其嘗試為不斷增多的服務(wù)寫入和維護(hù)配置文件,不如讓我們直接配置寫入Redis里面。只要將配置信息存儲在Redis里面,并編寫應(yīng)用程序來獲取這些信息,我們就不用再編寫工具來向服務(wù)器推送配置信息了,服務(wù)器和程序也不用再通過載入配置文件的方式 來更新配置信息了。
為了實(shí)現(xiàn)這個(gè)簡單的功能,讓我們假設(shè)自己已經(jīng)構(gòu)建了一個(gè)中間層或者插件,這個(gè)中間層額作用在于:當(dāng)is_under_maintenance()函數(shù)返回True時(shí),它將向用戶顯示維護(hù)頁面;與此相反,如何is_under_maintenance()函數(shù)返回False,它將如常地處理用戶的訪問請求。其中is_under_maintenance()函數(shù)通過檢查一個(gè)名為is-under-maintenance的鍵來判斷服務(wù)器是否正在進(jìn)行維護(hù):如果is-under-maintenance鍵非空,那么函數(shù)返回True;否則返回False,另外,因?yàn)樵L客在看見維護(hù)頁面的時(shí)候通常都會(huì)不耐煩的頻繁刷新頁面,所以為了盡量降低Redis在處理高訪問量Web服務(wù)器時(shí)的負(fù)載,is_under_maintenance()函數(shù)最多只會(huì)每秒更新一次服務(wù)器維護(hù)信息。
下面代碼展示了is_under_maintenance()函數(shù)的具體定義:
import time LAST_CHECKED=None IS_UNDER_MAINTENANCE=False def is_under_maintenance(conn): #將連個(gè)變量設(shè)置為全局變量以便在之后對它們進(jìn)行寫入 global LAST_CHECKED,IS_UNDER_MAINTENANCE #距離上次檢查是否以及超過1秒? if LAST_CHECKED通過將is_under_maintenance()函數(shù)插入應(yīng)用程序的正確位置上,我們可以在1秒內(nèi)改變數(shù)以千計(jì)Web服務(wù)器的行為。為了降低Redis在處理高訪問量web服務(wù)器時(shí)的負(fù)載,is_under_maintenance()函數(shù)將服務(wù)器維護(hù)狀態(tài)信息的更新頻率限制為最多每秒1次,但如果有需要的話,我們也可以加快信息的更新頻率,甚至直接移除函數(shù)里面限制更新速度的那些代碼。雖然is_under_maintenance()函數(shù)看上去似乎并不實(shí)用,但它的確展示了將配置信息存儲在一個(gè)普通可訪問位置的威力。
接下來我們要考慮的是,怎樣才能將更復(fù)雜的配置選項(xiàng)存儲到Redis里面呢?
為每個(gè)應(yīng)用程序組件分別配置一個(gè)Redis服務(wù)器在我們越來越多地使用Redis的過程中,無數(shù)的開發(fā)者已經(jīng)發(fā)現(xiàn),最終在某個(gè)時(shí)間點(diǎn)上,只使用一臺Redis服務(wù)器將不能滿足我們的需要。因?yàn)槲覀兛赡苄枰涗浉嘈畔?,可能需要更多用于緩存的空間,還可能會(huì)使用本書之后的章節(jié)會(huì)介紹的、使用Redis構(gòu)建的高級服務(wù)。但不管何種原因,我們都需要用到更多Redis服務(wù)器。
為了平滑地從單臺服務(wù)器過渡到多臺服務(wù)器,用戶最好還是為應(yīng)用程序中的每個(gè)獨(dú)立部分都分別運(yùn)行一個(gè)Redis服務(wù)器,比如說,一個(gè)專門負(fù)責(zé)記錄日志、一個(gè)專門負(fù)責(zé)記錄統(tǒng)計(jì)數(shù)據(jù)、一個(gè)專門負(fù)責(zé)進(jìn)行緩存、一個(gè)專門負(fù)責(zé)存儲cookies等。別忘了,一臺機(jī)器是可以運(yùn)行多個(gè)Redis服務(wù)器的,只要這些服務(wù)器使用的端口號各不同就可以了。除此之外,在一個(gè)Redis服務(wù)器里面使用多個(gè)【數(shù)據(jù)庫】,也可以減少系統(tǒng)管理的工作量。以上提到的兩種方法,都是通過將不同數(shù)據(jù)劃分至不同鍵空間的方式,來或多或少的簡化遷移至更大或更多服務(wù)器時(shí)所需的工作。但遺憾的是,隨著Redis服務(wù)器的數(shù)量或者Redis數(shù)據(jù)庫的數(shù)量不斷增多,為所有Redis服務(wù)器管理和分發(fā)配置信息的工作將變得越來越煩瑣和無趣。
在上一節(jié)中,我們用了Redis來存儲表示服務(wù)器是否正在進(jìn)行維護(hù)的標(biāo)志,并通過這個(gè)標(biāo)志來決定是否需要向訪客顯示維護(hù)頁面。而這一次,我們同樣可以使用Redis來存儲與其他Redis服務(wù)器有關(guān)的信息。說的更詳細(xì)一點(diǎn),我們可以把一個(gè)已知的Redis服務(wù)器用作配置信息字典,然后通過這個(gè)字典存儲的配置信息來連接為不同應(yīng)用或服務(wù)組件提供數(shù)據(jù)的其他Redis服務(wù)器。此外,這個(gè)字典還會(huì)在配置出現(xiàn)變更時(shí),幫助客戶端連接至正確的服務(wù)器。字典的具體實(shí)現(xiàn)比這個(gè)例子所要求的更為通用一些,因?yàn)槲腋铱隙ǎ?dāng)你開始使用這個(gè)字典來獲取配置信息的時(shí)候,你很快就會(huì)把它應(yīng)用到其他服務(wù)器以及其他服務(wù)上面,而不僅僅用于獲取Redis服務(wù)器的配置信息。
我們將構(gòu)建一個(gè)函數(shù),該函數(shù)可以從一個(gè)鍵里面取出一個(gè)JSON編碼的配置值,其中,存儲配置值的鍵由服務(wù)的類型以及使用該服務(wù)的應(yīng)用程序命名。舉個(gè)例子,如何我們想要獲取連接存儲統(tǒng)計(jì)數(shù)據(jù)的Redis服務(wù)器所需的信息,那么就需要獲取config:redis:statistics鍵的值。下面函數(shù)展示了設(shè)置配置值的具體方法:
def set_config(conn,type,component,config): conn.set("config:%s:%s"%(type,component)) json.dumps(config)通過這個(gè)函數(shù),我們可以隨心所欲的設(shè)置任何JSON編碼的配置信息。因?yàn)間et_config()函數(shù)和前面介紹過的is_under__maintenance()函數(shù)具有相似的結(jié)構(gòu),所以我們只要在語義上稍作修改,就可以使用get_config()函數(shù)來替代is__under_maintenance()函數(shù)。下面代碼列出了與set_config()相對應(yīng)的get_config()函數(shù),這個(gè)函數(shù)可以按照用戶的需要,對配置信息進(jìn)行0秒、1秒或者10秒的局部緩存。
import json import time CONFIGS={} CHECKED={} def get_config(conn,type,component,wait=1): key="config:%s:%s"%(type,component) #檢查是否需要對這個(gè)組件的信息進(jìn)行更新 if CHECKED.get(key)在擁有了配置信息和獲取配置信息的兩個(gè)函數(shù)之后,我們還可以在此之上更近一步。我們在前面一直考慮的都是怎樣存儲和獲取配置信息以便連接各個(gè)不同的Redis服務(wù)器,但直到目前為止,我們編寫的絕大多數(shù)函數(shù)和第一個(gè)參數(shù)都是一個(gè)連接參數(shù)。因此,為了不再需要手動(dòng)獲取我們正在使用的各項(xiàng)服務(wù)的連接,下面讓我們來構(gòu)建一個(gè)能夠幫助我們自動(dòng)連接這些服務(wù)的方法。
自動(dòng)Redis連接管理手動(dòng)創(chuàng)建和傳遞Redis連接并不是一件容易地事情,這不僅是因?yàn)槲覀冃枰貜?fù)查閱配置信息,還有一個(gè)原因就是,即使使用了 上一節(jié)介紹的配置管理函數(shù),我們還是需要獲取配置、連接Redis,并在使用完連接之后關(guān)閉連接。為了簡化連接的管理操作,我們將編寫一個(gè)裝飾器,讓它負(fù)責(zé)連接除配置服務(wù)器之外的所有其他Redis服務(wù)器。
裝飾器Python提供了一種語法,用于將函數(shù)X傳入另一個(gè)函數(shù)Y的內(nèi)部,其中函數(shù)Y就被成為裝飾器。裝飾器給用戶提供了一個(gè)修改函數(shù)X行為的機(jī)會(huì)。有些裝飾器可以用于校驗(yàn)參數(shù),而有些裝飾器則可以用于注冊回調(diào)函數(shù),甚至還有一些裝飾器可以用于管理連接:就像我們接下來要做的那樣。
下面代碼展示了我們定義的裝飾器,它接受一個(gè)指定的配置作為參數(shù)并生成一個(gè)包裝器,這個(gè)包裝器可以包裹一個(gè)函數(shù),使得之后對被包裹函數(shù)的調(diào)用可以自動(dòng)連接至正確的Redis服務(wù)器,并且連接Redis服務(wù)器所使用的那個(gè)連接會(huì)和用戶之后提供的其他參數(shù)一同傳遞至包裹的函數(shù):
REDIS_CONNECTIONS={} #將應(yīng)用組件的名字傳遞給裝飾器 def redis_connection(component,wait=1): #因?yàn)楹瘮?shù)每次被調(diào)用都需要獲取這個(gè)配置鍵,所以我們干脆把它緩存起來 key="config:redis:"+component #包裝器接受一個(gè)函數(shù)作為參數(shù),并使用另一個(gè)函數(shù)來包裹這個(gè)函數(shù)。 def wrapper(function): #將被包裹函數(shù)的一些有用的元數(shù)據(jù)復(fù)制給配置處理器。 @function.wraps(function) def call(*args,**kwargs):#創(chuàng)建負(fù)責(zé)管理連接信息的函數(shù) #如果有就配置存在,那么獲取它 old_config=CONFIGS.get(key,object()) #如果有新配置存在,那么獲取它 _config=get_config(config_connection,"redis",component,wait) config={} #對配置進(jìn)行處理并將其用于創(chuàng)建Redis連接 for k,v in _config.iteritems(): config[k.encode("utf-8")]=v #如果新舊配置并不相同,那么創(chuàng)建新的連接 if config!=old_config: REDIS_CONNECTIONS[key]=redis.Redis(**config) #將Redis連接以及其他匹配的參數(shù)傳遞給包裹函數(shù),然后調(diào)用該函數(shù)并返回它的執(zhí)行結(jié)果。 return function(REDIS_CONNECTIONS.get(key),*args,**kwargs) #返回被包裹的函數(shù) return call #返回用于包裹Redis函數(shù)的包裝器 return wrapper同時(shí)使用*args和**kwargs在Python中,函數(shù)定義的args變量用于獲取所有位置參數(shù),而kwargs變量則用于獲取所有命令出納和素,這兩種參數(shù)傳遞方式都可以將給定的參數(shù)傳入被調(diào)用的函數(shù)里面。
上面戰(zhàn)術(shù)的一系列嵌套函數(shù)初看上去可能會(huì)讓人感動(dòng)頭昏目眩,但它們實(shí)際上并沒有想象中的那么復(fù)雜。redis_connection()裝飾器接受一個(gè)應(yīng)用組件的名字作為參數(shù)并返回一個(gè)包裝器。這個(gè)包裝器接受一個(gè)我們想要將連接傳遞給它的函數(shù)為參數(shù),然后對函數(shù)進(jìn)行包裹并返回被包裹函數(shù)的調(diào)研器。這個(gè)調(diào)用器負(fù)責(zé)處理所有獲取配置信息的工作,除此之外,它還負(fù)責(zé)連接Redis服務(wù)器并調(diào)用被包裹的函數(shù)。盡管redis_connecition()函數(shù)描述起來相當(dāng)復(fù)雜,但實(shí)際使用起來卻是非常方便的,下面代碼就展示了怎樣將redis_connection()函數(shù)應(yīng)用到之間介紹的log_recent()函數(shù)上面。
@redis_connection("logs") def log_recent(conn,app,message,severity=logging.INFO,pipe=None): # 嘗試將日志的安全級別準(zhǔn)還為簡單的字符串 severity = str(SEVERITY.get(severity, severity)).lower() # 創(chuàng)建負(fù)責(zé)存儲消息的鍵 destination = "recent:%s:%s" % (name, severity) # 將當(dāng)前時(shí)間添加到消息里面,用于記錄消息的發(fā)送時(shí)間 message = time.asctime() + " " + message # 使用流水線來將通信往返次數(shù)降低為一次 pipe = pipe or conn.pipeline() # 將消息添加到日志列表的最前面 pipe.lpush(destination, message) # 對日志列表進(jìn)行修建,讓它只包含最新的100條消息 pipe.ltrim(destination, 0, 99) # 執(zhí)行兩個(gè)命令 pipe.execute() log_recent("main","User 235 logged in")現(xiàn)在你已經(jīng)看到怎樣使用redis_connection()來裝飾log_recent()函數(shù),這個(gè)裝飾器還是蠻有用的,不是嗎?通過使用這個(gè)改良后的方法來處理鏈接和配置,我們幾乎可以把我們要調(diào)用的所有函數(shù)的代碼都刪去好幾行。
作為練習(xí),請嘗試使用redis_connection()去裝飾之前介紹的access_time()上下文管理器,使得這個(gè)上下文管理器可以在不必手動(dòng)傳遞Redis服務(wù)器連接的情況下執(zhí)行。
本章小結(jié)本章介紹的所有主題都直接或間接地用于對應(yīng)用程序進(jìn)行幫助和支持,這里展示的函數(shù)和裝飾器都旨在幫助讀者學(xué)會(huì)如何使用Redis來支撐應(yīng)用程序的不用部分:日志、計(jì)數(shù)器以及統(tǒng)計(jì)數(shù)據(jù)可以幫助用戶直觀地了解應(yīng)用程序的性能,而IP所屬地查找程序則可以告訴你客戶所在的地點(diǎn)。除此之外,存儲服務(wù)的發(fā)現(xiàn)和配置信息可以幫助我們減少大量需要手動(dòng)處理連接的工作。
現(xiàn)在我們已經(jīng)知道了怎樣使用Redis來對應(yīng)用程序進(jìn)行支持了,在接下來的第6章,我們將學(xué)習(xí)如何使用Redis來構(gòu)建應(yīng)用程序組件。
上一篇文章:Python--Redis實(shí)戰(zhàn):第五章:使用Redis構(gòu)建支持程序:第3節(jié):查找IP所屬城市以及國家
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/42741.html
摘要:下面清單展示了地址所屬地查找程序的具體實(shí)現(xiàn)方法將地址轉(zhuǎn)換為分值以便執(zhí)行命令查找唯一城市方法用來根據(jù)指定的分隔符將字符串進(jìn)行分割。 上一篇文章:Python--Redis實(shí)戰(zhàn):第五章:使用Redis構(gòu)建支持程序:第2節(jié):計(jì)數(shù)器和統(tǒng)計(jì)數(shù)據(jù)下一篇文章:Python--Redis實(shí)戰(zhàn):第五章:使用Redis構(gòu)建支持程序:第4節(jié):服務(wù)的發(fā)現(xiàn)與配置 通過將統(tǒng)計(jì)數(shù)據(jù)和日志存儲到Redis里面,我們...
摘要:包括在內(nèi)的很多軟件都使用這種方法來記錄日志。在這一節(jié)中,我們將介紹如何使用來存儲于時(shí)間緊密相關(guān)的日志,從而在功能上替代那些需要在短期內(nèi)被存儲的消息。 上一篇文章:Python--Redis實(shí)戰(zhàn):第四章:數(shù)據(jù)安全與性能保障:第8節(jié):關(guān)于性能方面的注意事項(xiàng)下一篇文章:Python--Redis實(shí)戰(zhàn):第五章:使用Redis構(gòu)建支持程序:第2節(jié):計(jì)數(shù)器和統(tǒng)計(jì)數(shù)據(jù) 在構(gòu)建應(yīng)用程序和服務(wù)的過程中...
閱讀 2434·2021-10-11 10:57
閱讀 1284·2021-10-09 09:59
閱讀 2000·2019-08-30 15:53
閱讀 3216·2019-08-30 15:53
閱讀 1014·2019-08-30 15:45
閱讀 742·2019-08-30 15:44
閱讀 3448·2019-08-30 14:24
閱讀 955·2019-08-30 14:21