一、什麼是多路復用
多路復用是指在單一的通信信道上,同時傳輸多個信號或數據流的技術。舉個例子,我們可以想象一條高速公路,車輛在不同的車道上行駛,但它們共用同一條路。
二、Redis的多路復用
Redis是一個基於內存的key-value存儲系統,常用於緩存,消息隊列和會話管理等場景。Redis Server在啟動時會監聽一定端口,接收來自客戶端的連接請求。在客戶端與服務端之間的通信中,多路復用起到了至關重要的作用。
Redis服務端採用了單線程模型,避免了多線程並髮帶來的競爭、鎖等問題。但在單線程模型下,如果客戶端連接較多,也會導致服務器性能下降。多路復用技術可以同時處理多個連接請求,減少IO的消耗,提升Redis Server的性能。
三、Redis的多路復用實現原理
Redis服務端對於客戶端的請求,採取了以下兩種方式進行處理:
- 阻塞式
- 非阻塞式
1. 阻塞式
// 阻塞式版本的實現 while(1) { fd = accept(); read(fd); }
在阻塞式的方式中,Redis Server會為每個連接請求創建一個新的線程,然後使用阻塞方式處理IO。當連接請求較多時,會導致服務器創建的線程數太多,系統性能大幅下降。
2. 非阻塞式
// 非阻塞式版本的實現 while(1) { fd_set rfds; FD_ZERO(&rfds); FD_SET(serversock); for (i=0; i<numclients; i++) FD_SET(clients[i]); select(n, &rfds, NULL, NULL, NULL); for (i=0; i<numclients; i++) { if (FD_ISSET(clients[i])) { read(clients[i]); } } }
在非阻塞式中,Redis Server使用了IO多路復用的方式,將多個連接請求的處理過程合併為一個事件循環中。在每個循環周期中,Redis Server對所有的連接請求進行監聽,如果有請求就進行接收處理,如果沒有請求則繼續等待。
在select、poll和epoll等多路復用IO模型中,Redis Server使用的就是epoll模型。epoll模型的特點是,將連接請求的狀態保存在內核中,減少了每次輪詢的開銷,提高了服務器的效率。
四、Redis多路復用實現代碼
1. 服務器端
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #define MAX_EVENTS 10 int main() { struct epoll_event event, events[MAX_EVENTS]; int listen_sock, conn_sock, nfds, epollfd; struct sockaddr_in addr; char buf[256]; int i; listen_sock = socket(AF_INET, SOCK_STREAM, 0); if (listen_sock == -1) { perror("socket failed"); exit(EXIT_FAILURE); } memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons(8080); addr.sin_addr.s_addr = INADDR_ANY; if (bind(listen_sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == -1) { perror("bind failed"); exit(EXIT_FAILURE); } if (listen(listen_sock, 5) == -1) { perror("listen failed"); exit(EXIT_FAILURE); } epollfd = epoll_create1(0); if (epollfd == -1) { perror("epoll_create1 failed"); exit(EXIT_FAILURE); } event.data.fd = listen_sock; event.events = EPOLLIN | EPOLLET; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &event) == -1) { perror("epoll_ctl failed"); exit(EXIT_FAILURE); } for (;;) { nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait failed"); exit(EXIT_FAILURE); } for (i = 0; i < nfds; ++i) { if (events[i].data.fd == listen_sock) { conn_sock = accept(listen_sock, NULL, NULL); if (conn_sock == -1) { perror("accept failed"); exit(EXIT_FAILURE); } sprintf(buf, "Hello, client %d", conn_sock); write(conn_sock, buf, strlen(buf)); event.data.fd = conn_sock; event.events = EPOLLIN | EPOLLET; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &event) == -1) { perror("epoll_ctl add conn_sock failed"); } } else { int n = read(events[i].data.fd, buf, sizeof(buf)); if (n <= 0) { if (epoll_ctl(epollfd, EPOLL_CTL_DEL, events[i].data.fd, NULL) == -1) { perror("epoll_ctl delete fd failed"); } close(events[i].data.fd); } else { write(events[i].data.fd, buf, n); } } } } close(listen_sock); exit(EXIT_SUCCESS); }
2. 客戶端
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main() { int client_sock; struct sockaddr_in server_addr; char buf[256]; client_sock = socket(AF_INET, SOCK_STREAM, 0); if (client_sock == -1) { perror("socket failed"); exit(EXIT_FAILURE); } memset(&server_addr, 0, sizeof(struct sockaddr_in)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(client_sock, (struct sockaddr *) &server_addr, sizeof(struct sockaddr_in)) == -1) { perror("connect failed"); exit(EXIT_FAILURE); } int n = read(client_sock, buf, sizeof(buf)); if (n > 0) { buf[n] = '\0'; printf("%s\n", buf); } close(client_sock); exit(EXIT_SUCCESS); }
五、結論
Redis的多路復用技術可以在單線程模型下提升服務器的性能,避免了多線程並髮帶來的競爭、鎖等問題。Redis服務端採用的是epoll模型,將連接請求的狀態保存在內核中,減少了每次輪詢的開銷。在實際應用中,我們可以參考該示例代碼,進行自己的開發和部署。
原創文章,作者:FOYAF,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/333490.html