redis数据结构

来源:互联网 发布:电脑软件快捷键冲突 编辑:程序博客网 时间:2024/05/04 04:35

redis支持的数据结构

  • Binary-safe strings
  • Lists
  • Sets
  • Sorted sets
  • Hashes
  • Bit arrays
  • HyperLogLogs

redis的key注意事项

  • 不建议用太长的key, 不只因为浪费内存和宽带, 而且在数据库查找key时会做一些费时的key的比较工作
  • 不建议用太短的key, 为确保key值的可读性
  • 对key做一些规范化工作, 比如, 用”object-type: id”这种格式作为redis的key
  • key所被允许最大的大小是512MB ( value也是如此 )

redis的strings类型

redis的key是string类型的, 而string也是可分配的最简单的value. 下面演示一个简单的例子:

> set  mykey  somevalue  OK> get  mykey  "somevalue"  

如果需要更新某一个key的value, 并返回旧的值, 可以使用getset命令

> set  mykey  "Hello"  OK> getset  mykey  "World"  "Hello"  > get  mykey  "World"  

如果需要批量保存key-value, 可以使用mset命令, 相对应的mget命令可以获取多个key的值

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

正如上面的演示, get和set分别是获取和保存的命令, 而需要注意的是, set命令将覆盖已经存在的key所对应的value.
set命令还可以附带一些有趣的选项参数以达到一些效果. 比如, 当key已经存在时就保存失败, 或者当key已经存在时才保存成功.(mykey已经存在)

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

除此之外, 还有一个原子子增长的命令

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

incr相对应的还有原子性的递减, decrdecrby等命令.

修改和查询key空间

有很多命令与key空间相关, 虽然它们不是为特殊类型所定义, 但是很有用.
比如exists, deltype命令, exists命令将返回1或0表示给出的key是否存在, 而del返回1或0表示给出的key是否被删除

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

type命令将返回key所对应的value的类型

> set  mykey  xOK> type  mykey  string  > del  mykey  (integer) 1  > type  mykey  none  

redis过期时间

在我们学习复杂的数据结构之前, 我们有必要了解其他功能, 而不管value是什么类型, 它就是redis expires.
如果设置了过期时间, 那么这个key将在时间触发时自动销毁, 就好像对这个key调用了del命令.
关于redis expires的信息:

  • 它能够用秒或者毫秒作为保存单位
  • 过期时间的处理通常在1个毫秒
  • 过期时间的相关信息被复制和保留在硬盘上, 如果redis服务器挂掉, 那么那些设置了过期时间的key也将被销毁.

对于过期时间, set命令还有ex和px选项参数

  • ex: 以秒为单位设置过期时间
  • px: 以毫秒为单位设置过期时间

同时秒和毫秒都有对应的命令

  • expire: 修改已有的key过期时间, 以秒为单位
  • pexpire: 修改已有的key过期时间, 以毫秒为单位
  • ttl: 返回剩余可存活的时间, 以秒为单位
  • pttl: 返回剩余可存活的时间, 以毫秒为单位

redis list数据结构

redis list是由linked list实现的. 这就意味着即使你有几百万个元素在list里面, 添加一个新元素到list的头部或者尾部所花费的时间都是固定的. 那么你用lpush命令将一个新元素添加到已经有十个元素的list的头部所花费的时间, 和添加到已经有一千万个元素的list的头部所花费的时间是一样的.
如果array来实现list, 那么通过index来访问list就会很快. 而linked list实现的list则访问速度没那么快. 这就是redis list的弊端.
而redis list之所以没有这么做, 是因为它觉得能够以快速时间添加一个元素到一个很长的列表是很重要的事情. 另一个重要的因素是, redis list能够以常数时间获取常数长度.

redis list第一步

  • 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为倒数第一个, -2为倒数第二个, 以此类推)
    1) “first”
    2) “A”
    3) “B”

上面的命令都是可变长度命令, 意味这你能够单个命令添加多个元素

> 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 list中有一个重要的操作, 即pop. pop元素既从list获取元素, 也会将此元素从list剔除. 同时, 你能够从左边(left)和右边(right)pop元素.

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

常见list用例

list有两个典型的用例, 如下:

  • 记住用户发布到社交网络的最新更新
  • 能够通过使用消费者-生产者模式在进程之间通信, 生产者将元素推送到list中, 而消费者获取元素并执行操作. redis有特殊的list命令使得操作这些用例更加灵活和高效.

让我们简单描述常用的用例, 可以想象你的主页展示了社交网络发布的最新相片, 你为了更快速的访问这些相片

  • 每次用户发布一个新的相片, 我们就用lpush将它的ID添加到list中
  • 当用户访问到主页时, 我们就使用lrange 0 9获取最新发布的10个相片

限制list

redis允许我们使用list作为一个限制集合, 通过ltrim命令只保留最新的N个元素, 而抛弃最老的元素.
ltrim命令类似于lrange, 但是并不显示特定范围的list元素. 它保存给出的范围元素作为list的新值, 而删除范围外的元素.

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

list的阻塞操作

list有一个特殊的功能使它能够用来实现队列(queue).
想象你需要在一个进程添加元素到list中, 同时在不同的进程用list中的这些元素做各种类型的工作.
这就是通用的生产者/消费者模式, 我们可以用如下简单方式去实现它.

  • 添加元素到list中, 生产者调用lpush命令
  • 获取元素从list中, 消费者调用rpop命令

然而有可能当list是空或者没有东西处理的时候, 那么rpop将返回NULL. 这种情况下, 消费者不得不等待一段时间或者循环rpop直到获取到元素.
这并不是一种好的方式, 因为它有几个不好之处:

  • redis和客户端不得不去执行这个没用的命令(只返回NULL也不会做任何有用的工作)
  • 增加了延迟. 因为在处理器接收到一个NULL之后, 它会等待一段时间. 为了减少等待时间, 我们将更频繁地调用rpop, 而这又扩大了问题1的影响, 即更多的无用命令将被调用.

因此redis实现了rpop和lpop的阻塞命令brpopblpop. 如果list为空: 只有当有新的元素添加到list或者到了自定义的超时时间, 它才会返回一个caller.

> brpop  tasks  51) "tasks"  2) "do_something"  

如上操作: 等待tasks列表的元素, 但是如果5秒后还没有可用的元素将返回.
注意: 你能够使用0作为超时时间, 那么将一直等待list元素. 而且你能够用brpop定义多个list, 这样的话, 多个list都将以相同的时间等待, 也将获取到通知当第一个list获取到一个元素时.
下面有些关于brpop主要要点:

  • 客户端以有序的方式提供服务: 第一个客户端在阻塞等待一个list, 那么当有元素添加到list时, 第一个客户端先获取到这个元素, 接着是第二个等待的客户端, 以此类推.
  • 比起rpop, brpop返回的值不用. 它将返回两个元素(key(即list名)和value)的数组. 因为brpop和lrpop可以阻塞等待多个list的元素, 所以需要识别返回的元素是哪个list的元素
  • 如果过了超时时间, 那么返回NULL

还有两个命令, 也可以去了解

  • rpoplpush命令能够建立安全的列表, 或者转动列表
  • brpoplpush命令是阻塞的命令

自动创建以及移除key

到目前为止, 我们不需要在添加元素之前手动创建一个空list, 也不需要在list为空的时候手动删除list. 这些工作都由redis帮我们完成了.
这些工作并不只发生在list中, redis对所有由多个元素组合而成的数据类型(集合)都自动做了此类工作—-set, sorted set和hash.
基本上, 我们可以对这些行为总结出三个规则.

  • 当我们添加元素到一个集合数据类型时, 当key不存在, 则redis先创建一个空的集合类型
  • 当我们从一个集合数据类型移除元素时, 如果value是空的, 则redis会删除这个key.
  • 使用不存在的key去调用一个读命令例如llen, 或者调用一个写命令移除元素, 那么将返回的结果和调用空集合返回的结果一样.

redis hash数据结构

redis hash以field-value的方式保存数据, 因此hash更方便表现一个对象, 而且hash中的field数量并没有实际的限制(看redis所在服务器的内存有多大).
hmset命令保存多个field-value, 相对应地, hmget获取多个field对应的value数组, 而hget获取单个field的value.

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

如果需要了解更多hash相关命令, 可以从 http://redis.io/commands#hash 中了解
这里特别需要说明: 小hash(即拥有很少的field以及value值很小)会被特殊编码, 使它们很高效..

redis set数据结构

redis set是无序的strign集合.
sadd命令添加元素到set集合中.

> sadd  myset  1 2 3  OK> smembers  myset  1) "1"  2) "2"  3) "3"  

redis有sismember命令检查是否存在给出的元素

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

set能够表示对象之间的关系, 比如我们用set实现标签.
假如我们需要给一个对象添加标签, 那么set可以包含所有某个对象相关联的ID. 想象我们需要给news对象添加标签, 如果ID为1000的news对象被标签1,2,5和77打上标签, 那么我们就可以用news:1000:tags集合保存标签ID.

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

然而,有时候我们也可以对这种关系进行反转存储:即通过给出的标签ID查询出所有与此有关联的news对象ID

> sadd  tag:1:news  1000 2000  (integer) 2  > sadd  tag:2:news  1000 3000  (integer) 2  

我们也可以使用sinter命令查看覆盖了多个tag的news对象, sinter将返回多个set集合的公共拥有的值
spop能够从set中随机移除一个元素, 这个命令可以解决一些问题. 比如, 你需要实现一个扑克牌游戏, 那么就可以使用set去模拟一副牌. 我们用前缀C,D,H,S分别模拟梅花,方块,红心,黑桃.

> 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中获取牌, 那么玩第二轮的时候,牌就不齐了. 所以我们一开始就应该拷贝一份出来, 方便第二轮使用. 为了完成这个工作, 我们可以调用sunionstore命令, 它能够将唯一的值拷贝一份到目的set集合中.

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

如果你需要从set中随机获取元素, 而又不删除它, 那么你可以使用srandmember命令. 我们可以用scard命令去检查set中的元素个数, 确定是否删除了元素.

> scard  game:1:deck  (integer) 52  > srandmember  game:1:deck  2  1) "D2"  2) "S10"  > scard  game:1:deck  (integer) 52  

redis sorted set数据结构

sorted set很想hash和set的结合体. sorted set也是由不重复的string元素构成.
在sorted set中的每个元素都会关联一个浮点值, 称之为分数(score)(这也是为什么sorted set类型很像hash, 因为每个元素都映射到一个值). sorted set中的元素是有序的, 而这个顺序依照一下规则:

  • 如果有A元素和B元素, 满足A.score > B.score, 那么A > B.
  • 如果有A元素和B元素, 满足A.score = B.score, 且比较A元素和B元素的字母顺序, 有A.value > B.value, 那么A > B (不会出现A = B的情况, 因为set确保唯一性)

    zadd mysortset 1 b 1 a 4 e 3 m
    (integer) 4

根据以上的排序规则, 我们可以知道mysortedset里面的元素排序是: a, b, m, e

> zrange  mysortset  0 -1  1) "a"  2) "b"  3) "m"  4) "e"  

如果我们需要倒序的元素数组,怎么办? 这时,只需要用zrevrange命令就可以实现了

> zrevrange  mysortset  0 -1  1) "e"  2) "m"  3) "b"  4) "a"  

如果我们需要将score也一起返回,怎么办?使用withscores选项参数就可以了

> zrange  mysortset  0 -1 withscores1) "a"  2) "1"  3) "b"  4) "1"  5) "m"  6) "3"  7) "e"  8) "4"  

关于sorted sets的结构实现的注意点:
sorted set通过双端口的数据结构实现, 它包含了一个跳跃list和一个hash表, 所以每次我们添加元素, redis花费的时间复杂度将是O(log(N)). 但这也有好处就是当我们可以直接获取已经排序好的元素, 而不用再做任何工作.

操作ranges

sorted sets的ranges功能更加强大, 如果有个sorted set通过出生年作为score,那么我们如何获取1960年之前(包含1960)出生的人的列表, 很简单, 通过zrangebyscore命令就能完成它.

> zrangebysocre  mysortset  -inf 3  1) "a"  2) "b"  3) "m"  

器重-inf表示负无穷. 即获取mysortset的score在负无穷和3之间的值, 包括负无穷和3.
我们也可以删除一定范围的元素

> zremrangebyscore  mysortset  1 3  ( Integer ) 3  

除了上面这些range命令外, 还有一个get-rank的操作命令, 它能够返回在sorted set中的元素为知

> zrank  mysortset  "m"  (integer) 2  

字典score

在redis 2.8中有一个新功能引入, 它可以获取一定范围的字典顺序元素, 只需要在sorted set中添加相同的score. 主要命令有zrangebylex, zremrangebylexzlexcount.

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

可以使用字典来检索数据

> zrangebylex  hackers  [B  [P1) "Claude  Shannon"  2) "Hedy  Lamarr"  3) "Linus  Torvalds"  

需要注意的是一些标识符的作用

+: 无限大  -: 无限小  [: 大于或等于  ]: 小于或等于  (: 大于  ): 小于

更新score

sorted set的score可以随时更新, 只需要在此zadd一次需要更新的元素, 它的时间复杂度是O(log(N)).

redis bitmap数据结构

bitmap不是一个真实的数据类型, 一系列的bit操作都定义在string类型, 因为string是二进制安全的, 而且它的最大长度是512mb. redis允许使用二进制的数据的key(binary key)和二进制数据的value(binary value). bitmap就是二进制数据的value.
redis的setbit(key, offset, value)操作对指定key的value的指定偏移(offset)量置1或者0, 时间复杂度为O(1). 为了统计今日登录的用户数, 我们建立一个bitmap, 每一位标识一个用户ID. 当某个用户访问我们的网页或者执行了某个操作, 就在bitmap中把标识此用户的bit置为1. 在redis中获取此bitmap的key值是通过用户执行操作的类型和时间戳获得的.
bitmap

这个简单的例子中, 每次用户登录会执行一次redis.setbit(daily_active_users, user_id, 1). 将bitmap对应为知的bit置1. 统计bitmap结果显示今天有9个用户登录. bitmap的key是daily_active_users, 它的值为1011110100100101.
因为日活跃用户每天都变化, 所以需要每天创建一个新的bitmap. 我们简单地把日期添加到key后面, 实现这个功能. 例如, 要统计某一个天有多少用户至少听了一个因为app中给的一首歌曲, 可以把这个bitmap的redis key设计为play:yyyy-mm-dd-hh. 当用户听了一首歌曲,我们只是简单在bitmap中把表示这个用户的bit置1.

0 0
原创粉丝点击