【3y】從零單排學Redis【青銅】

来源:https://www.cnblogs.com/Java3y/archive/2018/10/29/9870829.html
-Advertisement-
Play Games

前言 只有光頭才能變強 最近在學Redis,我相信只要是接觸過Java開發的都會聽過Redis這麼一個技術。面試也是非常高頻的一個知識點,之前一直都是處於瞭解階段。秋招過後這段時間是沒有什麼壓力的,所以打算系統學學Redis,這也算是我從零學習Redis的筆記吧。 本文 力求講清每個知識點 ,希望大 ...


前言

只有光頭才能變強

redis

最近在學Redis,我相信只要是接觸過Java開發的都會聽過Redis這麼一個技術。面試也是非常高頻的一個知識點,之前一直都是處於瞭解階段。秋招過後這段時間是沒有什麼壓力的,所以打算系統學學Redis,這也算是我從零學習Redis的筆記吧。

本文力求講清每個知識點,希望大家看完能有所收穫。

一、介紹一下Redis

首先,肯定是去官網看看官方是怎麼介紹Redis的啦。https://redis.io/topics/introduction

如果像我一樣,英語可能不太好的,可能看不太懂。沒事,咱們Chrome瀏覽器可以切換成中文的,中文是我們的母語,肯定沒啥壓力了。Eumm...

讀完之後,發現中文也就那樣了。

一大堆沒見過的技術:lua(Lua腳本)、replication(複製)、Redis Sentinel(哨兵)、Redis Cluster(Redis 集群),當然我們也會有看得懂的技術:transactions(事務)、different levels of on-disk persistence(數據持久化)、LRU eviction(LRU淘汰機制)..

至少官方介紹Redis的第一句應該是可以很容易看懂:"Redis is an open source (BSD licensed),in-memory data structure store, used as a database,cache and message broker."

Redis是一個開源的,基於記憶體的數據結構存儲,可用作於資料庫、緩存、消息中間件。

  • 從官方的解釋上,我們可以知道:Redis是基於記憶體,支持多種數據結構。
  • 從經驗的角度上,我們可以知道:Redis常用作於緩存。

就我個人認為:學習一種新技術,先把握該技術整體的知識(思想),再扣細節,這樣學習起來會比較輕鬆一些。所以我們先以“記憶體”、“數據結構”、“緩存”來對Redis入門。

1.1為什麼要用Redis?

從上面可知:Redis是基於記憶體,常用作於緩存的一種技術,並且Redis存儲的方式是以key-value的形式。

我們可以發現這不就是Java的Map容器所擁有的特性嗎,那為什麼還需要Redis呢?

  • Java實現的Map是本地緩存,如果有多台實例(機器)的話,每個實例都需要各自保存一份緩存,緩存不具有一致性
  • Redis實現的是分散式緩存,如果有多台實例(機器)的話,每個實例都共用一份緩存,緩存具有一致性
  • Java實現的Map不是專業做緩存的,JVM記憶體太大容易掛掉的。一般用做於容器來存儲臨時數據,緩存的數據隨著JVM銷毀而結束。Map所存儲的數據結構,緩存過期機制等等是需要程式員自己手寫的。
  • Redis是專業做緩存的,可以用幾十個G記憶體來做緩存。Redis一般用作於緩存,可以將緩存數據保存在硬碟中,Redis重啟了後可以將其恢復。原生提供豐富的數據結構、緩存過期機制等等簡單好用的功能。

參考資料:

1.2為什麼要用緩存?

如果我們的網站出現了性能問題(訪問時間慢),按經驗來說,一般是由於資料庫撐不住了。因為一般資料庫的讀寫都是要經過磁碟的,而磁碟的速度可以說是相當慢的(相對記憶體來說)

資料庫撐不住了

如果學過Mybaits、Hibernate的同學就可以知道,它們有一級緩存、二級緩存這樣的功能(終究來說還是本地緩存)。目的就是為了:不用每次讀取的時候,都要查一次資料庫

有了緩存之後,我們的訪問就變成這樣了:

有了緩存提高了併發和性能

二、Redis的數據結構

本文不會講述命令的使用方式,具體的如何使用可查詢API。

Redis支持豐富的數據結構,常用的有string、list、hash、set、sortset這幾種。學習這些數據結構是使用Redis的基礎!

"Redis is written in ANSI C"-->Redis由C語言編寫

首先還是得聲明一下,Redis的存儲是以key-value的形式的。Redis中的key一定是字元串,value可以是string、list、hash、set、sortset這幾種常用的。

redis數據結構

但要值得註意的是:Redis並沒有直接使用這些數據結構來實現key-value資料庫,而是基於這些數據結構創建了一個對象系統

  • 簡單來說:Redis使用對象來表示資料庫中的鍵和值。每次我們在Redis資料庫中新創建一個鍵值對時,至少會創建出兩個對象。一個是鍵對象,一個是值對象。

Redis中的每個對象都由一個redisObject結構來表示:


typedef struct redisObject{
    
    // 對象的類型
    unsigned type 4:;

    // 對象的編碼格式
    unsigned encoding:4;

    // 指向底層實現數據結構的指針
    void * ptr;

    //.....


}robj;

數據結構對應的類型與編碼

簡單來說就是Redis對key-value封裝成對象,key是一個對象,value也是一個對象。每個對象都有type(類型)、encoding(編碼)、ptr(指向底層數據結構的指針)來表示。

以值為1006的字元串對象為例

下麵我就來說一下我們Redis常見的數據類型:string、list、hash、set、sortset。它們的底層數據結構究竟是怎麼樣的!

2.1SDS簡單動態字元串

簡單動態字元串(Simple dynamic string,SDS)

Redis中的字元串跟C語言中的字元串,是有點差距的

Redis使用sdshdr結構來表示一個SDS值:


struct sdshdr{

    // 位元組數組,用於保存字元串
    char buf[];

    // 記錄buf數組中已使用的位元組數量,也是字元串的長度
    int len;

    // 記錄buf數組未使用的位元組數量
    int free;
}

例子:

SDS例子

2.1.1使用SDS的好處

SDS與C的字元串表示比較

  1. sdshdr數據結構中用len屬性記錄了字元串的長度。那麼獲取字元串的長度時,時間複雜度只需要O(1)
  2. SDS不會發生溢出的問題,如果修改SDS時,空間不足。先會擴展空間,再進行修改!(內部實現了動態擴展機制)。
  3. SDS可以減少記憶體分配的次數(空間預分配機制)。在擴展空間時,除了分配修改時所必要的空間,還會分配額外的空閑空間(free 屬性)。
  4. SDS是二進位安全的,所有SDS API都會以處理二進位的方式來處理SDS存放在buf數組裡的數據。

2.2鏈表

對於鏈表而言,我們不會陌生的了。在大學期間肯定開過數據結構與演算法課程,鏈表肯定是講過的了。在Java中Linkedxxx容器底層數據結構也是鏈表+[xxx]的。我們來看看Redis中的鏈表是怎麼實現的:

使用listNode結構來表示每個節點:




typedef strcut listNode{

    //前置節點
    strcut listNode  *pre;

    //後置節點
    strcut listNode  *pre;

    //節點的值
    void  *value;

}listNode

使用listNode是可以組成鏈表了,Redis中使用list結構來持有鏈表


typedef struct list{

    //表頭結點
    listNode  *head;

    //表尾節點
    listNode  *tail;

    //鏈表長度
    unsigned long len;

    //節點值複製函數
    void *(*dup) (viod *ptr);

    //節點值釋放函數
    void  (*free) (viod *ptr);

    //節點值對比函數
    int (*match) (void *ptr,void *key);

}list

具體的結構如圖:

2.2.1Redis鏈表的特性

Redis的鏈表有以下特性:

  • 無環雙向鏈表
  • 獲取表頭指針,表尾指針,鏈表節點長度的時間複雜度均為O(1)
  • 鏈表使用void *指針來保存節點值,可以保存各種不同類型的值

2.3哈希表

聲明:《Redis設計與實現》裡邊有“字典”這麼一個概念,我個人認為還是直接叫哈希表比較通俗易懂。從代碼上看:“字典”也是在哈希表基礎上再抽象了一層而已。

在Redis中,key-value的數據結構底層就是哈希表來實現的。對於哈希表來說,我們也並不陌生。在Java中,哈希表實際上就是數組+鏈表的形式來構建的。下麵我們來看看Redis的哈希表是怎麼構建的吧。

在Redis裡邊,哈希表使用dictht結構來定義:

    typedef struct dictht{
        
        //哈希表數組
        dictEntry **table;  
    
        //哈希表大小
        unsigned long size;    
    
        //哈希表大小掩碼,用於計算索引值
        //總是等於size-1
        unsigned long sizemark;     
    
        //哈希表已有節點數量
        unsigned long used;
         
    }dictht

哈希表的數據結構

我們下麵繼續寫看看哈希表的節點是怎麼實現的吧:


    typedef struct dictEntry {
        
        //鍵
        void *key;
    
        //值
        union {
            void *value;
            uint64_tu64;
            int64_ts64;
        }v;    
    
        //指向下個哈希節點,組成鏈表
        struct dictEntry *next;
    
    }dictEntry;

從結構上看,我們可以發現:Redis實現的哈希表和Java中實現的是類似的。只不過Redis多了幾個屬性來記錄常用的值:sizemark(掩碼)、used(已有的節點數量)、size(大小)。

同樣地,Redis為了更好的操作,對哈希表往上再封裝了一層(參考上面的Redis實現鏈表),使用dict結構來表示:


typedef struct dict {

    //類型特定函數
    dictType *type;

    //私有數據
    void *privdata;
  
    //哈希表
    dictht ht[2];

    //rehash索引
    //當rehash不進行時,值為-1
    int rehashidx;  

}dict;


//-----------------------------------

typedef struct dictType{

    //計算哈希值的函數
    unsigned int (*hashFunction)(const void * key);

    //複製鍵的函數
    void *(*keyDup)(void *private, const void *key);
 
    //複製值得函數
    void *(*valDup)(void *private, const void *obj);  

    //對比鍵的函數
    int (*keyCompare)(void *privdata , const void *key1, const void *key2)

    //銷毀鍵的函數
    void (*keyDestructor)(void *private, void *key);
 
    //銷毀值的函數
    void (*valDestructor)(void *private, void *obj);  

}dictType

所以,最後我們可以發現,Redis所實現的哈希表最後的數據結構是這樣子的:

從代碼實現和示例圖上我們可以發現,Redis中有兩個哈希表

  • ht[0]:用於存放真實key-vlaue數據
  • ht[1]:用於擴容(rehash)

Redis中哈希演算法和哈希衝突跟Java實現的差不多,它倆差異就是:

  • Redis哈希衝突時:是將新節點添加在鏈表的表頭
  • JDK1.8後,Java在哈希衝突時:是將新的節點添加到鏈表的表尾

2.3.1rehash的過程

下麵來具體講講Redis是怎麼rehash的,因為我們從上面可以明顯地看到,Redis是專門使用一個哈希表來做rehash的。這跟Java一次性直接rehash是有區別的。

在對哈希表進行擴展或者收縮操作時,reash過程並不是一次性地完成的,而是漸進式地完成的。

Redis在rehash時採取漸進式的原因:數據量如果過大的話,一次性rehash會有龐大的計算量,這很可能導致伺服器一段時間內停止服務

Redis具體是rehash時這麼乾的:

  • (1:在字典中維持一個索引計數器變數rehashidx,並將設置為0,表示rehash開始。
  • (2:在rehash期間每次對字典進行增加、查詢、刪除和更新操作時,除了執行指定命令外;還會將ht[0]中rehashidx索引上的值rehash到ht[1],操作完成後rehashidx+1。
  • (3:字典操作不斷執行,最終在某個時間點,所有的鍵值對完成rehash,這時將rehashidx設置為-1,表示rehash完成
  • (4:在漸進式rehash過程中,字典會同時使用兩個哈希表ht[0]和ht[1],所有的更新、刪除、查找操作也會在兩個哈希表進行。例如要查找一個鍵的話,伺服器會優先查找ht[0],如果不存在,再查找ht[1],諸如此類。此外當執行新增操作時,新的鍵值對一律保存到ht[1],不再對ht[0]進行任何操作,以保證ht[0]的鍵值對數量只減不增,直至變為空表。

2.4跳躍表(shiplist)

跳躍表(shiplist)是實現sortset(有序集合)的底層數據結構之一!

跳躍表可能對於大部分人來說不太常見,之前我在學習的時候發現了一篇不錯的文章講跳躍表的,建議大家先去看完下文再繼續回來閱讀:

Redis的跳躍表實現由zskiplist和zskiplistNode兩個結構組成。其中zskiplist保存跳躍表的信息(表頭,表尾節點,長度),zskiplistNode則表示跳躍表的節點

按照慣例,我們來看看zskiplistNode跳躍表節點的結構是怎麼樣的:


typeof struct zskiplistNode {
        // 後退指針
        struct zskiplistNode *backward;
        // 分值
        double score;
        // 成員對象
        robj *obj;
        // 層
        struct zskiplistLevel {
                // 前進指針
                struct zskiplistNode *forward;
                // 跨度
                unsigned int span;
        } level[];
} zskiplistNode;

zskiplistNode的對象示例圖(帶有不同層高的節點):

帶有不同層高的節點

示例圖如下:

跳躍表節點的示例圖

zskiplist的結構如下:

typeof struct zskiplist {
        // 表頭節點,表尾節點
        struct skiplistNode *header,*tail;
        // 表中節點數量
        unsigned long length;
        // 表中最大層數
        int level;
} zskiplist;

最後我們整個跳躍表的示例圖如下:

跳躍表示例圖

2.5整數集合(intset)

整數集合是set(集合)的底層數據結構之一。當一個set(集合)只包含整數值元素,並且元素的數量不多時,Redis就會採用整數集合(intset)作為set(集合)的底層實現。

整數集合(intset)保證了元素是不會出現重覆的,並且是有序的(從小到大排序),intset的結構是這樣子的:


typeof struct intset {
        // 編碼方式
        unit32_t encoding;
        // 集合包含的元素數量
        unit32_t lenght;
        // 保存元素的數組
        int8_t contents[];
} intset;

intset示例圖:

intset示例圖

說明:雖然intset結構將contents屬性聲明為int8_t類型的數組,但實際上contents數組並不保存任何int8_t類型的值,contents數組的真正類型取決於encoding屬性的值

  • INTSET_ENC_INT16
  • INTSET_ENC_INT32
  • INTSET_ENC_INT64

從編碼格式的名字我們就可以知道,16,32,64編碼對應能存放的數字範圍是不一樣的。16明顯最少,64明顯最大。

如果本來是INTSET_ENC_INT16的編碼,想要存放大於INTSET_ENC_INT16編碼能存放的整數值,此時就得編碼升級(從16升級成32或者64)。步驟如下:

  • 1)根據新元素類型拓展整數集合底層數組的空間併為新元素分配空間。
  • 2)將底層數組現有的所以元素都轉換成與新元素相同的類型,並將類型轉換後的元素放到正確的位上,需要維持底層數組的有序性質不變。
  • 3)將新元素添加到底層數組。

另外一提:只支持升級操作,並不支持降級操作

2.6壓縮列表(ziplist)

壓縮列表(ziplist)是list和hash的底層實現之一。如果list的每個都是小整數值,或者是比較短的字元串,壓縮列表(ziplist)作為list的底層實現。

壓縮列表(ziplist)是Redis為了節約記憶體而開發的,是由一系列的特殊編碼的連續記憶體塊組成的順序性數據結構。

壓縮列表結構圖例如下:

壓縮列表的組成部分

下麵我們看看節點的結構圖:

壓縮列表從表尾節點倒序遍歷,首先指針通過zltail偏移量指向表尾節點,然後通過指向節點記錄的前一個節點的長度依次向前遍歷訪問整個壓縮列表

三、Redis中數據結構的對象

再次看回這張圖,覺不覺得就很好理解了?

數據結構對應的類型與編碼

3.1字元串(stirng)對象

在上面的圖我們知道string類型有三種編碼格式

  • int:整數值,這個整數值可以使用long類型來表示
    • 如果是浮點數,那就用embstr或者raw編碼。具體用哪個就看這個數的長度了
  • embstr:字元串值,這個字元串值的長度小於39位元組
  • raw:字元串值,這個字元串值的長度大於39位元組

embstr和raw的區別

  • raw分配記憶體和釋放記憶體的次數是兩次,embstr是一次
  • embstr編碼的數據保存在一塊連續的記憶體裡面

編碼之間的轉換

  • int類型如果存的不再是一個整數值,則會從int轉成raw
  • embstr是只讀的,在修改的時候回從embstr轉成raw

3.2列表(list)對象

在上面的圖我們知道list類型有兩種編碼格式

  • ziplist:字元串元素的長度都小於64個位元組&&總數量少於512個
  • linkedlist:字元串元素的長度大於64個位元組||總數量大於512個

ziplist編碼的列表結構:

    redis > RPUSH numbers 1 "three" 5
    (integer) 3 

ziplist的列表結構

linkedlist編碼的列表結構:

linkedlist編碼的列表結構

編碼之間的轉換:

  • 原本是ziplist編碼的,如果保存的數據長度太大或者元素數量過多,會轉換成linkedlist編碼的。

3.3哈希(hash)對象

在上面的圖我們知道hash類型有兩種編碼格式

  • ziplist:key和value的字元串長度都小於64位元組&&鍵值對總數量小於512
  • hashtable:key和value的字元串長度大於64位元組||鍵值對總數量大於512

ziplist編碼的哈希結構:

ziplist編碼的哈希結構1
ziplist編碼的哈希結構2

hashtable編碼的哈希結構:

hashtable編碼的哈希結構

編碼之間的轉換:

  • 原本是ziplist編碼的,如果保存的數據長度太大或者元素數量過多,會轉換成hashtable編碼的。

3.4集合(set)對象

在上面的圖我們知道set類型有兩種編碼格式

  • intset:保存的元素全都是整數&&總數量小於512
  • hashtable:保存的元素不是整數||總數量大於512

intset編碼的集合結構:

intset編碼的集合結構

hashtable編碼的集合結構:

hashtable編碼的集合結構

編碼之間的轉換:

  • 原本是intset編碼的,如果保存的數據不是整數值或者元素數量大於512,會轉換成hashtable編碼的。

3.5有序集合(sortset)對象

在上面的圖我們知道set類型有兩種編碼格式

  • ziplist:元素長度小於64&&總數量小於128
  • skiplist:元素長度大於64||總數量大於128

ziplist編碼的有序集合結構:

ziplist編碼的有序集合結構1

ziplist編碼的有序集合結構2

skiplist編碼的有序集合結構:

skiplist編碼的有序集合結構

有序集合(sortset)對象同時採用skiplist和哈希表來實現

  • skiplist能夠達到插入的時間複雜度為O(logn),根據成員查分值的時間複雜度為O(1)

編碼之間的轉換:

  • 原本是ziplist編碼的,如果保存的數據長度大於64或者元素數量大於128,會轉換成skiplist編碼的。

3.6Redis對象一些細節

  • (1:伺服器在執行某些命令的時候,會先檢查給定的鍵的類型能否執行指定的命令。
    • 比如我們的數據結構是sortset,但你使用了list的命令。這是不對的,伺服器會檢查一下我們的數據結構是什麼才會進一步執行命令
  • (2:Redis的對象系統帶有引用計數實現的記憶體回收機制
    • 對象不再被使用的時候,對象所占用的記憶體會釋放掉
  • (3:Redis會共用值為0到9999的字元串對象
  • (4:對象會記錄自己的最後一次被訪問時間,這個時間可以用於計算對象的空轉時間。

最後

本文主要講了一下Redis常用的數據結構,以及這些數據結構的底層設計是怎麼樣的。整體來說不會太難,因為這些數據結構我們在學習的過程中多多少少都接觸過了,《Redis設計與實現》這本書寫得也足夠通俗易懂。

至於我們在使用的時候挑選哪些數據結構作為存儲,可以簡單看看:

  • string-->簡單的key-value
  • list-->有序列表(底層是雙向鏈表)-->可做簡單隊列
  • set-->無序列表(去重)-->提供一系列的交集、並集、差集的命令
  • hash-->哈希表-->存儲結構化數據
  • sortset-->有序集合映射(member-score)-->排行榜

如果大家有更好的理解方式或者文章有錯誤的地方還請大家不吝在評論區留言,大家互相學習交流~~~

參考博客:

參考資料:

  • 《Redis設計與實現》
  • 《Redis實戰》

一個堅持原創的Java技術公眾號:Java3y,歡迎大家關註

原創技術文章導航:


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

-Advertisement-
Play Games
更多相關文章
  • 1. 基礎知識 安裝mysql5.6資料庫Mysql binlog初步理解 2. 配置mysql 開啟binlog、修改binlog模式為Row Level模式 修改mysql配置文件,在[mysqld]下增加以下內容 3. 重啟mysql資料庫 binlog開啟 生成文件/var/lib/mysq ...
  • MySQL · 引擎特性 · InnoDB 崩潰恢復過程 innodb中的 3個lsn innodb的lsn和oracle的scn一樣,是一個重要的概念。比如 在flush list中正是是使用low lsn作為鏈表的條件參考buf_page_t中的lsn_t oldest_modification ...
  • Ref:https://www.aliyun.com/jiaocheng/1109809.html 摘要: 簡介 undrop-for-innodb 是針對 innodb 的一套數據恢復工具,可以從文件級別恢復諸如:DROP/TRUNCATE table, 刪除表中某些記錄,innodb 文件被刪除 ...
  • 原文鏈接:http://click.aliyun.com/m/42521/ 摘要: 本文主要通過一個bug來記錄一下如何分析一個MySQL bug的崩潰信息。 版本:Percona 5.7.17-11 一、資料庫重啟日誌分析 terminate called after throwing an in ...
  • 1.查看資料庫歸檔和閃回狀態,及環境準備SQL> archive log list;SQL> select flashback_on from v$database;關閉資料庫,啟動歸檔和閃回SQL> shutdown immediate;啟動到mount模式SQL> startup mount;開 ...
  • 一. 概述 在上一篇中,搭建了一主一從的複製架構,這篇通過一些診斷方法來瞭解複製的運行狀態和一些選項參數說明。上次mysql主從服務關機,今天在打開mysql服務,出現了錯誤信息。 1.首先 啟動主從mysql服務 2.在從庫上執行START SLAVE, 開始複製。 3.在從庫上執行SHOW PR ...
  • 註意:本地MySQL服務要開啟 更新整個資料庫 1、將正式伺服器上的資料庫做備份 登錄到正式伺服器,執行如下命令:(註意空格) mysqldump -uroot –p密碼 資料庫名 -P 介面 --default-character-set=gbk --opt -Q -R --skip-lock-t ...
  • 介紹 這篇隨筆主要介紹MySQL的基礎API的使用姿勢 基本使用姿勢: 第一步:登陸資料庫 第二步:建立MySqlCommand對象然後進行增刪改查 建立MySqlDataReader對象-讀取查詢結果 使用: 註釋:(為了放置SQL註入,可以使用參數的方式進行傳參,執行上沒有差別) SQL可以查詢 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...