关于redis:如何保证Redis数据库性能与安全看这篇Redis性能测试及安全优化配置指南就够了

3次阅读

共计 27071 个字符,预计需要花费 68 分钟才能阅读完成。

本章目录
0x00 Redis 性能指标监控

(1) 性能指标

1. 根本流动指标:Basic activity
2. 性能指标:Performance
3. 内存指标: Memory
4. 持久性指标: Persistence
5. 谬误指标:Error
6. 其余指标阐明

(2) 性能测试工具

1.redis-benchmark 命令
2.redisbench 工具
3.rdb 内存剖析工具

(3) 基准测试实际

3.1 K8s 中单实例 redis 测试

0x01 Redis 平安优化

1.Security

非特权运行
文件权限
接口绑定
更改默认服务端口
认证配置
禁用特定命令
日志记录
防备字符串本义和 NoSQL 注入
防备由内部客户端精心筛选的输出触发的攻打
防火墙限度拜访
禁止 redis 中存储敏感的明文数据
Redis 平安配置总结示例

2.Performance Optimization

要害优化项
Redis 性能优化总结示例


前置常识学习补充
1.Redis 数据库根底入门介绍与装置 – https://blog.weiyigeek.top/20…

2.Redis 数据库根底数据类型介绍与应用 – https://blog.weiyigeek.top/20…

3.Redis 根底运维之原理介绍和主从配置 – https://blog.weiyigeek.top/20…

4.Redis 根底运维之哨兵和集群装置配置 – https://blog.weiyigeek.top/20…

5.Redis 根底运维之在 K8S 中的装置与配置 – https://blog.weiyigeek.top/20…

7.Redis 数据库容灾备份企业实战 – https://blog.weiyigeek.top/20…

8.Redis 数据库客户端操作实际及入坑出坑 – https://blog.weiyigeek.top/20…


0x00 Redis 性能指标监控

(1) 性能指标

Redis 服务端常见指标参数:

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 值总数
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) 缓存命中率(计算出来的)
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 而备阻塞的客户端
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 自最初一次长久化以来数据库的更改数
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 主从断开的持续时间(以秒为单位)
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

<br/>

6. 其余指标阐明

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

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

(2) 性能测试工具

1.redis-benchmark 命令

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

官网参考: https://redis.io/topics/bench…

语法参数:

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) 同时执行 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

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

#!/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 生成的图形模式的后果:

影响基准测试因素

  • 1) 工作负载(连贯的客户端的数量)
  • 2) 不同版本的 Redis
  • 3) 提供服务的服务器物理配置(磁盘、网络、CPU、内存),在多 CPU 插槽服务器上,Redis 性能取决于 NUMA 配置和过程地位。

  • 该测试由 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/r…

redisbench 特点

  • 以 Golang 开发构建
  • 能够测试 redis 单实例
  • 能够测试 redis 集群
  • 能够利用多核
  • 反对同时在多台机器上运行,用于测试大型 redis 集群(须要雷同的机器硬件)

格局语法:

./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)

根底示例:

# 测试单实例模式
./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 此处不理论演示应用了,感兴趣的敌人能够自行下载测试。

<br/>

3.rdb 内存剖析工具

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

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

装置下载地址: https://github.com/xueqiu/rdr…

注意事项:

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

根底语法:

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

根底实例(以 Linux 为例):

#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 测试

环境阐明:

# 运行该 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

<br/>

基准测试:

# 测试 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

<br/>

实际测试

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

    ## 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 命令记录了脚本插入的执行效率

    ## 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 管道形式进行提交数据。

# 为了不便后续演示,握又向数据库中插入了 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 目录和其余相似运行时配置指令的可能性。。

# 设置一个独自的 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-------)

# 文件权限
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/

<br/>

接口绑定

形容: 除了网络中受信赖的客户端之外,每个人都应该回绝拜访 Redis 端口,因而运行 Redis 的服务器应该只能由应用 Redis 实现应用程序的计算机间接拜访。

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

# 通过在 redis.conf 文件中增加如下一行,能够将 Redis 绑定到单个接口:bind 127.0.0.1 192.168.1.200

Tips:留神除了您能够绑定 IPV4 认为你还可绑定 IPV6

更改默认服务端口

形容: 除了咱们能够指定绑定的接口外,咱们还能够更改默认的 redis 服务端口,能够避免黑客针对于 Redis 服务扫描探测。

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

<br/>

认证配置

形容: 为 Redis 服务端设置一个认证明码是十分必须,上面解说 Redis 配置明码认证的几种形式总结:

操作流程:

# 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"  

<br/>

应用明码验证登陆 Redis 服务器:

# 形式 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) 以加密数据与命令传输。

<br/>

禁用特定命令

形容: 咱们能够禁用 Redis 中的命令或将它们重命名为不可猜想的名称,以便一般客户端仅限于指定的一组命令,比方破绽就利用 config/save 两个命令实现攻打。

因为 redis 无用户权限限度,倡议将危险的命令应用 rename 配置项进行禁用或重命名,这样内部不理解重命名规定攻击者,就不能执行这类命令FLUSHDB, FLUSHALL, KEYS, PEXPIRE, DEL, CONFIG, SHUTDOWN, BGREWRITEAOF, BGSAVE, SAVE, SPOP, SREM, RENAME, DEBUG, EVAL

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

# 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_* 指标信息报警;

# 执行 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

日志记录配置:

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

防备字符串本义和 NoSQL 注入

形容: Redis 协定没有字符串本义的概念,所以个别状况下应用一般客户端库是不可能注入的, 但有可能会通过 EVAL 和 EVALSHA 命令执行的 Lua 脚本来结构歹意脚本。

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

解决办法: 应用程序应该防止应用从不受信赖的起源取得的字符串来组成 Lua 脚本的主体。

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

<br/>

防备由内部客户端精心筛选的输出触发的攻打

形容: 有可能攻击者结构歹意的数据结构插入到 Redis 数据库中, 这可能会触发 Redis 外部实现的数据结构的病态(最坏状况)算法复杂性。

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

解决办法: 为了避免这种特定的攻打,Redis 对哈希函数应用了每次执行的伪随机种子。

<br/>

防火墙限度拜访

形容: 后面针对 Redis-server 服务层面进行平安配置,此处针对网络层面进行限度,只容许指定的 IP 地址进行拜访,在主机上配置防火墙的长处是避免同一网段的货色流量。

在 Linux 上零碎防火墙设置命令:

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 上零碎防火墙设置命令:

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 平安配置总结示例

平安配置示例:

# 配置文件 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。

    # 如果 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 `

<br/>

  • 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 操作系统发现可用内存有余时,强制杀死一些用户过程(非内核过程),来保证系统有足够的可用内存进行调配。

<br/>

  • Step 3.kernel.mm.transparent_hugepage.enabled 最佳实际
    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.

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

<br/>

  • 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 日志中倡议将此个性进行禁用,禁用办法如下:

# 配置机器重启后 THP 配置仍然失效
tee -a /etc/rc.local <<'EOF'
echo never >  /sys/kernel/mm/transparent_hugepage/enabled
EOF

<br/>

  • 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 杀掉,设置办法如下:

# 命令
echo {value} > /proc/${process_id}/oom_adj

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

<br/>

  • Step 6. 设置其关上文件数句柄数以及单个用户最大过程数
    形容: 上面得参数次要设置是单个过程可能应用得 Linux 最大文件句柄数, 解决在高并发的状况下不会异样报错。在 Redis 官网提到的倡议

    # 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 大。

    解决办法:

    # 长期
    ulimit –Sn 10032
    # 永恒
  • soft nofile 10032
  • hard nofile 10032
  • soft nproc 65535
  • hard nproc 65535
    EOF

<br/>

  • Step 7.TCP backlog 日志队列优化
    形容: Redis 默认的 tcp-backlog 为 511 咱们能够通过批改配置 tcp-backlog 进行调整,如果 Linux 的 tcp-backlog 小于 Redis 设置的 tcp-backlog,那么在 Redis 启动时会看到如下日志:

    # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

    解决办法:

    # 查看
    cat /proc/sys/net/core/somaxconn
    128
    
    # 批改
    echo 511 > /proc/sys/net/core/somaxconn

<br/>

  • Step 8. 保障 Redis 服务器时钟的一致性
    形容: 咱们晓得像 Redis Sentinel 和 Redis Cluster 这两种须要多个 Redis 实例的类型,可能会波及多台服务器。尽管 Redis 并没有对多个服务器的时钟有严格的要求,然而如果多个 Redis 实例所在的服务器时钟不统一,对于一些异常情况的日志排查是十分艰难的,例如 Redis Cluster 的故障转移,如果日志工夫不统一,对于咱们排查问题带来很大的困扰(注:但不会影响集群性能,集群节点依赖各自时钟)。个别公司里都会有 NTP 服务用来提供规范工夫服务,从而达到纠正时钟的成果

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

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

Redis 性能优化总结示例

系统优化配置

# - 设置内存调配策略
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

利用配置优化

# 最大客户端下限连接数(需依据理论状况调整与零碎的 open files 无关,其数量值为 open files(10032) - 32)maxclients 10000

# 集群配置优化要害项
# 集群超时工夫,如果此工夫设置太小时因为网络稳定可能会导致进行从新选 Master 的操作
cluster-node-timeout 5000
# 主节点写入后必须同步到一台从上,避免数据失落的无效办法(要求是其从节点必须 >=1)
min‐replicas‐to‐write 1

利用应用中优化

# (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

原文链接: https://blog.weiyigeek.top/20…


文章书写不易,如果您感觉这篇文章还不错的,请给这篇专栏【点个赞、投个币、收个藏、关个注,转个发】(世间五大情),这将对我的必定,谢谢!。

本文章起源 我的 Blog 站点 或 WeiyiGeek 公众账号 以及 我的 BiliBili 专栏 (技术交换、友链替换请邮我哟 ), 谢谢反对!(๑′ᴗ‵๑) ❤
欢送各位气味相投的敌人一起学习交换,如文章有误请留下您贵重的常识倡议,通过邮箱【master#weiyigeek.top】分割我哟!

正文完
 0