Redis有五种数据结构(String、Hash、List、Set、Sorted-Set),可以用这五个数据结构解决绝大多数的问题。

本文将要介绍一些新的功能,命令以及一些设计模式。

 

O标识符(时间复杂度)

贯穿这个教程,我们一直用O表示 从O(n)到O(1)。在Redis,这个会告诉我们一个命令有多快。

Redis的文档告诉我们每个命令的O标识符的结果。他也告诉我们应当关注与哪些影响性能。让我们看看一些例子。

最快的当然是O(1)。不管我们操作的是5个还是5百万条数据,这都是同样的效率。

sismember命令会告诉我们一个value是不是属于一个set—-O(1)。

sismember是一个很有用的命令,所以性能很重要。

 

对数O(log(N)),是次最快的,因为他被越来越小的分割

用这样的命令操作,一个很大的数据会分成很多次的迭代。

zadd就是一个O(log(N))的命令,N代表在set中的数据个数。

 

下一个就是线性命令O(N),查找table中一个无序的列就是一个O(N)。ltrim就是一个这样的命令。

但是在ltrim N不是list中元素的个数,而是要删除元素的个数。

ltrim删除一百万条数据中的一条比删除1000条数据中的10条要快(不过你感觉不出来,因为速度很快)。

 

zremrangebyscore这个命令是从sorted set中删除2个分数之间的元素。他的时间复杂度是O(log(N)+M)。

这是一个混合。我们从文档上看到N是set中有多少元素。M是有多少元素要删除。换句话说删除元素是关键。

 

排序命令的时间复杂度为O(N+M*log(M))。看到这个你可能会说这是一个Redis最复杂命令之一了。

这还有2个其他的。O(N2)和O(CN)。N越大性能越差。在Redis中没有这么复杂的

 

值得说明的O标示符表示的是最坏的情况。

当我们说一些需要O(N)的,最好的情况是马上找到,最坏的情况是在最后才找到。

 

 

伪键查询

在通常情况下我们想要通过不同的key查询相同的value

举个例子,你可能想通过email查找用户,并且也想通过id找到他。

一个可怕的解决办法就是重复2条string作为value:

set users:let@dune.gov "{id:9001,email:'leto@dune.gov',....}"
set users:9001 "{id:9001,email:'leto@dune.gov',...}"

这非常不好,这非常难管理,它占2个内存。

如果Redis能提供一个key引用另一个就好了,但是他没有。Redis的一个主要成员要保持Redis的code和API整洁和简单。内部实现的连接Key(我们可以对keys做很多事情)是hashes。

用hash,我们可以去掉复制的操作。

set users:9001 "{id:9001,email:'leto@dune.gov',....}"
hset users:loopup:email leto@dune.gov 9001

上面就是我们用一个伪键引用一个user。

1)通过id获得user :

get users:9001

2)通过emial获得(Ruby):

id = redis.hget('users:loopup:email','leto@dune.gov')
user = redis.get("user:#{id}");

这就是我们经常做的。对于我这就是hashes的用处,当你用到的时候就会体会的到。

 

 

引用和索引

我们在list的例子中看到过,上面的hashes的例子我们用来简便查询操作。

接下来我们要从本质上手动管理索引和引用。

实话说,在Redis中当你要考虑手动管理,更新,删除引用,很让人头疼,因为没有什么神奇的方法。

我们已经看到过set手动的管理索引:

sadd friends:leto ghanima paul chani jessica

set中每个成员都是一个Redis的string value描述user的详细信息。假如chani欢乐她的名字,或者删除她的账号呢?也想还有个逆向关系:

sadd friedns:chani leto pual

考虑这些额外的索引值的维护成本,你就可能畏惧了。在下一节中,我们将讨论如何降低性能开销而不做额外的操作。

如果你仔细想想,关系型数据库也有同样的开销。索引消耗内存

手动处理Reids的索引是不行的,但是你测试起来,这并不是一个问题。

 

 

循环操作和管道

我们已经提到多次运行在Redis是很常见的模式。既然是你经常做的事情,我们就进一个看看这些功能。

首先,很多命令既接收一个参数,也接收多个参数,或者有个姐妹命令带着多个参数:

keys = redis.lrange('newusers',0,10)
redis.mget(*keys.map{|u| "users:#{u}})

或者sadd命令 增加1个或多个成员到set中:

sadd friends:vladimir piter
sadd friends:paul jessica leto "leto II" chani

Redis同时也支持管道。通常的一个客户端发送一个请求到Redis,他就是等待回应,然后发下一个。

如果用管道的话,你可以发送一连串的请求而不必等待响应。这样就能减少网络开销,并提高效率

值得注意点是,Redis用把命令变成队列放到内存中。使用多达要看你用多大的命令,具体的说要用多少参数。

如果你用一个包含50以上的字符串key就会占用非常大的数量级的内存。

事实是你怎么在管道中执行是有所不同的。在Ruby中你可以传一个block到管道方法:

redis.pipelined do
    9001.times do
        redis.incr('powerlevel')
    end
end

正如你看到的,管道很提高很多效率!

 

 

事务

每个Redis的命令都是原子性的,包括一个可以做多个事的。

此外,Redis运行多个命令的时候是支持事务的。

你可能不知道,Redis是单线程的,可以保证每个命令的原子性

当一个命令执行的时候没有其他命令会运行。这是非常有用的,当你考虑一些命令执行多个事情的时候。

举个例子:

incr可以跟在set后 

getset 可以set一个新值然后返回原先的 setnx 首先检查key是否存在,只有不存的时候才set。

这些命令都很有用。你必须运行多个命令作为一个原子。

你首先发出多个命令,然后你想让所有命令座位事务的一部分,然后顺序执行,或者全部撤销。

是什么能保证Redis的事务?

  • 命令会按顺序执行
  • 这些命令会按一个原子执行
  • 要不是所有命令都执行,要不就一个都不能执行

你可以在终端中试试。注意为什么不能结合管道一起用:

multi
hincrby groups:1percent blance  -9000000000
hincrby groups:99percent balance 9000000000
exec

最后Redis让你贯穿指定的一个key或多个key。这用于当你获得values然后操作这些value。

在之前的代码,一旦运行我们不能实现自己的incr命令:

redis.multi()
current = redis.get('powerlevel')
redis.set('powerlevel',current+1)
redis.exec()

这就是Redis的事务为什么不好使。但是如果我们给 powerlevel加个watch:

redis.watch('powerlevel')
current = redis.get('powerlevel')
redis.multi()
redis.set('powerlevel',current+1)
redis.exec()

如果另一个客户端在我们watch了后改变了powerlevel的值,我们的事务就会失败。

如果没有客户端改变value,这个set就会好使。

我们可以执行这段代码在一个循环中之道好使为止。

 

 

keys的反模式

在下一章,我们将谈论一些不是指定的数据结构的命令。其中一些是管理或调试的工具。

但是有一个要特别说一下:keys命令。这个命令是一个模式,能找到匹配的keys。

这个命令似乎适合一些任务,但是这个不能用到生产环境中。为什么?因为他是线性的搜索所有匹配的keys。简单的说,速度慢。

怎么用呢?你要家里一个处理bug的服务。每一账户会有一个id,然后你觉得存储每个bug为一个string value 像 bug:account_id:bug_id。如果你需要找到所有相互的bugs(显示这些,或者删除他们),你会试着:

keys bug:1233:*

好的解决方式是用hash。我能用hash提供一个副引用。所以我们能用他们组织我们的数据:

hset  bugs:1233 1 "{id:1,account:1233,subject:'...'}"
hset  bugs:1233 2 "{id:2,account:1233,subject:'...'}"

为了获得一个账号所有的bug ids 我们只需简单的调用 hkeys bugs:1233

删除指定的bug我们调用hdel bugs:1233 2然后我们要删除一个账户 我们可以用del bugs:1223

 

小节

结合前一章,我们展示了Redis的一些强大的功能。

还有很多其他的模式你可以构建自己的类型的数据。

但重要的是要了解基本的数据结构,了解后用他们实现属于自己的。

 

 

参考推荐

Redis系列(4)—— 高级功能

Redis系列(5)—— 排序与订阅

PHP-redis 中文文档

php-redis 各种函数中文手册