Redis 性能调优

记录下Redis的一些优化点,以后可能随时会有用到。

Redis 监控

这里把一些常见的监控命令总结一下,时不时都会用到。

redis-cli

命令行客户端。

1
2
3
4
5
6
# 创建连接,可以用PING-PONG来检查连接是否OK
redis-cli -h localhost -p 6379
# 监控Redis的连接和读写操作
redis-cli -h localhost -p 6379 monitor
# Redis服务器的统计信息
redis-cli -h localhost -p 6379 info
  • 内存使用
    Memory下可以查看 Redis 内存使用情况。如果 Redis 使用的内存超出了可用的物理内存大小,那么 Redis 很可能系统会被杀掉。针对这一点,你可以通过 info 命令对 used_memory 和 used_memory_peak 进行监控,为使用内存量设定阀值,并设定相应的报警机制。当然,报警只是手段,重要的是你得预先计划好,当内存使用量过大后,你应该做些什么,是清除一些没用的冷数据,还是把 Redis 迁移到更强大的机器上去。
  • 持久化
    Persistence下可以查看 RDB 和 AOF 的备份情况。如果因为你的机器或 Redis 本身的问题导致 Redis 崩溃了,那么你唯一的救命稻草可能就是 dump 出来的 rdb 文件了,所以,对 Redis dump 文件进行监控也是很重要的。可以通过对 rdb_last_save_time 进行监控,了解最近一次 dump 数据操作的时间,还可以通过对 rdb_changes_since_last_save 进行监控来获得如果这时候出现故障,会丢失(即已改变)多少数据。
  • Keys
    通过获取 Keyspace 中的结果得到各个数据库中 key 的数量
  • QPS
    即每分钟执行的命令个数,即:(total_commands_processed2-total_commands_processed1)/span,为了实时得到 QPS,可以设定脚本在后台运行,记录过去几分钟的 total_commands_processed。在计算 QPS 时,利用过去的信息和当前的信息得出 QPS 的估计值。

redis-stat

Redis 服务器的实时信息。
这个命令不是 Redis 官方提供的,而是一个三方用 ruby 写的监控程序,安装起来有点麻烦,这里就不说明了。

1
2
3
4
5
6
7
# usage:redis-stat [HOST[:PORT] ...] [INTERVAL [COUNT]]
# 每1s收集一次
redis-stat 1
# 指定主机和服务器端口,间隔为1s,收集10次
redis-stat localhost:6379 1 10
# 启动一个redis-stat服务进程,提供一个Dashboard来查看Redis服务器的状态,按如下启动,可以访问 `localhost:8080` 查看
redis-stat localhost:6379 --server=8080 5 --daemon

-a, –auth=PASSWORD 密码
-v, –verbose 展示更多信息
–style=STYLE 输出样式:unicode|ascii
–no-color 去掉颜色
–csv[=CSV_FILE] 打印或将结果保存到 CSV 文件内
–es=ELASTICSEARCH_URL 将结果发送到 ElasticSearch:[http://]HOST[:PORT][/INDEX]
–server[=PORT] 启动 redis-stat 服务器(默认端口是 63790)
–daemon 启动 redis-stat 作为守护进程,必须和 –server 选项一起使用
–version 版本
–help 帮助信息

slowlog

慢查询日志。
可以在 redis.conf 中配置:

1
2
3
4
# 记录执行时间超过5秒的查询
config set slowlog-log-slower-than 5000
# 最多保存25条日志
config set slowlog-max-len 25

使用 redis-cli 登录查看慢查询日志:

1
2
# 获取10条日志
slowlog get 10

redis-benchmark

redis-benchmark 是 Redis 官方提供的 Redis 服务器性能基准测试工具:
-t 选择你想测试的命令,比如 redis-benchmark -t set
-p 指定 port redis-benchmark -p 6379
-l 一直循环
-c 指定客户端数量
-n 指定 request 数量
-q Quiet,不显示额外信息(多少时间内完成了多少条之类的),只显示 query/sec 的值

1
2
3
4
5
6
7
8
# 测试并发连接性能,100个并发连接,总共发100000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
# 测试大数据包读写性能
redis-benchmark -h localhost -p 6379 -q -d 100
# 只测试某些操作的性能
redis-benchmark -h localhost -p 6379 -t set,lpush -n 100000 -q
# 只测试某些数值存取的性能
redis-benchmark -h localhost -p 6379 -q script load "redis.call('set', 'foo', 'bar')"

RDB 文件分析

Redis 内存比较大的时候不容易查出是哪些 key 比较占空间,这时可以使用 redis-rdb-tools 这种工具来查看报告。

Redis性能问题排查

哪些场景会导致Redis阻塞?

Redis实例运行期间会和多种对象进行交互:

  • 客户端:网络 IO,键值对增删改查操作,数据库操作;
  • 磁盘:生成 RDB 快照,记录 AOF 日志,AOF 日志重写;
  • 主从节点:主库生成、传输 RDB 文件,从库接收 RDB 文件、清空数据库、加载 RDB 文件;
  • 切片集群实例:向其他实例传输哈希槽信息,数据迁移。

如果Redis变慢,可能是什么导致的?

  1. Redis是单线程模型,如果前面有请求执行比较慢,后面的都要排队等着;
    耗时的操作包括:
    • 集合全量查询和聚合操作:比如HGETALL、SMEMBERS
    • 操作bigkey:写入一个bigkey在分配内存时需要消耗更多的时间,同样,删除bigkey释放内存同样会产生耗时;
      特别是删除大key时需要释放内存,操作系统需要将释放掉的内存块插入到一个空闲内存块的链表,以便后续进行管理和再分配(指malloc/free)。
    • 使用复杂度过高的命令:例如SORT/SUNION/ZUNIONSTORE,或者O(N)命令,但是N很大,例如lrange key 0 -1一次查询全量数据;
    • 大量key集中过期:Redis的过期机制也是在主线程中执行的,大量key集中过期会导致处理一个请求时,耗时都在删除过期key,耗时变长;
    • 淘汰策略:淘汰策略也是在主线程执行的,当内存超过Redis内存上限后,每次写入都需要淘汰一些key,也会造成耗时变长;
    • AOF刷盘开启always机制:每次写入都需要把这个操作刷到磁盘,写磁盘的速度远比写内存慢,会拖慢Redis的性能;
    • 主从全量同步生成RDB:虽然采用fork子进程生成数据快照,但fork这一瞬间也是会阻塞整个线程的,实例越大,阻塞时间越久;
      对此,需要业务人员主动规避上述可能导致超时的情况;
  2. 并发非常大,单线程读写客户端IO数据存在性能瓶颈,虽然采用IO多路复用机制,但是读写客户端数据依旧是同步IO,只能单线程依次读取客户端的数据,无法利用到CPU多核。
    对此,Redis6.0推出了多线程,可以在高并发场景下利用CPU多核多线程读写客户端数据。

如何检测Redis是否变慢?

如何排查Redis是否真的变慢,一个直接的方法是查看Redis的响应延迟,但是响应延迟多长算慢呢?不同硬件性能不同,这个值的判断也是不同的,并没有绝对的标准。
另一种方法是测算当前环境下的 Redis 基线性能,也就是一个系统在低压力、无干扰下的基本性能,这个性能只由当前的软硬件配置决定。
从 2.8.7 版本开始,redis-cli 命令提供了–intrinsic-latency 选项,可以用来监测和统计测试期间内的最大延迟,这个延迟可以作为 Redis 的基线性能。
一般我们可以把Redis基线性能和响应延迟结合起来判断Redis是否变慢了,如果观察到Redis延迟是其基线性能的2倍以上,就可以认定Redis变慢了。

Redis性能优化

内存优化

  1. 压缩的类型
    在数据量比较少时,Redis会使用占用内存更少的数据类型,包括hash、list、set、zset均为如此,占用的内存大小甚至可以达到普通类型的1/5。
    hash-max-ziplist-entries 512
    hash-max-ziplist-value 64
    zset-max-ziplist-entries 128
    zset-max-ziplist-value 64
    set-max-intset-entries 512
    压缩的类型是“拿时间换空间”,因此效率肯定是比不上普通类型的,只是数据量比较小的情况下二者是差不多的,试想:虽然一个压缩类型对象并不能省多少空间,但是如果很多很多对象都经过压缩,那么最终总体上就能省很多空间了。
    当对象占用内存超过配置的最大值,Redis会自动将其转换为普通类型。
  2. 尽量使用散列表
    小散列表占用的内存非常小

慢查询命令

使用Redis前必须先了解各种指令的复杂度,比如get、set是O(1)的,set的smembers是O(N)的。
当发现性能变慢时,可以通过:

  1. Redis日志查询变慢的请求;
  2. latency monitor工具查询变慢请求;

如果确实有大量满查询命令,可以:

  1. 用其他高效命令代替;
  2. sort、sunion、sinter这些复杂操作可以将逻辑转移到客户端完成。
    如果业务逻辑就是得用慢查询命令完成,那可以考虑采用性能更好的CPU,从而更快地完成查询命令。

过期key操作

Redis4.0之前,淘汰key的机制中,删除操作是阻塞的,而淘汰机制会不断抽样,直到过期的key只占不到25%的比例,因此淘汰很有可能会影响主线程;在Redis4.0之后采用异步线程机制来减少阻塞影响)。
比如同一时间有很多key设置了相同的过期时间,就会导致这种情况。

批量请求优化

使用管道Pipelineing:

1
$ (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379

优点:

  1. 一次连接就可以执行多次请求;

缺点:

  1. 使用管道发送命令时,服务器将被迫回复一个队列答复,占用很多内存。所以,如果你需要发送大量的命令,最好是把他们按照合理数量分批次的处理,例如10K的命令,读回复,然后再发送另一个10k的命令,等等。这样速度几乎是相同的,但是在回复这10k命令队列需要非常大量的内存用来组织返回数据内容。

大量数据插入

大量数据插入时会面临以下问题:

  1. 一个一个插入的话会有很多时间浪费在请求的往返上,因此需要批量执行
    批量执行可以是:管道、lua脚本
  2. 批量插入的过程怎么保证所有key都能插入成功
    1
    2
    3
    4
    SET Key0 Value0
    SET Key1 Value1
    ...
    SET KeyN ValueN
    如果是如下的pipeline,其实并不可靠,因为执行过程中不能检查错误。
    (cat data.txt; sleep 10) | nc localhost 6379 > /dev/null
    一种更好的方式是使用redis-cli中的pipe mode,期间会把错误输出到终端:
    cat data.txt | redis-cli --pipe

大 Key 问题

大key大的是value。

大 Key 有两种状况:

  1. Redis 中单个简单的 Key 存储的 value 很大
  2. hash、set、zset、list 中存储的元素过多(以万为单位)。

由于 Redis 的单线程模型,读写大 Key 时服务器的耗时可能会比较长、甚至阻塞。

分析大key可以通过--bigkeys进行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
hgc@hgc-X555LD:~$ redis-cli -h 127.0.0.1 -p 6379 -n 0 --bigkeys

# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).

[00.00%] Biggest string found so far '143' with 35 bytes
[00.00%] Biggest string found so far 'a' with 184 bytes
[73.33%] Biggest hash found so far 'b' with 1 fields

-------- summary -------

Sampled 15 keys in the keyspace!
Total key length in bytes is 41 (avg len 2.73)

Biggest hash found 'b' has 1 fields
Biggest string found 'a' has 184 bytes

0 lists with 0 items (00.00% of keys, avg size 0.00)
1 hashs with 1 fields (06.67% of keys, avg size 1.00)
14 strings with 639 bytes (93.33% of keys, avg size 45.64)
0 streams with 0 entries (00.00% of keys, avg size 0.00)
0 sets with 0 members (00.00% of keys, avg size 0.00)
0 zsets with 0 members (00.00% of keys, avg size 0.00)

bigkeys是通过scan指令来实现的,所以并不会造成服务器长时间的阻塞,当然这种数据库全扫命令最好还是少用。

解决方案一般是能拆则拆,对于单个大 Key 的情况:
1.1 将大 Key 进行分割,拆成几个小的 key-value,使用 multiGet 获取值。
这样分拆的意义是将单次操作的压力分摊到多个 Redis 实例上,降低对单个 Redis 的 IO 影响,而且大 Key 拆分之后每次只查询一部分,减小了 IO 阻塞的风险。
为了均匀分割,可以对 field 进行 hash 并通过质数 N 取余,将余数加到 key 上面。
1.2 将大 Key 拆分成多个 key-value,并将这些存储在一个 hash 中,每个 field 代表一个具体的属性,使用 hget、hmget 来获取部分的 value,使用 hset、hmset 来更新部分属性

对于 hash、set、zset、list 中存储的元素过多的情况,可以控制将 field 分散到多个集合内。
比如以下代码将属于一个大 hash 内的 field 分散到 10000 个拆分后的小 hash 内:

1
2
3
newHashKey = hashKey + (hash(field) % 10000)
hset(newHashKey, field, value)
hget(newHashKey, field)

对于一些需要考虑顺序的场景,比如 lpop、zrange,需要在 hash 函数上做些文章,比如按照时间来拆分。

Redis与CPU的关系

在现代CPU架构中,每个物理核心都会有一个私有的L1 cache和L2 cache,不同核之间共用一个L3缓存,尽可能避免访问主存。
应用程序会被分配到一个CPU上执行,但是进程调度机制可能会将Redis进程调度到另一个核心上,导致之前加载的缓存都白费了。
可以通过taskset命令将Redis进程绑定在一个核上运行:

1
2
# -c 用于设置要绑定的核编号
taskset -c 0 ./redis-server

内存碎片

Redis底层分配内存函数是jmalloc,它分配的内存大小总是2的幂倍数,如果分配的内存大小比实际使用的要多一些,就会产生内存碎片,内存碎片多了后就会有很多内存的浪费情况,特别是分配键值对的大小不一的情况下,内存碎片会尤其严重

查看内存碎片

查看内存碎片情况很容易,可以直接使用info命令:

1
2
3
4
5
6
7
8
10.32.140.14:6211> info memory
# Memory
used_memory:1441123632
used_memory_human:1.34G
used_memory_rss:1853603840
used_memory_rss_human:1.73G
...
mem_fragmentation_ratio:1.29

其中mem_fragmentation_ratio这个指标就是Redis当前的内存碎片率,它的值其实就是used_memory_rssused_memory相除的结果(used_memory_rss/used_memory),前者是OS分配给Redis的内存空间,包含碎片,后者不包含碎片。

  • mem_fragmentation_ratio[1,1.5]范围内是合理的,因为内存碎片无法完全避免
  • mem_fragmentation_ratio大于1.5则需要采取一些措施来减小内存碎片率。

处理内存碎片

  • 重启Redis实例。这是最简单的方法,但是重启后Redis内存中的数据会全部丢失,除非进行了持久化,但恢复也是需要不少时间的。
  • 从Redis4.0-RC3后,Redis自身提供了内存碎片清理的功能,也即将内存数据拷贝到一块新的内存位置,可以设置参数控制碎片清理的开始、结束时机、占用的内存比例等:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 开启内存碎片清理功能
    config set activedefrag yes
    # 表示内存碎片达到100MB时开始清理
    active-defrag-ignore-bytes 100mb
    # 表示内存碎片空间占Redis总空间大小的10%时
    tive-defrag-threshold-lower 10
    # 控制清理过程占用CPU时间比例不低于25%,保证清理能正常
    adtive-defrag-cycle-min 25
    # 控制清理过程占用CPU时间比例不高于75%,尽量不要影响主线程
    active-defrag-cycle-max 75

多线程

Redis 6.0引入的多线程特性是多IO线程,而不是命令线程。

引入多IO线程特性的理由

随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 IO 的处理上,也就是说,单个主线程处理网络请求的速度跟不上底层网络硬件的速度
为了应对这个问题,一般有两种解决办法:

  1. 用用户态网络协议栈(例如 DPDK)取代内核网络协议栈,让网络请求的处理不用在内核里执行,直接在用户态完成处理就行。
    这种方案在Redis中改动会比较大,因此Redis6.0没有采用这个方案。
  2. 采用多个 IO 线程来处理网络请求,提高网络请求处理的并行度。

IO线程和主线程之间的交互

  1. 服务端和客户端建立 Socket 连接,并分配处理线程首先,主线程负责接收建立连接请求。当有客户端请求和实例建立 Socket 连接时,主线程会创建和客户端的连接,并把 Socket 放入全局等待队列中。紧接着,主线程通过轮询方法把 Socket 连接分配给 IO 线程。
  2. IO 线程读取并解析请求主线程一旦把 Socket 分配给 IO 线程,就会进入阻塞状态,等待 IO 线程完成客户端请求读取和解析。因为有多个 IO 线程在并行处理,所以,这个过程很快就可以完成。
  3. 主线程执行请求操作等到 IO 线程解析完请求,主线程还是会以单线程的方式执行这些命令操作。
    Redis主线程和IO线程的交互
    当主线程执行完请求操作后,会把需要返回的结果写入缓冲区,然后,主线程会阻塞等待 IO 线程把这些结果回写到 Socket 中,并返回给客户端。
    和 IO 线程读取和解析请求一样,IO 线程回写 Socket 时,也是有多个线程在并发执行,所以回写 Socket 的速度也很快。等到 IO 线程回写 Socket 完毕,主线程会清空全局队列,等待客户端的后续请求。
    Redis主线程和IO线程的交互2

配置开启多线程

如果在实际应用Redis的过程中,发现Redis实例的CPU开销不大,吞吐量却没有提升,可以考虑使用Redis6.0的多线程机制来加速网络处理,进而提升实例的吞吐量。

  1. 设置 io-thread-do-reads 配置项为 yes,表示启用多线程。
    io-threads-do-reads yes
  2. 设置线程个数。一般来说,线程个数要小于 Redis 实例所在机器的 CPU 核个数,例如,对于一个 8 核的机器来说,Redis 官方建议配置 6 个 IO 线程
    io-threads 6

客户端缓存

Redis6.0后新增了一个服务端协助的客户端缓存功能,也被称为跟踪(Tracking)功能。
本地缓存的主要问题是:怎么和服务端数据保持一致?
Tracking功能提供两种模式来解决这个问题:

  1. 普通模式
    实例会在服务端记录客户端读取过的 key,并监测 key 是否有修改。一旦 key 的值发生变化,服务端会给客户端发送 invalidate 消息,通知客户端缓存失效了。
    打开或关闭普通模式下的Tracking功能:CLIENT TRACKING ON|OFF
  2. 广播模式
    服务端会给客户端广播所有 key 的失效情况,不过,这样做了之后,如果 key 被频繁修改,服务端会发送大量的失效广播消息,这就会消耗大量的网络带宽资源。

NVM

Redis是基于内存的键值对数据库,因此数据具有易失性,虽然有持久化和主从复制机制,但是由于持久化是异步的,因此也无法完全避免数据丢失问题。
近年来的一个趋势是将NVM(新型非易失性存储,Non-Volatile Memory)作为Redis的存储介质,NVM具有容量大、性能快、能持久化保存数据的特性,可以替代内存来使用。

NVM的三个特点

  1. NVM 内存最大的优势是可以直接持久化保存数据
    DRAM是掉电后数据会丢失,而NVM不会。
    如果使用NVM来实现持久化,甚至连RDB和AOF也不再需要了(现在的主从复制仍是基于RDB+AOF的,尚不清楚以后是否会有别的特性来支持直接内存的复制)。
  2. NVM 内存的访问速度接近 DRAM 的速度
    根据一些测试报告,NVM内存的访问速度:读延迟大约是 200300ns,而写延迟大约是 100ns。在读写带宽方面,单根 NVM 内存条的写带宽大约是 12GB/s,而读带宽约是 5~6GB/s。
    但我没有条件进行测试。
  3. NVM内存的容量很大
    NVM 器件的密度大,单个 NVM 的存储单元可以保存更多数据。
    例如,单根 NVM 内存条就能达到 128GB 的容量,最大可以达到 512GB,而单根 DRAM 内存条通常是 16GB 或 32GB。

其他优化点

  • 单进程单线程,无法充分发挥服务器多核 cpu 的性能;大流量下造成 IO 阻塞,同样是由于单进程单线程, cpu 在处理业务逻辑的时候,网络 IO 被阻塞住, 造成无法处理更多的请求.
    多线程 master + Nwork 工作模式.master 线程负责监听网络事件, 在接收到一个新的连接后, master 会把新的 fd 注册到 worker 的 epoll 事件中, 交由 worker 处理这个 fd 的所有读写事件, 这样 master 线程就可以完全被释放出来接收更多的连接, 同时又不妨碍 worker 处理业务逻辑和 IO 读写.
    采用这种 master + N
    worker 的网络层事件模型,可以实现 redis 性能的平行扩展. 真正的让 redis 在面临高并发请求时可以丛容面对.
  • 维护成本高, 如果想要充分发挥服务器的所有资源包括 cpu, 网络 io 等, 就必须建立多个 instance, 但此时不可避免会增加维护成本. 拿 24 核服务器举例来讲, 如果部署 24 个单机版的 instance,理论上可以实现 10w*24core= 240wQPS 的总体性能.但是每个 instance 有各自独立的数据,占用资源如内存也会同比上升,反过来制约一台服务器又未必能支持这么多的 instance. 如果部署 24 个 Instance 来构成单机集群, 虽然可以共享数据,但是因为节点增加, redis 的状态通讯更加频繁和费时,性能也下会降很多. 并且两种方式都意味着要维护 24 个 Instance,运维成本都会成倍增加.
  • 持久化:redis 提供了两种 save 方式 1)save 触发. 2)bgsave. 当然也可以使用 3)aof 来实现持久化, 但是这 3 点都有弊端.
    • save: 由于是单进程单线程, redis 会阻塞住所有请求, 来遍历所有 redisDB, 把 key-val 写入 dump.rdb. 如果内存数据量过大, 会造成短时间几秒到几十秒甚至更长的时间停止服务, 这种方案对于 twitter, taobao 等大流量的网站, 显然是不可取的.
    • bgsave: 在触发 bgsave 时, redis 会 fork 自身, child 进程会进入 1)的处理方式,这意味着服务器内存要有一半的冗余才可以, 如今内存已变得越来越廉价, 但是对于存储海量数据的情况,内存以及服务器的成本还是不容忽视的.
    • aof: 说到持久化, redis 提供的 aof 算是最完美的方案了, 但是有得必有失, 严重影响性能! 因为 redis 每接收到一条请求, 就要把命令内容完整的写到磁盘文件, 且不说频繁读写会影响磁盘寿命,写磁盘的时间足以拖垮 redis 整体性能 . 当然熟悉 redis 的开发者会想到用 appendfsync 等参数来调整, 但都不是完美.即使使用 SSD,性能也只是略有提升,并且性价比不高。
  • 优化 jemalloc, 采用大内存页. Redis 在使用内存方面可谓苛刻至极, 压缩, string 转 number 等, 能省就省, 但是在实际生产环境中, 为了追求性能, 对于内存的使用可以适度(不至于如 bgsave 般浪费)通融处理, 因此 AliRedis 对 jemalloc 做了微调, 通过调整 pagesize 来让一次 je_malloc 分配更多 run 空间来储备更多的用户态可用内存, 同时可以减轻换页表的负载, 降低 user sys 的切换频率, 来提高申请内存的性能, 对 jemalloc 有兴趣的开发者可以参考 jemalloc 源码中的 bin, run, chunk 数据结构进行分析.

Redis配置

内存

因为系统的内存大小有限,所以我们在使用 Redis 的时候可以配置 Redis 能使用的最大的内存大小。
redis.conf:

1
2
# Redis最大占用内存大小
maxmemory 100mb

通过命令修改

1
2
127.0.0.1:6379> config set maxmemory 100mb
127.0.0.1:6379> config get maxmemory

如果不设置最大内存大小或设置最大内存大小为 0,在 64 位操作系统下不限制内存大小,在 32 位操作系统下最大使用 3GB 内存。

修改数据库配置

redis 默认创建 16 个数据库(类似一个数组),默认值对应在 redis.conf 配置文件中 database 的值。
默认使用 0 号库,可以使用 select 命令来选择其他库。

过期时间的设置

如果大量的 key 过期时间设置的过于集中,到过期的那个时间点,redis 可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。

连接设置

Redis连接配置

参数 含义 建议
port 16371 指定Redis监听端口,默认端口为6379
bind 10.56.50.164 绑定的主机地址
timeout 2000 当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能 在压测时需要修改
tcp-keepalive 0 redis服务端主动向空闲的客户端发起ack请求,以判断连接是否有效,0表示未启用。定时向client发送tcp_ack包来探测client是否存活的。默认不探测,官方建议值为60秒 建议开启,避免连接意外中断后,服务端不能释放
maxclients 30000 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息

修改最大TCP连接

1、编辑文件 sudo vim /etc/security/limits.conf ,添加如下内容

  • soft nproc 60240
  • hard nproc 60240
  • soft nofile 65535
  • hard nofile 65535
    2、编辑文件 vi /etc/pam.d/login,在末尾添加如下内容
    session required /usr/lib64/security/pam_limits.so
    3、编辑文件 /etc/pam.d/system-auth ,添加如下内容
    session required /lib/security/$ISA/pam_limits.so
    4、重启虚机

修改TCP连接等待队列长度

编辑系统控制文件,加入tcp最长队列参数
1、编辑文件 vim /etc/sysctl.conf 添加如下内容
net.core.somaxconn=5120
2、查看修改
sudo sysctl -p
3、同步修改
sudo sysctl vm.overcommit_memory=1
4、关闭透明大页

  • 具有sudo权限的用户 (尝试过echo 命令 ,权限不允许)
    sudo vim /etc/grub2.cfg 文件尾加上
    transparent_hugepage=never
    【或者使用root用户执行 echo “transparent_hugepage=never”>> /etc/grub2.cfg 】
  • root用户
    echo never>/sys/kernel/mm/transparent_hugepage/enabled
  • 验证结果 返回 0 说明生效
    $ grep -i HugePages_Total /proc/meminfo
    HugePages_Total: 0
    $ cat /proc/sys/vm/nr_hugepages
    0

持久化

1
2
3
save 900 1
save 300 10
save 60 10000

指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
save
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。
建议值:

1
2
3
save 900 20
save 300 2000
save 60 200000
参数 含义 建议
stop-writes-on-bgsave-error yes 配置项stop-writes-on-bgsave-error no (默认值为yes),即当bgsave快照操作出错时停止写数据到磁盘,这样后面写错做均会失败,为了不影响后续写操作,故需将该项值改为no。 强制关闭Redis快照可能会导致不能持久化。MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk.
rdbcompression yes 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大 开启压缩
rdbchecksum yes (对rdb数据进行校验,耗费CPU资源,默认为yes)默认值是yes。在存储快照后,让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。 建议关闭
appendfsync everysec 指定更新日志条件,共有3个可选值:no表示等操作系统进行数据缓存同步到磁盘(快);always表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全);everysec表示每秒同步一次(折衷,默认值)
no-appendfsync-on-rewrite no 是否在后台写时同步单写,默认值no(表示需要同步).这里的后台写,表示后台正在重写文件(包括bgsave和bgrewriteaof.bgrewriteaof网上很多资料都没有涉及到。其实关掉bgsave之后,主要的即是aof重写文件了).no表示新的主进程的set操作会被阻塞掉,而yes表示新的主进程的set不会被阻塞,待整个后台写完成之后再将这部分set操作同步到aof文件中。但这可能会存在数据丢失的风险(机率很小),如果对性能有要求,可以设置为yes,仅在后台写时会异步处理命令. 压测时建议修改成yes
auto-aof-rewrite-percentage 100 aof文件增长比例,指当前aof文件比上次重写的增长比例大小。aof重写即在aof文件在一定大小之后,重新将整个内存写到aof文件当中,以反映最新的状态(相当于bgsave)。这样就避免了,aof文件过大而实际内存数据小的问题(频繁修改数据问题).
auto-aof-rewrite-min-size 64mb aof文件重写最小的文件大小,即最开始aof文件必须要达到这个文件时才触发,后面的每次重写就不会根据这个变量了(根据上一次重写完成之后的大小).此变量仅初始化启动redis有效.如果是redis恢复时,则lastSize等于初始aof文件大小.
aof-load-truncated yes 指redis在恢复时,会忽略最后一条可能存在问题的指令。默认值yes。即在aof写入时,可能存在指令写错的问题(突然断电,写了一半),这种情况下,yes会log并继续,而no会直接恢复失败.

主从配置

参数 含义 建议
slave-serve-stale-data yes slave-serve-stale-data参数设置成yes,主从复制中,从服务器可以响应客户端请求
slave-read-only yes 如果为 yes,代表为只读状态,但并不表示客户端用集群方式以从节点为入口连入集群时,不可以进行 set 操作,且 set 操作的数据不会被放在从节点的槽上,会被放到某主节点的槽上
repl-diskless-sync no 一个RDB文件从master端传到slave端,分为两种情况:1、支持disk:master端将RDB file写到disk,稍后再传送到slave端;2、无磁盘diskless:master端直接将RDB file传到slave socket,不需要与disk进行交互。
无磁盘diskless方式适合磁盘读写速度慢但网络带宽非常高的环境。repl-diskless-sync no 默认不使用diskless同步方式
repl-diskless-sync-delay 5 无磁盘diskless方式在进行数据传递之前会有一个时间的延迟,以便slave端能够进行到待传送的目标队列中,这个时间默认是5秒
repl-disable-tcp-nodelay no 是否启用TCP_NODELAY,如果启用则会使用少量的TCP包和带宽去进行数据传输到slave端,当然速度会比较慢;如果不启用则传输速度比较快,但是会占用比较多的带宽。
slave-priority 100 slave端的优先级设置,值是一个整数,数字越小表示优先级越高。当master故障时将会按照优先级来选择slave端进行恢复,如果值设置为0,则表示该slave永远不会被选择。

Redis 使用规范

来自:《加餐(六)| Redis的使用规范小建议

业务层面

1、key 的长度尽量短,节省内存空间
2、避免 bigkey,防止阻塞主线程
3、4.0+版本建议开启 lazy-free
4、把 Redis 当作缓存使用,设置过期时间
5、不使用复杂度过高的命令,例如SORT、SINTER、SINTERSTORE、ZUNIONSTORE、ZINTERSTORE
6、查询数据尽量不一次性查询全量,写入大量数据建议分多批写入
7、批量操作建议 MGET/MSET 替代 GET/SET,HMGET/HMSET 替代 HGET/HSET
8、禁止使用 KEYS/FLUSHALL/FLUSHDB 命令
9、避免集中过期 key
10、根据业务场景选择合适的淘汰策略
11、使用连接池操作 Redis,并设置合理的参数,避免短连接
12、只使用 db0,减少 SELECT 命令的消耗
13、读请求量很大时,建议读写分离,写请求量很大,建议使用切片集群

运维层面

1、按业务线部署实例,避免多个业务线混合部署,出问题影响其他业务
2、保证机器有足够的 CPU、内存、带宽、磁盘资源
3、建议部署主从集群,并分布在不同机器上,slave 设置为 readonly
4、主从节点所部署的机器各自独立,尽量避免交叉部署,对从节点做维护时,不会影响到主节点
5、推荐部署哨兵集群实现故障自动切换,哨兵节点分布在不同机器上
6、提前做好容量规划,防止主从全量同步时,实例使用内存突增导致内存不足
7、做好机器 CPU、内存、带宽、磁盘监控,资源不足时及时报警,任意资源不足都会影响 Redis 性能
8、实例设置最大连接数,防止过多客户端连接导致实例负载过高,影响性能
9、单个实例内存建议控制在 10G 以下,大实例在主从全量同步、备份时有阻塞风险
10、设置合理的 slowlog 阈值,并对其进行监控,slowlog 过多需及时报警
11、设置合理的 repl-backlog,降低主从全量同步的概率
12、设置合理的 slave client-output-buffer-limit,避免主从复制中断情况发生
13、推荐在从节点上备份,不影响主节点性能
14、不开启 AOF 或开启 AOF 配置为每秒刷盘,避免磁盘 IO 拖慢 Redis 性能
15、调整 maxmemory 时,注意主从节点的调整顺序,顺序错误会导致主从数据不一致
16、对实例部署监控,采集 INFO 信息时采用长连接,避免频繁的短连接
17、做好实例运行时监控,重点关注 expired_keys、evicted_keys、latest_fork_usec,这些指标短时突增可能会有阻塞风险
18、扫描线上实例时,记得设置休眠时间,避免过高 OPS 产生性能抖动