Redis(5.0.0)持久化AOF和 RDB 结合源码分析

主要是挖个坑。候补(代码还没看完。。)

https://github.com/antirez/redis/tree/5.0

一、Redis保存持久化文件

二、Redis启动加载持久化文件

src/server.c loadDataFromDisk函数 AOF 和 RDB 通过不同的方式加载

 1 /* Function called at startup to load RDB or AOF file in memory. */
 2 void loadDataFromDisk(void) {
 3     long long start = ustime();
 4     if (server.aof_state == AOF_ON) {
 5         if (loadAppendOnlyFile(server.aof_filename) == C_OK)
 6             serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
 7     } else {
 8         rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
 9         if (rdbLoad(server.rdb_filename,&rsi) == C_OK) {
10             serverLog(LL_NOTICE,"DB loaded from disk: %.3f seconds",
11                 (float)(ustime()-start)/1000000);
12 
13             /* Restore the replication ID / offset from the RDB file. */
14             if (server.masterhost &&
15                 rsi.repl_id_is_set &&
16                 rsi.repl_offset != -1 &&
17                 /* Note that older implementations may save a repl_stream_db
18                  * of -1 inside the RDB file in a wrong way, see more information
19                  * in function rdbPopulateSaveInfo. */
20                 rsi.repl_stream_db != -1)
21             {
22                 memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
23                 server.master_repl_offset = rsi.repl_offset;
24                 /* If we are a slave, create a cached master from this
25                  * information, in order to allow partial resynchronizations
26                  * with masters. */
27                 replicationCacheMasterUsingMyself();
28                 selectDb(server.cached_master,rsi.repl_stream_db);
29             }
30         } else if (errno != ENOENT) {
31             serverLog(LL_WARNING,"Fatal error loading the DB: %s. Exiting.",strerror(errno));
32             exit(1);
33         }
34     }
35 }
loadDataFromDisk

AOF路线

src/aof.c loadAppendOnlyFile 主要通过检查是否含有RDB前缀文件,如果有则加载,创建一个FakeClient一条条的执行AOF文件中的指令 。

特别地,事务会单独加载。

  1 int loadAppendOnlyFile(char *filename) {
  2     struct client *fakeClient;
  3     FILE *fp = fopen(filename,"r");
  4     struct redis_stat sb;
  5     int old_aof_state = server.aof_state;
  6     long loops = 0;
  7     off_t valid_up_to = 0; /* Offset of latest well-formed command loaded. */
  8     off_t valid_before_multi = 0; /* Offset before MULTI command loaded. */
  9 
 10     if (fp == NULL) {
 11         serverLog(LL_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno));
 12         exit(1);
 13     }
 14 
 15     /* Handle a zero-length AOF file as a special case. An empty AOF file
 16      * is a valid AOF because an empty server with AOF enabled will create
 17      * a zero length file at startup, that will remain like that if no write
 18      * operation is received. */
 19     if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
 20         server.aof_current_size = 0;
 21         fclose(fp);
 22         return C_ERR;
 23     }
 24 
 25     /* Temporarily disable AOF, to prevent EXEC from feeding a MULTI
 26      * to the same file we're about to read. */
 27     server.aof_state = AOF_OFF;
 28 
 29     fakeClient = createFakeClient();
 30     startLoading(fp);
 31 
 32     /* Check if this AOF file has an RDB preamble. In that case we need to
 33      * load the RDB file and later continue loading the AOF tail. */
 34     char sig[5]; /* "REDIS" */
 35     if (fread(sig,1,5,fp) != 5 || memcmp(sig,"REDIS",5) != 0) {
 36         /* No RDB preamble, seek back at 0 offset. */
 37         if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
 38     } else {
 39         /* RDB preamble. Pass loading the RDB functions. */
 40         rio rdb;
 41 
 42         serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
 43         if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
 44         rioInitWithFile(&rdb,fp);
 45         if (rdbLoadRio(&rdb,NULL,1) != C_OK) {
 46             serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
 47             goto readerr;
 48         } else {
 49             serverLog(LL_NOTICE,"Reading the remaining AOF tail...");
 50         }
 51     }
 52 
 53     /* Read the actual AOF file, in REPL format, command by command. */
 54     while(1) {
 55         int argc, j;
 56         unsigned long len;
 57         robj **argv;
 58         char buf[128];
 59         sds argsds;
 60         struct redisCommand *cmd;
 61 
 62         /* Serve the clients from time to time */
 63         if (!(loops++ % 1000)) {
 64             loadingProgress(ftello(fp));
 65             processEventsWhileBlocked();
 66         }
 67 
 68         if (fgets(buf,sizeof(buf),fp) == NULL) {
 69             if (feof(fp))
 70                 break;
 71             else
 72                 goto readerr;
 73         }
 74         if (buf[0] != '*') goto fmterr;
 75         if (buf[1] == '\0') goto readerr;
 76         argc = atoi(buf+1);
 77         if (argc < 1) goto fmterr;
 78 
 79         argv = zmalloc(sizeof(robj*)*argc);
 80         fakeClient->argc = argc;
 81         fakeClient->argv = argv;
 82 
 83         for (j = 0; j < argc; j++) {
 84             if (fgets(buf,sizeof(buf),fp) == NULL) {
 85                 fakeClient->argc = j; /* Free up to j-1. */
 86                 freeFakeClientArgv(fakeClient);
 87                 goto readerr;
 88             }
 89             if (buf[0] != '$') goto fmterr;
 90             len = strtol(buf+1,NULL,10);
 91             argsds = sdsnewlen(SDS_NOINIT,len);
 92             if (len && fread(argsds,len,1,fp) == 0) {
 93                 sdsfree(argsds);
 94                 fakeClient->argc = j; /* Free up to j-1. */
 95                 freeFakeClientArgv(fakeClient);
 96                 goto readerr;
 97             }
 98             argv[j] = createObject(OBJ_STRING,argsds);
 99             if (fread(buf,2,1,fp) == 0) {
100                 fakeClient->argc = j+1; /* Free up to j. */
101                 freeFakeClientArgv(fakeClient);
102                 goto readerr; /* discard CRLF */
103             }
104         }
105 
106         /* Command lookup */
107         cmd = lookupCommand(argv[0]->ptr);
108         if (!cmd) {
109             serverLog(LL_WARNING,
110                 "Unknown command '%s' reading the append only file",
111                 (char*)argv[0]->ptr);
112             exit(1);
113         }
114 
115         if (cmd == server.multiCommand) valid_before_multi = valid_up_to;
116 
117         /* Run the command in the context of a fake client */
118         fakeClient->cmd = cmd;
119         if (fakeClient->flags & CLIENT_MULTI &&
120             fakeClient->cmd->proc != execCommand)
121         {
122             queueMultiCommand(fakeClient);
123         } else {
124             cmd->proc(fakeClient);
125         }
126 
127         /* The fake client should not have a reply */
128         serverAssert(fakeClient->bufpos == 0 &&
129                      listLength(fakeClient->reply) == 0);
130 
131         /* The fake client should never get blocked */
132         serverAssert((fakeClient->flags & CLIENT_BLOCKED) == 0);
133 
134         /* Clean up. Command code may have changed argv/argc so we use the
135          * argv/argc of the client instead of the local variables. */
136         freeFakeClientArgv(fakeClient);
137         fakeClient->cmd = NULL;
138         if (server.aof_load_truncated) valid_up_to = ftello(fp);
139     }
140 
141     /* This point can only be reached when EOF is reached without errors.
142      * If the client is in the middle of a MULTI/EXEC, handle it as it was
143      * a short read, even if technically the protocol is correct: we want
144      * to remove the unprocessed tail and continue. */
145     if (fakeClient->flags & CLIENT_MULTI) {
146         serverLog(LL_WARNING,
147             "Revert incomplete MULTI/EXEC transaction in AOF file");
148         valid_up_to = valid_before_multi;
149         goto uxeof;
150     }
151 
152 loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
153     fclose(fp);
154     freeFakeClient(fakeClient);
155     server.aof_state = old_aof_state;
156     stopLoading();
157     aofUpdateCurrentSize();
158     server.aof_rewrite_base_size = server.aof_current_size;
159     return C_OK;
160 
161 readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
162     if (!feof(fp)) {
163         if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
164         serverLog(LL_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
165         exit(1);
166     }
167 
168 uxeof: /* Unexpected AOF end of file. */
169     if (server.aof_load_truncated) {
170         serverLog(LL_WARNING,"!!! Warning: short read while loading the AOF file !!!");
171         serverLog(LL_WARNING,"!!! Truncating the AOF at offset %llu !!!",
172             (unsigned long long) valid_up_to);
173         if (valid_up_to == -1 || truncate(filename,valid_up_to) == -1) {
174             if (valid_up_to == -1) {
175                 serverLog(LL_WARNING,"Last valid command offset is invalid");
176             } else {
177                 serverLog(LL_WARNING,"Error truncating the AOF file: %s",
178                     strerror(errno));
179             }
180         } else {
181             /* Make sure the AOF file descriptor points to the end of the
182              * file after the truncate call. */
183             if (server.aof_fd != -1 && lseek(server.aof_fd,0,SEEK_END) == -1) {
184                 serverLog(LL_WARNING,"Can't seek the end of the AOF file: %s",
185                     strerror(errno));
186             } else {
187                 serverLog(LL_WARNING,
188                     "AOF loaded anyway because aof-load-truncated is enabled");
189                 goto loaded_ok;
190             }
191         }
192     }
193     if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
194     serverLog(LL_WARNING,"Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.");
195     exit(1);
196 
197 fmterr: /* Format error. */
198     if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
199     serverLog(LL_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>");
200     exit(1);
201 }
loadAppendOnlyFile

RDB路线

RDB就像是一个redis快照

src/rdb.c  redis抽象出一个rio层,负责处理IO操作的,通过rio进行rdb文件恢复redis(快照保存的逆操作)。

 1 /* Like rdbLoadRio() but takes a filename instead of a rio stream. The
 2  * filename is open for reading and a rio stream object created in order
 3  * to do the actual loading. Moreover the ETA displayed in the INFO
 4  * output is initialized and finalized.
 5  *
 6  * If you pass an 'rsi' structure initialied with RDB_SAVE_OPTION_INIT, the
 7  * loading code will fiil the information fields in the structure. */
 8 int rdbLoad(char *filename, rdbSaveInfo *rsi) {
 9     FILE *fp;
10     rio rdb;
11     int retval;
12 
13     if ((fp = fopen(filename,"r")) == NULL) return C_ERR;
14     startLoading(fp);
15     rioInitWithFile(&rdb,fp);
16     retval = rdbLoadRio(&rdb,rsi,0);
17     fclose(fp);
18     stopLoading();
19     return retval;
20 }
rdbload
  1 /* Load an RDB file from the rio stream 'rdb'. On success C_OK is returned,
  2  * otherwise C_ERR is returned and 'errno' is set accordingly. */
  3 int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
  4     uint64_t dbid;
  5     int type, rdbver;
  6     redisDb *db = server.db+0;
  7     char buf[1024];
  8 
  9     rdb->update_cksum = rdbLoadProgressCallback;
 10     rdb->max_processing_chunk = server.loading_process_events_interval_bytes;
 11     if (rioRead(rdb,buf,9) == 0) goto eoferr;
 12     buf[9] = '\0';
 13     if (memcmp(buf,"REDIS",5) != 0) {
 14         serverLog(LL_WARNING,"Wrong signature trying to load DB from file");
 15         errno = EINVAL;
 16         return C_ERR;
 17     }
 18     rdbver = atoi(buf+5);
 19     if (rdbver < 1 || rdbver > RDB_VERSION) {
 20         serverLog(LL_WARNING,"Can't handle RDB format version %d",rdbver);
 21         errno = EINVAL;
 22         return C_ERR;
 23     }
 24 
 25     /* Key-specific attributes, set by opcodes before the key type. */
 26     long long lru_idle = -1, lfu_freq = -1, expiretime = -1, now = mstime();
 27     long long lru_clock = LRU_CLOCK();
 28     
 29     while(1) {
 30         robj *key, *val;
 31 
 32         /* Read type. */
 33         if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
 34 
 35         /* Handle special types. */
 36         if (type == RDB_OPCODE_EXPIRETIME) {
 37             /* EXPIRETIME: load an expire associated with the next key
 38              * to load. Note that after loading an expire we need to
 39              * load the actual type, and continue. */
 40             expiretime = rdbLoadTime(rdb);
 41             expiretime *= 1000;
 42             continue; /* Read next opcode. */
 43         } else if (type == RDB_OPCODE_EXPIRETIME_MS) {
 44             /* EXPIRETIME_MS: milliseconds precision expire times introduced
 45              * with RDB v3. Like EXPIRETIME but no with more precision. */
 46             expiretime = rdbLoadMillisecondTime(rdb,rdbver);
 47             continue; /* Read next opcode. */
 48         } else if (type == RDB_OPCODE_FREQ) {
 49             /* FREQ: LFU frequency. */
 50             uint8_t byte;
 51             if (rioRead(rdb,&byte,1) == 0) goto eoferr;
 52             lfu_freq = byte;
 53             continue; /* Read next opcode. */
 54         } else if (type == RDB_OPCODE_IDLE) {
 55             /* IDLE: LRU idle time. */
 56             uint64_t qword;
 57             if ((qword = rdbLoadLen(rdb,NULL)) == RDB_LENERR) goto eoferr;
 58             lru_idle = qword;
 59             continue; /* Read next opcode. */
 60         } else if (type == RDB_OPCODE_EOF) {
 61             /* EOF: End of file, exit the main loop. */
 62             break;
 63         } else if (type == RDB_OPCODE_SELECTDB) {
 64             /* SELECTDB: Select the specified database. */
 65             if ((dbid = rdbLoadLen(rdb,NULL)) == RDB_LENERR) goto eoferr;
 66             if (dbid >= (unsigned)server.dbnum) {
 67                 serverLog(LL_WARNING,
 68                     "FATAL: Data file was created with a Redis "
 69                     "server configured to handle more than %d "
 70                     "databases. Exiting\n", server.dbnum);
 71                 exit(1);
 72             }
 73             db = server.db+dbid;
 74             continue; /* Read next opcode. */
 75         } else if (type == RDB_OPCODE_RESIZEDB) {
 76             /* RESIZEDB: Hint about the size of the keys in the currently
 77              * selected data base, in order to avoid useless rehashing. */
 78             uint64_t db_size, expires_size;
 79             if ((db_size = rdbLoadLen(rdb,NULL)) == RDB_LENERR)
 80                 goto eoferr;
 81             if ((expires_size = rdbLoadLen(rdb,NULL)) == RDB_LENERR)
 82                 goto eoferr;
 83             dictExpand(db->dict,db_size);
 84             dictExpand(db->expires,expires_size);
 85             continue; /* Read next opcode. */
 86         } else if (type == RDB_OPCODE_AUX) {
 87             /* AUX: generic string-string fields. Use to add state to RDB
 88              * which is backward compatible. Implementations of RDB loading
 89              * are requierd to skip AUX fields they don't understand.
 90              *
 91              * An AUX field is composed of two strings: key and value. */
 92             robj *auxkey, *auxval;
 93             if ((auxkey = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
 94             if ((auxval = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
 95 
 96             if (((char*)auxkey->ptr)[0] == '%') {
 97                 /* All the fields with a name staring with '%' are considered
 98                  * information fields and are logged at startup with a log
 99                  * level of NOTICE. */
100                 serverLog(LL_NOTICE,"RDB '%s': %s",
101                     (char*)auxkey->ptr,
102                     (char*)auxval->ptr);
103             } else if (!strcasecmp(auxkey->ptr,"repl-stream-db")) {
104                 if (rsi) rsi->repl_stream_db = atoi(auxval->ptr);
105             } else if (!strcasecmp(auxkey->ptr,"repl-id")) {
106                 if (rsi && sdslen(auxval->ptr) == CONFIG_RUN_ID_SIZE) {
107                     memcpy(rsi->repl_id,auxval->ptr,CONFIG_RUN_ID_SIZE+1);
108                     rsi->repl_id_is_set = 1;
109                 }
110             } else if (!strcasecmp(auxkey->ptr,"repl-offset")) {
111                 if (rsi) rsi->repl_offset = strtoll(auxval->ptr,NULL,10);
112             } else if (!strcasecmp(auxkey->ptr,"lua")) {
113                 /* Load the script back in memory. */
114                 if (luaCreateFunction(NULL,server.lua,auxval) == NULL) {
115                     rdbExitReportCorruptRDB(
116                         "Can't load Lua script from RDB file! "
117                         "BODY: %s", auxval->ptr);
118                 }
119             } else {
120                 /* We ignore fields we don't understand, as by AUX field
121                  * contract. */
122                 serverLog(LL_DEBUG,"Unrecognized RDB AUX field: '%s'",
123                     (char*)auxkey->ptr);
124             }
125 
126             decrRefCount(auxkey);
127             decrRefCount(auxval);
128             continue; /* Read type again. */
129         } else if (type == RDB_OPCODE_MODULE_AUX) {
130             /* This is just for compatibility with the future: we have plans
131              * to add the ability for modules to store anything in the RDB
132              * file, like data that is not related to the Redis key space.
133              * Such data will potentially be stored both before and after the
134              * RDB keys-values section. For this reason since RDB version 9,
135              * we have the ability to read a MODULE_AUX opcode followed by an
136              * identifier of the module, and a serialized value in "MODULE V2"
137              * format. */
138             uint64_t moduleid = rdbLoadLen(rdb,NULL);
139             moduleType *mt = moduleTypeLookupModuleByID(moduleid);
140             char name[10];
141             moduleTypeNameByID(name,moduleid);
142 
143             if (!rdbCheckMode && mt == NULL) {
144                 /* Unknown module. */
145                 serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load: no matching module '%s'", name);
146                 exit(1);
147             } else if (!rdbCheckMode && mt != NULL) {
148                 /* This version of Redis actually does not know what to do
149                  * with modules AUX data... */
150                 serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load for the module '%s'. Probably you want to use a newer version of Redis which implements aux data callbacks", name);
151                 exit(1);
152             } else {
153                 /* RDB check mode. */
154                 robj *aux = rdbLoadCheckModuleValue(rdb,name);
155                 decrRefCount(aux);
156             }
157         }
158 
159         /* Read key */
160         if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
161         /* Read value */
162         if ((val = rdbLoadObject(type,rdb)) == NULL) goto eoferr;
163         /* Check if the key already expired. This function is used when loading
164          * an RDB file from disk, either at startup, or when an RDB was
165          * received from the master. In the latter case, the master is
166          * responsible for key expiry. If we would expire keys here, the
167          * snapshot taken by the master may not be reflected on the slave. */
168         if (server.masterhost == NULL && !loading_aof && expiretime != -1 && expiretime < now) {
169             decrRefCount(key);
170             decrRefCount(val);
171         } else {
172             /* Add the new object in the hash table */
173             dbAdd(db,key,val);
174 
175             /* Set the expire time if needed */
176             if (expiretime != -1) setExpire(NULL,db,key,expiretime);
177             
178             /* Set usage information (for eviction). */
179             objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock);
180 
181             /* Decrement the key refcount since dbAdd() will take its
182              * own reference. */
183             decrRefCount(key);
184         }
185 
186         /* Reset the state that is key-specified and is populated by
187          * opcodes before the key, so that we start from scratch again. */
188         expiretime = -1;
189         lfu_freq = -1;
190         lru_idle = -1;
191     }
192     /* Verify the checksum if RDB version is >= 5 */
193     if (rdbver >= 5) {
194         uint64_t cksum, expected = rdb->cksum;
195 
196         if (rioRead(rdb,&cksum,8) == 0) goto eoferr;
197         if (server.rdb_checksum) {
198             memrev64ifbe(&cksum);
199             if (cksum == 0) {
200                 serverLog(LL_WARNING,"RDB file was saved with checksum disabled: no check performed.");
201             } else if (cksum != expected) {
202                 serverLog(LL_WARNING,"Wrong RDB checksum. Aborting now.");
203                 rdbExitReportCorruptRDB("RDB CRC error");
204             }
205         }
206     }
207     return C_OK;
208 
209 eoferr: /* unexpected end of file is handled here with a fatal exit */
210     serverLog(LL_WARNING,"Short read or OOM loading DB. Unrecoverable error, aborting now.");
211     rdbExitReportCorruptRDB("Unexpected EOF reading RDB file");
212     return C_ERR; /* Just to avoid warning */
213 }
rdbLoadRio

 

posted @ 2019-01-30 22:20  zxMrlc  阅读(916)  评论(0编辑  收藏  举报