redis相关

2.Redis 单线程含义

 

  • Redis 的网络 IO 和键值对读写是由一个线程来完成的
  • Redis 其他功能,如持久化、异步删除、集群数据同步等其实都是由额外线程执行

3.Redis单线程设计

3.1 多线程需要解决的问题

4.多路复用机制

       Redis 多路复用机制在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率(每秒处理的请求数)。

4.1 IO 模型

 

.2 潜在阻塞点

 

  • accept:当 Redis 监听到一个客户端有连接请求,但一直未能成功建立起连接时,会阻塞其他客户端和 Redis 建立连接
  • recv:当 Redis 通过 recv() 从一个客户端读取数据时,如果一直没有到达,Redis 也会一直阻塞在 recv()

 

4.3 socket网络模型非阻塞模式

  • socket()方法:socket()方法会返回主动套接字,然后调用 listen() 方法
  • listen()方法:将主动套接字转化为监听套接字,此时可以用来监听来自客户端的连接请求,可设置 accept() 非阻塞模式
  • accept()方法:最后调用 accept() 方法接收到达的客户端连接,并返回连接套接字,可设置 send()/recv() 非阻塞模式

 

5.1 使用多线程原因

  • 随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 IO 的处理上
  • 单个主线程处理网络请求的速度跟不上底层网络硬件的速度

5.2 对应网络 IO 瓶颈方法

  • 用户态网络协议栈(例如 DPDK)取代内核网络协议栈,让网络请求的处理不用在内核里执行,直接在用户态完成处理就行,该方法需要修改网络源码,可能引入新BUG,导致不稳定,该方法没有采用
  • 采用多个IO线程处理网络请求
    • 阶段一:服务端和客户端建立 Socket 连接,并分配处理线程
    • 阶段二:IO 线程读取并解析请求(有多个 IO 线程在并行处理)
    • 阶段三:主线程执行请求操作
    • 阶段四:IO 线程回写 Socket 和主线程清空全局队列

5.3 Redis6.0 多线程开启方式

  • 需要在 redis.conf 中设置 io-thread-do-reads 配置项为 yes,表示启用多线程
  • 需要在 redis.conf 中设置线程个数。一般来说,线程个数要小于 Redis 实例所在机器的 CPU 核个数,例如,对于一个 8 核的机器来说,Redis 官方建议配置 6IO 线程

2. Redis6.0之前为什么一直不使用多线程?

官方曾做过类似问题的回复:使用Redis时,几乎不存在CPU成为瓶颈的情况, Redis主要受限于内存和网络。例如在一个普通的Linux系统上,Redis通过使用pipelining每秒可以处理100万个请求,所以如果应用程序主要使用O(N)或O(log(N))的命令,它几乎不会占用太多CPU。

使用了单线程后,可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。Redis通过AE事件模型以及IO多路复用等技术,处理性能非常高,因此没有必要使用多线程。单线程机制使得 Redis 内部实现的复杂度大大降低,Hash 的惰性 Rehash、Lpush 等等 “线程不安全” 的命令都可以无锁进行。

布式架构中对数据进行分区并采用多个服务器,但该方案有非常大的缺点,例如要管理的Redis服务器太多,维护代价大;某些适用于单个Redis服务器的命令不适用于数据分区;数据分区无法解决热点读/写问题;数据偏斜,重新分配和放大/缩小变得更加复杂等等。

从Redis自身角度来说,因为读写网络的read/write系统调用占用了Redis执行期间大部分CPU时间,瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:

• 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式
• 使用多线程充分利用多核,典型的实现比如 Memcached。

协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以总结起来,redis支持多线程主要就是两个原因:

• 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核
• 多线程任务可以分摊 Redis 同步 IO 读写负荷

 

7.Redis6.0多线程的实现机制?

在这里插入图片描述

流程简述如下

1、主线程负责接收建立连接请求,获取 socket 放入全局等待读处理队列
2、主线程处理完读事件之后,通过 RR(Round Robin) 将这些连接分配给这些 IO 线程
3、主线程阻塞等待 IO 线程读取 socket 完毕
4、主线程通过单线程的方式执行请求命令,请求数据读取并解析完成,但并不执行
5、主线程阻塞等待 IO 线程将数据回写 socket 完毕
6、解除绑定,清空等待队列

 

 

该设计有如下特点:
1、IO 线程要么同时在读 socket,要么同时在写,不会同时读或写
2、IO 线程只负责读写 socket 解析命令,不负责命令处理

8.开启多线程后,是否会存在线程并发安全问题?

从上面的实现机制可以看出,Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。所以我们不需要去考虑控制 key、lua、事务,LPUSH/LPOP 等等的并发及线程安全问题。

 

10.Redis6.0的多线程和Memcached多线程模型进行对比

前些年memcached 是各大互联网公司常用的缓存方案,因此redis 和 memcached 的区别基本成了面试官缓存方面必问的面试题,最近几年memcached用的少了,基本都是 redis。不过随着Redis6.0加入了多线程特性,类似的问题可能还会出现,接下来我们只针对多线程模型来简单比较一下。

在这里插入图片描述

如上图所示:Memcached 服务器采用 master-woker 模式进行工作,服务端采用 socket 与客户端通讯。主线程、工作线程 采用 pipe管道进行通讯。主线程采用 libevent 监听 listen、accept 的读事件,事件响应后将连接信息的数据结构封装起来,根据算法选择合适的工作线程,将连接任务携带连接信息分发出去,相应的线程利用连接描述符建立与客户端的socket连接 并进行后续的存取数据操作。

Redis6.0与Memcached多线程模型对比:
相同点:都采用了 master线程-worker 线程的模型
不同点:Memcached 执行主逻辑也是在 worker 线程里,模型更加简单,实现了真正的线程隔离,符合我们对线程隔离的常规理解。而 Redis 把处理逻辑交还给 master 线程,虽然一定程度上增加了模型复杂度,但也解决了线程并发安全等问题。

11.Redis作者是如何点评 “多线程”这个新特性的?

关于多线程这个特性,在6.0 RC1时,Antirez曾做过说明:

Redis支持多线程有2种可行的方式:第一种就是像“memcached”那样,一个Redis实例开启多个线程,从而提升GET/SET等简单命令中每秒可以执行的操作。这涉及到I/O、命令解析等多线程处理,因此,我们将其称之为“I/O threading”。另一种就是允许在不同的线程中执行较耗时较慢的命令,以确保其它客户端不被阻塞,我们将这种线程模型称为“Slow commands threading”。

经过深思熟虑,Redis不会采用“I/O threading”,redis在运行时主要受制于网络和内存,所以提升redis性能主要是通过在多个redis实例,特别是redis集群。接下来我们主要会考虑改进两个方面:
1.Redis集群的多个实例通过编排能够合理地使用本地实例的磁盘,避免同时重写AOF。
2.提供一个Redis集群代理,便于用户在没有较好的集群协议客户端时抽象出一个集群。

补充说明一下,Redis和memcached一样是一个内存系统,但不同于Memcached。多线程是复杂的,必须考虑使用简单的数据模型,执行LPUSH的线程需要服务其他执行LPOP的线程。

我真正期望的实际是“slow operations threading”,在redis6或redis7中,将提供“key-level locking”,使得线程可以完全获得对键的控制以处理缓慢的操作。

详见:http://antirez.com/news/126

 

Redis线程中经常提到IO多路复用,如何理解?

这是IO模型的一种,即经典的Reactor设计模式,有时也称为异步阻塞IO。
在这里插入图片描述

多路指的是多个socket连接,复用指的是复用一个线程。多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。

posted @ 2024-02-04 10:38  予真  阅读(2)  评论(0编辑  收藏  举报