Redis是如何基於单线程来应对多线程并发访问呢?
从存储角度
Redis所有数据都在内存中,所有运算也是在内存运算,速度相对来说比较快,虽然对那些时间复杂度为O(n)级别的指令会导致Redis卡顿,但对于其他指令来说,还行。
从网络角度
非阻塞IO + 事件轮询(多路复用)技术
第一:这里Redis用到了非阻塞IO,所谓的非阻塞IO,说白了就是当应用服务器与Redis服务器建立起连接之后,Redis通过在套接字的Non_Blocking选项建立起非阻塞IO模式,该模式下read方法能读多少读多少,write方法能写多少写多少,read/write完全不受阻塞,read方法能读多少取决于内核为套接字分配的读缓冲区内部的数据字节数,write方法能写多少取决于内核为套接字分配的写缓冲区的空闲空间字节数。读方法和写方法都会通过返回值来告知程序实际读写了多少字节。
第二:通过非阻塞IO技术解决了网络读取与写入,那如何确保read与write执行的完整性??这里使用事件轮询(多路复用)技术,借助操作系统提供的API来使用。就拿该API最古老的select函数来说,该函数需要3个参数:read_fds读描述符列表、write_fds写描述符列表、timeout,函数返回值则是对应的可读可写事件。
当没有任何事件到来时则最多等待timeout时间,线程处于阻塞状态;一旦期间有任何事件到来,就可以立即返回。时间过了之后还是没有任何事件到来,也会立即返回。拿到事件后,Redis线程就可以继续挨个处理相应的事件,处理完了继续轮询。
read_fds:Redis会为每个客户端套接字都关联一个指令队列。客户端的指令通过队列来排队进行顺序处理,先到先服务。
write_fds:Redis 同样也会为每个客户端套接字关联一个响应队列。Redis 服务器通过响应队列来将指令的返回结果回复给客户端。如果队列为空,那么意味着连接暂时处于空闲状态,不需要去获取写事件,也就是可以将当前的客户端描述符从write_fds里面移出来。等到队列有数据了,再将描述符放进去。避免select系统调用立即返回写事件,结果发现没什么数据可以写。出这种情况的线程会飙高 CPU。