redis原始碼分析與思考(十九)——AOF持久化
為了解決持久化檔案很龐大以及會阻塞伺服器的 情況,redis提出一種新的持久化方案:AOF持久化。AOF持久化是redis儲存資料的另外一種方式,全稱Append Only File,與RDB持久化不同的是,AOF持久化是隻儲存從客戶端鍵入的命令,而RDB持久化是單純的儲存資料。 AOF持久化的實現分為3個步驟:命令的追加、緩衝區寫入檔案、檔案同步。
AOF持久化的觸發
- 伺服器配置預設開啟AOF持久化:伺服器預設開啟AOF持久化的時候,會在伺服器重啟或者關閉時預設採用AOF持久化,而不是RDB持久化;
- 手動鍵入BGWRITEAOF:手動輸入BGWRITEAOF命令,啟動AOF持久化;
- 時間事件的定期操作:在redis中有三種策略來將AOF緩衝區裡面的內容寫入到AOF檔案中去。
AOF檔案協議
AOF持久化採用自己獨特的字串協議方式,在寫入AOF檔案時,程式會將AOF緩衝區裡面的命令字串轉化為該協議方式下的字串。該協議格式如下:
*<number>\r\n$<length>\r\n<commnd>\r\n$<length>\r\n<key>\r\n$<length>\r\n<value>......
首先開頭會用*與後面的number數字表示這是第幾條命令,然後接\r\n這一部分表示結束,再接$開頭length表明接下來的命令的位元組大小,再是命令字元,後面接若干個鍵值對。加入輸入如下指令:
SADD msg 1 name 2
則該字串轉化後如下:
*2\r\n$4\r\nSADD\r\n$3\r\nmsg\r\n$1\r\n$4\r\n$1\r\n2\r\n
之所以開頭是2,是因為在之前redis會預設執行選擇資料庫的命令。上面是沒有過期時間的鍵,如果某個鍵設定了過期時間,那麼redis會從中取出過期事件,建立PEXPIREAT命令來新增:
......
/*
* 如果過期值的格式為相對值,那麼將它轉換為絕對值
*/
if ( cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
cmd->proc == setexCommand || cmd->proc == psetexCommand)
{
when += mstime();
}
decrRefCount(seconds);
// 構建 PEXPIREAT 命令
argv[0] = createStringObject("PEXPIREAT",9);
argv[1] = key;
argv[2] = createStringObjectFromLongLong(when);
// 追加到 AOF 快取中
buf = catAppendOnlyGenericCommand(buf, 3, argv);
......
下面列出該協議的轉換程式碼:
sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) {
char buf[32];
int len, j;
robj *o;
// 重建命令的個數,格式為 *<count>\r\n
// 例如 *3\r\n
buf[0] = '*';
len = 1+ll2string(buf+1,sizeof(buf)-1,argc);
buf[len++] = '\r';
buf[len++] = '\n';
dst = sdscatlen(dst,buf,len);
// 重建命令和命令引數,格式為 $<length>\r\n<content>\r\n
// 例如 $3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n
for (j = 0; j < argc; j++) {
o = getDecodedObject(argv[j]);
// 組合 $<length>\r\n
buf[0] = '$';
len = 1+ll2string(buf+1,sizeof(buf)-1,sdslen(o->ptr));
buf[len++] = '\r';
buf[len++] = '\n';
dst = sdscatlen(dst,buf,len);
// 組合 <content>\r\n
dst = sdscatlen(dst,o->ptr,sdslen(o->ptr));
dst = sdscatlen(dst,"\r\n",2);
decrRefCount(o);
}
// 返回重建後的協議內容
return dst;
}
AOF緩衝區
AOF緩衝區是用來存貯需要寫入AOF檔案的命令的,因為服務端可能來不及的處理大量的請求命令,所以需要定義一個緩衝區來緩衝,在寫入一個命令時,redis會先將它寫入到伺服器的AOF的緩衝區去,在redis伺服器有如下的定義:
struct redisServer {
// AOF 緩衝區
sds aof_buf;
// AOF 狀態(開啟/關閉/可寫)
int aof_state;
// 所使用的 fsync 策略(每個寫入/每秒/從不)
int aof_fsync;
// AOF 重寫快取連結串列,連結著多個快取塊
list *aof_rewrite_buf_blocks;
}
aof_rewrite_buf_blocks是一個指標,它指向一個連結串列,該連結串列是伺服器的多個緩衝區,這個緩衝區連結串列的長度是由寫入的命令多少而定的。aof_buf裡面存貯著新建入的命令,而aof_state代表著是否開啟AOF持久化,而aof_fsync代表著同步的策略,在AOF持久化中有三種方式,同步策略放在後面講解。在redis中對AOF緩衝區的定義如下:
// 每個快取塊的大小
#define AOF_RW_BUF_BLOCK_SIZE (1024*1024*10) /* 10 MB per block */
typedef struct aofrwblock {
// 快取塊已使用位元組數和可用位元組數
unsigned long used, free;
// 快取塊
char buf[AOF_RW_BUF_BLOCK_SIZE];
} aofrwblock;
上面談到AOF緩衝區連結串列是一個可擴充的連結串列,當該連結串列中最後一個緩衝區記憶體已經不夠寫入新的命令字串時(前面的已經寫滿了),會新建一個緩衝區加入該連結串列,同時這也是將命令列追加到AOF緩衝區的實現,程式碼如下:
/*
* 將字元陣列 s 追加到 AOF 快取的末尾,
* 如果有需要的話,分配一個新的快取塊。
*/
void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
// 指向最後一個快取塊
listNode *ln = listLast(server.aof_rewrite_buf_blocks);
aofrwblock *block = ln ? ln->value : NULL;
while(len) {
//如果已經有至少一個快取塊,那麼嘗試將內容追加到這個快取塊裡面
if (block) {
unsigned long thislen = (block->free < len) ? block->free : len;
if (thislen) {
memcpy(block->buf+block->used, s, thislen);
block->used += thislen;
block->free -= thislen;
s += thislen;
len -= thislen;
}
}
// 如果 block != NULL ,那麼這裡是建立另一個快取塊買容納 block 裝不下的內容
// 如果 block == NULL ,那麼這裡是建立快取連結串列的第一個快取塊
if (len) { /* First block to allocate, or need another block. */
int numblocks;
// 分配快取塊
block = zmalloc(sizeof(*block));
block->free = AOF_RW_BUF_BLOCK_SIZE;
block->used = 0;
// 連結到連結串列末尾
listAddNodeTail(server.aof_rewrite_buf_blocks,block);
...
//列印。。。。。
...
}
}
}
AOF的寫入與同步
為什麼要進行同步呢?在現在的作業系統中,為了提高效率,將資料寫入檔案時,作業系統會把資料暫時的儲存在記憶體的緩衝區中(不是AOF緩衝區,AOF緩衝區借鑑了這個思想),當緩衝區滿了或者達到一定的時限後,才會將資料寫入到檔案中。而同步的意思就是清空緩衝區(flush),直接將資料寫入到檔案中,而不要等到緩衝區滿了或者到達一定的時限。這樣就保證了資料的正確性,因為在儲存資料時可能遇見斷電或者電腦宕機等情況,不及時flush掉資料的話,容易造成資料的遺漏。而redis中寫入同步策略有三種,也就是aof_fsync的值:
#define AOF_FSYNC_NO 0
#define AOF_FSYNC_ALWAYS 1
#define AOF_FSYNC_EVERYSEC 2
aof_fsync | 表達的內容 |
---|---|
always | 將AOF緩衝區裡面的所有內容寫入並同步到AOF檔案 |
everysec | 將AOF緩衝區裡面的所有內容寫入到AOF檔案,如果上次同步AOF檔案超過了1s鍾,那麼再次進行同步 |
no | 將AOF緩衝區裡面的所有內容寫入AOF檔案,同步的操作由作業系統決定 |
緩衝區的寫入與同步是由flushAppendOnlyFile函式執行的,這個函式會在每次事件執行前執行一次,如下程式碼:
void beforeSleep(struct aeEventLoop *eventLoop) {
REDIS_NOTUSED(eventLoop);
......
// 將 AOF 緩衝區的內容寫入到 AOF 檔案
flushAppendOnlyFile(0);
......
}
redis在預設情況下是everysec同步,beforeSleep函式是事件先行函式,在每個事件執行之前,都會是呼叫它執行一次。該原始碼註釋如下:
/*
* 將 AOF 快取寫入到檔案中。
* 因為程式需要在回覆客戶端之前對 AOF 執行寫操作。
* 而客戶端能執行寫操作的唯一機會就是在事件 loop 中,
* 因此,程式將所有 AOF 寫累積到快取中,
* 並在重新進入事件 loop 之前,將快取寫入到檔案中。
* 關於 force 引數:
* 當 fsync 策略為每秒鐘儲存一次時,如果後臺執行緒仍然有 fsync 在執行,
* 那麼我們可能會延遲執行沖洗(flush)操作,
* 因為 Linux 上的 write(2) 會被後臺的 fsync 阻塞。
* 當這種情況發生時,說明需要儘快沖洗 aof 快取,
* 程式會嘗試在 serverCron() 函式中對快取進行沖洗。
* 不過,如果 force 為 1 的話,那麼不管後臺是否正在 fsync ,
* 程式都直接進行寫入。
*/
#define AOF_WRITE_LOG_ERROR_RATE 30
void flushAppendOnlyFile(int force) {
ssize_t nwritten;
int sync_in_progress = 0;
// 緩衝區中沒有任何內容,直接返回
if (sdslen(server.aof_buf) == 0) return;
// 策略為每秒 FSYNC
if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
// 是否有 SYNC 正在後臺進行?
sync_in_progress = bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC) != 0;
// 每秒 fsync ,並且強制寫入為假
if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
// 當 fsync 策略為每秒鐘一次時, fsync 在後臺執行。
// 如果後臺仍在執行 FSYNC ,那麼我們可以延遲寫操作一兩秒
//(如果強制執行 write 的話,伺服器主執行緒將阻塞在 write 上面)
if (sync_in_progress) {
// 有 fsync 正在後臺進行 。。。
if (server.aof_flush_postponed_start == 0) {
/*
* 前面沒有推遲過 write 操作,這裡將推遲寫操作的時間記錄下來
* 然後就返回,不執行 write 或者 fsync
*/
server.aof_flush_postponed_start = server.unixtime;
return;
} else if (server.unixtime - server.aof_flush_postponed_start < 2) {
/*
* 如果之前已經因為 fsync 而推遲了 write 操作
* 但是推遲的時間不超過 2 秒,那麼直接返回
* 不執行 write 或者 fsync
*/
return;
}
/*
* 如果後臺還有 fsync 在執行,並且 write 已經推遲 >= 2 秒
* 那麼執行寫操作(write 將被阻塞)
*/
server.aof_delayed_fsync++;
redisLog(REDIS_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.");
}
}
/*
* 執行到這裡,程式會對 AOF 檔案進行寫入。
* 清零延遲 write 的時間記錄
*/
server.aof_flush_postponed_start = 0;
/*
* 執行單個 write 操作,如果寫入裝置是物理的話,那麼這個操作應該是原子的
* 當然,如果出現像電源中斷這樣的不可抗現象,那麼 AOF 檔案也是可能會出現問題的
* 這時就要用 redis-check-aof 程式來進行修復。
*/
//server.aof_fd指本機的套接字,對自身檔案讀寫
nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
......
//日誌列印
......
//移除錯誤的命令
......
//處理寫入AOF檔案時出現的錯誤
......
// 寫入成功,更新最後寫入狀態
......
// 更新寫入後的 AOF 檔案大小
server.aof_current_size += nwritten;
/*
* 如果 AOF 快取的大小足夠小的話,那麼重用這個快取,
* 否則的話,釋放 AOF 快取。
*/
if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) {
// 清空快取中的內容,等待重用
sdsclear(server.aof_buf);
} else {
// 釋放快取
sdsfree(server.aof_buf);
server.aof_buf = sdsempty();
}
//同步執行
/*
* 如果 no-appendfsync-on-rewrite 選項為開啟狀態,
* 並且有 BGSAVE 或者 BGREWRITEAOF 正在進行的話,
* 那麼不執行 fsync
*/
if (server.aof_no_fsync_on_rewrite &&
(server.aof_child_pid != -1 || server.rdb_child_pid != -1))
return;
// 總是執行 fsnyc
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
aof_fsync(server.aof_fd);
// 更新最後一次執行 fsnyc 的時間
server.aof_last_fsync = server.unixtime;
// 策略為每秒 fsnyc ,並且距離上次 fsync 已經超過 1 秒
} else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
server.unixtime > server.aof_last_fsync)) {
// 放到後臺執行
if (!sync_in_progress) aof_background_fsync(server.aof_fd);
// 更新最後一次執行 fsync 的時間
server.aof_last_fsync = server.unixtime;
}
}
AOF重寫
因為AOF持久化是採用存貯命令字串的形式來儲存資料的,所以當其大小達到一定的程度時,我們就可以對其進行AOF重寫,什麼是AOF重寫呢?就是原本兩條命令資料,其實可以用一條命令來實現,兩條命令重寫成一條命令的過程就叫做AOF重寫,如:
set a b
set a c
上述過程可以寫成下面一條命令:
set a c
AOF重寫過程其實是建立一個零時檔案,然後直接讀取當前的資料庫中的資料,將當前資料庫中的資料讀取出來然後以命令字串的形式寫入到零時檔案中,然後用零時檔案替換掉以前的AOF檔案完成重寫,也就是說AOF重寫沒有讀寫之前的AOF檔案。在其中遇到過期的鍵會自動過濾掉,實現程式碼流程如下:
int rewriteAppendOnlyFile(char *filename) {
......
/*
* 建立臨時檔案
* 注意這裡建立的檔名和 rewriteAppendOnlyFileBackground() 建立的檔名稍有不同
*/
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());
fp = fopen(tmpfile,"w");
if (!fp) {
redisLog(REDIS_WARNING, "Opening the temp file for AOF rewrite in rewriteAppendOnlyFile(): %s", strerror(errno));
return REDIS_ERR;
}
......
// 設定每寫入 REDIS_AOF_AUTOSYNC_BYTES 位元組
// 就執行一次 FSYNC
// 防止快取中積累太多命令內容,造成 I/O 阻塞時間過長
if (server.aof_rewrite_incremental_fsync)
rioSetAutoSync(&aof,REDIS_AOF_AUTOSYNC_BYTES);
// 遍歷所有資料庫
for (j = 0; j < server.dbnum; j++) {
......
//先寫入 SELECT 命令,確保之後的資料會被插入到正確的資料庫上
if (rioWrite(&aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr;
if (rioWriteBulkLongLong(&aof,j) == 0) goto werr;
//遍歷資料庫所有鍵,並通過命令將它們的當前狀態(值)記錄到新 AOF 檔案中
while((de = dictNext(di)) != NULL) {
......
// 取出過期時間
expiretime = getExpire(db,&key);
//如果鍵已經過期,那麼跳過它,不儲存
if (expiretime != -1 && expiretime < now) continue;
// 根據值的型別,選擇適當的命令來儲存值
........
// 儲存鍵的過期時間
......
......
}
// 沖洗並關閉新 AOF 檔案
if (fflush(fp) == EOF) goto werr;
if (aof_fsync(fileno(fp)) == -1) goto werr;
if (fclose(fp) == EOF) goto werr;
//原子地改名,用重寫後的新 AOF 檔案覆蓋舊 AOF 檔案
if (rename(tmpfile,filename) == -1) {
redisLog(REDIS_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno));
unlink(tmpfile);
return REDIS_ERR;
}
......
}
實際上是,AOF重寫都是放在子執行緒裡面進行的,也就是BGWRITEAOF命令,後臺AOF重寫,而檔案的新舊替換是發生在AOF檔案重寫後,在子執行緒裡將至替換掉。而開啟後臺AOF重寫後,服務端不會阻塞,新鍵入的命令會追加到AOF重寫緩衝區去,隨著重寫的進行寫入AOF檔案中。
AOF檔案的載入
AOF檔案的載入值得一提,當伺服器重啟時,其載入的步驟有如下:
- 建立一個偽客戶端,因為redis的命令寫入只能在客戶端的上下文中執行,所以需要一個假的客戶端來執行命令的寫入;
- 為客戶端從AOF檔案讀取命令並向伺服器傳送命令;
- 一直執行步驟2直到檔案中所有資料被讀完,釋放資源;
程式碼如下:
int loadAppendOnlyFile(char *filename) {
// 為客戶端
struct redisClient *fakeClient;
// 開啟 AOF 檔案
FILE *fp = fopen(filename,"r");
struct redis_stat sb;
int old_aof_state = server.aof_state;
long loops = 0;
// 檢查檔案的正確性
if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
server.aof_current_size = 0;
fclose(fp);
return REDIS_ERR;
}
// 檢查檔案是否正常開啟
if (fp == NULL) {
redisLog(REDIS_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno));
exit(1);
}
/*
* 暫時性地關閉 AOF ,防止在執行 MULTI 時,
* EXEC 命令被傳播到正在開啟的 AOF 檔案中。
*/
server.aof_state = REDIS_AOF_OFF;
fakeClient = createFakeClient();
// 設定伺服器的狀態為:正在載入
// startLoading 定義於 rdb.c
startLoading(fp);
while(1) {
int argc, j;
unsigned long len;
robj **argv;
char buf[128];
sds argsds;
struct redisCommand *cmd;
/*
* 間隔性地處理客戶端傳送來的請求
* 因為伺服器正處於載入狀態,所以能正常執行的只有 PUBSUB 等模組
*/
if (!(loops++ % 1000)) {
loadingProgress(ftello(fp));
processEventsWhileBlocked();
}
// 讀入檔案內容到快取
if (fgets(buf,sizeof(buf),fp) == NULL) {
if (feof(fp))
// 檔案已經讀完,跳出
break;
else
goto readerr;
}
// 確認協議格式,比如 *3\r\n
if (buf[0] != '*') goto fmterr;
// 取出命令引數,比如 *3\r\n 中的 3
argc = atoi(buf+1);
// 至少要有一個引數(被呼叫的命令)
if (argc < 1) goto fmterr;
// 從文字中建立字串物件:包括命令,以及命令引數
// 例如 $3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n
// 將建立三個包含以下內容的字串物件:
// SET 、 KEY 、 VALUE
argv = zmalloc(sizeof(robj*)*argc
相關推薦
redis原始碼分析與思考(十九)——AOF持久化
為了解決持久化檔案很龐大以及會阻塞伺服器的 情況,redis提出一種新的持久化方案:AOF持久化。AOF持久化是redis儲存資料的另外一種方式,全稱Append Only File,與RDB持久化不同的是,AOF持久化是隻儲存從客戶端鍵入
redis原始碼分析與思考(十八)——RDB持久化
redis是一個鍵值對的資料庫伺服器,伺服器中包含著若干個非空的資料庫,每個非空資料庫裡又包含著若干個鍵值對。因為redis是一個基於記憶體存貯的資料庫,他將自己所存的資料存於記憶體中,如果不將這些資料及時的儲存在硬碟中,當電腦關機或者進行
redis原始碼分析與思考(十七)——有序集合型別的命令實現(t_zset.c)
有序集合是集合的延伸,它儲存著集合元素的不可重複性,但不同的是,它是有序的,它利用每一個元素的分數來作為有序集合的排序依據,現在列出有序集合的命令:
有序集合命令
命令
對應操作
時
redis原始碼分析與思考(十六)——集合型別的命令實現(t_set.c)
集合型別是用來儲存多個字串的,與列表型別不一樣,集合中不允許有重複的元素,也不能以索引的方式來通過下標獲取值,集合中的元素還是無序的。在普通的集合上增刪查改外,集合型別還實現了多個集合的取交集、並集、差集,集合的命令如下表所示:
集合命
redis原始碼分析與思考(十五)——雜湊型別的命令實現(t_hash.c)
雜湊型別又叫做字典,在redis中,雜湊型別本身是一個鍵值對,而雜湊型別裡面也存貯著鍵值對,其對應關係是,每個雜湊型別的值對應著一個鍵值對或多對鍵值對,如圖所示:
雜湊型別命令
命令
對應操
redis原始碼分析與思考(十四)——列表型別的命令實現(t_list.c)
列表型別是用來存貯多個字串物件的結構。一個列表可以存貯232-1個元素,可以對列表兩端進行插入(push)、彈出(pop),還可以獲取指定範圍內的元素列表、獲取指定索引的元素等等,它可以靈活的充當棧和佇列的角色。下面列出列表的命令:
列
redis原始碼分析與思考(十七)——有序集合型別的命令實現(t_set.c)
有序集合是集合的延伸,它儲存著集合元素的不可重複性,但不同的是,它是有序的,它利用每一個元素的分數來作為有序集合的排序依據,現在列出有序集合的命令:
有序集合命令
命令
對應操作
時間複
redis原始碼分析與思考(十三)——字串型別的命令實現(t_string.c)
在對字串操作的命令中,主要有增加刪查該、批處理操作以及編碼的轉換命令,現在列出對字串物件操作的主要常用命令:
常用命令表
命令
對應操作
時間複雜度
redis原始碼分析與思考(三)——字典中鍵的兩種hash演算法
在Redis字典中,得到鍵的hash值顯得尤為重要,因為這個不僅關乎到是否字典能做到負載均衡,以及在效能上優勢是否突出,一個良好的hash演算法在此時就能發揮出巨大的作用。而一個良好的has
redis原始碼分析與思考(八)——物件
談及物件,我們不免會立即聯想到Java、C++等面向物件的語言,而在C中是沒有物件這一說法的,為了方便管理與程式碼整體的優化,redis基於前面幾篇部落格的資料結構自建了一套物件系統。這個系統包含著字串物件、列表物件、雜湊物件、集合物件以及有序集合物件。
redis原始碼分析與思考(一)——sds
在閱讀黃健巨集的書《Redis設計與實現》的時候,深刻的意識到僅僅看別人的作品是遠遠不夠,自己更應該去閱讀原始碼,形成自己的思考,這樣才算真正的學進去了。
現如今,Nosql的概念大行其道,redis作為其中的佼佼者被廣大的開發者愛好著,而且Redis的原始碼僅僅只
Redis原始碼剖析和註釋(十九)--- Redis 事件處理實現
Redis 事件處理實現
1. Redis事件介紹
Redis伺服器是一個事件驅動程式。下面先來簡單介紹什麼是事件驅動。
所謂事件驅動,就是當你輸入一條命令並且按下回車,然後訊息被組裝成Redis協議的格式傳送給Redis伺服器,這就會產生一個事件,Red
TiDB 原始碼閱讀系列文章(十九)tikv-client(下)
上篇文章 中,我們介紹了資料讀寫過程中 tikv-client 需要解決的幾個具體問題,本文將繼續介紹 tikv-client 裡的兩個主要的模組——負責處理分散式計算的 copIterator 和執行二階段提交的 twoPhaseCommitter。
copIterator
cop
element-ui Carousel 走馬燈原始碼分析整理筆記(十一)
Carousel 走馬燈原始碼分析整理筆記,這篇寫的不詳細,後面有空補充
main.vue
<template>
<!--走馬燈的最外層包裹div-->
<div class="el-carousel"
:class="{ 'el-carousel--card
Redis原始碼剖析和註釋(十四)---- Redis 資料庫及相關命令實現(db)
Redis 資料庫及相關命令實現
1. 資料庫管理命令
命令
描述
FLUSHDB
清空當前資料庫的所有key
FLUSHALL
清空整個Redis伺服器的所有key
DBSIZE
返回當前資料庫的
百度大腦人臉識別深度驗證與思考(十一)之動態實時
前言
我已經厭倦了靜態圖片的識別,那些技術對我已經沒有了挑戰性。今天我們就來看看動態實時的深度識別表現如何。
攝像頭
央視
我們直接採集央視rtmp推流地址的視訊,直接進行人臉識別和即時
百度大腦人臉識別深度驗證與思考(十二)之斷章
點開這篇博文,讓我帶你進入一個全新的世界,那是一片我們所有人從未涉足過的領域,充滿了玄妙、驚愕和震撼,感謝百度大腦這個人工智慧,讓我們可以有機會推開那扇小小的神奇之門。
前言
乍一看標題,覺得有些愕然,但這篇博文不以標題見長。用到斷章一詞,是因為其極為符合這
關於大型網站技術演進的思考(十九)--網站靜態化處理—web前端優化—上(11)
對於一個網路請求的處理,是由兩個不同型別的操作共同完成,這兩個操作是CPU的計算操作和IO操作,如果我們以處理效率角度來評判這兩個操作,CPU操作效率是光速的,而IO操作就不盡然了,計算機裡的IO操作就是對儲存資料介質的操作,計算機裡有如下幾個介質可以儲存資料,它們分別是:CPU的一級快取、二級快取、記憶
Redis原始碼剖析和註釋(十一)--- 雜湊鍵命令的實現(t_hash)
Redis 雜湊鍵命令實現(t_hash)
1. 雜湊命令介紹
Redis 所有雜湊命令如下表所示:Redis 雜湊命令詳解
序號
命令及描述
1
HDEL key field2 [field2]:刪除一個或多個雜湊表字段
rocketmq之原始碼分析broker之核心MessageStore訊息拉取(十九)
根據訊息的拉取程式碼,broker端的大體操作步驟如下,主要進行pullMessage 1,構建net