[译]Redis Cookbook(3.1)

用Redis做键值存储

问题

         很多应用程序需要存储临时数据,比如使用说明,配置信息,以及其它一些不太适合用关系型数据库存储的数据。开发者们一般会通过一些手段勉强为这些数据设计出一种表结构,把它们存储在像MySQL这样的关系型数据库里。接下来,我们来看看如何利用Redis内建的数据类型来更快更轻便地存储应用程序的数据。

解决方案

         Redis不只是简单的键值存储引擎,它更是一个数据结构服务器。这意味着它在基本的键值存储功能基础上提供更多的方式来存储和操作应用数据。我们将会利用这些数据结构和操作命令来存储样本数据:用标准的键存储计数器,用哈希来存储对象,用集合来实现朋友圈功能。

讨论

计数器的存储

         先从存储基本数据开始:计数器。假设我们在运营一个社交网络,需要追踪个人主页的访问情况。我们可以在数据库的任何一张跟个人主页相关的表里面增加一个字段。不过随着流量增加,对这个字段的更新会成为问题。我们需要更快的速度来查询和更新数据,于是Redis派上用场了。

         得益于Redis命令的原子性,如果我们存的是一个计数器的键,可以用INCR或DECR命令来增加或减少它的值。如果键的命名空间设计得当,维护应用程序里的计数器会变得很容易。

         Redis没有规定该如何为键命名,不过很多人喜欢用冒号分隔关键字来为键命名,所以这里我们也会这么做。对于页面访问计数器的键,可以定义一个叫visits:pageid:totals的命名空间,ID是635的页面计数器的键看起来是这样的visits.635:totals。如果之前已经有了计数器的值,可以把它们设置给这些键。

SET visits:1:totals 21389 
SET visits:2:totals 1367894 
(...)

        页面每被访问一次,INCR命令会更新一次计数器:

INCR visits:635:totals

        然后可以使用GET命令随意地获取这个计数器:

GET visits:635:totals

        我们可以利用Redis命令的返回值做一些更巧妙的事情,比如你想让页面的访问者看到他正在浏览的页面有多少人访问过,这个要求你要把他自己的访问次数也计算进去。因为INCR会返回计算以后的结果,所以你都不需要再次调用GET就可以拿到最新的值。大致过程如下:

  1.     用户访问页面
  2.     用INCR更新计数器
  3.     截获INCR命令的返回值
  4.     把截获的值展示在页面上

         这样可以保证用户看到的计数器是实时的,他自己的访问也算在内。而这些只需要一个Redis命令就可以完成。

用哈希存储对象

         之前讨论过Redis的数据类型,Redis实现了哈希,很适合用来存储对象数据。接下来的例子,我们会探讨如何使用哈希来存储用户信息。

         我们先为用户数据定义键的命名空间。像之前那样,我们会用冒号分隔关键字的形式来生成有意义的键。在这个例子里,我们用users:alias这样的格式,其中alias是二进制安全的字符串。要存储一个叫John Doe的用户的信息,我们会创建一个叫users:jdoe的哈希。

          假设我么还需要存储用户的一些其它字段,比如他的全名,电子邮件地址,电话号码和对应用的访问次数。我们会用HSET,HGET和HINCRBY来存储这些信息:

redis> hset users:jdoe name "John Doe" 
(integer) 1
redis> hset users:jdoe email "jdoe@test.com" 
(integer) 1
redis> hset users:jdoe phone "+1555313940" 
(integer) 1
redis> hincrby users:jdoe visits 1 
(integer) 1

        建好了,我们可以通过HGET获取单个字段,或者用HGETALL取得整个哈希:

redis> hget users:jdoe email "jdoe@test.com"
redis> hgetall users:jdoe
1) "name"
2) "John Doe"
3) "email"
4) "jdoe@test.com" 5) "phone"
6) "+1555313940" 7) "visits"
8) "1"

         还有一些辅助命令,比如HKEYS会返回哈希所有的键,HVALS返回哈希所有的值。是用HGETALL还是其它的命令来获取Redis数据完全取决于你自己:

redis> hkeys users:jdoe 
1) "name"
2) "email"
3) "phone"
4) "visits"
redis> hvals users:jdoe 
1) "John Doe"
2) "jdoe@test.com"
3) "+1555313940"
4) "1"

         更多关于哈希命令操作用法可以参考Redis官方文档。

用集合存储"朋友圈"数据

         最近流行的Google+有朋友圈的功能,我们将使用Redis的集合来实现一个类似功能,作为学习Redis数据存储方式的完结。集合非常适合用来实现"朋友圈"功能,因为集合代表了一组数据,并且提供了交集和并集操作。

         我们先来为圈子定义命名空间。我们想为每一个用户定义若干个圈子,所以键里面应该包含用户和他们的圈子的信息。作为例子,John Doe家庭圈子的键可能是这样的:circle:john:family。类似的,他的足球圈子的键可能是这样的:circle:john:soccer。

         键的命名没有固定的规则,只要对应用程序来说有意义就可以了。

         现在我们知道该用哪个键来存储相应的集合了,那么就让我们开始创建John Doe的家庭圈子集合和足球圈子集合。集合里的数据可以是任何东西,用户的ID或者对其它数据的引用。我们选择使用后者,因为它们对我们来说更有意义。如果我们要获取John家庭圈子成员列表,并且查看具体的成员信息,可以通过集合操作返回结果,然后查看每个成员的对象哈希。

redis> sadd circle:jdoe:family users:anna 
(integer) 1 
redis> sadd circle:jdoe:family users:richard
(integer) 1 
redis> sadd circle:jdoe:family users:mike
(integer) 1 
redis> sadd circle:jdoe:soccer users:mike
(integer) 1 
redis> sadd circle:jdoe:soccer users:adam
(integer) 1 
redis> sadd circle:jdoe:soccer users:toby
(integer) 1 
redis> sadd circle:jdoe:soccer users:apollo
(integer) 1

         要注意,在上面的例子里,我们要提供成员的实际ID,而不是users:name。这个例子可以正常运行,但如果出于性能考虑,牺牲一点可读性来换取速度和内存空间会是个好主意。

         现在我们有了两个集合,一个叫circle:jdoe:family,有3个值,一个叫circle:jdoe:soccer,有4个值。这些值对我们来说都是有意义的字符串,我们可以用SMEMBERS命令获取指定成员的信息:

redis> smembers circle:jdoe:family 
1) "users:richard"
2) "users:mike"
3) "users:anna"
redis> hgetall users:mike 
(...)

         现在我们知道如何使用集合存储数据,接下来我们做一些更有意思的事情,比如知道哪些成员是John所有集合里共有的,或者知道John全部集合里的所有成员。

redis> sinter circle:jdoe:family circle:jdoe:soccer 
1) "users:mike"
redis> sunion circle:jdoe:family circle:jdoe:soccer 
1) "users:anna"
2) "users:mike"
3) "users:apollo" 
4) "users:adam"
5) "users:richard" 
6) "users:toby"

         从结果来看,Mike既在John的家庭圈子里,也在他的足球圈子里。通过对两个圈子的并集操作,可以得到John所有朋友的列表。

         正如你所看到的,Redis的集合可以轻松做到的一些事情,在关系型数据库里通常需要更多的操作才能完成。对于需要管理集合的应用,用Redis来实现是很理想的。除了朋友圈,诸如推荐系统或文本搜索也很适合用集合来实现,这个会在后面的章节中看到。

检查你的数据

问题

         在开发的时候,你可能需要时不时地看看数据情况。尽管不像MySQL那样有SHOW TABLES命令或者SELECT语法,Redis还是有一些命令可以用来查看数据。

解决方案

         Redis的Keys命令允许你罗列出数据,可以配合通配符使用。比如:

KEYS * 

        会返回数据库里所有的键。不过这些远远不够,你可能还想知道这些键的类型,而TYPE命令就可以做到这点:

TYPE keyname

         这个命令会告诉你这个键的类型是字符串,哈希,列表,集合还是有序集合。

         有时候你想知道一个键存储的对象最后的读取或更新时间,可以使用OBJECT命令:

OBJECT IDLETIME keyname

讨论

        KEYS命令的通配符匹配功能虽然有一定局限性,不过还是很有用的。它支持这样的查询:

KEYS h*llo

         返回所有以h开头llo结尾的键。

KEYS h?llo

         返回以h开头llo结尾并且中间只包含一个字符的键。

KEYS h[ae]llo

         只返回hallo或者hello这样的键,如果它们存在的话。

         要注意,每次执行KEYS命令,Redis都会对数据库的所有键进行扫描。这样会拖慢数据库,所以不要把这个命令当成普通的操作来用。如果你想获得所有键的列表或者一个子集,可以事先把它们存到一个集合里,然后再通过查询集合获得。MONITOR命令在你调试的时候很有用,它会实时打印出那些被发送到服务器的命令。

基于Redis实现OAuth

问题

         在这个小节,我们将实现一个OAuth v1.0a API。这个一般通过MySQL或其它关系型数据库来实现,不过我们将利用Redis的数据结构实现一个更高效的版本。

解决方案

         我们不谈如何实现API和OAuth交互本身的细节,我们只关心实现API所需要的数据。我们将在Redis里存储这5种数据: consumer keys,consumer secrets,request tokens,access tokens,nonces。

         我们的需求是这样的:消费者用key和secret来标识,每个消费者只有一对key和secret。每个消费者可以有任意多个request token和access token,nonce对每个消费者是唯一的。

         根据这些数据的特点,它们将分别被存储成哈希,集合和字符串。

讨论

初始化设置

         首先,在消费者发出请求之前要输入他们的信息。我们会把这些信息放入哈希,key就是消费者在注册时分配的那一个。

HMSET /consumers/key:dpf43f3p2l4k3l03
      secret kd94hf93k423kf44 
      created_at 201103060000 
      redirect_url http://www.example.com/oauth_redirect 
      name test_application

        通过这个命令得到一个包含一般性数据的哈希,我们可以根据需要对其进行扩展。在Memcache里可以实现同样的操作,可以使用不同的key来存储这些值,也可以用JSON或YAML格式来存储。

获取request token

        要获取一个request token,消费者需要发送key,时间戳,nonce,回调url以及一个request signature。Request signature是用secret处理过的请求路径和参数的哈希值。

         API提供者用key和secret来验证signature是否正确,检查时间戳和nonce之前是否已经被用过,然后生成一个全新的request token。

         服务器端需要获取消费者数据:

HGETALL /consumers/key:dpf43f3p2l4k3l03

         然后检查这个nonce之前有没有被使用过:

SADD /nonces/key:dpf43f3p2l4k3l03/timestamp:20110306182600 dji430splmx33448

         用集合来存储nonce数据有一些好处:首先,集合可以保证唯一性。再则,通过给每一个nonce设置过期时间,可以在指定的时间把过期的nonce从集合种移除。在这个应用里,我们把过期时间设为30分钟:

EXPIRE /nonces/key:dpf43f3p2l4k3l03/timestamp:20110306182600 1800

        如果有人通过相同的时间戳和nonce再次发送相同的请求来触发相同的SADD命令,结果会返回0,表示这个值在集合里已经出现过了。如果碰到这种情况,服务器就应该拒绝生成新的request token。

         对所有数据进行验证通过之后,我们就可以创建token和secret:

HSET /request_tokens/key:dpf43f3p2l4k3l03 hh5s93j4hdidpola hdhd0244k9j7ao03

重定向和授权

         在成功获得request token之后,消费者要把用户请求重定向到API提供者,后者会对用户进行鉴权。一旦用户获得授权,我们要把它存下来:

SET /authorizations/request_token:hh5s93j4hdidpola 16

         然后我们就可以把用户重定向到之前的url上:

HGET /consumers/key:dpf43f3p2l4k3l03 redirect_url

用request token换取access token

         API提供者需要消费者提供access token作为认证凭据。它们是通过消费者的key,request token和secret来生成的。很多API都依赖OAuth来进行认证,我们也不例外,我们会检查access token是否已经得到用户的授权。

HGETALL /consumers/key:dpf43f3p2l4k3l03

         这一步检查消费者的key是否合法有效。

HGET /request_tokens/key:dpf43f3p2l4k3l03 hh5s93j4hdidpola

         我们还需要检查request token,如果找不到,说明有人试图重复使用request token,这个是不被允许的。

GET /authorizations/request_token:hh5s93j4hdidpola

         最后我们要检查用户授权的是哪个应用。

SADD /nonces/key:dpf43f3p2l4k3l03/timestamp:20110306182700 kllo9940pd9333jh
EXPIRE /nonces/key:dpf43f3p2l4k3l03/timestamp:20110306182600 1800

         再次强调一下,每一个消费者的nonce必须是唯一的,而SADD命令返回的集合结果确保了唯一性。之前的任何一个步骤验证失败,说明请求是非法的,我们就不应该生成access token。如果每一步都验证通过,我们可以生成token:

HMSET /access_tokens/consumer_key:dpf43f3p2l4k3l03/access_token:nnch7
      secret pfkkdhi9sl3r4s00 
      user_id 16 
      created_at 20110306182600
HDEL /request_tokens/key:dpf43f3p2l4k3l03 hh5s93j4hdidpola 
DEL /authorizations/request_token:hh5s93j4hdidpola

         或许我们的系统允许用户知道哪些应用可以访问他们的敏感数据。为了获取这些信息,我们把它们加到一个集合里:

HSET /users/user_id:16/applications dpf43f3p2l4k3l03 nnch734d00sl2jdk

         我们也允许用户取消对应用的访问授权:

HDEL /users/user_id:16/applications dpf43f3p2l4k3l03
DEL /access_tokens/consumer_key:dpf43f3p2l4k3l03/access_token:nnch7

         我们可以为每一个token设置过期时间。在这里,我们假设用户的授权时间是24小时:

EXPIRE /access_tokens/consumer_key:dpf43f3p2l4k3l03/access_token:nnch7 86400

         有一个细节需要注意:在给一个access token设置过期时间时,需要做一些检查。在把授权应用列表返回给用户前要检查token是否存在,或者如果key在/users/user_id:16/applications里仍然有效,那么就做一个常规的清理操作。

访问API

         消费者访问API的流程很简单,就是检查key,secret,signature和nonce。

HGETALL /consumers/key:dpf43f3p2l4k3l03
HGETALL /access_tokens/key:dpf43f3p2l4k3l03/access_token:nnch734d00sl2j
SADD /nonces/key:dpf43f3p2l4k3l03/timestamp:20110306182800 kllo9940pd9333jh
EXPIRE /nonces/key:dpf43f3p2l4k3l03/timestamp:20110306182600 1800

 

转载于:https://my.oschina.net/xuemingdeng/blog/739660

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值