redis內部數據結構深入淺出

来源:http://www.cnblogs.com/chenpingzhao/archive/2017/06/10/6965164.html
-Advertisement-
Play Games

最大感受,無論從設計還是源碼,Redis都儘量做到簡單,其中運用到的原理也通俗易懂。特別是源碼,簡潔易讀,真正做到clean and clear, 這篇文章以unstable分支的源碼為基準,先從大體上整理Redis的對象類型以及底層編碼。 當我們在本文中提到Redis的“數據結構”,可能是在兩個不 ...


最大感受,無論從設計還是源碼,Redis都儘量做到簡單,其中運用到的原理也通俗易懂。特別是源碼,簡潔易讀,真正做到clean and clear, 這篇文章以unstable分支的源碼為基準,先從大體上整理Redis的對象類型以及底層編碼。 當我們在本文中提到Redis的“數據結構”,可能是在兩個不同的層面來討論它。

  • 第一個層面,是從使用者的角度,string,list,hash,set,sorted set
  • 第二個層面,是從內部實現的角度,屬於更底層的實現,   ht(dict),raw,embstr,intset,sds,ziplist,quicklist,skiplist

在討論任何一個系統的內部實現的時候,我們都要先明確它的設計原則,這樣我們才能更深刻地理解它為什麼會進行如此設計的真正意圖。

  • 存儲效率(memory efficiency)。Redis是專用於存儲數據的,它對於電腦資源的主要消耗就在於記憶體,因此節省記憶體是它非常非常重要的一個方面。這意味著Redis一定是非常精細地考慮了壓縮數據、減少記憶體碎片等問題。

  • 快速響應時間(fast response time)。與快速響應時間相對的,是高吞吐量(high throughput)。Redis是用於提供線上訪問的,對於單個請求的響應時間要求很高,因此,快速響應時間是比高吞吐量更重要的目標。有時候,這兩個目標是矛盾的。

  • 單線程(single-threaded)。Redis的性能瓶頸不在於CPU資源,而在於記憶體訪問和網路IO。而採用單線程的設計帶來的好處是,極大簡化了數據結構和演算法的實現。相反,Redis通過非同步IO和pipelining等機制來實現高速的併發訪問。顯然,單線程的設計,對於單個請求的快速響應時間也提出了更高的要求。

比如:Redis一個重要的基礎數據結構:dict。

  • dict是一個用於維護key和value映射關係的數據結構,與很多語言中的Map或dictionary類似。Redis的一個database中所有key到value的映射,就是使用一個dict來維護的。不過,這隻是它在Redis中的一個用途而已,它在Redis中被使用的地方還有很多。比如,一個Redis hash結構,當它的field較多時,便會採用dict來存儲。再比如,Redis配合使用dict和skiplist來共同維護一個sorted set

  • dict本質上是為瞭解決演算法中的查找問題(Searching),一般查找問題的解法分為兩個大類:一個是基於各種平衡樹,一個是基於哈希表。我們平常使用的各種Map或dictionary,大都是基於哈希表實現的。在不要求數據有序存儲,且能保持較低的哈希值衝突概率的前提下,基於哈希表的查找性能能做到非常高效,接近O(1),而且實現簡單。

  • dict也是一個基於哈希表的演算法。和傳統的哈希演算法類似,它採用某個哈希函數從key計算得到在哈希表中的位置,採用拉鏈法解決衝突,併在裝載因數(load factor)超過預定值時自動擴展記憶體,引發重哈希(rehashing)。Redis的dict實現最顯著的一個特點,就在於它的重哈希。它採用了一種稱為增量式重哈希(incremental rehashing)的方法,在需要擴展記憶體時避免一次性對所有key進行重哈希,而是將重哈希操作分散到對於dict的各個增刪改查的操作中去。這種方法能做到每次只對一小部分key進行重哈希,而每次重哈希之間不影響dict的操作。dict之所以這樣設計,是為了避免重哈希期間單個請求的響應時間劇烈增加,這與前面提到的“快速響應時間”的設計原則是相符的。

一、對象類型

redis 是 key-value 存儲系統,其中 key 類型一般為字元串,而 value 類型則為 redis 對象(redis object),可以綁定各種類型的數據,譬如 string、list 和set,redis.h 中定義了 struct redisObject,它是一個簡單優秀的數據結構

#define LRU_BITS 24
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */

typedef struct redisObject {
    //對象的數據類型,占4bits,共5種類型
    unsigned type:4;        
    //對象的編碼類型,占4bits,共10種類型
    unsigned encoding:4;

    //least recently used
    //實用LRU演算法計算相對server.lruclock的LRU時間
    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */

    //引用計數
    int refcount;

    //指向底層數據實現的指針
    void *ptr;
} robj;

//type的占5種類型:
/* Object types */
#define OBJ_STRING 0    //字元串對象
#define OBJ_LIST 1      //列表對象
#define OBJ_SET 2       //集合對象
#define OBJ_ZSET 3      //有序集合對象
#define OBJ_HASH 4      //哈希對象

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
// encoding 的10種類型
#define OBJ_ENCODING_RAW 0     /* Raw representation */     //原始表示方式,字元串對象是簡單動態字元串
#define OBJ_ENCODING_INT 1     /* Encoded as integer */         //long類型的整數
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */      //字典
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */          //不在使用
#define OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */  //雙端鏈表,不在使用
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */         //壓縮列表
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */          //整數集合
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */      //跳躍表和字典
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */   //embstr編碼的簡單動態字元串
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */   //由壓縮列表組成的雙向列表-->快速列表

其中,void *ptr 已經給了我們無限的遐想空間了(把最後一個指針留給了真正的數據)

每種類型的對象至少都有兩種或以上的encoding方式,不同編碼可以在不同的使用場景上優化對象的使用場景,用TYPE命令可查看某個鍵值對的類型

二、對象編碼

 不同類型和編碼的對象

REDIS_STRING	REDIS_ENCODING_INT	使用整數值實現的字元串對象。
REDIS_STRING	REDIS_ENCODING_EMBSTR	使用 embstr 編碼的簡單動態字元串實現的字元串對象。
REDIS_STRING	REDIS_ENCODING_RAW	使用簡單動態字元串實現的字元串對象。
REDIS_LIST	REDIS_ENCODING_ZIPLIST	使用壓縮列表實現的列表對象。
REDIS_LIST	REDIS_ENCODING_LINKEDLIST	使用雙端鏈表實現的列表對象。
REDIS_HASH	REDIS_ENCODING_ZIPLIST	使用壓縮列表實現的哈希對象。
REDIS_HASH	REDIS_ENCODING_HT	使用字典實現的哈希對象。
REDIS_SET	REDIS_ENCODING_INTSET	使用整數集合實現的集合對象。
REDIS_SET	REDIS_ENCODING_HT	使用字典實現的集合對象。
REDIS_ZSET	REDIS_ENCODING_ZIPLIST	使用壓縮列表實現的有序集合對象。
REDIS_ZSET	REDIS_ENCODING_SKIPLIST	使用跳躍表和字典實現的有序集合對象。

OBJECT ENCODING 對不同編碼的輸出

整數	REDIS_ENCODING_INT	"int"
embstr 編碼的簡單動態字元串(SDS)	REDIS_ENCODING_EMBSTR	"embstr"
簡單動態字元串	REDIS_ENCODING_RAW	"raw"
字典	REDIS_ENCODING_HT	"hashtable"
雙端鏈表	REDIS_ENCODING_LINKEDLIST	"linkedlist"
壓縮列表	REDIS_ENCODING_ZIPLIST	"ziplist"
整數集合	REDIS_ENCODING_INTSET	"intset"
跳躍表和字典	REDIS_ENCODING_SKIPLIST	"skiplist" 

本質上,Redis就是基於這些數據結構而構造出一個對象存儲系統。

關於redisObject 

  • ptr指針,指向對象的底層實現數據結構

  • encoding屬性記錄對象所使用的編碼

  • 淘汰時鐘,Redis 對數據集占用記憶體的大小有「實時」的計算,當超出限額時,會淘汰超時的數據

  • 引用計數,一個 Redis 對象可能被多個指針引用。當需要增加或者減少引用的時候,必須調用相應的函數,程式員必須遵守這一准則

// 增加 Redis 對象引用
void incrRefCount(robj *o) {
    o->refcount++;
}

// 減少 Redis 對象引用。特別的,引用為零的時候會銷毀對象
void decrRefCount(robj *o) {
    if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");
    // 如果取消的是最後一個引用,則釋放資源
    if (o->refcount == 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 {
        o->refcount--;
    }
}

得益於 Redis 是單進程單線程工作的,所以增加/減少引用的操作不必保證原子性,這在 memcache 中是做不到的(memcached 是多線程的工作模式,需要做到互斥)

1、Keys 

redis是一個key-value db,首先key也是字元串類型,但是key中不能包括邊界字元,由於key不是binary safe的字元串,所以像”my key”和”mykey\n”這樣包含空格和換行的key是不允許的 ,順便說一下在redis內部並不限制使用binary字元,這是redis協議限制的,”\r\n”在協議格式中會作為特殊字元。   redis 1.2以後的協議中部分命令已經開始使用新的協議格式了(比如MSET),總之目前還是把包含邊界字元當成非法的key,另外關於key的一個格式約定介紹下,object-type:id:field。比如user:1000:password,blog:xxidxx:title  

2、string

string是redis最基本的類型,而且string類型是二進位安全的。意思是redis的string可以包含任何數據,比如jpg圖片或者序列化的對象。從內部實現來看其實string可以看作byte數組,最大上限是1G位元組。 

struct sdshdr {
   long len;
   long free;
   char buf[];
};

buf是個char數組用於存貯實際的字元串內容。其實char和c#中的byte是等價的,都是一個位元組 ,len是buf數組的長度,free是數組中剩餘可用位元組數。 由此可以理解為什麼string類型是二進位安全的了。因為它本質上就是個byte數組。當然可以包含任何數據了。 另外string類型可以被部分命令按int處理,比如incr等命令,redis的其他類型像list,set,sorted set ,hash它們包含的元素與都只能是string類型。 

編碼

字元串對象的編碼可以是 INT、RAW 或 EMBSTR。如果保存的是整數值並且可以用long表示,那麼編碼會設置為INT。當字元串值得長度大於44位元組使用RAW,小於等於44位元組使用EMBSTR。

Redis在3.0引入EMBSTR編碼,這是一種專門用於保存短字元串的一種優化編碼方式,這種編碼和RAW編碼都是用sdshdr簡單動態字元串結構來表示。RAW編碼會調用兩次記憶體分配函數來分別創建redisObject和sdshdr結構,而EMBSTR只調用一次記憶體分配函數來分配一塊連續的空間保存數據,比起RAW編碼的字元串更能節省記憶體,以及能提升獲取數據的速度。

不過要註意!EMBSTR是不可修改的,當對EMBSTR編碼的字元串執行任何修改命令,總會先將其轉換成RAW編碼再進行修改;而INT編碼在條件滿足的情況下也會被轉換成RAW編碼。

兩種字元串對象編碼方式的區別

/* Create a string object with EMBSTR encoding if it is smaller than
 * REIDS_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
 * used.
 *
 * The current limit of 39 is chosen so that the biggest string object
 * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */

//sdshdr8的大小為3個位元組,加上1個結束符共4個位元組
//redisObject的大小為16個位元組
//redis使用jemalloc記憶體分配器,且jemalloc會分配8,16,32,64等位元組的記憶體
//一個embstr固定的大小為16+3+1 = 20個位元組,因此一個最大的embstr字元串為64-20 = 44位元組
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44

// 創建字元串對象,根據長度使用不同的編碼類型
// createRawStringObject和createEmbeddedStringObject的區別是:
// createRawStringObject是當字元串長度大於44位元組時,robj結構和sdshdr結構在記憶體上是分開的
// createEmbeddedStringObject是當字元串長度小於等於44位元組時,robj結構和sdshdr結構在記憶體上是連續的
robj *createStringObject(const char *ptr, size_t len) {
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

字元串對象編碼的優化

/* Try to encode a string object in order to save space */
//嘗試優化字元串對象的編碼方式以節約空間
robj *tryObjectEncoding(robj *o) {
    long value;
    sds s = o->ptr;
    size_t len;

    /* Make sure this is a string object, the only type we encode
     * in this function. Other types use encoded memory efficient
     * representations but are handled by the commands implementing
     * the type. */
    serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);

    /* We try some specialized encoding only for objects that are
     * RAW or EMBSTR encoded, in other words objects that are still
     * in represented by an actually array of chars. */
    //如果字元串對象的編碼類型為RAW或EMBSTR時,才對其重新編碼
    if (!sdsEncodedObject(o)) return o;

    /* It's not safe to encode shared objects: shared objects can be shared
     * everywhere in the "object space" of Redis and may end in places where
     * they are not handled. We handle them only as values in the keyspace. */
    //如果refcount大於1,則說明對象的ptr指向的值是共用的,不對共用對象進行編碼
     if (o->refcount > 1) return o;

    /* Check if we can represent this string as a long integer.
     * Note that we are sure that a string larger than 20 chars is not
     * representable as a 32 nor 64 bit integer. */
    len = sdslen(s);            //獲得字元串s的長度

    //如果len小於等於20,表示符合long long可以表示的範圍,且可以轉換為long類型的字元串進行編碼
    if (len <= 20 && string2l(s,len,&value)) {
        /* This object is encodable as a long. Try to use a shared object.
         * Note that we avoid using shared integers when maxmemory is used
         * because every object needs to have a private LRU field for the LRU
         * algorithm to work well. */
        if ((server.maxmemory == 0 ||
             (server.maxmemory_policy != MAXMEMORY_VOLATILE_LRU &&
              server.maxmemory_policy != MAXMEMORY_ALLKEYS_LRU)) &&
            value >= 0 &&
            value < OBJ_SHARED_INTEGERS)    //如果value處於共用整數的範圍內
        {
            decrRefCount(o);                //原對象的引用計數減1,釋放對象
            incrRefCount(shared.integers[value]); //增加共用對象的引用計數
            return shared.integers[value];      //返回一個編碼為整數的字元串對象
        } else {        //如果不處於共用整數的範圍
            if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);   //釋放編碼為OBJ_ENCODING_RAW的對象
            o->encoding = OBJ_ENCODING_INT;     //轉換為OBJ_ENCODING_INT編碼
            o->ptr = (void*) value;             //指針ptr指向value對象
            return o;
        }
    }

    /* If the string is small and is still RAW encoded,
     * try the EMBSTR encoding which is more efficient.
     * In this representation the object and the SDS string are allocated
     * in the same chunk of memory to save space and cache misses. */
    //如果len小於44,44是最大的編碼為EMBSTR類型的字元串對象長度
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
        robj *emb;

        if (o->encoding == OBJ_ENCODING_EMBSTR) return o;   //將RAW對象轉換為OBJ_ENCODING_EMBSTR編碼類型
        emb = createEmbeddedStringObject(s,sdslen(s)); //創建一個編碼類型為OBJ_ENCODING_EMBSTR的字元串對象
        decrRefCount(o);    //釋放之前的對象
        return emb;
    }

    /* We can't encode the object...
     *
     * Do the last try, and at least optimize the SDS string inside
     * the string object to require little space, in case there
     * is more than 10% of free space at the end of the SDS string.
     *
     * We do that only for relatively large strings as this branch
     * is only entered if the length of the string is greater than
     * OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
    //無法進行編碼,但是如果s的未使用的空間大於使用空間的10分之1
    if (o->encoding == OBJ_ENCODING_RAW &&
        sdsavail(s) > len/10)
    {
        o->ptr = sdsRemoveFreeSpace(o->ptr);    //釋放所有的未使用空間
    }

    /* Return the original object. */
    return o;
}

3、list

list類型其實就是一個每個子元素都是string類型的雙向鏈表。所以[lr]push和[lr]pop命令的演算法時間複雜度都是O(n),另外list會記錄鏈表的長度。所以llen操作也是O(n).鏈表的最大長度是(2的32次方-1)。 

我們可以通過push,pop操作從鏈表的頭部或者尾部添加刪除元素。這使得list既可以用作棧,也可以用作隊列。 有意思的是list的pop操作還有阻塞版本的。當我們[lr]pop一個list對象,如果list是空,或者不存在,會立即返回nil。但是阻塞版本的b[lr]pop可以則可以阻塞, 當然可以加超時時間,超時後也會返回nil。為什麼要阻塞版本的pop呢,主要是為了避免輪詢。 如果我們用list來實現一個工作隊列。執行任務的thread可以調用阻塞版本的pop去 ,獲取任務這樣就可以避免輪詢去檢查是否有任務存在。當任務來時候工作線程可以立即返回,也可以避免輪詢帶來的延遲。

編碼

Redis3.0之前的列表對象的編碼可以是ziplist或者linkedlist。當列表對象保存的字元串元素的長度都小於64位元組並且保存的元素數量小於512個,使用ziplist編碼,可以通過修改配置list-max-ziplist-value和list-max-ziplist-entries來修改這兩個條件的上限值、兩個條件任意一個不滿足時,ziplist會變為linkedlist。

從3.2開始Redis只使用quicklist作為列表的編碼,quicklist是ziplist和雙向鏈表的結合體,quicklist的每個節點都是一個ziplist。可以通過修改list-max-ziplist-size來設置一個quicklist節點上的ziplist的長度,取正值表示通過元素數量來限定ziplist的長度;負數表示按照占用位元組數來限定,並且Redis規定只能取-1到-5這五個負值

-5: 每個quicklist節點上的ziplist大小不能超過64 Kb。(註:1kb => 1024 bytes)
-4: 每個quicklist節點上的ziplist大小不能超過32 Kb。
-3: 每個quicklist節點上的ziplist大小不能超過16 Kb。
-2: 每個quicklist節點上的ziplist大小不能超過8 Kb。(預設值)
-1: 每個quicklist節點上的ziplist大小不能超過4 Kb。

另外配置參數list-compress-depth表示一個quicklist兩端不被壓縮的節點個數

0: 表示都不壓縮。預設值。
1: 表示quicklist兩端各有1個節點不壓縮,中間的節點壓縮。
2: 表示quicklist兩端各有2個節點不壓縮,中間的節點壓縮。
3: 表示quicklist兩端各有3個節點不壓縮,中間的節點壓縮。
依此類推…

這裡採用的是一種叫LZF的無損壓縮演算法

4、hash

哈希對象的編碼可以是ziplist或者hashtable。使用ziplist 編碼時,保存同一鍵值對的兩個節點總是緊挨在一起,鍵節點在前,值節點在後,同時滿足以下兩個條件將使用ziplist編碼:

  • 所有鍵和值的字元串長度小於64位元組

  • 鍵值對的數量小於512個

不能滿足這兩個條件的都需要使用hashtable編碼。以上兩個上限值可以通過hash-max-ziplist-value和hash-max-ziplist-entries來修改

hash是一個string類型的field和value的映射表,它的添加,刪除操作都是O(1),hash特別適合用於存儲對象。 相較於將對象的每個欄位存成單個string類型,將一個對象存儲在hash類型中會占用更少的記憶體,並且可以更方便的存取整個對象。

省記憶體的原因是新建一個hash對象時開始是用zipmap(又稱為small hash)來存儲的。 這個zipmap其實並不是hash table,但是zipmap相比正常的hash實現可以節省不少hash本身需要的一些元數據存儲開銷。 儘管zipmap的添加,刪除,查找都是O(n),但是由於一般對象的field數量都不太多。 所以使用zipmap也是很快的,也就是說添加刪除平均還是O(1)。 如果field或者value的大小超出一定限制後,redis會在內部自動將zipmap替換成正常的hash實現,這個限制可以在配置文件中指定 

hash-max-zipmap-entries 64 #配置欄位最多64個 
hash-max-zipmap-value 512 #配置value最大為512位元組

5、set

集合對象的編碼可以是intset或者hashtable。當滿足以下兩個條件時使用intset編碼:

  • 所有元素都是整數值

  • 元素數量不超過512個

可以修改set-max-intset-entries設置元素數量的上限。使用hashtable編碼時,字典的每一個鍵都是字元串對象,每個字元串對象包含一個集合元素,字典的值全部設置為null。

redis的set是string類型的無序集合。set元素最大可以包含(2的32次方-1)個元素。 set的是通過hash table實現的,所以添加,刪除,查找的複雜度都是O(1)。hash table會隨著添加或者刪除自動的調整大小。 需要註意的是調整hash table大小時候需要同步(獲取寫鎖)會阻塞其他讀寫操作。 可能不久後就會改用跳錶(skip list)來實現跳錶已經在sorted set中使用了 關於set集合類型除了基本的添加刪除操作,其他有用的操作還包含集合的取並集(union),交集(intersection), 差集(difference)。

6、sorted set

有序集合對象的編碼可以是ziplist或者skiplist。同時滿足以下條件時使用ziplist編碼:

  • 元素數量小於128個

  • 所有member的長度都小於64位元組

以上兩個條件的上限值可通過zset-max-ziplist-entries和zset-max-ziplist-value來修改。

ziplist編碼的有序集合使用緊挨在一起的壓縮列表節點來保存,第一個節點保存member,第二個保存score。ziplist內的集合元素按score從小到大排序,score較小的排在表頭位置。

skiplist編碼的有序集合底層是一個命名為zset的結構體,而一個zset結構同時包含一個字典和一個跳躍表。跳躍表按score從小到大保存所有集合元素。而字典則保存著從member到score的映射,這樣就可以用O(1)的複雜度來查找member對應的score值。雖然同時使用兩種結構,但它們會通過指針來共用相同元素的member和score,因此不會浪費額外的記憶體。

和set一樣sorted set也是string類型元素的集合,不同的是每個元素都會關聯一個double類型的score。sorted set的實現是skip list和hash table的混合體,當元素被添加到集合中時,一個元素到score的映射被添加到hash table中,所以給定一個元素獲取score的開銷是O(1),另一個score到元素的映射被添加到skip list並按照score排序,所以就可以有序的獲取集合中的元素。 添加,刪除操作開銷都是O(1)和skip list的開銷一致,redis的skip list實現用的是雙向鏈表,這樣就可以逆序從尾部取元素。 sorted set最經常的使用方式應該是作為索引來使用,我們可以把要排序的欄位作為score存儲,對象的id當元素存儲。

 參考:

http://weixin.niurenqushi.com/article/2017-05-07/4842721.html

https://mp.weixin.qq.com/s?__biz=MzA5ODM5MDU3MA==&mid=2650862682&idx=1&sn=41ea245ac0a9dbfc943dd1d03228a14e&chksm=8b66131fbc119a09ec27e70dca884425c5c1b54deca2f1fde471b54fe723a23b1b347752b7f8&scene=21#wechat_redirect

https://mp.weixin.qq.com/s?__biz=MzA5ODM5MDU3MA==&mid=2650862680&idx=1&sn=978a6ea4971b6b98f266fa34bf1b49d8&chksm=8b66131dbc119a0bc82165c67dd6b13c1621b7ea289da6dd23e93adc6a86c43790c41d47da13&scene=21#wechat_redirect

http://zhangtielei.com/posts/blog-redis-dict.html

http://crazyjvm.iteye.com/blog/1720289

http://blog.csdn.net/men_wen/article/details/70257207


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1。不要充滿至 100% 。 2。不要放電至 0% 。 3。電池的容量儘量維持在 50%。 4。不要在高溫下操作。 5。不要使用快充功能的充電器。 6。使用一般的充電器,如,500mA 或是 800mA。 ...
  • 本文想要完成對twemproxy發送流程——msg_send的探索,對於twemproxy發送流程的數據結構已經在《twemproxy接收流程探索——剖析twemproxy代碼正編》介紹過了,msg_send和msg_recv的流程大致類似。請在閱讀代碼時,查看註釋,英文註釋是作者對它的代碼的註解, ...
  • 登錄到安裝oracle資料庫伺服器的操作系統。打開命令視窗:(我的演示機器是windows) 登錄到安裝oracle資料庫伺服器的操作系統。打開命令視窗:(我的演示機器是windows) 查看環境變數ORACLE_SID的設置情況: windows: echo %ORACLE_SID% linux: ...
  • 本文出處:http://www.cnblogs.com/wy123/p/6984885.html 最近遇到一個SQL Server伺服器響應極度緩慢,並且出現客戶端請求報錯的情況,在資料庫中的errorlog中出現磁碟請求超過15s才完成的error消息。對於此類問題,到底是存儲系統或者磁碟的故障, ...
  • 1、基本構成 (1)需要查詢的表(單表,多表,視圖) (2)需要查詢的信息(欄位信息,過濾處理) (3)查詢條件(欄位關聯,欄位值範圍,記錄截取設置,排序方式,分組方式,去重,or ,and) 2、實例展示(以user表為例) 2.1查詢單表(user) (1)查詢單表所有欄位 select * f ...
  • 下載apache-flume-1.7.0-bin.tar.gz,用 解壓,在/etc/profile文件中增加設置: 修改$FLUME_HOME/conf/下的兩個文件,在flume-env.sh中增加JAVA_HOME: 最重要的,修改flume-conf.properties文件: 以上文件設置 ...
  • 同Oracle一樣,SQL Server在非一致性關閉的時候也會進行實例恢復(Instance Recovery),本文根據stack overflow的文章介紹一些SQL Server實例恢復的知識。 原文鏈接:https://stackoverflow.com/questions/4193273 ...
  • 本文出處:http://www.cnblogs.com/wy123/p/6979499.html show profile 命令用於跟蹤執行過的sql語句的資源消耗信息,可以幫助查看sql語句的執行情況,可以在做性能分析或者問題診斷的時候作為參考。 在MySQL5.7中, show profile ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...