本章介绍数据类型:Set, Sorted Set, Bitmap, HyperLogLog。
Set是未排序的,无重复值的String集合。内部一年hash table实现,因此如成员的增删查都是固定时间,O(1)数量级。
如果Set的成员都是Integer,内存还可以优化。
一个Set最多存储232-1成员,约40亿。
Set的使用场景包括:
* 数据过滤:如从某城市出发到另一城市的航班
* 数据分组: 查看近似产品的用户,如Amazon.com的推荐系统
* 成员检查: 某用户是否在白名单/黑名单中
推荐阅读:
使用Set过滤数据: http://robots.thoughtbot.com/redis-set-intersection-using-sets-to-filter-data.
这是一个航班时刻表查询程序,有多个集合,所有航班集合,按出发城市的集合,按到达城市的集合。然后通过这些集合的操作,得到所需的结果,如指定发到站的航班,指定出发时间段的航班等。
文章中有段话说的很好:
Redis is a fantastic lightweight key-value store. It also acts as a data structure server for storing data types like lists, sets, and hashes.
下面就是些集合操作了,交集,并集,差集…
SADD可为Set添加一个或多个成员,返回添加的成员数目,如成员已存在则忽略。
$ redis-cli
127.0.0.1:6379> SADD user:john:favorite_fruits "Apple" "Peach" "Banana" "Lemon"
(integer) 4
127.0.0.1:6379> SADD user:danny:favorite_fruits "Apple" "Melon" "Lemon"
(integer) 3
127.0.0.1:6379> SADD user:rose:favorite_fruits "Apple" "Melon" "Pear"
(integer) 3
SINTER返回多个个Set的交集:
127.0.0.1:6379> SINTER user:john:favorite_fruits user:danny:favorite_fruits
1) "Lemon"
2) "Apple"
127.0.0.1:6379> SINTER user:john:favorite_fruits user:danny:favorite_fruits user:rose:favorite_fruits
1) "Apple"
SDIFF返回多个Set的差集(存在于第一个Set,在余下Set中不存在),结果和key的顺序有关:
127.0.0.1:6379> SDIFF user:john:favorite_fruits user:danny:favorite_fruits
1) "Peach"
2) "Banana"
127.0.0.1:6379> SDIFF user:danny:favorite_fruits user:john:favorite_fruits
1) "Melon"
127.0.0.1:6379> SDIFF user:john:favorite_fruits user:danny:favorite_fruits user:rose:favorite_fruits
1) "Peach"
2) "Banana"
SUNION返回多个Set的并集,并集中没有重复值
127.0.0.1:6379> SUNION user:danny:favorite_fruits user:john:favorite_fruits
1) "Melon"
2) "Lemon"
3) "Apple"
4) "Peach"
5) "Banana"
127.0.0.1:6379> SUNION user:john:favorite_fruits user:danny:favorite_fruits user:rose:favorite_fruits
1) "Pear"
2) "Lemon"
3) "Apple"
4) "Peach"
5) "Banana"
6) "Melon"
SRANDMEMBER返回Set中的随机成员。
127.0.0.1:6379> SRANDMEMBER user:john:favorite_fruits
"Banana"
SISMEMBER判断成员是否属于Set,1表示True,0表示False
127.0.0.1:6379> SISMEMBER user:john:favorite_fruits "Banana"
(integer) 1
127.0.0.1:6379> SISMEMBER user:john:favorite_fruits "banana"
(integer) 0
SREM从Set中删除成员,SCARD返回集合的成员数(cardinality)
127.0.0.1:6379> SREM user:john:favorite_fruits "Banana"
(integer) 1
127.0.0.1:6379> SISMEMBER user:john:favorite_fruits "Banana"
(integer) 0
127.0.0.1:6379> SCARD user:john:favorite_fruits
(integer) 3
SMEMBERS返回集合的所有成员
127.0.0.1:6379> SMEMBERS user:john:favorite_fruits
1) "Peach"
2) "Lemon"
3) "Apple"
Yipit, Groupon, 和 LivingSocial每天都给用户发送邮件,其中包括折扣券等用户感兴趣的内容,发送的内容(deal)与用户的地区和偏好相关。
在本例中, 有两个Set,key是dealID,values是Sets,包括需要发送此DealID的userID列表。
Sorted Set与Set类似, 只不过每个成员多了个score,并根据其排序。和Set一样,成员不允许重复,但score可以一样。
Sorted Set 操作不如Set快,因为需要比较score, Sorted Set成员的增删改运行时间为O(log(N)), N为Sorted Set的成员数。Sorted Set的内部实现为:
A skip list with a hash table. A skip list is a data structure that allows fast search within an ordered sequence of elements.
A ziplist, based on the zset-max-ziplist-entries and zset-max-ziplist-value configurations.
Sorted Sets可用于:
* 构建实时客服等待队列
* 在线游戏积分榜,显示排名靠前的选手和积分,或你的队友的积分
* 单词拼写自动完成系统
ZADD:增加一个或多个成员到Sorted Set, 如果field相同,score不同,则更新score
$ redis-cli
127.0.0.1:6379> ZADD math 99 john
(integer) 1
127.0.0.1:6379> ZADD math 100 john
(integer) 0
127.0.0.1:6379> ZADD math 99 rose
(integer) 1
127.0.0.1:6379> ZADD math 98 richard 96 tom
(integer) 2
127.0.0.1:6379> ZADD math 99 jean
(integer) 1
由于每一个成员由一个String和一个score组成,排序也有两个标准,如果score相同,则按字符顺序排序String.
从Sorted Set中取某区间的数据并排序的命令包括:ZRANGE, ZRANGEBYLEX, ZRANGEBYSCORE, ZREVRANGE, ZREVRANGEBYLEX, and ZREVRANGEBYSCORE。
这些命令都需要指定起始index, key,唯一的区别是排序结果不同。
127.0.0.1:6379> ZRANGE math 0 -1
1) "tom"
2) "richard"
3) "jean"
4) "rose"
5) "john"
127.0.0.1:6379> ZREVRANGE math 0 -1
1) "john"
2) "rose"
3) "jean"
4) "richard"
5) "tom"
也可以指定WITHSCORES参数,让结果显示score
127.0.0.1:6379> ZREVRANGE math 0 -1 WITHSCORES
1) "john"
2) "100"
3) "rose"
4) "99"
5) "jean"
6) "99"
7) "richard"
8) "98"
9) "tom"
10) "96"
ZREM删除Sorted Set成员:
127.0.0.1:6379> ZREM math richard
(integer) 1
127.0.0.1:6379> ZREVRANGE math 0 -1
1) "john"
2) "rose"
3) "jean"
4) "tom"
可使用ZSCORE和ZRANK/ZREVRANK得到某成员在Sorted Set中的排名和积分, 排名最低的返回0
127.0.0.1:6379> ZSCORE math rose
"99"
127.0.0.1:6379> ZRANK math rose
(integer) 2
127.0.0.1:6379> ZREVRANK math rose
(integer) 1
查看key的类型和Sorted Set相关命令
127.0.0.1:6379> TYPE math
zset
127.0.0.1:6379> help @sorted_set
此例较简单,略过。
Bitmap (bit arrays 或 bitsets)实际上针对String的一些bit操作,本质上不算数据类型。Redis提供了一系列Bitmap操作。
127.0.0.1:6379> type visits:2015-01-01
string
127.0.0.1:6379> help setbit
SETBIT key offset value
summary: Sets or clears the bit at offset in the string value stored at key
since: 2.2.0
group: string
Bitmap是存储了0或1的bit序列,存储效率很高(因其紧凑,占用空间少),支持快速查询。可存储232 bit。
Bitmap非常适合于实时分析,例如某用户是否做了某操作,多少用户做了这些操作等。
SETBIT设置offset为index的值为0或1。index从0开始。
127.0.0.1:6379> SETBIT visits:2015-01-01 10 1
(integer) 0
127.0.0.1:6379> SETBIT visits:2015-01-01 15 1
(integer) 0
127.0.0.1:6379> SETBIT visits:2015-01-02 10 1
(integer) 0
127.0.0.1:6379> SETBIT visits:2015-01-02 11 1
(integer) 0
GETBIT获取某一位置的值。
127.0.0.1:6379> GETBIT visits:2015-01-01 10
(integer) 1
127.0.0.1:6379> GETBIT visits:2015-01-02 15
(integer) 0
BITCOUNT计算Bitmap中1的个数。
127.0.0.1:6379> BITCOUNT visits:2015-01-01
(integer) 2
127.0.0.1:6379> BITCOUNT visits:2015-01-02
(integer) 2
BITOP执行bitmap操作,如OR, AND, XOR, NOT,并将结果存入目标key中。
127.0.0.1:6379> BITOP OR total_users visits:2015-01-01 visits:2015-01-02
(integer) 2
127.0.0.1:6379> BITCOUNT total_users
(integer) 3
彩票的场景也比较适合使用Bitmap,例如对于六合彩,可以统计你的选择和开奖结果中相同的号码个数
127.0.0.1:6379> SETBIT lottery:select 10 1
(integer) 0
127.0.0.1:6379> SETBIT lottery:select 12 1
(integer) 0
127.0.0.1:6379> SETBIT lottery:select 14 1
(integer) 0
127.0.0.1:6379> SETBIT lottery:select 19 1
(integer) 0
127.0.0.1:6379> SETBIT lottery:select 22 1
(integer) 0
127.0.0.1:6379> SETBIT lottery:select 26 1
(integer) 0
127.0.0.1:6379> bitcount lottery:select
(integer) 6
127.0.0.1:6379> SETBIT lottery:result 6 1
(integer) 0
127.0.0.1:6379> SETBIT lottery:result 7 1
(integer) 0
127.0.0.1:6379> SETBIT lottery:result 10 1
(integer) 0
127.0.0.1:6379> SETBIT lottery:result 14 1
(integer) 0
127.0.0.1:6379> SETBIT lottery:result 22 1
(integer) 0
127.0.0.1:6379> SETBIT lottery:result 24 1
(integer) 0
127.0.0.1:6379> BITOP AND lottery:same lottery:result lottery:select
(integer) 4
127.0.0.1:6379> bitcount lottery:same
(integer) 3
本例计算网站在某时间段内的访问量,并列举出这些用户。此应用只能显示是否访问,但访问了多少次无法得到。
本例中key为’visits:daily:’ + date,userID为数字型,表示index
计算访问用户的总数比较简单,但列举哪些用户进行了访问比较麻烦,需要遍历整个Bitmap。
严格来说,HyperLogLog不是一种数据类型,而是一种算法,可以估算出Set中的唯一元素的数量,计算速度快,消耗内存少,但注意是估算,尽管准确率较高,还是会有误差。
关于HyperLogLog算法的描述参见: The analysis of a near-optimal cardinality estimation algorithm (Philippe Flajolet, Éric Fusy, Olivier Gandouet, and Frédéric Meunier)
HyperLogLogs就三个命令: PFADD, PFCOUNT, and PFMERGE。前缀PF表示算法的发明人Philippe Flajolet,他于2011年3月去世。
HyperLogLogs的优缺点如下:
Usually, to perform unique counting, you need an amount of memory proportional to the number of items in the set that you are counting. HyperLogLogs solve these kinds of problems with great performance, low computation cost, and a small amount of memory. However, it is important to remember that HyperLogLogs are not 100 percent accurate. Nonetheless, in some cases, 99.19 percent is good enough.
HyperLogLogs可用于:
* 访问网站的用户数
* 在特定时间段在网站搜索的不同的词语数。
* 用户使用的不同的hashtags数
* 书中出现的不同的单词数
场景为统计每小时内访问某网页的不同用户数,我们用HyperLogLog和Set做个比较。
用户ID用32字节的UUID表示,每一个小时需要一个key,例如visit:date:hour,每天需要24个key,每月720个key。
假设每小时有100000个不同用户的访问,则对于Set,每天需要100000 × 32 × 24 = 76.8MB,每月需要2.25GB。
而HyperLogLog存100000个用户的访问最多需要12kB,这样对应的每天需要288KB,每月需要8.4MB。但问题是:为何HyperLogLog只用12k? 难道是12K=12 x 1024 x 8 约等于100000?
PFADD可以添加一个或多个唯一值到HyperLogLog。
$ redis-cli
127.0.0.1:6379> PFADD visits:2015-01-01 "carl" "max" "hugo" "arthur"
(integer) 1
127.0.0.1:6379> PFADD visits:2015-01-01 "max" "hugo"
(integer) 0
127.0.0.1:6379> PFADD visits:2015-01-02 "max" "kc" "hugo" "renata"
(integer) 1
PFCOUNT可以计算单个集合中唯一值的个数,也可以对多个集合进行UNION操作:
127.0.0.1:6379> PFCOUNT visits:2015-01-01
(integer) 4
127.0.0.1:6379> PFCOUNT visits:2015-01-02
(integer) 4
127.0.0.1:6379> PFCOUNT visits:2015-01-01 visits:2015-01-02
(integer) 6
127.0.0.1:6379> type visits:2015-01-01
string
PFMERGE将集合合并的结果存入目标key。
127.0.0.1:6379> PFMERGE visits:total visits:2015-01-01 visits:2015-01-02
OK
127.0.0.1:6379> PFCOUNT visits:total
(integer) 6
略
本章讲述了高级数据类型: Sets, Sorted Sets, Bitmaps, 和 HyperLogLogs. 并演示了它们在实际场景中的运用,这些都是Redis的优势,但选择合适的数据类型对应具体的问题也很重要。