Redis 數據結構與記憶體管理策略(下)

来源:https://www.cnblogs.com/wangiqngpei557/archive/2018/01/27/8364859.html
-Advertisement-
Play Games

說到 ___hash table___ 有兩個東西是我們經常會碰到的,首先就是 ___hash 碰撞___ 問題,__redis dict__ 是採用鏈地址法來解決,___dictEntry->next___ 就是指向下個衝突 __key__ 的節點。 還有一個經常碰到的就是 __rehash... ...


Redis 數據結構與記憶體管理策略(下)

標簽: Redis Redis數據結構 Redis記憶體管理策略 Redis數據類型 Redis類型映射


  • Redis 數據類型特點與使用場景
    • StringListHashSetZset
    • 案例:滬江團購系統大促 hot-top 介面 cache 設計
  • Redis 記憶體數據結構與編碼
    • OBJECT encoding key、DEBUG OBJECT key
    • 簡單動態字元串(simple dynamic string)
    • 鏈表(linked list)
    • 字典(dict)
    • 跳錶(skip list)
    • 整數集合(int set)
    • 壓縮表(zip list)
    • Redis Object 類型與映射
  • Redis 記憶體管理策略
    • 鍵 過期時間、生存時間
    • 過期鍵刪除策略
    • AOFRDB 處理過期鍵策略
    • Redis LRU 演算法
  • Redis 持久化方式
    • RDB (Redis DataBase)
    • AOF (Append-only file)

字典(dict)

dict 字典是基於 hash演算法 來實現,是 Hash 數據類型的底層存儲數據結構。我們來看下 redis 3.0.0 版本的 dict.h 頭文件定義。

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx;
    int iterators; 
} dict;
typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;
typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

說到 hash table 有兩個東西是我們經常會碰到的,首先就是 hash 碰撞 問題,redis dict 是採用鏈地址法來解決,dictEntry->next 就是指向下個衝突 key 的節點。

還有一個經常碰到的就是 rehash 的問題,提到 rehash 我們還是有點擔心性能的。那麼redis 實現是非常巧妙的,採用 惰性漸進式 rehash 演算法

dict struct 里有一個 ht[2] 組數,還有一個 rehashidx 索引。redis 進行 rehash 的大致演算法是這樣的,首先會開闢一個新的 dictht 空間,放在 ht[2] 索引上,此時將 rehashidx 設置為0,表示開始進入 rehash 階段,這個階段可能會持續很長時間,rehashidx 表示 dictEntry 個數。

每次當有對某個 ht[1] 索引中的 key 進行訪問時,獲取、刪除、更新,redis 都會將當前 dictEntry 索引中的所有 key rehashht[2] 字典中。一旦 rehashidx=-1 表示 rehash 結束。

跳錶(skip list)

skip listzset 的底層數據結構,有著高性能的查找排序能力。

我們都知道一般用來實現帶有排序的查找都是用 Tree 來實現,不管是各種變體的 B Tree 還是 B+ Tree,本質都是用來做順序查找。

skip list 實現起來簡單,性能也與 B Tree 相接近。

typedef struct zskiplistNode {
    robj *obj;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned int span;
    } level[];
} zskiplistNode;

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;

zskiplistNode->zskiplistLevel->span 這個值記錄了當前節點距離下個節點的跨度。每一個節點會有最大不超過 zskiplist->level 節點個數,分別用來表示不同跨度與節點的距離。

每個節點會有多個 forward 向前指針,只有一個 backward 指針。每個節點會有對象 __*obj__ 和 score 分值,每個分值都會按照順序排列。

整數集合(int set)

int set 整數集合是 set 數據類型的底層實現數據結構,它的特點和使用場景很明顯,只要我們使用的集合都是整數且在一定的範圍之內都會使用整數集合編碼。

SADD set:userid 100 200 300
(integer) 3
OBJECT encoding set:userid
"intset"

int set 使用一塊連續的記憶體來存儲集合數據,它是數組結構不是鏈表結構。

typedef struct intset {
    uint32_t encoding;
    uint32_t length;
    int8_t contents[];
} intset;

intset->encoding 用來確定 contents[] 是什麼類型的整數編碼,以下三種值之一。

#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

redis 會根據我們設置的值類型動態 sizeof 出一個對應的空間大小。如果我們集合原來是 int16 ,然後往集合里添加了 int32 整數將觸發升級,一旦升級成功不會觸發降級操作。

壓縮表(zip list)

zip list 壓縮表是 listzsethash 數據類型的底層數據結構之一。它是為了節省記憶體通過壓縮數據存儲在一塊連續的記憶體空間中。

typedef struct zlentry {
    unsigned int prevrawlensize, prevrawlen;
    unsigned int lensize, len;
    unsigned int headersize;
    unsigned char encoding;
    unsigned char *p;
} zlentry;

它最大的優點就是壓縮空間,空間利用率很高。缺點就是一旦出現更新可能就是連鎖更新,因為數據在內容空間中都是連續的,最極端情況下就是可能出現順序連鎖擴張。

壓縮列表會由多個 zlentry 節點組成,每一個 zlentry 記錄上一個節點長度和大小,當前節點長度 lensize 和大小 len 包括編碼 encoding

這取決於業務場景,redis 提供了一組配置,專門用來針對不同的場景進行閾值控制。

hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

上述配置分別用來配置 ziplist 作為 hashlistzset 數據類型的底層壓縮閾值控制。

Redis Object 類型與映射

redis 內部每一種數據類型都是對象化的,也就是我們所說的5種數據類型其實內部都會對應到 redisObject 對象,然後在由 redisObject 來包裝具體的存儲數據結構和編碼。

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:REDIS_LRU_BITS; 
    int refcount;
    void *ptr;
} robj;

這是一個很 OO 的設計,redisObject->type5 種數據類型之一,redisObject->encoding 是這個數據類型所使用的數據結構和編碼。

我們看下 redis 提供的 5 種數據類型與每一種數據類型對應的存儲數據結構和編碼。

/* Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
#define REDIS_ENCODING_RAW 0     
#define REDIS_ENCODING_INT 1    
#define REDIS_ENCODING_HT 2
#define REDIS_ENCODING_ZIPMAP 3
#define REDIS_ENCODING_LINKEDLIST 4
#define REDIS_ENCODING_ZIPLIST 5 
#define REDIS_ENCODING_INTSET 6  
#define REDIS_ENCODING_SKIPLIST 7  
#define REDIS_ENCODING_EMBSTR 8 

REDIS_ENCODING_ZIPMAP 3 這個編碼可以忽略了,在特定的情況下有性能問題,在 redis 2.6 版本之後已經廢棄,為了相容性保留。

Redis 數據結構與記憶體管理策略

上圖是 redis 5 種數據類型與底層數據結構和編碼的對應關係,但是這種對應關係在每一個版本中都會有可能發生變化,這也是 redisObject 的靈活性所在,有著 OO 的這種多態性。

redisObject->refcount 表示當前對象的引用計數,在 redis 內部為了節省記憶體採用了共用對象的方法,當某個對象被引用的時候這個 refcount 會加 1,釋放的時候會減 1

redisObject->lru 表示當前對象的 空轉時長,也就是 idle time ,這個時間會是 redis lru 演算法用來釋放對象的時間依據。可以通過 OBJECT idletime 命令查看某個 key 的空轉時長 lru 時間。

Redis 記憶體管理策略

redis 在服務端分別為不同的 db index 維護一個 dict 這個 dict 稱為 key space 鍵空間 。每一個 redis client 只能屬於一個 db index ,在 redis 服務端會維護每一個鏈接的 redisClient

typedef struct redisClient {
    uint64_t id;
    int fd;
    redisDb *db;
} redisClient;

在服務端每一個 redis 客戶端都會有一個指向 redisDb 的指針。

typedef struct redisDb {
    dict *dict;
    dict *expires;
    dict *blocking_keys;
    dict *ready_keys;
    dict *watched_keys;
    struct evictionPoolEntry *eviction_pool;
    int id;
    long long avg_ttl;
} redisDb;

key space 鍵空間就是這裡的 redisDb->dictredisDb->expires 是維護所有鍵空間的每一個 key 的過期時間。

鍵 過期時間、生存時間

對於一個 key 我們可以設置它多少秒、毫秒之後過期,也可以設置它在某個具體的時間點過期,後者是一個時間戳。

EXPIRE 命令可以設置某個 key 多少秒之後過期
PEXPIRE 命令可以設置某個 key 多少毫秒之後過期

EXPIREAT 命令可以設置某個 key 在多少秒時間戳之後過期
PEXPIREAT 命令可以設置某個 key 在多少毫秒時間戳之後過期

PERSIST 命令可以移除鍵的過期時間

其實上述命令最終都會被轉換成對 PEXPIREAT 命令。在 redisDb->expires 指向的 key 字典中維護著一個到期的毫秒時間戳。

TTL、PTTL 可以通過這兩個命令查看某個 key 的過期秒、毫秒數。

redis 內部有一個 事件迴圈,這個事件迴圈會檢查鍵的過期時間是否小於當前時間,如果小於則會刪除這個鍵。

過期鍵刪除策略

在使用 redis 的時候我們最關心的就是鍵是如何被刪除的,如何高效的準時的刪除某個鍵。其實 redis 提供了兩個方案來完成這件事情。

redis 採用 惰性刪除定期刪除 雙重刪除策略。

當我們訪問某個 key 的時候 redis 會檢查它是否過期,這是惰性刪除。

robj *lookupKeyRead(redisDb *db, robj *key) {
    robj *val;

    expireIfNeeded(db,key);
    val = lookupKey(db,key);
    if (val == NULL)
        server.stat_keyspace_misses++;
    else
        server.stat_keyspace_hits++;
    return val;
}
int expireIfNeeded(redisDb *db, robj *key) {
    mstime_t when = getExpire(db,key);
    mstime_t now;

    if (when < 0) return 0; /* No expire for this key */
    
    if (server.loading) return 0;
    
    now = server.lua_caller ? server.lua_time_start : mstime();
    if (server.masterhost != NULL) return now > when;

    /* Return when this key has not expired */
    if (now <= when) return 0;

    /* Delete the key */
    server.stat_expiredkeys++;
    propagateExpire(db,key);
    notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,"expired",key,db->id);
    return dbDelete(db,key);
}

redis 也會通過 事件迴圈 周期性的執行 key 的過期刪除動作,這是定期刪除。

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    /* Handle background operations on Redis databases. */
    databasesCron();
}
void databasesCron(void) {
    /* Expire keys by random sampling. Not required for slaves
     * as master will synthesize DELs for us. */
    if (server.active_expire_enabled && server.masterhost == NULL)
        activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
}

惰性刪除 是每次只要有讀取、寫入都會觸發惰性刪除代碼。周期刪除 是由 redis EventLoop 來觸發的。redis 內部很多維護性工作都是基於 EventLoop

AOFRDB 處理過期鍵策略

既然鍵會隨時存在過期問題,那麼涉及到持久化 redis 是如何幫我們處理的。

redis 使用 RDB 方式持久化時,每次持久化的時候就會檢查這些即將被持久化的 key 是否已經過期,如果過期將直接忽略,持久化那些沒有過期的鍵。當 redis 作為 master 主伺服器 啟動的時候,在載入 rdb 持久化鍵時也會檢查這些鍵是否過期,將忽略過期的鍵,只載入沒過期的鍵。

redis 使用 AOF 方式持久化時,每次遇到過期的 key redis 會追加一條 DEL 命令 到 AOF 文件,也就是說只要我們順序載入執行 AOF 命令文件就會刪除過期的鍵。

如果 redis 作為從伺服器啟動的化,它一旦與 master 主伺服器 建立鏈接就會清空所有數據進行完整同步,當然新版本的 redis 支持 SYCN2 的半同步。如果是已經建立了 master/slave 主從同步之後,主伺服器會發送 DEL 命令給所有從伺服器執行刪除操作。

Redis LRU 演算法

在使用 redis 的時候我們會設置 maxmemory 選項,64 位的預設是 0 不限制。線上的伺服器必須要設置的,要不然很有可能導致 redis 宿主伺服器直接記憶體耗盡最後鏈接都上不去。

所以基本要設置兩個配置:

maxmemory 最大記憶體閾值
maxmemory-policy 到達閾值的執行策略

可以通過 CONFIG GET maxmemory/maxmemory-policy 分別查看這兩個配置值,也可以通過 CONFIG SET 去分別配置。

maxmemory-policy 有一組配置,可以用在很多場景下:

noeviction:客戶端嘗試執行會讓更多記憶體被使用的命令直接報錯
allkeys-lru: 在所有key里執行lru演算法
volatile-lru:在所有已經過期的key里執行lru演算法
allkeys-random:在所有key里隨機回收
volatile-random:在已經過期的key里隨機回收
volatile-ttl:回收已經過期的key,並且優先回收存活時間(TTL)較短的鍵

關於 cache 的命中率可以通過 info 命令查看 鍵空間的命中率和未命中率。

# Stats
keyspace_hits:33
keyspace_misses:5

maxmemory 在到達閾值的時候會採用一定的策略去釋放記憶體,這些策略我們可以根據自己的業務場景來選擇,預設是 noeviction

redis LRU 演算法有一個取樣的優化機制,可以通過一定的取樣因數來加強回收的 key 的準確度。CONFIG GET maxmemory-samples 查看取樣配置,具體可以參考更加詳細的文章。

Redis 持久化方式

redis 本身提供持久化功能,有兩種持久化機制,一種是數據持久化 RDB ,一種是命令持久化 AOF,這兩種持久化方式各有優缺點,也可以組合使用,一旦組合使用 redis 在載入數據的時候會優先載入 aof 文件,只有當 AOF 持久化關閉的時候才會載入 rdb 文件。

RDB (Redis DataBase)

RDBredis 資料庫,redis 會根據一個配置來觸發持久化。

#save <seconds> <changes>

save 900 1
save 300 10
save 60 10000
CONFIG GET save
1) "save"
2) "3600 1 300 100 60 10000"

表示在多少秒之類的變化次數,一旦達到這個觸發條件 redis 將觸發持久化動作。redis 在執行持久化的時候有兩種模式 BGSAVE、SAVEBGSAVE 是後臺保存,redisfork 出一個子進程來處理持久化,不會 block 用戶的執行請求。而 SAVE 則會 block 用戶執行請求。

struct redisServer {
long long dirty;/* Changes to DB from the last save */
time_t lastsave; /* Unix time of last successful save */
long long dirty_before_bgsave;
pid_t rdb_child_pid;/* PID of RDB saving child */
struct saveparam *saveparams; /* Save points array for RDB */
}
struct saveparam {
    time_t seconds;
    int changes;
};

redisServer 包含的信息很多,其中就包含了有關於 RDB 持久化的信息。redisServer->dirty 至上次 save 到目前為止的 change 數。redisServer->lastsave 上次 save 時間。

saveparam struct 保存了我們通過 save 命令設置的參數,time_t 是個 long 時間戳。

typedef __darwin_time_t     time_t; 
typedef long    __darwin_time_t;    /* time() */
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
         for (j = 0; j < server.saveparamslen; j++) {
            struct saveparam *sp = server.saveparams+j;
            if (server.dirty >= sp->changes &&
                server.unixtime-server.lastsave > sp->seconds &&
                (server.unixtime-server.lastbgsave_try >
                 REDIS_BGSAVE_RETRY_DELAY ||
                 server.lastbgsave_status == REDIS_OK))
            {
                redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...",
                    sp->changes, (int)sp->seconds);
                rdbSaveBackground(server.rdb_filename);
                break;
            }
         }
}

redis 事件迴圈會周期性的執行 serverCron 方法,這段代碼會迴圈遍歷 server.saveparams 參數鏈表。

如果 server.dirty 大於等於 我們參數里配置的變化並且 server.unixtime-server.lastsave 大於參數里配置的時間並且 server.unixtime-server.lastbgsave_try 減去 bgsave 重試延遲時間或者當前 server.lastbgsave_status==REDIS_OK 則執行 rdbSaveBackground 方法。

AOF (Append-only file)

AOF 持久化是採用對文件進行追加對方式進行,每次追加都是 redis 處理的 命令。有點類似 command sourcing 命令溯源 的模式。

只要我們可以將所有的命令按照執行順序在重放一遍就可以還原最終的 redis 記憶體狀態。

AOF 持久化最大的優勢是可以縮短數據丟失的間隔,可以做到秒級的丟失率。RDB 會丟失上一個保存周期到目前的所有數據,只要沒有觸發 save 命令設置的 save seconds changes 閾值數據就會一直不被持久化。

struct redisServer {
 /* AOF buffer, written before entering the event loop */
 sds aof_buf;
 }
struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];
};

aof_buf 是命令緩存區,採用 sds 結構緩存,每次當有命令被執行當時候都會寫一次到 aof_buf 中。有幾個配置用來控制 AOF 持久化的機制。

appendonly no 
appendfilename "appendonly.aof"

appendonly 用來控制是否開啟 AOF 持久化,appendfilename 用來設置 aof 文件名。

appendfsync always
appendfsync everysec
appendfsync no

appendfsync 用來控制命令刷盤機制。現在操作系統都有文件 cache/buffer 的概念,所有的寫入和讀取都會走 cache/buffer,並不會每次都同步刷盤,因為這樣性能一定會受影響。所以 redis 也提供了這個選項讓我們來自己根據業務場景控制。

always :每次將 aof_buf 命令寫入 aof 文件並且執行實時刷盤。
everysec :每次將 aof_buf 命令寫入 aof 文件,但是每隔一秒執行一次刷盤。
no :每次將 aof_buf 命令寫入 aof 文件不執行刷盤,由操作系統來自行控制。

AOF 也是採用後臺子進程的方式進行,與主進程共用數據空間也就是 aof_buf,但是只要開始了 AOF 子進程之後 redis 事件迴圈文件事件處理器 會將之後的命令寫入另外一個 aof_buf ,這樣就可以做到平滑的切換。

AOF 會不斷的追加命令進 aof 文件,隨著時間和併發量的加大 aof 文件會極速膨脹,所以有必要對這個文件大小進行優化。redis 基於 rewrite 重寫對文件進行壓縮。

no-appendfsync-on-rewrite no/yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

no-appendfsync-on-rewrite 控制是否在 bgrewriteaof 的時候還需要進行命令追加,如果追加可能會出現磁碟 IO 跑高現象。

上面說過,當 AOF 進程在執行的時候原來的事件迴圈還會正常的追加命令進 aof 文件,同時還會追加命令進另外一個 aof_buf ,用來做新 aof 文件的重寫。這是兩條並行的動作,如果我們設置成 yes 就不追加原來的 aof_buf 因為新的 aof 文件已經包含了之後進來的命令。

auto-aof-rewrite-percentage、auto-aof-rewrite-min-size 64mb 這兩個配置前者是文件增長百分比來進行 rewrite ,後者是按照文件大小增長進行 rewrite


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

-Advertisement-
Play Games
更多相關文章
  • 今天豹哥主要講的是工程開發最終的output文件,即image文件。image文件也叫鏡像文件,這個文件主要包含的是只有晶元能夠解釋執行的二進位機器碼數據。 ...
  • 概述 S3C2440A中的中斷控制器接受來自60個中斷源的請求。提供這些中斷源的可以是內部外設,如DMA控制器、UART、IIC等等。在這些中斷源中,UARTn、AC97和EINTn中斷對於中斷控制器而言是“或”關係(在這幾個源中還可以有中斷分支)。 當從內部外設和外部中斷請求引腳收到多個中斷請求時 ...
  • dos燒錄鏡像腳本,以防備忘; ...
  • 在windows系統中有很多好用的圖片排序軟體,可以批量的進行圖片排序。然而在ubuntu中,圖片排序只能自己寫一個shell腳本,編寫shell代碼。下麵是具體的操作步驟。(1)、新建一個rename.sh文件(這個名字可以隨便起但是尾碼必須是.sh,而且一般放在與圖片集所在的目錄下),之後在文件 ...
  • 在上章-製作文件系統,並使內核成功啟動jffs2文件系統了 本章便開始使內核支持燒寫yaffs2文件系統 1.首先獲取yaffs2源碼(參考git命令使用詳解) 2.將yaffs2源碼來配置到內核里(使內核支持yaffs2) 找到以下幾段話: 參考上圖: 搜索yaffs,找到位於: -> File ...
  • 一、基礎設置: 二、配置nginx.conf文件,在原來的vhost中增加如下代碼: 三、重啟nginx: ...
  • 1、通知DBA停庫; 串列登陸伺服器 2、備份系統信息 mkdir p /bakinfo df h /bakinfo/df.txt_ ps ef /bakinfo/ps.txt_ ip a /bakinfo/ip.txt_ netstat rn /bakinfo/netstat.txt_ free ...
  • 1、打開cmd ,輸入 F: // 切換到Apache安裝路徑,我的Apache安裝目錄在 F盤 2、cd F:\Apache\bin 3、set "openssl_conf = F:\Apache\conf\openssl.cnf" 臨時設置openssl_conf路徑,也可在環境變數中建新項目, ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...