KeeWiDB的高性能修煉之路:架構篇

来源:https://www.cnblogs.com/tencentdb/archive/2022/11/09/16873932.html
-Advertisement-
Play Games

數據也有冷熱之分,你知道嗎? 根據訪問的頻率的高低可將數據分為熱數據和冷數據,訪問頻率高的則為熱數據,低為冷數據。如果熱、冷數據不區分,一併存儲,顯然不科學。將冷數據也存儲在昂貴的記憶體中,那麼你想,成本得多高呢? 有趣的是,根據我們實際的觀察,目前很多使用 Redis 的業務就是這樣操作的。 得益於 ...


數據也有冷熱之分,你知道嗎?

根據訪問的頻率的高低可將數據分為熱數據和冷數據,訪問頻率高的則為熱數據,低為冷數據。如果熱、冷數據不區分,一併存儲,顯然不科學。將冷數據也存儲在昂貴的記憶體中,那麼你想,成本得多高呢?

有趣的是,根據我們實際的觀察,目前很多使用 Redis 的業務就是這樣操作的。

得益於高性能以及豐富的數據結構命令,Redis 成為目前最受歡迎的 KV 記憶體資料庫。但隨著業務數據量的爆炸增長,Redis 的記憶體消耗也會隨之爆炸。無論客戶是自建伺服器還是雲伺服器,記憶體都是一個必須考慮的成本問題,它不僅貴還要持續購買。

此外 Redis 雖然提供了 AOF 和 RDB 兩種方案來實現數據的持久化,但是使用不當可能會對性能造成影響甚至引發丟數據的問題。

好在,隨著科技的發展,持久化硬體的發展速度也在提升,持久記憶體的出現進一步縮小了與記憶體的性能差距。或許,合理利用新型持久化技術會成為一個好的成本解決方案

基於這一思路,為解決 Redis 可能帶來的記憶體成本、容量限制以及持久化等一系列問題,騰訊雲資料庫團隊推出了新一代分散式KV存儲資料庫 KeeWiDB。本文將詳細介紹KeeWiDB 的架構設計思路、實現路徑及成效。先簡單總結一下 KeeWiDB 的特性:

  • 友好:完全相容 Redis 協議,原先使用 Redis 的業務無需修改任何代碼便可以遷移到 KeeWiDB 上;
  • 高性能低延遲:通過創新性的分級存儲架構設計,單節點讀寫能力超過 18 萬 QPS,訪問延遲達到毫秒級;
  • 更低的成本:內核自動區分冷熱數據,冷數據存儲在相對低價的 SSD 上;
  • 更大的容量:節點支持 TB 級別的數據存儲,集群支持 PB 級別的數據存儲;
  • 保證了事務的 ACID (原子性 Atomicity、一致性 Consitency、隔離性 Isolation、持久性 Durability)四大特性;

一、整體架構

KeeWiDB 的架構由代理層和服務層兩個部分構成:
代理層:由多個無狀態的Proxy節點組成,主要功能是負責與客戶端進行交互;
服務層:由多個Server節點組成的集群,負責數據的存儲以及在機器發生故障時可以自動進行故障切換。

file

圖:KeeWiDB整體架構圖

代理層

客戶端通過 Proxy 連接來進行訪問,由於 Proxy 內部維護了後端集群的路由信息,所以 Proxy 可以將客戶端的請求轉發到正確的節點進行處理,從而客戶端無需關心集群的路由變化,用戶可以像使用 Redis 單機版一樣來使用 KeeWiDB。

Proxy 的引入,還帶來了諸多優勢:

  • 客戶端直接和 Proxy 進行交互,後端集群在擴縮容場景不會影響客戶端請求
  • Proxy 內部有自己的連接池和後端 KeeWiDB 進行交互,可大大減少 KeeWiDB 上的連接數量,同時有效避免業務短連接場景下反覆建連斷連對內核造成性能的影響
  • 支持讀寫分離,針對讀多寫少的場景,通過添加副本數量可以有效分攤 KeeWiDB 的訪問壓力
  • 支持命令攔截和審計功能,針對高危命令進行攔截和日誌審計,大幅度提高系統的安全性
  • 由於 Proxy 是無狀態的,負載較高場景下可以通過增加 Proxy 數量來緩解壓力,此外我們的 Proxy 支持熱升級功能,後續 Proxy 添加了新功能或者性能優化,存量 KeeWiDB 實例的 Proxy 都可以進行對客戶端無感知的平滑升級

file

圖:Proxy上的功能

服務層

KeeWiDB 的後端採用了集群的架構,這是因為集群具有高可用、可擴展性、分散式、容錯等優質特性;同時,在具體的實現上參考了 Redis 的集群模式。KeeWiDB 集群同樣由若幹個分片構成,而每個分片上又存在若幹個節點,由這些節點共同組成一主多從的高可用架構;此外每個分片的主節點負責集群中部分 Slot 的數據,並且可以通過動態修改主節點負責 Slot 區間的形式來實現橫向的擴縮容,為客戶提供了容量可彈性伸縮的能力。

二、Server內部模型

在 Server 內部存在一個集群管理模塊,該模塊通過 Gossip 協議與集群中的其他節點進行通信,獲取集群的最新狀態信息;另外 Server 內部存在多個工作線程,KeeWiDB 會將當前 Server 負責的 Slot 區間按一定的規則劃分給各個工作線程進行處理, 並且每個工作線程都有自己獨立的連接管理器,事務模塊以及存儲引擎等重要組件,線程之間不存在資源共用,做到了進程內部的 Shared-Nothing。正是這種 Shared-Nothing 的體繫結構,減少了 KeeWiDB 進程內部線程之間由於競爭資源的等待時間,獲得了良好並行處理能力以及可擴展的性能。

file
圖:Server內部模塊

線程模型

KeeWiDB 的設計目的,就是為瞭解決 Redis 的痛點問題。所以大容量,高性能以及低延遲是 KeeWiDB 追求的目標。

和數據都存放在記憶體中的 Redis 不同,KeeWiDB 的數據是存儲在 PMem(Persistent Memory)和相對低價的 SSD 上。在用戶執行讀寫訪問請求期間 KeeWiDB 都有可能會涉及到跟硬碟的交互,所以如果還像 Redis 一樣採用單進程單線程方案的話,單節點的性能肯定會大打折扣。因此,KeeWiDB 採取了單進程多線程的方案,一方面可以更好的利用整機資源來提升單節點的性能,另一方面也能降低運維門檻

多線程方案引入的核心思想是通過提高並行度來提升單節點的吞吐量,但是在處理用戶寫請求期間可能會涉及到不同線程操作同一份共用資源的情況,比如存儲引擎內部為了保證事務的原子性和持久性需要寫 WAL,主從之間進行同步需要寫 Binlog,這些日誌文件在寫入的過程中通常會涉及到持久化操作,相對較慢。雖然我們也採取了一系列的優化措施,例如使用組提交策略來降低持久化的頻率,但是優化效果有限。

同時為了保證線程安全,在這類日誌的寫入期間通常都要進行加鎖,這樣一來,一方面雖然上層可以多線程並行的處理用戶請求,但是到了寫日誌期間卻退化成了串列執行;另一方面,申請和釋放鎖通常會涉及到用戶態和內核態的切換,頻繁的申請釋放操作會給 CPU 帶來額外的開銷,顯然會導致性能問題。

file
圖:線程模型

正是由於進程內不同線程訪問同一份共用資源需要加鎖,而大量的鎖衝突無法將多線程的性能發揮到極致,所以我們將節點內部負責的 Slot 區間進行進一步的拆分,每個工作線程負責特定一組 Slot 子區間的讀寫請求,互不衝突;此外每個工作線程都擁有自己獨立的事務模塊以及存儲引擎等重要組件,不再跨線程共用

通過對共用資源進行線程級別的拆分,各個線程在處理用戶請求時都可以快速的獲得所需要的資源,不發生等待事件,這無論是對單個請求延遲的降低還是多個請求併發的提升,都有巨大的好處;此外由於處理用戶請求所需的資源都線上程內部,KeeWiDB 無需再為了線程安全而上鎖,有效規避了由於頻繁上鎖帶來的額外性能開銷。

引入協程

通過上面的章節介紹,KeeWiDB 通過進程內部 Shared-Nothing 的體繫結構減少了線程之間由於競爭共用資源花費的等待時間,提升了進程內部的併發度。此時我們再將視角轉移到線程內部,在業務高峰時期工作線程也需要負責處理大量的客戶端請求,由於每次請求操作都有可能會涉及到和磁碟的交互,此時如果再採用同步 IO 的形式和磁碟進行交互的話,由於一個客戶端請求執行的 I/O 操作就會阻塞當前線程,此時後面所有的客戶端請求需要排隊等待處理, 顯然併發度會大打折扣。

這時候也許有讀者會提出按照 KeeWiDB 目前這套進程內部 Shared-Nothing 的體繫結構,線程之間不存在共用資源競爭了,是不是可以通過增加線程數來緩解這個問題?想法不錯,但是大量的線程引入可能會帶來另外一些問題:

  • 開啟過多的線程會耗費大量的系統資源, 包括記憶體;
  • 線程的上下文切換涉及到用戶空間和內核空間的切換, 大量線程的上下文切換同樣會給 CPU 帶來額外的開銷;

為了讓單個線程的性能能夠發揮到極致,不把時間浪費在等待磁碟 I/O 上,KeeWiDB 首先考慮的是採取非同步 I/O 的方案(應用層觸發 I/O 操作後立即返回,線程可以繼續處理其他事件,而當 I/O 操作已經完成的時候會得到 I/O 完成的通知)。

很明顯,使用非同步 I/O 來編寫程式性能會遠遠高於同步 I/O,但是非同步 I/O 的缺點是編程模型複雜。我們常規的編碼方式是自上而下的,但是非同步 I/O 編程模型大多採用非同步回調的方式。

隨著項目工程的複雜度增加,由於採用非同步回調編寫的代碼和常規編碼思維相悖,尤其是回調嵌套多層的時候,不僅開發維護成本指數級上升,出錯的幾率以及排查問題的難度也大幅度增加。

正是由於非同步 I/O 編程模型有上面提到的種種缺點, 我們經過一系列調研工作之後,決定引入協程來解決我們的痛點, 下麵先來看一下cppreference中對協程的描述:

A coroutine is a function that can suspend execution to be resumed later. Coroutines are stackless: they suspend execution by returning to the caller and the data that is required to resume execution is stored separately from the stack. This allows for sequential code that executes asynchronously (e.g. to handle non-blocking I/O without explicit callbacks).

fromhttps://en.cppreference.com/w/cpp/language/coroutines

協程實際上就是一個可以掛起(suspend)恢復(resume)的函數,我們可以暫停協程的執行,去做其他事情,然後在適當的時候恢復到之前暫停的位置繼續執行接下來的邏輯。總而言之,協程可以讓我們使用同步方式編寫非同步代碼

file
圖:協程切換示意圖

KeeWiDB 為每一個客戶端連接都創建了一個協程,以上圖為例,在工作線程內服務三個客戶端連接,就創建三個協程。在[T0,T1)階段協程0正在執行邏輯代碼,但是到了T1時刻協程0發現需要執行磁碟 I/O 操作獲取數據,於是讓出執行權並且等待 I/O 操作完成,此時協程2獲取到執行權,並且在[T1,T2)時間段內執行邏輯代碼,到了T2時刻協程2讓出執行權,並且此時協程0的 I/O 事件正好完成了,於是執行權又回到協程0手中繼續執行。

可以看得出來,通過引入協程,我們有效解決了由於同步 I/O 操作導致線程阻塞的問題,使線程儘可能的繁忙起來,提高了線程內的併發;另外由於協程切換隻涉及基本的 CPU 上下文切換,並且完全在用戶空間進行,而線程切換涉及特權模式切換,需要在內核空間完成,所以協程切換比線程切換開銷更小,性能更優

三、數據存儲

在文章開頭有提到在持久記憶體出現後,進一步縮小了與記憶體的性能差距,持久記憶體是一種新的存儲技術,它結合了 DRAM 的性能和位元組定址能力以及像 SSD 等傳統存儲設備的可持久化特性,正是這些特性使得持久記憶體非常有前景,並且也非常適合用於資料庫系統。

file
圖:Dram和PMem以及SSD的性能比較

通過上圖可以看到,PMem(Persistent Memory)相對於 DRAM 有著更大的容量,但是相對於 SSD 有著更大的帶寬和更低的讀寫延遲,正是因為如此,它非常適合存儲引擎中的大容量Cache和高性能 WAL 日誌。

前面有提到過日誌文件在寫入的過程中涉及到的持久化操作有可能會成為整個系統的瓶頸,我們通過將 WAL 存放在 PMem 上,日誌持久化操作耗時大幅降低,提升了服務整體的性能;此外由於 PMem 的讀寫速度比 SSD 要快1~2個數量級,在故障恢復期間,回放 WAL 的時間也大幅度的縮短,整個系統的可用性得到了大幅度的提升。

考慮到 KeeWiDB 作為高性能低延遲的資料庫,我們不僅需要做到平均延遲低,更要做到長尾延遲可控。

雖然在涉及到文件操作的場景下,利用 Page Cache 技術能夠大幅提升文件的讀寫速度,但是由於 Page Cache 預設由操作系統調度分配,存在一定的不確定性(內核總是積極地將所有空閑記憶體都用作 Page Cache 和 Buffer Cache,當記憶體不夠用時就會使用 LRU 等演算法淘汰緩存頁, 此時有可能造成文件讀寫操作有時延抖動),在一些極端場景下可能會直接影響客戶實例的P99,P100。所以** KeeWiDB 採用了 Direct I/O的方式來繞過操作系統的 Page Cache 並自行維護一份應用層數據的 Cache,讓磁碟的 IO 更加可控**。

使用 Page cache 能夠大大加速文件的讀寫速度,那什麼是頁面緩存(Page Cache)呢?

In computing, a page cache, sometimes also called disk cache, is a transparent cache for the pages originating from a secondary storage device such as a hard disk drive (HDD) or a solid-state drive (SSD). The operating system keeps a page cache in otherwise unused portions of the main memory (RAM), resulting in quicker access to the contents of cached pages and overall performance improvements. A page cache is implemented in kernels with the paging memory management, and is mostly transparent to applications.

fromhttps://en.wikipedia.org/wiki/Page_cache#cite_note-1

和大多數磁碟資料庫一樣,KeeWiDB 將 Page 作為存儲引擎磁碟管理的最小單位,將數據文件內部劃分成若幹個 Page,每個 Page 的大小為 4K,用於存儲用戶數據和一些我們存儲引擎內部的元信息。從大容量低成本的角度出發,KeeWiDB 將數據文件存放在 SSD 上。

file
圖:數據頁的升溫和落冷

此外,得益於 PMem 接近於 DRAM 的讀寫速度以及支持位元組定址的能力,KeeWiDB在 PMem 上實現了存儲引擎的 Cache 模塊,在服務運行期間存放業務熱數據的數據頁會被載入到 PMem 上,KeeWiDB 在處理用戶請求期間不再直接操作 SSD 上的數據頁,而是操作讀寫延遲更低的 PMem,使得 KeeWiDB 的性能以及吞吐量得到了進一步的提升;

同時為了能夠合理高效的利用 PMem 上的空間,KeeWiDB 內部實現了高效的 LRU 淘汰演算法,並且通過非同步刷髒的方式,將 PMem 中長時間沒有訪問的數據頁寫回到 SSD 上的數據文件中。

四、主從同步

文章開頭的架構圖有提到 KeeWiDB 集群由多個分片構成,每個分片內部有多個節點,這些節點共同組成一主多從的高可用架構。和 Redis 類似,用戶的請求會根據 Key 被路由到對應分片的主節點,主節點執行完後再將請求轉化為 Binlog Record 寫入本地的日誌文件並轉發給從節點,從節點通過應用日誌文件完成數據的複製。

在 Redis 的 Replication 實現中,從節點每接收一個請求都立即執行,然後再繼續處理下一個請求,如此往複。依賴其全記憶體的實現,單個請求的執行耗時非常短,從節點的回放相當於是單連接的 pipeline 寫入,其回放速度足以跟上主節點的執行速度。但這種方式卻不適合 KeeWiDB 這樣的存儲型資料庫,主要原因如下:

  • 存儲型資料庫的請求執行過程中涉及到磁碟 IO,單個請求的執行耗時本身就比較長;
  • 主節點同時服務多個客戶端連接,不同連接的請求併發執行,發揮了協程非同步 IO 的優勢,節點整體 QPS 有保障;
  • 主從同步只有一個連接,由於從庫順序回放請求,無法併發,回放的 QPS 遠遠跟不上主節點處理用戶請求的 QPS;

為了提升從節點回放的速度,避免在主庫高負載寫入場景下,出現從庫追不上主庫的問題,KeeWiDB 的 Replication 機製做了以下兩點改進:

  • 在從節點增加 RelayLog 作為中繼,將從節點的命令接收和回放兩個過程拆開,避免回放過程拖慢命令的接收速度;
  • 在主節點記錄 Binlog 的時候增加邏輯時鐘信息,回放的時候根據邏輯時鐘確定依賴關係,將互相之間沒有依賴的命令一起放進回放的協程池,併發完成這批命令的回放,提升從節點整體的回放 QPS;

file
圖:從庫併發回放

所謂的邏輯時鐘,對應到 KeeWiDB 的具體實現里,就是我們在每一條 BinLog record 中添加了 seqnumparent 兩個欄位:

  • seqnum 是主節點事務 commit 的序列號,每次有新的事務 commit,當前 seqnum 賦給當前事務,全局 seqnum 自增1;
  • parent 由主節點在每個事務開始執行前的 prepare 階段獲取,記錄此時已經 commit 的最大 seqnum,記為 max,說明當前事務是在 max 對應事務 commit 之後才開始執行,二者在主節點端有邏輯上的先後關係;

從節點回放 RelayLog 中的 Binlog Record 時,我們只需要簡單地將它的 parent 和 seqnum 看作一個區間,簡記為(P,S),如果它的(P,S)區間和當前正在回放的其它Record 的(P,S)區間有交集,說明他們在主節點端 Prepare 階段沒有衝突,可以把這條 Record 放進去一塊併發地回放,反之,則這條 Record 需要阻塞等待,等待當前正在回放的這批 Binlog Record 全部結束後再繼續。

通過在 Binlog 中添加 seqnum 和 parent 兩個欄位,我們在保證數據正確性的前提下實現了從庫的併發回放,確保了主庫在高負載寫入場景下,從庫依舊可以輕鬆的追上主庫,為我們整個系統的高可用提供了保障。

五、總結

本篇文章先從整體架構介紹了 KeeWiDB 的各個組件,然後深入 Server 內部分析了線上程模型選擇時的一些思考以及面臨的挑戰,最後介紹了存儲引擎層面的數據文件以及相關日誌在不同存儲介質上的分佈情況,以及 KeeWiDB 是如何解決從庫回放 Binlog 低效的問題。通過本文,相信不少讀者對 KeeWiDB 又有了進一步的瞭解。那麼,在接下來的文章中我們還會深入到 KeeWiDB 自研存儲引擎內部,向讀者介紹 KeeWiDB 在存儲引擎層面如何實現高效的數據存儲和索引,敬請期待。

目前,KeeWiDB 正在公測階段(鏈接:https://cloud.tencent.com/product/keewidb ),現已在內外部已經接下了不少業務,其中不乏有一些超大規模以及百萬 QPS 級的業務,線上服務均穩定運行中。

後臺回覆“KeeWiDB”,試試看,有驚喜。

關於作者
吳顯堅,騰訊雲資料庫高級工程師。負責過開源項目Pika的核心研發工作,對資料庫、分散式存儲有一定瞭解,現從事騰訊雲Redis內核以及KeeWiDB的研發工作。


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

-Advertisement-
Play Games
更多相關文章
  • 一:背景 1.講故事 這篇文章起源於昨天的一位朋友發給我的dump文件,說它的程式出現了卡死,看了下程式的主線程棧,居然又碰到了 OnUserPreferenceChanged 導致的掛死問題,真的是經典中的經典,線程棧如下: 0:000:x86> !clrstack OS Thread Id: 0 ...
  • Red Giant PluralEyes for Mac雖然只是Shooter Suite其中的一部分,但是卻十分受歡迎,功能也非常強大。PluralEyes Mac版提供了用戶需要的音頻和視頻同步的一切功能,可以自動分析視頻和音頻文件,並同步起來。 詳情:Red Giant PluralEyes ...
  • PasteNow mac是一款強大的剪貼板工具,該軟體使你的日常工作更輕鬆和快捷。你可以通過它存儲各種各樣的臨時數據:文本、鏈接、圖像等等,功能非常的豐富。 詳情:PasteNow for Mac 功能特色 - 在多台設備同步數據 - 創建智能列表以更方便地過濾數據 - 支持各種風格不同的列表樣式 ...
  • EdgeView 3是一款運行在Mac系統上的圖片查看器,不僅可以打開JPEG、PNG、TIFF、BMP、DSlr、Eps、PDF、AI(Adobe illustrator)的RAW文件等各種圖像文件,還可以直接打開存檔中的圖像文件,無需提取。 詳情:EdgeView 3 for Mac 亮點特征: ...
  • TEE學習(一) OP-TEE OP-TEE CONCEPT OP-TEE(open source project Trusted Execution Environment),REE中的系統和應用無法直接訪問TEE中的資源,只能通過TEE提供的介面獲取一個結果。 main design goals ...
  • MergeTree擁有主鍵,但是它的主鍵卻沒有唯一鍵的約束。這意味著即便多行數據的主鍵相同,它們還是能夠被正常寫入。在某些使用場合,用戶並不希望數據表中含有重覆的數據。ReplacingMergeTree就是在這種背景下為了數據去重而設計的,它能夠在合併分區時刪除重覆的數據。但是ReplacingM ...
  • 課件獲取:關註公眾號 “數棧研習社”,後臺私信 “ChengYing” 獲得直播課件 視頻回放:點擊這裡 ChengYing 開源項目地址:github 丨 gitee 喜歡我們的項目給我們點個__ STAR!STAR!!STAR!!!(重要的事情說三遍)__ 技術交流釘釘 qun:30537511 ...
  • 不知不覺間,2022年的腳步已經走到了倒數第二個月。臨近年末,我們對產品本身以及客戶反饋的一些問題進行了持續的更新和優化,例如基線告警、數據服務平臺新增TDengine 數據源支持、行級許可權根據用戶屬性實現動態賦權。 以下為袋鼠雲產品功能更新報告第二期內容,更多探索,請繼續閱讀。 數棧DTinsig ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...