03:小功能大用处

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-cliredis命令行工具
redis-benchmark基准测试工具
redis-check-aofAOF持久化文件检测工具和修复工具
redis-check-dumpRDB持久化文件检测工具和修复工具
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-k1=keep alive 0=reconnect1
8-rSET/GET/INCR 使用随机 key, SADD 使用随机值
9-P通过管道传输 请求1
10-q强制退出 redis。仅显示 query/sec 值
11–csv以 CSV 格式输出
12-l生成循环,永久执行测试
13-t仅运行以逗号分隔的测试命令列表。
14-IIdle 模式。仅打开 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> 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值