Redis系列(4)—— 高级功能
Redis有五种数据结构(String、Hash、List、Set、Sorted-Set),可以用这五个数据结构解决绝大多数的问题。
本文将要介绍一些新的功能,命令以及一些设计模式。
O标识符(时间复杂度)
贯穿这个教程,我们一直用O表示 从O(n)到O(1)。在Redis,这个会告诉我们一个命令有多快。
Redis的文档告诉我们每个命令的O标识符的结果。他也告诉我们应当关注与哪些影响性能。
让我们看看一些例子:
最快的当然是O(1)。不管我们操作的是5个还是500万条数据,这都是同样的效率。
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
删除100万条数据中的一条比删除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)通过email获得(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的一些强大的功能。
还有很多其他的模式你可以构建自己的类型的数据。
但重要的是要了解基本的数据结构,了解后用他们实现属于自己的。
参考推荐:
BloomFilter + Redis 大数据去重策略的实现
版权所有: 本文系米扑博客原创、转载、摘录,或修订后发表,最后更新于 2021-01-21 11:31:36
侵权处理: 本个人博客,不盈利,若侵犯了您的作品权,请联系博主删除,莫恶意,索钱财,感谢!
转载注明: Redis系列(4)—— 高级功能 (米扑博客)