《Redis入门指南》一5.3 Python与Redis

5.3 Python与Redis

Redis入门指南
Redis官方推荐的Python客户端是redis-py1。

5.3.1 安装

推荐使用pip install redis安装最新版本的redis-py,也可以使用easy_install:easy_install redis。

5.3.2 使用方法

首先需要引入redis-py:

import redis

下面的代码将创建一个默认连接到地址127.0.0.1,端口6379的Redis连接:

r = redis.StrictRedis()

也可以显式地指定需要连接的地址:

r = redis.StrictRedis(host='127.0.0.1', port=6379, db=0)

使用起来很容易,这里以SET和GET命令作为示例:

r.set('foo', 'bar') # True
r.get('foo')     # 'bar'

5.3.3 简便用法

1.HMSET/HGETALL
HMSET支持将字典作为参数存储,同时HGETALL的返回值也是一个字典,搭配使用十分方便:

r.hmset('dict', {'name': 'Bob'})
people = r.hgetall('dict')

print people # {'name': 'Bob'}

2.事务和管道
redis-py的事务使用方式如下:

pipe = r.pipeline()
pipe.set('foo', 'bar')
pipe.get('foo')
result = pipe.execute()

print result # [True, 'bar']

管道的使用方式和事务相同,只不过需要在创建时加上参数transaction=False:

pipe = r.pipeline(transaction=False)

事务和管道还支持链式调用:

result = r.pipeline().set('foo', 'bar').get('foo').execute()
# [True, 'bar']

5.3.4 实践:在线的好友

一般的社交网站上都可以看到用户在线的好友列表,如图 5-4 所示。在 Redis 中可以很容易地实现这个功能。

在线好友其实就是全站在线用户的集合和某个用户所有好友的集合取交集的结果。如果现在我们的网站就是使用集合类型键来存储用户的好友 ID 的,那么只需要一个存储在线用户列表的集合即可。如何判定一个用户是否在线呢?通常的方法是每当用户发送HTTP 请求时都记录下请求发生的时间,所有指定时间内发送过请求的用户就算作在线用户。这段时间根据场景不同取值也不同,以10分钟为例:某个用户发送了一个HTTP请求,9分钟后系统仍然认为他是在线的,但到了第11分钟就不算作他在线了。

在 Redis 中我们可以每隔 10 分钟就使用一个键来存储该 10分钟内发送过请求的用户ID列表。如12点20分到12点29分的用户ID存储在active.users:2中,12点30分到12点39分的用户ID存储在active.users:3中,以此类推(注意每次调用SADD命令增加用户ID时需要同时设置键的生存时间在50分钟内以防止命名冲突)。这样需要获得当前在线用户只需要读取当前分钟数对应的键即可。不过这种方案会造成较大的误差,比如某个用户在29分访问了一个页面,他的ID被记录在active.users:2键中,而在30分时系统会读取active.users:3键来获取在线用户列表,即该用户的在线状态只持续了1分钟而不是预想的10分钟。

这时就需要粒度更小的记录方案来解决这个问题。我们可以将原先每 10 分钟记录一个键改为每 1 分钟记录一个键,即在 12点29分访问的用户的ID将会被记录在active.users:29中。而判断一个用户是否在最近10分钟在线只需要判断其在最近的10个集合键中是否出现过至少一次即可,这一过程可以通过SUNION命令实现。

下面介绍使用Python来实现这一过程。我们这里使用了web.py框架,web.py是一个易于使用的Python网站开发框架,可以通过sudo pip install web.py来安装它。

代码如下:

# -- coding: utf-8 --
import web
import time
import redis

r = redis.StrictRedis()

""" 配置路由规则
'/':    模拟用户的访问
'/online': 查看在线用户
"""
urls = (
   '/', 'visit',
   '/online', 'online'
)
app = web.application(urls, globals())

""" 返回当前时间对应的键名
如28分对应的键名是active.users:28
"""
def time_to_key(current_time):
   return 'active.users:' + time.strftime('%M', time.localtime(current_time))

""" 返回最近10分钟的键名
结果是列表类型
"""
def keys_in_last_10_minutes():
   now = time.time()
   result = []
   for i in range(10):
     result.append(time_to_key(now - i * 60))
   return result

class visit:
   """ 模拟用户访问
   将用户的User agent作为用户的ID加入到当前时间对应的键中
   """
   def GET(self):
     user_id = web.ctx.env['HTTP_USER_AGENT']
     current_key = time_to_key(time.time())
     pipe = r.pipeline()
     pipe.sadd(current_key, user_id)
     # 设置键的生存时间为10分钟
     pipe.expire(current_key, 10 * 60)
     pipe.execute()

     return 'User:\t' + user_id + '\r\nKey:\t' + current_key

class online:
   """ 查看当前在线的用户列表
   """
   def GET(self):
     online_users = r.sunion(keys_in_last_10_minutes())
     result = ''
     for user in online_users:
       result += 'User agent:' + user + '\r\n'
     return result

if name == "__main__":
   app.run()

在代码中我们建立了两个页面。首先我们打开http://127.0.0.1:8080,该页面对应visit类,每次访问该页面都会将用户的浏览器User agent存储在记录当前分钟在线用户的键中,并将User agent和键名显示出来,如图5-5所示。

从键名可知该次访问是在某时26分钟的时候发生的。然后使用另一个浏览器打开该页面,如图5-6所示。

该次访问发生在29分钟。最后我们在37分钟时访问http://127.0.0.1:8080/online来查看当前在线用户列表,如图5-7所示。

结果与预期一样,在线列表中只有在29分钟访问的用户。

另一种方法:有序集合
有时网站本来就要记录全站用户的最后访问时间(如图5-8所示),这时就可以直接利用此数据获得最后一次访问发生在10分钟内的用户列表(即在线用户)。

我们使用一个有序集合来记录用户的最后访问时间,元素值为用户的ID,分数为最后一次访问的UNIX时间。要获得最近10分钟访问过的用户列表可以使用ZRANGEBYSCORE命令:

ten_minutes_ago = time.time() - 10 * 60
online_users = r.zrangebyscore('last.seen', ten_minutes_ago, '+inf')

那么如何获取在线的好友列表呢(与上一个例子一样,此时依然使用集合类型存储用户的好友列表)?最直接的方法就是将上面存储在线用户列表的online_users变量存入 Redis 的一个集合类型的键中然后和用户的好友列表取交集。然而这种方法需要在服务端和客户端之间传输数据,如果在线用户多的话会有较大的网络开销,而且这种方法也不能通过Redis的事务功能实现原子操作。为了解决这些问题,我们希望实现一个方法将ZRANGEBYSCORE命令的结果直接存入一个新键中而不返回到客户端。思路如下:有序集合只有ZINTERSTORE和ZUNIONSTORE两个命令支持直接将运算结果存入键中,然而这两个命令都不能实现我们要的操作。所以只能换种思路:既然没办法直接把有序集合中某一分数段的元素存入新键中,那何不干脆复制一个新建,并使用ZREMRANGEBYSCORE命令将我们不需要的分数段的元素删除?

有了这一思路后下面的实现方法就很简单了,步骤如下。

(1)复制一个last.seen键的副本temp.last.seen,方法为ZUNIONSTORE temp.last.seen 1 last.seen。在这里我们巧妙地借助了ZUNIONSTORE命令实现了对有序集合类型键的复制过程,即参加求并集操作的元素只有一个,结果自然就是它本身。

(2)将不在线的用户(即10分钟以前的用户)删除。方法为ZREMRANGEBYSCOREtemp.last.seen 0 10分钟前的UNIX时间。

(3)现在temp.last.seen键中存储的就是当前的在线用户了。我们将其和用户的好友列表做交集:ZINTERSTORE online.friends 2 temp.last.seen user:42:friends。这里我们以ID为42的用户举例,user:42:friends是存储其好友的集合类型键2。

(4)使用ZRANGE命令获取online.friends键的值。

(5)收尾工作,删除temp.last.seen和online.friends键。因为temp.last.seen键可以被所有用户共用,所以可以根据情况将其缓存一段时间,在下次需要生成时先判断是否有该键,如果有则直接使用。

以上5步需要使用事务或脚本实现以保证每个步骤的原子性。

有的时候我们会使用有序集合键来存储用户的好友列表以记录成为好友的时间,此时第3步依然奏效。

虽然以上的步骤有些复杂,但是实现起来并不难,有兴趣的读者可以自己完成。

时间: 2024-09-14 18:15:06

《Redis入门指南》一5.3 Python与Redis的相关文章

《Redis入门指南(第2版)》一导读

前 言 Redis入门指南(第2版)Redis如今已经成为Web开发社区中最火热的内存数据库之一,而它的诞生距现在不过才4年.随着Web 2.0的蓬勃发展,网站数据快速增长,对高性能读写的需求也越来越多,再加上半结构化的数据比重逐渐变大,人们对早已被铺天盖地地运用着的关系数据库能否适应现今的存储需求产生了疑问.而Redis的迅猛发展,为这个领域注入了全新的思维. Redis凭借其全面的功能得到越来越多的公司的青睐,从初创企业到新浪微博这样拥有着几百台Redis服务器的大公司,都能看到Redis的

《Redis入门指南》一第5章 实践

第5章 实践 Redis入门指南 小白把宋老师向自己讲解的知识总结成了一篇帖子发在了学校的网站上,引起了强烈的反响.很多同学希望宋老师能够再写一些关于Redis实践方面的教程,宋老师爽快地答应了. 在此之前我们进行的操作都是通过Redis的命令行客户端redis-cli进行的,并没有介绍实际编程时如何操作Redis.本章将会通过4个实例分别介绍Redis的PHP.Python.Ruby和 Node.js 客户端的使用方法,即使你不了解其中的某些语言,粗浅的阅读一下也能收获很多实践方面的技巧.

《Redis入门指南》一导读

前 言 Redis入门指南Redis如今已经成为Web开发社区中最火热的内存数据库之一,而它的诞生距现在不过才4年.随着Web 2.0的蓬勃发展,网站数据快速增长,对高性能读写的需求也越来越多,再加上半结构化的数据比重逐渐变大,人们对早已被铺天盖地地运用着的关系数据库能否适应现今的存储需求产生了疑问.而Redis的迅猛发展,为这个领域注入了全新的思维. Redis凭借其全面的功能得到越来越多的公司的青睐,从初创企业到新浪微博这样拥有着几百台Redis服务器的大公司,都能看到Redis的身影.Re

《Redis入门指南》一4.3 排序

4.3 排序 Redis入门指南 午后,宋老师正在批改学生们提交的程序,再过几天就会迎来第一次计算机全市联考.他在每个学生的程序代码末尾都用注释详细地做了批注--严谨的治学态度让他备受学生们的爱戴. 一个电话打来."小白的?"宋老师拿出手机,"博客最近怎么样了?"未及小白开口,他就抢先问道. 特别好!现在平均每天都有50多人访问我的博客.不过昨天我收到一个访客的邮件,他向我反映了一个问题:查看一个标签下的文章列表时文章不是按照时间顺序排列的,找起来很麻烦.我看了一下

《Redis入门指南》一5.1 PHP与Redis

5.1 PHP与Redis Redis入门指南 Redis官方推荐的PHP客户端是Predis1和phpredis2.前者是完全使用PHP代码实现的原生客户端,而后者则是使用C语言编写的PHP扩展.在功能上两者区别并不大,就性能而言后者会更胜一筹.考虑到很多主机并未提供安装PHP扩展的权限,本节会以Predis为示例介绍如何在PHP中使用Redis. 虽然Predis的性能逊于phpredis,但是除非执行大量Redis命令,否则很难区分二者的性能.而且实际应用中执行Redis命令的开销更多在网

《Redis入门指南》一4.1 事务

4.1 事务 Redis入门指南 傍晚时候,忙完了一天的教学工作,宋老师坐在办公室的电脑前开始为明天的课程做准备.尽管有着近5年的教学经验,可是宋老师依然习惯在备课时写一份简单的教案.正在网上查找资料时,在浏览器的历史记录里他突然看到了小白的博客.心想:不知道他的博客怎么样了? 于是宋老师点进了小白的博客,页面刚载入完他就被博客最下面的一行大得夸张的文字吸引了:"Powered by Redis".宋老师笑了笑,接着就看到了小白博客中最新的一篇文章: 标题: 使用Redis来存储微博中

《Redis入门指南》一4.4 消息通知

4.4 消息通知 Redis入门指南 凭着小白的用心经营,博客的访问量逐渐增多,甚至有了小白自己的粉丝.这不,小白刚收到一封来自粉丝的邮件,在邮件中那个粉丝强烈建议小白给博客加入邮件订阅功能,这样当小白发布新文章后订阅小白博客的用户就可以收到通知邮件了.在信的末尾,那个粉丝还着重强调了一下:"这个功能对不习惯使用RSS的用户很重要,希望能够加上!" 看过信后,小白心想:"是个好建议!不过话说回来,似乎他还没发现其实我的博客连RSS功能都没有." 邮件订阅功能太好实现

《Redis入门指南》一5.4 Node.js与Redis

5.4 Node.js与Redis Redis入门指南 Redis官方推荐的Node.js客户端是node_redis1. 5.4.1 安装 使用npm install redis命令安装最新版本的node_redis,目前版本是0.8.2. 5.4.2 使用方法 首先加载node_redis模块: var redis = require('redis'); 下面的代码将创建一个默认连接到地址127.0.0.1,端口6379的Redis连接: var client = redis.createC

《Redis入门指南》一4.5 管道

4.5 管道 Redis入门指南 客户端和Redis使用TCP协议连接.不论是客户端向Redis发送命令还是Redis向客户端返回命令的执行结果,都需要经过网络传输,这两个部分的总耗时称为往返时延.根据网络性能不同,往返时延也不同,大致来说到本地回环地址(loop back address)的往返时延在数量级上相当于Redis处理一条简单命令(如LPUSH list 1 2 3)的时间.如果执行较多的命令,每个命令的往返时延累加起来对性能还是有一定影响的. 在执行多个命令时每条命令都需要等待上一