本章将介绍以下内容:
慢查询分析:通过慢查询分析,找到有问题的命令进行优化
Redis Shell:功能强大的Redis Shell会有意想不到的使用功能
Pipeline:通过Pipeline机制有效提供客户端性能
事务与Lua:制作自己的专属原子命令
Bitmaps:通过在字符串数据结构上使用位操作,有效节省内存,为开发提供新的思路
HyperLogLog:一种基于概率的新算法,难以想象地节省内存空间
GEO:Redis3.2 提供了基于地理位置信息的功能
3.1 慢查询分析
慢查询日志:系统在命令执行前后计算每条命令的执行时间,当超过预设阀值,就将这条命令的相关信息记录下来
Redis客户端执行一条命令:
3.1.1 慢查询的两个配置参数
预设阀值设置:slowlog-log-slower-than和slowlog-max-len配置来解决这两个问题。
Redis两种修改配置的方法:一种是修改配置文件,另一种是使config set命令动态修改
config set slowlog-log-slower-than 20000
config set slowlog-max-len 1000
config rewrite
1 获取慢查询日志 slowlog get
2 获取慢查询日志的长度 slowlog len
3 慢查询日志重置 slowlog reset
3.1.2 最佳实践
slowlog-max-len配置建议:线上建议调大慢查询列表,记录慢查询时Redis会对长命令截断操作,并不会占用大量内存。增大慢查询列表可以减慢查询被剔除的可能,可设置为1000以上
slowlog-log-slower-than配置建议:默认值超过10ms判定为慢查询,需要根据Redis并发量调整该值。
慢查询只记录命令的执行时间,并不包含命令排队和网络传输时间。
由于慢查询日志是一个先进先出的队列,慢查询比较多的时候,会丢失部分慢查询命令,可以定期执行slow get命令将慢查询日志持久化到其他存储中,然后制作可视化的界面进行查询
3.2 Redis Shell
3.2.1 redis-cli详解
3.2.2 redic-server详解
3.2.3 redis-benchmark详解
3.3 Pipeline
3.3.1 Pipeline概念
Redis客户端执行一条命令分为如下四个过程:1 发送命令 2 命令排队 3 命令执行 4 返回结果 RTT往返时间
Pipeline 将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令执行的结果按顺序返回给客户端
redis-cli的--pipe选项实际上就是使用了Pipeline机制,
3.3.2 性能测试
Pipeline执行速度一般比逐条执行要快
客户端和服务端的网络延时越大,Pipe的效果越明显
3.3.3 原生批量命令与Pipeline对比
原生批命令是原子的,Pipeline是非原子的
原生批命令是一个命令对应多个key,Pipeline支持多个命令
原生批命令是Redis服务端支持实现的,而Pipeline需要服务端和客户端的共同实现
3.3.4 最佳实践
Pipe虽然好用,但是每次Pipeline组装的命令个数不能没有节制,否则一次组装的Pipeline数据量过大,一方面会增加客户端的等待时间,另一方面会造成一定的网络阻塞,可以将一次包含大量命令的Pipeline拆分成多次小的Pipeline来完成
3.4 事务与Lua
为了保证多条命令组合的原子性,Redis提供了简单的事务功能以及集成Lua脚本来解决这个问题
3.4.1 事务
Redis提供了简单的事务功能,将一组需要一起执行的命令放到multi和exec两个命令之间。停止事务的执行,可以使用discard命令代替exec命令即可。Redis不支持事务中的回滚特性
3.4.2 Lua用法简述
1 数据类型及其逻辑处理
Lua语言提供了几种数据类型:booleans(布尔)、numbers(数值)、strings(字符串)、table(表格)
一 字符串
local strings val="world"
local代表val是一个局部变量,没有代表全局变量
--是Lua的注释
二 数组
lua的数组下标从1开始计算
local table myArray={"redis","jedis",true,88.0}
print(myArray[3])
for
关键字for以end作为结束符
local int sum=0
for i=1,100
do
sum=sum+i;
end
print(sum)
要遍历myArray,首先需要知道tables长度,只需要在变量前加一个#
Lua还提供了内置函数ipairs,使用for index,value ipairs(tables)可以遍历出所有索引下标和值
for index.value in ipairs(myArray)
do
print(index)
print(value)
end
while循环
local int sum=0
local int i=0
while i<=100
do
sum=sum+i
i=i+1
end
print(sum)
if else
要确定数组中是否包含了jedis,有则打印true
local table myArray={"redis","jedis",true}
for i=1,#myArray
do
if myArray[i]=="jedis"
then
print(true)
break
else
do nothing
end
end
三 哈希
local tables user_1={age=28,name="tom"}
print("user_1 age is"..user_1["age"])
2 函数定义
函数以function开头,以end结尾,funcName是函数名
function funcName()
end
function contact(str1,str2)
return str1..str2
end
3.4.3 Redis与Lua
1 在Redis中使用Lua
在Redis中执行Lua脚本两种方法:eval和evalsha
1 eval 脚本内容 key个数 key列表 参数列表 如果Lua脚本较长,还可以使用redis-cli-eval直接执行文件
2 evalsha
`
加载脚本:script load命令可以将脚本内容加载到Redis内存中
执行脚本:evalsha的使用方法,参数使用SHA1值,执行逻辑和eval一致
2 Lua的Redis API
Lua可以使用redis.call函数实现对Redis的访问。
redis.pcall会忽略错误继续执行
开发提示:Lua可以使用redis.log函数将Lua脚本的日志输出到Redis的日志文件中,但是一定要控制日志级别
3.4.4 案例
三个好处:Lua脚本在Redis是原子执行的,执行过程中间不会插入其他命令,Lua脚本可以帮助开发和运维人员创造出自己定制的命令,并可以将这些命令常驻在Redis内存中,实现复用的效果,可以将多条命令一次性打包,有效的减少网络开销
local mylist=redis.call{"lrange",KEYS[1],0,-1}
local count=0
for index,key in ipairs(mylist)
do
redis.call("incr",key)
count=count+1
end
return count
3.4.5 Redis如何管理Lua脚本
Redis提供了4个命令实现对Lua脚本的管理
1 script load script 将Lua脚本加载到Redis内存中
2 script exists 用于判断sha1是否已经加载到Redis内存中
3 script flush 清除Redis内存已经加载的所有Lua脚本
4 script kill 用于杀掉正在执行的Lua脚本
3.5 Bitmaps
3.5.1 数据结构模型
Redis提供了Bitmaps这个“数据结构”可以实现对位的操作
Bitmaps本身不是一种数据结构,实际上它就是字符串,但是它可以对字符串进行位操作
Bitmaps单独提供了一套命令,所以在Redis中使用字符串的方法不太相同
3.5.2 命令
将每个独立用户是否访问过网站存放在Bitmaps中,将访问过的用户记做1,没有访问的用户记做0,用偏移量作为用户的id
1 设置值 setbit key offset value setbit unique:user:2016-04-05 0 1
2 获取值 gitbit key offset
3 获取Bitmaps制定范围值的个数 bitcount unique:users:2016-04-05
4 Bitmaps间的运算 bitop op destkey key
5 计算Bitmaps中第一个值为targetBit的偏移量 bitpos key targetBit的偏移量
3.5.3 Bitmaps分析
数据量大的情况下,使用Bitmaps比集合要好
3.6 HyperLogLog
是一种基数算法,可以利用极小的内存空间完成独立总数的统计,数据集可以是IP、Email、ID等
1 添加 pfadd key element
pfadd 2016_03_06:unque:ids "uuid-1" "uuid-2" "uuid-3" "uuid-3" "uuid-4"
2 计算独立用户数 pfcount key
pfcount 2016_03_06:unque:ids
3 合并 pfmerge destkey sourcekey
pfadd 2016_03_06:unque:ids "uuid-1" "uuid-2" "uuid-3" "uuid-4" "uuid-5"
pfadd 2016_03_06:unque:ids "uuid-6" "uuid-7"
pfmerge 2016_03_06:unque:ids 2016_03_06:unque:ids 2016_03_05:unque:ids
只为了计算独立总数,不需要获取单条数据,可以容忍一定误差率,毕竟在内存占有量上有很大的优势
3.7 发布订阅
Redis提供了基于发布订阅模式的消息机制,消息发布和订阅者不进行直接通信,发布者客户端向指定的频道(channel)发布消息,订阅该频道的每个客户度都可以收到该消息
3.7.1 命令
1 发布消息 publish channel message
2 订阅消息 subscribe channel 订阅者可以订阅一个或多个频道
注:客户端在执行订阅命令之后进入了订阅状态:只能接收subscribe、psbuscribe、unsubscribe、punsubscribe的四个命令,新开启的订阅客户端,无法收到该频道之前的消息,因为Redis不会对发布的消息进行持久化
3 取消订阅 unsubscribe channel
4 暗示模式订阅和取消订阅 psubscribe pattern
5 查询订阅
查看活跃的频道 pubsub channels
查看频道订阅数 pubsub numsub channel
查看模式订阅数 pubsbu numpat
3.7.2 使用场景
视频服务订阅到video:changes频道下 subscribe video:changes
视频管理系统发布消息到video:changes频道如下 publish video:changes "video1,video2,video3"
视频服务收到消息,对视频信息进行更新 for video in video1,video2,video3
update{video}
3.8 GEO
1 增加地理位置信息 geoadd key longitude latitude member 分别是经度,纬度,成员
2 获取地理位置信息 geopos key member
3 获取两个地理位置的距离 geodist key member1 member2 unit
4 获取指定位置范围内的地理信息位置集合
5 获取geohash geohash key member
Redis使用geohash将二维经纬度转换为一维字符串,下面操作会返回beijing的geohash值
geohash有如下特点:
GEO的数据类型为zset,Redis将所有地理位置信息的geohash存放在zset中、
字符串越长,表示的位置更精确
两个字符串越相似,它们之间的距离越近,Redis利用字符串前缀匹配算法实现相关的命令
geohash编码和经纬度是可以互相转换的
6 删除地理位置信息 zrem key member
因为GEO的底层实现是zset,所以借用zrem命令实现对地理位置信息的删除
3.9 本章重点回顾