代码改变世界

《Redis开发与运维》阅读笔记

2018-09-04 16:21  墨叶凌枫  阅读(293)  评论(0)    收藏  举报
第二章 API的理解和使用
 
1)redis提供5种数据结构,每种数据结构都有多种内部编码实现。
2)纯内存存储、IO多路复用技术、单线程架构是造就Redis高性能的三个因素。
3)由于Redis的单线程架构,所以需要每个命令能被快速执行完,否则会存在阻塞Redis的可能,理解Redis单线程命令处理机制是开发和运维Redis的核心之一。
4)批量操作(例mget、mset、hmet等)能够有效提高命令执行的效率,但要注意每次批量操作的个数和字节数。
5)了解每个命令的时间复杂度在开发中至关重要,例如在使用keys、hgetall、smembers、zrange等时间复杂度较高的命令时,需要考虑数据规模对于Redis的影响。
6)persist命令可以删除任意类型键的过期时间,但是set命令也会删除字符串类型键的过期时间,这在开发时容易被忽视。
7)move、dump+restore、migrate是Redis发展过程中三种迁移键的方式,其中move命令基本废弃,migrate命令用原子性的方式实现了dump+restore,并且支持批量操作,是Redis Cluster实现水平扩容的重要工具。
8)scan命令可以解决keys命令可能带来的阻塞问题,同时Redis还提供了hscan、sscan、zscan渐进式地遍历hash、set、zset。
 
redis的应用场景
 
 
 
 
第三章 小功能大用处(redis的其他功能)
 
Redis还提供了诸如慢查询分析、功能强大的Redis Shell、Pipeline、事务与Lua脚本、Bitmaps、HyperLogLog、发布订阅GEO等附加功能,这些功能可以在某些场景发挥重要的作用。
慢查询分析:通过慢查询分析,找到有问题的命令进行优化。
Redis Shell:功能强大的Redis Shell会有意想不到的实用功能。
Pipeline:通过Pipeline(管道或者流水线)机制有效提高客户端性能。
事务与Lua:制作自己的专属原子命令。
Bitmaps:通过在字符串数据结构上使用位操作,有效节省内存,为开发提供新的思路。
HyperLogLog:一种基于概率的新算法,难以想象地节省内存空间。
发布订阅:基于发布订阅模式的信息通信机制。
GEO:Redis3.2提供了基于地理位置信息的功能。
 
3.1慢查询分析
 
慢查询:许多操作系统提供慢查询日志帮助开发和运维人员定位系统存在的慢操作。
 
因慢查询的时间统计不等待返回结果,所以没有慢查询并不代表客户端没有超时问题。
 
3.1.1 设置两个参数
slowlog-log-slower-than  预设阈值怎么设置
slowlog-max-len      慢查询记录存放在哪;其实该参数是用来说明慢查询日志最多存储多少条。
 
相关命令:
设置伐值与队列长度
config set slowlog-log-slower-than 0
config set slowlog-max-len  5
查询慢日志
slowlog get [number]
注意:先打印出来的是链表头,即最近当前的条目
查看当前慢日志的数量
slowlog len
清空慢日志队列   
slowlog reset
 
3.1.2 最佳实践
1.slowlog-max-len配置建议:线上建议调大慢查询列表,记录慢查询时Redis会对长命令做截断操作,并不会占用大量内存。增大慢查询列表可以减缓慢查询被剔除的可能,例如线上可以设置为1000以上。
2.slowlog-log-slower-than配置建议:默认值超过10毫秒判定为慢查询,需要根据Redis并发量调整该值。由于Redis采用单线响应命令,对于高流量的场景,如果命令执行时间在1毫秒以上,那么Redis最多可支撑OPS不到1000.因此对于高OPS场景的Redis建议设置为1毫秒。
3.慢查询只记录命令执行时间,并不包括命令排队和网络传输时间。
4.由于慢查询日志是一个先进先出的队列,也就是说如果慢查询比较多的情况下,可能会丢失部分慢查询命令,为了防止这种情况发生,所以定期执行slow get命令将查询日志持久化到其他存储中(例如MySQL),然后可以制作可视化界面进行查询。
 
 
3.2 redis shell 
 
3.2.1 redis-cli详解
参数设置,-r -i -x -c  -a  --scan --pattern  --slave 
 
3.2.2 redis-server详解
redis-server除了启动Redis外,还有一个--test-memory选项。redis-server --test-memory可以用来检测当前操作系统能否稳定地分配指定容量的内存给Redis,通过这种检测可以有效避免因为内存问题造成Redis崩溃,例如下面的操作检查当前操作系统能否提供1G的内存给Redis:
     redis-server --test-memory 1024
整个内存检测的时间比较长。当输出passed this test说明内存检测完毕。
该功能更偏向于调试和测试,例如,想快速占满机器内存做一些极端条件的测试,这个功能是一个不错的选择。
 
3.2.3 redis-benchmark 详解
 redis-benchmark可以为Redis做基准性能测试,它提供了很多选项帮助开发和运维人员测试Redis的相关性能。
 
例如:redis-benchmark -c 100 -n 20000 代表100个客户端同时请求Redis,一共执行20000次。
还有参数 -c -n -q -r -P -k -t --csv
 
3.3 Pipeline
  
    Pipeline(流水线)机制能改善上面这类问题,它能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端。
    实验证明,Pipeline执行速度一般比逐条执行要快。
    客户端和服务端的网络延时越大,Pipeline的效果越明显。
 
   Pipeline虽然好用,但是每次Pipeline组装的命令个数不能没有节制,否则一次组装Pipeline数据量过大,一方面会增加客户端的等待时间,另一方面会造成一定的网络阻塞,可以将一次包含大量命令的Pipeline拆分成多次较小的Pipeline来完成。
 
3.4事务与lua
事务表示一组动作,要么全部执行,要么全部不执行。
Redis提供了简单的事务功能,将一组需要一起执行的命令放到multi和exec两个命令之间。
multi命令代表事务开始,exec命令代表事务结束,它们之间的命令是原子顺序执行的。
如果停止事务的执行,可以使用discard命令代替exec命令即可。
Redis不支持回滚功能。
有些应用场景需要在事务之前,确保事务中的key没有被其他客户端修改过,才执行事务,否则不执行(类似乐观锁)。Redis提供了watch命令来解决这类问题。
Redis提供了简单的事务,之所以说它简单,主要是因为它不支持事务中的回滚特性,同时无法实现命令之间的逻辑关系计算,当然也体现了Redis的“keep it simple”的特性。
 
3.4.3Redis与Lua
在Redis中执行Lua脚本有两种方法:eval和evalsha.
(1) eval
eval 脚本内容 key个数 key列表 参数列表
如果Lua脚本较长,还可以使用redis-cli--eval直接执行文件
(2)evalsha
evalsha命令使用SHA1作为参数可以直接执行对应Lua脚本,避免每次发送Lua脚本的开销。这样客户端就不需要每次执行脚本内容,而脚本也会常驻在服务端,脚本功能得到了复用。
 
Lua的Redis API
Lua可以使用redis.call、redis.pcall函数实现对Redis的访问。如果redis.call执行失败,那么脚本执行结束会直接返回错误,而redis.pcall会忽略错误继续执行脚本,所以在实际开发中药根据具体的应用场景进行函数的选择。
Redis3.2提供了Lua Script Debugger功能用来调试复杂的Lua脚本。
 
Lua脚本功能带来的三个好处:
1.Lua脚本在Redis中是原子执行的,执行过程中间不会插入其他命令。
2.Lua脚本可以帮助开发和运维人员创造出自己定制的命令,并可以将这些命令常驻Redis内存,实现复用的效果。
3.Lua脚本可以将多条命令一次性打包,有效减少网络开销。
 
3.4.5Redis对Lua脚本管理
script load scrip
script exitsts sha1 [sha1 ···]
script flush
script kill
 
第三章总结
1)慢查询中的两个重要参数slowlog-log-slower-than和slowlog-max-len
2)慢查询不包括命令网络传输和排队时间。
3)有必要将慢查询定期存放。
4)redis-cli一些重要的选项,例如--latency、--bigkeys、-i和-r组合。
5)redis-benchmark的使用方法和重要参数。
6)Pipeline可以有效减少RTT次数,但每次Pipeline的命令数量不能无节制。
7)Redis可以使用Lua脚本创造出原子、高效、自定义命令组合。
8)Redis执行Lua脚本有两种方法:eval和evalsha
9)Bitmaps可以用来做独立用户统计,有效节省内存。
10)Bitmaps总setbit一个大的偏移量,由于申请大量内存会导致阻塞。
11)HyperLogLog虽然在统计独立总量时存在一定的误差,但是节省的内存量十分惊人。
12)Redis的发布订阅机制相比许多专业的消息队列系统功能较弱,不具备堆积和回溯消息的能力,但胜在足够简单。
13)Redis3.2提供GEO功能,用来实现基于地理位置信息的应用,但底层实现是zset。
 
 
第五章 持久化
RDB的优缺点:
优点:
1.RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景。比如每6小时执行bgsave备份,并把RDB文件拷贝到远程机器或者文件系统中(如hdfs),用于灾难恢复。
2.Redis加载RDB恢复数据远远快于AOF的方式。
缺点:
1.RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。
2.RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。
 
针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决。
 
AOF:
AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。
AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。理解掌握好AOF持久化机制对我们兼顾数据安全性和性能非常有帮助。 
 
Redis持久化功能一直是影响Redis性能的高发地。
当Redis做RDB或AOF重写时,一个必不可少的操作是执行fork操作创建子进程,对于大多数操作系统来说fork是个重量级操作。虽然fork创建的子进程不需要拷贝父进程的物理内存空间,但是会复制父进程的空间内存页表。
例如对于10GB的Redis进程,需要复制大约20MB的内存页表,因此fork操作耗时跟进程总内存量息息相关,如果使用虚拟化技术,特别是Xen虚拟机,fork操作会更耗时。
 
如何改善fork操作的耗时:
1)优先使用物理机或者高效支持fork操作的虚拟化技术,避免使用Xen。
2)控制Redis实例最大可用内存,fork耗时跟内存量成正比,线上建议每个Redis实例内存控制在10GB以内。
3)合理配置Linux内存分配策略,避免物理内存不足导致fork失败。
4)降低fork操作的频率,如适度放宽AOF自动触发时机,避免不必要的全量复制等。
 
本章重点回顾:
1)Redis提供两种持久化方式:RDB和AOF.
2)RDB使用一次性生成内存快照的方式,产生的文件紧凑压缩比更高,因此读取RDB恢复速度更快。由于每次生成RDB开销较大,无法做到实时持久化,一般用于数据冷备和复制传输。
3)save命令会阻塞主线程不建议使用,bgsave命令通过fork操作创建子进程生成RDB避免阻塞。
4)AOF通过追加写命令到文件实现持久化,通过appendsync参数可以控制实时/秒级持久化。因为需要不断追加写命令,所以AOF文件体积逐渐变大,需要定期执行重写操作来降低文件体积。
5)AOF重写可以通过auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数控制自动触发,也可以使用bgrewriteaof命令手动触发。
6)子进程执行期间使用copy-on-write机制与父进程共享内存,避免内存消耗翻倍。AOF重写期间也需要维护重写缓冲区,保存新的写入命令避免数据丢失。
7)持久化阻塞主线程场景有:fork阻塞和AOF追加阻塞。fork阻塞时间跟内存量和系统有关,AOF追加阻塞说明硬盘资源紧张。
8)单机下部署多个实例时,为了防止出现多个子进程执行重写操作,建议做隔离控制,避免CPU和IO资源竞争。
 
 
第六章 Redis复制
Redis复制总结
1)Redis通过复制功能实现主节点的多个副本。从节点可灵活地通过slaveof命令建立或断开复制流程。
2)复制支持树状结构,从节点可以复制另一个从节点,实现一层层向下的复制流。Redis2.8之后复制的流程分为:全量复制和部分复制。全量复制需要同步全部主节点的数据集,大量消耗机器和网络资源。而部分复制有效减少因网络异常等原因造成的不必要全量复制情况。通过配置合理调的复制积压缓冲区尽量避免全量复制。
3)主从节点之间维护心跳和偏移量检查机制,保证主从节点通信正常和数据一致。
4)Redis为了保证高性能复制过程是异步的,写命令处理完后直接返回给客户端,不等待从节点复制完成。因此从节点数据集会有延迟情况。
5)当使用从节点用于读写分离时会存在数据延迟、过期数据、从节点可用性等问题需要根据自身业务提前作出规避。
6)在运维过程中,主节点存在多个从节点或者一台机器上部署大量主节点的情况下,会有复制风暴的风险
 
第七章 阻塞
Redis阻塞
阻塞内在原因排查:
1.API或数据结构使用不合理。
例如:使用hgetall操作,在高并发的情况下容易造成阻塞。
   1)如何发现慢查询
       慢查询本身只记录了命令执行时间,不包括数据网络 传输时间和命令排队时间,因此客户端发生阻塞异常后,可能不是当前命令缓慢,而是在等待其他命令执行。需要重点比对异常和慢查询发生的时间点,确认是否有慢查询造成的命令阻塞队列。
       发现慢查询后的调整:
          1)修改为低算法度的命令,如hgetall改为hmget等,禁用keys、sort等命令。
          2)调整大对象:缩减大对象数据或把大对象拆分为多个小对象,防止一次命令操作过多的数据。大对象拆分过程需要视具体的业务决定,如用户好友集合存储在Redis中,有些热点用户会关注大量好友,这时可以按时间或其他维度拆分到多个集合中。
   2)如何发现大对象
      命令:redis-cli --bigkeys
 
2.CPU饱和的问题。
     CPU饱和是非常危险的,将导致Redis无法处理更多的命令,严重影响吞吐量和应用方的稳定性。对于这种情况,首先判断当前Redis的并发量是否达到极限, 建议使用命令redis-cli --stat查看。
     另:如果Redis实例为了最求低内存使用量,过度放宽ziplist使用条件,进程内的hash对象平均存储着上万个元素,而针对ziplist的操作的算法复杂度在O(n)到O(n^2)之间。虽然采用ziplist编码后hash结构内存占用会变小,但是操作变得更慢且更消耗CPU。ziplist压缩编码是Redis用来平衡空间和效率的优化手段,不可过度使用。
 
3.持久化相关的阻塞
   持久化引起主线程阻塞的操作主要有:fork阻塞、AOF刷盘阻塞、HugePage写操作阻塞。
1).fork阻塞
fork操作发生在RDB和AOF重写时,Redis主线程调用fork操作产生共享内存的子进程,由子进程完成持久化文件重写工作。如过fork操作本身耗时过长,必然会导致主线程的阻塞。
可以执行info stats命令获取到lastest_fork_usec指标,表示Redis最近一次fork操作耗时,如果耗时很大,比如超过1秒,则需要做出优化调整,如避免使用过大的内存实例和规避fork缓慢的操作系统等。
 
2).AOF刷盘阻塞
当我们开启AOF持久化功能时,文件刷盘的方式一般采用每秒一次,后台线程每秒对AOF文件最fsync操作。当硬盘压力过大时,fsync操作需要等待,直到写入完成。如果主线程发现距离上一次的fsync成功超过2秒,为了数据安全性它会阻塞直到后台线程执行fsync操作完成。这种阻塞行为主要是硬盘压力一起。可以通过Redis日志来识别。也可以查看info persistence统计aof_delayed_fsync指标,每次发生fdatasync阻塞主线程时会累加。
 
3).HugePage写操作阻塞
子进程在执行重写期间利用Linux写时复制技术降低内存开销,因此只有写操作时Redis才复制要修改的内存页。对于开启Transparent HugePage的操作系统,每次写命令引起的复制内存页单位由4K变为2MB,放大了512倍,会拖慢写操作的执行时间,导致大量写操作慢查询。例如简单的incr命令也会出现在慢查询中。
 
阻塞外在原因排查:
围绕 CPU竞争、内存交换、网络问题进行。
1.CPU竞争
1).进程竞争:Redis是典型的CPU密集型应用,不建议和其他多核CPU密集型服务部署在一起。
2).绑定CPU:这个优化正常情况下没有问题,但是存在例外情况。当Redis父进程创建子进程进行RDB/AOF重写时,如果做了CPU绑定,会与父进程共享使用一个CPU。子进程重写时对单核CPU使用率通常在90%以上,父进程与子进程将产生激烈CPU竞争,极大影响Redis稳定性。因此对于开启了持久化或参入复制的主节点不建议绑定CPU。
2.内存交换
内存交换(swap)对于Redis来说是非常致命的,Redis保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把Redis使用的部分内存换出到硬盘,由于内存与硬盘读写速度差几个数量级,会导致发生交换后的Redis性能急剧下降。识别Redis内存交换的检查方法如下:1)查询Redis进程号 ;2)根据进程号查询内存交换信息。
预防内存交换的方法有:
1.保证机器充足的可用内存。2.确保所有Redis实例设置最大可用内存(maxmemory),防止极端情况下Redis内存不可控的增长。3.降低系统使用swap优先级,如echo 10>/proc/sys/vm/swappiness。可查找Redis的linux配置优化相关资料。
3.网路问题
常见的网络问题主要有:连接拒绝、网络延迟、网卡软中断等。 
1)连接拒绝有三种情况:1.网络闪断。2.Redis连接拒绝。3.连接溢出。
2)网络延迟取决于客户端到Redis服务器之间的网络环境。
3)网卡软中断是指由于单个网卡队列只能使用一个CPU,高并发下网卡数据交互都集中在同一个CPU,导致无法充分利用多核CPU的情况。
 
阻塞重点回顾:
1)客户端最先感知阻塞等Redis超时行为,加入日志监控报警工具可快速定位阻塞问题,同时需要对Redis进程和机器做全面监控。
2)阻塞的内在原因:确认主线程是否存在阻塞,检查慢查询等信息,发现不合理使用API或数据结构的清苦,如keys、sort、hgetall等。关注CPU使用率防止单核跑满。当硬盘IO资源紧张时,AOF追加也会阻塞主线程。
3)阻塞的外在原因:从CPU竞争、内存交换、网络问题等方面入手排查是否因为系统层面问题引起阻塞。
 
 
第八章 理解内存
1.内存消耗
   used_memory_rss:从操作系统的角度显示Redis进程占用的物理内存总量
   mem_fragmentation_ratio(mfr):used_memory_rss/used_memory比值,表示内存碎片率。
碎片率 mfr>1时,说明碎片率很严重;当mfr<1时,这种情况一般出现在操作系统把Redis内存交换(Swap)到硬盘导致,出现这种情况时要格外关注,由于硬盘远远慢于内存,Redis性能会变得很差,甚至僵死。
 
Redis进程内消耗主要包括:自身内存+对象内存+缓冲内存+内存碎片,其中Redis空进程自身内存消耗非常少,通常used_memory_rss在3MB左右,used_memory在800KB左右,一个空的Redis进程消耗内存可以忽略不计。
对象内存是Redis内存占用最大的一块,存储着用户所有的数据。
缓冲内存主要包括:客户端缓冲、复制积压缓冲区、AOF缓冲区。