Python--Redis实战:第二章:使用Redis构建Web应用:第四节:数据行缓存

上一篇文章:
Python–Redis实战:第二章:使用Redis构建Web应用:第三节:网页缓存

下一篇文章:
Python–Redis实战:第二章:使用Redis构建Web应用:第五节:网页分析

到目前为止,我们已经:

  • 将原本由关系数据库和网页浏览器实现的登录和访客会话转移到了Redis上面实现;
  • 将原本有关系数据库实现的购物车也放到了Redis上面实现;
  • 将所有页面缓存到了Redis里面;

这一系列工作提升了网站的性能,降低了关系数据库的负载并较少了网站的成本。

现在经过分析,我们的网站的商品页面通常只会从数据库里面载入一两行数据,包括已登录用户的用户信息【这些信息可以通过AJAX动态载入,所以不会对页面缓存造成影响】和商品本身的信息。即使是那些无法被整个缓存起来的页面:比如用户账号页面、记录用户以往购买商品的页面等等,程序也可以通过缓存页面载入时所需的数据库行来减少载入页面所需的时间。

为了展示数据行缓存的作用,我们假设我们的网站为了清空旧库存和吸引客户消费,决定开始新一轮的促销活动:这个活动每天都会推出一些特价商品供用户抢购,所有特价商品的数量都是限定的,卖完即止。在这种情况下,网址是不能对整个促销页面进行缓存的,因为这可能会导致用户看到错误的特价商品的剩余数量,但是每次载入页面都从数据库里面取出特价商品的剩余数量的话,又会给数据库带来巨大的压力,并导致我们需要花费额外的成本来扩展数据库。

为了应对促销互动带来的大量负载,我们需要对数据行进行缓存,具体的做法是:编写一个持续运行的守护进程函数,让这个函数将指定的数据行缓存到Redis里面,并不定期地对这些缓存进行更新。缓存函数会将数据行编码【encode】为JSON字典并存储在Redis的字符串里面,其中,数据列【column】的名字会被映射为JSON字典的键,而数据行的值则会被映射为JSON字典的值。

程序使用了两个有序集合来记录应该在何时对缓存进行更新:

  • 第一个有序集合为调度【schedule】有序集合,它的成员为数据行的行ID,而分值则是一个时间戳,这个时间戳记录了应该在何时将制定的数据行缓存到Redus里面
  • 第二个有序集合为延时【delay】有序集合,它的成员也是数据行的ID,而分值则记录了指定数据行的缓存需要每隔多少秒更新一次。

为了让缓存函数定期的缓存数据行,程序首先需要将行ID和给定的延迟值添加到延迟有序集合里面,然后再将行ID和当前时间的时间戳添加到调度有序集合里面。实际执行缓存操作的函数需要用到数据行的延迟值,如果某个数据行的延迟值不存在,那么程序将取消对这个数据行的调度。如果我们想要移除某个数据行已有的缓存,并且让缓存函数不再缓存那个数据行,那么只需要把那个数据行的延迟值设置为小于或等于0就可以了。

#负责调度缓存和终止缓存的函数
import time

def schedule_row_cache(conn,row_id,delay):
    #先设置数据行的延迟值
    conn.zadd('delay:',row_id,delay)
    #立即对需要缓存的数据行进行调度
    conn.zadd('schedule:',row_id,time.time())

现在我们已经完成了调度部分,那么接下来该如何对数据进行缓存呢?负责缓存数据行的函数会尝试读取调度有序集合的第一个元素以及该元素的分值,如果调度有序集合没有包含任何元素,或者分值存储的时间戳所指定的时间尚未来临,那么函数会先休眠50毫秒,然后再重新进行检查。当缓存函数发现了一个需要理解进行更新的数据行时,缓存函数会检查这个数据行的延迟值:如果数据行的延迟值小于或者等于0,那么缓存函数会从延迟有序集合和调度有序集合里面移除这个数据行ID,并从缓存里面删除这个数据行已有的缓存,然后再重新进行检查;对于延迟值大于0的数据行来说,缓存函数会中数据库里面取出这些行,将他们编码为JSON格式并存储到Redis里面,然后更新这些行的调度时间。

def cahce_rows(conn):
    while not QUIT:
        #尝试获取下一个需要被缓存的数据行以及该行的调度时间戳,命令会返回一个包含0或1个元祖【tuple】的列表
        next=conn.zrange('schedule:',0,0,withscores=True)
        now=time.time()

        if not next or next[0][3]>now:
            #暂时没有行需要被缓存,休眠50毫秒后重试
            time.sleep(.05)
            continue
        row_id=next[0][0]

        #获取下一次调度的延迟水岸
        delay=conn.zscore('delay:',row_id)
        if delay<=0:
            #不必再缓存这个行,将他从缓存中移除
            conn.zrem('delay:',row_id)
            conn.delete('inv:'+row_id)
            continue

        #读取行数据
        row=Inventory.get(row_id)
        #更新调度时间并设置缓存值
        conn.zadd('schedule:',row_id,now+delay)
        conn.set('inv:'+row_id,json.dumps(row.to_dict()))

通过组合使用调度函数和持续运行缓存函数,我们实现了一种重复进行调度的自动缓存机制,并且可以随心所欲地控制缓存行缓存的更新频率:如果数据行记录的是特价促销商品的剩余数量,并且参与促销活动的用户非常多的话,那么我们最好每隔几秒更新一次数据行缓存;另一方面,如果数据并不经常改变,或者商品缺货是可以接受的,那么我们可以每分钟更新一次缓存。

在这一节中,我们学习了如何将数据行缓存到Redis里面,在下面一节中,我们通过只缓存一部分页面来减少时间页面缓存所需的内存数量。

上一篇文章:
Python–Redis实战:第二章:使用Redis构建Web应用:第三节:网页缓存

下一篇文章:
Python–Redis实战:第二章:使用Redis构建Web应用:第五节:网页分析

    原文作者:Mark
    原文地址: https://segmentfault.com/a/1190000016597989
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞