一、什麼是多路復用
多路復用是指在單一的通信信道上,同時傳輸多個信號或數據流的技術。舉個例子,我們可以想像一條高速公路,車輛在不同的車道上行駛,但它們共用同一條路。
二、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-tw/n/333490.html
微信掃一掃
支付寶掃一掃