服務器裝liunx系統 Linux服務器配置與管理



文章插圖
服務器裝liunx系統 Linux服務器配置與管理

文章插圖
這篇文章 , 我們循序漸進 , 從內存、磁盤I/O、網絡I/O、CPU、緩存、架構、算法等多層次遞進 , 串聯起高性能開發十大必須掌握的核心技術 。
– I/O優化:零拷貝技術– I/O優化:多路復用技術– 線程池技術– 無鎖編程技術– 進程間通信技術– RPC && 序列化技術– 數據庫索引技術– 緩存技術 && 布隆過濾器– 全文搜索技術– 負載均衡技術
準備好了嗎 , 坐穩了 , 發車!
首先 , 我們從最簡單的模型開始 。
老板告訴你 , 開發一個靜態web服務器 , 把磁盤文件(網頁、圖片)通過網絡發出去 , 怎么做?
你花了兩天時間 , 擼了一個1.0版本:
主線程進入一個循環 , 等待連接來一個連接就啟動一個工作線程來處理工作線程中 , 等待對方請求 , 然后從磁盤讀文件、往套接口發送數據 , 完事兒
上線一天 , 老板發現太慢了 , 大一點的圖片加載都有卡頓感 。讓你優化 , 這個時候 , 你需要:
I/O優化:零拷貝技術
上面的工作線程 , 從磁盤讀文件、再通過網絡發送數據 , 數據從磁盤到網絡 , 兜兜轉轉需要拷貝四次 , 其中CPU親自搬運都需要兩次 。
零拷貝技術 , 解放CPU , 文件數據直接從內核發送出去 , 無需再拷貝到應用程序緩沖區 , 白白浪費資源 。
Linux API:
ssize_t sendfile(int out_fd,int in_fd,off_t *offset,size_t count);函數名字已經把函數的功能解釋的很明顯了:發送文件 。指定要發送的文件描述符和網絡套接字描述符 , 一個函數搞定!
用上了零拷貝技術后開發了2.0版本 , 圖片加載速度明顯有了提升 。不過老板發現同時訪問的人變多了以后 , 又變慢了 , 又讓你繼續優化 。這個時候 , 你需要:
I/O優化:多路復用技術
前面的版本中 , 每個線程都要阻塞在recv等待對方的請求 , 這來訪問的人多了 , 線程開的就多了 , 大量線程都在阻塞 , 系統運轉速度也隨之下降 。
這個時候 , 你需要多路復用技術 , 使用select模型 , 將所有等待(accept、recv)都放在主線程里 , 工作線程不需要再等待 。
過了一段時間之后 , 網站訪問的人越來越多了 , 就連select也開始有點應接不暇 , 老板繼續讓你優化性能 。
這個時候 , 你需要升級多路復用模型為epoll 。
select有三弊 , epoll有三優 。
select底層采用數組來管理套接字描述符 , 同時管理的數量有上限 , 一般不超過幾千個 , epoll使用樹和鏈表來管理 , 同時管理數量可以很大 。select不會告訴你到底哪個套接字來了消息 , 你需要一個個去詢問 。epoll直接告訴你誰來了消息 , 不用輪詢 。select進行系統調用時還需要把套接字列表在用戶空間和內核空間來回拷貝 , 循環中調用select時簡直浪費 。epoll統一在內核管理套接字描述符 , 無需來回拷貝 。
用上了epoll多路復用技術 , 開發了3.0版本 , 你的網站能同時處理很多用戶請求了 。
但是貪心的老板還不滿足 , 不舍得升級硬件服務器 , 卻讓你進一步提高服務器的吞吐量 。你研究后發現 , 之前的方案中 , 工作線程總是用到才創建 , 用完就關閉 , 大量請求來的時候 , 線程不斷創建、關閉、創建、關閉 , 開銷挺大的 。這個時候 , 你需要:
線程池技術
我們可以在程序一開始啟動后就批量啟動一波工作線程 , 而不是在有請求來的時候才去創建 , 使用一個公共的任務隊列 , 請求來臨時 , 向隊列中投遞任務 , 各個工作線程統一從隊列中不斷取出任務來處理 , 這就是線程池技術 。
多線程技術的使用一定程度提升了服務器的并發能力 , 但同時 , 多個線程之間為了數據同步 , 常常需要使用互斥體、信號、條件變量等手段來同步多個線程 。這些重量級的同步手段往往會導致線程在用戶態/內核態多次切換 , 系統調用 , 線程切換都是不小的開銷 。
在線程池技術中 , 提到了一個公共的任務隊列 , 各個工作線程需要從中提取任務進行處理 , 這里就涉及到多個工作線程對這個公共隊列的同步操作 。
【文章福利】需要C/C++ Linux服務器架構師學習資料加群812855908(資料包括C/C++ , Linux , golang技術 , 內核 , Nginx , ZeroMQ , MySQL , Redis , fastdfs , MongoDB , ZK , 流媒體 , CDN , P2P , K8S , Docker , TCP/IP , 協程 , DPDK , ffmpeg等)
有沒有一些輕量級的方案來實現多線程安全的訪問數據呢?這個時候 , 你需要:
無鎖編程技術
多線程并發編程中 , 遇到公共數據時就需要進行線程同步 。而這里的同步又可以分為阻塞型同步和非阻塞型同步 。
阻塞型同步好理解 , 我們常用的互斥體、信號、條件變量等這些操作系統提供的機制都屬于阻塞型同步 , 其本質都是要加“鎖” 。
與之對應的非阻塞型同步就是在無鎖的情況下實現同步 , 目前有三類技術方案:
Wait-freeLock-freeObstruction-free
三類技術方案都是通過一定的算法和技術手段來實現不用阻塞等待而實現同步 , 這其中又以Lock-free最為應用廣泛 。
Lock-free能夠廣泛應用得益于目前主流的CPU都提供了原子級別的read-modify-write原語 , 這就是著名的CAS(Compare-And-Swap)操作 。在Intel x86系列處理器上 , 就是cmpxchg系列指令 。
// 通過CAS操作實現Lock-freedo {...} while(!CAS(ptr , old_data , new_data ))我們常常見到的無鎖隊列、無鎖鏈表、無鎖HashMap等數據結構 , 其無鎖的核心大都來源于此 。在日常開發中 , 恰當的運用無鎖化編程技術 , 可以有效地降低多線程阻塞和切換帶來的額外開銷 , 提升性能 。
服務器上線了一段時間 , 發現服務經常崩潰異常 , 排查發現是工作線程代碼bug , 一崩潰整個服務都不可用了 。于是你決定把工作線程和主線程拆開到不同的進程中 , 工作線程崩潰不能影響整體的服務 。這個時候出現了多進程 , 你需要:
【服務器裝liunx系統 Linux服務器配置與管理】進程間通信技術
提起進程間通信 , 你能想到的是什么?
管道命名管道socket消息隊列信號信號量共享內存
以上各種進程間通信的方式詳細介紹和比較 , 推薦一篇文章淺析進程間通信的幾種方式(含實例源碼) , 這里不再贅述 。
對于本地進程間需要高頻次的大量數據交互 , 首推共享內存這種方案 。
現代操作系統普遍采用了基于虛擬內存的管理方案 , 在這種內存管理方式之下 , 各個進程之間進行了強制隔離 。程序代碼中使用的內存地址均是一個虛擬地址 , 由操作系統的內存管理算法提前分配映射到對應的物理內存頁面 , CPU在執行代碼指令時 , 對訪問到的內存地址再進行實時的轉換翻譯 。
從上圖可以看出 , 不同進程之中 , 雖然是同一個內存地址 , 最終在操作系統和CPU的配合下 , 實際存儲數據的內存頁面卻是不同的 。
而共享內存這種進程間通信方案的核心在于: