Cao Gong a déclaré que le code source de Redis (3) - analyse complète du processus de démarrage du serveur redis (milieu)

Navigation dans l'article

L'intention initiale de la série de codes source Redis est de nous aider à mieux comprendre Redis et à mieux comprendre Redis, mais comment le comprendre n'est pas suffisant. Il est recommandé de suivre cet article pour créer l'environnement et suivre le code source vous-même, ou Lisez avec moi. Depuis que j'ai utilisé C il y a plusieurs années, certaines erreurs sont inévitables, j'espère que le lecteur pourra le signaler.

Cao Gong a déclaré que le code source de Redis (1) -réduire l'environnement de débogage, utiliser clion, obtenir le même effet que le débogage java

Cao Gong dit Redis code source (2) - analyse du processus de démarrage du serveur Redis et supplément de connaissances de base en langage c simple

Sujet de cette conférence

Tout d'abord, j'ajouterai un peu de connaissances sur les pointeurs en langage C. Ensuite, je commencerai à suivre l'article d'hier sur le processus de démarrage de redis, du plus grand au plus petit, pour éviter de tomber rapidement dans les détails.

Compréhension des pointeurs

Un pointeur pointe en fait vers une adresse mémoire. Ce pointeur peut être interprété arbitrairement par vous si vous savez ce qui est stocké avant et après cette adresse. Permettez-moi de donner un exemple:

typedef struct Test_Struct{
    int a;
    int b;
}Test_Struct;

int main() {
    // 1
    void *pVoid = malloc(4);
    // 2
    memset(pVoid,0x01,4);

    // 3
    int *pInt = pVoid;
    // 4
    char *pChar = pVoid;
    // 5
    short *pShort = pVoid;
    // 6
    Test_Struct *pTestStruct = pVoid;

    // 7
    printf("address:%p, point to %d\n", pChar, *pChar);
    printf("address:%p, point to %d\n", pShort, *pShort);
    printf("address:%p, point to %d\n", pInt, *pInt);
    printf("address:%p, point to %d\n", pTestStruct, pTestStruct->a);
}
  • À un endroit, allouez un morceau de mémoire, 4 octets, 32 bits; renvoyez un pointeur vers cette zone de mémoire, pour être précis, pointez sur le premier octet, car la mémoire allouée est continue, vous pouvez la comprendre comme un tableau.

    La fonction malloc () alloue des octets de taille et renvoie un pointeur vers la mémoire allouée.

  • À deux endroits, appelez memset pour définir les 4 octets de la mémoire pointés par pVoid sur 0x01, en fait, définissez chaque octet sur 00000001.

    Les notes du memset sont les suivantes:

    NAME
           memset - fill memory with a constant byte
    
    SYNOPSIS
           #include <string.h>
    
           void *memset(void *s, int c, size_t n);
    
    DESCRIPTION
           The memset() function fills the first n bytes of the memory area pointed to by s with the constant byte c.
    

    Documents de référence: https://www.cnblogs.com/yhlboke-1992/p/9292877.html

    Ici, nous définissons chaque octet sur 0x01, et le binaire final est en fait le suivant:

  • 3 places, définissez un pointeur de type int, affectez-lui pVoid, int prend 4 octets

  • 4 places, définissez le pointeur de type char, affectez-lui pVoid, car prend 1 octet

  • 5 places, définissez un pointeur de type short, affectez-lui pVoid, short prend 2 octets

  • À six endroits, un pointeur de type Test_Struct est défini. Il s'agit d'une structure similaire à une classe de langage de haut niveau. La structure de cette structure est la suivante:

    typedef struct Test_Struct{
        int a;
        int b;
    }Test_Struct;
    

    De même, nous lui attribuons pVoid.

  • À 7 emplacements, les adresses des différents types de pointeurs et leurs valeurs déréférencées sont imprimées séparément.

La sortie est la suivante:

Le binaire de 257 est: 0000 0001 0000 0001

Le binaire de 16843009 est: 0000 0001 0000 0001 0000 0001 0000 0001

La structure est également facile à comprendre car cette structure, le premier attribut a, est de type int et occupe 4 octets.

En outre, tout le monde doit noter que les adresses de pointeur affichées ci-dessus sont exactement les mêmes.

Si vous pouvez comprendre cette démo, regardez ce lien, je crois qu'il comprendra mieux le pointeur:

Opération arithmétique du pointeur C

Processus de démarrage approximatif du serveur Redis

int main(int argc, char **argv) {
    struct timeval tv;

    /**
     * 1 设置时区等等
     */
    setlocale(LC_COLLATE,"");
    ...

    // 2 检查服务器是否以 Sentinel 模式启动
    server.sentinel_mode = checkForSentinelMode(argc,argv);

    // 3 初始化服务器配置
    initServerConfig();

	// 4
    if (server.sentinel_mode) {
        initSentinelConfig();
        initSentinel();
    }

    // 5 检查用户是否指定了配置文件,或者配置选项
    if (argc >= 2) {
        ...
        // 载入配置文件, options 是前面分析出的给定选项
        loadServerConfig(configfile,options);
        sdsfree(options);
    }

    // 6 将服务器设置为守护进程
    if (server.daemonize) daemonize();

    // 7 创建并初始化服务器数据结构
    initServer();

    // 8 如果服务器是守护进程,那么创建 PID 文件
    if (server.daemonize) createPidFile();

    // 9 为服务器进程设置名字
    redisSetProcTitle(argv[0]);

    // 10 打印 ASCII LOGO
    redisAsciiArt();

    // 11 如果服务器不是运行在 SENTINEL 模式,那么执行以下代码
    if (!server.sentinel_mode) {
        // 从 AOF 文件或者 RDB 文件中载入数据
        loadDataFromDisk();
        // 启动集群
        if (server.cluster_enabled) {
            if (verifyClusterConfigWithData() == REDIS_ERR) {
                redisLog(REDIS_WARNING,
                    "You can't have keys in a DB different than DB 0 when in "
                    "Cluster mode. Exiting.");
                exit(1);
            }
        }
        // 打印 TCP 端口
        if (server.ipfd_count > 0)
            redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);
    } else {
        sentinelIsRunning();
    }

    // 12 运行事件处理器,一直到服务器关闭为止
    aeSetBeforeSleepProc(server.el,beforeSleep);
    aeMain(server.el);

    // 13 服务器关闭,停止事件循环
    aeDeleteEventLoop(server.el);

    return 0;
}
  • 1, 2, 3, comme déjà mentionné dans l'article précédent, initialise principalement divers paramètres de configuration, tels que le socket; redis.conf impliqué, aof, rdb, réplication, sentinelle, etc.; Structure de données, telle que runid, adresse du fichier de configuration, informations relatives au serveur (32 bits ou 64 bits, car redis s'exécute directement sur le système d'exploitation, plutôt que les langages de haut niveau comme les machines virtuelles, 32 bits et 64 bits, données différentes La longueur est différente), le niveau de journalisation, le nombre maximal de clients, le temps d'inactivité maximal des clients, etc.

  • 4 emplacements, car sentinelle et le serveur redis commun partagent en fait le même code, donc lorsque vous commencez ici, cela dépend si vous voulez démarrer sentinelle ou serveur redis ordinaire. S'il est envoyé sentinelle, configurez la configuration liée à la sentinelle

  • 5 places, vérifiez si le fichier de configuration est spécifié dans les paramètres de ligne de commande au démarrage, si spécifié, la configuration du fichier de configuration prévaudra

  • 6 places, définies comme démon

  • 7 emplacements, selon la configuration précédente, initialisent le serveur redis

  • 8 places, créer un fichier pid, le chemin par défaut général: /var/run/redis.pid, cela peut être configuré dans redis.conf, comme:

    pidfile "/var/run/redis_6379.pid"

  • 9 emplacements, définissez le nom du processus serveur

  • 10 places, logo imprimé

  • 11. S'il n'est pas envoyé en mode sentinelle, chargez un fichier aof ou rdb

  • À 12 ans, il saute dans une boucle sans fin, commence à attendre la réception des connexions et traite les demandes des clients; en attendant, il exécute périodiquement des tâches d'arrière-plan, telles que la suppression de clés expirées, etc.

  • À 13, le serveur est arrêté. En règle générale, si vous n'allez pas ici, vous êtes généralement pris dans une boucle infinie à 12; seulement dans certains scénarios, après avoir changé un arrêt de variable globale sur true, le programme sortira de 12. Boucle sans fin, puis est venu ici.

Le processus d'initialisation du serveur redis

Cette section vise principalement à affiner l'opération précédente de l'étape 7, qui consiste à initialiser le serveur redis. Cette fonction, située dans redis.c, s'appelle initServer et fait beaucoup de choses, qui seront expliquées séquentiellement.

Définir la fonction globale de traitement du signal

    // 设置信号处理函数
    signal(SIGHUP, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    setupSignalHandlers();

Le plus important est la dernière ligne:

void setupSignalHandlers(void) {
    // 1
    struct sigaction act;

    /* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used.
     * Otherwise, sa_handler is used. */
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    // 2
    act.sa_handler = sigtermHandler;
    // 3
    sigaction(SIGTERM, &act, NULL);

    return;
}

Trois endroits définis: lors de la réception du signal SIGTERM, utilisé actpour traiter le signal, l'acte est défini en 1, est une variable locale, il a un champ, est attribué en 2 endroits, il s'agit d'un pointeur de fonction. Le pointeur de fonction est similaire à la référence d'une méthode statique en java, pourquoi est-il statique, car l'implémentation de telles méthodes ne nécessite pas de nouvel objet; en langage C, toutes les méthodes sont de niveau supérieur, lors de l'appel, n'ont pas besoin d'un nouvel objet Donc, de ce point de vue, le pointeur de fonction du langage C est similaire à la référence de la méthode statique en java.

On peut regarder 2 endroits,

    act.sa_handler = sigtermHandler;

Ce sigtermHandler doit être une fonction globale, voyez comment il est défini:

// SIGTERM 信号的处理器
static void sigtermHandler(int sig) {
    REDIS_NOTUSED(sig);

    redisLogFromHandler(REDIS_WARNING,"Received SIGTERM, scheduling shutdown...");
    
    // 打开关闭标识
    server.shutdown_asap = 1;
}

Cette fonction consiste à ouvrir la variable globale shutdown_asap du serveur. Ce champ est utilisé aux endroits suivants:

serverCron in redis.c
    
	/* We received a SIGTERM, shutting down here in a safe way, as it is
     * not ok doing so inside the signal handler. */
    // 服务器进程收到 SIGTERM 信号,关闭服务器
    if (server.shutdown_asap) {

        // 尝试关闭服务器
        if (prepareForShutdown(0) == REDIS_OK) exit(0);

        // 如果关闭失败,那么打印 LOG ,并移除关闭标识
        redisLog(REDIS_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information");
        server.shutdown_asap = 0;
    }

La première ligne du code ci-dessus, qui identifie l'emplacement de ce code, est la fonction serverCron dans redis.c. Cette fonction est la fonction d'exécution périodique du serveur redis, similaire à ScheduledThreadPoolExecutor en java. Après avoir détecté que server.shutdown_asap est activé, il arrêtera le serveur.

Ensuite, après avoir reçu le signal ci-dessus, l'action à effectuer est terminée, puis, quel est le signal, le signal est en fait un moyen de communication interprocessus sous Linux, tel que kill -9, il enverra une commande SIGKILL au pid correspondant ; Lorsque le premier plan redis est en cours d'exécution, vous appuyez sur ctrl + c, en fait, il envoie également un signal, le signal est SIGINT, la valeur est 2. Vous pouvez voir l'image ci-dessous:

Donc, quel est le signal que nous avons enregistré plus tôt, c'est: SIGTERM, 15. Autrement dit, lorsque nous appuyons sur kill -15, ce signal sera déclenché.

À propos de la différence entre kill 9 et kill 15, vous pouvez lire ce blog:

La différence entre Linux kill -9 et kill -15

Ouvrir syslog

// 设置 syslog
if (server.syslog_enabled) {
    openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT,
        server.syslog_facility);
}

Il s'agit du syslog qui envoie le journal au système Linux. Vous pouvez voir la description de la fonction openlog:

envoyer des messages à l'enregistreur système

Ce sentiment n'est pas très utilisé, vous pouvez vérifier:

Le journal syslog de Redis n'est pas imprimé lors du processus d'exploration

Initialiser certaines propriétés du redisServer actuel

	// 初始化并创建数据结构
    server.current_client = NULL;
	// 1
    server.clients = listCreate();
    server.clients_to_close = listCreate();
    server.slaves = listCreate();
    server.monitors = listCreate();
    server.slaveseldb = -1; /* Force to emit the first SELECT command. */
    server.unblocked_clients = listCreate();
    server.ready_keys = listCreate();
    server.clients_waiting_acks = listCreate();
    server.get_ack_from_slaves = 0;
    server.clients_paused = 0;

En fait, il n'y a rien à dire. Comme vous pouvez le voir, par exemple, ce serveur.clients, serveur est une variable globale qui conserve les différents états du serveur redis actuel. Les clients sont utilisés pour enregistrer le client actuel connecté au serveur redis. , Le type est une liste liée:

    // 一个链表,保存了所有客户端状态结构
    list *clients;              /* List of active clients */

Donc, voici en fait appeler listCreate(), créer une liste chaînée vide, puis attribuer des valeurs aux clients.

D'autres attributs sont similaires.

Créer un pool de chaînes constant pour réutilisation

Comme nous le savons tous, lorsque redis renvoie une réponse, il s'agit généralement d'une phrase: "+ OK" et similaires. Cette chaîne, si vous passez à une nouvelle chaque fois que vous répondez, elle est trop inutile, donc, simplement, redis cache ces chaînes couramment utilisées.

void createSharedObjects(void) {
    int j;

    // 常用回复
    shared.crlf = createObject(REDIS_STRING,sdsnew("\r\n"));
    shared.ok = createObject(REDIS_STRING,sdsnew("+OK\r\n"));
    shared.err = createObject(REDIS_STRING,sdsnew("-ERR\r\n"));
    ...
    // 常用错误回复
    shared.wrongtypeerr = createObject(REDIS_STRING,sdsnew(
        "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"));
    ...
}

C'est la même chose que java, qui met en cache les littéraux de chaîne, tous pour améliorer les performances; en Java, ne mettez-vous pas également en cache des entiers jusqu'à 128, non?

Ajustez le nombre maximum de fichiers que le processus peut ouvrir

Le serveur est généralement dans un véritable environnement en ligne. Si vous devez gérer une concurrence élevée, il peut y avoir des dizaines de millions de clients et un processus sur le serveur pour établir une connexion TCP. À ce stade, vous devez généralement ajuster le processus. Le nombre maximum de fichiers ouverts (les sockets sont également des fichiers).

Avant de lire le code source de redis, je sais que le moyen de modifier le nombre maximum de fichiers qu'un processus peut ouvrir est via ulimit. Plus précisément, vous pouvez voir les deux liens suivants:

Récapitulatif du nombre maximal de descripteurs de fichiers Linux

Optimisation Elasticsearch

Cependant, dans ce code source, une autre méthode a été trouvée:

  • API pour obtenir la valeur limite actuelle de la ressource spécifiée
#define RLIMIT_NOFILE	5		/* max number of open files */
    
struct rlimit {
	rlim_t	rlim_cur;
	rlim_t	rlim_max;
};
struct rlimit limit;

getrlimit(RLIMIT_NOFILE,&limit)

Le code ci-dessus obtient la taille limite de ressource de la valeur de NOFILE (le nombre maximal de fichiers dans le processus) dans le système actuel.

Grâce à man getrlimit (besoin d'installer d'abord, méthode d'installation :) yum install man-pages.noarch, vous pouvez voir:

  • setrlimit peut définir la limite de ressources appropriée

    limit.rlim_cur = f;
    limit.rlim_max = f;
    setrlimit(RLIMIT_NOFILE,&limit)
    

Créer des structures de données liées à la boucle d'événements

La structure du circulateur d'événement est la suivante:

/* 
 * State of an event based program 
 *
 * 事件处理器的状态
 */
typedef struct aeEventLoop {

    // 目前已注册的最大描述符
    int maxfd;   /* highest file descriptor currently registered */

    // 目前已追踪的最大描述符
    int setsize; /* max number of file descriptors tracked */

    // 用于生成时间事件 id
    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;

} aeEventLoop;

Le code pour initialiser la structure de données ci-dessus est à: aeCreateEventLoop dans redis.c

Dans la structure ci-dessus, les principaux sont:

  1. Dans les apidata, il est principalement utilisé pour stocker les données pertinentes de la bibliothèque multiplex. Chaque fois que la bibliothèque multiplex est appelée pour sélectionner, si elle constate qu'un événement io prêt se produit, il sera stocké dans l'attribut déclenché.

    Par exemple, select est une implémentation du multiplexage dans l'ancienne version du noyau linux sous linux. En redis, le code est le suivant:

    static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    	...
    	// 1
        retval = select(eventLoop->maxfd+1,
                    &state->_rfds,&state->_wfds,NULL,tvp);
        if (retval > 0) {
            for (j = 0; j <= eventLoop->maxfd; j++) {
    			...
                // 2
                eventLoop->fired[numevents].fd = j;
                eventLoop->fired[numevents].mask = mask;
                numevents++;
            }
        }
        return numevents;
    }
    

    Partie du code omise. Parmi eux, 1 place sélectionne, cette étape est similaire à l'opération de sélection de nio en java; 2 places remplissent les descripteurs de fichier renvoyés par select à l'attribut fired.

  2. De plus, nous avons mentionné que redis a certaines tâches en arrière-plan, telles que le nettoyage des clés expirées, cela ne se fait pas du jour au lendemain; chaque fois que la tâche en arrière-plan est exécutée périodiquement, elle nettoiera une partie, et la tâche en arrière-plan se trouve en fait dans la structure de données ci-dessus Événement de temps.

        // 时间事件
        aeTimeEvent *timeEventHead;
    

Allouer de l'espace mémoire pour 16 bases de données

server.db = zmalloc(sizeof(redisDb) * server.dbnum);

Ouvrez le port d'écoute et écoutez les demandes

    /* Open the TCP listening socket for the user commands. */
    // 打开 TCP 监听端口,用于等待客户端的命令请求
    listenToPort(server.port, server.ipfd, &server.ipfd_count)

C'est là que le port habituel 6379 est ouvert.

Initialiser la structure de données correspondant à 16 bases de données

    /* Create the Redis databases, and initialize other internal state. */
    // 创建并初始化数据库结构
    for (j = 0; j < server.dbnum; j++) {
        server.db[j].dict = dictCreate(&dbDictType, NULL);
        server.db[j].expires = dictCreate(&keyptrDictType, NULL);
        server.db[j].blocking_keys = dictCreate(&keylistDictType, NULL);
        server.db[j].ready_keys = dictCreate(&setDictType, NULL);
        server.db[j].watched_keys = dictCreate(&keylistDictType, NULL);
        server.db[j].eviction_pool = evictionPoolAlloc();
        server.db[j].id = j;
        server.db[j].avg_ttl = 0;
    }

La structure des données de db est la suivante:

typedef struct redisDb {

    // 数据库键空间,保存着数据库中的所有键值对
    dict *dict;                 /* The keyspace for this DB */

    // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
    dict *expires;              /* Timeout of keys with a timeout set */

    // 正处于阻塞状态的键
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP) */

    // 可以解除阻塞的键
    dict *ready_keys;           /* Blocked keys that received a PUSH */

    // 正在被 WATCH 命令监视的键
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */

    struct evictionPoolEntry *eviction_pool;    /* Eviction pool of keys */

    // 数据库号码
    int id;                     /* Database ID */

    // 数据库的键的平均 TTL ,统计信息
    long long avg_ttl;          /* Average TTL, just for stats */

} redisDb;

Ici, vous pouvez voir que la clé avec le délai d'expiration défini, en plus d'être stockée dans l'attribut dict, ajoutera également un enregistrement au dictionnaire expirant.

expire la clé du dictionnaire: pointeur sur la clé d'exécution; valeur: heure d'expiration.

Créer et initialiser des structures de données liées à pub / sub

    // 创建 PUBSUB 相关结构
    server.pubsub_channels = dictCreate(&keylistDictType, NULL);
    server.pubsub_patterns = listCreate();

Initialiser certaines propriétés statistiques

	// serverCron() 函数的运行次数计数器
    server.cronloops = 0;
    // 负责执行 BGSAVE 的子进程的 ID
    server.rdb_child_pid = -1;
    // 负责进行 AOF 重写的子进程 ID
    server.aof_child_pid = -1;
    aofRewriteBufferReset();
    // AOF 缓冲区
    server.aof_buf = sdsempty();
    // 最后一次完成 SAVE 的时间
    server.lastsave = time(NULL); /* At startup we consider the DB saved. */
    // 最后一次尝试执行 BGSAVE 的时间
    server.lastbgsave_try = 0;    /* At startup we never tried to BGSAVE. */
    server.rdb_save_time_last = -1;
    server.rdb_save_time_start = -1;
    server.dirty = 0;
    resetServerStats();
    /* A few stats we don't want to reset: server startup time, and peak mem. */
    //  服务器启动时间
    server.stat_starttime = time(NULL);
    //  已使用内存峰值
    server.stat_peak_memory = 0;
    server.resident_set_size = 0;
    // 最后一次执行 SAVE 的状态
    server.lastbgsave_status = REDIS_OK;
    server.aof_last_write_status = REDIS_OK;
    server.aof_last_write_errno = 0;
    server.repl_good_slaves_count = 0;
    updateCachedTime();

Définir le pointeur de fonction correspondant à l'événement temporel

    /* Create the serverCron() time event, that's our main way to process
     * background operations. */    
	// 为 serverCron() 创建时间事件
    if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
        redisPanic("Can't create the serverCron time event.");
        exit(1);
    }

Le serverCron est ici une fonction, et le serverCron sera exécuté chaque fois qu'un événement temporel est déclenché dans le cycle suivant.

Vous pouvez voir le commentaire en anglais ici, l'auteur a également mentionné que c'est le principal moyen de gérer les tâches d'arrière-plan.

Cela sera également analysé à l'avenir.

Définissez le gestionnaire de connexion correspondant à l'événement de connexion

aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler, NULL)

L'acceptTcpHandler est ici la fonction pour gérer la nouvelle connexion:

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
    char cip[REDIS_IP_STR_LEN];
    REDIS_NOTUSED(el);
    REDIS_NOTUSED(mask);
    REDIS_NOTUSED(privdata);

    while (max--) {
        // accept 客户端连接
        cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
        if (cfd == ANET_ERR) {
            if (errno != EWOULDBLOCK)
                redisLog(REDIS_WARNING,
                         "Accepting client connection: %s", server.neterr);
            return;
        }
        // 为客户端创建客户端状态(redisClient)
        acceptCommonHandler(cfd, 0);
    }
}

Créer un fichier OF

Si aof est ouvert, vous devez créer un fichier aof.

    if (server.aof_state == REDIS_AOF_ON) {
        server.aof_fd = open(server.aof_filename,
                             O_WRONLY | O_APPEND | O_CREAT, 0644);
    }

Les quelques tâches restantes qui ne sont pas impliquées pour le moment

    // 如果服务器以 cluster 模式打开,那么初始化 cluster
    if (server.cluster_enabled) clusterInit();

    // 初始化复制功能有关的脚本缓存
    replicationScriptCacheInit();

    // 初始化脚本系统
    scriptingInit();

    // 初始化慢查询功能
    slowlogInit();

    // 初始化 BIO 系统
    bioInit();

Nous ne pouvons pas expliquer ceux ci-dessus pour le moment, regardez-les d'abord.

À ce stade, l'initialisation du serveur redis est pratiquement terminée.

Résumé

Il y a beaucoup de contenu dans cette conférence, principalement dans le processus de démarrage de redis, il y a trop de choses à faire. J'espère que je l'ai dit clairement: parmi eux, ceux qui sont connectés au processeur ne sont que grossièrement expliqués, et je continuerai plus tard. Merci à tous.

Je suppose que tu aimes

Origine www.cnblogs.com/grey-wolf/p/12685918.html
conseillé
Classement