Linux配置优化

内存配置

vm.overcommit_memory

Linux系统对内存请求都会分配,以便能运行更多程序。在申请内存后并不会马上使用内存,这种技术就叫做overcommit,如果参数设置为0则Redis会出现告警。

WARNING overcommit_memory is set 0!
说明
0 内核将检查是否有可用内存(包括swap),如果有足够的内存,则申请通过,否则将失败信息返回给应用程序
1 内核允许超量使用内存直到用完为止
2 内核保证不会超量使用内存,即不超过swap+50%的RAM值,50%是参数overcommit_ratio控制

Redis建议vm.overcommit_memory设置为1并设置maxmemory,预留30%左右的内存空间。

echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory=1

swappiness

swap内存交换空间能够在内存不足时,对部分内存页进行swap操作。但swap空间是由硬盘提供,对于高并发应用程序来说,磁盘IO能力会成为系统瓶颈。Linux并不是完全等到内存不够用时才使用swap,参数swappiness会决定系统使用swap的策略。swappiness取值范围为0~100,值越大说明系统会更积极使用swap,值越低系统就更倾向于物理内存,默认值为60。

策略
0 Linux3.5以及以上:宁愿OOM killer也不用swap;Linux3.4以及更早:宁愿swap也不要OOM killer
1 Linux3.5以及以上:宁愿swap也不要OOM killer
60 默认值
100 操作系统会主动地使用swap

查看swap使用情况

$ redis-cli -h ip -p port info server | grep process_id
process_id:986

$ cat /proc/886/smaps | grep swap
Swap: 58056 kB
....

设置swappiness

$ echo {value} >/proc/sys/vm/swappiness
$ echo vm.swappiness={value} >> /etc/sysctl.conf

如果Linux > 3.5 ,则设置为1,否则设置为0。如果Redis为高可用环境,死掉会比阻塞更好,能够及时切换到正常节点上。

THP

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

$ echo never >  /sys/kernel/mm/transparent_hugepage/enabled
$ echo never > /sys/kernel/mm/transparent_hugepage/defrag
$ echo "echo never > /sys/kernel/mm/transparent_hugepage/enabled" >> /etc/rc.local
$ echo "echo never > /sys/kernel/mm/transparent_hugepage/defrag" >> /etc/rc.local

OOM Killer

OOM Killer在可用内存不足的情况下会选择性的杀掉用户进程,来释放部分内存。OOM Killer进程会给每个用户进程设置一个权重,这个值越高,被杀的风险就越高,每个进程的权重都放在/proc/{process id}/oom_core中,这个值是由/proc/{process id}/oom_adj控制,oom_adj在不同版本中最小值不同,当oom_adj设置为最小值时,该进程不会被OOM Killer
。因此可以将redis进程的oom_adj设置为一个较小值,降低被OOM Killer杀掉的概率

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

相对设置oom_adj来说,更建议科学规划Redis内存,在Redis高可用环境下,被杀掉要比进程僵死更好。

NTP

无论是什么数据库环境,都强烈建议配置时间同步,避免集群节点出现时间不一致的情况,增加问题排查日志难度,定时任务异常等情况。

可以使用NTP服务来作为时间同步,通过crontab定时同步系统时间

ulimit

open files限制单个用户最大文件打开个数。Redis的maxclients默认为10000,加上最大32个文件描述符,因此建议open files设置为至少10032,如果采用默认的4096,减去32个文件描述符,则maxclients仅为4064

ulimit -Sn 10032

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.

修改tcp backlog

echo 1024 > /proc/sys/net/core/somaxconn

Redis缓存优化

大部分业务系统会将Redis作为缓存设计的核心,由于Redis采用内存存储数据,能够加速读写速度,同时能够降低后端存储访问的负载压力。但这些好处都是有成本的,最直接的就是缓存和后端存储数据存在窗口期,数据不一致性,窗口期大小与缓存数据更新策略有关。同时,业务也需要处理缓存和存储关联关系的逻辑,加大业务量及维护量。

缓存更新策略

缓存中的数据往往都有生命周期,与后端存储间存在时间窗口,导致缓存数据陈旧,数据结果不一致,需要制定更新策略,来控制时间窗口大小。

  1. 算法剔除:
    剔除算法通过用于缓存使用量超过了maxmemory设置的最大值时,根据特定算法对数据进行剔除。Redis提供了maxmemory_policy参数控制剔除算法,实现较为简单,但数据一致性太差
  2. 超时剔除:
    超时剔除就是给键设置过期时间,在过期后自动删除,并从后端存储再次获取并缓存最新数据,更新周期取决于过期时间
  3. 主动更新:
    如果对数据一致性要求较高时,需要当后端数据更新后,立即缓存更新为最新数据。数据一致性最高,但是需要业务自主设计更新逻辑,通常建议与超时剔除策略一同使用

空查询

当查询的数据根本不存在,处理逻辑会先访问缓存数据,如果不存在再访问后端数据去获取,大量空请求会造成缓存形同虚设。

针对这种情况如果缓存空对象,下一次查询就不会去访问后端存储。但这种设计增加了缓存键数量,空对象占用了更多内存,时,一旦后端添加了数据,将会造成数据不一致还是返回空。

布隆过滤器会在访问缓存层和存储层之前,将存在的KEY用布隆过滤器提前保存起来,达到一种提前过滤的效果,避免空查询直接访问缓存和存储。适用于数据命中不高,数据相对固定的情况。相关具体信息可以参考:bloom-filter

无底洞优化

当业务数据量不断激增时,通过横向扩展集群使集群分布更广,来降低单个节点的压力。但这并不意味着性能一定会正向提升。缓存数据库通常常用hash函数将key来映射到不同节点上,分布方式与业务无关,当节点不断增长,数据不断分布到更多节点上。因此,在做批量操作时,我们就需要访问更多的节点数据,这时就需要N次网络操作。

start客户端在前面的内容提到过它会保存slot和节点的映射关系,然后再对每个节点执行批量操作,它需要的网络时间就是实际访问的节点数量来计算了。同时我们也可以将命令操作并行化,进一步降低耗时。具体代码实现片段可参考《 Redis开发与运维》一书。

Tips:通过Redis Cluster的hash_tag将多个key强制分配到一个节点上,只需要一次网络消耗,但容易造成数据倾斜。

雪崩优化

缓存承载了大量请求,降低了后端存储的负载率,但是当缓存异常无法提供服务时,所有的业务请求都会走到后端,造成后端负载过大,同时引发异常

针对这种情况,可以针对下面几个方向进行优化:

  1. 保证缓存服务的高可用性,Redis Sentinel和Redis Cluster架构都可以
  2. 隔离组件限流并降级
  3. 定期演练并制定应对方案

热点Key优化

并发量大的热点key频繁进行更新重建同样也会给后端带来巨大压力,针对这种情况我们需要在尽可能保存数据一致的情况下减少更新重建。

  1. 互斥锁
    此方法仅允许同时只有一个线程负责重建缓存,其它线程只能等待重建完成,再从缓存读取数据即可。这里可以使用setnx实现。但这种方式利用锁带来了一定的风险,存在死锁的可能。
  2. 永不过期
    永不过期是指不设置过键期时间,也就不存在重建的问题,业务针对value设置一个逻辑过期时间,定时采用单线程的方式去更新重建缓存。这种方式也就意味着在重建过程中数据的不一致性,需要判断是否能忍受数据的不一致。

针对热点key我们通过Monitor命令,ELK抓取网络协议包等方式去获取。

Redis安全

Redis的安全问题也十分重要,如果处于一个不安全的环境下,容易遭受攻击,导致服务异常,数据丢失等情况。通常被攻击的Redis环境都存在以下特点:

  • Redis使用公网IP
  • 以默认的6379作为Redis端口
  • 以root用户启动服务
  • Redis没有设置密码认证
  • Redis bind设置为0.0.0.0或者*

攻击者利用Redis的dir和filename可以动态设置以及RDB持久化特性,将自己的公钥写入目标端机器的/root/.ssh/authotrized_keys文件中,从而实现对目标机器的攻击。

改进建议

  1. 启用密码认证:
    Redis通过requirepass参数设置Redis访问密码,设置完成之后,每次连接Redis都需要提供访问密码。Redis访问加密Redis时可以通过redis-cli的-a选项或者进入redis-cli再执行auth {password}。访问密码需要高复杂度
  2. rename-command:
    Redis包含很多高危命令,一旦错误使用将导致服务异常,例如:keys,flushall/flushdb,save,debug,config,shutdown等。为了避免开发人员或者其它运维无关人员使用,可以通过rename-command将命令重命名。但需要注意的是RDB或AOF如果包含rename-command之前的命令,Redis将无法识别,导致启动失败;Redis Sentinel在修改配置时调用的config命令,如果rename-command修改了config,那Sentinel将无法正常工作
  3. bind:
    bind参数能够指定Redis与哪张网卡进行绑定,限制网络请求入口。如果没有设置密码,没有配置bind,则默认仅支持127.0.0.1本地访问
  4. 定期备份数据
  5. 不采用默认端口
  6. 使用非root用户启动服务

bigkey

bigkey指key对应的value占用的内存空间较大,字符串bigkey通常为单个value值很大,非字符串bigkey通常为元素过多。bigkey会造成群集环境下节点内存使用不均衡,操作bigkey耗时较大,相应的网络资源消耗也会更大。

查找bigkey

redis-cli –bigkey选项可以统计bigkey的分布情况,但往往会希望自己来确定bigkey的大小,判断一个key是否bigkey,可以执行debug object key查看serializedlength属性,它表示key对应的value序列化之后的字节数

10.0.139.161:6380> debug object hello
Value at:0x7f0101a1b080 refcount:1 encoding:embstr serializedlength:6 lru:7262128 lru_seconds_idle:14

对于元素较多的数据,debug object执行速度比较慢,可能存在阻塞。建议在从节点上使用scan+debug object的方式

删除bigkey

在对bigkey进行del命令删除时,非字符串类型的数据结构元素越多,删除也就越慢,存在阻塞Redis的可能,建议采用scan命令分批读取并删除元素。在Redis 4.0中加入了lazy delete free模式,在删除bigkey时不会造成阻塞。