主要内容
慢查询
生命周期
如图所示为客户端请求到Redis的完整生命周期:发送命令、排队、执行命令、返回结果
- 慢查询发生在第三阶段(也就是说其他阶段像排队耗时都不算)
- 客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因素
两个配置
-
slowlog-max-len
1.配置慢查询队列最大长度
2.筛选出的慢查询会进入一个先进先出队列
3.该队列是固定长度的
4.该队列是保存在内存中 -
slowlog-log-slower-than
1.慢查询阈值(单位:微秒),也就是说超过多少时间的查询是慢查询
2.slowlog-log-slower-than=0; 记录所有命令
3.slowlog-log-slower-than<0; 不记录任何命令
配置方法
- 默认值
config get slowlog-max-len = 128
config get slowlog-log-slower-than = 10000 - 修改配置文件重启(不建议,因为生产环境要尽量避免重启,在第一次启动redis前可以这么做)
- 动态配置
config set slowlog-max-len 1000
config set slowlog-log-slower-than 1000
三个命令
-
slowlog get [n]:
获取慢查询队列(n为可选参数,指定慢查询条数) -
slowlog len:
获取慢查询队列长度 -
slowlog reset:
清空慢查询队列
运维经验
-
slowlog-log-slower-than
不要设置过大,默认10ms,通常设置1ms -
slowlog-max-len
不要设置过小,通常设置1000左右 - 理解命令生命周期
- 定期持久化慢查询(方便查到历史慢查询操作)
pipeline
什么是流水线
对比: 1次网络命令通信模型 vs 批量网络命令通信模型
执行redis命令时间通常是非常快的,而网络则存在很多不稳定因素。另外redis虽然提供了mget、mset和hmget、hmset等这样的命令,但是如果我们需要同时执行get和hget命令需要怎么做呢,其实这就是流水线帮助我们实现的功能。
流水线就是将一批命令进行一个打包,而在服务端进行批量的计算,然后按顺序将结果返回给客户端,使用流水线可以大大节省网络的开销。
流水线的作用
两点说明:
1.redis的命令时间是微秒级别
2.pipeline每次条数要控制(网络)
分析一个极端例子,假设客户端和服务端相距1300公里,粗略计算命令传输时间为13毫秒,而命令执行时间只有微秒级,所以如果想要做批量操作而没有使用pipeline这样的功能,那redis的使用效率就不会很高。
客户端实现(jedis)
假设需要执行hset命令1万次,key也有1万个,这样的操作是无法使用hmset来完成的,因为hmset是只针对一个key。
我们先使用for循环进行批量命令操作,最终执行时间为50s,显然对于这样的时间是没办法接受的。
我们换成使用pipeline实现,每次执行100个命令,执行100次pipeline操作,最终时间只需要0.7秒,速度大大提升。
与原生M操作
与原生M操作对比,M操作是一个原子操作,只需要执行和计算一次,而pipeline是将命令进行打包,传送到redis时,则会拆分成子命令,结果会按顺序返回。
使用建议
- 注意每次pipeline携带数据量(数据量过大需合理拆分次数)
- pipeline每次只能作用在一个Redis节点上
- 明确M操作与pipeline区别
发布订阅
角色
角色主要有发布者(publisher)、订阅者(subscriber)、频道(channel)
发布者会发布消息到频道上,订阅者通过订阅频道来获取消息。
类似一些新闻APP(微信公众号),只要订阅了某些频道,这些频道有新消息发布订阅者就能收到消息。
模型
模型如图所示(类似生产者与消费者模型),对于订阅者而言其实也是个客户端,订阅者是可以订阅多个频道的,有个问题就是假如发布者已经发布了一条消息到频道中,但是一个新的订阅者是收不到之前已经发送过的消息的,使用时一定要注意这种场景,就是说redis并没有提供消息堆积的功能,所以无法获取历史消息。
相关API
发布消息:publish channel message
订阅频道:subscribe [channel]
返回信息:订阅了哪个频道,收到消息详细信息
取消订阅:unsubscribe [channel]
其他API,例如第一个命令根据pattern去匹配订阅,如: v*,匹配v开头的频道
消息队列
发布订阅是频道发送一条消息,订阅该频道的订阅者都能收到消息。而消息队列是抢模式,最终只有一个订阅者能抢到消息进行消费。
当然redis本身没有提供消息队列这样的功能,我们可以使用list实现,使用阻塞去拉取...
具体根据场景选择相应的模式,就是要搞清楚你的消费者是都需要收到还是只有一个收到。
Bitmap
位图
每个字符串对应的有其ASCII码,将ASCII码转换成对应的二进制,二进制每个数字代表位,即bit
bitmap
就是可以用来对位进行操作。
例如设置了一个键值后,我们可以通过key获取到对应的value,同时我们也可以获取每一位二进制数。
127.0.0.1:6379> set hello big
OK
127.0.0.1:6379> getbit hello 0
(integer) 0
127.0.0.1:6379> getbit hello 1
(integer) 1
相关命令
设置位图:setbit key offset value
最终效果,设置对应的位为对应的值,其他都用0补充
如果对刚才的user执行setbit key 50 1
,那么从19位到49位全部补0。这个过程本身会比较慢,所以在执行 setbit
命令时最好不要在很短的位图上做很大的偏移量。
获取位图:getbit key offset
范围获取:bitcount key [start end]
注意start和end指定的是字节,1个字节代表8位
如0 0代表第一个字节,即0-7位,1 2代表第二个字节到第三个字节,即8-23位
多位图操作:bitop op destkey key[key...]
op代表操作符,比如and、or,返回值为操作后的值字节长度
获取索引:bitpos key targetBit [start] [end]
独立用户统计
- 使用set和Bitmap
- 有1亿用户,5千万独立(每天有5千万人独立访问)
使用位图实现的思路就是,每个用户的ID占一位,比如用户的ID为10000,那我们就将key的第1万位设置为1。
当然这里内存只是个预估值,如果每天都要进行统计,使用位图还是能节省非常大的内存开销的。
那么是不是说在做这样的功能的时候 set
就完全不如 bitmap
好呢。其实是没有完美绝对的事情。
假设只有100万独立用户呢,可以看到使用set
会更节省内存,所以使用bitmap
也不是绝对的好
实际上位图的id不一定要和userid一样,两者之前可以有一定的偏移量。只要有规则就好,这样可以避免过多的空间浪费。
使用建议
- bitmap实际是string类型,type=string,值最大可以存储512MB,对于大部分的独立用户统计应该都可以满足
- 注意 setbit 时的偏移量,可能有较大耗时,如果偏移量过大可能造成redis服务器阻塞
- 位图不是绝对好,合理的场景选择合适的类型
HyperLogLog
- 基于HyperLogLog算法:使用极小空间完成独立数量统计
- 本质还是字符串:type hyperloglog_key (返回的是string)
三个命令
-
pfadd key element [element ...]
#向hyperloglog添加元素 -
pfcount key [key ...]
#计算hyperloglog的独立总数 -
pfmerge destkey sourcekey [sourcekey ...]
#合并多个hyperloglog
API使用示例一
API使用示例二
内存消耗(百万独立用户)
使用shell脚本实现百万用户添加,分1千次添加,每次加入1千条数据
#!/bin/bash
elements=""
key="2018_05_01:unique:ids"
for i in `seq 1 1000000`
do
elements="${elements} uuid-"${i}
if [[ $((i%1000)) == 0 ]]
then
/usr/local/redis/bin/redis-cli pfadd ${key} ${elements}
elements=""
fi
done
内存消耗情况如图,相对于使用bitmap或者set来说,消耗内存是非常的小。
使用经验
还是那句话,没有绝对完美的事情,在使用HyperLogLog
前考虑以下两点
- 数据是否容忍错误(错误率:0.81%)
- 是否需要单条数据?(hyperLogLog无法获取)
例如我们之前插入的百万数据,真实查询结果是有偏差的,并且再次插入新的值然后查询结果又会不一样。
GEO
redis3.2新特性,用来计算地理位置信息相关的功能
GEO(地理信息定位):存储经纬度,计算两地距离、范围计算等
应用场景
- 比如实现类似微信摇一摇这样的功能(社交)
- 计算周围酒店、餐馆功能(外卖)
相关API
首先给出5个城市的经纬度
添加地理位置:geoadd key lng lat member[lng lat member...]
获取地理位置:geopos key memeber[member...]
计算距离:geodist key member1 member2 [unit]
获取周边:georadius
示例:计算在北京150km以内的城市
相关说明
- since 3.2+(3.2版本提供)
- 类型实际上是zset,type geoKey = zset
- 没有删除API,zrem key member(直接使用zset的API)