为什么reids是单线程还那么快?
背景介绍:
redis一个非常大的特点就是快,官方给出说明:
1.单线程,减少线程切换时间。
2.纯内存操作
3.I/O多路复用机制
理解起来单线程操作减少了线程切换的时间,以及减少了多线程的复杂度这个很好理解;纯内存操作,减少了访问硬盘的操作,但是I/O多路复用是什么意思?
内容
1.什么是I/O多路复用机制?
多路 I/O 复用模型是利用select、poll、epoll可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快
2.redis操作的瓶颈在哪里?
操作的瓶颈在于网络的I/O,I/O操作的步骤分为:
- 数据通过网关到达内核,内核准备好数据
- 数据从内核缓存缓存写入到用户程序数据
3.内核和用户数据分别代表什么区域?
4.阻塞I/O,非阻塞I/O,I/O多路复用之间的区别
类比于jdk中NIO操作,NIO提供了selector器,是selectableChannel的多路服务器,用于监控SelectableChannel的io状态。通道注册到selector器上,而且可以选择注册那种事件类型,由selector对注册事件进行轮询。
以nio的SocketChannel为例,写示例代码;
public class TestNonBlockNIO {
@Test
public void testClient() throws IOException {
//1.获取通道
SocketChannel sChannel=SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));
//2.切换到非阻塞的状态
sChannel.configureBlocking(false);
//3.分配指定大小的缓冲区
ByteBuffer buffer=ByteBuffer.allocate(1024);
//4.发送数据给服务端
Scanner scan=new Scanner(System.in);
while(scan.hasNext()){
String str=scan.next();
buffer.put((new Date().toString()+"\n"+str).getBytes());
buffer.flip();
sChannel.write(buffer);
buffer.clear();
}
// buffer.put(LocalDate.now().toString().getBytes());
//5.关闭通道
sChannel.close();
}
@Test
public void testServer() throws IOException {
//1.获取server通道
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//2.切换成非阻塞的模式
serverSocketChannel.configureBlocking(false);
//3.绑定连接
serverSocketChannel.bind(new InetSocketAddress(9898));
//4.获取选择器
Selector selector = Selector.open();
//5.通道注册到选择器上,并且选择监听的事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.轮询式获取选择器上已经“准备就绪”的事件
while (selector.select()>0){
//7.获取当前选择器中所有注册的“已经就绪的监听事件”
Iterator<SelectionKey> iterable= selector.selectedKeys().iterator();
while (iterable.hasNext()){
//8.获取准备就绪的事件
SelectionKey sk=iterable.next();
//9.判断具体是什么事件就绪了
if(sk.isAcceptable()){ //接收就绪
//10.若接收就绪,获取客户端的连接
SocketChannel socketChannel=serverSocketChannel.accept();
//11.切换到非阻塞的模式
socketChannel.configureBlocking(false);
//12.将该通道注册到选择器上
socketChannel.register(selector,SelectionKey.OP_READ);
}
if(sk.isReadable()){
//13.获取读状态就绪的通道
SocketChannel socketChannel= (SocketChannel) sk.channel();
ByteBuffer buffer=ByteBuffer.allocate(1024);
int len=0;
while((len=socketChannel.read(buffer))>0){
buffer.flip();
System.out.println(new String(buffer.array(),0,len));
buffer.clear();
}
}
}
//14.取消选择键
iterable.remove();
}
}
}
总结
其实reids之所以单线程还如此之快的原因就是因为内部采用了I/O多路复用机制模型,但是这种机制不是什么情况下都是使用的,应为用与大量的链接,处理时间又不是很长的业务,连接数最好是大于1000,并发程度不高或者局域网环境下NIO并没有显著的性能优势
下一篇: PHP + Redis 实现简单消息队列