IT序号网

Redis 基本使用知识解答

lxf 2021年05月25日 数据库 179 0

基本的数据类型

  • 二进制安全的字符串
  • Lists: 按插入顺序排序的字符串元素的集合。他们基本上就是链表(linked lists)
  • Sets: 不重复且无序的字符串元素的集合。
  • Sorted sets,类似Sets,但是每个字符串元素都关联到一个叫score浮动数值(floating number value)。里面的元素总是通过score进行着排序,所以不同的是,它是可以检索的一系列元素。(例如你可能会问:给我前面10个或者后面10个元素)。
  • Hashes,由field和关联的value组成的map。field和value都是字符串的。这和Ruby、Python的hashes很像。

Redis keys

Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值,从形如”foo”的简单字符串到一个JPEG文件的内容都可以。空字符串也是有效key值。

关于key的几条规则:

  • 太长的键值不是个好主意,例如1024字节的键值就不是个好主意,不仅因为消耗内存,而且在数据中查找这类键值的计算成本很高。
  • 太短的键值通常也不是好主意,如果你要用”u:1000:pwd”来代替”user:1000:password”,这没有什么问题,但后者更易阅读,并且由此增加的空间消耗相对于key object和value object本身来说很小。当然,没人阻止您一定要用更短的键值节省一丁点儿空间。
  • 最好坚持一种模式。例如:”object-type:id:field”就是个不错的注意,像这样”user:1000:password”。我喜欢对多单词的字段名中加上一个点,就像这样:”comment:1234:reply.to”。

Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值,从形如”foo”的简单字符串到一个JPEG文件的内容都可以。空字符串也是有效key值。

关于key的几条规则:

  • 太长的键值不是个好主意,例如1024字节的键值就不是个好主意,不仅因为消耗内存,而且在数据中查找这类键值的计算成本很高。
  • 太短的键值通常也不是好主意,如果你要用”u:1000:pwd”来代替”user:1000:password”,这没有什么问题,但后者更易阅读,并且由此增加的空间消耗相对于key object和value object本身来说很小。当然,没人阻止您一定要用更短的键值节省一丁点儿空间。
  • 最好坚持一种模式。例如:”object-type:id:field”就是个不错的注意,像这样”user:1000:password”。我喜欢对多单词的字段名中加上一个点,就像这样:”comment:1234:reply.to”。

Redis Strings

这是最简单Redis类型。如果你只用这种类型,Redis就像一个可以持久化的memcached服务器(注:memcache的数据仅保存在内存中,服务器重启后,数据将丢失)。

> set mykey somevalue 
OK 
> get mykey 
"somevalue"

正如你所见到的,通常用SET command 和 GET command来设置和获取字符串值。

值可以是任何种类的字符串(包括二进制数据),例如你可以在一个键下保存一副jpeg图片。值的长度不能超过512 MB。

 虽然字符串是Redis的基本值类型,但你仍然能通过它完成一些有趣的操作。例如:原子递增:

> set counter 100 
OK 
> incr counter 
(integer) 101 
> incr counter 
(integer) 102 
> incrby counter 50 
(integer) 152

INCR 命令将字符串值解析成整型,将其加一,最后将结果保存为新的字符串值,类似的命令有INCRBYDECR 和 DECRBY。实际上他们在内部就是同一个命令,只是看上去有点儿不同。

INCR是原子操作意味着什么呢?就是说即使多个客户端对同一个key发出INCR命令,也决不会导致竞争的情况。例如如下情况永远不可能发生:『客户端1和客户端2同时读出“10”,他们俩都对其加到11,然后将新值设置为11』。最终的值一定是12,read-increment-set操作完成时,其他客户端不会在同一时间执行任何命令。

对字符串,另一个的令人感兴趣的操作是GETSET命令,行如其名:他为key设置新值并且返回原值。这有什么用处呢?例如:你的系统每当有新用户访问时就用INCR命令操作一个Redis key。你希望每小时对这个信息收集一次。你就可以GETSET这个key并给其赋值0并读取原值。

为减少等待时间,也可以一次存储或获取多个key对应的值,使用MSETMGET命令:

> mset a 10 b 20 c 30 
OK 
> mget a b c 
1) "10" 
2) "20" 
3) "30"

MGET 命令返回由值组成的数组。

修改或查询键空间

有些指令不是针对任何具体的类型定义的,而是用于和整个键空间交互的。因此,它们可被用于任何类型的键。

使用EXISTS命令返回1或0标识给定key的值是否存在,使用DEL命令可以删除key对应的值,DEL命令返回1或0标识值是被删除(值存在)或者没被删除(key对应的值不存在)。

> set mykey hello 
OK 
> exists mykey 
(integer) 1 
> del mykey 
(integer) 1 
> exists mykey 
(integer) 0

TYPE命令可以返回key对应的值的存储类型:

> set mykey x 
OK 
> type mykey 
string 
> del mykey 
(integer) 1 
> type mykey 
none

Redis超时:数据在限定时间内存活

在介绍复杂类型前我们先介绍一个与值类型无关的Redis特性:超时。你可以对key设置一个超时时间,当这个时间到达后会被删除。精度可以使用毫秒或秒。

> set key some-value 
OK 
> expire key 5 
(integer) 1 
> get key (immediately) 
"some-value" 
> get key (after some time) 
(nil)

上面的例子使用了EXPIRE来设置超时时间(也可以再次调用这个命令来改变超时时间,使用PERSIST命令去除超时时间 )。我们也可以在创建值的时候设置超时时间:

> set key 100 ex 10 
OK 
> ttl key 
(integer) 9

TTL命令用来查看key对应的值剩余存活时间。

Redis Lists

要说清楚列表数据类型,最好先讲一点儿理论背景,在信息技术界List这个词常常被使用不当。例如”Python Lists”就名不副实(名为Linked Lists),但他们实际上是数组(同样的数据类型在Ruby中叫数组)

一般意义上讲,列表就是有序元素的序列:10,20,1,2,3就是一个列表。但用数组实现的List和用Linked List实现的List,在属性方面大不相同。

Redis lists基于Linked Lists实现这意味着即使在一个list中有数百万个元素,在头部或尾部添加一个元素的操作,其时间复杂度也是常数级别的。用LPUSH 命令在十个元素的list头部添加新元素,和在千万元素list头部添加新元素的速度相同。

那么,坏消息是什么?在数组实现的list中利用索引访问元素的速度极快,而同样的操作在linked list实现的list上没有那么快。

Redis Lists用linked list实现的原因是:对于数据库系统来说,至关重要的特性是:能非常快的在很大的列表上添加元素。另一个重要因素是,正如你将要看到的:Redis lists能在常数时间取得常数长度。

如果快速访问集合元素很重要,建议使用可排序集合(sorted sets)。可排序集合我们会随后介绍。

Redis lists 入门

LPUSH 命令可向list的左边(头部)添加一个新元素,而RPUSH命令可向list的右边(尾部)添加一个新元素。最后LRANGE命令可从list中取出一定范围的元素:

> rpush mylist A 
(integer) 1 
> rpush mylist B 
(integer) 2 
> lpush mylist first 
(integer) 3 
> lrange mylist 0 -1 
1) "first" 
2) "A" 
3) "B"

注意:LRANGE 带有两个索引,一定范围的第一个和最后一个元素。这两个索引都可以为负来告知Redis从尾部开始计数,因此-1表示最后一个元素,-2表示list中的倒数第二个元素,以此类推。

上面的所有命令的参数都可变,方便你一次向list存入多个值。

> rpush mylist 1 2 3 4 5 "foo bar" 
(integer) 9 
> lrange mylist 0 -1 
1) "first" 
2) "A" 
3) "B" 
4) "1" 
5) "2" 
6) "3" 
7) "4" 
8) "5" 
9) "foo bar"

还有一个重要的命令是pop,它从list中删除元素并同时返回删除的值。可以在左边或右边操作。

> rpush mylist a b c 
(integer) 3 
> rpop mylist 
"c" 
> rpop mylist 
"b" 
> rpop mylist 
"a"

我们增加了三个元素,并弹出了三个元素,因此,在这最后 列表中的命令序列是空的,没有更多的元素可以被弹出。如果我们尝试弹出另一个元素,这是我们得到的结果:

> rpop mylist 
(nil)

当list没有元素时,Redis 返回了一个NULL。

List的常用案例

正如你可以从上面的例子中猜到的,list可被用来实现聊天系统。还可以作为不同进程间传递消息的队列。关键是,你可以每次都以原先添加的顺序访问数据。这不需要任何SQL ORDER BY 操作,将会非常快,也会很容易扩展到百万级别元素的规模。

例如在评级系统中,比如社会化新闻网站 reddit.com,你可以把每个新提交的链接添加到一个list,用LRANGE可简单的对结果分页。

在博客引擎实现中,你可为每篇日志设置一个list,在该list中推入博客评论,等等。

Capped lists

可以使用LTRIM把list从左边截取指定长度。

> rpush mylist 1 2 3 4 5  // 5在最左边 
(integer) 5 
> ltrim mylist 0 2 
OK 
> lrange mylist 0 -1 
1) "1" 
2) "2" 
3) "3"

List上的阻塞操作

可以使用Redis来实现生产者和消费者模型,如使用LPUSH和RPOP来实现该功能。但会遇到这种情景:list是空,这时候消费者就需要轮询来获取数据,这样就会增加redis的访问压力、增加消费端的cpu时间,而很多访问都是无用的。为此redis提供了阻塞式访问 BRPOP 和 BLPOP 命令。 消费者可以在获取数据时指定如果数据不存在阻塞的时间,如果在时限内获得数据则立即返回,如果超时还没有数据则返回null, 0表示一直阻塞。

同时redis还会为所有阻塞的消费者以先后顺序排队。

如需了解详细信息请查看 RPOPLPUSH 和 BRPOPLPUSH

key 的自动创建和删除

目前为止,在我们的例子中,我们没有在推入元素之前创建空的 list,或者在 list 没有元素时删除它。在 list 为空时删除 key,并在用户试图添加元素(比如通过 LPUSH)而键不存在时创建空 list,是 Redis 的职责。

这不光适用于 lists,还适用于所有包括多个元素的 Redis 数据类型 – Sets, Sorted Sets 和 Hashes。

基本上,我们可以用三条规则来概括它的行为:

  1. 当我们向一个聚合数据类型中添加元素时,如果目标键不存在,就在添加元素前创建空的聚合数据类型。
  2. 当我们从聚合数据类型中移除元素时,如果值仍然是空的,键自动被销毁。
  3. 对一个空的 key 调用一个只读的命令,比如 LLEN (返回 list 的长度),或者一个删除元素的命令,将总是产生同样的结果。该结果和对一个空的聚合类型做同个操作的结果是一样的。

规则 1 示例:

> del mylist 
(integer) 1 
> lpush mylist 1 2 3 
(integer) 3

但是,我们不能对存在但类型错误的 key 做操作:   > set foo bar OK > lpush foo 1 2 3 (error) WRONGTYPE Operation against a key holding the wrong kind of value > type foo string

规则 2 示例:

> lpush mylist 1 2 3 
(integer) 3 
> exists mylist 
(integer) 1 
> lpop mylist 
"3" 
> lpop mylist 
"2" 
> lpop mylist 
"1" 
> exists mylist 
(integer) 0

所有的元素被弹出之后, key 不复存在。

规则 3 示例:

> del mylist 
(integer) 0 
> llen mylist 
(integer) 0 
> lpop mylist 
(nil)

Redis Hashes

Redis hash 看起来就像一个 “hash” 的样子,由键值对组成:

> hmset user:1000 username antirez birthyear 1977 verified 1 
OK 
> hget user:1000 username 
"antirez" 
> hget user:1000 birthyear 
"1977" 
> hgetall user:1000 
1) "username" 
2) "antirez" 
3) "birthyear" 
4) "1977" 
5) "verified" 
6) "1"

Hash 便于表示 objects,实际上,你可以放入一个 hash 的域数量实际上没有限制(除了可用内存以外)。所以,你可以在你的应用中以不同的方式使用 hash。

HMSET 指令设置 hash 中的多个域,而 HGET 取回单个域。HMGET 和 HGET 类似,但返回一系列值:

> hmget user:1000 username birthyear no-such-field 
1) "antirez" 
2) "1977" 
3) (nil)

也有一些指令能够对单独的域执行操作,比如 HINCRBY

> hincrby user:1000 birthyear 10 
(integer) 1987 
> hincrby user:1000 birthyear 10 
(integer) 1997

你可以在文档中找到 hash 指令的完整列表

值得注意的是,小的 hash 被用特殊方式编码,非常节约内存。

Redis Sets

Redis Set 是 String 的无序排列。SADD 指令把新的元素添加到 set 中。对 set 也可做一些其他的操作,比如测试一个给定的元素是否存在,对不同 set 取交集,并集或差,等等。

> sadd myset 1 2 3 
(integer) 3 
> smembers myset 
1. 3 
2. 1 
3. 2

现在我已经把三个元素加到我的 set 中,并告诉 Redis 返回所有的元素。可以看到,它们没有被排序 —— Redis 在每次调用时可能按照任意顺序返回元素,因为对于元素的顺序并没有规定。

Redis 有检测成员的指令。一个特定的元素是否存在?

> sismember myset 3 
(integer) 1 
> sismember myset 30 
(integer) 0

“3” 是 set 的一个成员,而 “30” 不是。

Sets 适合用于表示对象间的关系。 例如,我们可以轻易使用 set 来表示标记。

一个简单的建模方式是,对每一个希望标记的对象使用 set。这个 set 包含和对象相关联的标签的 ID。

假设我们想要给新闻打上标签。 假设新闻 ID 1000 被打上了 1,2,5 和 77 四个标签,我们可以使用一个 set 把 tag ID 和新闻条目关联起来:

> sadd news:1000:tags 1 2 5 77 
(integer) 4

但是,有时候我可能也会需要相反的关系:所有被打上相同标签的新闻列表:

> sadd tag:1:news 1000 
(integer) 1 
> sadd tag:2:news 1000 
(integer) 1 
> sadd tag:5:news 1000 
(integer) 1 
> sadd tag:77:news 1000 
(integer) 1

获取一个对象的所有 tag 是很方便的:

> smembers news:1000:tags 
1. 5 
2. 1 
3. 77 
4. 2

注意:在这个例子中,我们假设你有另一个数据结构,比如一个 Redis hash,把标签 ID 对应到标签名称。

使用 Redis 命令行,我们可以轻易实现其它一些有用的操作。比如,我们可能需要一个含有 1, 2, 10, 和 27 标签的对象的列表。我们可以用 SINTER 命令来完成这件事。它获取不同 set 的交集。我们可以用:

> sinter tag:1:news tag:2:news tag:10:news tag:27:news 
... results here ...

不光可以取交集,还可以取并集,差集,获取随机元素,等等。


发布评论
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

在NodeJS中使用Redis缓存数据知识解答
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。