在使用MS opentech開發的redis 2.6 windows版本時,會出現在連接數過多(在我的機器上是超過800)時,會出現如下錯誤
"Error registering fd event for the new client: Result too large (fd=xxxxx)"
在網上卻收不到任何結果.在看過代碼之后,發現windows版本的redis的實現是有問題的.
原版的redis是使用accept來獲取一個新的連接的fd,然后使用這個fd作為aeEventLoop->events的index來存儲並獲取events的值,從而處理事件.而MS OpenTech使用IOCP模型實現了一個aeWinAccept來獲取fd.關鍵代碼如下:
int aeWinQueueAccept(SOCKET listensock) { aeSockState *sockstate; aeSockState *accsockstate; DWORD result, bytes; SOCKET acceptsock; aacceptreq * areq; if ((sockstate = aeGetSockState(iocpState, (int)listensock)) == NULL) { errno = WSAEINVAL; return -1; } //這里使用了socket的返回值來初始化aeEventLoop中的fd值 acceptsock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (acceptsock == INVALID_SOCKET) { errno = WSAEINVAL; return -1; } accsockstate = aeGetSockState(iocpState, (int)acceptsock); if (accsockstate == NULL) { errno = WSAEINVAL; return -1; } ...
int aeWinAccept(int fd, struct sockaddr *sa, socklen_t *len) { aeSockState *sockstate; int acceptsock; int result; SOCKADDR *plocalsa; SOCKADDR *premotesa; int locallen, remotelen; aacceptreq * areq; SOCKET listenSock = (SOCKET)fd; if ((sockstate = aeGetSockState(iocpState, fd)) == NULL) { errno = WSAEINVAL; return SOCKET_ERROR; } //在這里取回之前存在aeEventLoop中的socket句柄作為fd areq = sockstate->reqs; if (areq == NULL) { errno = WSAEINVAL; return SOCKET_ERROR; } sockstate->reqs = areq->next; acceptsock = (int)areq->accept; ...
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, aeFileProc *proc, void *clientData) { aeFileEvent *fe; if (fd >= eventLoop->setsize) { errno = ERANGE; return AE_ERR; } //在這里判斷fd是否超過pool的size,並通過fd的值來取得事件 fe = &eventLoop->events[fd]; ...
Windows版本的redis和原版的redis的實現是一樣的,重要的是accept的返回值和aeWinAccept所返回的socket句柄邏輯不一樣導致windows版本的redis出現了bug.在Linux中,accept的返回是遞增的,因此linux版本的redis是可以處理eventLoop->setsize個連接的,而在windows上socket返回的句柄會以大概12的步進增長,所以windows版本的redis就只能處理約eventLoop->setsize/12個連接了.
解決方法:
我的解決方法比較hack,我在aeEventLoop中添加一個fd_map的數組,用於把fd映射到0到eventLoop->setsize的值,這樣每次從events中存取數據就通過查詢fd_map來獲取index,從而達到讓redis能處理eventLoop->setsize個連接的效果.
不過由於這個方法比較猥瑣,我就沒有提交patch了,而是提交了一個issue,不知道下一個版本會不會解決這個問題.