Redis有5种常见的基础数据结构:string,list,set(集合),hash,zset(有序集合)。
在了解这些Redis的数据结构之前,我们必须清楚,Redis是采用key—Value格式存储的。在存进去之前采用序列化的方式,返回来的数据又是反序列化。key是作为唯一的键存在,但是value根据不同的基础数据类型就会有不同的数据结构。
Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值,从形如”foo”的简单字符串到一个JPEG文件的内容都可以。空字符串也是有效key值。

设置key值需要注意:

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

string

redis的字符串是一种动态字符串,是可以修改的
内部机构类似于java中的ArrayList,有预分配空间来减少内存的冗余分配。类似于这样。但是最高的字符串大小为512M。而且这种字符串比较特殊,它其实也可以是一张图片。

redis存储数据格式 redis 数据格式_字符串

通常采用Set和Get来创建和获取字符串
pattern匹配:
  • 表示任意长度字符
  • ?表示一个字符
  • []表示可以是集合中的任意一个
基本操作:字符串操作,追加,截取,即set,get,append

set

redis存储数据格式 redis 数据格式_数据类型_02

在后面会有语法提示

redis存储数据格式 redis 数据格式_redis存储数据格式_03

redis存储数据格式 redis 数据格式_数据类型_04


redis存储数据格式 redis 数据格式_字符串_05

即ex代表秒倒计时,px代表毫秒倒计时,设置了在内存中的过期时间

redis存储数据格式 redis 数据格式_redis存储数据格式_06


redis存储数据格式 redis 数据格式_Redis_07

set后有nx即在not exist情况下添加,即意思时不修改原值
xx即在存在的情况下才可以添加,即修改的意思。

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

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

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

INCR是原子操作意味着什么呢?就是说即使多个客户端对同一个key发出INCR命令,也决不会导致竞争的情况。例如如下情况永远不可能发生:
『客户端1和客户端2同时读出“10”,他们俩都对其加到11,然后将新值设置为11』。最终的值一定是12,read-increment-set操作完成时,其他客户端不会在同一时间执行任何命令。
对字符串,另一个的令人感兴趣的操作是GETSET命令,行如其名:他为key设置新值并且返回原值。这有什么用处呢?例如:你的系统每当有新用户访问时就用INCR命令操作一个Redis key。你希望每小时对这个信息收集一次。你就可以GETSET这个key并给其赋值0并读取原值。
为减少等待时间,也可以一次存储或获取多个key对应的值,使用MSET和MGET命令:

mset a 10 b 20 c 30
 OK
 mget a b c
  1. “10”
  2. “20”
  3. “30”

修改或查询键空间
有些指令不是针对任何具体的类型定义的,而是用于和整个键空间交互的。因此,它们可被用于任何类型的键。
使用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

数据类型计算:
位图bitmap:底层位运算

列表:lists

要说清楚列表数据类型,最好先讲一点儿理论背景,在信息技术界List这个词常常被使用不当。例如”Python Lists”就名不副实(名为Linked Lists),但他们实际上是数组(同样的数据类型在Ruby中叫数组)
一般意义上讲,列表就是有序元素的序列:10,20,1,2,3就是一个列表。但用数组实现的List和用Linked List实现的List,在属性方面大不相同。

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

那么,坏消息是什么?在数组实现的list中利用索引访问元素的速度极快,而同样的操作在linked list实现的list上没有那么快。
Redis Lists用linked list实现的原因是:对于数据库系统来说,至关重要的特性是:能非常快的在很大的列表上添加元素。另一个重要因素是,正如你将要看到的:Redis lists能在常数时间取得常数长度。
如果快速访问集合元素很重要,建议使用可排序集合(sorted sets)。可排序集合我们会随后介绍。

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中推入博客评论,等等。

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 的长度),或者一个删除元素的命令,将总是产生同样的结果。该结果和对一个空的聚合类型做同个操作的结果是一样的。

散列:Hash

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 被用特殊方式编码,非常节约内存。

集合:set

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 …
 不光可以取交集,还可以取并集,差集,获取随机元素,等等。
 获取一个元素的命令是 SPOP,它很适合对特定问题建模。比如,要实现一个基于 web 的扑克游戏,你可能需要用 set 来表示一副牌。假设我们用一个字符的前缀来表示不同花色:
 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 命令删除一个随机元素,把它返回给客户端,因此它是完全合适的操作。
 但是,如果我们对我们的牌直接调用它,在下一盘我们就需要重新充满这副牌。开始,我们可以复制 deck 键中的内容,并放入 game:1:deck 键中。
 这是通过 SUNIONSTORE 实现的,它通常用于对多个集合取并集,并把结果存入另一个 set 中。但是,因为一个 set 的并集就是它本身,我可以这样复制我的牌:
 sunionstore game:1:deck deck
 (integer) 52
 现在,我已经准备好给 1 号玩家发五张牌:
 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”

排序集数据类型

排序集类似于集:元素集合。但是,在“排序集”中,每个元素都与一个称为元素得分的浮点值相关联 。由于存在得分,因此排序集中的元素是有序的,因为我们始终可以按得分比较两个元素(如果得分恰好相同,则可以将两个元素作为字符串进行比较)。
与“排序集中的集合”一样,不可能添加重复的元素,每个元素都是唯一的。但是,可以更新元素的分数。
排序集命令的前缀为Z。以下是“排序集”用法的示例:
ZADD zset 10 a
ZADD zset 5 b
ZADD zset 12.55 c
ZRANGE zset 0 -1 => b,a,c
在上面的示例中,我们使用添加了一些元素ZADD,随后使用检索了元素ZRANGE。如您所见,元素按照其得分顺序返回。为了检查给定元素是否存在,并检索其分数(如果存在),我们使用以下ZSCORE命令:
ZSCORE zset a => 10
ZSCORE zset non_existing_element => NULL
排序集是一个非常强大的数据结构,您可以按分数范围,字典顺序,反向顺序等查询元素

HyperLogLog