成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Python--Redis實(shí)戰(zhàn):第五章:使用Redis構(gòu)建支持程序:第2節(jié):計(jì)數(shù)器和統(tǒng)計(jì)數(shù)據(jù)

sourcenode / 3051人閱讀

摘要:清理程序通過對(duì)記錄已知計(jì)數(shù)器的有序集合執(zhí)行命令來一個(gè)接一個(gè)的遍歷所有已知的計(jì)數(shù)器。

上一篇文章:Python--Redis實(shí)戰(zhàn):第五章:使用Redis構(gòu)建支持程序:第1節(jié):使用Redis來記錄日志
下一篇文章:Python--Redis實(shí)戰(zhàn):第五章:使用Redis構(gòu)建支持程序:第3節(jié):查找IP所屬城市以及國家

正如第三章所述,通過記錄各個(gè)頁面的被訪問次數(shù),我們可以根據(jù)基本的訪問計(jì)數(shù)信息來決定如何緩存頁面。但是第三章只是一個(gè)非常簡單的例子,現(xiàn)實(shí)情況很多時(shí)候并非是如此簡單的,特別是涉及實(shí)際網(wǎng)站的時(shí)候,尤為如此。

知道我們的網(wǎng)站在最近5分鐘內(nèi)獲得了10 000次點(diǎn)擊,或者數(shù)據(jù)庫在最近5秒內(nèi)處理了200次寫入和600次讀取,是非常有用的。通過在一段時(shí)間內(nèi)持續(xù)地記錄這些信息,我們可以注意到流量的突增和漸增情況,預(yù)測何時(shí)需要對(duì)服務(wù)器進(jìn)行升級(jí),從而預(yù)防系統(tǒng)因?yàn)樨?fù)載超載而下線。

這一節(jié)將分別介紹使用Redis來實(shí)現(xiàn)計(jì)數(shù)器的方法以及使用Redis來進(jìn)行數(shù)據(jù)統(tǒng)計(jì)的方法,并在最后討論如何簡化示例中的數(shù)據(jù)統(tǒng)計(jì)操作。本節(jié)展示的例子都是由實(shí)際的用例和需求驅(qū)動(dòng)的。首先,讓我們來看看,如何使用Redis來實(shí)現(xiàn)時(shí)間序列計(jì)數(shù)器,以及如何使用這些計(jì)數(shù)器來記錄和監(jiān)視應(yīng)用程序的行文。

將計(jì)數(shù)器存儲(chǔ)到Redis里面

在監(jiān)控應(yīng)用程式的同時(shí),持續(xù)的收集信息是一件非常重要的事情。那些影響網(wǎng)站響應(yīng)速度以及網(wǎng)站所能服務(wù)的頁面數(shù)量的代碼改動(dòng)、新的廣告營銷活動(dòng)或者是剛剛接觸系統(tǒng)的新用戶,都有可能會(huì)徹底地改變網(wǎng)站載入頁面的數(shù)量,并因此而影響網(wǎng)站的各項(xiàng)性能指標(biāo)。但如果我們平時(shí)不記錄任何指標(biāo)數(shù)據(jù)的話,我們就不可能知道指標(biāo)發(fā)生了變化,也就不知道網(wǎng)站的性能是在提高還是在下降。

為了收集指標(biāo)數(shù)據(jù)并進(jìn)行監(jiān)視和分析,我們將構(gòu)建一個(gè)能夠持續(xù)創(chuàng)建并維護(hù)計(jì)數(shù)器的工具,這個(gè)工具創(chuàng)建的每個(gè)計(jì)數(shù)器都有自己的名字(名字帶有網(wǎng)站點(diǎn)擊量、銷量或者數(shù)據(jù)庫查詢字樣的計(jì)數(shù)器都是比較重要的計(jì)數(shù)器)。這些計(jì)數(shù)器會(huì)以不同的精度(如1秒、5秒、1分鐘等)存儲(chǔ)最新的120個(gè)數(shù)據(jù)樣本,用戶也可以根據(jù)自己的需要,對(duì)取樣的數(shù)量和精度進(jìn)行修改。

實(shí)現(xiàn)計(jì)數(shù)器首先需要考慮的就是如何存儲(chǔ)計(jì)數(shù)器的信息,接下來將說明我們是如何將計(jì)數(shù)器信息存儲(chǔ)在Redis里面

對(duì)計(jì)數(shù)器進(jìn)行更新

為了對(duì)計(jì)數(shù)器進(jìn)行更新,我們需要存儲(chǔ)實(shí)際的計(jì)數(shù)器信息,對(duì)于每個(gè)計(jì)數(shù)器以及每種精度,如網(wǎng)站點(diǎn)擊量計(jì)數(shù)器/5秒,我們將使用一個(gè)散列來存儲(chǔ)網(wǎng)站在每個(gè)5秒時(shí)間片之內(nèi)獲得的點(diǎn)擊量,其中,散列的每個(gè)鍵都是某個(gè)時(shí)間片的開始時(shí)間,而鍵對(duì)應(yīng)的值則存儲(chǔ)了網(wǎng)站在該時(shí)間片之內(nèi)獲得的點(diǎn)擊量。下表展示了一個(gè)點(diǎn)擊量計(jì)數(shù)器存儲(chǔ)的其中一部分?jǐn)?shù)據(jù),這個(gè)計(jì)數(shù)器以每5秒為一個(gè)時(shí)間片記錄著網(wǎng)站的點(diǎn)擊量:

鍵名:count:5:hits 類型:hash
1336376410 45
1336376405 28
1336376395 17(本行數(shù)據(jù)表示:網(wǎng)站在2012年5月7日早晨7:39:55到7:40:00總共獲得了17次點(diǎn)擊)
1336376400 29

為了能夠清理計(jì)數(shù)器包含的舊數(shù)據(jù),我們需要在使用計(jì)數(shù)器的同時(shí),對(duì)被使用的計(jì)數(shù)器進(jìn)行記錄。為了做到這一點(diǎn),我們需要一個(gè)有序序列,這個(gè)序列不能包含任何重復(fù)元素,并且能夠讓我們一個(gè)接一個(gè)地遍歷序列中包含的所有元素。雖然同時(shí)使用列表和集合可以實(shí)現(xiàn)這種序列,但同時(shí)使用兩種數(shù)據(jù)結(jié)構(gòu)需要編寫更多代碼,并且增加客戶端和Redis之間的通信往返次數(shù)。實(shí)際上,實(shí)現(xiàn)有序序列更好的方法時(shí)使用有序集合,有序集合的各個(gè)成員分別由計(jì)數(shù)器的精度以及計(jì)數(shù)器的名字組成,而所有成員的分值都是0.因?yàn)樗谐蓡T的分值都被設(shè)置為0,所以Redis在嘗試按分值對(duì)有序集合進(jìn)行排序的時(shí)候,就會(huì)發(fā)現(xiàn)這一點(diǎn),并改為使用成員名進(jìn)行排序,這使得一組給定的成員總是具有固定的排列順序,從而可以方便地對(duì)這些成員進(jìn)行順序性的掃描。下表展示了一個(gè)有序集合,這個(gè)有序集合記錄了正在使用的計(jì)數(shù)器。

鍵名:known: 類型:zset(有序集合)
1:hits 0
5:hits 0
60:hits 0

既然我們已經(jīng)知道應(yīng)該使用什么結(jié)構(gòu)來記錄并表示計(jì)數(shù)器了,現(xiàn)在是時(shí)候來考慮一下如何使用和更新這些計(jì)數(shù)器了。

下面代碼展示了程序更新計(jì)數(shù)器的方法,對(duì)于每種時(shí)間片精度,程序都會(huì)將計(jì)數(shù)器 的精度和名字作為引用信息添加都記錄已有計(jì)數(shù)器的有序集合里面,并增加散列計(jì)數(shù)器在指定時(shí)間片內(nèi)的計(jì)數(shù)值。

#以秒為單位的計(jì)數(shù)器精度,分別為1秒/5秒/1分鐘/5分鐘/1小時(shí)/5小時(shí)/1天
#用戶可以按需調(diào)整這些精度
import time

PRECISION=[1,5,60,300,3600,18000,86400]

def update_counter(conn,name,count=1,now=None):
    #通過獲取當(dāng)前時(shí)間來判斷應(yīng)該對(duì)哪個(gè)時(shí)間片執(zhí)行自增操作。
    now=now or time.time()
    #為了保證之后的清理工作可以正確的執(zhí)行,這里需要?jiǎng)?chuàng)建一個(gè)事務(wù)性流水線
    pipe=conn.pipeline()
    #為我們記錄的每種精度都創(chuàng)建一個(gè)計(jì)數(shù)器
    for prec in PRECISION:
        #取得當(dāng)前時(shí)間片的開始時(shí)間
        pnow=int(now/prec)*prec
        #創(chuàng)建負(fù)責(zé)存儲(chǔ)計(jì)數(shù)信息的散列
        hash="%s:%s"%(prec,name)
        # 將計(jì)數(shù)器的引用信息添加到有序集合里面,并將其分值設(shè)為0,以便在之后執(zhí)行清理操作
        pipe.zadd("known:",hash,0)
        #對(duì)給定名字和精度的計(jì)數(shù)器進(jìn)行更新
        pipe.hincrby("count:"+hash,pnow,count)
    pipe.execute()

更新計(jì)數(shù)器信息的過程并不復(fù)雜,程序只需要為每種時(shí)間片精度執(zhí)行zadd命令和hincrby命令就可以了。于此類似,從指定精度和名字的計(jì)數(shù)器里面獲取計(jì)數(shù)數(shù)據(jù)也是一件非常容易地事情。下面代碼展示了用于執(zhí)行這一操作的代碼:程序首先使用hgetall命令來獲取整個(gè)散列,接著將命令返回的時(shí)間片和計(jì)數(shù)器的值從原來的字符串格式轉(zhuǎn)換成數(shù)字格式,根據(jù)時(shí)間對(duì)數(shù)據(jù)進(jìn)行排序,最后返回排序后的數(shù)據(jù):

def get_counter(conn,name,precision):
    #取得存儲(chǔ)計(jì)數(shù)器數(shù)據(jù)的鍵的名字
    hash="%s:%s"%(precision,name)
    #從Redis里面取出計(jì)數(shù)器數(shù)據(jù)
    data=conn.hgetall("count:"+hash)
    to_return=[]
    #將計(jì)數(shù)器數(shù)據(jù)轉(zhuǎn)換成指定的格式
    for key,value in data.iteritems():
        to_return.append((int(key),int(value)))
    #對(duì)數(shù)據(jù)進(jìn)行排序,把舊的數(shù)據(jù)樣本排在前面
    to_return.sort()
    return to_return

get_counter()函數(shù)的工作方式就和之前描述的一樣,它獲取計(jì)數(shù)器數(shù)據(jù)并將其轉(zhuǎn)換成整數(shù),然后根據(jù)時(shí)間先后對(duì)轉(zhuǎn)換后的數(shù)據(jù)進(jìn)行排序。

在弄懂了獲取計(jì)數(shù)器存儲(chǔ)的數(shù)據(jù)之后,接下來我們要考慮的是如何防止這些計(jì)數(shù)器存儲(chǔ)過多的數(shù)據(jù)。

清理舊計(jì)數(shù)器

經(jīng)過前面的介紹,我們已經(jīng)知道了怎樣將計(jì)數(shù)器存儲(chǔ)到Redis里面,已經(jīng)怎樣從計(jì)數(shù)器里面取出數(shù)據(jù)。但是,如果我們只是一味地對(duì)計(jì)數(shù)器進(jìn)行更新而不執(zhí)行任何清理操作的話,那么程序最終將會(huì)因?yàn)榇鎯?chǔ)了過多的數(shù)據(jù)而導(dǎo)致內(nèi)存不足。好在我們事先已將所有已知的計(jì)數(shù)器記錄到了一個(gè)有序集合里面,所以對(duì)計(jì)數(shù)器進(jìn)行清理只需要遍歷有序集合并刪除其中的舊計(jì)數(shù)器舊可以了。

為什么不使用expire?

expire命令的其中一個(gè)限制就是它只能應(yīng)用整個(gè)鍵,而不能只對(duì)鍵的某一部分?jǐn)?shù)據(jù)進(jìn)行過期處理。并且因?yàn)槲覀儗⑼粋€(gè)計(jì)數(shù)器在不同精度下的所有計(jì)數(shù)器數(shù)據(jù)都存放到了同一個(gè)鍵里面,所以我們必須定期地對(duì)計(jì)數(shù)器進(jìn)行清理。如果讀者感興趣的話,也可以試試改變計(jì)數(shù)器組織數(shù)據(jù)的方式,使用Redis的過期鍵功能來代替手工的清理操作。

在處理和清理舊數(shù)據(jù)的時(shí)候,有幾件事情是需要我們格外留心的,其中包括以下幾件:

任何時(shí)候都可能會(huì)有新的計(jì)數(shù)器被添加進(jìn)來

同一時(shí)間可能會(huì)有多個(gè)不同的清理操作在執(zhí)行

對(duì)于一個(gè)每天只更新一次的計(jì)數(shù)器來說,以每分鐘一次的頻率嘗試清理這個(gè)計(jì)數(shù)器只會(huì)浪費(fèi)計(jì)算資源。

如果一個(gè)計(jì)數(shù)器不包含任何數(shù)據(jù),那么程序就不應(yīng)該嘗試對(duì)它進(jìn)行清理。

我們接下來要構(gòu)建一個(gè)守護(hù)進(jìn)程函數(shù),這個(gè)函數(shù)的工作方式和第三章中展示的守護(hù)進(jìn)程函數(shù)類似,并且會(huì)嚴(yán)格遵守上面列出的各個(gè)注意事項(xiàng)。和之前展示的守護(hù)進(jìn)程函數(shù)一樣,這個(gè)守護(hù)進(jìn)程函數(shù)會(huì)不斷地重復(fù)循環(huán)知道系統(tǒng)終止這個(gè)進(jìn)程為止。為了盡可能地降低清理操作的執(zhí)行負(fù)載,守護(hù)進(jìn)程會(huì)以每分鐘一次的頻率清理那些每分鐘更新一次或者每分鐘更新多次的計(jì)數(shù)器,而對(duì)于那些更新頻率低于每分鐘一次的計(jì)數(shù)器,守護(hù)進(jìn)程則會(huì)根據(jù)計(jì)數(shù)器自身的更新頻率來決定對(duì)他們進(jìn)行清理的頻率。比如說,對(duì)于每秒更新一次或者每5秒更新一次的計(jì)數(shù)器,守護(hù)進(jìn)程將以每分鐘一次的頻率清理這些計(jì)數(shù)器;而對(duì)于每5分鐘更新一次的計(jì)數(shù)器,守護(hù)進(jìn)程將以每5分鐘一次的頻率清理這些計(jì)數(shù)器。

清理程序通過對(duì)記錄已知計(jì)數(shù)器的有序集合執(zhí)行zrange命令來一個(gè)接一個(gè)的遍歷所有已知的計(jì)數(shù)器。在對(duì)計(jì)數(shù)器執(zhí)行清理操作的時(shí)候,程序會(huì)取出計(jì)數(shù)器記錄的所有計(jì)數(shù)樣本的開始時(shí)間,并移除那些開始時(shí)間位于指定截止時(shí)間之前的樣本,清理之后的計(jì)數(shù)器最多只會(huì)保留最新的120個(gè)樣本。如果一個(gè)計(jì)數(shù)器在執(zhí)行清理操作之后不再包含任何樣本,那么程序?qū)挠涗浺阎?jì)數(shù)器的有序集合里面移除這個(gè)計(jì)數(shù)器的引用信息。以上給出的描述大致地說明了計(jì)數(shù)器清理函數(shù)的運(yùn)作原理,至于程序的一些邊界情況最好還是通過代碼來說明,要了解該函數(shù)的所有細(xì)節(jié),請看下面代碼:

import bisect
import time

import redis

QUIT=True
SAMPLE_COUNT=1

def clean_counters(conn):
    pipe=conn.pipeline(True)
    #為了平等的處理更新頻率各不相同的多個(gè)計(jì)數(shù)器,程序需要記錄清理操作執(zhí)行的次數(shù)
    passes=0
    #持續(xù)地對(duì)計(jì)數(shù)器進(jìn)行清理,知道退出為止
    while not QUIT:
        #記錄清理操作開始執(zhí)行的時(shí)間,這個(gè)值將被用于計(jì)算清理操作的執(zhí)行時(shí)長
        start=time.time()
        index=0
        #漸進(jìn)的遍歷所有已知計(jì)數(shù)器
        while index

正如之前所說,clean_counters()函數(shù)會(huì)一個(gè)接一個(gè)地遍歷有序集合里面記錄的計(jì)數(shù)器,查找需要進(jìn)行清理的計(jì)數(shù)器。程序在每次遍歷時(shí)都會(huì)對(duì)計(jì)數(shù)器進(jìn)行檢查,確保只清理應(yīng)該清理的計(jì)數(shù)器。當(dāng)程序嘗試清理一個(gè)計(jì)數(shù)器的時(shí)候,它會(huì)取出計(jì)數(shù)器記錄的所有數(shù)據(jù)樣本,并判斷哪些樣本是需要被刪除的。如果程序在對(duì)一個(gè)計(jì)數(shù)器執(zhí)行清理操作之后,然后這個(gè)計(jì)數(shù)器已經(jīng)不再包含任何數(shù)據(jù),那么程序會(huì)檢查這個(gè)計(jì)數(shù)器是否已經(jīng)被清空,并在確認(rèn)了它已經(jīng)被清空之后,將它從記錄已知計(jì)數(shù)器的有序集合中移除。最后,在遍歷完所有計(jì)數(shù)器之后,程序會(huì)計(jì)算此次遍歷耗費(fèi)的時(shí)長,如果為了執(zhí)行清理操作而預(yù)留的一分鐘時(shí)間沒有完全耗盡,那么程序?qū)⑿菝咧钡竭@一分鐘過去為止,然后繼續(xù)進(jìn)行下次遍歷。

現(xiàn)在我們已經(jīng)知道怎樣記錄、獲取和清理計(jì)數(shù)器數(shù)據(jù)了,接下來要做的視乎就是構(gòu)建一個(gè)界面來展示這些數(shù)據(jù)了。遺憾的是,這些內(nèi)容設(shè)計(jì)到前端,并不在本內(nèi)容介紹范圍內(nèi),如果感興趣,可以試試jqplot、Highcharts、dygraphs已經(jīng)D3,這幾個(gè)JavaScript繪圖庫無論是個(gè)人使用還是專業(yè)使用都非常合適。

在和一個(gè)真實(shí)的網(wǎng)站打交道的時(shí)候,知道頁面每天的點(diǎn)擊可以幫助我們判斷是否需要對(duì)頁面進(jìn)行緩存。但是,如果被頻繁訪問的頁面只需要花費(fèi)2毫秒來進(jìn)行渲染,而其他流量只要十分之一的頁面卻需要花費(fèi)2秒來進(jìn)行渲染,那么在緩存被頻繁訪問的頁面之前,我們可以先將注意力放到優(yōu)化渲染速度較慢的頁面上去。在接下來的一節(jié)中,我們將不再使用計(jì)數(shù)器來記錄頁面的點(diǎn)擊量,而是通過記錄聚合統(tǒng)計(jì)數(shù)據(jù)來更準(zhǔn)確地判斷哪些地方需要進(jìn)行優(yōu)化。

使用Redis存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù)

首先需要說明的一點(diǎn)是,為了統(tǒng)計(jì)數(shù)據(jù)存儲(chǔ)到Redis里面,筆者曾經(jīng)實(shí)現(xiàn)過5種不同的方法,本節(jié)介紹的方法綜合了這5種方法里面的眾多優(yōu)點(diǎn),具有非常大的靈活性和可擴(kuò)展性。

本節(jié)所展示的存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù)的方法,在工作方式上與上節(jié)介紹的log_common()函數(shù)類似:這兩者存儲(chǔ)的數(shù)據(jù)記錄的都是當(dāng)前這一小時(shí)以及前一小時(shí)所產(chǎn)生的事情。另外,本節(jié)介紹的方法會(huì)記錄最小值、最大值、平均值、標(biāo)準(zhǔn)差、樣本數(shù)量以及所有被記錄值之和等眾多信息,以便不時(shí)之需。

對(duì)于一種給定的上下文和類型,程序?qū)⑹褂靡粋€(gè)有序集合來記錄這個(gè)上下文以及這個(gè)類型的最小值、最大值、樣本數(shù)量、值的和、值的平方之和等信息,并通過這些信息來計(jì)算平均值以及標(biāo)準(zhǔn)差。程序?qū)⒅荡鎯?chǔ)在有序集合里面并非是為了按照分值對(duì)成員進(jìn)行排序、而是為了對(duì)存儲(chǔ)著統(tǒng)計(jì)信息的有序集合和其他有序集合進(jìn)行并集計(jì)算,并通過min和max這兩個(gè)聚合函數(shù)來篩選相交的元素。下表展示了一個(gè)存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù)的有序集合實(shí)例,它記錄了ProfilePage(個(gè)人簡歷)上下文的AccessTime(訪問時(shí)間)統(tǒng)計(jì)數(shù)據(jù)。

表名:starts:ProfilePage:AccessTime 類型:zset
min 0.035
max 4.958
sunsq 194.268
sum 258.973
count 2323

既然我們已經(jīng)知道了程序要存儲(chǔ)的是什么類型的數(shù)據(jù),那么接下來要考慮的就是如何將這些數(shù)據(jù)寫到數(shù)據(jù)結(jié)構(gòu)里面了。

下面代碼展示了負(fù)責(zé)更新統(tǒng)計(jì)數(shù)據(jù)的代碼。和之前介紹過的常見日志程序一樣,統(tǒng)計(jì)程序在寫入數(shù)據(jù)之前會(huì)進(jìn)行檢查,確保被記錄的是當(dāng)前這小時(shí)的統(tǒng)計(jì)數(shù)據(jù),并將不屬于當(dāng)前這一小時(shí)的舊數(shù)據(jù)進(jìn)行歸檔。在此之后,程序會(huì)構(gòu)建兩個(gè)臨時(shí)有序集合,其中一個(gè)用于保存最小值,而另一個(gè)則用于保存最大值然后使用zunionstore命令以及它的兩個(gè)聚合函數(shù)min和max,分別計(jì)算兩個(gè)臨時(shí)有序集合與記錄當(dāng)前統(tǒng)計(jì)數(shù)據(jù)的有序集合之前的并集結(jié)果。通過使用zunionstore命令,程序可以快速的更新統(tǒng)計(jì)數(shù)據(jù),而無須使用watch去監(jiān)視可能會(huì)頻繁進(jìn)行更新的存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù)的鍵,因?yàn)檫@個(gè)鍵可能會(huì)頻繁地進(jìn)行更新。程序在并集計(jì)算完畢之后就會(huì)刪除那些臨時(shí)有序集合,并使用zincrby命令對(duì)統(tǒng)計(jì)數(shù)據(jù)有序集合里面的count、sum、sumsq這3個(gè)成員進(jìn)更新。

import datetime
import time
import uuid

import redis


def update_status(conn,context,type,value,timeout=5):
    #負(fù)責(zé)存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù)的鍵
    destination="stats:%s:%s"%(context,type)
    #像common_log()函數(shù)一樣,處理當(dāng)前這一個(gè)小時(shí)的數(shù)據(jù)和上一個(gè)小時(shí)的數(shù)據(jù)
    start_key=destination+":start"
    pipe=conn.pipeline(True)
    end=time.time()+timeout
    while time.time()<=end:
        try:
            pipe.watch(start_key)
            now=datetime.utcnow().timetuple()
            # 像common_log()函數(shù)一樣,處理當(dāng)前這一個(gè)小時(shí)的數(shù)據(jù)和上一個(gè)小時(shí)的數(shù)據(jù)
            hour_start=datetime(*now[:4]).isoformat()

            existing=pipe.get(start_key)
            pipe.multi()
            if existing and existing

update__status()函數(shù)的前半部分代碼基本上可以忽略不看,因?yàn)樗鼈兒蜕瞎?jié)介紹的log_common()函數(shù)用來輪換數(shù)據(jù)的代碼幾乎一模一樣,而update__status()函數(shù)的后半部分則做了我們前面描述過的事情:程序首先創(chuàng)建兩個(gè)臨時(shí)有序集合,然后使用適當(dāng)?shù)木酆虾瘮?shù),對(duì)存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù)的有序集合以及兩個(gè)臨時(shí)有序集合分別執(zhí)行zunionstore命令;最后,刪除臨時(shí)有序集合,并將并集計(jì)算所得的統(tǒng)計(jì)數(shù)據(jù)更新到存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù)的有序集合里面。update__status()函數(shù)展示了將統(tǒng)計(jì)數(shù)據(jù)存儲(chǔ)到有序集合里面的方法,但如果想要獲取統(tǒng)計(jì)數(shù)據(jù)的話,又應(yīng)該怎么做呢?

下面代碼展示了程序取出統(tǒng)計(jì)數(shù)據(jù)的方法:程序會(huì)從記錄統(tǒng)計(jì)數(shù)據(jù)的有序集合里面取出所有被存儲(chǔ)的值,并計(jì)算出平均值和標(biāo)準(zhǔn)差。其中,平均值可以通過值的和(sum)除以取樣數(shù)量(count)來計(jì)算得出;而標(biāo)準(zhǔn)差的計(jì)算則更復(fù)雜一些,程序需要多做一些工作才能根據(jù)已有的統(tǒng)計(jì)信息計(jì)算出標(biāo)注差,但是為了簡潔起見,這里不會(huì)解釋計(jì)算標(biāo)準(zhǔn)差時(shí)用到的數(shù)學(xué)知識(shí)。

import datetime
import time
import uuid

import redis

def get_stats(conn,context,type):
    #程序?qū)倪@個(gè)鍵里面取出統(tǒng)計(jì)數(shù)據(jù)
    key="stats:%s:%s"%(context,type)
    #獲取基本的統(tǒng)計(jì)數(shù)據(jù),并將它們都放到一個(gè)字典里面
    data=dict(conn.zrange(key,0,-1,withscores=True))
    #計(jì)算平均值
    data["average"]=data["sum"]/data["count"]
    #計(jì)算標(biāo)準(zhǔn)差的第一個(gè)步驟
    numerator=data["sumsq"]-data["sun"]**2/data["count"]
    #完成標(biāo)準(zhǔn)差的計(jì)算工作
    data["stddev"]=(numerator/data["count"]-1 or 1)** .5
    return data

除了用于計(jì)算標(biāo)準(zhǔn)差的代碼之外,get_stats()函數(shù)并沒有什么難懂的地方,如果讀者愿意花些時(shí)間在網(wǎng)上了解什么叫標(biāo)準(zhǔn)差的話,那么讀懂這些標(biāo)準(zhǔn)差的代碼應(yīng)該也不是什么難事。盡管有了那么多統(tǒng)計(jì)數(shù)據(jù),但我們可能還不太清楚自己應(yīng)該觀察哪些數(shù)據(jù),而接下來的一節(jié)就會(huì)解答這個(gè)問題。

簡化統(tǒng)計(jì)數(shù)據(jù)的記錄與發(fā)現(xiàn)

在將統(tǒng)計(jì)數(shù)據(jù)存儲(chǔ)到Redis里面之后,接下來我們該做些什么呢?說的更詳細(xì)一點(diǎn),在知道了訪問每個(gè)頁面所需的時(shí)間之后,我們要怎樣才能找到那些生成速度較慢的網(wǎng)頁?或者說,當(dāng)某個(gè)頁面的生成速度變得比以往要慢的時(shí)候,我們?nèi)绾尾拍苤み@一情況?簡單的說,為了發(fā)現(xiàn)以上提到的這些情況,我們需要存儲(chǔ)更多信息,而具體的方法將這一節(jié)里面介紹。

要記錄頁面的訪問時(shí)長,程序就必須在頁面被訪問時(shí)進(jìn)行計(jì)時(shí)。為了做到這一點(diǎn),我們可以在各個(gè)不同的頁面設(shè)置計(jì)時(shí)器,并添加代碼來記錄計(jì)時(shí)的結(jié)果,但最好的辦法是直接實(shí)現(xiàn)一個(gè)能夠進(jìn)行計(jì)時(shí)并將計(jì)時(shí)結(jié)果存儲(chǔ)起來的東西,讓它將平均訪問速度最慢的頁面都記錄到一個(gè)有序集合里面,并向我們報(bào)告哪些頁面的載入時(shí)間變得比以前更長了。

為了計(jì)算和記錄訪問時(shí)長,我們會(huì)編寫一個(gè)Python上下文管理器,并使用這個(gè)上下文管理器來包裹那些需要計(jì)算并記錄訪問時(shí)長的代碼。

在Python里面,一個(gè)上下文管理器就是一個(gè)專門定義的函數(shù)或者類,這個(gè)函數(shù)或者類的不同部分可以在一段代碼執(zhí)行之前以及執(zhí)行之后分別執(zhí)行。上下文管理器使得用戶可以很容易地實(shí)現(xiàn)類似【自動(dòng)關(guān)閉已打開的文件】這樣的功能。

下面代碼展示了用于計(jì)算和記錄訪問時(shí)長的上下文管理器:程序首先會(huì)取得當(dāng)前時(shí)間,接著執(zhí)行被包裹的代碼,然后計(jì)算這些代碼的執(zhí)行時(shí)長,并將結(jié)果記錄到
Redis里面;除此之外,程序還會(huì)對(duì)記錄當(dāng)前上下文最大訪問的時(shí)間的有序集合進(jìn)行更新。

import contextlib
import time

#將這個(gè)Python生成器用作上下文管理器
@contextlib.contextmanager
def access__time(conn,context):
    #記錄代碼塊執(zhí)行前的時(shí)間
    start=time.time()
    #運(yùn)行被包裹的代碼塊
    yield

    #計(jì)算代碼塊的執(zhí)行時(shí)長
    data=time.time()-start
    #更新這一上下文的統(tǒng)計(jì)數(shù)據(jù)
    stats=update_stats(conn,context,"AccessTime",data)
    #計(jì)算頁面的平局訪問時(shí)長
    average=stats[1]/stats[0]

    pipe=conn.pipeline(True)
    #將頁面的平均訪問時(shí)長添加到記錄最長訪問時(shí)間的有序集合里面
    pipe.zadd("slowest:AccessTime",context,average)
    #AccessTime有序集合只會(huì)保留最慢的100條記錄
    pipe.zremrangebyrank("slowessTime",0,-101)
    pipe.execute()

因?yàn)閍ccess__time()上下文管理器里面有一些沒辦法只用三言兩語來解釋的概念,所以我們最好還是直接通過使用這個(gè)管理器來了解它是如何運(yùn)作的。接下來的這段代碼展示了使用access__time()上下文管理器記錄web頁面訪問時(shí)長的方法,負(fù)責(zé)處理被記錄頁面的是一個(gè)回調(diào)函數(shù):

#這個(gè)視圖接收一個(gè)Redis連接以及一個(gè)生成內(nèi)容的回調(diào)函數(shù)作為參數(shù)
def process_view(conn,callback):
    #計(jì)算并記錄訪問時(shí)長的上下文管理器就是這一包裹代碼塊的
    with access_time(conn,request.path):
        #當(dāng)上下文管理器中的yield語句被執(zhí)行時(shí),這個(gè)語句就會(huì)被執(zhí)行
        return callback()

如果還不理解,看下面簡單的實(shí)例:

import contextlib


@contextlib.contextmanager
def mark():
    print("1")
    yield
    print(2)

def test(callback):
    with mark():
        return callback()

def xxx():
    print("xxx")

if __name__ == "__main__":
    test(xxx)

運(yùn)行結(jié)果:

1
xxx
2

在看過這個(gè)例子之后,即使讀者沒有學(xué)過上下文管理器的創(chuàng)建方法,但是至少也已經(jīng)知道該如何去使用它了。這個(gè)例子使用了訪問時(shí)間上下文管理器來計(jì)算生成一個(gè)頁面需要花費(fèi)時(shí)多長時(shí)間,此外,同樣的上下文管理器還可以用于計(jì)算數(shù)據(jù)庫查詢花費(fèi)的時(shí)長,或者用來計(jì)算渲染一個(gè)模板所需的時(shí)長。作為練習(xí),你能否構(gòu)思一些其他種類的上下文管理器,并使用它們來記錄有用的統(tǒng)計(jì)信息呢?另外,你能否讓程序在頁面的訪問時(shí)長比平均情況要高出兩個(gè)標(biāo)注差或以上時(shí),在recent_log()函數(shù)里面記錄這一情況呢?

對(duì)現(xiàn)實(shí)世界中的統(tǒng)計(jì)數(shù)據(jù)進(jìn)行收集和計(jì)數(shù)

盡管本書已經(jīng)花費(fèi)了好幾頁篇幅來講述該如何收集生產(chǎn)系統(tǒng)運(yùn)作時(shí)產(chǎn)生的相當(dāng)重要的統(tǒng)計(jì)信息,但是別忘了已經(jīng)有很多現(xiàn)成的軟件包可以用于收集并繪制計(jì)數(shù)器以及統(tǒng)計(jì)數(shù)據(jù),我個(gè)人最喜歡的是Graphite,在時(shí)間嘗試構(gòu)建自己的數(shù)據(jù)繪圖庫之前,不妨先試試這個(gè)。

在學(xué)會(huì)了如何將應(yīng)用程序相關(guān)的各種重要信息存儲(chǔ)到Redis之后,在接下來一節(jié)中,我們將了解更多與訪客有關(guān)的信息,這些信息可以幫助我們處理其他問題。

上一篇文章:Python--Redis實(shí)戰(zhàn):第五章:使用Redis構(gòu)建支持程序:第1節(jié):使用Redis來記錄日志
下一篇文章: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/42733.html

相關(guān)文章

  • Python--Redis實(shí)戰(zhàn)五章使用Redis構(gòu)建支持程序3節(jié):查找IP所屬城市以及國家

    摘要:下面清單展示了地址所屬地查找程序的具體實(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ù)和日志存儲(chǔ)到Redis里面,我們...

    fengxiuping 評(píng)論0 收藏0
  • Python--Redis實(shí)戰(zhàn)五章使用Redis構(gòu)建支持程序1節(jié)使用Redis來記錄日志

    摘要:包括在內(nèi)的很多軟件都使用這種方法來記錄日志。在這一節(jié)中,我們將介紹如何使用來存儲(chǔ)于時(shí)間緊密相關(guān)的日志,從而在功能上替代那些需要在短期內(nèi)被存儲(chǔ)的消息。 上一篇文章: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ù)的過程中...

    mdluo 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<