1. 程式人生 > >Redis原始碼分析(三十六)--- Redis中的11大優秀設計

Redis原始碼分析(三十六)--- Redis中的11大優秀設計

            堅持了一個月左右的時間,從最開始的對Redis的程式碼做分類,從struct結構體分析開始,到最後分析main主程式結束,中間,各大模組的程式碼逐個擊破,學習,總之,收穫了非常多,好久沒有這麼久的耐心把一個框架學透,學習一個框架,會用那只是小小的一部分,能把背後的原理吃透才是真功夫。在這個學習的最後階段,是時候要來點乾貨了,我把這1個多月來的一些總結的一些比較好的程式碼,和設計思想總結出來了,原本想湊成10大精彩設計的,可後來感覺每個點都挺精彩的,還是做成了11大優秀設計,包證讓你開啟研究,這裡無關語言,重在一種程式設計的思想和設計,希望大家能好好領會。(下面的排序無關緊要,我只是按照時間順序下來。後面的連結為我寫的相關文章,如果想具體瞭解,請點選請入)

        1.hyperloglog基量統計演算法的實現(http://blog.csdn.net/androidlushangderen/article/details/40683763)。說到這個,比較搞笑的一點是,我剛剛開始竟然以為是某種型別的日誌,和slowLog一樣,後來才明白,這是一種基量統計演算法,類似的演算法還有LLC,HLLC是他的升級版本。

         2.zmalloc記憶體分配的重新實現(http://blog.csdn.net/androidlushangderen/article/details/40659331)。Redis的作者在記憶體分配上顯然是早有準備,不會傻傻的還是呼叫系統的mallo和free方法,人家在這裡做了一個小小的封裝,便於管理者更方便的控制系統的記憶體,下面是一個小小的結構體的宣告,看到這個大家估計會明白。

/* 呼叫zmalloc申請size個大小的空間 */
void *zmalloc(size_t size) {
	//實際呼叫的還是malloc函式
    void *ptr = malloc(size+PREFIX_SIZE);
	
	//如果申請的結果為null,說明發生了oom,呼叫oom的處理方法
    if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
	//更新used_memory的大小
    update_zmalloc_stat_alloc(zmalloc_size(ptr));
    return ptr;
#else
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;
#endif
}
            3.multi事務操作(http://blog.csdn.net/androidlushangderen/article/details/40392209)。Redis中的事務操作給我一種煥然一新的感覺,作者在做此設計的時候,用到了key,和watch key的概念,一個key維護了一個所有watch他的所有Client列表,一個Client自身也擁有一個他所監視的所有key,如果一個key被touch了,所有同樣見識此key的客戶端的下一步操作統統失效,具體怎麼實現,請猛點後面的連結。

         5.zipmap壓縮結構的設計(http://blog.csdn.net/androidlushangderen/article/details/39994599)。Redis在記憶體處理上可謂是想盡了辦法,ziplist壓縮列表和zipmap壓縮圖就是非常典型的設計。與往常的結構體內直接放一個int64型別的整形變數,這樣就佔了8個位元組,但是一般情況下,我們儲存的數值都比較小,1個位元組差不多就夠了,所有就浪費了7個位元組,所以zip壓縮系列結構體,就可以動態分配位元組應對不同的情況,這個設計非常精彩,要確定這個key-value 的位置,通過前面保留的長度做偏移量的定位。

        6.sparkline微線圖的重新設計(http://blog.csdn.net/androidlushangderen/article/details/39964591)。Redis的sparkline的出現應該又是幫我掃盲了,人家可以用字串的形式輸出一張類似折線圖的表,利用了採集的很多歌Sample的樣本點,這個類多用於分析統計中出現,比如latency.c延時分析類中用到了。

       7.物件引用計數實現記憶體管理(http://blog.csdn.net/androidlushangderen/article/details/40716469)。我們知道管理物件的生命週期一般有2種方法,1個是根搜尋法(JVM中用的就是這個),另一個就是引用計數法,而Redis就給我們對此方法的實現,下面是物件增引用和減少引用的實現:

/* robj物件增減引用計數,遞增robj中的refcount的值 */
void incrRefCount(robj *o) {
	//遞增robj中的refcount的值
    o->refcount++;
}
/* 遞減robj中的引用計數,引用到0後,釋放物件 */
void decrRefCount(robj *o) {
	//如果之前的引用計數已經<=0了,說明出現異常情況了
    if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");
    if (o->refcount == 1) {
    	//如果之前的引用計數為1,再遞減一次,恰好內有被任何物件引用了,所以就可以釋放物件了
        switch(o->type) {
        case REDIS_STRING: freeStringObject(o); break;
        case REDIS_LIST: freeListObject(o); break;
        case REDIS_SET: freeSetObject(o); break;
        case REDIS_ZSET: freeZsetObject(o); break;
        case REDIS_HASH: freeHashObject(o); break;
        default: redisPanic("Unknown object type"); break;
        }
        zfree(o);
    } else {
    	//其他對於>1的引用計數的情況,只需要按常規的遞減引用計數即可
        o->refcount--;
    }
}
減少引用的方法實現是重點。

       8.fork子程序實現後臺程式(http://blog.csdn.net/androidlushangderen/article/details/40266579)。fork建立子執行緒實現後臺程式的操作,我還是第一次見能這麼用的,以前完全不知道fork能怎麼使用的,這次真的是漲知識了。裡面關鍵的一點是fork方法在子執行緒和父執行緒中的返回值不同做處理,父執行緒返回子執行緒的PID號,在子執行緒中返回的是0.

/* 後臺進行rbd儲存操作 */
int rdbSaveBackground(char *filename) {
    pid_t childpid;
    long long start;

    if (server.rdb_child_pid != -1) return REDIS_ERR;

    server.dirty_before_bgsave = server.dirty;
    server.lastbgsave_try = time(NULL);

    start = ustime();
    //利用fork()建立子程序用來實現rdb的儲存操作
    //此時有2個程序在執行這段函式的程式碼,在子進行程返回的pid為0,
    //所以會執行下面的程式碼,在父程序中返回的程式碼為孩子的pid,不為0,所以執行else分支的程式碼
    //在父程序中放返回-1代表建立子程序失敗
    if ((childpid = fork()) == 0) {
    	//在這個if判斷的程式碼就是在子執行緒中後執行的操作
        int retval;

        /* Child */
        closeListeningSockets(0);
        redisSetProcTitle("redis-rdb-bgsave");
        //這個就是剛剛說的rdbSave()操作
        retval = rdbSave(filename);
        if (retval == REDIS_OK) {
            size_t private_dirty = zmalloc_get_private_dirty();

            if (private_dirty) {
                redisLog(REDIS_NOTICE,
                    "RDB: %zu MB of memory used by copy-on-write",
                    private_dirty/(1024*1024));
            }
        }
        exitFromChild((retval == REDIS_OK) ? 0 : 1);
    } else {
    	//執行父執行緒的後續操作
        /* Parent */
        server.stat_fork_time = ustime()-start;
        server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
        latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
        if (childpid == -1) {
            server.lastbgsave_status = REDIS_ERR;
            redisLog(REDIS_WARNING,"Can't save in background: fork: %s",
                strerror(errno));
            return REDIS_ERR;
        }
        redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid);
        server.rdb_save_time_start = time(NULL);
        server.rdb_child_pid = childpid;
        updateDictResizePolicy();
        return REDIS_OK;
    }
    return REDIS_OK; /* unreached */
}
          9.long long 型別轉為String型別方法(http://blog.csdn.net/androidlushangderen/article/details/40649623)。以前做過很多字串轉數值和數值轉字串的演算法實現,也許你的功能是實現了,但是效率呢,但面對的是非常長的long long型別的數字時,效率可能會更低。Redis在這裡給我們提供了一個很好的思路,平時我們/10的計算,再%1o求餘數,人家直接來了個/100的,然後直接通過字串陣列和餘數值直接的對映,進行計算。演算法如下;
/* Convert a long long into a string. Returns the number of
 * characters needed to represent the number.
 * If the buffer is not big enough to store the string, 0 is returned.
 *
 * Based on the following article (that apparently does not provide a
 * novel approach but only publicizes an already used technique):
 *
 * https://www.facebook.com/notes/facebook-engineering/three-optimization-tips-for-c/10151361643253920
 *
 * Modified in order to handle signed integers since the original code was
 * designed for unsigned integers. */
/* long long型別轉化為string型別 */
int ll2string(char* dst, size_t dstlen, long long svalue) {
    static const char digits[201] =
        "0001020304050607080910111213141516171819"
        "2021222324252627282930313233343536373839"
        "4041424344454647484950515253545556575859"
        "6061626364656667686970717273747576777879"
        "8081828384858687888990919293949596979899";
    int negative;
    unsigned long long value;

    /* The main loop works with 64bit unsigned integers for simplicity, so
     * we convert the number here and remember if it is negative. */
    /* 在這裡做正負號的判斷處理 */
    if (svalue < 0) {
        if (svalue != LLONG_MIN) {
            value = -svalue;
        } else {
            value = ((unsigned long long) LLONG_MAX)+1;
        }
        negative = 1;
    } else {
        value = svalue;
        negative = 0;
    }

    /* Check length. */
    uint32_t const length = digits10(value)+negative;
    if (length >= dstlen) return 0;

    /* Null term. */
    uint32_t next = length;
    dst[next] = '\0';
    next--;
    while (value >= 100) {
    	//做值的換算
        int const i = (value % 100) * 2;
        value /= 100;
        //i所代表的餘數值用digits字元陣列中的對應數字代替了
        dst[next] = digits[i + 1];
        dst[next - 1] = digits[i];
        next -= 2;
    }

    /* Handle last 1-2 digits. */
    if (value < 10) {
        dst[next] = '0' + (uint32_t) value;
    } else {
        int i = (uint32_t) value * 2;
        dst[next] = digits[i + 1];
        dst[next - 1] = digits[i];
    }

    /* Add sign. */
    if (negative) dst[0] = '-';
    return length;
}
         10.正則表示式的實現演算法(http://blog.csdn.net/androidlushangderen/article/details/40649623)。正則表示式在我們平時用的可是非常多的,可有多少知道,正則表示式是如何實現通過簡單的模式程序匹配,背後的原理實現到底怎麼樣呢,為什麼?就可以代表任何一個字元接著往後匹配,*代表的是所有字元,要實現這樣一個演算法,也不是那麼容易的哦,Redis就實現了這麼一個演算法,算是撿到寶了吧。
/* Glob-style pattern matching. */
/*支援glob-style的萬用字元格式,如*表示任意一個或多個字元,?表示任意字元,[abc]表示方括號中任意一個字母。*/
int stringmatchlen(const char *pattern, int patternLen,
        const char *string, int stringLen, int nocase)
{
    while(patternLen) {
        switch(pattern[0]) {
        case '*':
            while (pattern[1] == '*') {
            	//如果出現的是**,說明一定匹配
                pattern++;
                patternLen--;
            }
            if (patternLen == 1)
                return 1; /* match */
            while(stringLen) {
                if (stringmatchlen(pattern+1, patternLen-1,
                            string, stringLen, nocase))
                    return 1; /* match */
                string++;
                stringLen--;
            }
            return 0; /* no match */
            break;
        case '?':
            if (stringLen == 0)
                return 0; /* no match */
            /* 因為?能代表任何字元,所以,匹配的字元再往後挪一個字元 */
            string++;
            stringLen--;
            break;
        case '[':
        {
            int not, match;

            pattern++;
            patternLen--;
            not = pattern[0] == '^';
            if (not) {
                pattern++;
                patternLen--;
            }
            match = 0;
            while(1) {
                if (pattern[0] == '\\') {
                	//如果遇到轉義符,則模式字元往後移一個位置
                    pattern++;
                    patternLen--;
                    if (pattern[0] == string[0])
                        match = 1;
                } else if (pattern[0] == ']') {
                	//直到遇到另外一個我中括號,則停止
                    break;
                } else if (patternLen == 0) {
                    pattern--;
                    patternLen++;
                    break;
                } else if (pattern[1] == '-' && patternLen >= 3) {
                    int start = pattern[0];
                    int end = pattern[2];
                    int c = string[0];
                    if (start > end) {
                        int t = start;
                        start = end;
                        end = t;
                    }
                    if (nocase) {
                        start = tolower(start);
                        end = tolower(end);
                        c = tolower(c);
                    }
                    pattern += 2;
                    patternLen -= 2;
                    if (c >= start && c <= end)
                        match = 1;
                } else {
                    if (!nocase) {
                        if (pattern[0] == string[0])
                            match = 1;
                    } else {
                        if (tolower((int)pattern[0]) == tolower((int)string[0]))
                            match = 1;
                    }
                }
                pattern++;
                patternLen--;
            }
            if (not)
                match = !match;
            if (!match)
                return 0; /* no match */
            string++;
            stringLen--;
            break;
        }
        case '\\':
            if (patternLen >= 2) {
                pattern++;
                patternLen--;
            }
            /* fall through */
        default:
        	/* 如果沒有正則表示式的關鍵字元,則直接比較 */
            if (!nocase) {
                if (pattern[0] != string[0])
                	//不相等,直接不匹配
                    return 0; /* no match */
            } else {
                if (tolower((int)pattern[0]) != tolower((int)string[0]))
                    return 0; /* no match */
            }
            string++;
            stringLen--;
            break;
        }
        pattern++;
        patternLen--;
        if (stringLen == 0) {
            while(*pattern == '*') {
                pattern++;
                patternLen--;
            }
            break;
        }
    }
    if (patternLen == 0 && stringLen == 0)
    	//如果匹配字元和模式字元匹配的長度都減少到0了,說明匹配成功了
        return 1;
    return 0;
}
         11.Redis的drand48()隨機演算法重實現(http://blog.csdn.net/androidlushangderen/article/details/40582189)。Redis隨機演算法的實現作為11大設計的最後一個,並不是說這個設計相比前面有多麼的爛,因為我覺得比較有特點,我就追加了一個上去。由於Redis的作者考慮到隨機演算法的在不同的作業系統可能會表現出不同的特性,所以不建議採用math.rand()方法,而是基於drand48()的演算法重新實現了一個。具體什麼叫drand48().請猛點連結處。

       好了,以上就是我印象中的Redis中比較優秀的設計。其實在Redis的很多還有很多優秀程式碼的痕跡,由於篇幅有限,等待著讀者自己去學習,發現。

相關推薦

Redis原始碼分析--- Redis11優秀設計

            堅持了一個月左右的時間,從最開始的對Redis的程式碼做分類,從struct結構體分析開始,到最後分析main主程式結束,中間,各大模組的程式碼逐個擊破,學習,總之,收穫了非常多,好久沒有這麼久的耐心把一個框架學透,學習一個框架,會用那只是小小的一部

Redis原始碼分析--- redis.c服務端的實現分析2

       在Redis服務端的程式碼量真的是比較大,如果一個一個API的學習怎麼實現,無疑是一種效率很低的做法,所以我今天對服務端的實現程式碼的學習,重在他的執行流程上,而對於他的模組設計在上一篇中我已經分析過了,不明白的同學可以接著看上篇。所以我學習分析redis服務

Redis原始碼分析--- redis.h服務端的實現分析1

       上次剛剛分析過了客戶端的結構體分析,思路比較簡答,清晰,最後學習的是服務端的實現,服務端在Redis可是重中之重,裡面基本上囊括了之前模組中涉及到的所有知識點,從redis的標頭檔案就可以看出了,redis.h程式碼量就已經破1000+行了,而且都還只是一些變

Redis原始碼分析--- slowLog和hyperloglog

            今天學習的是是2個log的檔案,2個檔案的實現功能都超出我原本理解的意思。開始時我以為就是記錄不同的型別的日誌,後來才慢慢的明白了額,slowLog記錄的是超時的查詢記錄,而hyperloglog其實跟日誌一點關係都沒有,好吧,我再一次傻眼了,他其實

二叉樹的經典面試題分析

        我們之前學習了二叉樹相關的概念,那麼我們今天來分析下二叉樹中的一些經典面試題。         1、單度結點的刪除  

笨辦法學Python

有時 就會 ast 區分 對待 似的 pre 並且 寫代碼 習題 36: 設計和調試 現在你已經學會了“if 語句”,我將給你一些使用“for 循環”和“while 循環”的規則,一面你日後碰到麻煩。我還會教你一些調試的小技巧,以便你能發現自己程序的問題。最後,你將

JMeter學習發送HTTPS請求轉載

無法 strong 控制 json localhost 閱讀 amp local cat  Jmeter一般來說是壓力測試的利器,最近想嘗試jmeter和BeanShell進行接口測試。由於在雲閱讀接口測試的過程中需要進行登錄操作,而登錄請求是HTTPS協議。這就需要對

geotrellis使用瓦片入庫更新圖層

err don 對象 即使 基礎上 變慢 get imp spatial 前言 Geotrellis 是針對大數據量柵格數據進行分布式空間計算的框架,這一點毋庸置疑,並且無論采取何種操作,其實都是先將大塊的數據切割成一定大小的小數據(專業術語為瓦片),這是分治的思想,也是分

Linux學習總結lamp之配置防盜鏈

lamp 防盜鏈 filematch 限制ip訪問 一配置防盜鏈 referer,中文釋義為來源,也就是在說網站從哪裏訪問過來,在訪問日誌裏,它指的是不同網站之間的跳轉鏈接信息。也就是跳轉源的網址。雖然直接在瀏覽器輸入某個站點,跟從其他網站打開該網站,我們感受到的效果沒有差別,但是服務器端會

C之內存布局

C語言 內存布局 .text .bss .data 我們在上節中說到了棧區、堆區以及靜態存儲區,那麽我們來看看在程序文件的一般布局。我們先來看看不同代碼在可執行程序中的對應關系,如下圖所示 我們看到程序中的經過初始化的全局變量和加 static 修飾的初始化過

Android項目實戰:給背景加上陰影效果

灰色 top set 設置 star 部分 ble utf 產品 原文:Android項目實戰(三十六):給背景加上陰影效果 圓角背景大家應該經常用: 一個drawable資源文件 裏面控制corner圓角 和solid填充色 <shape xmlns

繼承的意義

C++ 繼承 組合關系 代碼復用 今天我們來講下 C++ 三大特性之繼承。我們首先來思考下,類與類之間是否存在直接的關聯關系呢?我們還是以之前的講解的電腦為例,說下組合關系,組合便是整體與部分的關系,如下 我們以這個關系為例,用代碼來描述下#include <

樂優商城——訂單微服務

目錄 二、訂單結算頁 2.1 頁面跳轉 2.2 收貨人資訊 2.3 支付方式 2.4 商品列表 2.4.1 購物車資訊獲取 2.4.2 頁面渲染 2.5 總金額 2.6 提交訂單 2.6.1 頁面提交 2.6.2 精度損失問題 三、微信支付 3.1

ElasticSearch最佳入門實踐query string search 語法以及 _all metadata 原理揭祕

1、query string基礎語法 GET /test_index/test_type/_search?q=test_field:test GET /test_index/test_type/_search?q=+test_field:test

演算法題:對稱的二叉樹

題目描述 請實現一個函式,用來判斷一顆二叉樹是不是對稱的。注意,如果一個二叉樹同此二叉樹的映象是同樣的,定義其為對稱的。 分析 從上而下遞迴掃描,只要發現左右結點不相同的返回false。 程式碼 public class Symmetry { public static

osgEarth的Rex引擎原理分析TileNode的_renderModel作用

目標:(十八)中的問題59 osgEarth::Drivers::RexTerrainEngine::TileRenderModel主要維護兩個變數:取樣器osgEarth::Drivers::RexTerrainEngine::Sampler向量_sharedSamplers和渲染通道osgE

Effective_STL 學習筆記 了解 copy_if 的正確 實現

了解 算法 十六 ota eve nbsp remove rep ack STL 提供了 11 個名字帶有 “copy” 的算法     copy        copy_backward     replace_copy     reverse_copy     re

Java基礎系列:泛型需要注意的地方

一、不能用型別引數代替基本型別 因為型別擦除之後,原本的型別會被替代為Object型別的域,而Object不能儲存基本型別的值。就是說沒有Pair<double>,取而代之的是該基本型別的包裝器型別Pair<Double> 二、執行時型別查詢之適用於原始型

Coding and Paper Letter

資源整理。 1 Coding: 1.簡單的TensorFlow初學者教程。 TensorFlow Course 2.R語言包SpatialEpiApp,執行一個Shiny Web應用程式,可以顯示空間和時空疾病資料,估計疾病風險並檢測叢集。 SpatialEpiApp 3.R

PDF格式分析Article thread

用途 某些型別的文件可能會包含邏輯連線,而這個邏輯順序並不是物理順序。比如:新聞報道可以從新聞通訊的第一頁開始,然後轉到一個或多個非連續的內頁。 為了表示物理上不連續但邏輯相關的專案的序列,PDF文件定義了一個或多個Article(PDF 1.1)。 Art