In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article mainly explains "why single-threaded Redis is so fast". The content in the article is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn why single-threaded Redis is so fast.
Preface
Generally speaking, Redis is a single thread, which mainly means that the network IO and read-write key-value pairs of redis are completed by a single thread. This is also the main process for redis to provide key storage services. But other functions, such as persistence, cluster data synchronization, etc., are actually performed by additional threads.
Therefore, redis is not a single thread in a complete sense, but it is generally regarded as a typical representative of single-thread high performance. So, many friends will ask, why use single thread? Why a single thread can be so fast.
Why does Redis use single thread
First of all, we need to understand the cost of multithreading. At ordinary times, many people think that using multi-threading can increase system throughput or increase system scalability. Indeed, for a multi-threaded system, in the case of reasonable resource allocation, it can increase the resource entities in the system to handle request operations, and then improve the number of requests that the system can process at the same time, that is, throughput. However, if you do not have good system design experience, the actual result will actually increase the system throughput when the number of threads begins to increase. However, when you further increase the number of threads, the system throughput increases slowly, or even decreases.
Why did this happen? The key performance bottleneck is the simultaneous access to critical resources by multiple threads in the system. For example, when multiple threads want to modify a shared data, in order to ensure the correctness of the resource, an additional mechanism such as mutex is needed to ensure that this additional mechanism requires additional overhead. For example, redis has a data type List, which provides LPOP and LPUSH operations. Assume that redis is multithreaded. Now suppose there are two threads T1 and T2 threads T1 performing a LPUSH operation on a List, and thread T2 performing a LPOP operation on that List and subtracting 1 from the queue length. In order to ensure the correctness of the queue length, we need to have the LPUSH and LPOP of these two threads execute sequentially, otherwise, we may get the wrong length result. This is the problem of concurrent access control of shared resources that is often encountered in multithreaded programming.
And concurrency control has always been a difficult problem in multithreaded development. If we have no design experience and simply adopt a coarse-grained mutex, there will be an unsatisfactory result, that is, even with the addition of threads, most threads are still waiting for the mutex to access the critical resources, resulting in parallelism changing to serial, and the system throughput does not increase with the increase of threads.
Why is single-thread Redis so fast?
In general, the processing power of single thread is much worse than that of multi-thread, but Redis can achieve 100, 000 processing power per second using single-thread model. Why?
On the one hand, most of the operations of Redis are done in memory, coupled with the fact that it uses efficient data structures (such as hash tables, jump tables).
On the other hand, Redis adopts the multiplexing mechanism, which can process a large number of client requests concurrently in the network IO operation, thus achieving high throughput. So why does Redis use multiplexing?
As shown in the figure above, in order to process a get request, Redis needs to listen to the client request (bind/listen) and then establish a connection (accept) with the client. Read the request (recv) from the socket, parse the client to send the request, read the key value data (get) according to the request type, and finally return the result to the client (send). Where accept () and recv () are blocking operations by default. When Redis listens for a client to have a connection request, but fails to establish a connection successfully, it will block in the accept () function, which can easily cause other clients to fail to establish a connection with Redis. Similarly, when Redist reads data from a client through recv (), Redis blocks at recv () if the data never arrives. Therefore, this will cause the entire thread of Redis to block and unable to process other client requests, which is extremely inefficient. Therefore, you need to set socket to non-blocking.
Non-blocking mode of Redis
Non-blocking mode settings for the socket network model. Generally speaking, fcntl is mainly called. The sample code is as follows
Int flag flags = fcntl (fd, F_GETFL, 0); if (flags
< 0) { ... } flags |= O_NONBLOCK; if(fcntl(fd, F_SETFL, flags) < 0) { ... return -1; } 在Redis的anet.c文件中也是的非阻塞代码也是类似逻辑。用anetSetBlock函数处理,函数定义如下: int anetSetBlock(char *err, int fd, int non_block) { int flags; /* Set the socket blocking (if non_block is zero) or non-blocking. * Note that fcntl(2) for F_GETFL and F_SETFL can't be * interrupted by a signal. */ if ((flags = fcntl(fd, F_GETFL)) == -1) { anetSetError(err, "fcntl(F_GETFL): %s", strerror(errno)); return ANET_ERR; } /* Check if this flag has been set or unset, if so, * then there is no need to call fcntl to set/unset it again. */ if (!!(flags & O_NONBLOCK) == !!non_block) return ANET_OK; if (non_block) flags |= O_NONBLOCK; else flags &= ~O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) == -1) { anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s", strerror(errno)); return ANET_ERR; } return ANET_OK;} 监听套接字设置为非阻塞模式,Redis调动accept()函数但一直未有连接请求到达时,Redis线程可以返回处理其它操作,而不用一直等待。类似的,也可以针对已连接套接字设置非阻塞模式,Redis调用recv()后,如果已连接套接字上一直没有数据到达,Redis线程同样可以返回处理其它操作。但是我们也需要有机制继续监听该已连接套接字,并在有数据到达时通知Redis。这样才能保证Redis线程,即不会像基本IO模型中一直阻塞点等待,也不会导致Redis无法处理实际到达的连接请求。 基于EPOLL机制实现 Linux中的IO多路复用是指一个执行体可以同时处理多个IO流,就是经常听到的select/EPOLL机制。该机制可以允许内核中同时允许多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求。一旦有请求到达就会交给Redis线程处理。 Redis网络框架基于EPOLL机制,此时,Redis线程不会阻塞在某个特定的监听或已连接套接字上,也就不会阻塞在某一个特定的客户端请求处理上。所以,Redis可以同时处理多个客户端的连接请求。如下图In order to notify the Redis thread when the request arrives, EPOLL provides a callback mechanism for events. That is, the corresponding handling functions are called for different events. Let's introduce how it is implemented.
File event
Redis records a file event with the following structure:
/ * File event structure * / typedef struct aeFileEvent {int mask; / * one of AE_ (READABLE | WRITABLE | BARRIER) * / aeFileProc * rfileProc; aeFileProc * wfileProc; void * clientData;} aeFileEvent
Mask is used in the structure to describe what happened:
AE_READABLE: file descriptors are readable
AE_WRITABLE: file descriptor writable
AE_BARRIER: file descriptor blocking
So how does the callback mechanism work? In fact, rfileProc and wfileProc are callback functions when read events and write events occur, respectively. Their corresponding functions are as follows
Typedef void aeFileProc (struct aeEventLoop * eventLoop, int fd, void * clientData, int mask); event loop
Redis uses the following structure to record events registered in the system and their status:
/ * State of an event based program * / typedef struct aeEventLoop {int maxfd; / * highest file descriptor currently registered * / int setsize; / * max number of file descriptors tracked * / long long timeEventNextId; time_t lastTime; / * Used to detect system clock skew * / aeFileEvent * events; / * Registered events * / aeFiredEvent * fired; / * Fired events * / aeTimeEvent * timeEventHead; int stop; void * apidata / * This is used for polling API specific data * / aeBeforeSleepProc * beforesleep; aeBeforeSleepProc * aftersleep;} aeEventLoop
In this structure, the most important are the file event pointer events and the time event header pointer timeEventHead. The file event pointer event points to a fixed-size (configurable) array, and the event object corresponding to the file can be obtained by using the file descriptor as the subscript.
AeApiAddEvent function
This function is mainly used to correlate events to EPOLL, so the ctl method of epoll is defined as follows:
Static int aeApiAddEvent (aeEventLoop * eventLoop, int fd, int mask) {aeApiState * state = eventLoop- > apidata; struct epoll_event ee = {0}; / * avoid valgrind warning * / / * If the fd was already monitored for some event, we need a MOD * operation. Otherwise we need an ADD operation. * * if fd is not associated with any events, then this is an ADD operation. * this is a MOD operation if one / some events are already associated. * / int op = eventLoop- > events [FD] .mask = = AE_NONE? EPOLL_CTL_ADD: EPOLL_CTL_MOD; ee.events = 0; mask | = eventLoop- > events [FD] .mask; / * Merge old events * / if (mask & AE_READABLE) ee.events | = EPOLLIN; if (mask & AE_WRITABLE) ee.events | = EPOLLOUT; ee.data.fd = fd; if (epoll_ctl (state- > epfd,op,fd,&ee) =-1) return-1; return 0;}
It is called when the Redis service creates a client request and registers a read event.
PrepareClientToWrite is called when Redis needs to write data to the client. The main purpose of this method is to register the write event corresponding to fd.
If the registration fails, Redis will not write the data to the buffer.
If the corresponding package word is writable, then Redis's event loop writes new buffer data to socket.
Event registration function aeCreateFileEvent
This is the registration process of the file event, and the function is implemented as follows
Int aeCreateFileEvent (aeEventLoop * eventLoop, int fd, int mask, aeFileProc * proc, void * clientData) {if (fd > = eventLoop- > setsize) {errno = ERANGE; return AE_ERR;} aeFileEvent * fe = & eventLoop- > events [fd]; if (aeApiAddEvent (eventLoop, fd, mask) =-1) return AE_ERR; fe- > mask | = mask; if (mask & AE_READABLE) fe- > rfileProc = proc If (mask & AE_WRITABLE) fe- > wfileProc = proc; fe- > clientData = clientData; if (fd > eventLoop- > maxfd) eventLoop- > maxfd = fd; return AE_OK;}
This function first obtains the file event object according to the file descriptor, then adds the file descriptor you care about in the operating system (using the addApiAddEvent function mentioned above), and finally logs the callback function to the file event object. Therefore, a thread can listen for multiple file events at the same time, which is called IO multiplexing.
AeMain function
The main loop of the Redis event handler
Void aeMain (aeEventLoop * eventLoop) {eventLoop- > stop = 0; while (! eventLoop- > stop) {/ / start processing event aeProcessEvents (eventLoop, AE_ALL_EVENTS | AE_CALL_BEFORE_SLEEP | AE_CALL_AFTER_SLEEP);}}
This method will eventually call epoll_wait () to get the corresponding event and execute it.
These events are put into an event queue, which is continuously processed by a single thread of Redis. For example, when a read request arrives, a read request corresponds to a read event. Redis registers the get callback function for this event. When the kernel monitor hears that a read request arrives, it triggers a read event, and then calls back the corresponding get function of Redis.
Return data to the client
After Redis completes the request, Redis does not register a write file event after processing a request, and then write the result back to the client in the event callback function. When a file event is detected, Redis handles these file events by calling the rReadProc or writeProc callback function. After the processing is completed, the data that needs to be written back to the client is cached into memory first.
Typedef struct client {... List * reply; / * List of reply objects to send to the client. * /. Int bufpos; char buf [proto _ REPLY_CHUNK_BYTES];} client
The data sent to the client is stored in two places:
The reply pointer stores the object to be sent
The data to be returned is stored in the buf, and the bufpos indicates the location of the last byte in the data.
Note: as long as it can be stored in buf, try to store it in the buf byte array, and if the buf cannot be saved, it will be stored in the reply object array.
The writeback client calls the following function to handle the writeback logic before entering the next file waiting event
Int writeToClient (int fd, client * c, int handler_installed) {while (clientHasPendingReplies (c)) {if (c-> bufpos > 0) {nwritten = write (fd,c- > buf+c- > sentlen,c- > bufpos-c- > sentlen); if (nwritten sentlen + = nwritten; totwritten + = nwritten; if ((int) c-> sentlen = = c-> bufpos) {c-> bufpos = 0 C-> sentlen = 0;}} else {o = listNodeValue (listFirst (c-> reply)); objlen = o-> used; if (objlen = = 0) {c-> reply_bytes-= o-> size; listDelNode (c-> reply,listFirst (c-> reply)); continue } nwritten = write (fd, o-> buf + c-> sentlen, objlen-c-> sentlen); if (nwritten sentlen + = nwritten; totwritten + = nwritten) Thank you for reading. The above is the content of "Why single-threaded Redis is so fast". After the study of this article, I believe you have a deeper understanding of why single-threaded Redis is so fast, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.