0%

Redis - 性能分析概述

官方给出的Redis性能数据是10W+QPS,在实际业务中,Redis很少会成为性能瓶颈,但是针对Redis的一些简单的性能分析方法,还是可以了解一下。

info命令

Redis自带的info命令,可以输出当前Redis各个维度的统计数据。

memory模块

info memory命令,可以输出内存相关的数据,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
127.0.0.1:6579> info memory
# Memory
used_memory:3173165056
used_memory_human:2.96G
used_memory_rss:3490344960
used_memory_rss_human:3.25G
used_memory_peak:3278386848
used_memory_peak_human:3.05G
used_memory_peak_perc:96.79%
used_memory_overhead:14773484
used_memory_startup:786696
used_memory_dataset:3158391572
used_memory_dataset_perc:99.56%
total_system_memory:8202436608
total_system_memory_human:7.64G
used_memory_lua:37888
used_memory_lua_human:37.00K
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
mem_fragmentation_ratio:1.10
mem_allocator:jemalloc-4.0.3
active_defrag_running:0
lazyfree_pending_objects:0

其中几个比较重要的参数:

  • used_memory: Redis所有数据占用的内存大小

  • used_memory_rss: Redis进程占用的物理内存大小

  • used_memory_peak: 内存使用的最大值,即是used_memory峰值

  • mem_fragmentation_ratio: 碎片率(重要指标)

    mem_fragmentation_ratio = used_memory_rss / used_memory

    • mem_fragmentation_ratio < 1
      Redis占用的内存大小 < Redis数据的大小,这个时候Redis会虚拟内存,虚拟内存的存储媒介就是硬盘,性能比内存低得多,一般出现这种情况,是因为系统内存不够

    • mem_fragmentation_ratio>1

      存在内存碎片,这个值越大,则碎片越多。这个值在1-1.5之间,是比较正常的值。

  • maxmeory:限制最大可用内存,防止所用内存超过服务器物理内存,0表示不限制最大内存

  • maxmemory_policy:达到设置的最大内存之后,采用的内存淘汰策略

stats模块

info state命令,可以输出一些统计数据,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
127.0.0.1:6579> info stats
# Stats
total_connections_received:8329309
total_commands_processed:902618799
instantaneous_ops_per_sec:245
total_net_input_bytes:129397633747
total_net_output_bytes:156979287960
instantaneous_input_kbps:37.00
instantaneous_output_kbps:10.35
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:107910
expired_stale_perc:0.00
expired_time_cap_reached_count:0
evicted_keys:0
keyspace_hits:69694621
keyspace_misses:333025754
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:42363
migrate_cached_sockets:0
slave_expires_tracked_keys:0
active_defrag_hits:0
active_defrag_misses:0
active_defrag_key_hits:0
active_defrag_key_misses:0

其中几个比较重要的参数:

  • total_commands_processed:从Rdis上一次启动以来总计处理的命令数

  • instantaneous_ops_per_sec:当前Redis实例的OPS

  • instantaneous_input_kbps:每秒输入量,单位是kb/s

  • instantaneous_output_kbps:每秒输出量,单位是kb/s

  • evicted_keys:因为达到最大内存上限,而被内存淘汰策略淘汰的key的数量

  • keyspace_hits:命中的key的数量

  • keyspace_misses:没有命中的key的数量

    缓存命中率 = keyspace_hits / (keyspace_hits + keyspace_misses),如果这个值过低,则表示大量的查询请求都是无效的。

延迟监控

Redis提供了一系列跟命令耗时相关的命令,用于分析命令执行性能。

slow log

通过config set slowlog-log-slower-than N(微秒)命令,配置slow log的最小耗时。当命令执行时间超过N,则认为这是一次慢查询,注意这个耗时仅仅指命令的执行时间,不包含网络I/O的时间,N的默认配置是10000微秒,即10ms,可以适当改小一些。

设置之后,通过slowlog get来查询,日志按照时间倒叙排列,输出格式为:

1
2
3
4
5
6
127.0.0.1:6579> slowlog get 
1) 1) (integer) 1 # Unique ID
2) (integer) 1581319053 # Unix timestamp
3) (integer) 1000 # Execution time in microseconds
4) 1) "keys" # Command
2)

每条slow log分为几行,分别记录了ID,执行命令的时间戳,执行命令的耗时,已经完整的命令和参数。

latency monitor

延迟监控功能,通过CONFIG SET latency-monitor-threshold N(毫秒),监控Redis事件中,执行耗时超过N的事件。

例如将N设置成1,然后查看延迟事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
> debug sleep 1
OK
(1.03s)

> latency latest
1) 1) "command"
2) (integer) 1581321721
3) (integer) 2
4) (integer) 1000
2) 1) "fork"
2) (integer) 1581321715
3) (integer) 24
4) (integer) 24

输出的四行内容分别为:事件名称-事件执行的时间戳-事件执行耗时(毫秒)-本事件类型中的最大一次耗时(毫秒)。

sleep 1命令是为了制造一个超过1毫秒的事件,执行之后,通过latency latest命令查看,得到两条数据,一条是command,即是刚刚的sleep 1,一条是在测试过程中,发生了一次持久化,fork指令耗费了24毫秒。

注意,这里的输出结果会将同一个类别的事件去重,只显示最后一次事件的数据,例如发生多次持久化,则latency latest命令输出结果中,还是只有一个fork,可以使用如下方式,查看对应事件的历史高延迟事件:

1
2
3
4
5
6
7
> latency history fork
1) 1) (integer) 1581321715
2) (integer) 24
2) 1) (integer) 1581321794
2) (integer) 45
3) 1) (integer) 1581321871
2) (integer) 46

输出的两行内容分别为:时间戳-延迟。

intrinsic latency

Redis服务内部延迟。通过执行命令:src/redis-cli --intrinsic-latency N(秒),统计N秒内的延迟数据,它的结果可以用来衡量Redis服务内部延迟时间,命令使用参考:

1
2
3
4
5
6
7
8
9
10
11
12
$ ./src/redis-cli --intrinsic-latency 10
Max latency so far: 1 microseconds.
Max latency so far: 5 microseconds.
Max latency so far: 8 microseconds.
Max latency so far: 12 microseconds.
Max latency so far: 24 microseconds.
Max latency so far: 28 microseconds.
Max latency so far: 77 microseconds.
Max latency so far: 1890 microseconds.

180672281 total runs (avg latency: 0.0553 microseconds / 55.35 nanoseconds per run).
Worst run took 34147x longer than the average latency.

network latency

相比起内部延迟,Redis客户端到服务端的网络延迟影响更大,不确定因素也更多,比如网络抖动等。Redis也提供了相关命令来统计网络延迟情况,这个命令的本质就是通过ping你的Redis服务端来衡量响应时间。使用方法如下:

1
2
$ ./src/redis-cli --latency -h 192.168.0.100 -p 6379
min: 0, max: 1, avg: 0.06 (383 samples)

数据分析

如果Redis内存占用过大,可以通过一些第三方工具,或者利用Redis提供的命令,来分析Redis的内存占用。

redis-rdb-tools-master

一个开源工具,解析redis的dump.rdb文件,分析内存,以JSON格式导出数据,安装方式:

导出命令:rdb -c memory ./dump.rdb > redis_memory_report.csv

1
2
3
database,type,key,size_in_bytes,encoding,num_elements,len_largest_element,expiry
0,list,pair_kline_1min_8839,12660,quicklist,242,88
0,string,token_f97d86e586774131a1a1a044eb248a4a,144,string,18,18,2019-12-02T04:50:23.631000
  • type: 数据类型
  • size_in_bytes: 数据占用内存的大小
  • encoding:底层数据存储方式
  • expiry: 过期时间

Redis命令组合

  • object idletime

    Redis自带的命令,显示某个{key}自上次读取或者修改之后的空闲时间(单位为秒),例如:

    1
    2
    > object idletime user_info_123
    (integer) 13887
  • debug object

    Redis自带的一个调试命令,不推荐在客户端调用。可以显示某个key的一些信息,其中包含了value序列化之后的长度,例如:

    1
    2
    > debug object cmc_exchange_rate
    Value at:0x7f0a369b6a70 refcount:1 encoding:hashtable serializedlength:97787 lru:4320929 lru_seconds_idle:6

    serializedlength表示value序列化之后的长度。

结合上面两个命令,我们可以统计Redis中,各个key的大小,空余时间,python脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# -*- coding: utf-8 -*-

import redis

# 利用pipeline批量查询key的idle time和size
def query_redis(conn, keys, output):
if len(keys) == 0:
return

pipe = conn.pipeline(transaction=True)
for key in keys:
pipe.object('idletime', key)
pipe.debug_object(key)

pipe_ret = pipe.execute()
for i in range(0, len(keys)):
idle_time = pipe_ret[i * 2]
debug_info = pipe_ret[i * 2 + 1]
value_size = debug_info['serializedlength']
output.write('%s,%s,%s\n' % (keys[i].decode('utf-8'), str(value_size), str(idle_time)))


output = open('output.csv', 'w')
output.write('key,size,idletime\n')

conn = redis.Redis(host='127.0.0.1', port=6379, password='123')
cursor = 0
count = 0
# 利用scan分页查询
while True:
scan_ret = conn.scan(cursor, count=1000)
cursor = scan_ret[0]
keys = scan_ret[1]
query_redis(conn, keys, output)

count += len(keys)
print('Got %s items now.' % str(count))

if cursor == 0:
print('Finish')
break
output.close()

如果是统计Redis集群的话,安装pip install redis-py-cluster之后,使用如下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# -*- coding: utf-8 -*-
# create by yihui 14:34 20/1/6

from rediscluster import RedisCluster
# 利用pipeline批量查询key的idle time和size
def query_redis(conn, keys, output):
if len(keys) == 0:
return

pipe = conn.pipeline()
for key in keys:
pipe.object('idletime', key)
pipe.debug_object(key)
pipe_ret = pipe.execute()
for i in range(0, len(keys)):
idle_time = pipe_ret[i * 2]
debug_info = pipe_ret[i * 2 + 1]
value_size = debug_info['serializedlength']
output.write('%s,%s,%s\n' % (keys[i], str(value_size), str(idle_time)))


output = open('output.csv', 'w')
output.write('key,size,idletime\n')
startup_nodes = [
{"host": "127.0.0.1", "port": 6379},
{"host": "127.0.0.1", "port": 6380},
{"host": "127.0.0.1", "port": 6381}
]
conn = RedisCluster(startup_nodes=startup_nodes, decode_responses=True, password="123")
cursor = 0
count = 0
# 利用scan分页查询
while True:
scan_ret_res = conn.scan(cursor, count=1000)
target = None
keys = []
for scan_ret in scan_ret_res.values():
if target is None:
target = scan_ret[0]
else:
target = min(scan_ret[0], target)
keys.extend(scan_ret[1])

query_redis(conn, keys, output)
count += len(keys)
print('Got %s items now.' % str(count))
cursor = target
if cursor == 0:
print('Finish')
break

output.close()

监控

还有一些第三方的监控工具,可以提供可视化的监控,例如redis-stat



-=全文完=-