03:小功能大用处
文章目录
1、本章知识
1.1 本章内容
- 慢查询分析:通过慢查询分析,找到有问题的命令进行优化。
- Redis Shell:功能强大的Redis Shell会有意想不到的实用功能。
- Pipeline:通过Pipeline(管道或者流水线)机制有效提高客户端性能。
- 事务与Lua:制作自己的专属原子命令。
- Bitmaps:通过在字符串数据结构上使用位操作,有效节省内存,为开 发提供新的思路。
- HyperLogLog:一种基于概率的新算法,难以想象地节省内存空间。
- 发布订阅:基于发布订阅模式的消息通信机制。
- GEO:Redis3.2提供了基于地理位置信息的功能。
1.2 重点
- 1)慢查询中的两个重要参数slowlog-log-slower-than和slowlog-max-len。
- 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虽然在统计独立总量时存在一定的误差,但是节省的内存量十分惊人。
2、慢查询分析
所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间
,当超过预设阀值,就将这条命令的相关信息(例如:发生时 间,耗时,命令的详细信息)记录下来,Redis也提供了类似的功能。
Redis客户端执行一条命令分为如下4个部分:
- 一条客户端命令的生命周期:
- 发送命令
- 命令排队
- 命令执行
- 返回结果
需要注意,慢查询
只统计(命令执行)的时间
,所以没有慢查询并不代表客户端没有超时问题。
2.1 慢查询的两个配置参数
- 对于慢查询功能,需要明确两件事:
- 预设阀值怎么设置?
- 慢查询记录存放在哪?
Redis提供了slowlog-log-slower-than和slowlog-max-len配置来解决这两个问题。
- slowlog-log-slower-than
从字面意思就可以看出,slowlog-log-slower-than就是那个预设阀值, 它的单位是微秒(1秒=1000毫秒=1000000微秒),默认值是
10000即10毫秒
,假如执 行了一条很慢的命令(例如keys*),如果它的执行时间超过了10000微秒,那么它将被记录在慢查询日志中。
注意:如slowlog-log-slower-than = 0
会记录所有的命令,slowlog-log-slower-than < 0
对于任何命令都不会进行记录。
- slowlog-max-len
从字面意思看,只是说明了慢查询日志最多存储多少条,并没有说明存放在哪里?
实际上Redis使用了一个列表来存储慢查询日志,slowlog-max-len就是列表的最大长度。
一个新的命令满足慢查询条件时 被插入到这个列表中,当慢查询日志列表已处于其最大长度时,最早插入的 一个命令将从列表中移出,例如slowlog-max-len设置为5,当有第6条慢查询插入的话,那么队头的第一条数据就出列,第6条慢查询就会入列。
- 在Redis中有两种修改配置的方法:
- 一种是修改配置文件
- 另一种是使用config set命令动态修改
下面使用config set命令将slowlog-log-slower- than设置为20000微秒,slowlog-max-len设置为256
命令行查看修改配置文件项config get | config set | config rewrite
192.168.49.171:6392> config get slow*
1) "slowlog-log-slower-than"
2) "10000"
3) "slowlog-max-len"
4) "128"
192.168.49.171:6392> config set slowlog-log-slower-than 20000
OK
192.168.49.171:6392> config set slowlog-max-len 256
OK
192.168.49.171:6392> config rewrite
OK
192.168.49.171:6392> config get slow*
1) "slowlog-log-slower-than"
2) "20000"
3) "slowlog-max-len"
4) "256"
192.168.49.171:6392>
要Redis将配置持久化到本地配置文件,需要执行config rewrite
命令,config rewrite命令重写配置文件
192.168.49.171:6392> slowlog get
1) 1) (integer) 0
2) (integer) 1574061827
3) (integer) 14832
4) 1) "DEL"
2) "baobao"
192.168.49.171:6392> slowlog len
(integer) 1
192.168.49.171:6392>
- 慢查询日志数据结构
- 那么记录的中的1)2)3)4)分别表示什么呢?
- 1)表示日志唯一标识符uid
- 2)命令执行时系统的时间戳
- 3)命令执行的时长,以微妙来计算
- 4)命令和命令的参数
192.168.49.171:6392> slowlog len --当前Redis中有1条慢查询
(integer) 1
192.168.49.171:6392> slowlog reset --慢查询日志重置
OK
192.168.49.171:6392> slowlog len
(integer) 0
192.168.49.171:6392>
2.2 慢查询最佳实践
- 慢查询功能可以有效地帮助我们找到Redis可能存在的瓶颈,但在实际 使用过程中要注意以下几点:
- slowlog-max-len配置建议:线上建议调大慢查询列表,记录慢查询时 Redis会对长命令做截断操作,并不会占用大量内存。增大慢查询列表可以减缓慢查询被剔除的可能,例如线上可设置为1000以上。
- slowlog-log-slower-than配置建议:默认值超过10毫秒判定为慢查询,需要根据Redis并发量调整该值。由于Redis采用单线程响应命令,对于高流量的场景,如果命令执行时间在1毫秒以上,那么Redis最多可支撑OPS不到 1000。因此对于高OPS场景的Redis建议设置为1毫秒。
- 慢查询只记录命令执行时间,并不包括命令排队和网络传输时间。因此客户端执行命令的时间会大于命令实际执行时间。因为命令执行排队机制,慢查询会导致其他命令级联阻塞,因此当客户端出现请求超时,需要检 查该时间点是否有对应的慢查询,从而分析出是否为慢查询导致的命令级联阻塞。
- 由于慢查询日志是一个先进先出的队列,也就是说如果慢查询比较多 的情况下,可能会丢失部分慢查询命令,为了防止这种情况发生,可以定期执行slow get命令将慢查询日志持久化到其他存储中(例如MySQL),然后 可以制作可视化界面进行查询。
3、Redis Shell
Redis提供了redis-cli、redis-server、redis-benchmark等Shell工具。
可执行文件 | 作用 |
---|---|
redis-server | 启动redis |
redis-cli | redis命令行工具 |
redis-benchmark | 基准测试工具 |
redis-check-aof | AOF持久化文件检测工具和修复工具 |
redis-check-dump | RDB持久化文件检测工具和修复工具 |
redis-sentinel | 启动redis-sentinel |
3.1 redis-cli常用命令
1. -r(repeat)选项代表将命令执行多次,例如下面操作将会执行三次ping命令:
[root@manager01 ~]# redis-cli -r 3 -h 192.168.49.171 -p 6393 ping
PONG
PONG
PONG
2.-i(interval)选项代表每隔几秒执行一次命令,但是-i选项必须和-r选项一起使用,下面的操作会每隔1秒执行一次ping命令,一共执行5次:
[root@manager01 ~]# redis-cli -h 192.168.49.171 -p 6393 -r 5 -i 1 ping
PONG
PONG
PONG
PONG
PONG
注意-i的单位是秒,不支持毫秒为单位,但是如果想以每隔10毫秒执行一次,可以用-i 0.01,例如:
[root@manager01 ~]# redis-cli -h 192.168.49.171 -p 6393 -r 5 -i 0.01 ping
PONG
PONG
PONG
PONG
PONG
例如下面的操作利用-r和-i选项,每隔1秒输出内存的使用量,一共输出5次:
[root@manager01 ~]# redis-cli -h 192.168.49.171 -p 6393 -r 5 -i 1 info |grep used_memory_human
used_memory_human:40.34M
used_memory_human:40.34M
used_memory_human:40.34M
used_memory_human:40.31M
used_memory_human:40.34M
3.-x选项代表从标准输入(stdin)读取数据作为redis-cli的***一个参数,例如下面的操作会将字符串world作为set hello的值:
[root@manager01 ~]# echo "world" | redis-cli -h 192.168.49.171 -p 6393 -x set hello
OK
[root@manager01 ~]# redis-cli -h 192.168.49.171 -p 6393 get hello
"world\n"
[root@manager01 ~]# echo -n "world_1" | redis-cli -h 192.168.49.171 -p 6393 -x set hello_1 --因为echo命令是默认带有回车\n的,不带回车需要echo –n命令:从标准输入读入一个参数到redis,就不会有回车符.
OK
[root@manager01 ~]# redis-cli -h 192.168.49.171 -p 6393 get hello_1
"world_1"
[root@manager01 ~]#
4.-c(cluster)选项是连接Redis Cluster节点时需要使用的,-c选项可以防止moved和ask异常。
[root@manager01 ~]# redis-cli -h 192.168.49.173 -p 6379 -c
192.168.49.173:6379> info cluster
# Cluster
cluster_enabled:1
192.168.49.173:6379>
5.-a 如果Redis配置了密码,可以用-a(auth)选项,有了这个选项就不需要手动输入auth命令。
6.--scan选项和--pattern选项用于扫描指定模式的键,相当于使用scan命令。
7.--slave选项是把当前客户端模拟成当前Redis节点的从节点,可以用来获取当前Redis节点的更新操作,有关于Redis复制将在第6章进行详细介绍。合理的利用这个选项可以记录当前连接Redis节点的一些更新操作,这些更新操作很可能是实际开发业务时需要的数据。
下面开启***个客户端,使用--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节点的更新操作:
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"
PING命令是由于主从复制产生的,第6章会对主从复制进行介绍。
8.--rdb选项会请求Redis实例生成并发送RDB持久化文件,保存在本地。可使用它做持久化文件的定期备份。
9.--pipe选项用于将命令封装成Redis通信协议定义的数据格式,批量发送给Redis执行:
[root@manager01 ~]# echo -en "PING\r\n SET w3ckey redis\r\nGET w3ckey\r\nINCR visitor\r\nINCR visitor\r\nINCR visitor\r\n" | redis-cli -h 192.168.49.171 -p 6392
PONG
OK
"redis"
(integer) 1
(integer) 2
(integer) 3
10、--bigkeys统计bigkey的分布,使用scan命令对redis的键进行采样,从中找到内存占用比较大的键,这些键可能是系统的瓶颈。
11、--eval用于执行lua脚本
12、--latency
[root@manager01 ~]# redis-cli -h 192.168.49.171 -p 6393 --latency
min: 0, max: 4, avg: 0.30 (3757 samples)
有三个选项,--latency、--latency-history、--latency-dist。它们可检测网络延迟,展现的形式不同。
13、--stat 可实时获取redis的重要统计信息。info命令虽然比较全,但这里可看到一些增加的数据,如requests(每秒请求数)
14、--raw 和 --no-raw --no-raw 要求返回原始格式。--raw 显示格式化的效果。
[root@manager01 ~]# redis-cli -h 192.168.49.171 -p 6393 --stat
------- data ------ --------------------- load -------------------- - child -
keys mem clients blocked requests connections
8 40.31M 8 0 2798512 ( 0) 19198
8 40.35M 8 0 2798517 ( 5) 19198
8 40.35M 8 0 2798525 ( 8) 19198
3.2 redis-cli命令总结
- 字符串命令
- 修改字符串操作命令
- 修改数字值操作命令
- 位图命令
- 列表命令
- 其他列表命令
- 哈希命令
*其他哈希命令
- 集合命令
集合并交差运算操作命令
*有序集合命令
- 其他有序集合命令
- 发布订阅命令
- 连接操作相关的命令
- 服务端相关命令
与客户端连接、获取命令信息相关的命令
与配置文件、磁盘操作相关的命令
其他命令
- 脚本命令
- 对KEY操作的命令
- Hyperloglog命令
- 地理空间命令
- 事务命令
- 集群命令
连接操作相关的命令
默认直接连接 远程连接-h 192.168.1.20 -p 6379
ping:测试连接是否存活如果正常会返回pong
echo:打印
select:切换到指定的数据库,数据库索引号 index 用数字值指定,以 0 作为起始索引值
quit:关闭连接(connection)
auth:简单密码认证
服务端相关命令
time:返回当前服务器时间
client list: 返回所有连接到服务器的客户端信息和统计数据 参见http://redisdoc.com/server/client_list.html
client kill ip:port:关闭地址为 ip:port 的客户端
save:将数据同步保存到磁盘
bgsave:将数据异步保存到磁盘
lastsave:返回上次成功将数据保存到磁盘的Unix时戳
shundown:将数据同步保存到磁盘,然后关闭服务
info:提供服务器的信息和统计
config resetstat:重置info命令中的某些统计数据
config get:获取配置文件信息
config set:动态地调整 Redis 服务器的配置(configuration)而无须重启,可以修改的配置参数可以使用命令 CONFIG GET * 来列出
config rewrite:Redis 服务器时所指定的 redis.conf 文件进行改写
monitor:实时转储收到的请求
slaveof:改变复制策略设置
发布订阅相关命令
psubscribe:订阅一个或多个符合给定模式的频道 例如psubscribe news.* tweet.*
publish:将信息 message 发送到指定的频道 channel 例如publish msg "good morning"
pubsub channels:列出当前的活跃频道 例如PUBSUB CHANNELS news.i*
pubsub numsub:返回给定频道的订阅者数量 例如PUBSUB NUMSUB news.it news.internet news.sport news.music
pubsub numpat:返回客户端订阅的所有模式的数量总和
punsubscribe:指示客户端退订所有给定模式。
subscribe:订阅给定的一个或多个频道的信息。例如 subscribe msg chat_room
unsubscribe:指示客户端退订给定的频道。
exists(key):确认一个key是否存在
del(key):删除一个key
type(key):返回值的类型
keys(pattern):返回满足给定pattern的所有key
randomkey:随机返回key空间的一个
keyrename(oldname, newname):重命名key
dbsize:返回当前数据库中key的数目
expire:设定一个key的活动时间(s)
ttl:获得一个key的活动时间
move(key, dbindex):移动当前数据库中的key到dbindex数据库
flushdb:删除当前选择数据库中的所有key
flushall:删除所有数据库中的所有key
对String操作的命令
set(key, value):给数据库中名称为key的string赋予值value
get(key):返回数据库中名称为key的string的value
getset(key, value):给名称为key的string赋予上一次的value
mget(key1, key2,…, key N):返回库中多个string的value
setnx(key, value):添加string,名称为key,值为value
setex(key, time, value):向库中添加string,设定过期时间time
mset(key N, value N):批量设置多个string的值
msetnx(key N, value N):如果所有名称为key i的string都不存在
incr(key):名称为key的string增1操作
incrby(key, integer):名称为key的string增加integer
decr(key):名称为key的string减1操作
decrby(key, integer):名称为key的string减少integer
append(key, value):名称为key的string的值附加value
substr(key, start, end):返回名称为key的string的value的子串
对List操作的命令
rpush(key, value):在名称为key的list尾添加一个值为value的元素
lpush(key, value):在名称为key的list头添加一个值为value的 元素
llen(key):返回名称为key的list的长度
lrange(key, start, end):返回名称为key的list中start至end之间的元素
ltrim(key, start, end):截取名称为key的list
lindex(key, index):返回名称为key的list中index位置的元素
lset(key, index, value):给名称为key的list中index位置的元素赋值
lrem(key, count, value):删除count个key的list中值为value的元素
lpop(key):返回并删除名称为key的list中的首元素
rpop(key):返回并删除名称为key的list中的尾元素
blpop(key1, key2,… key N, timeout):lpop命令的block版本。
brpop(key1, key2,… key N, timeout):rpop的block版本。
rpoplpush(srckey, dstkey):返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部
对Set操作的命令
sadd(key, member):向名称为key的set中添加元素member
srem(key, member) :删除名称为key的set中的元素member
spop(key) :随机返回并删除名称为key的set中一个元素
smove(srckey, dstkey, member) :移到集合元素
scard(key) :返回名称为key的set的基数
sismember(key, member) :member是否是名称为key的set的元素
sinter(key1, key2,…key N) :求交集
sinterstore(dstkey, (keys)) :求交集并将交集保存到dstkey的集合
sunion(key1, (keys)) :求并集
sunionstore(dstkey, (keys)) :求并集并将并集保存到dstkey的集合
sdiff(key1, (keys)) :求差集
sdiffstore(dstkey, (keys)) :求差集并将差集保存到dstkey的集合
smembers(key) :返回名称为key的set的所有元素
srandmember(key) :随机返回名称为key的set的一个元素
对Hash操作的命令
hset(key, field, value):向名称为key的hash中添加元素field
hget(key, field):返回名称为key的hash中field对应的value
hmget(key, (fields)):返回名称为key的hash中field i对应的value
hmset(key, (fields)):向名称为key的hash中添加元素field
hincrby(key, field, integer):将名称为key的hash中field的value增加integer
hexists(key, field):名称为key的hash中是否存在键为field的域
hdel(key, field):删除名称为key的hash中键为field的域
hlen(key):返回名称为key的hash中元素个数
hkeys(key):返回名称为key的hash中所有键
hvals(key):返回名称为key的hash中所有键对应的value
hgetall(key):返回名称为key的hash中所有的键(field)及其对应的value
3.2 redis-benchmark命令
redis-benchmark可以为Redis做基准性能测试,它提供了很多选项帮助开 发和运维人员测试Redis的相关性能
序号 | 选项 | 描述 | 默认值 |
---|---|---|---|
1 | -h | 指定服务器主机名 | 127.0.0.1 |
2 | -p | 指定服务器端口 | 6379 |
3 | -s | 指定服务器 socket | |
4 | -c | 指定并发连接数 | 50 |
5 | -n | 指定请求数 | 10000 |
6 | -d | 以字节的形式指定 SET/GET 值的数据大小 | 2 |
7 | -k | 1=keep alive 0=reconnect | 1 |
8 | -r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
9 | -P | 通过管道传输 请求 | 1 |
10 | -q | 强制退出 redis。仅显示 query/sec 值 | |
11 | –csv | 以 CSV 格式输出 | |
12 | -l | 生成循环,永久执行测试 | |
13 | -t | 仅运行以逗号分隔的测试命令列表。 | |
14 | -I | Idle 模式。仅打开 N 个 idle 连接并等待。 |
以上实例中主机为 192.168.49.171,端口号为 6392,执行的命令为 set,lpush,请求数为 10000,通过 -q 参数让结果只显示每秒执行的请求数。
[root@manager01 ~]# redis-benchmark -h 192.168.49.171 -p 6392 -t set,lpush -n 100000 -q
SET: 43917.44 requests per second
LPUSH: 51786.64 requests per second
[root@manager01 ~]# redis-benchmark -h 192.168.49.171 -p 6392 -c 100 -n 100000 -q
PING_INLINE: 42607.59 requests per second
PING_BULK: 50864.70 requests per second
SET: 51706.31 requests per second
GET: 50581.69 requests per second
INCR: 50125.31 requests per second
LPUSH: 50684.23 requests per second
RPUSH: 57142.86 requests per second
LPOP: 49043.65 requests per second
RPOP: 51948.05 requests per second
SADD: 51177.07 requests per second
SPOP: 51334.70 requests per second
LPUSH (needed to benchmark LRANGE): 50864.70 requests per second
LRANGE_100 (first 100 elements): 24758.60 requests per second
LRANGE_300 (first 300 elements): 11036.31 requests per second
LRANGE_500 (first 450 elements): 8940.54 requests per second
LRANGE_600 (first 600 elements): 7476.08 requests per second
MSET (10 keys): 30797.66 requests per second
[root@manager01 ~]# redis-benchmark -h 192.168.49.171 -p 6392 -c 100 -n 20000 -r 10000
====== PING_INLINE ======
20000 requests completed in 0.58 seconds
100 parallel clients
3 bytes payload
keep alive: 1
0.28% <= 1 milliseconds
91.85% <= 2 milliseconds
98.81% <= 3 milliseconds
99.81% <= 4 milliseconds
99.99% <= 5 milliseconds
100.00% <= 5 milliseconds
34246.57 requests per second
====== PING_BULK ======
20000 requests completed in 0.45 seconds
100 parallel clients
3 bytes payload
keep alive: 1
1.61% <= 1 milliseconds
99.28% <= 2 milliseconds
99.72% <= 3 milliseconds
99.90% <= 4 milliseconds
100.00% <= 4 milliseconds
44543.43 requests per second
====== SET ======
20000 requests completed in 0.45 seconds
100 parallel clients
3 bytes payload
keep alive: 1
6.17% <= 1 milliseconds
96.38% <= 2 milliseconds
99.87% <= 3 milliseconds
100.00% <= 3 milliseconds
44843.05 requests per second
====== GET ======
20000 requests completed in 0.39 seconds
100 parallel clients
3 bytes payload
keep alive: 1
28.71% <= 1 milliseconds
99.81% <= 2 milliseconds
100.00% <= 3 milliseconds
100.00% <= 3 milliseconds
50890.59 requests per second
====== INCR ======
20000 requests completed in 0.41 seconds
100 parallel clients
3 bytes payload
keep alive: 1
6.71% <= 1 milliseconds
98.00% <= 2 milliseconds
99.75% <= 3 milliseconds
99.96% <= 4 milliseconds
100.00% <= 4 milliseconds
48899.75 requests per second
====== LPUSH ======
20000 requests completed in 0.40 seconds
100 parallel clients
3 bytes payload
keep alive: 1
5.18% <= 1 milliseconds
93.09% <= 2 milliseconds
99.61% <= 3 milliseconds
100.00% <= 3 milliseconds
49875.31 requests per second
====== RPUSH ======
20000 requests completed in 0.42 seconds
100 parallel clients
3 bytes payload
keep alive: 1
7.76% <= 1 milliseconds
87.24% <= 2 milliseconds
98.24% <= 3 milliseconds
99.54% <= 4 milliseconds
99.72% <= 5 milliseconds
99.88% <= 6 milliseconds
100.00% <= 6 milliseconds
47846.89 requests per second
====== LPOP ======
20000 requests completed in 0.44 seconds
100 parallel clients
3 bytes payload
keep alive: 1
1.74% <= 1 milliseconds
81.83% <= 2 milliseconds
99.30% <= 3 milliseconds
99.73% <= 4 milliseconds
99.90% <= 5 milliseconds
100.00% <= 5 milliseconds
45248.87 requests per second
====== RPOP ======
20000 requests completed in 0.41 seconds
100 parallel clients
3 bytes payload
keep alive: 1
5.95% <= 1 milliseconds
99.17% <= 2 milliseconds
99.76% <= 3 milliseconds
100.00% <= 4 milliseconds
49140.05 requests per second
====== SADD ======
20000 requests completed in 0.41 seconds
100 parallel clients
3 bytes payload
keep alive: 1
6.28% <= 1 milliseconds
97.25% <= 2 milliseconds
99.98% <= 3 milliseconds
100.00% <= 3 milliseconds
49019.61 requests per second
====== SPOP ======
20000 requests completed in 0.39 seconds
100 parallel clients
3 bytes payload
keep alive: 1
15.18% <= 1 milliseconds
99.61% <= 2 milliseconds
100.00% <= 2 milliseconds
51679.59 requests per second
====== LPUSH (needed to benchmark LRANGE) ======
20000 requests completed in 0.44 seconds
100 parallel clients
3 bytes payload
keep alive: 1
3.17% <= 1 milliseconds
77.82% <= 2 milliseconds
97.53% <= 3 milliseconds
99.60% <= 4 milliseconds
99.76% <= 5 milliseconds
99.93% <= 6 milliseconds
100.00% <= 6 milliseconds
45871.56 requests per second
====== LRANGE_100 (first 100 elements) ======
20000 requests completed in 0.81 seconds
100 parallel clients
3 bytes payload
keep alive: 1
0.01% <= 1 milliseconds
19.49% <= 2 milliseconds
92.38% <= 3 milliseconds
98.71% <= 4 milliseconds
99.69% <= 5 milliseconds
99.96% <= 6 milliseconds
100.00% <= 6 milliseconds
24600.25 requests per second
====== LRANGE_300 (first 300 elements) ======
20000 requests completed in 1.86 seconds
100 parallel clients
3 bytes payload
keep alive: 1
0.00% <= 2 milliseconds
0.81% <= 3 milliseconds
27.92% <= 4 milliseconds
47.61% <= 5 milliseconds
80.65% <= 6 milliseconds
90.54% <= 7 milliseconds
93.58% <= 8 milliseconds
96.07% <= 9 milliseconds
97.94% <= 10 milliseconds
98.86% <= 11 milliseconds
99.26% <= 12 milliseconds
99.69% <= 13 milliseconds
99.92% <= 14 milliseconds
99.97% <= 15 milliseconds
99.99% <= 16 milliseconds
100.00% <= 16 milliseconds
10746.91 requests per second
====== LRANGE_500 (first 450 elements) ======
20000 requests completed in 1.82 seconds
100 parallel clients
3 bytes payload
keep alive: 1
0.00% <= 1 milliseconds
0.06% <= 2 milliseconds
0.45% <= 3 milliseconds
24.24% <= 4 milliseconds
41.03% <= 5 milliseconds
69.07% <= 6 milliseconds
83.25% <= 7 milliseconds
90.21% <= 8 milliseconds
94.71% <= 9 milliseconds
97.28% <= 10 milliseconds
98.53% <= 11 milliseconds
98.98% <= 12 milliseconds
99.35% <= 13 milliseconds
99.63% <= 14 milliseconds
99.80% <= 15 milliseconds
99.89% <= 16 milliseconds
99.96% <= 17 milliseconds
99.99% <= 18 milliseconds
100.00% <= 18 milliseconds
10970.93 requests per second
====== LRANGE_600 (first 600 elements) ======
20000 requests completed in 2.76 seconds
100 parallel clients
3 bytes payload
keep alive: 1
0.00% <= 3 milliseconds
0.38% <= 4 milliseconds
11.99% <= 5 milliseconds
23.41% <= 6 milliseconds
51.24% <= 7 milliseconds
64.32% <= 8 milliseconds
73.74% <= 9 milliseconds
86.28% <= 10 milliseconds
93.30% <= 11 milliseconds
96.13% <= 12 milliseconds
97.79% <= 13 milliseconds
98.67% <= 14 milliseconds
98.98% <= 15 milliseconds
99.24% <= 16 milliseconds
99.44% <= 17 milliseconds
99.61% <= 18 milliseconds
99.72% <= 19 milliseconds
99.79% <= 20 milliseconds
99.85% <= 21 milliseconds
99.90% <= 22 milliseconds
99.94% <= 23 milliseconds
99.97% <= 24 milliseconds
100.00% <= 25 milliseconds
7256.89 requests per second
====== MSET (10 keys) ======
20000 requests completed in 0.85 seconds
100 parallel clients
3 bytes payload
keep alive: 1
0.00% <= 1 milliseconds
3.28% <= 2 milliseconds
24.63% <= 3 milliseconds
59.44% <= 4 milliseconds
84.07% <= 5 milliseconds
96.65% <= 6 milliseconds
98.99% <= 7 milliseconds
99.67% <= 8 milliseconds
99.97% <= 9 milliseconds
100.00% <= 9 milliseconds
23668.64 requests per second
3.3 Pipeline
3.3.1 执行过程
Redis客户端执行一条命令分为如下四个过程:
1)发送命令 2)命令排队 3)命令执行 4)返回结果 ;其中1) 4)称为Round Trip Time(RTT,往返时间)。
Redis提供了批量操作命令(例如mget、mset等),有效地节约RTT。但 大部分命令是不支持批量操作的,例如要执行n次hgetall命令,并没有 mhgetall命令存在,需要消耗n次RTT。Redis的客户端和服务端可能部署在不 同的机器上。例如客户端在北京,Redis服务端在上海,两地直线距离约为1300公里,那么1次RTT时间=1300×2/(300000×2/3)=13毫秒(光在真空中 传输速度为每秒30万公里,这里假设光纤为光速的2/3),那么客户端在1秒 内大约只能执行80次左右的命令,这个和Redis的高并发高吞吐特性背道而驰。
Pipeline(流水线)机制能改善上面这类问题,它能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端,为没有使用Pipeline执行了n条命令,整个过程需要n次 RTT
redis-cli的–pipe选项实际上就是使用Pipeline机制,例如下面操作将set hello world和incr counter两条命令组装
[root@manager01 ~]# 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 -h 192.168.49.171 -p 6393 --pipe
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 2
[root@manager01 ~]#
3.3.2 Pipeline性能测试
- 给出了在不同网络环境下非Pipeline和Pipeline执行10000次set操作 的效果,可以得到如下两个结论:
- Pipeline执行速度一般比逐条执行要快。
- 客户端和服务端的网络延时越大,Pipeline的效果越明显。
在不同网络下,10000条set非Pipeline和Pipeline的执行时间对比
3.3.3 原生批量命令与Pipeline对比
- Pipeline模拟出批量操作的效果与原生批量命令的区别:
- 原生批量命令是原子的,Pipeline是非原子的。
- 原生批量命令是一个命令对应多个key,Pipeline支持多个命令。
- 原生批量命令是Redis服务端支持实现的,而Pipeline需要服务端和客户端的共同实现。
3.3.4 最佳实践
Pipeline虽然好用,但是每次Pipeline组装的命令个数不能没有节制,否 则一次组装Pipeline数据量过大,一方面会增加客户端的等待时间,另一方 面会造成一定的网络阻塞,可以将一次包含大量命令的Pipeline拆分成多次 较小的Pipeline来完成。
Pipeline只能操作一个Redis实例,但是即使在分布式Redis场景中,也可 以作为批量操作的重要优化手段
3.4 Redis与Lua
3.5 Bitmaps
3.5.1 数据结构模型
在我们平时的开发过程中,会有-些 bool 型数据需要存取,比如用户1年的签到记录,签了是1,没签是0,要记录 365 天。如果使用普通的 key/value ,每个用户
要记录 365 个,当用户数上亿的时候,需要的存储空间是惊人的。
为了解决这个问题, Redis 提供了位图数据结构,这样每天的签到记录只占据一个位, 365 天就是 365 个位, 46 个字节(一个稍长一点的字符串)就可以完全容纳下,
这就大大节约了存储空间。位图的最小单位是比特(bit ),每个 bit 的取值只能是0或1
3.5.2 命令
- 设置值setbit key offset value
设置键的第offset个位的值(从0算起),假设现在有20个用户, userid=0,5,11,15,19的用户对网站进行了访问,那么当前Bitmaps初始化结果如图所示。
如果此时有一个userid=50的用户访问了网站,那么Bitmaps的结构变成了下图,第20位~49位都是0(补零)。
192.168.49.171:6392> setbit unique:users:2019-11-19 0 1
(integer) 0
192.168.49.171:6392> setbit unique:users:2019-11-19 5 1
(integer) 0
192.168.49.171:6392> setbit unique:users:2019-11-19 11 1
(integer) 0
192.168.49.171:6392> setbit unique:users:2019-11-19 15 1
(integer) 0
192.168.49.171:6392> setbit unique:users:2019-11-19 19 1
(integer) 0
192.168.49.171:6392> setbit unique:users:2019-11-19 50 1
(integer) 0
192.168.49.171:6392> bitcount unique:users:2019-11-19 --计算2019-11-19这天的独立访问用户数量(获取Bitmaps指定范围值为1的个数)
(integer) 6
192.168.49.171:6392> bitcount unique:users:2019-11-19 0 20
(integer) 6
192.168.49.171:6392> bitcount unique:users:2019-11-19 1 5 --[start]和[end]代表起始和结束字节数;面操作计算用户id在第1个字节 到第3个字节之间的独立访问用户数,对应的用户id是11,15,19
(integer) 3
- Bitmaps间的运算
bitop是一个复合操作,它可以做多个Bitmaps的and(交集)、or(并 集)、not(非)、xor(异或)操作并将结果保存在destkey中
利用bitop and命令计算两天都访问网站的用户
3.5.3 Bitmaps分析
假设网站有1亿用户,每天独立访问的用户有5千万,如果每天用集合类型和Bitmaps分别存储活跃用户可以得到下表
3.6 HyperLogLog
先思考一个常见的业务问题:如果你负责开发维护一个大型的网站,有一天老板技产品经理要网站上每个网页每天的 UV 数据,然后让你来开发这个统计模块,你会如何实现?
如果统计 PV ,那非常好办,给每个网页配一个独立的 Redis 计数器就可以了,把这个计数器的 key 后缀加上当天的日期。这样来一个请求,执行 incrby 指令一次,最终就可以统计出所有的 PV 数据。
但是 UV 不一样,它要去重,同一个用户一天之内的多次访问请求只能计数一次。这就要求每一个网页请求都需要带上用户的 ID ,无论是登录用户还是未登录用户都需要一个唯一 来标识。
你也许已经想到了一个简单的方案,那就是为每一个页面设置一个独立的 set集合来存储所有当天访问过此页面的用户囚。当一个请求过来时,我们使用 sadd 将用户ID 塞进去就可以了。通过 scard 可以取出这个集合的大小,这个数字就是这个页
面的 UV 数据。没错,这是一个非常简单的可行方案。
但是,如果你的页面访问量非常大,比如一个爆款页面可能有几千万个 UV ,你就需要一个很大的 set 集合来统计,这就非常浪费空间。如果这样的页面很多,那所需要的存储空间是惊人的。为这样一个去重功能就耗费这样多的存储空间,值得吗?
其实老板所需要的数据并不需要太精确, 105 万和 106 万这两个数字对于老板来说并没有多大区别。那么,有没有更好的解决方案呢?
3.6.1 HyperLogLog命令
HyperLogLog并不是一种新的数据结构(实际类型为字符串类型),而 是一种基数算法,通过HyperLogLog可以利用极小的内存空间完成独立总数的统计,数据集可以是IP、Email、ID等。
- HyperLogLog提供了3个命令:pfmerge
- pfadd 增加计数
- pfcount 计算独立用户数
- pfmerge 可以求出多个HyperLogLog的并集并赋值给destkey
192.168.49.171:6392> pfadd 2016_03_06:unique:ids "uuid-1" "uuid-2" "uuid-3" "uuid-4" --添加四个元素
(integer) 1
192.168.49.171:6392> pfadd 2016_03_06:unique:ids "uuid-1" "uuid-2" "uuid-3" "uuid-90"
(integer) 1
192.168.49.171:6392> pfcount 2016_03_06:unique:ids --计算独立用户数
(integer) 5
192.168.49.171:6392> pfadd 2016_03_05:unique:ids "uuid-4" "uuid-5" "uuid-6" "uuid-7"
(integer) 1
192.168.49.171:6392> pfmerge 2016_03_05_06:unique:ids 2016_03_05:unique:ids 2016_03_06:unique:ids --合并
OK
192.168.49.171:6392> pfcount 2016_03_05_06:unique:ids
(integer) 8
192.168.49.171:6392>
集合类型和HyperLogLog占用空间对比
HyperLogLog内存占用量非常小,但是存在错误率
- 结构选型时只需要确认如下两条即可:
- 只为了计算独立总数,不需要获取单条数据。
- 可以容忍一定误差率,毕竟HyperLogLog在内存的占用量上有很大的优势。
3.7 发布订阅
Redis提供了基于“发布/订阅”模式的消息机制,此种模式下,消息发布 者和订阅者不进行直接通信,发布者客户端向指定的频道(channel)发布消 息,订阅该频道的每个客户端都可以收到该消息
3.7.1 命令
1.发布消息 publish channel message
2.订阅消息 subscribe channel [channel …]
3.取消订阅 unsubscribe [channel [channel …]]
4.按照模式订阅和取消订阅 psubscribe pattern [pattern…] punsubscribe [pattern [pattern …]]
5.查询订阅
(1)查看活跃的频道 pubsub channels [pattern]
(2)查看频道订阅数 pubsub numsub [channel …]
(3)查看模式订阅数 pubsub numpat
--redis源端
192.168.49.171:6392> publish channel:xiaomidou "A baby"
(integer) 0
--redis接收端
192.168.49.171:6392> subscribe channel:xiaomidou
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel:xiaomidou"
3) (integer) 1
--redis源端再发送一次信息
192.168.49.171:6392> publish channel:xiaomidou "baby pig"
(integer) 1
192.168.49.171:6392>
192.168.49.171:6392> subscribe channel:xiaomidou
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel:xiaomidou"
3) (integer) 1
1) "message"
2) "channel:xiaomidou"
3) "baby pig"
- 如果有多个客户端同时订阅了;有关订阅命令有两点需要注意:
- 客户端在执行订阅命令之后进入了订阅状态,只能接收subscribe、 psubscribe、unsubscribe、punsubscribe的四个命令。
- 新开启的订阅客户端,无法收到该频道之前的消息,因为Redis不会对 发布的消息进行持久化。
3.8 GEO(地理信息定位)
Redis3.2版本提供了GEO地理信息定位功能, 支持存储地理位置信息用来实现诸如附近位置、 摇一摇这类依赖于地理位置信息的功能, 对于需要实现这些功能的开发者来说是一大福音。
geoadd key longitude latitude member [longitude latitude member …] 增加地理位置信息
geopos key member 获取地理位置信息
longitude、 latitude、 member分别是该地理位置的经度、 纬度、 成员, 表3-7展示5个城市的经纬度
192.168.49.171:6392> geoadd cities:locations 116.28 39.55 beijing --增加地理位置信息
(integer) 1
192.168.49.171:6392> geoadd cities:locations 116.28 39.55 beijing --返回结果为1, 如果已经存在则返回0
(integer) 0
192.168.49.171:6392> geoadd cities:locations 117.12 39.08 tianjin 114.29 38.02 shijiazhuang 118.01 39.38 tangshan 115.29 38.51 baoding --geoadd命令可以同时添加多个地理位置信息
(integer) 4
geodist key member1 member2 [unit] 获取两个地理位置的距离
- 其中unit代表返回结果的单位,包含以下四种:
- m(meters) 代表米。
- km(kilometers) 代表公里。
- mi(miles) 代表英里。
- ft( feet) 代表尺
192.168.49.171:6392> geopos cities:locations tianjin --获取天津的经维度
1) 1) "117.12000042200088501"
2) "39.0800000535766543"
- 获取指定位置范围内的地理信息位置集合
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]
georadius和georadiusbymember两个命令的作用是一样的, 都是以一个地理位置为中心算出指定半径内的其他地理信息位置,不同的是georadius命令的中心位置给出了具体的经纬度,georadiusbymember只需给出成员即可。其中radiusm|km|ft|mi是必需参数,指定了半径(带单位),
- 这两个命令有很多可选参数, 如下所示:
- withcoord: 返回结果中包含经纬度。
- withdist: 返回结果中包含离中心节点位置的距离。
- withhash: 返回结果中包含geohash, 有关geohash后面介绍。
- COUNT count: 指定返回结果的数量。
- asc|desc: 返回结果按照离中心节点的距离做升序或者降序。
- store key: 将返回结果的地理位置信息保存到指定键。
- storedist key: 将返回结果离中心节点的距离保存到指定键
192.168.49.171:6392> georadiusbymember cities:locations beijing 150 km --获取两个地理位置的距离
1) "beijing"
2) "tianjin"
3) "tangshan"
4) "baoding"
192.168.49.171:6392> geohash cities:locations beijing --Redis使用geohash将二维经纬度转换为一维字符串, 下面操作会返回beijing的geohash值。
1) "wx48ypbe2q0"
192.168.49.171:6392> type cities:locations
zset
192.168.49.171:6392> zrem key cities:locations beijing
(integer) 0
192.168.49.171:6392> geohash cities:locations beijing
1) "wx48ypbe2q0"
192.168.49.171:6392>