对计数器进行更新

为了对计数器进行更新,我们需要存储实际的计数器信息,对于每个计数器以及每种精度,如网站点击量计数器/5秒,我们将使用一个散列来存储网站在每个5秒时间片之内获得的点击量,其中,散列的每个键都是某个时间片的开始时间,而键对应的值则存储了网站在该时间片之内获得的点击量。下表展示了一个点击量计数器存储的其中一部分数据,这个计数器以每5秒为一个时间片记录着网站的点击量:

键名:count:5:hits 类型:hash
1336376410 45
1336376405 28
1336376395 17(本行数据表示:网站在2012年5月7日早晨7:39:55到7:40:00总共获得了17次点击)
1336376400 29

为了能够清理计数器包含的旧数据,我们需要在使用计数器的同时,对被使用的计数器进行记录。为了做到这一点,我们需要一个有序序列,这个序列不能包含任何重复元素,并且能够让我们一个接一个地遍历序列中包含的所有元素。虽然同时使用列表和集合可以实现这种序列,但同时使用两种数据结构需要编写更多代码,并且增加客户端和Redis之间的通信往返次数。实际上,实现有序序列更好的方法时使用有序集合,有序集合的各个成员分别由计数器的精度以及计数器的名字组成,而所有成员的分值都是0.因为所有成员的分值都被设置为0,所以Redis在尝试按分值对有序集合进行排序的时候,就会发现这一点,并改为使用成员名进行排序,这使得一组给定的成员总是具有固定的排列顺序,从而可以方便地对这些成员进行顺序性的扫描。下表展示了一个有序集合,这个有序集合记录了正在使用的计数器。

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

既然我们已经知道应该使用什么结构来记录并表示计数器了,现在是时候来考虑一下如何使用和更新这些计数器了。

下面代码展示了程序更新计数器的方法,对于每种时间片精度,程序都会将计数器 的精度和名字作为引用信息添加都记录已有计数器的有序集合里面,并增加散列计数器在指定时间片内的计数值。

#以秒为单位的计数器精度,分别为1秒/5秒/1分钟/5分钟/1小时/5小时/1天
#用户可以按需调整这些精度
import time

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

def update_counter(conn,name,count=1,now=None):
    #通过获取当前时间来判断应该对哪个时间片执行自增操作。
    now=now or time.time()
    #为了保证之后的清理工作可以正确的执行,这里需要创建一个事务性流水线
    pipe=conn.pipeline()
    #为我们记录的每种精度都创建一个计数器
    for prec in PRECISION:
        #取得当前时间片的开始时间
        pnow=int(now/prec)*prec
        #创建负责存储计数信息的散列
        hash='%s:%s'%(prec,name)
        # 将计数器的引用信息添加到有序集合里面,并将其分值设为0,以便在之后执行清理操作
        pipe.zadd('known:',hash,0)
        #对给定名字和精度的计数器进行更新
        pipe.hincrby('count:'+hash,pnow,count)
    pipe.execute()

更新计数器信息的过程并不复杂,程序只需要为每种时间片精度执行zadd命令和hincrby命令就可以了。于此类似,从指定精度和名字的计数器里面获取计数数据也是一件非常容易地事情。下面代码展示了用于执行这一操作的代码:程序首先使用hgetall命令来获取整个散列,接着将命令返回的时间片和计数器的值从原来的字符串格式转换成数字格式,根据时间对数据进行排序,最后返回排序后的数据:

def get_counter(conn,name,precision):
    #取得存储计数器数据的键的名字
    hash='%s:%s'%(precision,name)
    #从Redis里面取出计数器数据
    data=conn.hgetall('count:'+hash)
    to_return=[]
    #将计数器数据转换成指定的格式
    for key,value in data.iteritems():
        to_return.append((int(key),int(value)))
    #对数据进行排序,把旧的数据样本排在前面
    to_return.sort()
    return to_return

get_counter()函数的工作方式就和之前描述的一样,它获取计数器数据并将其转换成整数,然后根据时间先后对转换后的数据进行排序。

在弄懂了获取计数器存储的数据之后,接下来我们要考虑的是如何防止这些计数器存储过多的数据。

results matching ""

    No results matching ""