Redis 記憶體突增時,如何定量分析其記憶體使用情況

来源:https://www.cnblogs.com/ivictor/p/18426260
-Advertisement-
Play Games

背景 最近碰到一個 case,一個 Redis 實例的記憶體突增,used_memory最大時達到了 78.9G,而該實例的maxmemory配置卻只有 16G,最終導致實例中的數據被大量驅逐。 以下是問題發生時INFO MEMORY的部分輸出內容。 # Memoryused_memory:84716 ...


背景

最近碰到一個 case,一個 Redis 實例的記憶體突增,used_memory最大時達到了 78.9G,而該實例的maxmemory配置卻只有 16G,最終導致實例中的數據被大量驅逐。

以下是問題發生時INFO MEMORY的部分輸出內容。

# Memory
used_memory:84716542624
used_memory_human:78.90G
used_memory_rss:104497676288
used_memory_rss_human:97.32G
used_memory_peak:84716542624
used_memory_peak_human:78.90G
used_memory_peak_perc:100.00%
used_memory_overhead:75682545624
used_memory_startup:906952
used_memory_dataset:9033997000
used_memory_dataset_perc:10.66%
allocator_allocated:84715102264
allocator_active:101370822656
allocator_resident:102303637504
total_system_memory:810745470976
total_system_memory_human:755.07G
used_memory_lua:142336
used_memory_lua_human:139.00K
used_memory_scripts:6576
used_memory_scripts_human:6.42K
number_of_cached_scripts:13
maxmemory:17179869184
maxmemory_human:16.00G
maxmemory_policy:volatile-lru
allocator_frag_ratio:1.20
allocator_frag_bytes:16655720392

記憶體突增導致數據被驅逐,是 Redis 中一個較為常見的問題。很多童鞋在面對這類問題時往往缺乏清晰的分析思路,常常誤以為是複製、RDB 持久化等操作引起的。接下來,我們看看如何系統地分析這類問題。

本文主要包括以下幾部分:

  1. INFO 中的used_memory是怎麼來的?
  2. 什麼是used_memory
  3. used_memory記憶體通常會被用於哪些場景?
  4. Redis 7 在記憶體統計方面的變化。
  5. 數據驅逐的觸發條件——當used_memory 超過maxmemory後,是否一定會觸發驅逐?
  6. 最後,分享一個腳本,幫助實時分析used_memory增長時,具體是哪一部分的記憶體消耗導致的。

INFO 中的 used_memory 是怎麼來的?

當我們執行INFO命令時,Redis 會調用genRedisInfoString函數來生成其輸出。

// server.c
sds genRedisInfoString(const char *section) {
    ...
    /* Memory */
    if (allsections || defsections || !strcasecmp(section,"memory")) {
        ...
        size_t zmalloc_used = zmalloc_used_memory();
        ...
        if (sections++) info = sdscat(info,"\r\n");
        info = sdscatprintf(info,
            "# Memory\r\n"
            "used_memory:%zu\r\n"
            "used_memory_human:%s\r\n"
            "used_memory_rss:%zu\r\n"
            ...
            "lazyfreed_objects:%zu\r\n",
            zmalloc_used,
            hmem,
            server.cron_malloc_stats.process_rss,
            ...
            lazyfreeGetFreedObjectsCount()
        );
        freeMemoryOverheadData(mh);
    }
    ...
    return info;
}

可以看到,used_memory 的值來自 zmalloc_used,而 zmalloc_used 又是通過zmalloc_used_memory()函數獲取的。

// zmalloc.c
size_t zmalloc_used_memory(void) {
    size_t um;
    atomicGet(used_memory,um);
    return um;
}

zmalloc_used_memory() 的實現很簡單,就是以原子方式讀取 used_memory 的值。

什麼是 used_memory

used_memory是一個靜態變數,其類型為redisAtomic size_t,其中redisAtomic_Atomic類型的別名。_Atomic是 C11 標準引入的關鍵字,用於聲明原子類型,保證在多線程環境中對該類型的操作是原子的,避免數據競爭。

#define redisAtomic _Atomic
static redisAtomic size_t used_memory = 0;

used_memory 的更新主要通過兩個巨集定義實現:

#define update_zmalloc_stat_alloc(__n) atomicIncr(used_memory,(__n))
#define update_zmalloc_stat_free(__n) atomicDecr(used_memory,(__n))

其中,update_zmalloc_stat_alloc(__n)是在分配記憶體時調用,它通過原子操作讓 used_memory 加__n。

update_zmalloc_stat_free(__n)則是在釋放記憶體時調用,它通過原子操作讓 used_memory 減__n

這兩個巨集確保了在記憶體分配和釋放過程中used_memory的準確更新,並且避免了併發操作帶來的數據競爭問題。

在通過記憶體分配器(常用的記憶體分配器有 glibc's malloc、jemalloc、tcmalloc,Redis 中一般使用 jemalloc)中的函數分配或釋放記憶體時,會同步調用update_zmalloc_stat_allocupdate_zmalloc_stat_free來更新 used_memory 的值。

在 Redis 中,記憶體管理主要通過以下兩個函數來實現:

// zmalloc.c
void *ztrymalloc_usable(size_t size, size_t *usable) {
    ASSERT_NO_SIZE_OVERFLOW(size);
    void *ptr = malloc(MALLOC_MIN_SIZE(size)+PREFIX_SIZE);

    if (!ptr) return NULL;
#ifdef HAVE_MALLOC_SIZE
    size = zmalloc_size(ptr);
    update_zmalloc_stat_alloc(size);
    if (usable) *usable = size;
    return ptr;
#else
    ...
#endif
}

void zfree(void *ptr) {
    ...
    if (ptr == NULL) return;
#ifdef HAVE_MALLOC_SIZE
    update_zmalloc_stat_free(zmalloc_size(ptr));
    free(ptr);
#else
   ...
#endif
}

其中,

  • ztrymalloc_usable函數用於分配記憶體。該函數首先會調用malloc分配記憶體。如果分配成功,則會通過 update_zmalloc_stat_alloc更新 used_memory 的值。
  • zfree 函數用於釋放記憶體。在釋放記憶體之前,先通過update_zmalloc_stat_free調整 used_memory 的值,然後再調用free釋放記憶體。

這種機制保證了 Redis 能夠準確跟蹤記憶體的分配和釋放情況,從而有效地管理記憶體使用。

used_memory 記憶體通常會被用於哪些場景?

used_memory主要由兩部分組成:

  1. 數據本身:對應 INFO 中的used_memory_dataset
  2. 內部管理和維護數據結構的開銷:對應 INFO 中的used_memory_overhead

需要註意的是,used_memory_dataset 並不是根據 Key 的數量及 Key 使用的記憶體計算出來的,而是通過 used_memory 減去 used_memory_overhead 得到的。

接下來,我們重點分析下used_memory_overhead 的來源。實際上,Redis 提供了一個單獨的函數-getMemoryOverheadData,專門用於計算這一部分的記憶體開銷。

// object.c
struct redisMemOverhead *getMemoryOverheadData(void) {
    int j;
    // mem_total 用於累積總的記憶體開銷,最後會賦值給 used_memory_overhead。
    size_t mem_total = 0;
    // mem 用於計算每一部分的記憶體使用量。
    size_t mem = 0;
    // 調用 zmalloc_used_memory() 獲取 used_memory。
    size_t zmalloc_used = zmalloc_used_memory();
    // 使用 zcalloc 分配一個 redisMemOverhead 結構體的記憶體。
    struct redisMemOverhead *mh = zcalloc(sizeof(*mh));
    ...
    // 將 Redis 啟動時的記憶體使用量 server.initial_memory_usage 加入到總記憶體開銷中。
    mem_total += server.initial_memory_usage;

    mem = 0;
    // 將複製積壓緩衝區的記憶體開銷加入到總記憶體開銷中。
    if (server.repl_backlog)
        mem += zmalloc_size(server.repl_backlog);
    mh->repl_backlog = mem;
    mem_total += mem;

    /* Computing the memory used by the clients would be O(N) if done
     * here online. We use our values computed incrementally by
     * clientsCronTrackClientsMemUsage(). */
    // 計算客戶端記憶體開銷
    mh->clients_slaves = server.stat_clients_type_memory[CLIENT_TYPE_SLAVE];
    mh->clients_normal = server.stat_clients_type_memory[CLIENT_TYPE_MASTER]+
                         server.stat_clients_type_memory[CLIENT_TYPE_PUBSUB]+
                         server.stat_clients_type_memory[CLIENT_TYPE_NORMAL];
    mem_total += mh->clients_slaves;
    mem_total += mh->clients_normal;
    // 計算 AOF 緩衝區和 AOF Rewrite Buffer 的記憶體開銷
    mem = 0;
    if (server.aof_state != AOF_OFF) {
        mem += sdsZmallocSize(server.aof_buf);
        mem += aofRewriteBufferSize();
    }
    mh->aof_buffer = mem;
    mem_total+=mem;
    // 計算 Lua 腳本緩存的記憶體開銷
    mem = server.lua_scripts_mem;
    mem += dictSize(server.lua_scripts) * sizeof(dictEntry) +
        dictSlots(server.lua_scripts) * sizeof(dictEntry*);
    mem += dictSize(server.repl_scriptcache_dict) * sizeof(dictEntry) +
        dictSlots(server.repl_scriptcache_dict) * sizeof(dictEntry*);
    if (listLength(server.repl_scriptcache_fifo) > 0) {
        mem += listLength(server.repl_scriptcache_fifo) * (sizeof(listNode) +
            sdsZmallocSize(listNodeValue(listFirst(server.repl_scriptcache_fifo))));
    }
    mh->lua_caches = mem;
    mem_total+=mem;
    // 計算資料庫的記憶體開銷:遍歷所有資料庫 (server.dbnum)。對於每個資料庫,計算主字典 (db->dict) 和過期字典 (db->expires) 的記憶體開銷。
    for (j = 0; j < server.dbnum; j++) {
        redisDb *db = server.db+j;
        long long keyscount = dictSize(db->dict);
        if (keyscount==0) continue;

        mh->total_keys += keyscount;
        mh->db = zrealloc(mh->db,sizeof(mh->db[0])*(mh->num_dbs+1));
        mh->db[mh->num_dbs].dbid = j;

        mem = dictSize(db->dict) * sizeof(dictEntry) +
              dictSlots(db->dict) * sizeof(dictEntry*) +
              dictSize(db->dict) * sizeof(robj);
        mh->db[mh->num_dbs].overhead_ht_main = mem;
        mem_total+=mem;

        mem = dictSize(db->expires) * sizeof(dictEntry) +
              dictSlots(db->expires) * sizeof(dictEntry*);
        mh->db[mh->num_dbs].overhead_ht_expires = mem;
        mem_total+=mem;

        mh->num_dbs++;
    }
    // 將計算的 mem_total 賦值給 mh->overhead_total。
    mh->overhead_total = mem_total;
    // 計算數據的記憶體開銷 (zmalloc_used - mem_total) 並存儲在 mh->dataset。
    mh->dataset = zmalloc_used - mem_total;
    mh->peak_perc = (float)zmalloc_used*100/mh->peak_allocated;

    /* Metrics computed after subtracting the startup memory from
     * the total memory. */
    size_t net_usage = 1;
    if (zmalloc_used > mh->startup_allocated)
        net_usage = zmalloc_used - mh->startup_allocated;
    mh->dataset_perc = (float)mh->dataset*100/net_usage;
    mh->bytes_per_key = mh->total_keys ? (net_usage / mh->total_keys) : 0;

    return mh;
}

基於上面代碼的分析,可以知道 used_memory_overhead 由以下幾部分組成:

  • server.initial_memory_usage:Redis 啟動時的記憶體使用量,對應 INFO 中used_memory_startup

  • mh->repl_backlog:複製積壓緩衝區的記憶體開銷,對應 INFO 中的mem_replication_backlog

  • mh->clients_slaves:從庫的記憶體開銷。對應 INFO 中的mem_clients_slaves

  • mh->clients_normal:其它客戶端的記憶體開銷,對應 INFO 中的mem_clients_normal

  • mh->aof_buffer:AOF 緩衝區和 AOF 重寫緩衝區的記憶體開銷,對應 INFO 中的mem_aof_buffer。AOF 緩衝區是數據寫入 AOF 之前使用的緩衝區。AOF 重寫緩衝區是 AOF 重寫期間,用於存放新增數據的緩衝區。

  • mh->lua_caches:Lua 腳本緩存的記憶體開銷,對應 INFO 中的used_memory_scripts。Redis 5.0 新增的。

  • 字典的記憶體開銷,這部分記憶體在 INFO 中沒有顯示,需要通過MEMORY STATS查看。

    17) "db.0"
    18) 1) "overhead.hashtable.main"
        2) (integer) 2536870912
        3) "overhead.hashtable.expires"
        4) (integer) 0

在這些記憶體開銷中,used_memory_startup 基本不變,mem_replication_backlog 受 repl-backlog-size 的限制,used_memory_scripts 開銷一般不大,而字典的記憶體開銷則與數據量的大小成正比。

所以,重點需要註意的主要有三項:mem_clients_slavesmem_clients_normal 和mem_aof_buffer

  • mem_aof_buffer:重點關註 AOF 重寫期間緩衝區的大小。
  • mem_clients_slaves 和 mem_clients_normal:都是客戶端,記憶體分配方式相同。客戶端的記憶體開銷主要包括以下三部分:
    1. 輸入緩衝區:用於暫存客戶端命令,大小由 client-query-buffer-limit 限制。
    2. 輸出緩衝區:用於緩存發送給客戶端的數據,大小受 client-output-buffer-limit 控制。如果數據超過軟硬限制並持續一段時間,客戶端會被關閉。
    3. 客戶端對象本身占用的記憶體。

Redis 7 在記憶體統計方面的變化

在 Redis 7 中,還會統計以下項的記憶體開銷:

  • mh->cluster_links:集群鏈接的記憶體開銷,對應 INFO 中的mem_cluster_links
  • mh->functions_caches:Function 緩存的記憶體開銷,對應 INFO 中的used_memory_functions
  • 集群模式下鍵到槽映射的記憶體開銷,對應 MEMORY STATS 中的overhead.hashtable.slot-to-keys

此外,Redis 7 引入了 Multi-Part AOF,這個特性移除了 AOF 重寫緩衝區。

需要註意的是,mh->repl_backlog 和 mh->clients_slaves 的記憶體計算方式也發生了變化。

在 Redis 7 之前,mh->repl_backlog 統計的是複製積壓緩衝區的大小,mh->clients_slaves 統計的是所有從節點客戶端的記憶體使用量。

if (server.repl_backlog)
    mem += zmalloc_size(server.repl_backlog);
mh->repl_backlog = mem;
mem_total += mem;

mem = 0;
// 遍歷所有從節點客戶端,累加它們的輸出緩衝區、輸入緩衝區的記憶體使用量以及客戶端對象本身的記憶體占用。
if (listLength(server.slaves)) {
    listIter li;
    listNode *ln;

    listRewind(server.slaves,&li);
    while((ln = listNext(&li))) {
        client *c = listNodeValue(ln);
        mem += getClientOutputBufferMemoryUsage(c);
        mem += sdsAllocSize(c->querybuf);
        mem += sizeof(client);
    }
}
mh->clients_slaves = mem;

因為每個從節點都會分配一個獨立的複製緩衝區(即從節點對應客戶端的輸出緩衝區),所以當從節點的數量增加時,這種實現方式會造成記憶體的浪費。不僅如此,當client-output-buffer-limit設置過大且從節點數量過多時,還容易導致主節點 OOM。

針對這個問題,Redis 7 引入了一個全局複製緩衝區。無論是複製積壓緩衝區(repl-backlog),還是從節點的複製緩衝區都是共用這個緩衝區。

replBufBlock結構體用於存儲全局複製緩衝區的一個塊。

typedef struct replBufBlock {
    int refcount;           /* Number of replicas or repl backlog using. */
    long long id;           /* The unique incremental number. */
    long long repl_offset;  /* Start replication offset of the block. */
    size_t size, used;
    char buf[];
} replBufBlock;

每個replBufBlock包含一個refcount欄位,用於記錄該塊被多少個複製實例(包括主節點的複製積壓緩衝區和從節點)所引用。

當新的從節點添加時,Redis 不會為其分配新的複製緩衝區塊,而是增加現有replBufBlockrefcount

相應地,在 Redis 7 中,mh->repl_backlog 和 mh->clients_slaves 的記憶體計算方式也發生了變化。

if (listLength(server.slaves) &&
    (long long)server.repl_buffer_mem > server.repl_backlog_size)
{
    mh->clients_slaves = server.repl_buffer_mem - server.repl_backlog_size;
    mh->repl_backlog = server.repl_backlog_size;
} else {
    mh->clients_slaves = 0;
    mh->repl_backlog = server.repl_buffer_mem;
}
if (server.repl_backlog) {
    /* The approximate memory of rax tree for indexed blocks. */
    mh->repl_backlog +=
        server.repl_backlog->blocks_index->numnodes * sizeof(raxNode) +
        raxSize(server.repl_backlog->blocks_index) * sizeof(void*);
}
mem_total += mh->repl_backlog;
mem_total += mh->clients_slaves;

具體而言,如果全局複製緩衝區的大小大於repl-backlog-size,則複製積壓緩衝區(mh->repl_backlog)的大小取 repl-backlog-size,剩餘部分視為從庫使用的記憶體(mh->clients_slaves)。如果全局複製緩衝區的大小小於等於 repl-backlog-size,則直接取全局複製緩衝區的大小。

此外,由於引入了一個 Rax 樹來索引全局複製緩衝區中的部分節點,複製積壓緩衝區還需要計算 Rax 樹的記憶體開銷。

數據驅逐的觸發條件

很多人有個誤區,認為只要 used_memory 大於 maxmemory ,就會觸發數據的驅逐。但實際上不是。

數據被驅逐需滿足以下條件:

  1. maxmemory 必須大於 0。
  2. maxmemory-policy 不能是 noeviction。
  3. 記憶體使用需滿足一定的條件。不是 used_memory 大於 maxmemory,而是 used_memory 減去 mem_not_counted_for_evict 後的值大於 maxmemory。

其中,mem_not_counted_for_evict的值可以通過 INFO 命令獲取,它的大小是在freeMemoryGetNotCountedMemory函數中計算的。

size_t freeMemoryGetNotCountedMemory(void) {
    size_t overhead = 0;
    int slaves = listLength(server.slaves);

    if (slaves) {
        listIter li;
        listNode *ln;

        listRewind(server.slaves,&li);
        while((ln = listNext(&li))) {
            client *slave = listNodeValue(ln);
            overhead += getClientOutputBufferMemoryUsage(slave);
        }
    }
    if (server.aof_state != AOF_OFF) {
        overhead += sdsalloc(server.aof_buf)+aofRewriteBufferSize();
    }
    return overhead;
}

freeMemoryGetNotCountedMemory函數統計了所有從節點的複製緩存區、AOF 緩存區和 AOF 重寫緩衝區的總大小。

所以,在 Redis 判斷是否需要驅逐數據時,會從used_memory中剔除從節點複製緩存區、AOF 緩存區以及 AOF 重寫緩衝區的記憶體占用。

Redis 記憶體分析腳本

最後,分享一個腳本。

這個腳本能夠幫助我們快速分析 Redis 的記憶體使用情況。通過輸出結果,我們可以直觀地查看 Redis 各個部分的記憶體消耗情況並識別當 used_memory 增加時,具體是哪一部分的記憶體消耗導致的。

腳本地址:https://github.com/slowtech/dba-toolkit/blob/master/redis/redis_mem_usage_analyzer.py

# python3 redis_mem_usage_analyzer.py -host 10.0.1.182 -p 6379
Metric(2024-09-12 04:52:42)    Old Value            New Value(+3s)       Change per second   
==========================================================================================
Summary
---------------------------------------------
used_memory                    16.43G               16.44G               1.1M                
used_memory_dataset            11.93G               11.93G               22.66K              
used_memory_overhead           4.51G                4.51G                1.08M               

Overhead(Total)                4.51G                4.51G                1.08M               
---------------------------------------------
mem_clients_normal             440.57K              440.52K              -18.67B             
mem_clients_slaves             458.41M              461.63M              1.08M               
mem_replication_backlog        160M                 160M                 0B                  
mem_aof_buffer                 0B                   0B                   0B                  
used_memory_startup            793.17K              793.17K              0B                  
used_memory_scripts            0B                   0B                   0B                  
mem_hashtable                  3.9G                 3.9G                 0B                  

Evict & Fragmentation
---------------------------------------------
maxmemory                      20G                  20G                  0B                  
mem_not_counted_for_evict      458.45M              461.73M              1.1M                
mem_counted_for_evict          15.99G               15.99G               2.62K               
maxmemory_policy               volatile-lru         volatile-lru                             
used_memory_peak               16.43G               16.44G               1.1M                
used_memory_rss                16.77G               16.77G               1.32M               
mem_fragmentation_bytes        345.07M              345.75M              232.88K             

Others
---------------------------------------------
keys                           77860000             77860000             0.0                 
instantaneous_ops_per_sec      8339                 8435                                     
lazyfree_pending_objects       0                    0                    0.0       

該腳本每隔一段時間(由 -i 參數決定,預設是 3 秒)採集一次 Redis 的記憶體數據。然後,它會將當前採集到的數據(New Value)與上一次的數據(Old Value)進行對比,計算出每秒的增量(Change per second)。

輸出主要分為四大部分:

  • Summary:彙總部分,used_memory = used_memory_dataset + used_memory_overhead。
  • Overhead(Total):展示 used_memory_overhead 中各個細項的記憶體消耗情況。Overhead(Total) 等於所有細項之和,理論上應與 used_memory_overhead 相等。
  • Evict & Fragmentation:顯示驅逐和記憶體碎片的一些關鍵指標。其中,mem_counted_for_evict = used_memory - mem_not_counted_for_evict,當 mem_counted_for_evict 超過 maxmemory 時,才會觸發數據驅逐。
  • Others:其他一些重要指標,包括 keys(鍵的總數量)、instantaneous_ops_per_sec(每秒操作數)以及 lazyfree_pending_objects(通過非同步刪除等待釋放的對象數)。

如果發現mem_clients_normalmem_clients_slaves比較大,可指定 --client 查看客戶端的記憶體使用情況。

# python3 redis_mem_usage_analyzer.py -host 10.0.1.182 -p 6379 --client
ID    Address            Name  Age    Command         User     Qbuf       Omem       Total Memory   
----------------------------------------------------------------------------------------------------
216   10.0.1.75:37811          721    psync           default  0B         232.83M    232.85M        
217   10.0.1.22:35057          715    psync           default  0B         232.11M    232.13M        
453   10.0.0.198:51172         0      client          default  26B        0B         60.03K         
...  

其中,

  • Qbuf:輸入緩衝區的大小。
  • Omem:輸出緩衝區的大小。
  • Total Memory:該連接占用的總記憶體。

結果按 Total Memory 從大到小的順序輸出。


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

-Advertisement-
Play Games
更多相關文章
  • ladp服務搭建 用戶名:cn=admin,dc=test,dc=com 密碼:123456 1)軟體安裝yum install openldap openldap-clients openldap-servers -y2)配置 OpenLDAP Servervim /etc/openldap/sl ...
  • liwen01 2024.09.16 前言 802.11 無線 WiFi 網有三類幀:數據幀、管理幀、控制幀。與有線網相比,無線 WiFi 網會複雜很多。大部分應用軟體開發對 WiFi 的控制幀和管理幀瞭解得並不多,因為它們在物理層和數據鏈路層就已經被處理了,上層應用很少能感知到。 一般是在設備出現 ...
  • 寫在前面 本隨筆是非常菜的菜雞寫的。如有問題請及時提出。 可以聯繫:[email protected] GitHhub:https://github.com/WindDevil (目前啥也沒有 引言 兜兜轉轉又是新的一章的開始,還是首先要看官方手冊里的理論介紹和內容. 這裡主要還是提綱挈領地摘抄裡面 ...
  • 金葫蘆STM32L431上手流程教材書名和開發板教材:《嵌入式技術基礎與實踐(第6版)》(王宜懷主編)開發板:AHL-STM32L431金葫蘆STM32L431上手流程1、需要用到的軟體和電子資源 AHL-GEC-IDE(4.55)————>AHL-GEC-IDE (suda.edu.cn)AHL- ...
  • 筆者出於學習(折騰)原因想要改換 Linux 發行版,於是將目光投向大名鼎鼎的 ArchLinux。 ArchLinux 的安裝過程漫長且複雜,遂彙總成小記,以備日後參考。 小記中筆者共使用兩塊硬碟,一塊已含有 Windows10 分區,另一塊作為雙系統數據共用盤。 LiveCD 本小記略過燒錄鏡像 ...
  • 出乎意料的現象 我們有一張測試表 t1,表中有一些數據,當 session1 開啟一個事務,並執行了 select for update 操作後仍未提交事務,在併發事務(如 session2)開啟事務並行執行一些操作會有不同的鎖現象,表現在: select for update 會出現鎖等待 del ...
  • Linux平臺安裝Oracle 19c的時候遇到了下麵錯誤“[INS-35180] Unable to check for available memory”,如圖所示: 具體的錯誤信息如下所示: Additional Information:Exception details - PRVG-190 ...
  • SQL Server的Descending Indexes降序索引 背景索引是關係型資料庫中優化查詢性能的重要手段之一。對於需要處理大量數據的場景,合理的索引策略能夠顯著減少查詢時間。 特別是在涉及多欄位排序的複雜查詢中,選擇合適的索引類型(如降序索引)顯得尤為重要。本文將探討如何在SQL Serv ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...