1、慢查询分析
所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间,当超过预设阀值,就将这条命令的相关信息(例如:发生时间,耗时,命令的详细信息)记录下来,Redis也提供了类似的功能。
注意,慢查询只统计执行命令的时间,所以没有慢查询并不代表客户端没有超时问题(网络延时、服务端待处理命令较多等等)。
2、慢查询的两个配置参数
1)在Redis 中有两种修改配置的方法,一种是修改配置文件,另一种是使用config set命令动态修改。
// 将slowlog-log-slower-than设置为20000微秒,slowlog-max-len设置为1000:
config set slowlog-log-slower-than 20000
config set slowlog-max-len 1000
config rewrite // // 将配置持久化到本地配置文件
2)实现对慢查询日志的访问和管理的命令
1、获取慢查询日志
slowlog get [n]
127.0.0.1:6379> slowlog get
1) 1) (integer) 666
2) (integer) 1456786500
3) (integer) 11615
4) 1) "BGREWRITEAOF"
2) 1) (integer) 665
2) (integer) 1456718400
3) (integer) 12006
4) 1) "SETEX"
2) "video_info_200"
3) "300"
4) "2"
...
2、获取慢查询日志列表当前的长度
slowlog len
127.0.0.1:6379> slowlog len
(integer) 45
3、慢查询日志重置(实际是对列表做清理操作)
slowlog reset
127.0.0.1:6379> slowlog len
(integer) 45
127.0.0.1:6379> slowlog reset
OK
127.0.0.1:6379> slowlog len
(integer) 0
3、慢查询功能可以有效地帮助我们找到Redis可能存在的瓶颈,但在实际使用过程中要注意以下几点:
4、Redis提供了redis-cli、redis-server、redis-benchmark等Shell工具。
5、redis-cli详解
要了解redis-cli的全部参数,可以执行redis-cli-help命令来进行查看。
1)-r
$ redis-cli -r 3 ping // 执行三次ping命令
pong
pong
pong
2)-i
$ redis-cli -r 5 -i 1 ping // 每隔1秒执行一次ping命令,一共执行5次
PONG
PONG
PONG
PONG
PONG
$ redis-cli -r 100 -i 1 info | grep used_memory_human // 每隔1秒输出内存的使用量,一共输出 100次
used_memory_human:2.95G
used_memory_human:2.95G
. . . . . . . . . . . . . . . . . . . . . .
used_memory_human:2.94G
3)-x
$ echo "world" | redis-cli -x set hello
OK
4)-c
5)-a
6)--scan和--pattern
7)--slave
①下面开启第一个客户端,使用--slave选项,看到同步已完成:
$ redis-cli --slave
SYNC with master, discarding 72 bytes of bulk transfer . . .
SYNC done . Logging commands from master .
②再开启另一个客户端做一些更新操作:
redis-cli
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> set a b
OK
127.0.0.1:6379> incr count
1
127.0.0.1:6379> get hello
"world"
③第一个客户端会收到Redis节点的更新操作:(PING命令是由于主从复制产生的)
redis-cli --slave
SYNC with master, discarding 72 bytes of bulk transfer . . .
SYNC done . Logging commands from master .
"PING"
"PING"
"PING"
"PING"
"PING"
"SELECT","0"
"set","hello","world"
"set","a","b"
"PING"
"incr","count"
8)--rdb
9)--pipe
下面操作同时执行了set hello world和incr counter两条命令:
echo -en '*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n*2\r\n$4\r\nincr\r\ n$7\r\ncounter\r\n ' | redis-cli --pipe
10)--bigkeys
11)--eval
12)--latency
redis-cli -h {machineB} --latency
min: 0, max: 1, avg: 0.07 (4211 samples)
// 延时信息每15秒输出一次,可以通过-i参数控制间隔时间
redis-cli -h 10.10.xx.xx --latency-history
min: 0, max: 1, avg: 0.28 (1330 samples) -- 15.01 seconds range…
min: 0, max: 1, avg: 0.05 (1364 samples) 15.01 seconds range
13)--stat
14)--raw和--no-raw
①在Redis中设置一个中文的value:
$redis-cli set hello "你好"
OK
②如果正常执行get或者使用--no-raw选项,那么返回的结果是二进制格式;如果使用了--raw选项,将会返回中文。
$redis-cli get hello
"\xe4\xbd\xa0\xe5\xa5\xbd"
$redis-cli --no-raw get hello
"\xe4\xbd\xa0\xe5\xa5\xbd"
$redis-cli --raw get hello
你好
6、redis-server详解
redis-server除了启动Redis外,还有一个–test-memory选项。redis-server–test-memory可以用来检测当前操作系统能否稳定地分配指定容量的内存给Redis,通过这种检测可以有效避免因为内存问题造成Redis崩溃。
下面操作检测当前操作系统能否提供1G的内存给Redis:
redis-server --test-memory 1024
7、redis-benchmark详解
redis-benchmark可以为Redis做基准性能测试。
1)-c
2)-n
redis-benchmark -c 100 -n 20000代表100个客户端同时请求Redis,一共执行20000次。
redis-benchmark会对各类数据结构的命令进行测试,并给出性能指标.
====== GET ======
20000 requests completed in 0.27 seconds
100 parallel clients
3 bytes payload keep alive: 1
99.11% <= 1 milliseconds
100.00% <= 1 milliseconds
73529.41 requests per second
3)-q
$redis-benchmark -c 100 -n 20000 -q
PING_INLINE: 74349.45 requests per second
PING_BULK: 68728.52 requests per second
SET : 71174.38 requests per second…
LRANGE_500 (first 450 elements) : 11299.44 requests per second
LRANGE_600 (first 600 elements) : 9319.67 requests per second
MSET (10 keys) : 70671.38 requests per second
4)-r
在一个空的Redis上执行了redis-benchmark会发现只有3个键:
127.0.0.1:6379> dbsize
(integer) 3
127.0.0.1:6379> keys *
1) "counter:__rand_int__"
2) "mylist"
3) "key:__rand_int__"
$redis-benchmark -c 100 -n 20000 -r 10000
5)-P
6)-k
7)-t
redis-benchmark -t get,set -q
SET: 98619.32 requests per
GET: 97560.98 requests per
8)--csv
redis-benchmark -t get,set --csv
"SET","81300.81"
"GET","79051.38"
8、Pipeline概念
Redis客户端执行一条命令分为如下四个过程:
①发送命令
②命令排队
③命令执行
④返回结果
其中①+④称为Round Trip Time (RTT,往返时间)。
9、Pipeline性能测试
10、原生批量命令与Pipeline对比
Pipeline与原生批量命令的区别,具体包含以下几点:
11、Pipeline最佳实践
12、事务
为了保证多条命令组合的原子性,Redis提供了简单的事务功能以及集成Lua脚本来解决这个问题。
事务表示一组动作,要么全部执行,要么全部不执行。
Redis提供了简单的事务功能,将一组需要一起执行的命令放到multi和exec两个命令之间。multi命令代表事务开始,exec命令代表事务结束,它们之间的命令是原子顺序执行的。
// 用户关注的例子
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:a:follow user:b
QUEUED
127.0.0.1:6379> sadd user:b:fans user:a
QUEUED
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 0
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 1
127.0.0.1:6379> discard
OK
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 0
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:a:follow user:b
QUEUED
127.0.0.1:6379> zadd user:b:fans 1 user:a
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 127.0.0.1:6379> sismember user:a:follow user:b
(integer) 1
#T1:客户端1
127.0.0.1:6379> set key "java"
OK
#T2:客户端1
127.0.0.1:6379> watch key
OK
#T3:客户端1
127.0.0.1:6379> multi
OK
#T4:客户端2
127.0.0.1:6379> append key python
(integer) 11
#T5:客户端1
127.0.0.1:6379> append key jedis
QUEUED
#T6:客户端1
127.0.0.1:6379> exec
(nil)
#T7:客户端1
127.0.0.1:6379> get key
"javapython"
13、Lua用法概述
Redis将Lua作为脚本语言可帮助开发者定制自己的Redis命令,在这之前,必须修改源码。
1)数据类型及其逻辑处理
Lua语言提供了如下几种数据类型: booleans(布尔)、numbers(数值)、strings(字符串)、tables(表格),和许多高级语言相比,相对简单。
1、字符串
-- local代表val是一个局部变量,如果没有local代表是全局变量。 print函数可以打印出变量的值
local strings val = "world";
-- 结果是"world"
print (hello)
2、数组
local tables myArray = {"redis", "jedis", true, 88.0}
-- true
print (myArray[3])
①for
local int sum = 0
for i = 1, 100
do
sum = sum + i
end
-- 输出结果为5050
print (sum)
for i = 1, #myArray
do
print (myArray [i])
end
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
--输出结果为5050
print (sum)
③if else
local tables myArray = {"redis", "jedis", true, 88 .0}
for i = 1, #myArray
do
if myArray [i] == "jedis"
then
print ("true")
break
else
--do nothing
end
end
3、哈希
local tables user_1 = {age = 28, name = "tome"}
--user_1 age is 28
print ("user_1 age is " . . user_1 ["age"])
for key,value in pairs (user_1)
do print (key . . value)
end
2)函数定义
在Lua中,函数以function开头,以end结尾,funcName是函数名,中间部分是函数体。
function funcName ()
. . .
end
// contact函数将两个字符串拼接:
function contact (str1, str2)
return str1 . . str2
end
--"hello world"
print (contact ("hello ", "world"))
14、在Redis中使用Lua
在Redis中执行Lua脚本有两种方法:eval和evalsha。
1)eval
eval 脚本内容 key个数 key列表 参数列表
// 此时KEYS[1]="redis",ARGV[1]="world",所以最终的返回结果是"hello redisworld"。
127.0.0.1:6379> eval 'return "hello " . . KEYS [1] . . ARGV [1] ' 1 redis world
"hello redisworld"
2)evalsha
①加载脚本:script load命令可以将脚本内容加载到Redis内存中:
将lua_get.lua加载到Redis中,得到SHA1
# redis-cli script load "$ (cat lua_get.lua)"
"7413dc2440db1fea7c0a0bde841fa68eefaf149c"
②执行脚本:evalsha 的使用方法如下,参数使用SHA1值,执行逻辑和eval一致。
evalsha 脚本SHA1值 key个数 key列表 参数列表
127.0.0.1 :6379> evalsha 7413dc2440db1fea7c0a0bde841fa68eefaf149c 1 redis world
"hello redisworld"
15、Lua的Redis API
Lua可以使用redis.call函数实现对Redis的访问。
redis.call ("set", "hello", "world")
redis.call ("get", "hello")
放在Redis 的执行效果如下:
127.0.0.1 :6379> eval 'return redis.call ("get", KEYS [1]) ' 1 hello
"world"
15、Lua脚本功能为Redis开发和运维人员带来如下三个好处:
16、Redis如何管理Lua脚本
Redis提供了4个命令实现对Lua脚本的管理:
1、script load
script load script
2、script exists
scripts exists sha1 [sha1 … ]
127.0.0.1:6379> script exists a5260dd66ce02462c5b5231c727b3f7772c0bcc5
1) (integer) 1
3、script flush
127.0.0.1:6379> script exists a5260dd66ce02462c5b5231c727b3f7772c0bcc5
1) (integer) 1
127.0.0.1:6379> script flush
OK
127.0.0.1:6379> script exists a5260dd66ce02462c5b5231c727b3f7772c0bcc5
1) (integer) 0
4、script kill
127.0.0.1:6379> eval 'while 1==1 do end ' 0 // 死循环,当前客户端会阻塞
127.0.0.1:6379> get hello
(error) BUSY Redis is busy running a script . You can only call SCRIPT KILL or
SHUTDOWN NOSAVE
127.0.0.1:6379> script kill
OK
127.0.0.1:6379> get hello
"world"
17、Bitmaps数据结构模型
Bitmaps本身不是一种数据结构,实际上它就是字符串,但是它可以对字符串的位进行操作。
Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量。
18、Bitmaps命令
假设将每个独立用户是否访问过网站存放在Bitmaps中,将访问的用户记做1,没有访问的用户记做0,用偏移量作为用户的id。
1、设置值
setbit key offset value
// 将第0、5、11位用户设置为1
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
2、获取值
getbit key offset
127.0.0.1:6379> getbit unique:users:2016-04-05 8
(integer) 0
127.0.0.1:6379> getbit unique:users:2016-04-05 5
(integer) 1
3、获取Bitmaps指定范围值为1的个数
bitcount [start] [end]
127.0.0.1:6379> bitcount unique:users:2016-04-05
(integer) 5
// 计算用户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 . . . .]
127.0.0.1:6379> bitop and unique:users:and:2016-04-04_03 unique:users:2016-04- unique:users:2016-04-03
(integer) 2
127.0.0.1:6379> bitcount unique:users:and:2016-04-04_03
(integer) 2
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
// 计算第0个字节到第1个字节之间,第一个值为0的偏移量
127.0.0.1:6379> bitpos unique:users:2016-04-04 0 0 1
(integer) 0 // id=0的用户
19、Bitmaps分析
当用户量很少的时候,对比用set,用Bitmaps会占用更大的内存。因为set是随着用户量一个一个增长内存的,而Bitmaps是根据id的,因此一开始就是这么大的内存,此时使用Bitmaps也不太合适,因为大部分位都是0。
20、HyperLogLog
1、添加
pfadd key element [element … ]
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 … ]
127.0.0.1:6379> pfcount 2016_03_06:unique:ids
(integer) 4
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 // 新增uuid-90
// 向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
127.0.0.1:6379> info memory
# Memory
used_memory :850616
used_memory_human :830.68K // 内存只增加了15K左右.
127.0.0.1:6379> pfcount 2016_05_01:unique:ids // pfcount的执行结果并不是100万
(integer) 1009838
// 如果使用集合,内存使用约84MB,但独立用户数为100万。
3、合并
pfmerge destkey sourcekey [sourcekey . . .]
21、发布订阅概述
Redis提供了基于“发布/订阅”模式的消息机制,此种模式下,消息发布者和订阅者不进行直接通信,发布者客户端向指定的频道(channel)发布消息,订阅该频道的每个客户端都可以收到该消息。
22、发布订阅的命令
Redis主要提供了发布消息、订阅频道、取消订阅以及按照模式订阅和取消订阅等命令。
1、发布消息
publish channel message
// 向channel:sports频道发布一条消息“Tim won the championship”
127.0.0.1:6379> publish channel:sports "Tim won the championship"
(integer) 0
2、订阅消息
subscribe channel [channel . . .]
(一客户端A)
127.0.0.1:6379> subscribe channel:sports
Reading messages . . . (press Ctrl-C to quit)
1) "subscribe"
2) "channel:sports"
3) (integer) 1
(另一客户端B)
127.0.0.1:6379> publish channel:sports "James lost the championship"
(integer) 1
(一客户端A)
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"
3、取消订阅
unsubscribe [channel [channel . . .]]
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* // 订阅以it开头的所有频道
Reading messages . . . (press Ctrl-C to quit)
1) "psubscribe"
2) "it*"
3) (integer) 1
5、查询订阅
①查看活跃的频道
pubsub channels [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"
②查看频道订阅数
pubsub numsub [channel . . .]
127.0.0.1:6379> pubsub numsub channel:sports
1) "channel:sports"
2) (integer) 2
③查看模式订阅数
pubsub numpat
127.0.0.1:6379> pubsub numpat
(integer) 1 // 当前只有一个客户端通过模式来订阅
23、发布订阅的使用场景
聊天室、公告牌、服务之间利用消息解耦都可以使用发布订阅模式。
24、GEO
Redis3.2版本提供了GEO(地理信息定位)功能,支持存储地理位置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。
1、增加地理位置信息
geoadd key longitude latitude member [longitude latitude member . . .]
127.0.0.1:6379> geoadd cities:locations 116.28 39.55 beijing
(integer) 1
127.0.0.1:6379> geoadd cities:locations 116.28 39.55 beijing
(integer) 0
127.0.0.1:6379> geoadd cities:locations 117.12 39.08 tianjin 114.29 38.02 shijiazhuang 118.01 39.38 tangshan 115.29 38.51 baoding
(integer) 4
2、获取地理位置信息
geopos key member [member . . .]
127.0.0.1:6379> geopos cities:locations tianjin
1) 1) "117.12000042200088501"
2) "39.0800000535766543"
3、获取两个地理位置的距离
geodist key member1 member2 [unit]
127.0.0.1:6379> geodist cities:locations tianjin beijing km
"89.2061"
4、获取指定位置范围内的地理信息位置集合
georadius key longitude latitude radiusm |km |ft |mi [withcoord] [withdist] [withhash] [COUNT count] [asc |desc] [store key] [storedist key]
georadiusbymember key member radiusm |km |ft |mi [withcoord] [withdist]
[withhash] [COUNT count] [asc |desc] [store key] [storedist key]
// 计算五座城市中,距离北京150公里以内的城市
127.0.0.1:6379> georadiusbymember cities:locations beijing 150 km
1) "beijing"
2) "tianjin"
3) "tangshan"
4) "baoding"
5、获取geohash
geohash key member [member . . .]
127.0.0.1:6379> geohash cities:locations beijing
1) "wx4ww02w070"
127.0.0.1:6379> type cities:locations
zset
6、删除地理位置信息
zrem key member
geohash长度与精度对应关系表
geohash长度 | 精确度(km) |
---|---|
1 | 2500 |
2 | 630 |
3 | 78 |
4 | 20 |
5 | 2.4 |
6 | 0.61 |
7 | 0.076 |
8 | 0.019 |
9 | 0.002 |