La première chose est d'enregistrer le processeur ;
Ouvrez le port d'écoute en boucle, et une Goroutine sera créée à chaque fois qu'une connexion est surveillée
Ensuite, la Goroutine attendra dans un boucle pour recevoir les données de la demande, puis faire correspondre le processeur correspondant dans la table de routage du processeur en fonction de l'adresse demandée, puis transmettre la demande au processeur pour traitement
exprimé en code est comme ceci :
func (srv *Server) Serve(l net.Listener) error { ... baseCtx := context.Background() ctx := context.WithValue(baseCtx, ServerContextKey, srv) for { // 接收 listener 过来的网络连接 rw, err := l.Accept() ... tempDelay = 0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // 创建协程处理连接 go c.serve(connCtx) } }
Il ; est un peu différent pour Redis, car il est monothread et ne peut pas utiliser le multithreading pour traiter les connexions, donc Redis choisit d'utiliser un pilote d'événement basé sur le mode Reactor pour implémenter le traitement simultané des événements.
Le mode dit Reactor dans Redis consiste à surveiller plusieurs fds via epoll Chaque fois que ces fds répondent, epoll sera notifié sous la forme d'événements pour les rappels. Chaque événement a un gestionnaire d'événements correspondant.
Par exemple : accept correspond au gestionnaire d'événements acceptTCPHandler, read & write correspond au gestionnaire d'événements readQueryFromClient, etc., puis l'événement est attribué au processeur d'événements pour être traité via la répartition de la boucle d'événements.
Donc, le mode Reactor ci-dessus est implémenté via epoll. Pour epoll, il existe principalement trois méthodes :
//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大 int epoll_create(int size); /* * 可以理解为,增删改 fd 需要监听的事件 * epfd 是 epoll_create() 创建的句柄。 * op 表示 增删改 * epoll_event 表示需要监听的事件,Redis 只用到了可读,可写,错误,挂断 四个状态 */ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); /* * 可以理解为查询符合条件的事件 * epfd 是 epoll_create() 创建的句柄。 * epoll_event 用来存放从内核得到事件的集合 * maxevents 获取的最大事件数 * timeout 等待超时时间 */ int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
Nous pouvons donc implémenter un serveur simple basé sur ces trois méthodes :
// 创建监听 int listenfd = ::socket(); // 绑定ip和端口 int r = ::bind(); // 创建 epoll 实例 int epollfd = epoll_create(xxx); // 添加epoll要监听的事件类型 int r = epoll_ctl(..., listenfd, ...); struct epoll_event* alive_events = static_cast<epoll_event*>(calloc(kMaxEvents, sizeof(epoll_event))); while (true) { // 等待事件 int num = epoll_wait(epollfd, alive_events, kMaxEvents, kEpollWaitTime); // 遍历事件,并进行事件处理 for (int i = 0; i < num; ++i) { int fd = alive_events[i].data.fd; // 获取事件 int events = alive_events[i].events; // 进行事件的分发 if ( (events & EPOLLERR) || (events & EPOLLHUP) ) { ... } else if (events & EPOLLRDHUP) { ... } ... } }
Donc, basé sur ce qui précède. introduction, vous pouvez savoir que pour Redis, une boucle d'événements n'est rien de plus que quelques étapes :
Enregistrer les fonctions d'écoute et de rappel des événements
Boucle en attente pour obtenir les événements et les traiter ; fonction de rappel pour traiter la logique des données ;
Réécrire les données au client ;
Enregistrez fd sur epoll et définissez la fonction de rappel acceptTcpHandler. être appelé ;
Démarrez une boucle infinie pour appeler epoll_wait pour attendre et continuer à traiter l'événement. Plus tard, nous reviendrons à la fonction aeMain pour boucler la fonction aeProcessEvents
Lorsqu'un événement réseau survient, la fonction de rappel ; acceptTcpHandler sera appelé jusqu'au bout. readQueryFromClient traitera les données. readQueryFromClient analysera les données client et trouvera la fonction cmd correspondante à exécuter
Après avoir reçu la demande du client, l'instance Redis traitera la commande client et écrira les données renvoyées ; au tampon de sortie du client au lieu de revenir immédiatement ;
Ensuite, la fonction beforeSleep est appelée à chaque fois que la fonction aeMain boucle pour réécrire les données dans le tampon au client
L'ensemble du processus de boucle d'événement ci-dessus est ; en fait, le code Les étapes ont été écrites très clairement, et il existe de nombreux articles sur Internet à ce sujet, donc je n'entrerai pas dans les détails.
Exécution de commande#
readQueryFromClient appellera la fonction processInputBufferAndReplicate pour traiter la commande demandée ;
Dans la fonction processInputBufferAndReplicate sera appelée et déterminera s'il est nécessaire d'utiliser le mode cluster. la commande est copiée sur d'autres nœuds ; la fonction
processInputBuffer traitera la commande demandée dans une boucle et appellera la fonction processInlineBuffer selon le protocole demandé, puis appellera processCommand pour exécuter la commande après l'objet redisObject ;
processCommand passera lors de l'exécution de la commande. lookupCommand va dans la tableserver.commands
pour trouver la fonction d'exécution correspondante en fonction de la commande, puis après une série de vérifications, appelle la fonction correspondante pour exécuter la commande. et appelle addReply pour écrire les données renvoyées dans la zone de tampon de sortie du client ; server.commands
enregistrera toutes les commandes Redis dans la fonction populateCommandTable en tant que table qui obtient les fonctions de commande basées sur. le nom de la commande. void getCommand(client *c) { getGenericCommand(c); } int getGenericCommand(client *c) { robj *o; // 查找数据 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL) return C_OK; ... } robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) { //到db中查找数据 robj *o = lookupKeyRead(c->db, key); // 写入到缓存中 if (!o) addReply(c,reply); return o; }
server.commands
表中根据命令查找对应的执行函数,然后经过一系列的校验之后,调用相应的函数执行命令,调用 addReply 将要返回的数据写入客户端输出缓冲区;server.commands
Recherchez les données dans la fonction getCommand, puis appelez addReply pour écrire les données renvoyées dans le tampon de sortie client.
Réécriture des données sur le client#
Après avoir écrit la commande dans le tampon, les données doivent être retirées du tampon et renvoyées au client. Pour le processus de réécriture des données vers le client, il est en fait terminé dans la boucle d'événements du serveur.
Tout d'abord, Redis appellera la fonction aeSetBeforeSleepProc dans la fonction principale pour enregistrer la fonction beforeSleep du package writeback dans eventLoop
Ensuite, Redis déterminera si beforesleep existe lors de l'appel de la fonction aeMain pour ; la boucle d'événement. est définie, si elle existe, elle sera appelée ; la fonction
beforesleep appellera la fonction handleClientsWithPendingWrites, qui appellera writeToClient pour réécrire les données sur le client à partir du tampon.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!