1. 程式人生 > 其它 >如何保證Redis效能與安全?看這篇Redis資料庫效能測試及安全優化配置指南就夠了

如何保證Redis效能與安全?看這篇Redis資料庫效能測試及安全優化配置指南就夠了

本章目錄
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/2019/4-17-49.html

2.Redis資料庫基礎資料型別介紹與使用 - https://blog.weiyigeek.top/2020/5-17-50.html

3.Redis基礎運維之原理介紹和主從配置 - https://blog.weiyigeek.top/2019/4-17-97.html

4.Redis基礎運維之哨兵和叢集安裝配置 - https://blog.weiyigeek.top/2019/4-17-576.html

5.Redis基礎運維之在K8S中的安裝與配置 - https://blog.weiyigeek.top/2019/4-17-524.html

7.Redis資料庫容災備份企業實戰 - https://blog.weiyigeek.top/2019/4-17-51.html

8.Redis資料庫客戶端操作實踐及入坑出坑 - https://blog.weiyigeek.top/2019/4-17-577.html


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

6.其他指標說明

# 1.複製積壓緩衝區如果設定得太小,會導致裡面的指令被覆蓋掉找不到偏移量,從而觸發全量同步
repl_backlog_size: 1048576

# 2.通過檢視sync_partial_err變數的次數來決定是否需要擴大積壓緩衝區,它表示主從半同步複製失敗的次數
sync_partial_err:1

(2) 效能測試工具

1.redis-benchmark 命令

描述: Redis 效能測試是通過同時執行多個命令實現的,該命令是在 redis 的目錄下執行的;

官網參考: https://redis.io/topics/benchmarks

語法引數:

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. 工作負載(連線的客戶端的數量)
    1. 不同版本的Redis
    1. 提供服務的伺服器物理配置(磁碟、網路、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
    1. 與使用相同硬體不使用虛擬化的情況相比,Redis 在 VM 上執行速度較慢(推薦物理機按照Redis為首選)
    1. 根據平臺的不同,unix 域套接字可以實現比 TCP/IP 環回(例如在 Linux 上)多約 50% 的吞吐量。
    1. 與 TCP/IP 環回相比,Unix 域套接字的效能優勢在大量使用流水線(即長流水線)時趨於降低。
    1. 當使用乙太網網路訪問 Redis 時,當資料大小保持在乙太網資料包大小(約 1500 位元組)以下時,使用流水線聚合命令特別有效, 在處理 10 位元組、100 位元組或 1000 位元組的查詢幾乎會產生相同的吞吐量。

2.redisbench 工具

描述: 官方推薦的redis-benchmark在進行叢集的基準測試時,沒有辦法指定叢集模式,此處引入 Redis & Redis Cluster benchmark Tool 更方便對叢集基準測試的處理。下載地址: https://github.com/panjiang/redisbench


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 此處不實際演示使用了,感興趣的朋友可以自行下載測試。


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

基礎語法:

# 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

基準測試:

# 測試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萬資料進行對比
## 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
    1. 利用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/

介面繫結

描述: 除了網路中受信任的客戶端之外,每個人都應該拒絕訪問 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

認證配置

描述: 為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"  

使用密碼驗證登陸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)以加密資料與命令傳輸。


禁用特定命令

描述: 我們可以禁用 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相同。


防範由外部客戶端精心挑選的輸入觸發的攻擊

描述: 有可能攻擊者構造惡意的資料結構插入到 Redis 資料庫中, 這可能會觸發Redis 內部實現的資料結構的病態(最壞情況)演算法複雜性。

例如,攻擊者可以通過 Web 表單將一組已知雜湊到同一桶的字串提供到散列表中,以便將 O(1)預期時間(平均時間)變為O(N )最壞的情況,消耗比預期更多的 CPU,並最終導致拒絕服務。

解決辦法: 為了防止這種特定的攻擊,Redis 對雜湊函式使用了每次執行的偽隨機種子。


防火牆限制訪問

描述: 前面針對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


  • 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在啟動時可能會看到如下日誌:
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日誌中建議將此特性進行禁用,禁用方法如下:

# 配置機器重啟後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殺掉,設定方法如下:

# 命令
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官方提到的建議
# 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
# 永久
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啟動時會看到如下日誌:
# 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

  • 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

文章書寫不易,如果您覺得這篇文章還不錯的,請給這篇專欄 【點個贊、投個幣、收個藏、關個注,轉個發】(人間五大情),這將對我的肯定,謝謝!。

本文章來源 我的Blog站點WeiyiGeek 公眾賬號 以及 我的BiliBili專欄 (技術交流、友鏈交換請郵我喲),謝謝支援!(๑′ᴗ‵๑) ❤
歡迎各位志同道合的朋友一起學習交流,如文章有誤請留下您寶貴的知識建議,通過郵箱【master#weiyigeek.top】聯絡我喲!