Redis深刻系列-0x014:Redis数据类型和概念介绍(上)

0x000 概述

Redis不是一个简单键值对存储器,而是一个数据结构服务,它支持不一样类型的值。这意味着传统的键值对存储器将字符串键和字符串值关联起来,在Redis中,值的类型不只仅局限于字符串,还能够是更加复杂的数据结构,下面是Redis支持的数据结构,将会在各个章节接触到:redis

  • 字节安全的字符串
  • 列表:根据插入顺序排序的字符串集合元素,是最基本的链表
  • 集合:惟一的无序的字符串元素
  • 有序集合:和集合很想可是每一个字符串元素都关联着一个浮点型数字做为值,叫作分数。他老是按照分数排序,因此它不想集合那样,获取一个范围以内的元素。
  • 哈希:是一个由键值对关联起来的map,键和值都是字符串。对于RubyPython很是的友好。
  • 比特数组:使用特殊的命令能够向处理比特数组同样处理字符串,你能够设置或者清除独立的比特,将全部的比特设置为1,找到第一个比特等等等
  • HyperLogLogs:不解释。

知道这些数据类型和怎样使用对于解决命令索引给出的问题并不老是微不足道的。知道这些数据类型和怎样使用对于解决命令索引给出的问题是很重要的,因此这个文档将做为了解Redis数据类型和他们基本模式的一个入门课程。数据库

对于全部的案例咱们将使用redis-cli工具,这是一个很简单可是很便利的命令行工具,用来和Redis服务端作交互。数组

0x001 Rediskey

Rediskey是比特安全的,这意味着你可使用任何的二进制序列做为key,从像foo的字符串到一个JPEG文件的内容。甚至空字符串也是能够的。缓存

关于key有一些其余的规则:安全

  • 很是长的key是不推荐的。一个1024 bytes是一个很是坏的注意,不只仅是由于内存浪费,更是由于在数据集中搜索对比的时候须要耗费更多的成本。当要处理的是匹配一个很是大的值,从内存和带宽的角度来看,使用这个值的hash值是更好的办法(好比使用SHA1)。
  • 特别短的key一般也是不推荐的。在写像u100flw这样的键的时候,有一个小小的要点,咱们能够用user:1000:followers代替。可读性更好,对于key对象和value对象增长的空间占用与此相比来讲却是次要的。当短的key能够很明显减小空间占用的时候,你的工做就是找到正确的平衡
  • 尝试去固定一个密室。好比object-type:id是一个好主意,-.一般用于多个字符的域,就像comment:1234:reply.to,或者comment:1234:reply-to
  • 最大的key容许512MB

0x002 Redis字符串

Redi字符串类型是Rediskey能够关联的的最简单的数据类型。这是Mmcached惟一的数据类型,因此对于Redis的使用新手来讲,这是很是天然的。网络

由于Rediskey是字符串,当咱们使用字符串类型做为值的时候,咱们是将一个字符串映射到另外一个字符串。字符串类型在不少场景中是很是有用的,好比缓存HTML片断或者页面。数据结构

接下来使用redis-cli使用一下字符串类型(在这个文章中全部的示例都经过使用redis-cli):app

> set mykey somevalue
OK
> get mykey
"somevalue"

正如你看到的,使用SETGET命令能够设置和获取一个字符串值。值得注意的是SET将会覆盖key已经存在的值,即便这个key关联了一个不是字符串的值。因此SET表现为一个任务。ide

值能够是任意类型的字符串(包括二进制数据),好比你能够存储jpeg图片。一个值不能超过512MB工具

SET命令有一些有趣的选项,做为而外的参数。好比。我可让SETkey已经存在的时候失败,或者相反,只有在key存在的时候才成功:

> set mykey newval nx
(nil)
> set mykey newval xx
OK

即便字符串是Redis最基本的值,依旧有不少有趣的操做可使用。好比,原子增加:

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

INCR命令将字符串转化为integer,自增1,而后保存成新的值,还有其余相似的命令,好比INCRBYDECRDECRBY。在内部他们实际上是同样的命令,只是执行的时候有一点小差异而已。

INCR是原子的意味着什么?这意味着即便多个客户端发送INCR获取同一个key,将不会进入竞争状态,例如,客户端1获取到10,同时客户端2也获取到10是不可能的,所有获取的都是11,而且将11保存成新的值。最懂的值将会是12读取-自增-设置三个操做将在其余客户端还没执行命令的时候同时完成。

有不少的命令能够操做字符串。好比GETSET命令给一个key设置一个新的个值,同时返回旧的值做为结果。好比,你的系统在你的网站有一个新的访客到来的时候,使用INCR自增一个Rediskey,你可使用这个命令。你可能须要每小时收集全部的信息,甚至不错过每一次增加,你能够GETSET一个key,将它的值设置为0的同时获取新的值。

使用一个命令同时设置或者获取多个key的能力是下降延迟的好方法。MSETMGET命令能够作到:

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

MGET使用的时候,Redis将会返回一个值。

0x003 修改和查询key空间

有一些命令在部分类型中并无定义,可是和key空间交互的时候是很是有用的,因此,可使用在任意类型的key之上。

好比,当DEL命令删了吃了一个key和他所关联的值的时候,EXISTS命令返回1或者0去标记一个key是否存在在数据库,无论这个key关联的值是什么类型。

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

从例子中能够看出,DEL命令返回1或者0取决与key是否被移除了(存在,或者没有这个名字的key)。
From the examples you can also see how DEL itself returns 1 or 0 depending on whether the key was removed (it existed) or not (there was no such key with that name).
有很过key空间相关的命令,上面的两个命令和TYPE命令是最主要的,TYPE命令返回的是存储在这个key中的类型。

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

0x004 Redis期限:key的生存时间

在继续了解更多复杂的数据类型以前,咱们须要先讨论另外一个无视值类型的特性,咱们称之为Redis生存时间。简单来讲你能够为一个key设置一个过时时间,这个就是key能够存在的时间。当能够存在的时间过了,这个key就会自动销毁,就像用户使用DEL命令删除了这个key。
关于Redis期限的一些简单信息:

  • 他们可使用秒或者微妙做为单位
  • 最小的单位是1微妙
  • 关于期限的信息是复制并持久化到磁盘的,当你的Redis服务端中止的时候,时间也会过去(这意味着Redis将会保存一个key的过时日期)。

设置一个过时时间是很简单的

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

在两次相隔5s的GET调用中,key彻底消失了。在上面的例子中,咱们用EXPIRE去设置过时时间(固然也能够用来给一个已经存在过时时间的key设置一个不一样的过时时间,好比PERSIST能够用来移除过时时间,使这个key永久持久化)。固然咱们也可使用其余Redis命令建立一个有过时时间的key。好比,使用SET命令的选项:

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

的绗棉这个栗子设置了一个值为100,过时时间为10秒的key,接下来的TTL命令用来检查这个key剩下的生存时间。

为了用毫秒设置和检查生存时间,可使用PEXPIREPTTL命令,和完整的SET命令选项。

Redis List

为了解释List这种数据类型,最好先来点理论知识做为开胃菜,其实术语List在信息技术领域的使用是常常是不恰单的。好比Python Lists并不像名字所体现的(Linked Lists),更像Arrays(实际上相同的数据类型在Ruby中称为Array)。

从通常的观点看,一个List只是一个由一系列有序元素组成的列表:10,20,1,2,3。可是使用Array实现的List和用Linked List实现的List在特性上有很大的不一样。
Redis List是经过Linked List实现的。这意味着即便你有百万个元素在列表内,添加一个元素的操做到头部或者尾部的操做的时间是一个常量。使用LPUSH命令添加一个新元素到一个有10个元素的列表的头部所耗费的时间是和添加一个元素到一个有10000000万元素的列表的头部是同样的。

不利的一面是什么呢?使用Arrays实现的List经过索引访问一个元素是很是迅速的(常量时间),然而用Linked List实现的则不会这么快(这个操做须要的时间是和要访问的索引成正比的)。

Redis List使用Linked List实现是由于对于数据库系统来讲,它须要可以经过很是快的方式添加元素到一个很是长的列表。接下来你将看到一个很是强的优点,那就是Redis Lists能够采起常量长度在常量时间内。

当须要很是快的访问一个巨大聚合元素的其中一个数据时候,有另外一种数据结构可供选择,那就是Sorted SetsSorted Set将在下面的章节涉及。

Redis Lists使用第一步

LPUSH命令添加一个新的元素到一个列表的左边(头部),RPUSH命令添加一个新的元素到一个列表的右边(尾部)。LRAGE命令从列表中提取元素。

> 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"

注意:LRAGE须要两个索引,要返回的第一个元素的索引和最后一个元素的索引。两个因此均可以被导航,告诉Redis从开始统计到结束:因此,-1是列表的最后一个元素,-2是列表的倒数第一个元素,以此类推。

就像你看到的RPISH添加元素到列表的右边,LPUSH添加元素到列表的左边。
As you can see RPUSH appended the elements on the right of the list, while the final LPUSH appended the element on the left.
两个命令都是可变参数长度的命令,,这意味着你能够在一次执行中自由的推入多个元素到一个列表:

> 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"

定义在Redis Lists中的一个重要操做是pop的能力。弹出元素是从列表获取元素而且淘汰元素的操做。你能够从左边或则右边弹出元素,就像你能够从列表两边推入元素同样:

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

咱们推入了三个元素而且弹出了三个元素,因此执行完这一系列命令,这个列表最终变成空的,而且将再也不有数据弹出。若是咱们依旧尝试弹出其余元素,咱们将会获得以下结果:

> rpop mylist
(nil)
Redis returned a NULL value to signal that there are no elements in the list.

Redis Lists的应用场景

Lists对一系列任务都颇有帮助,下面是两个典型应用场景:

  • 在社交网络中记住用户最新更新的文章
  • 进程间交流,使用生产-消费模式,生产者推入元素到列表中,消费者消费这些元素,并执行动做。Redis有特殊的列表命令去保证这种用户场景更加可靠和有效。

好比,Ruby的库resquesidekiq在底层使用Redis Lists去实现后台任务。
流行的社交网络Twitter将最新的Twitter用户文章推入Redis Lists

为了一步一步归纳一个普通的用户场景,想一想你的主页显示了发布在一个照片分享社交网络的最新的照片,你想要很快的访问。

每次一个用户发布一张新的照片,咱们使用LPUSH将照片的ID放入一个列表。当用户访问主页的时候,咱们使用LRANGE 0 9去获取最新的10张照片。

有限List

在不少应用场景下,咱们只是想使用列表去存储最新的项目,好比:社交网络更新,日志,诸如此类。
Redis容许咱们像使用有限集合同样使用列表,只记住最新的N条数据并使用LTRIM抛弃掉最旧的数据。

LTRIMLRANGE很像,可是它不是现实指定的元素范围,而是设置指定的方位为新的列表值。全部不在这个范围以内的元素将被移除:
An example will make it more clear:

> rpush mylist 1 2 3 4 5
(integer) 5
> ltrim mylist 0 2
OK
> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"

TRIM命令告诉Redis只获取列中中索引0到2的元素,其余不在这个范围内的元素所有抛弃。这让一个简单可是有用的模式获得实现:向列表推入数据操做+修剪操做一块儿,实现了添加一个新元素并抛弃超出范围的元素:

LPUSH mylist <some element>
LTRIM mylist 0 999

上面的命令结合起来实现了添加一个新的元素到列表并获取列表前1000条最新的元素。LRANGE命令让你能够获取到定模的元素而且不准要记住每个旧的数据

注意:尽管LRANGE命令技术上是一个O(N)的命令,获取列表头部或者尾部很小范围的的数据依旧是一个常量时间操做。

List会阻塞的操做

列表有一个很特别的特性让它能够很适合用来实现队列,通常做为内部进程通讯系统的构建块:阻塞操做。
想一想你的一个进程想要将一个元素推入列表,另外一个进程想要对这些元素进行某些操做。这就是一般说的生产者/消费者模式,能够用下面的方式简单的实现:

  • 生产者使用LPUSH向列表推入数据。
  • 消费者使用RPOP从列表消费数据。

然而,有时列表有可能时空的,没有什么好执行的,因此RPOP将会返回NULL,这种状况下,消费者强制等待一些时间而后从新

  • 强制Redis和客户端去执行无效的命令(当列表是空的的时候,针对全部的请求其实没有作任何的工做,只是简单的返回NULL)。
  • 添加一个延迟去执行项目,由于一个工做进程接收到NULL后会等待一些时间。让延迟更小,咱们在能够在两个执行RPOP命令之间等待更少的时间,可是会引发问题1,也就是更多的无效请求。

因此Redis实现了BRPOPBLPOP命令,这个命苦可让RPOPLPOP能够在列表为空的时候堵塞:他们将只在一个新的元素添加进列表的时候执行,或者当用户指定的超时时间到了。
这是一个关于咱们可使用的BRPOP命令示例:

> brpop tasks 5
1) "tasks"
2) "do_something"

这意味着:等待列表中的元素,可是若是5s以后没有元素就返回。
值得注意的是,你能够设置超时时间为0,从而让线程永远等待,固然你也能够指定多个列表,而不是一个,同一时间等待多个列表,将会收到第一个收到新元素列表的通知。
一些关于BRPOP的笔记:

  • 客户端在一种有序的方式下运行:第一个客户端堵塞等待一个列表,它将在其余客户端推入元素的时候第一个被服务,并以此类推。
  • 返回值和RPOP不同:是一个包含两个元素的数组,他包含了key的名字,由于BRPOPBLPOP能够作到堵塞等待多个列表的元素。
  • 若是超时时间已经到了,将会返回NULL

关于列表和堵塞操做还有更多的星系你须要知道。咱们推荐你能够阅读下面的更多内容:

  • 使用RPOPLPUSH命令能够构建一个更安全的队列或者旋转队列。
  • BRPOPLPUSH命令是RPOPLPUSH命令堵塞的变形。
相关文章
相关标签/搜索