為什麼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並沒有顯著的效能優勢