👉导读
👉目录
一位物理学家和一位数学家正坐在教职员休息室里。突然间,咖啡机着火了。物理学家抓起一个水桶,朝水槽跳跃,用水填满水桶,把火浇灭。第二天,同样的两个人坐在同一个休息室。咖啡机再次着火。这一次,数学家赶紧跳起来,抄起桶,递给了物理学家。(数学家擅长将问题规约到以前解决的问题中)
业务服务器到 Redis 服务器之间的网络存在问题,例如网络线路质量不佳,网络数据包在传输时存在延迟、丢包等情况。 网络和通信导致的固有延迟: 客户端使用 TCP/IP 连接或 Unix 域连接连接到 Redis,在 1 Gbit/s 网络下的延迟约为200 us,而 Unix 域 Socket 的延迟甚至可低至 30 us,这实际上取决于网络和系统硬件;在网络通信的基础之上,操作系统还会增加了一些额外的延迟(如线程调度、CPU 缓存、NUMA 等);并且在虚拟环境中,系统引起的延迟比在物理机上也要高得多。 结果就是,即使 Redis 在亚微秒的时间级别上能处理大多数命令,网络和系统相关的延迟仍然是不可避免的。
# 从Rdis上一次启动以来总计处理的命令数
total_commands_processed:2255
# 当前Redis实例的OPS,redis内部较实时的每秒执行的命令数
instantaneous_ops_per_sec:12
# 网络总入量
total_net_input_bytes:34312
# 网络总出量
total_net_output_bytes:78215
# 每秒输入量,单位是kb/s
instantaneous_input_kbps:1.20
# 每秒输出量,单位是kb/s
instantaneous_output_kbps:2.62
内存碎片率( mem_fragmentation_ratio )指标给出了操作系统( used_memory_rss )使用的内存与 Redis( used_memory )分配的内存的比率 mem_fragmentation_ratio = used_memory_rss / used_memory 操作系统负责为每个进程分配物理内存,而操作系统中的虚拟内存管理器保管着由内存分配器分配的实际内存映射 那么如果我们的 Redis 实例的内存使用量为1 GB,内存分配器将首先尝试找到一个连续的内存段来存储数据;如果找不到连续的段,则分配器必须将进程的数据分成多个段,从而导致内存开销增加,具体的相关解释可参考这篇文章:Redis 内存碎片的产生与清理 内存碎片率大于1表示正在发生碎片,内存碎片率超过1.5表示碎片过多,Redis 实例消耗了其实际申请的物理内存的150%的内存;另一方面,如果内存碎片率低于1,则表示Redis需要的内存多于系统上的可用内存,这会导致 swap 操作。内存交换到磁盘将导致延迟显著增加 理想情况下,操作系统将在物理内存中分配一个连续的段,Redis 的内存碎片率等于1或略大于1。
lazyfree-lazy-eviction yes
Redis 将所有数据放在内存中,内存的响应时长大约为100纳秒,对于小数据包,Redis 服务器可以处理80,000到100,000 QPS,这也是 Redis 处理的极限了,对于80%的公司来说,单线程的 Redis 已经足够使用了。 但随着越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的 QPS。常见的解决方案是在分布式架构中对数据进行分区并采用多个服务器,但该方案有非常大的缺点,例如要管理的 Redis 服务器太多,维护代价大;某些适用于单个 Redis 服务器的命令不适用于数据分区;数据分区无法解决热点读/写问题;数据偏斜,重新分配和放大/缩小变得更加复杂等等。 从 Redis 自身角度来说,因为读写网络的 read/write 系统调用占用了 Redis 执行期间大部分 CPU 时间,瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向: 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式 使用多线程充分利用多核,典型的实现比如 Memcached。 协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以总结起来,redis支持多线程主要就是两个原因: 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核。 多线程任务可以分摊 Redis 同步 IO 读写负荷。
而 Redis 6.0 以前的单线程网络 IO 模型的处理具体的负载在哪里呢?虽然 Redis 利用epoll机制实现 IO 多路复用(即使用 epoll 监听各类事件,通过事件回调函数进行事件处理),但 I/O 这一步骤是无法避免且始终由单线程串行处理的,且涉及用户态/内核态的切换,即: 从socket中读取请求数据,会从内核态将数据拷贝到用户态 (read 调用) 将数据回写到socket,会将数据从用户态拷贝到内核态 (write 调用)