Redis开发与运维读书笔记-第三章-实用功能介绍(二)

五.Bitmaps(位图)

1 数据结构模型

现代计算机用二进制(位)作为信息的基础单位,1个字节等于8位,例 如“big”字符串是由3个字节组成,但实际在计算机存储时将其用二进制表 示,“big”分别对应的ASCII码分别是98、105、103,对应的二进制分别是 01100010、01101001和01100111,如下图所示。

Redis提供了Bitmaps这个“数据结构”可以实现对位的操作。把数据结构加上引号主要因为:
·Bitmaps本身不是一种数据结构,实际上它就是字符串(如下图),但是它可以对字符串的位进行操作。
·Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符 串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的 每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量。

2 命令

将每个独立用户是否访问过网站存放在Bitmaps中,将访问的用户 记做1,没有访问的用户记做0,用偏移量作为用户的id。
1.设置值

setbit key offset value

设置键的第offset个位的值(从0算起),假设现在有20个用户, userid=0,5,11,15,19的用户对网站进行了访问,那么当前Bitmaps初始 化结果如下图所示。

具体操作过程如下,unique:users:2016-04-05代表2016-04-05这天的 独立访问用户的Bitmaps:

127.0.0.1:6379> setbit unique:users:2016-04-05 0 1 
(integer) 0 
127.0.0.1:6379> setbit unique:users:2016-04-05 5 1 
(integer) 0 
127.0.0.1:6379> setbit unique:users:2016-04-05 11 1 
(integer) 0 
127.0.0.1:6379> setbit unique:users:2016-04-05 15 1 
(integer) 0 
127.0.0.1:6379> setbit unique:users:2016-04-05 19 1 
(integer) 0

如果此时有一个userid=50的用户访问了网站,那么Bitmaps的结构变成了下图,第20位~49位都是0。

很多应用的用户id以一个指定数字(例如10000)开头,直接将用户id 和Bitmaps的偏移量对应势必会造成一定的浪费,通常的做法是每次做setbit 操作时将用户id减去这个指定数字。在第一次初始化Bitmaps时,假如偏移 量非常大,那么整个初始化过程执行会比较慢,可能会造成Redis的阻塞。
2.获取值

getbit key offset

获取键的第offset位的值(从0开始算),下面操作获取id=8的用户是否 在2016-04-05这天访问过,返回0说明没有访问过:

127.0.0.1:6379> getbit unique:users:2016-04-05 8 
(integer) 0

由于offset=1000000根本就不存在,所以返回结果也是0:

127.0.0.1:6379> getbit unique:users:2016-04-05 1000000 
(integer) 0

3.获取Bitmaps指定范围值为1的个数

bitcount [start][end]

下面操作计算2016-04-05这天的独立访问用户数量:

127.0.0.1:6379> bitcount unique:users:2016-04-05 
(integer) 5

[start]和[end]代表起始和结束字节数,下面操作计算用户id在第1个字节 到第3个字节之间的独立访问用户数,对应的用户id是11,15,19。

127.0.0.1:6379> bitcount unique:users:2016-04-05 1 3 
(integer) 3

4.Bitmaps间的运算

bitop op destkey key[key....] 

bitop是一个复合操作,它可以做多个Bitmaps的and(交集)、or(并 集)、not(非)、xor(异或)操作并将结果保存在destkey中。假设201604-04访问网站的userid=1,2,5,9,如下图所示。

下面操作计算出2016-04-04和2016-04-03两天都访问过网站的用户数量,如下图所示。

127.0.0.1:6379> bitop and unique:users:and:2016-04-04_03 unique: users:2016-04-03     unique:users:2016-04-03 
(integer) 2 
127.0.0.1:6379> bitcount unique:users:and:2016-04-04_03 
(integer) 2

如果想算出2016-04-04和2016-04-03任意一天都访问过网站的用户数量 (例如月活跃就是类似这种),可以使用or求并集,具体命令如下:

127.0.0.1:6379> bitop or unique:users:or:2016-04-04_03 unique:    users:2016-04-03 unique:users:2016-04-03 
(integer) 2 
127.0.0.1:6379> bitcount unique:users:or:2016-04-04_03 
(integer) 6

5.计算Bitmaps中第一个值为targetBit的偏移量

bitpos key targetBit [start] [end]

下面操作计算2016-04-04当前访问网站的最小用户id:

127.0.0.1:6379> bitpos unique:users:2016-04-04 1 
(integer) 1

除此之外,bitops有两个选项[start]和[end],分别代表起始字节和结束字 节,例如计算第0个字节到第1个字节之间,第一个值为0的偏移量,从上图可以得知结果是id=0的用户。

127.0.0.1:6379> bitpos unique:users:2016-04-04 0 0 1 
(integer) 0

六.HyperLogLog

HyperLogLog并不是一种新的数据结构(实际类型为字符串类型),而 是一种基数算法,通过HyperLogLog可以利用极小的内存空间完成独立总数 的统计,数据集可以是IP、Email、ID等。HyperLogLog提供了3个命令: pfadd、pfcount、pfmerge。例如2016-03-06的访问用户是uuid-1、uuid-2、 uuid-3、uuid-4,2016-03-05的访问用户是uuid-4、uuid-5、uuid-6、uuid-7,如下图所示。

1.添加(pfadd key element [element …])

pfadd用于向HyperLogLog添加元素,如果添加成功返回1:

127.0.0.1:6379> pfadd 2016_03_06:unique:ids "uuid-1" "uuid-2" "uuid-3" "uuid-4" 
(integer) 1

2.计算独立用户数

pfcount key [key …]

pfcount用于计算一个或多个HyperLogLog的独立总数,例如 2016_03_06:unique:ids的独立总数为4:

127.0.0.1:6379> pfcount 2016_03_06:unique:ids 
(integer) 4

如果此时向2016_03_06:unique:ids插入uuid-1、uuid-2、uuid-3、uuid90,结果是5(新增uuid-90):

127.0.0.1:6379> pfadd 2016_03_06:unique:ids "uuid-1" "uuid-2" "uuid-3" "uuid-90" 
(integer) 1 
127.0.0.1:6379> pfcount 2016_03_06:unique:ids 
(integer) 5

当前这个例子内存节省的效果还不是很明显,下面使用脚本向 HyperLogLog插入100万个id,插入前记录一下info memory:

127.0.0.1:6379> info memory 
# Memory 
used_memory:835144 
used_memory_human:815.57K ...
向2016_05_01:unique:ids插入100万个用户,每次插入1000条: 
elements="" 
key="2016_05_01:unique:ids" 
for i in `seq 1 1000000`
do    
    elements="${elements} uuid-"${i}    
    if [[ $((i%1000))  == 0 ]];    
    then        
        redis-cli pfadd ${key} ${elements}        
        elements=""    
    fi 
done

当上述代码执行完成后,可以看到内存只增加了15K左右:

127.0.0.1:6379> info memory 
# Memory 
used_memory:850616 
used_memory_human:830.68K

但是,同时可以看到pfcount的执行结果并不是100万:

127.0.0.1:6379> pfcount 2016_05_01:unique:ids 
(integer) 1009838

可以对100万个uuid使用集合类型进行测试,代码如下:

elements="" 
key="2016_05_01:unique:ids:set" 
for i in `seq 1 1000000` 
do    
    elements="${elements} "${i}    
    if [[ $((i%1000))  == 0 ]];    
    then        
        redis-cli sadd ${key} ${elements}        
        elements=""     
    fi 
done

可以看到内存使用了84MB:

127.0.0.1:6379> info memory 
# Memory 
used_memory:88702680 
used_memory_human:84.59M

但独立用户数为100万:

127.0.0.1:6379> scard 2016_05_01:unique:ids:set 
(integer) 1000000

可以看到,HyperLogLog内存占用量小得惊人,但是用如此小空间来估算如此巨大的数据,必然不是100%的正确,其中一定存在误差率。Redis官 方给出的数字是0.81%的失误率。

3.合并

pfmerge destkey sourcekey [sourcekey ...]

pfmerge可以求出多个HyperLogLog的并集并赋值给destkey,例如要计算 2016年3月5日和3月6日的访问独立用户数,可以按照如下方式来执行,可以 看到最终独立用户数是7:

127.0.0.1:6379> pfadd 2016_03_06:unique:ids "uuid-1" "uuid-2" "uuid-3" "uuid-4" 
(integer) 1 
127.0.0.1:6379> pfadd 2016_03_05:unique:ids "uuid-4" "uuid-5" "uuid-6" "uuid-7" 
(integer) 1 
127.0.0.1:6379> pfmerge 2016_03_05_06:unique:ids 2016_03_05:unique:ids  2016_03_06:unique:ids 
OK 
127.0.0.1:6379> pfcount 2016_03_05_06:unique:ids 
(integer) 7

HyperLogLog内存占用量非常小,但是存在错误率,开发者在进行数据结构选型时只需要确认如下两条即可:
·只为了计算独立总数,不需要获取单条数据。
·可以容忍一定误差率,毕竟HyperLogLog在内存的占用量上有很大的优势。

七.发布订阅

Redis提供了基于“发布/订阅”模式的消息机制,此种模式下,消息发布 者和订阅者不进行直接通信,发布者客户端向指定的频道(channel)发布消 息,订阅该频道的每个客户端都可以收到该消息,如下图所示。Redis提供了若干命令支持该功能,在实际应用开发时,能够为此类问题提供实现方法。

1.命令

Redis主要提供了发布消息、订阅频道、取消订阅以及按照模式订阅和取消订阅等命令。
1.发布消息(publish channel message)

下面操作会向channel:sports频道发布一条消息“Tim won the championship”,返回结果为订阅者个数,因为此时没有订阅,所以返回结果 为0:

127.0.0.1:6379> publish channel:sports "Tim won the championship" 
(integer) 0

2.订阅消息(subscribe channel [channel ...])

订阅者可以订阅一个或多个频道,下面操作为当前客户端订阅了 channel:sports频道:

127.0.0.1:6379> subscribe channel:sports 
Reading messages... (press Ctrl-C to quit) 
1) "subscribe" 
2) "channel:sports" 
3) (integer) 1

此时另一个客户端发布一条消息:

127.0.0.1:6379> publish channel:sports "James lost the championship" 
(integer) 1

当前订阅者客户端会收到如下消息:

127.0.0.1:6379> subscribe channel:sports 
Reading messages... (press Ctrl-C to quit) ... 
1) "message" 
2) "channel:sports" 
3) "James lost the championship"

如果有多个客户端同时订阅了channel:sports,整个过程如下图所示。

有关订阅命令有两点需要注意:
·客户端在执行订阅命令之后进入了订阅状态,只能接收subscribe、 psubscribe、unsubscribe、punsubscribe的四个命令。
·新开启的订阅客户端,无法收到该频道之前的消息,因为Redis不会对发布的消息进行持久化。

和很多专业的消息队列系统(例如Kafka、RocketMQ)相比,Redis的发布订阅略显粗糙,例如无法实现消息堆积和回溯。但胜在足够简单,如果当前场景可以容忍的这些缺点,也不失为一个不错的选择。
 

3.取消订阅(unsubscribe [channel [channel ...]])

客户端可以通过unsubscribe命令取消对指定频道的订阅,取消成功后,不会再收到该频道的发布消息:

127.0.0.1:6379> unsubscribe channel:sports 
1) "unsubscribe" 
2) "channel:sports" 
3) (integer) 0

4.按照模式订阅和取消订阅(psubscribe pattern [pattern...]     |      punsubscribe [pattern [pattern ...]])

127.0.0.1:6379> psubscribe it* Reading messages... (press Ctrl-C to quit) 
1) "psubscribe" 
2) "it*"
3) (integer) 1

5.查询订阅
(1)查看活跃的频道(pubsub channels [pattern])

所谓活跃的频道是指当前频道至少有一个订阅者,其中[pattern]是可以指定具体的模式:

127.0.0.1:6379> pubsub channels 
1) "channel:sports" 
2) "channel:it" 
3) "channel:travel" 
127.0.0.1:6379> pubsub channels channel:*r* 
1) "channel:sports" 
2) "channel:travel"

(2)查看频道订阅数(pubsub numsub [channel ...])

当前channel:sports频道的订阅数为2:

127.0.0.1:6379> pubsub numsub channel:sports 
1) "channel:sports" 
2) (integer) 2

(3)查看模式订阅数(pubsub numpat)

当前只有一个客户端通过模式来订阅:

127.0.0.1:6379> pubsub numpat 
(integer) 1

八. GEO

Redis3.2版本提供了GEO(地理信息定位)功能,支持存储地理位置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能,对于需 要实现这些功能的开发者来说是一大福音。GEO功能是Redis的另一位作者Matt Stancliff借鉴NoSQL数据库Ardb实现的,Ardb的作者来自中国,它提供了优秀的GEO功能。
本部分不展开叙述,可前往如下地址学习了解:[1] https://matt.sh/ [2] https://github.com/yinqiwen/ardb[3] https://en.wikipedia.org/wiki/Geohash

第三章小结:

1)慢查询中的两个重要参数slowlog-log-slower-than和slowlog-maxlen。
2)慢查询不包含命令网络传输和排队时间。
3)有必要将慢查询定期存放。
4)redis-cli一些重要的选项,例如--latency、–-bigkeys、-i和-r组合。
5)redis-benchmark的使用方法和重要参数。
6)Pipeline可以有效减少RTT次数,但每次Pipeline的命令数量不能无节制。
7)Redis可以使用Lua脚本创造出原子、高效、自定义命令组合。
8)Redis执行Lua脚本有两种方法:eval和evalsha。
9)Bitmaps可以用来做独立用户统计,有效节省内存。
10)Bitmaps中setbit一个大的偏移量,由于申请大量内存会导致阻塞。
11)HyperLogLog虽然在统计独立总量时存在一定的误差,但是节省的内存量十分惊人。
12)Redis的发布订阅机制相比许多专业的消息队列系统功能较弱,不具备堆积和回溯消息的能力,但胜在足够简单。
13)Redis3.2提供了GEO功能,用来实现基于地理位置信息的应用,但 底层实现是zset。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值