初识redis
1)Redis的8个特性:速度快、基于键值对的数据结构服务器、功能丰富、简单稳定、客户端语言多、持久化、主从复制、支持高可用和分布式。
2)Redis并不是万金油,有些场景不适合使用Redis进行开发。
3)开发运维结合以及阅读源码是用好Redis的重要方法。
4)生产环境中使用配置文件启动Redis。
5)生产环境选取稳定版本的Redis。
6)Redis3.0是重要的里程碑,发布了Redis官方的分布式实现Redis Cluster。
API的理解和使用
1)Redis提供5种数据结构,每种数据结构都有多种内部编码实现。
2)纯内存存储、IO多路复用技术、单线程架构是造就Redis高性能的三个因素。
3)由于Redis的单线程架构,所以需要每个命令能被快速执行完,否则会存在阻塞Redis的可能,理解Redis单线程命令处理机制是开发和运维Redis的核心之一。
4)批量操作(例如mget、mset、hmset等)能够有效提高命令执行的效率,但要注意每次批量操作的个数和字节数。
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。
小功能大用处
1)慢查询中的两个重要参数slowlog-log-slower-than和slowlog-maxlen。
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。
客户端
1)RESP(Redis Serialization Protocol Redis)保证客户端与服务端的正常通信,是各种编程语言开发客户端的基础。
2)要选择社区活跃客户端,在实际项目中使用稳定版本的客户端。
3)区分Jedis直连和连接池的区别,在生产环境中,应该使用连接池。
4)Jedis.close()在直连下是关闭连接,在连接池则是归还连接。
5)Jedis客户端没有内置序列化,需要自己选用。
6)客户端输入缓冲区不能配置,强制限制在1G之内,但是不会受到maxmemory限制。
7)客户端输出缓冲区支持普通客户端、发布订阅客户端、复制客户端配置,同样会受到maxmemory限制。
8)Redis的timeout配置可以自动关闭闲置客户端,tcp-keepalive参数可以周期性检查关闭无效TCP连接
9)monitor命令虽然好用,但是在大并发下存在输出缓冲区暴涨的可能性。
10)info clients帮助开发和运维人员找到客户端可能存在的问题。
11)理解Redis通信原理和建立完善的监控系统对快速定位解决客户端常见问题非常有帮助。
持久化
1)Redis提供了两种持久化方式:RDB和AOF。
2)RDB使用一次性生成内存快照的方式,产生的文件紧凑压缩比更高,因此读取RDB恢复速度更快。由于每次生成RDB开销较大,无法做到实时持久化,一般用于数据冷备和复制传输。
3)save命令会阻塞主线程不建议使用,bgsave命令通过fork操作创建子进程生成RDB避免阻塞。
4)AOF通过追加写命令到文件实现持久化,通过appendfsync参数可以控制实时/秒级持久化。因为需要不断追加写命令,所以AOF文件体积逐渐变大,需要定期执行重写操作来降低文件体积。
5)AOF重写可以通过auto-aof-rewrite-min-size和auto-aof-rewritepercentage参数控制自动触发,也可以使用bgrewriteaof命令手动触发。
6)子进程执行期间使用copy-on-write机制与父进程共享内存,避免内存消耗翻倍。AOF重写期间还需要维护重写缓冲区,保存新的写入命令避免数据丢失。
7)持久化阻塞主线程场景有:fork阻塞和AOF追加阻塞。fork阻塞时间跟内存量和系统有关,AOF追加阻塞说明硬盘资源紧张。
8)单机下部署多个实例时,为了防止出现多个子进程执行重写操作,建议做隔离控制,避免CPU和IO资源竞争。
复制
1)Redis通过复制功能实现主节点的多个副本。从节点可灵活地通过slaveof命令建立或断开复制流程。
2)复制支持树状结构,从节点可以复制另一个从节点,实现一层层向下的复制流。Redis2.8之后复制的流程分为:全量复制和部分复制。全量复制需要同步全部主节点的数据集,大量消耗机器和网络资源。而部分复制有效减少因网络异常等原因造成的不必要全量复制情况。通过配置合理的复制积压缓冲区尽量避免全量复制。
3)主从节点之间维护心跳和偏移量检查机制,保证主从节点通信正常和数据一致。
4)Redis为了保证高性能复制过程是异步的,写命令处理完后直接返回给客户端,不等待从节点复制完成。因此从节点数据集会有延迟情况。
5)当使用从节点用于读写分离时会存在数据延迟、过期数据、从节点可用性等问题,需要根据自身业务提前作出规避。
6)在运维过程中,主节点存在多个从节点或者一台机器上部署大量主节点的情况下,会有复制风暴的风险。
Redis的噩梦:阻塞
1)客户端最先感知阻塞等Redis超时行为,加入日志监控报警工具可快速定位阻塞问题,同时需要对Redis进程和机器做全面监控。
2)阻塞的内在原因:确认主线程是否存在阻塞,检查慢查询等信息,发现不合理使用API或数据结构的情况,如keys、sort、hgetall等。关注CPU使用率防止单核跑满。当硬盘IO资源紧张时,AOF追加也会阻塞主线程。
3)阻塞的外在原因:从CPU竞争、内存交换、网络问题等方面入手排查是否因为系统层面问题引起阻塞。
理解内存
1)Redis实际内存消耗主要包括:键值对象、缓冲区内存、内存碎片。
2)通过调整maxmemory控制Redis最大可用内存。当内存使用超出时,根据maxmemory-policy控制内存回收策略。
3)内存是相对宝贵的资源,通过合理的优化可以有效地降低内存的使用量,内存优化的思路包括:
- 精简键值对大小,键值字面量精简,使用高效二进制序列化工具。
- 使用对象共享池优化小整数对象。
- 数据优先使用整数,比字符串类型更节省空间。
- 优化字符串使用,避免预分配造成的内存浪费。
- 使用ziplist压缩编码优化hash、list等结构,注重效率和空间的平衡。
- 使用intset编码优化整数集合。
- 使用ziplist编码的hash结构降低小对象链规模。
哨兵
1)Redis Sentinel是Redis的高可用实现方案:故障发现、故障自动转移、配置中心、客户端通知。
2)Redis Sentinel从Redis2.8版本开始才正式生产可用,之前版本生产不可用。
3)尽可能在不同物理机上部署Redis Sentinel所有节点。
4)Redis Sentinel中的Sentinel节点个数应该为大于等于3且最好为奇数。
5)Redis Sentinel中的数据节点与普通数据节点没有区别。
6)客户端初始化时连接的是Sentinel节点集合,不再是具体的Redis节点,但Sentinel只是配置中心不是代理。
7)Redis Sentinel通过三个定时任务实现了Sentinel节点对于主节点、从节点、其余Sentinel节点的监控。
8)Redis Sentinel在对节点做失败判定时分为主观下线和客观下线。
9)看懂Redis Sentinel故障转移日志对于Redis Sentnel以及问题排查非常有帮助。
10)Redis Sentinel实现读写分离高可用可以依赖Sentinel节点的消息通知,获取Redis数据节点的状态变化。
集群
1)Redis集群数据分区规则采用虚拟槽方式,所有的键映射到16384个槽中,每个节点负责一部分槽和相关数据,实现数据和请求的负载均衡。
2)搭建集群划分三个步骤:准备节点,节点握手,分配槽。可以使用redis-trib.rb create命令快速搭建集群。
3)集群内部节点通信采用Gossip协议彼此发送消息,消息类型分为:ping消息、pong消息、meet消息、fail消息等。节点定期不断发送和接受ping/pong消息来维护更新集群的状态。消息内容包括节点自身数据和部分其他节点的状态数据。
4)集群伸缩通过在节点之间移动槽和相关数据实现。扩容时根据槽迁移计划把槽从源节点迁移到目标节点,源节点负责的槽相比之前变少从而达到集群扩容的目的,收缩时如果下线的节点有负责的槽需要迁移到其他节点,再通过cluster forget命令让集群内其他节点忘记被下线节点。
5)使用Smart客户端操作集群达到通信效率最大化,客户端内部负责计算维护键→槽→节点的映射,用于快速定位键命令到目标节点。集群协议通过Smart客户端全面高效的支持需要一个过程,用户在选择Smart客户端时建议review下集群交互代码如:异常判定和重试逻辑,更新槽的并发控制等。节点接收到键命令时会判断相关的槽是否由自身节点负责,如果不是则返回重定向信息。重定向分为MOVED和ASK,ASK说明集群正在进行槽数据迁移,客户端只在本次请求中做临时重定向,不会更新本地槽缓存。MOVED重定向说明槽已经明确分派到另一个节点,客户端需要更新槽节点缓存。
6)集群自动故障转移过程分为故障发现和故障恢复。节点下线分为主观下线和客观下线,当超过半数主节点认为故障节点为主观下线时标记它为客观下线状态。从节点负责对客观下线的主节点触发故障恢复流程,保证集群的可用性。
7)开发和运维集群过程中常见问题包括:超大规模集群带宽消耗,pub/sub广播问题,集群节点倾斜问题,手动故障转移,在线迁移数据等。
缓存设计
1)缓存的使用带来的收益是能够加速读写,降低后端存储负载。
2)缓存的使用带来的成本是缓存和存储数据不一致性,代码维护成本增大,架构复杂度增大。
3)比较推荐的缓存更新策略是结合剔除、超时、主动更新三种方案共同完成。
4)穿透问题:使用缓存空对象和布隆过滤器来解决,注意它们各自的使用场景和局限性。
5)无底洞问题:分布式缓存中,有更多的机器不保证有更高的性能。有四种批量操作方式:串行命令、串行IO、并行IO、hash_tag。
6)雪崩问题:缓存层高可用、客户端降级、提前演练是解决雪崩问题的重要方法。
7)热点key问题:互斥锁、“永远不过期”能够在一定程度上解决热点key问题,开发人员在使用时要了解它们各自的使用成本。
开发运维的“陷阱”
1)Linux相关优化:
- vm.overcommit_memory建议为1。
- Linux>3.5,vm.swappiness建议为1,否则建议为0。
- Transparent Huge Pages(THP)建议关闭掉,但需要注意Linux发行版本改变了THP的配置位置。
- 可以为Redis进程设置oom_adj,减少Redis被OOM killer杀掉的概率,但不要过度依赖此特性。
- 建议对Redis所有节点所在机器使用NTP服务。
- 设置合理的ulimit保证网络连接正常。
- 设置合理的tcp-backlog参数。
2)理解Redis的持久化有助于解决flush操作之后的数据快速恢复问题。
3)Redis安全建议:
- 根据具体网络环境决定是否设置Redis密码。
- rename-command可以伪装命令,但是要注意成本。
- 合理的防火墙是防止攻击的利器。
- bind可以将Redis的访问绑定到指定网卡上。
- 定期备份数据应该作为习惯性操作。
- 可以适当错开Redis默认端口启动。
- 使用非root用户启动Redis。
4)bigkey的危害不容忽视:数据倾斜、超时阻塞、网络拥塞,可能是Redis生产环境中的一颗定时炸弹,删除bigkey时通常使用渐进式遍历的方式,防止出现Redis阻塞的情况。
5)通过客户端、代理、monitor、机器抓包四种方式找到热点key,这几种方式各具优势,具体使用哪种要根据当前场景来决定。