[TOC]

0x00 Redis 性能指标监控

(1) 性能指标

Redis 服务端常见指标参数:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
redis-cli -a password info > redis-Performance.txt # 我们可以将redis服务端info相关信息导出到文件之中
# 2.clients:
# 3.memory:
# 4.persistence:
# 5.stats:通用统计数据
# 6.Replication:
# 7.CPU:CPU使用情况
# 8.cluster:
# 9.Keypass:键值对统计数量信息

10.20.172.108:6379> info # (1) Redis 服务端信息交互式查看
# Server 服务器运行的环境参数
redis_version:6.2.5
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:cb556a016f8668d
redis_mode:standalone
os:Linux 5.11.0-25-generic x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:c11-builtin
gcc_version:9.3.0
process_id:187640
process_supervised:no
run_id:97838216d4fe0de4739e7814b5a2e1d0d32d0982
tcp_port:6379
server_time_usec:1630241617439942
uptime_in_seconds:10930
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:2851665
executable:/opt/databases/redis-6.2.5/src/./redis-server
config_file:/home/weiyigeek/redis/6379/redis-6379.conf
io_threads_active:0

# Clients 客户端相关信息
connected_clients:7
cluster_connections:0
maxclients:10000
client_recent_max_input_buffer:32
client_recent_max_output_buffer:0
blocked_clients:0
tracking_clients:0
clients_in_timeout_table:0

# Memory 服务器运行内存统计数据
used_memory:2050432
used_memory_human:1.96M
used_memory_rss:5140480
used_memory_rss_human:4.90M
used_memory_peak:2253512
used_memory_peak_human:2.15M
used_memory_peak_perc:90.99%
used_memory_overhead:1982152
used_memory_startup:810376
used_memory_dataset:68280
used_memory_dataset_perc:5.51%
allocator_allocated:2204376
allocator_active:2555904
allocator_resident:5230592
total_system_memory:12442619904
total_system_memory_human:11.59G
used_memory_lua:37888
used_memory_lua_human:37.00K
used_memory_scripts:0
used_memory_scripts_human:0B
number_of_cached_scripts:0
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
allocator_frag_ratio:1.16
allocator_frag_bytes:351528
allocator_rss_ratio:2.05
allocator_rss_bytes:2674688
rss_overhead_ratio:0.98
rss_overhead_bytes:-90112
mem_fragmentation_ratio:2.59
mem_fragmentation_bytes:3153776
mem_not_counted_for_evict:124
mem_replication_backlog:1048576
mem_clients_slaves:0
mem_clients_normal:123000
mem_aof_buffer:128
mem_allocator:jemalloc-5.1.0
active_defrag_running:0
lazyfree_pending_objects:0
lazyfreed_objects:0

# Persistence 持久化数据相关信息
loading:0
current_cow_size:0
current_cow_size_age:0
current_fork_perc:0.00
current_save_keys_processed:0
current_save_keys_total:0
rdb_changes_since_last_save:3
rdb_bgsave_in_progress:0
rdb_last_save_time:1630230687
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:-1
rdb_current_bgsave_time_sec:-1
rdb_last_cow_size:0
aof_enabled:1
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:0
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
aof_last_cow_size:462848
module_fork_in_progress:0
module_fork_last_cow_size:0
aof_current_size:150
aof_base_size:92
aof_pending_rewrite:0
aof_buffer_length:0
aof_rewrite_buffer_length:0
aof_pending_bio_fsync:0
aof_delayed_fsync:0

# Stats 通用统计数据信息
total_connections_received:25
total_commands_processed:50482
instantaneous_ops_per_sec:4
total_net_input_bytes:2758703
total_net_output_bytes:22330756
instantaneous_input_kbps:0.23
instantaneous_output_kbps:0.55
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:0
expired_stale_perc:0.00
expired_time_cap_reached_count:0
expire_cycle_cpu_milliseconds:0
evicted_keys:0
keyspace_hits:1
keyspace_misses:0
pubsub_channels:1
pubsub_patterns:0
latest_fork_usec:310
total_forks:1
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
tracking_total_keys:0
tracking_total_items:0
tracking_total_prefixes:0
unexpected_error_replies:0
total_error_replies:8
dump_payload_sanitizations:0
total_reads_processed:48899
total_writes_processed:97139
io_threaded_reads_processed:0
io_threaded_writes_processed:0

# Replication 主从相关指标信息
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:32da05299a5a36de431b4c05122f7d2b93eca169
master_replid2:3e15749ad586d60bd0d1c93854f6f719a22316ce
master_repl_offset:8915
second_repl_offset:829
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:8915

# CPU 处理器模式占用信息
used_cpu_sys:12.012184
used_cpu_user:9.453505
used_cpu_sys_children:0.002158
used_cpu_user_children:0.000000
used_cpu_sys_main_thread:11.802969
used_cpu_user_main_thread:9.286577

# Modules 模块加载情况

# Errorstats 错误状态信息
errorstat_NOAUTH:count=6
errorstat_READONLY:count=1
errorstat_WRONGPASS:count=1

# Cluster 集群信息
cluster_enabled:0

# Keyspace 库id与键数量相关信息
db0:keys=1,expires=0,avg_ttl=0


1.基本活动指标:Basic activity

Name Description
connected_clients 客户端连接数
conected_laves slave数量
master_last_io_seconds_ago 最近一次主从交互之后的秒数
keyspace 数据库中的key值总数
1
2
3
4
5
grep -En "connected_clients|conected_laves|master_last_io_seconds_ago|keyspace" redis-Performance.txt
27:connected_clients:7 # # 客户端连接数量
28:connected_slaves:1 # # slave连接数量
128:keyspace_hits:1
129:keyspace_misses:0


2.性能指标:Performance

Name Description
latency Redis响应一个请求的时间
instantaneous_ops_per_sec 平均每秒处理请求总数
hi rate(calculated) 缓存命中率(计算出来的)
1
2
grep -En "latency|instantaneous_ops_per_sec|hi rate" redis-Performance.txt
114:instantaneous_ops_per_sec:3


3.内存指标: Memory

Name Description
used_memory 已使用内存
mem_fragmentation_ratio 内存碎片率
evicted_keys 由于最大内存限制被移除的key的数量
blocked_clients 由于BLPOP,BRPOP,or BRPOPLPUSH而备阻塞的客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
grep -En "used_memory|mem_fragmentation_ratio|evicted_keys|blocked_clients" redis-Performance.txt
32:blocked_clients:0
37:used_memory:2050432
38:used_memory_human:1.96M # # 内存分配器从操作系统分配的内存总量
39:used_memory_rss:5234688
40:used_memory_rss_human:4.99M # # 操作系统看到的内存占用,top命令看到的内存
41:used_memory_peak:2253512
42:used_memory_peak_human:2.15M # # redis内存消耗的峰值
43:used_memory_peak_perc:90.99%
44:used_memory_overhead:1982152
45:used_memory_startup:810376
46:used_memory_dataset:68280
47:used_memory_dataset_perc:5.51%
53:used_memory_lua:37888
54:used_memory_lua_human:37.00K # # lua脚本引擎占用的内存大小
55:used_memory_scripts:0
56:used_memory_scripts_human:0B
67:mem_fragmentation_ratio:2.63
127:evicted_keys:0

4.持久性指标: Persistence

Name Description
rdb_last_save_time 最后一次持久化保存磁盘的时间戳
rdb_changes_sice_last_save 自最后一次持久化以来数据库的更改数
1
2
3
grep -En "rdb_last_save_time|rdb_changes_sice_last_save" redis-Performance.txt
88:rdb_last_save_time:1630230687
89:rdb_changes_since_last_save:0 # 自最后一次持久化以来数据库的更改数

5.错误指标:Error

Name Description
rejected_connections 由于达到maxclient限制而被拒绝的连接数
keyspace_misses key值查找失败(没有命中)次数
master_link_down_since_seconds 主从断开的持续时间(以秒为单位)
1
2
3
4
grep -En "rejected_connections|master_link_down_since_seconds" redis-Performance.txt
9:master_link_down_since_seconds:10937
119:rejected_connections:0
keyspace_misses:0 # key值查找失败(没有命中)次数,出现多次可能是被Hacker Attack


6.其他指标说明

1
2
3
4
5
# 1.复制积压缓冲区如果设置得太小,会导致里面的指令被覆盖掉找不到偏移量,从而触发全量同步
repl_backlog_size: 1048576

# 2.通过查看sync_partial_err变量的次数来决定是否需要扩大积压缓冲区,它表示主从半同步复制失败的次数
sync_partial_err:1


(2) 性能测试工具

1.redis-benchmark 命令

描述: Redis 性能测试是通过同时执行多个命令实现的,该命令是在 redis 的目录下执行的;

官网参考: https://redis.io/topics/benchmarks

语法参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]

# 参数说明:
-h <hostname> Server hostname (default 127.0.0.1)
-p <port> Server port (default 6379)
-s <socket> Server socket (overrides host and port)
-a <password> Password for Redis Auth
-c <clients> Number of parallel connections (default 50)
-n <requests> Total number of requests (default 100000)
-d <size> Data size of SET/GET value in bytes (default 2)
--dbnum <db> SELECT the specified db number (default 0)
-k <boolean> 1=keep alive 0=reconnect (default 1)
-r <keyspacelen> 对SET/GET/INCR使用随机键,对SADD使用随机值使用此选项基准将扩展参数内的字符串_rand_int _uu),该参数的指定范围为0到keyspacelen-1之间的12位数字。
-P <numreq> Pipeline <numreq> requests. Default 1 (no pipeline).
-q Quiet. Just show query/sec values
--csv Output in CSV format
-l Loop. Run the tests forever
-t <tests> Only run the comma separated list of tests. The test names are the same as the ones produced as output.
-I Idle mode. Just open N idle connections and wait.


基础实例:

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
# (1) 同时执行 10000 个请求来检测性能(所有默认测试),通过 -q 参数让结果只显示每秒执行的请求数
$ ./redis-benchmark -h 127.0.0.1 -p 6379 -t set,lpush -n 10000 -q
$ ./redis-benchmark -n 10000 -q
# PING_INLINE: 41493.78 requests per second
# PING_BULK: 44843.05 requests per second
# SET: 42194.09 requests per second
# GET: 44052.86 requests per second
# INCR: 43290.04 requests per second
# LPUSH: 42194.09 requests per second
# RPUSH: 42372.88 requests per second
# LPOP: 42194.09 requests per second
# RPOP: 42194.09 requests per second
# SADD: 43668.12 requests per second
# HSET: 42372.88 requests per second
# SPOP: 44843.05 requests per second
# LPUSH (needed to benchmark LRANGE): 42553.19 requests per second
# LRANGE_100 (first 100 elements): 21367.52 requests per second
# LRANGE_300 (first 300 elements): 9451.80 requests per second
# LRANGE_500 (first 450 elements): 6807.35 requests per second
# LRANGE_600 (first 600 elements): 5350.46 requests per second
# MSET (10 keys): 36363.64 requests per second

# (2) 运行指定项目的测试,例如我们要求在安静模式下仅运行测试 SET 和 LPUSH 命令
$ redis-benchmark -t set,lpush -n 100000 -q
# SET: 74239.05 requests per second
# LPUSH: 79239.30 requests per second


# (3) 指定eval脚本命令进行基准测试
$ redis-benchmark -n 100000 -q script load "redis.call('set','foo','bar')"
# script load redis.call('set','foo','bar'): 69881.20 requests per second

# (4) 选择密钥空间的大小,默认情况下基准测试针对单个密钥运行,而我们通常可以通过使用大键来模拟更真实的工作负载空间。
# 例如,如果我想运行 100 万次 SET 操作,在 10 万个可能的密钥中为每个操作使用一个随机密钥,
$ redis-cli flushall
$ redis-benchmark -t set -r 100000 -n 1000000

# (5) 默认情况下,每个客户端仅在收到上一个命令的回复时发送下一个命令, Redis 支持流水线,因此可以一次发送多个命令可以想象为并行。
# 例如: 使用 16 个命令的流水线在 MacBook Air 11" 中运行基准测试
redis-benchmark -n 1000000 -t set,get -P 16 -q
# SET: 403063.28 requests per second
# GET: 508388.41 requests per second

# (6) 使用 Unix 域套接字形式进行基准测试
$ numactl -C 6 ./redis-benchmark -q -n 100000 -s /tmp/redis.sock -d 256

# (7) 使用 使用 TCP loopback
$ numactl -C 6 ./redis-benchmark -q -n 100000 -d 256

WeiyiGeek.redis-benchmark

WeiyiGeek.redis-benchmark


在Redis、Memcached内存数据库基准测试对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash

# BIN=./redis-benchmark
BIN=./mc-benchmark
payload=32
iterations=100000
keyspace=100000

for clients in 1 5 10 20 30 40 50 60 70 80 90 100 200 300
do
SPEED=0
for dummy in 0 1 2
do
S=$($BIN -n $iterations -r $keyspace -d $payload -c $clients | grep 'per second' | tail -1 | cut -f 1 -d'.')
if [ $(($S > $SPEED)) != "0" ]
then
SPEED=$S
fi
done
echo "$clients $SPEED"
done

最后以下是使用gnuplot生成的图形形式的结果:

WeiyiGeek.Redis、Memcached内存数据库基准测试

WeiyiGeek.Redis、Memcached内存数据库基准测试


影响基准测试要素

  • 1) 工作负载(连接的客户端的数量)
  • 2) 不同版本的Redis
  • 3) 提供服务的服务器物理配置(磁盘、网络、CPU、内存),在多 CPU 插槽服务器上,Redis 性能取决于 NUMA 配置和进程位置。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # 不同虚拟化和裸机服务器上的基准测试结果。
    * 该测试由 50 个同时执行 200 万个请求的客户端完成。
    * 使用环回接口执行测试。
    * 使用 100 万个密钥的密钥空间执行测试。
    * 测试在使用和不使用流水线(16 个命令流水线)的情况下执行。

    # Intel(R) Xeon(R) CPU E5520 @ 2.27GHz(带流水线)/ (无流水线)
    $ ./redis-benchmark -r 1000000 -n 2000000 -t get,set,lpush,lpop -P 16 -q # 优于无流水线。
    SET: 552028.75 requests per second
    GET: 707463.75 requests per second
    LPUSH: 767459.75 requests per second
    LPOP: 770119.38 requests per second

    $ ./redis-benchmark -r 1000000 -n 2000000 -t get,set,lpush,lpop -q
    SET: 122556.53 requests per second
    GET: 123601.76 requests per second
    LPUSH: 136752.14 requests per second
    LPOP: 132424.03 requests per second
  • 4) 与使用相同硬件不使用虚拟化的情况相比,Redis 在 VM 上运行速度较慢(推荐物理机按照Redis为首选)
  • 5) 根据平台的不同,unix 域套接字可以实现比 TCP/IP 环回(例如在 Linux 上)多约 50% 的吞吐量。
  • 6) 与 TCP/IP 环回相比,Unix 域套接字的性能优势在大量使用流水线(即长流水线)时趋于降低。
  • 7) 当使用以太网网络访问 Redis 时,当数据大小保持在以太网数据包大小(约 1500 字节)以下时,使用流水线聚合命令特别有效, 在处理 10 字节、100 字节或 1000 字节的查询几乎会产生相同的吞吐量。


2.redisbench 工具

描述: 官方推荐的redis-benchmark在进行集群的基准测试时,没有办法指定集群模式,此处引入 Redis & Redis Cluster benchmark Tool 更方便对集群基准测试的处理。下载地址: https://github.com/panjiang/redisbench


redisbench 特点

  • 以 Golang 开发构建
  • 可以测试redis单实例
  • 可以测试redis集群
  • 可以利用多核
  • 支持同时在多台机器上运行,用于测试大型redis集群(需要相同的机器硬件)


格式语法:

1
2
3
4
5
6
7
8
./redisbench -h
-a string #Redis instance address or Cluster addresses. IP:PORT[,IP:PORT]
-c int #Clients number for concurrence (default 1)
-cluster #true: cluster mode, false: instance mode
-d int #Data size in bytes (default 1000)
-ma string #addresses for run multiple testers at the same time
-mo int #the order current tester is in multiple testers
-n int #Testing times at every client (default 1)


基础示例:

1
2
3
4
5
6
7
8
9
10
# 测试单实例模式
./redisbench -a 127.0.0.1:6379 -c 200 -n 20000 -d 3

# 测试集群
./redisbench -cluster=true -a 192.168.1.11:6379,192.168.1.11:6380 -c 500 -n 2000 -d 3

# 使用多个测试节点
./redisbench -cluster=true -a 192.168.1.11:6379,192.168.1.11:6380 -c 500 -n 2000 -d 3 -ma 192.168.1.11:9001,192.168.1.11:9002,192.168.1.11:9003 -mo 1 &
./redisbench -cluster=true -a 192.168.1.11:6379,192.168.1.11:6380 -c 500 -n 2000 -d 3 -ma 192.168.1.11:9001,192.168.1.11:9002,192.168.1.11:9003 -mo 2 &
./redisbench -cluster=true -a 192.168.1.11:6379,192.168.1.11:6380 -c 500 -n 2000 -d 3 -ma 192.168.1.11:9001,192.168.1.11:9002,192.168.1.11:9003 -mo 3

Tips: 测试结果会自动打印出:请求值,请求时间,TPS 此处不实际演示使用了,感兴趣的朋友可以自行下载测试。


3.rdb 内存分析工具

描述: RDR 是解析 redis rdbfile 工具。与redis-rdb-tools相比,RDR 是由golang 实现的,速度更快。

  • 分析 Redis 内存中那个 Key 值占用的内存最多
  • 分析出 Redis 内存中那一类开头的 Key 占用最多,有利于内存优化
  • Redis Key 值以 Dashboard 展示,这样更直观

安装下载地址: https://github.com/xueqiu/rdr/releases

注意事项:

  • 1.linux和windows使用前先添加可执行权限 chmod +x rdr_linux

基础语法:

1
2
3
4
# RDR 参数解释
show 网页显示 rdbfile 的统计信息
keys 从 rdbfile 获取所有 key
help 帮助

基础实例(以Linux为例):

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
#1.分析统计多个 Redis rdb中各种类型的使用占比 
./rdr-linux keys *.rdb
SEARCH:PROJECTS_BY_ID:68
SEARCH:PROJECTS_BY_ID:64
SEARCH:PROJECTS_BY_PARENTID_LIST:0
SEARCH:PROJECTS_BY_PARENTID_LIST:64
SEARCH:PROJECTS_BY_PARENTID_LIST:66
SEARCH:PROJECTS_BY_PARENTID_LIST:68
SEARCH:PROJECTS_BY_ID:66
SEARCH:PROJECTSINFO_BY_PARENTID_LIST:64
ALLOW:SEARCH_BY_SFZH:230103197805153637

./rdr-linux dump dump.rdb
{"CurrentInstance": "dump.rdb",
"LargestKeyPrefixes": {
"list": [
{
"Type": "list",
"Key": "site",
"Bytes": 144,
"Num": 1
}
],


#2.网页显示分析结果
./rdr-linux show -p 8080 *.rdb
start parsing...
parse dump.rdb done
parsing finished, please access http://{$IP}:8080


(3) 基准测试实践

3.1 K8s中单实例redis测试

环境说明:

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
# 运行该Pod的主机节点
osImage: Ubuntu 20.04.1 LTS
kernelVersion: 5.4.0-42-generic
kubeProxyVersion: v1.19.6
kubeletVersion: v1.19.6
containerRuntimeVersion: docker://19.3.14

$ Server 相关信息
redis_version:6.2.5
os:Linux 5.4.0-42-generic x86_64
arch_bits:64
gcc_version:10.3.1
process_id:1
process_supervised:no
tcp_port:6379
hz:10
configured_hz:10
lru_clock:3627023
io_threads_active:0

$ redis 配置的内存限额
maxmemory:1073741824
maxmemory_human:1.00G
maxmemory_policy:volatile-lru

$ cpu 相关信息
Model name: Intel(R) Xeon(R) CPU E3-1220 V2 @ 3.10GHz
物理CPU数: 1
逻辑CPU数: 4
CPU核心数: 4


基准测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 测试1.执行1千万次set命令与get命令,其中每次读取得大小为256字节, 请求客户端数默认50。
~$ redis-benchmark -h 10.102.39.181 -a 123456 -d 256 -t set,get -n 10000000 -q
SET: 42445.36 requests per second
GET: 49504.70 requests per second
# - 测试时 Pod 资源峰值
very 1.0s: kubectl top pod -n database redis-cm-0 Tue Sep 7 20:36:50 2021
NAME CPU(cores) MEMORY(bytes)
redis-cm-0 848 18Mi

# 测试2.同样执行1千万次set命令与get命令,其中每次读取得大小为256字节,唯一不同的是采用 流水线 -P 16 进行测试(可以看出每秒set、get请求数显著提升)。
~$ redis-benchmark -h 10.102.39.181 -a 123456 -d 256 -t set,get -n 10000000 -P 16 -q
SET: 96019.98 requests per second
GET: 316575.91 requests per second

# - 测试时 Pod 资源峰值
very 1.0s: kubectl top pod -n database redis-cm-0 Tue Sep 7 20:46:50 2021
NAME CPU(cores) MEMORY(bytes)
redis-cm-0 457m 337Mi


实践测试

  • 1) 通过shell pipe 与 redis pipe插入10万数据进行对比

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    ## Shell-pipe.sh
    #!/bin/bash
    echo "开始时间: $(date +%s)"
    for ((i=0;i<100000;i++));do
    echo -en "helloworld-redis-${i}" | redis-cli -h 10.102.39.181 --no-auth-warning -a 123456 -x set username${i} >> ok.txt
    done
    echo "完成时间: $(date +%s)"

    ## redis-pipe.sh
    #!/bin/bash
    echo "开始时间: $(date +%s)"
    python3 redis-set.py >> set-command.txt
    cat set-command.txt | redis-cli -h 10.102.39.181 -a 123456 --no-auth-warning --pipe
    echo "完成时间: $(date +%s)"

    ## redis-set.py
    tee redis-set.py <<'EOF'
    #!/usr/bin/python
    for i in range(100000):
    print('set name'+str(i)+' helloworld-redis-'+str(i))
    EOF
  • 2) 利用time命令记录了脚本插入的执行效率

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    ## redis pipe 方式插入数据
    ~/k8s/benchmark$ time ./redis-pipe.sh
    # 开始时间:1631020862
    # All data transferred. Waiting for the last reply...
    # Last reply received from server.
    # errors: 0, replies: 100000
    # 完成时间: 1631020862

    # real 0m0.466s
    # user 0m0.126s
    # sys 0m0.035s

    # Keyspace
    db0:keys=100000,expires=0,avg_ttl=0

    ## shell pipe 方式插入数据
    ~/k8s/benchmark$ time ./shell-pip.sh
    # 开始时间: 1631021312
    # 完成时间: 1631021921
    # real 10m9.265s # 程序开始至结束总用时(包括CPU)。
    # user 3m44.411s # 程序本身以及调用子进程的时间。
    # sys 1m55.435s # 由程序本身或者间接调用的系统调用执行时间。
    # Keyspace
    db0:keys=200000,expires=0,avg_ttl=0

Tips : 可以从上面的结果看出两种方式real总耗时量相差之巨大,redis pipe方式效率相比较普通shell pipe方式不是一个量级,所以在开发程序中尽量使用redis pipe管道方式进行提交数据。

1
2
3
4
5
6
7
8
9
# 为了方便后续演示,握又向数据库中插入了80W条数据,只用了大约4s。
开始时间: 1631022423
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 800000
完成时间: 1631022427
real 0m4.023s
user 0m0.885s
sys 0m0.187s


0x01 Redis 安全优化

1.Security

描述: Redis提供的访问控制、代码安全问题、选择恶意输入可从外部触发的攻击等功能,需要我们运维人员进行相应的配置提高安全性。

非特权运行

描述: Redis 不需要 root 权限即可运行,建议以仅用于此目的的非特权redis用户身份运行它,此种方式能最大程度防止CONFIG SET/GET 目录和其他类似运行时配置指令的可能性。。

1
2
3
4
5
6
#设置一个单独的redis账户很有必要,redis crackit就利用到了root用户的特性来重置authorized_keys。首先创建一个redis账户,然后通过该账户启动。
$ useradd redis
$ setsid sudo -u redis redis-server /etc/redis.conf
$ ps -elf|grep redis #可以看到是redis用户启动
# 4 S root 9048 1 0 80 0 - 59753 poll_s 19:43 ? 00:00:00 sudo -u redis redis-server /etc redis.conf
# 4 S redis 9049 9048 0 80 0 - 38471 ep_pol 19:43 ? 00:00:00 redis-server


文件权限

描述: 因为redis密码明文存储在配置文件中,所以我们需要限制redis文件目录访问权限,如设置redis的主目录权限为700(rwx------),如果redis.conf配置文件独立于redis主目录权限修过为600(rw-------)

1
2
3
4
5
6
7
# 文件权限
chmod 700 /opt/redis/redis-5.0.4/
chmod 600 /etc/redis.conf

# 所属者、组
chown redis:redis /etc/redis.conf
chown redis:redis /opt/redis/redis-5.0.4/


接口绑定

描述: 除了网络中受信任的客户端之外,每个人都应该拒绝访问 Redis 端口,因此运行 Redis 的服务器应该只能由使用 Redis 实现应用程序的计算机直接访问。

假如服务器有两个网络接口(一个A区域、一个B区域),如果只需要A区域的机器访问则只绑定到A区域网络接口中,如服务器自身访问则只绑定到本地回环接口上。

1
2
# 通过在redis.conf文件中添加如下一行,可以将 Redis 绑定到单个接口:
bind 127.0.0.1 192.168.1.200

Tips:注意除了您可以绑定IPV4以为你还可绑定IPV6


更改默认服务端口

描述: 除了我们可以指定绑定的接口外,我们还可以更改默认的redis服务端口,可以防止黑客针对于Redis服务扫描探测。

1
2
# 将默认的服务断开从6379变成63791
port 63791


认证配置

描述: 为Redis服务端设置一个认证密码是非常必须,下面讲解 Redis 配置密码认证的几种方式总结:

操作流程:

1
2
3
4
5
6
7
8
9
10
11
# 1.通过redis.conf文件中进行配置,此种方式修改后需要重启Redis。
vim /etc/redis.conf
requirepass WeiyiGeek # WeiyiGeek 即认证密码
masterauth WeiyiGeek # 配置主节点认证密码, 注意若master配置了密码则slave也要配置相应的密码参数否则无法进行正常复制的

# 2.通过命令行进行配置,此种方式的优点无需重启Redis。
redis 127.0.0.1:6379[1]> config set requirepass my_redis
OK
redis 127.0.0.1:6379[1]> config get requirepass
1) "requirepass"
2) "my_redis"


使用密码验证登陆Redis服务器:

1
2
3
4
5
6
# 方式1:密码明文会被记录到系统命令执行历史中(极其不推荐/不安全)
redis-cli -h 127.0.0.1 -p 6379 -a WeiyiGeek

# 方式2:交互式进行配置
redis-cli -h 127.0.0.1 -p 6379
redis 127.0.0.1:6379> auth WeiyiGeek # OK

非常注意: AUTH 命令与其他所有 Redis 命令一样,以未加密的方式发送,因此它无法防范对网络有足够访问权限以执行窃听的攻击者, 所以对应高敏感的数据建议配置TLS 支持(Redis 在所有通信通道上都可选地支持 TLS)以加密数据与命令传输。


禁用特定命令

描述: 我们可以禁用 Redis 中的命令或将它们重命名为不可猜测的名称,以便普通客户端仅限于指定的一组命令,比如漏洞就利用config/save两个命令完成攻击 。

由于redis无用户权限限制,建议将危险的命令使用rename配置项进行禁用或重命名,这样外部不了解重命名规则攻击者,就不能执行这类命令FLUSHDB, FLUSHALL, KEYS, PEXPIRE, DEL, CONFIG, SHUTDOWN, BGREWRITEAOF, BGSAVE, SAVE, SPOP, SREM, RENAME, DEBUG, EVAL

例如: 普通用户可能无法调用Redis CONFIG 命令来更改实例的配置,但提供和删除实例的系统应该能够这样做。

1
2
3
4
5
6
7
8
# redis.conf 配置文件
# 方式1.CONFIG / FLUSHALL命令被重命名为一个不可猜测的名称
rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
rename-command FLUSHALL b840fc02d524045429941cc15f59e41cb7be6c53

# 方式2.通过将其重命名为空字符串来完全禁用它(或任何其他命令)
rename-command CONFIG ""
rename-command FLUSHALL ""

Tips: 注意配置后需要重新redis-server服务。


日志记录

描述: 为Redis创建访问(或Debug)日志(根据需求设置),在建立Redis蜜罐时,如果有攻击尝试时,就开业及时发现监控redis安全状态, 以及可以监控cmdstat_*指标信息报警;

1
2
3
4
5
# 执行info commandstats 看出命令执行的次数、命令耗费的 CPU 时间(单位毫秒)、执行每个命令耗费的平均 CPU 时间(单位毫秒)
cmdstat_get:calls=2,usec=15,usec_per_call=7.50
cmdstat_select:calls=1,usec=9,usec_per_call=9.00
cmdstat_keys:calls=4,usec=1948,usec_per_call=487.00
cmdstat_auth:calls=3123,usec=8291,usec_per_call=2.65

日志记录配置:

1
2
logfile "/usr/local/redis/redis.log" #日志文件存放目录
loglevel verbose #记录访问信息


防范字符串转义和 NoSQL 注入

描述: Redis 协议没有字符串转义的概念,所以一般情况下使用普通客户端库是不可能注入的, 但有可能会通过EVAL和EVALSHA命令执行的 Lua 脚本来构造恶意脚本。

1
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second

解决办法: 应用程序应该避免使用从不受信任的来源获得的字符串来组成 Lua 脚本的主体。

Tips : EVALSHA 通过其 SHA1 摘要评估缓存在服务器端的脚本。脚本使用SCRIPT LOAD命令缓存在服务器端。该命令在其他方面与EVAL相同。


防范由外部客户端精心挑选的输入触发的攻击

描述: 有可能攻击者构造恶意的数据结构插入到 Redis 数据库中, 这可能会触发Redis 内部实现的数据结构的病态(最坏情况)算法复杂性。

例如,攻击者可以通过 Web 表单将一组已知散列到同一桶的字符串提供到散列表中,以便将 O(1)预期时间(平均时间)变为O(N )最坏的情况,消耗比预期更多的 CPU,并最终导致拒绝服务。

解决办法: 为了防止这种特定的攻击,Redis 对哈希函数使用了每次执行的伪随机种子。


防火墙限制访问

描述: 前面针对Redis-server服务层面进行安全配置,此处针对网络层面进行限制,只允许指定的IP地址进行访问,在主机上配置防火墙的优点是防止同一网段的东西流量。

在Linux上系统防火墙设置命令:

1
2
iptables -A INPUT -s x.x.x.x -p tcp --dport 6379 -j ACCEPT  #如果需要其他机器访问或者设置了slave模式,那就记得加上相应的防火墙设置(Centos6)
firewall-cmd --add-rich-rule="rule family="ipv4" source address="x.x.x.x" port protocol="tcp" port="6379" accept" --permanent #(Centos7)

在Windows上系统防火墙设置命令:

1
2
New-NetFirewallRule -Name "redis-server-access" -DisplayName "redis-server" -Description "redis-server 客户端访问防火墙规则" -Direction Inbound -LocalPort 6379 -RemoteAddress x.x.x.x -Protocol TCP -Action Allow -Enabled True
Get-NetFirewallRule -Name "redis-server-access" | Format-Table


禁止redis中存储敏感的明文数据

描述: Redis设计旨在提供高性能的KV服务,至少目前在权限访问控制和数据持久化方面比较弱化,所以从应用层面上,不建议使用Redis来存储敏感信息,例如鉴权的密码。


Redis 安全配置总结示例

安全配置示例:

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
# 配置文件 vim /etc/redis/redis.conf
# 1.信任的内网运行,尽量避免有公网访问(如果存在内网中其他固定IP则需要设置防火墙)
bind 127.0.0.1

# 2.绑定redis监听的网络接口(通过redis配置项bind,可同时绑定多个IP), 把6379改为其他得端口(或者采用unix管道进行数据管理)
port 63791

# 3.开启redis密码认证,并设置高复杂度密码设置,因查询效率高,auth这种命令每秒能处理10w次以上(所以需要增加强度)
# echo -e "weiyigeek"|sha256sum
requirepass 097575a79efcd7ea7b1efa2bcda78a4fc7cbd0820736b2f2708e72c3d21f8b61

# 4.日志文件存放目录以及记录redis访问信息。
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 默认
# warning (only very important / critical messages are logged)
logfile "/usr/local/redis/redis.log"
loglevel verbose

# 5.默认情况下,启用保护模式。只有在以下情况下才应禁用(no)它
# - 您确定希望其他主机的客户端连接到Redis
# - 即使没有配置身份验证,也没有特定的接口集
# - 使用“bind”指令显式列出。
protected-mode yes

# 6.重命名特殊命令(根据需求)
# `FLUSHDB, FLUSHALL, KEYS, PEXPIRE, DEL, CONFIG, SHUTDOWN, BGREWRITEAOF, BGSAVE, SAVE, SPOP, SREM, RENAME, DEBUG, EVAL`
rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
rename-command FLUSHDB b840fc02d524045429941cc15f59e41cb7be6c53
rename-command FLUSHALL b840fc02d524045429941cc15f59e41cb7be6c54
rename-command EVAL b840fc02d524045429941cc15f59e41cb7be6c55
rename-command DEBUG b840fc02d524045429941cc15f59e41cb7be6c56
rename-command SHUTDOWN b840fc02d524045429941cc15f59e41cb7be6c7


2.Performance Optimization

描述: Redis开发和运维人员更加关注的是Redis本身的一些配置优化,例如AOF和RDB的配置优化、数据结构的配置优化等,但是对于操作系统是否需要针对Redis做一些配置优化不甚了解或者不太关心,然而事实证明一个良好的系统操作配置能够为Redis服务良好运行保驾护航。

关键优化项

  • Step 1.vm.overcommit_memory 最佳实践
    Redis在启动时可能会出现这样的日志, 然后弄清楚什么是overcommit?
    描述: Linux 操作系统对大部分申请内存的请求都回复yes以便能运行更多的程序。因为申请内存后并不会马上使用内存,这种技术叫做overcommit。
    1
    2
    3
    4
    5
    6
    7
    8
    # 如果Redis在启动时有上面的日志,说明`vm.overcommit_memory=0`,Redis提示把它设置为1。
    # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the
    command 'sysctl -w vm.overcommit_memory=1' for this to take effect.

    # 注意:本文的可用内存代表物理内存与swap之和。
    # 最佳实践
    - Redis建议把这个值设置为1是为了让fork能够在低内存下也执行成功(设置合理的maxmemory保证机器有20%~30%的闲置内存)。
    - 集中化管理aof重写和rdb的bgsave。
    Tips : 日志中的 Background save 代表的是 bgsave 和 bgrewriteaof,如果当前可用内存不足,操作系统应该如何处理fork。如果vm.overcommit_memory=0,代表如果没有可用内存,就申请内存失败,对应到Redis就是fork执行失败,在Redis的日志会出现:Cannot allocate memory


  • Step 2.vm.swapniess 最佳实践
    描述: swap对于操作系统来比较重要,当物理内存不足时,可以swap out一部分内存页,以解燃眉之急。但世界上没有免费午餐,swap空间由硬盘提供,对于需要高并发、高吞吐的应用来说,磁盘IO通常会成为系统瓶颈。在Linux中,并不是要等到所有物理内存都使用完才会使用到swap,系统参数swppiness会决定操作系统使用swap的倾向程度。swappiness的取值范围是0~100,swappiness的值越大,说明操作系统可能使用swap的概率越高,swappiness值越低,表示操作系统更加倾向于使用物理内存。

如果Linux > 3.5的情况下 vm.swapniess=1 (宁愿swap也不要OOM killer) 否则 vm.swapniess=0 (宁愿OOM killer也不用swap) 从而实现如下两个目标:

1.物理内存充足时候,使Redis足够快。
2.物理内存不足时候,避免Redis死掉(如果当前Redis为高可用,死掉比阻塞更好)。

运维提示:OOM(Out Of Memory) killer机制是指Linux操作系统发现可用内存不足时,强制杀死一些用户进程(非内核进程),来保证系统有足够的可用内存进行分配。


  • Step 3.kernel.mm.transparent_hugepage.enabled 最佳实践
    Redis在启动时可能会看到如下日志:
    1
    WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.

Tips : 从提示看Redis建议修改Transparent Huge Pages (THP)的相关配置,Linux kernel在2.6.38内核增加了Transparent Huge Pages (THP)特性 ,支持大内存页(2MB)分配,默认开启。当开启时可以降低fork子进程的速度,但fork之后,每个内存页从原来4KB变为2MB,会大幅增加重写期间父进程内存消耗。同时每次写命令引起的复制内存页单位放大了512倍,会拖慢写操作的执行时间,导致大量写操作慢查询。

因此Redis日志中建议将此特性进行禁用,禁用方法如下:echo never > /sys/kernel/mm/transparent_hugepage/enabled


  • Step 4.Transparent Huge Pages
    Redis在启动时可能会看到如下日志:WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.

从提示看Redis建议修改Transparent Huge Pages (THP)的相关配置,Linux kernel在2.6.38内核增加了Transparent Huge Pages (THP)特性 ,支持大内存页(2MB)分配,默认开启。当开启时可以降低fork子进程的速度,但fork之后,每个内存页从原来4KB变为2MB,会大幅增加重写期间父进程内存消耗。同时每次写命令引起的复制内存页单位放大了512倍,会拖慢写操作的执行时间,导致大量写操作慢查询。例如简单的incr命令也会出现在慢查询中。因此Redis日志中建议将此特性进行禁用,禁用方法如下:

1
2
3
4
# 配置机器重启后THP配置依然生效
tee -a /etc/rc.local <<'EOF'
echo never > /sys/kernel/mm/transparent_hugepage/enabled
EOF


  • Step 5.OOM killer 优化配置
    OOM killer会在可用内存不足时选择性的杀掉用户进程,它会为每个用户进程设置一个权值,这个权值越高,被“下手”的概率就越高,反之概率越低。每个进程的权值存放在/proc/{progress_id}/oom_score中,这个值是受/proc/{progress_id}/oom_adj的控制,oom_adj在不同的Linux版本的最小值不同,可以参考Linux源码中oom.h(从-15到-17)

oom_adj设置为最小值时,该进程将不会被OOM killer杀掉,设置方法如下:

1
2
3
4
5
6
7
8
# 命令
echo {value} > /proc/${process_id}/oom_adj

# 脚本
for redis_pid in $(pgrep -f "redis-server")
do
echo -17 > /proc/${redis_pid}/oom_adj
done


  • Step 6.设置其打开文件数句柄数以及单个用户最大进程数
    描述: 下面得参数主要设置是单个进程能够使用得Linux最大文件句柄数, 解决在高并发的情况下不会异常报错。在Redis官方提到的建议
    1
    2
    3
    4
    5
    6
    7
    8
    # You requested maxclients of 10000 requiring at least 10032 max file descriptors.
    第一行:Redis建议把open files至少设置成10032,那么这个10032是如何来的呢?因为maxclients的默认是10000,这些是用来处理客户端连接的,除此之外,Redis内部会使用最多32个文件描述符,所以这里的10032 = 10000 + 32。

    # Redis can’t set maximum open files to 10032 because of OS error: Operation not permitted.
    第二行:Redis不能将open files设置成10032,因为它没有权限设置。

    # Current maximum open files is 4096. Maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase ‘ulimit –n’.
    第三行:当前系统的open files是4096,所以maxclients被设置成4096-32=4064个,如果你想设置更高的maxclients,请使用ulimit -n来设置。从上面的三行日志分析可以看出open files的限制优先级比maxclients大。
    解决办法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 临时
    ulimit –Sn 10032
    # 永久
    tee etc/security/limits.conf <<'EOF'
    * soft nofile 10032
    * hard nofile 10032
    * soft nproc 65535
    * hard nproc 65535
    EOF


  • Step 7.TCP backlog 日志队列优化
    描述: Redis 默认的 tcp-backlog 为511 我们可以通过修改配置 tcp-backlog 进行调整,如果Linux的tcp-backlog 小于Redis设置的 tcp-backlog,那么在Redis启动时会看到如下日志:
    1
    # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
    解决方法:
    1
    2
    3
    4
    5
    6
    # 查看
    cat /proc/sys/net/core/somaxconn
    128

    # 修改
    echo 511 > /proc/sys/net/core/somaxconn


  • Step 8.保证Redis服务器时钟的一致性
    描述: 我们知道像Redis Sentinel和Redis Cluster这两种需要多个Redis实例的类型,可能会涉及多台服务器。虽然Redis并没有对多个服务器的时钟有严格的要求,但是假如多个Redis实例所在的服务器时钟不一致,对于一些异常情况的日志排查是非常困难的,例如Redis Cluster的故障转移,如果日志时间不一致,对于我们排查问题带来很大的困扰(注:但不会影响集群功能,集群节点依赖各自时钟)。一般公司里都会有NTP服务用来提供标准时间服务,从而达到纠正时钟的效果

例如:每小时的同步1次NTP服务

1
0 * * * * /usr/sbin/ntpdate ntp.xx.com > /dev/null 2>&1


Redis 性能优化总结示例

系统优化配置

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
# - 设置内存分配策略
sudo sysctl -w vm.overcommit_memory=1

# - 尽量使用物理内存(速度快)针对内核版本大于>=3.x (宁愿swap也不要OOM killer)
sudo sysctl -w vm.swapniess=1

# - 禁用 THP 特性减少内存消耗
echo never > /sys/kernel/mm/transparent_hugepage/enabled

# - OOM killer 特性优化
for redis_pid in $(pgrep -f "redis-server")
do
echo -17 > /proc/${redis_pid}/oom_adj
done

# - 设置其打开文件数句柄数以及单个用户最大进程数
tee etc/security/limits.conf <<'EOF'
* soft nofile 10032
* hard nofile 10032
* soft nproc 65535
* hard nproc 65535
EOF

# - SYN队列长度设置此参数可以容纳更多等待连接的网络。
echo 511 > /proc/sys/net/core/somaxconn
sudo sysctl -w net.ipv4.tcp_max_syn_backlog=2048

# - 每个小时同步一次时间
0 * * * * /usr/sbin/ntpdate ntp.xx.com > /dev/null 2>&1


应用配置优化

1
2
3
4
5
6
7
8
# 最大客户端上限连接数(需根据实际情况调整与系统的open files有关,其数量值为open files(10032) - 32)
maxclients 10000

# 集群配置优化关键项
# 集群超时时间,如果此时间设置太小时由于网络波动可能会导致进行重新选Master的操作
cluster-node-timeout 5000
# 主节点写入后必须同步到一台从上,防止数据丢失的有效方法(要求是其从节点必须>=1)
min‐replicas‐to‐write 1


应用使用中优化

1
2
3
4
5
6
7
8
9
10
11
# (1) 查询执行时间指的是不包括像客户端响应(talking)、发送回复等 IO 操作,而单单是执行一个查询命令所耗费的时间
redis> SLOWLOG LEN # 管理 redis 的慢日志查看当前日志的数量
redis> SLOWLOG RESET # 清空 slowlog 此时上面 LEN 变成 0

# (2) 断开耗时连接
# 列出所有已连接客户端
redis 127.0.0.1:6379> CLIENT LIST
addr=127.0.0.1:43501 fd=5 age=10 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
# 杀死当前客户端的连接
redis 127.0.0.1:6379> CLIENT KILL 127.0.0.1:43501
OK