1. Redis是单线程还是多线程的?
Redis 的核心执行模型是单线程的,但自 Redis 6.0 版本起,在特定场景下支持了多线程处理。
1.1. Redis 的核心执行是单线程的
Redis 的单线程指的是 Redis 的⽹络 IO 以及键值对指令读写是由⼀个线程来执⾏的。 对于
Redis 的持久化、集群数据同步、异步删除等都是其他线程执⾏。单线程保证了单条命令执行的有序性、原子性和一致性,同时避免了线程之间的切换所造成的资源开销,避免了线程之间的竞争问题,比如死锁等,不需要考虑各种锁问题。
Redis的网络I/O是单线程指的是使用一个线程处理所有客户端连接的I/O,不管有多少客户端与Redis连接,所有的客户端请求(包括读写数据)都是通过Redis的这个单线程来处理的。
1.2. Redis中的多线程
虽然 Redis来完成网络I/O和键值对读写操作是使用单线程的,但是因为单线程避免了线程切换之间的消耗、线程创建的开销等等,其执行效率在大多数场景下极高,但是随着网络I/O请求数量的急剧增加,当Redis服务器处于高负载情况时,单线程就无法高效的处理请求,因此自 Redis 6.0 开始,Redis引入了多线程网络I/O的功能,在高负载的情况下,Redis可以使用多线程来处理网络I/O,以减少网络I/O操作对性能的影响。
1.3. Redis多线程网络I/O的工作原理
多线程网络I/O使用多个线程来并行处理客户端的请求接收和响应发送,但命令的执行仍然是单线程完成的。这意味着即使可以使用多个线程处理大量的并发网络请求,但对数据的实际读写仍然是单线程执行的。
Redis的多线程网络I/O底层工作原理是:I/O多路复用实现,多路是指多个socket连接,复用是指复用一个线程。多路复用主要有三种技术:select、poll、epoll,epoll是最新也是目前最好的多路复用技术。当多个客户端连接请求发送到服务器时,Redis会通过I/O多路复用机制来监听多个客户端连接的I/O事件(内核不是监视应用程序本身的连接,而是监视应用程序的文件描述符),然后Redis单线程会处理客户端的连接,并将这些连接交给多个I/O线程进行处理,I/O线程会并发的从不同客户端读取请求,并将请求缓存在一个队列中。每个I/O线程负责从客户端读取数据(例如SET或GET),并解析这些请求的内容。
总结来说Redis的多线程是采用了 主线程 + 工作线程(I/O线程)模式,其中主线程负责核心的命令处理,只有一个核心线程, 它主要负责通过 I/O 多路复用机制 来监听多个客户端的连接,并处理命令的执行。 而工作线程(I/O线程)则负责网络I/O操作,解析具体的请求内容。
2. Redis是单线程的为什么还要使用分布式锁
Redis中的单线程指的是Redis中的网络I/O和键值对的读写操作是由单线程完成的,它只保证了单线程中操作的有序性和原子性,也就是说对于每个客户端发来的请求,Redis会一个一个地处理,不会在命令执行过程中出现并发问题,但是在分布式系统的高并发场景下,Redis的单线程机制无法避免 多个客户端在同一时刻对同一个资源进行修改时可能出现的问题,也就是说Redis的单线程只能保证 每个命令 对Redis实例内部的顺序执行,无法控制客户端之间的请求顺序,因此就会导致多个客户端之间出现对公共变量的竞争问题。
在分布式环境下,某些操作需要分成多个步骤进行,比如客户端需要先读取某共享变量的当前状态,然后基于该状态进行操作,最后更新该资源。这种情况下,如果多个客户端同时进行相同的操作,Redis无法保证这些操作之间的隔离性和有序性,会导致线程的不安全和数据的不一致。
举例:并发环境下的竞态示例
- 线程A和B同时想操作同一个键
key
,假如它们都读取了key
的初始值10,并且分别想把它加1。 - 线程A读取了
key
的值为10,准备将其准备修改为11 - 在A还没完成写入之前,B也读取了
key
的值为10,并准备将其修改为11. - 结果,A和B的写入操作最终导致
key
的值被设置为11,而不是12,因为B覆盖了A的结果。
因此需要使用 分布式锁 来解决上述问题,它的作用是当多个线程尝试操作同一资源时,确保同一时刻只有一个线程能够获得资源的控制权,其他客户端必须等待该客户端释放锁后才能继续操作,确保了多个线程对共享资源的修改是有序的,避免了数据竞争和不一致的情况。