Redis哈希表(Hashes)
Redis中的哈希表数据类型和很多编程语言的实现类似,通过键值对记录数据:
> 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"
尽管哈希表是一种方便的存储对象属性的数据类型。但由于在Redis中,向哈希表添加键值对并无数量限制(除了占用内存外),它可以在各种应用场景发挥作用。
HMSET命令可以设置多个哈希表字段, 而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
一个值得提示的情况是:在只有几个小字段组成的哈希表实现中,Redis使用了特殊的存储编码。使得在此场景下,哈希表的内存利用率得到了显著提升。
Redis集合(Sets)
Redis集合用于记录一组无序的Redis字符串。通过SADD命令,可以向集合中添加元素。同时,Redis也支持一系列其他操作,如:检测元素是否在集合中, 计算两个集合的交集、并集、差集等。
> sadd myset 1 2 3
(integer) 3
> smembers myset
1) "b"
2) "a"
3) "c"
在上面的示例中,我们向集合添加了三个元素,并操作Redis返回全部集合元素。你可能已经观察到Redis的返回元素是无序的。事实上,使用集合时应当注意,Redis在每次调用命令获取集合元素时,返回内容都无法保证顺序。
Redis可以通过以下命令检测元素是否已在集合中存在:
> sismember myset 3
(integer) 1
> sismember myset 30
(integer) 0
"3"已在集合中, 而"30"不在。
集合通常用于表达对象之间的关联关系。例如:通过集合可以很容易地实现标签功能。实现标签模型需要为每个标记对象创建一个集合,用于记录标记对象与标签ID的关联关系。比如给新闻文章打标签时,如果需要将ID为1000的文章使用标签1、2、5和77进行标记,则可以使用集合记录标签与文章的关联:
> 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
此时获取文章对应的标签十分容易:
> smembers news:1000:tags
1) "1"
2) "2"
3) "5"
4) "77"
这里需要注意的是,我们在上面示例中还假设存在另一个数据结构例如哈希表, 用来存储标签ID与标签名称的对应关系。
有了集合,一些其他类型的操作很容易实现。例如:我们需要获取同时包含标签1、2、10和27的文章列表。通过SINTER命令很容易实现,此命令可以计算各集合的交集结果:
> sinter tag:1:news tag:2:news tag:10:news tag:27:news
... results here ...
除此之外,你也可以对集合数据进行并集计算,差集计算,或抽取随机元素等操作。
抽取元素可以使用SPOP命令,在某些应用场景下十分方便。例如,为了实现某种网络扑克游戏,您可能需要用集合来表示不同花色。假设我们对(c)lubs,(d)iamonds,(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命令可以随机移除一个集合元素,并返回给客户端,正好适用以上场景。
然而,如果直接使用此命令,在下一轮发牌时,需要再次填充花色,十分低效。为解决此问题,我们可以提前将一副牌的数据存储到一个固定的Redis集合deck
中,每次重新开始游戏时,拷贝deck
到一个新集合:game:1:deck
。
以上场景可以通过SUNIONSTORE命令实现,它的功能是将一个或多个集合内容合并后存储到一个新的集合。例如:
> sunionstore game:1:deck deck
(integer) 52
之后,我们就可以给第一个玩家发牌了:
> 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"
此时,我们介绍一个新的集合命令SCARD,你可以通过此命令获取集合中元素的数量(基数)。
> scard game:1:deck
(integer) 47
此时,计算一下,还剩52 - 5 = 47张牌
另一个典型场景是双色球抽奖。其过程需要从一堆标有数字的小球中随机抽取几个球作为中奖号码。此时,使用SRANDMEMBER命令可以方便的实现功能。该命令支持从集合中一次性随机抽取一定数量的元素,同时支持通过参数控制:允许重复返回元素。(一个帮助理解重复返回元素的场景是:抽奖时,将每次抽取的小球重新放回,之后的动作可能重复抽到同一个小球的情况)
redis> SADD myset one two three
(integer) 3
redis> SRANDMEMBER myset
"three"
redis> SRANDMEMBER myset 2
1) "one"
2) "two"
redis> SRANDMEMBER myset -5
1) "one"
2) "two"
3) "two"
4) "one"
5) "one"