• 初识Redis(一)--Redis数据类型和数据抽象简介


    初识Redis(一)–Redis数据类型和数据抽象简介

    一、概述

    Redis(Remote Dictionary Server,远程字典服务 )并不只是简单的键值存储(key-value store ),它实际上是一个数据结构服务器(Data Structure Server),支持不同类型的值。 这意味着,尽管在传统的键值存储中,我们将字符串键与字符串值相关联(associated string keys to string values ),但是在Redis中,value值并不仅限于简单的字符串,还可以容纳更复杂的数据结构。

    以下是Redis支持的所有数据结构的列表:

    • Binary-safe strings(二进制安全字符串)
    • Lists根据插入顺序排序的字符串元素集合,基本上是链表
    • Sets:同样为字符串元素的集合,但集合中的元素不允许重复,元素之间是无序的
    • Sorted sets :与Sets类似,但是每个字符串元素都与一个名为score的浮点值相关联,元素总是按它们的score排序。因此与Sets不同,Sorted sets中可以按范围检索元素(例如,取前10名或后10名)。
    • Hashes(哈希) :由与值关联的字段组成的映射,字段和值都是字符串。
    • Bit arrays (位数组,或简称为位图):可以使用特殊命令像位数组一样处理字符串值:您可以设置和清除单个位,计数所有设置为1的位,找到第一个设置或未设置的位, 等等。
    • HyperLogLogs (超日志) :这是一个概率数据结构,用于估计集合的基数。
    • Streams:append-only collections of map-like entries that provide an abstract log data type.

    下面所有示例都将使用redis-cli 命令行工具来对redis服务器进行操作。

    二、数据类型详解

    1.Redis keys

    Redis键是二进制安全的,这意味着您可以使用任何二进制序列作为密钥,从“ foo”之类的字符串到JPEG文件的内容。 空字符串也是有效的键。

    有关键的其他一些规则:

    • 太长的键不是一个好主意。 例如,1024字节的键是糟糕的,不仅是因为内存方面的问题,还因为在数据集中查找这样的键可能需要进行一些代价高昂的键比较。 即使手头的任务是匹配一个大值的存在,对它进行散列(例如使用SHA1)也是一个更好的方法,尤其是从内存和带宽的角度来考虑。
    • 非常短的键通常也不是一个好主意。 如果您可以将键写成“ user:1000:followers”,那么编写“ u1000flw”做为键就毫无意义了。 后者更具可读性,并且与键对象本身和值对象使用的空间相比,增添的空间较小。 虽然短键显然会消耗更少的内存,但在键的可读性和存储空间上找到合适的平衡更重要。
    • 命名规范 。例如:“ object-type:id”、“ user:1000”。 点或破折号通常用于多字的字段。
    • 键的大小不允许超过512MB

    2.Redis Strings

    字符串类型是最简单的值类型,对于许多用例很有用,例如缓存HTML片段或页面。

    可以通过SETGET命令来设置和检索字符串值,案例如下:

    redis set|get 字符串值

    需要注意的是,执行SET命令时,会替换目标键中已存在的任何值,即使键是和非字符串值相关联。

    值可以是任意类型的字符串(包括二进制数据),例如,可以在值内存储jpeg图片,但值最大不能超过512 MB。

    SET命令的额外参数:

    • nx:如果键已经存在,那么不进行此次set操作
    • xx:如果键存在,才进行此次set操作

    set命令的nx和xx参数

    INCR命令可以将字符串值解析为整数,然后将其递增1,最后将获得的值设置为新值。 还有其他类似的命令,例如INCRBYDECRDECRBY。 在系统内部,它们实际上是相同的命令,只是执行方式略有不同。

    INCR是原子的,意味着即使多个客户同时使用相同键发出INCR命令,也永远不会进入竞争状态。 例如,客户端1读取“ 10”,客户端2同时读取“ 10”,都将值增为11,并将新值设置为11,但最终值将始终为12,而不会是11。

    对字符串值进行原子增量(atomic increment )操作:

    incr命令和incrby命令

    GETSET命令可以将键设置为新值,并返回旧值作为结果。

    MSETMGET**命令可以在单次调用中设置或检索多个键的值。**使用mget命令时,redis返回的是一个值数组。

    getset命令、mset和mget命令

    3.更改和查询key space

    有些命令未在特定类型上定义,因此可以与任何类型的键一起使用,在与键的空间进行交互时很有用。

    例如,EXISTS命令表示数据库中是否存在给定的键,而DEL命令则删除键和与键关联的值(无论该值是什么)。EXISTSDEL 命令都可以一次查询或删除多个键,返回的数字代表存在或成功删除多少个键。

    exists命令与del命令

    TYPE命令可以查看指定键的值的类型,例如:

    TYPE命令

    4.Redis expires(Redis键的超时时间)

    **可以为键设置一个超时时间,即有限的生存时间,超过生存时间后,该密钥将自动销毁,就像用户使用该键调用DEL命令一样。**如果执行SET命令时,未指定键的超时时间,则该键会一直存在,直至被手动删除等。

    Redis 超时时间的几个小点:

    • 可以使用秒或毫秒精度进行设置;
    • 但是,到期时间分辨率始终为1毫秒;
    • Redis会保存键过期的日期,有关过期的信息会被复制并保留在磁盘上。

    可以使用EXPIRE命令设置键的过期时间,同时可以**使用TTL命令查看键还剩多长时间过期。**具体示例如下:

    expire命令和ttl命令

    还可以使用PERSIST命令将取消键的超时时间,使之持久化SETEXPIRE命令可以在单次调用中一起使用,expire简写为ex。下面的示例中,在set name时同时设置其过期时间为20S,后面又用PERSIST命令取消其过期时间。可以看到前后使用TTL命令得到了不同的结果,后面返回-1代表该键是持久化的,无超时时间。

    persist命令

    可以使用PEXPIRE命令按毫秒设置过期时间,使用PTTL命令按毫秒查看键还剩多少时间过期。SETPEXPIRE 命令也可以在单个命令中一起使用,pexpire简写为px。使用PTTL命令查看键的过期时间时,如果键是持久化的,则返回-1,如果键不存在,则返回-2,TTL命令同理。

    pexpire命令和pttl命令

    5.Redis Lists(有序列表)

    Redis Lists是通过链表实现的,因为对于数据库系统而言,至关重要的是能够以非常快的方式将元素添加到很长的列表中,另一个强大的优势是Redis Lists可以在常量级的时间内以恒定的长度使用。

    当快速访问大型元素集合的中间部分很重要时,可以使用另一种不同的数据结构,称为排序集(Sorted Sets)。本教程稍后将讨论排序集。

    (1)插入数据

    1. 使用LPUSH命令会在List的左边,即链表头插入新的元素。
    2. 使用RPUSH会在List的右边,即链表尾插入新的元素。
    3. 使用LRANGE命令可以从List中获取指定范围的元素,LRANGE命令需要两个索引,要返回的范围的第一个和最后一个元素。 两个索引都可以为负,告诉Redis从末尾开始计数:因此-1是列表的最后一个元素,-2是列表的倒数第二个元素,以此类推。

    Redis Lists 插入和查找数据

    1. LPUSHRPUSH这两个命令都是可变参数命令,即可以在单次调用中随意将多个元素推入列表中。

    (2)删除数据

    List中既然可以插入数据,自然也少不了删除数据,Redis Lists上具有弹出元素的功能。 弹出元素是指从列表中检索元素并将其从列表中删除的操作。 您可以从左侧和右侧弹出(pop)元素,类似于在列表两边推送(push)元素的方式:

    1. 使用LPOP命令会在List的左边,即链表头弹出元素,删除成功后返回被删除的值。
    2. 使用RPOP 命令会在List的右边,即链表尾弹出元素,删除成功后返回被删除的值。
    3. 如果List是空列表,执行弹出命令时会返回Null,指示列表中没有元素。

    lpop命令和rpop命令

    (3)常见用例(Use Cases)

    列表对于许多任务很有用,以下是两个非常有代表性的用例:

    • 记住用户发布到社交网络上的最新更新。
    • 进程之间的通信,使用消费者-生产者模式,生产者将项目推入List,消费者(通常是工作人员)使用这些项目并执行操作。Redis有特殊的List命令,使这个用例更加可靠和高效。

    例如,流行的Ruby库resque和sidekiq都在底层使用Redis Lists来实现后台作业。流行的Twitter社交网络将用户发布的最新推文纳入Redis列表。

    设想您的主页显示了照片共享社交网络中发布的最新照片,并且您希望加快访问速度:

    • 每次用户发布新照片时,我们都会用LPUSH将其ID添加到列表中。
    • 当用户访问主页(Home Page)时,我们使用LRANGE 0 9来获取最新发布的10个条目。

    (4)上限集合(Capped lists)

    在许多场景中,我们只想使用List存储最新的内容,比如社交网络的更新内容,程序运行日志或其他任何内容。

    Redis允许我们使用List作为一个有上限的集合,只记住最新的N个项目,并使用LTRIM命令丢弃所有最旧的项目。

    LTRIM命令类似于LRANGE,但是不显示指定的元素范围,而是将这个范围设置为新列表元素的值,给定范围之外的所有元素都将被删除。 举个栗子:

    ltrim命令

    上面的LTRIM命令告诉Redis仅保留索引0到3的列表元素,其他所有内容都将被丢弃。 那么将List push操作 + List trim操作一起执行,就可以做到添加新元素并丢弃超出限制的元素,即有上限的集合。

    注意:虽然LRANGE 命令在技术上是一个O(N)命令,但是访问列表头部或尾部的小范围是一个常量时间操作。

    (5)List上的阻塞操作(Blocking operation on lists)

    可以用List实现队列,类推生产者-消费者模式(队列是先进先出,那么发布者发布消息是放到队列尾,接收者取走消息是从队列头拿,但官方文档里发布消息用的是LPUSH??取走消息是RPOP ??那不就反了么,没搞懂哪里出了问题,备注下,后面我是按自己思路修改的,原文见Redis-data-types-intro)。

    生产者调用RPUSH命令将消息放入队列尾,消费者调用LPOP命令从队列头中取走消息。但是,有时List可能为空,没有任何要处理的内容,因此LPOP仅返回NULL。 在这种情况下,消费者被迫等待一段时间,然后使用LPOP 重试。 这种方式被称为轮询,有以下几个缺点:

    1. 强制Redis和客户端处理无用的命令(列表为空时的所有请求将无法完成任何实际工作,它们只会返回NULL)。
    2. 由于消费者(worker)在收到NULL之后会等待一段时间,因此会增加项目处理的延迟。 为了减小延迟,我们可以在两次调用LPOP之间等待更少的时间,从而扩大了问题编号1,即更多无用的Redis调用。

    因此,Redis实现了BRPOPBLPOP的命令,它们是RPOPLPOP的阻塞版本。如果List为空,它们将进入阻塞状态,仅当将新元素添加到List中或用户指定的超时时间到时,它们才会返回到调用方。

    Redis官方文档示例:

    示例

    有关BRPOP的几点注意事项:

    • 客户端以有序方式提供服务:第一个阻塞等待列表的客户端,在某个元素被其他客户端推送时首先提供服务,依此类推。
    • 返回值与LPOP 相比有所不同:它是一个包含两个元素的数组,因为它还包含键的名称。BRPOP和BLPOP能够阻止等待来自多个列表的元素。
    • 如果达到超时时间,则返回NULL。

    使用RPOPLPUSH可以建立更安全的队列或轮换队列,该命令还有一个阻塞变体,称为BRPOPLPUSH。后面遇到再具体学习。

    (6)自动创建和删除Redis keys

    1. 当我们将元素添加到聚合数据类型时,如果目标键不存在,则在添加元素之前会创建一个空的聚合数据类型。

      案例1

    2. 当我们从聚合数据类型中删除元素时,如果该值保持为空,则键将自动销毁。流(Strems)数据类型是此规则的唯一例外。

      案例2

    3. 调用诸如LLEN(返回List的长度)之类的只读(Read Only)命令,或者使用空键执行删除元素的写命令,总是会产生相同的结果,就好像该键持有的的空聚合类型是命令希望找到的类型一样。

      案例3

    6.Redis Hashes

    尽管哈希表很容易表示对象,但是实际上可以放入哈希表中的字段数没有实际限制(可用内存除外),因此您可以在应用程序内以多种不同方式使用哈希表。

    HMSET命令可以一次设置哈希的多个字段,而HGET 命令检索单个字段,HMGET命令 可以一次检索多个字段,返回的是一个值数组。HGETALL命令直接返回目标键的所有字段。

    HMSET&HGET&HMGET&HGETALL

    还有一些命令也可以对单个字段执行操作,例如HINCRBY 命令。完整的关于Hash命令的文档

    值得注意的是,小哈希值(key对应的value比较小)在内存中以特殊的方式进行了编码,使它们的内存效率非常高。

    7.Redis Sets(无序集合)

    Redis Sets是字符串的无序集合。 使用SADD命令能向集合中添加新元素。 还可以对集合进行许多其他操作,例如检测给定元素是否已存在执行多个集合之间的交集,并集或求差等等。

    sadd命令和smembers命令

    在上面的示例中,首先使用sadd命令向集合中存进了9个元素,然后使用smembers命令获取指定键(myset)的所有元素,正如你所看到的,这些元素并没有排序——Redis可以在每次调用时,以任意顺序返回元素,因为它与用户没有关于元素排序的约定。

    Redis有测试成员状态的命令。例如,使用SISMEMBER命令检查元素是否存在:

    sismember

    集合很适合表示对象之间的关系。例如,我们可以很容易地使用集合来实现标记。

    对这个问题建模的一个简单方法是为我们要标记的每个对象设置一个集合,该集合包含与对象关联的标记的id

    举个例子:给新闻文章加标签。如果文章ID 1000带有标签1、2、5和77,则集合可以将这些标签ID与新闻项相关联:

    问题建模1

    我们也可能还需要有一个反向关系:所有的新闻列表用一个给定的标签来标记:

    问题建模2

    获取给定对象的所有标签是很简单的:

    问题建模3

    使用正确的Redis命令还可以轻松实现其他一些重要的操作。例如,我们可能想要一个包含标签1、2、10和27的所有对象的列表。我们可以使用SINTER命令来实现这一点,它执行不同集合之间的交集:

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

    除了交集之外,还可以执行union(求并集)、difference(集合的差)、提取随机元素等等。

    提取元素的命令称为SPOP,可以方便地对某些问题进行建模。例如,为了实现一个基于web的扑克游戏,您可能需要用一个集合来表示您的牌组。假设我们对©lub、(D)iamond、(H)earts、(S)pades使用一个字符前缀来表示:

    >  sadd deck C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 CJ CQ CK
       D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 DJ DQ DK H1 H2 H3
       H4 H5 H6 H7 H8 H9 H10 HJ HQ HK S1 S2 S3 S4 S5 S6
       S7 S8 S9 S10 SJ SQ SK
       (integer) 52
    

    现在我们要给每个玩家提供5张牌。这时使用SPOP命令删除一个随机元素,并将其返回给客户机,在本例中,这是一个完美的操作。然而,如果我们直接对我们的牌组调用SPOP命令,那在下一局游戏中,我们将需要再次填充纸牌组,这可能不是理想的。

    要解决上面出现的问题,我们可以在一开始,将存储在deck key中的集合复制到game:1:deck key中。这是使用SUNIONSTORE 命令来完成的,它通常在多个集合之间执行并集,并将结果存储到另一个集合中。但是,由于单个集合的并集就是它本身,所以我可以用以下命令:

    > sunionstore game:1:deck deck
    (integer) 52
    

    现在我准备给第一个玩家提供5张牌:

    > spop game:1:deck
    "C6"
    > spop game:1:deck
    "CQ"
    > spop game:1:deck
    "D1"
    > spop game:1:deck
    "CJ"
    > spop game:1:deck
    "SJ
    

    这是介绍set命令的好时机,set命令返回一个集合中元素的数量。在集合理论的上下文中,这通常称为集合的基数(cardinality),因此Redis命令中set命令被称为SCARD

    > scard game:1:deck
    (integer) 47 # 52 - 5 = 47
    

    当您需要只获取随机元素而不从集合中删除它们时,有一个适合于该任务的SRANDMEMBER命令。它还具有同时返回重复和非重复元素的功能。详见SRANDMEMBER

    8.Redis Sorted Sets(有序集)

    有序集是一种数据类型,类似于集合(Sets)和散列(Hashes)之间的混合。与集合类似,有序集由惟一的、不重复的字符串元素组成,因此在某种意义上,有序集也是集合。
    然而,与集合(Sets)内的元素都是无序的不同,有序集中的每个元素都与一个浮点值相关联,称为score(这就是为什么有序集也类似于散列,因为每个元素都映射到一个值)。
    此外,有序集中的元素是按顺序获取的(所以它们不是按请求排序的,顺序是用来表示有序集的数据结构的一个特性)。有序集中元素的排列规则如下:

    • 如果A和B是两个得分不同的元素,如果A.score > B.score,那么A > B。
    • 如果A和B的分数完全相同,那么如果A字符串在词法(lexicographically)上大于B字符串,则A > B。A和B字符串不能相等,因为排序集中的元素是唯一的。

    让我们从一个简单的例子开始,添加一些选定的人物名称作为排序的集合元素,它们的出生年份作为“score”。

    mark

    正如您所看到的,ZADD 命令类似于SADD 命令,但是接受一个额外的参数(放在要添加的元素之前),即分数。ZADD命令同样可以接收可变参数,所以您可以自由地指定多个score-value对,即使在上面的示例中没有使用到。

    对于有序集,返回按出生年份排序的黑客列表是很简单的,因为实际上他们已经排序了。

    实现提示:有序集是通过两个数据结构实现的,包含一个跳跃表(skip list)和一个散列表(hash table),所以每次添加一个元素Redis都会执行一个时间复杂度为O(log(N))的操作。 但当我们从有序集中取元素时,并不需要进行额外的操作,因为元素已经排序好了:

    mark

    注意:0和-1表示从元素索引0到最后一个元素(-1在这里的作用就像在LRANGE命令中的作用一样)。

    如果我想要相反的顺序呢,年龄从最小的到最大?使用ZREVRANGE代替ZRANGE

    mark

    也可以使用WITHSCORES参数表示在结果中将分数一起返回:

    mark

    范围操作(Operating on ranges)

    排序集在这方面的功能很强大。他们可以执行范围操作。让我们把1950年之前出生的所有人都包括在内。我们使用ZRANGEBYSCORE命令来完成:

    > zrangebyscore hackers -inf 1950
    1) "Alan Turing"
    2) "Hedy Lamarr"
    3) "Claude Shannon"
    4) "Alan Kay"
    5) "Anita Borg"
    123456
    

    我们要求Redis返回得分在负无穷(negative infinity)到1950之间的所有元素(包括两个极端负无穷,正无穷)。

    还可以删除元素的范围。让我们把1940年到1960年之间出生的所有黑客从排序集中删除:

    > zremrangebyscore hackers 1940 1960
    (integer) 4
    12
    

    ZREMRANGEBYSCORE可能不是最好的命令名,但它可能非常有用,并返回删除的元素数量。

    另一个为有序集元素定义的非常有用的操作是get-rank操作。可以询问元素在有序元素集中的位置。

    > zrank hackers "Anita Borg"
    (integer) 4
    12
    

    考虑到按降序排列的元素,还可以使用ZREVRANK命令来获得降序的位置。

    字典分数(Lexicographical scores)

    在 Redis 2.8中,引入了一个新功能,假设元素相同的一组排序都插入相同的分数,则可以按字典顺序获取范围内的元素(元素比较大小使用C的memcmp函数,所以它保证没有排序,每个Redis实例将返回相同的输出)(elements are compared with the C memcmp function, so it is guaranteed that there is no collation, and every Redis instance will reply with the same output)。

    使用词典范围操作的主要命令是ZRANGEBYLEX、ZREVRANGEBYLEX、ZREMRANGEBYLEX和ZLEXCOUNT。

    例如,让我们再次添加我们的著名黑客列表,但这次对所有元素使用0分:

    > zadd hackers 0 "Alan Kay" 0 "Sophie Wilson" 0 "Richard Stallman" 0
      "Anita Borg" 0 "Yukihiro Matsumoto" 0 "Hedy Lamarr" 0 "Claude Shannon"
      0 "Linus Torvalds" 0 "Alan Turing"
    123
    

    由于有序集排序规则,它们已经按字典顺序排序:

    > zrange hackers 0 -1
    1) "Alan Kay"
    2) "Alan Turing"
    3) "Anita Borg"
    4) "Claude Shannon"
    5) "Hedy Lamarr"
    6) "Linus Torvalds"
    7) "Richard Stallman"
    8) "Sophie Wilson"
    9) "Yukihiro Matsumoto"
    12345678910
    

    使用ZRANGEBYLEX,我们可以请求字典范围某范围的元素:

    > zrangebylex hackers [B [P
    1) "Claude Shannon"
    2) "Hedy Lamarr"
    3) "Linus Torvalds"
    1234
    

    范围可以包含或排除(取决于第一个字符),也可以用+和-字符串分别指定string infinite和- infinite。有关更多信息,请参阅文档。

    这个特性很重要,因为它允许我们使用有序集作为泛型索引。例如,如果您想通过一个128位无符号整数参数为元素建立索引,那么您所需要做的就是将元素添加到一个具有相同得分(例如0)但带有16字节前缀的排序集中,该前缀由128位大端数字组成。由于数字以大端为单位,当按词法顺序(按原始字节顺序)排序时,实际上也是按数字顺序排序的,所以您可以查询128位空间中的范围,并获得去掉前缀的元素值。
    如果您想在更严谨的上下文中查看该特性,请检查Redis autocomplete demo

    更新分数:排行榜(Updating the score: leader boards)

    在切换到下一个主题之前,请最后注意一下有序集。有序集的分数可以随时更新。只要对已包含在有序集中的元素调用ZADD,就会用O(log(N))时间复杂度更新其得分(和位置)。因此,当有大量更新时,有序集是合适的。

    由于这个特性,一个常见的用例是排行榜。典型的应用程序是一个Facebook游戏,在这个游戏中,你结合了根据用户的高分对用户进行排序的功能,以及get-rank操作,以显示前n名用户,以及用户在排行榜中的排名(例如,“你是这里得分最高的4932名用户”)。

    9.Bitmaps(位图)

    等用到再补充…

    10.HyperLogLogs

    等用到再补充…

    参考资料

    [1]:Redis Document

  • 相关阅读:
    sc 使用
    sql端点应用
    今天面试笔试了一道SQL面试题,狠简单
    指定域的名称或安全标识SID与该域的信任信息不一致
    查询登陆时间间隔不超过5分钟的所有记录
    sql打开xls
    Android控件开发
    android开发1【转】网络设备状态检测
    google.maps Simple Polylines
    Notification 使用详解(很全
  • 原文地址:https://www.cnblogs.com/blog567/p/12374647.html
Copyright © 2020-2023  润新知