这篇主要整理第七章,第八章和第十一章的内容,主要是一些缓存的优化内容。主从复制,哨兵和集群下一章节再讲,书中像是持久化,五种基础类型api,运维报警,统计配置等章节不会讲,转载注明出处:https://blog.csdn.net/Koikoi12
Redis阻塞
Redis是单线程架构,所有读写都在一条主线程完成,因此如果出现了阻塞是一件严重的事情。
1.发现阻塞
当Redis阻塞时,线上应用服务应该最先感知到,这时应用方会收到大量Redis超时异常。
常规做法是在应用方加入异常统计并通过邮件/微信/短信报警,以便及时发现通知问题。
对于开发人员,需要处理如何统计异常以及触发报警的时机。何时报警根据应用的并发量决定。
由于Redis调用API会分散在项目的多个地方,每个地方都监听异常并加入监控代码必然难以维护。这可以借助于日志系统,使用logback或者log4j。当异常发生时,异常信息最终会被日志系统收集到Appender,默认的Appender一般是具体的日志文件,开发人员可以自定义一个Appender,用于专门统计异常和触发报警逻辑。
书中以java logback作为例子,190页可以自行查看。代码大致干了几件事,就是把某个级别(如error级别)的redis异常捕获,如果报错10次以上,就触发自己写的报警代码。最后一定要remove掉防止内存泄露
如何定位Redis集群中哪个Redis节点(ip和port信息)发生了异常?由于客户端类库都会保存IP和PORT信息,可以在异常发生时打印出对应节点的IP和PORT信息。
修改Redis客户端成本很低,比如Jedis只需要修改Connection类下的connect、sendCommand、readProtocalWithCheckintBroken 方法专门捕获连接、发送命令、协议读取事件的异常。
也可以借助Redis监控系统(如CacheCloud或一些公司内部自行开发的监控系统)监控的关键指标,命令耗时,慢查询,持久化阻塞,连接拒绝、CPU、内存、网络、磁盘使用过载的问题等。
注意:线上Redis没有监控报警,是很不负责任和危险的
2.内在原因
1.API或者数据结构使用不合理
比如hgetall
操作 数据量比较大且命令算法复杂度是O(n)
这条命令执行速度必然很慢。
解决方案:
- 通过
$slowlog get
获取慢查询日志 $redis-cli -h {ip} -p {port} --bigkeys
发现大对象后进行优化
2.CPU饱和问题
如何排查cpu使用率是否过高:
- 通过linux的
top
命令查看redis进程的cpu使用率 $redis-cli -h {ip} -p {port} --stat
输入当前rediscpu使用率,该命令会每秒输出一行统计信息
垂直层面的命令优化很难达到效果,这时就需要水平扩展来分摊OPS压力。
如果只有几百或几千OPS的Redis接近CPU饱和是很不正常的,有可能使用了高算法复杂度的命令,需要注意排查。
3.持久化相关的阻塞
3.1. fork阻塞
fork操作发生在RDB和AOF重写时,redis主线程调用fork操作产生共享内存的子进程,由子进程完成持久化文件重写工作。如果fork操作本身耗时过长,必然会导致主线程的阻塞。
可以执行info stats
获取到latest_fork_usec
指示,表示redis最近一次fork操作耗时,如果耗时很大,比如超过1秒,则需要作出优化调整,如避免使用过大的内存实例和规避fork缓慢的操作系统等
3.2. AOF刷盘阻塞
文件刷盘采用每秒一次,后台线程每秒对AOF文件做fsync操作。当硬盘压力过大时候,fsync操作需要等待,直到写入完成。如果主线程发现距离数十年该一次的fsync成功超过2秒,为了数据安全性它会阻塞直到后台线程执行fsync操作完成。这种阻塞行为主要是硬盘压力引起,可以查看redis日志识别出这种情况。
也可以查看指令info persistence
统计aof_delayed_fsync
指标,每次发生fdatasync
阻塞主线程时会累加。
3.3. HugePage 写操作阻塞
子进程在执行重写期间利用Linux写时复制技术降低内存开销,因此只有写操作时候,redis才复制要修改的内存页。只引用书中原话,没接触过不做具体讨论
Redis官方文档列出了阻塞详细的分类说明,感兴趣的可以直接去啃生肉:https://www.redis.io/topics/latency
3.外在原因
1.CPU竞争
进程竞争:Redis是典型的CPU密集型应用,不建议和其他多核CPU密集型服务部署在一起。当其他进程过度消耗CPU时候,将严重影响Redis吞吐量。可以通过top、sar
等命令定位到CPU消耗的时间点和具体进程。
绑定CPU:部署Redis时候为了充分利用多核CPU,通常一台机器部署多个实例。常见的一种优化是把Redis进程绑定CPU上,用于降低CPU频繁上下文切花的开销。
注意:不建议对持久化的Redis进行绑定CPU操作,因为fork出来的子进程会去争抢CPU,极大影响了稳定性
2.内存交换
Redis保证高性能的一个重要前提是所有数据都在内存中,若果操作系统把Redis使用的部分内存换出到硬盘,由于内存与硬盘读写速度差几个数量级,会导致发生交换后的Redis性能急剧下降。
这一篇主要是linux层面的优化,详细可看书中的第12章
识别Redis内存交换的方法如下:
- 查询Redis进程号
#redis-cli -p 6383 info server | grep process_id
,返回process_id: 4476
- 根据进程号查询内存交换信息
#cat /proc/4476/smaps | grep Swap
预防内存交换:
- 保证机器充足的可用内存
- 确保所有Redis实例设置最大可用内存,防止极端情况下内存不可控的增长
- 降低系统使用swap优先级
3.网络问题
大致了解下就行,常见问题:
- 连接拒绝
- 网络延迟
- 网卡软中断
内存模型
1.内存消耗
获取内存统计相关指标命令:info memory
内存消耗划分:自身内存(作为Redis空进程,自身消耗非常少)+对象内存+缓冲内存+内存碎片
- 对象内存:Redis内存占用最大的一块,存储着用户所有的数据。对象内存消耗可以简单理解为sizeOf(keys) + sizeof(values)
- 内存碎片:默认的内存分配器采用
jemalloc
,可选的分配器还有:glibc
,tcmalloc
。出现高内存碎片的解决办法:1、数据对齐;2、安全重启:将碎片率过高的主节点转为从节点,进行重启。 - 缓冲内存:客户端缓冲、复制积压缓冲、AOF缓冲
客户端缓冲:指的是所有接入到Redis服务器TCP连接的输入输出缓冲。输入缓冲无法控制,最大空间为1G,如果超过将断开连接。输出缓冲通过参数client-output-buffer-limit控制。
复制积压缓冲:主节点的写命令会缓冲到复制积压缓冲区,从节点从中读取,根据repl-backlog-size参数控制,默认1MB。
AOF缓冲:用于在Redis重写期间保存最近的写入命令。
2.内存管理
限制内存的方法:
- 使用
maxmemory
参数限制最大可用内存 - 也可以通过
config set maxmemory
进行动态修改
限制内存的目的:
- 用于缓存场景,当超出内存上限
maxmemory
时使用LRU等回收策略释放空间 - 防止所用内存超过服务器物理内存
注意:maxmemory限制的是Redis实际使用的内存量。由于内存碎片率的存在,实际消耗的内存可能会比maxmemory设置的更大,实际使用时要小心内存溢出。
内存回收策略:
- 删除过期键对象
- 内存达到上限时触发回收策略
如果过期键一直在内存中没被删除,就需要用到内存的回收策略,Redis中大部分都是基于LRU实现:逐步解析力扣146. LRU算法(哈希表+双向链表,LinkedHashMap源码解析,Redis内存淘汰机制)
3.内存优化
优化包括redisObject对象,缩减键值对象,共享对象池,字符串优化,编码优化,控制键数量等方式。
别人整理的内容和书中一致,可以看这篇:《Redis开发与运维》---- 理解内存
缓存设计
书中都是基于优化来实现,目的无非就是加速读写,降低db压力,以及各类问题的解决,需要实践出真知
之前整理的:
缓存穿透、缓存击穿、缓存雪崩区别和解决方案
布隆过滤器实践,缓存穿透的预防及和bitmap的区别
Mysql和Redis数据同步策略