NodeJs 面试题 2023
(要知道對好事的稱頌過于夸大,也會招來人們的反感輕蔑和嫉妒。——培根)
???
???
????????????????
NodeJs相關
-
什么是NodeJs
- Nodejs是一個基于V8虛擬機的JavaScript的運行時平臺,使用了事件驅動和非阻塞IO模型,讓JavaScript能夠像Java,PHP等語言一樣運行在服務端
-
NodeJs優勢
- 開發效率高,js動態語言, 不強制要求類型
- io性能強勁,依靠libuv,不用考慮多線程等復雜操作(線程創建和銷毀,線程鎖,線程管理),降低開發成本
- 有強大的npm包平臺,類似Java的maven
- 前后端語言統一,降低開發成本
-
NodeJs常見應用場景
- web后端開發,目前流行的框架有express,koa,egg,nestjs等
- app后端開發,作為后端提供http等服務接口
- 桌面端開發,比如用nw.js和electron.js框架開發桌面端應用
- web端服務器控制臺后端開發,比如tty.js
- 3D引擎建模和渲染,比如使用Three.js和Grimoire.js制作3D引擎
- 游戲制作,比如使用pomelo.js和Phaser.js等制作網頁游戲
- 日常工作腳本,比如解析表格,統計數據等
-
和JavaScript的區別
- JavaScript只能應用在前端,Nodejs可以讓JavaScript運行在服務端
- JavaScript的全局對象是windows,而Node是Global
- JavaScript因為瀏覽器的差異要考慮兼容性,而Nodejs是服務端,所以不需要考慮兼容性
- Node的事件循環依賴底層libuv,JavaScript的事件循環依賴的是瀏覽器
-
和Java相比的區別
- node是單線程,java是多線程
- node使用js語言,開發成本低,io多線程由libuv提供,省去了操作多線程的成本(比如多線程的創建和銷毀,鎖的管理,設計復雜)
- node是解釋性語言,運行時才進行編譯檢查。而java是半編譯半解釋型語言
- node是弱類型語言,編碼中不強制類型。java是強類型語言,編碼中強制類型
- node使用的是js語言,前后端統一語言,降低開發成本,維護成本,前后端工具包通用,提高團隊整體開發效率
-
NodeJs的優缺點
- 優點
- js語言不強制要求類型,開發效率高
- 前后端語言統一
- 降低團隊的溝通成本,提高工作效率
- 前后端的工具包可以通用
- 全棧開發
- 性能強勁,node底層使用libuv管理多線程,利用異步事件模型來提高IO性能
- 使用libuv不用關心多線程的創建和銷毀,不用自己管理多線程,間接的提高了開發效率
- 多進程提高服務性能,利用work子進程分擔主進程的任務,也能間接的提高算力
- 缺點
- js弱類型,可以動態更改類型,導致后期代碼維護難度升高
- 雖然底層有libuv提高io性能,也有多進程提高并發執行,但對于cpu密集型任務來說,依然是劣勢。即便node10x版本提供了工作線程,但使用和管理起來過于麻煩
- 因為是單線程,所以代碼中任意一處發生的異常都是致命的,需要開發者謹慎處理異常
- 優點
-
NodeJs特性都有哪些
- 事件驅動
- 非阻塞io
- 單線程(但io操作是多線程)
-
NodeJs事件驅動是什么
- 典型的發布訂閱模式,只有當事件發生的時候才會調用回調函數。其中會有一個事件隊列不斷的獲取事件來執行
-
NodeJs事件驅動的優缺點
-
優點
- 適合處理密集型I/O任務:通過事件循環機制,多個并行的任務,在JavaScript處理起來,只是將不同的任務分配給不同的線程,等待返回結果執行即可。所以處理速度超級快,有很高的實時性。
- 適合處理高并發:RESTful Api動輒發起成千上萬條請求,但是請求本身并沒有太多的計算量,開啟多線程處理等待結果又太浪費機器性能。所以事件循環非常適合處理此種場景。
- 適合處理少量業務邏輯:例如瀏覽器中,在處理用戶交互事件,頁面渲染等少量業務邏輯的場景上,具有很好實時性,能給用戶提供很流暢的體驗。
-
缺點
- 不適合cpu密集型應用:因為JavaScript單線程的設計,因此,對于高強度運算的任務,可能會因為運算能力有限,導致任務處理時間過長,影響后續任務執行。
- 解決辦法
- 業務代碼優化,將單個cpu密集型任務拆成多個子任務,留出一定時間間隔執行其他任務
- 利用多進程提高服務處理能力
- 利用工作線程提高計算能力
- 解決辦法
- cpu利用率低:因為單線程的原因,cpu多核性能利用率低。
- 解決辦法
- 利用多進程提高cpu多核利用率
- 解決辦法
- 安全性低:因為單線程的原因,如果主線程發生錯誤,將直接導致應用崩潰。
- 解決辦法
- 添加全局異常過濾器,做默認異常處理
- 做好線上監控,收到反饋時及時處理
- 解決辦法
- 不適合cpu密集型應用:因為JavaScript單線程的設計,因此,對于高強度運算的任務,可能會因為運算能力有限,導致任務處理時間過長,影響后續任務執行。
-
-
NodeJs事件驅動原理
-
事件循環是 Node.js 處理非阻塞 I/O 操作的機制——盡管 JavaScript 是單線程處理的——當有可能的時候,它們會把操作轉移到系統內核中去。
既然目前大多數內核都是多線程的,它們可在后臺處理多種操作。當其中的一個操作完成的時候,內核通知 Node.js 將適合的回調函數添加到 輪詢 隊列中等待時機執行
-
詳情請看NodeJs事件循環原理
-
-
libuv線程池運行原理
-
libuv最初是為nodejs編寫的跨平臺支持庫。它是圍繞事件驅動的異步 I/O 模型設計的
-
libuv 提供了一個線程池,可用于運行用戶代碼并在循環線程中獲得通知。此線程池在內部用于運行所有文件系統操作,以及 getaddrinfo 和 getnameinfo 請求。
它的默認大小是 4,但可以在啟動時通過將 UV_THREADPOOL_SIZE環境變量設置為任何值來更改它,最大支持1024個線程
-
線程池是全局的,并且在所有事件循環中共享。當特定函數使用線程池時(即使用時uv_queue_work()),libuv 會預先分配并初始化 允許的最大線程數 UV_THREADPOOL_SIZE。這會導致相對較小的內存開銷,但會提高運行時線程的性能
-
-
NodeJs如何使用libuv中的線程池
- node通過使用node-gyp將c++代碼編譯成.node文件,然后在js內部引入就可以使用了。
- 通過c++提供的接口,在node里就可以利用libuv驅動來使用系統資源了
-
NodeJs事件驅動和瀏覽器的事件循環區別是什么
-
瀏覽器的事件循環隊列分為macro(宏任務)隊列和 micro(微任務)隊列。宏任務隊列可以有多個
-
當某個宏任務執行完后,會查看是否有微任務隊列。如果有,先執行微任務隊列中的所有任務,如果沒有,會讀取宏任務隊列中排在最前的任務,執行宏任務的過程中,遇到微任務,依次加入微任務隊列。棧空后,再次讀取微任務隊列里的任務,依次類推。
-
比如以下代碼就能夠很明顯的看出瀏覽器事件循環和nodejs事件循環的區別
function test () {console.log('start')setTimeout(() => {console.log('children2')Promise.resolve().then(() => {console.log('children2-1')})}, 0)setTimeout(() => {console.log('children3')Promise.resolve().then(() => {console.log('children3-1')})}, 0)Promise.resolve().then(() => {console.log('children1')})console.log('end') }test()// 以上代碼在node11以下版本的執行結果(先執行所有的宏任務,再執行微任務) // start // end // children1 // children2 // children3 // children2-1 // children3-1// 以上代碼在node11及瀏覽器的執行結果(順序執行宏任務和微任務) // start // end // children1 // children2 // children2-1 // children3 // children3-1
-
-
瀏覽器線程模型
-
chrom線程模型
-
chrom是多進程+多線程的,對于每一個chrom進程,它都有一個主線程用來處理UI和JS代碼,還有一個IO線程用來處理網絡請求。
-
-
NodeJs如何提升性能和代碼質量
- 提高性能
- node自身
- 多用promise并行處理io,或者其他的第三方異步io庫,比如async,bluebird
- 減少同步代碼,多使用異步
- 使用typescript,雖然開發成本提高了,但性能會提升。因為動態類型的代碼,v8虛擬機在運行的時候會反復的執行優化,如果不更改類型,那么就不會觸發v8虛擬機的反優化。也是一個提高性能的手段
- 使用多進程,充分利用CPU的多核機制。提高處理非IO類業務的性能
- 數據庫
- 熱點數據使用緩存設計,比如使用redis進行存儲
- 數據庫分庫分表,減小庫和表的數據大小,提高檢索性能。數據量的減少意味著檢索范圍也會縮小,從而提升了性能
- 分庫(適用于系統全局化處理)
- 比如后臺有定時腳本,每天凌晨遍歷目前所有庫/指定庫的大小,如果某庫的磁盤空間占用大于100g,則按照日期規則,使用日期作為后綴進行分庫,然后將該庫作為新租戶/用戶庫
- 分表(適用于針對某業務模塊或某租戶)
- 比如后臺有定時腳本,每天凌晨遍歷所有庫/指定庫內表的大小,對超過2gb或者3gb的,按照日期規則,使用日期作為后綴進行分表,然后將該表作為新租戶/用戶的表
- 分庫分表后帶來的問題
- 如果設計不當,則導致租戶/用戶的數據分散存儲在不同的庫和表中,導致后期維護困難,所以我們盡量讓他們之間獨立,每個租戶/用戶只使用一個分庫,分庫下的表可以根據大小進行分表,新業務和分表呈一對一的關系。就業務的表不再分表處理,而是做冷熱數據處理
- 分庫(適用于系統全局化處理)
- 數據庫表添加索引,對熱點字段建立索引,提高檢索性能。數據庫性能提升的主要方式之一
- sql優化
- 為熱點數據建立合適的索引,唯一索引,組合索引等
- 使用projection只查詢需要的字段,避免返回多余的字段
- 比如mongodb數據庫,避免使用" n e " , " ne"," ne","exist"," n o t " , " not"," not","nin"等字段,這些字段在查詢時不會使用索引,所以檢索性能較差
- 限制返回的結果數,比如limit。當結果數過多時,會降低性能
- 表字段內容不要過大,比如幾m,幾十m的那種。過大的字段會在保存和查詢的時候降低性能
- 表字段優化,避免因過度設計而導致的冗余字段降低性能,比如加入了未來5-10年的發展中,業務可能需要的字段
- 數據庫集群 以mongodb為例
- 使用副本集,讀寫分離,提高檢索性能
- 當數據量達到千萬,億級別時可以使用分片。副本集雖然讀寫分離,但數據的存儲并沒有分離。mongodb的分片解決了這個問題,相比較副本集,一個副本存儲了所有數據庫,但分片等于一個副本的數據分別存儲到數十個,數百個分片中。查詢的時候,同時從這些分片中進行查詢,將查詢的性能又提高了一個檔次
- 用戶的請求會發送給 mongos 路由服務器, 路由服務器會根據查詢條件去配置服務器查詢對應的數據段和屬于哪個分片服務器, 如果用戶查詢的條件是分片片鍵字段, 那么路由服務器會返回保存在那一臺分片服務器上, 路由服務器就會去對應的分片服務器獲取數據, 并將取到的數據返回給用戶。
- 如果用戶查詢的條件不是分片片鍵字段, 那么配置服務器無法告知路由服務器數據保存在哪一個分片服務器上,路由服務器會把請求發送到所有的分片服務器上, 然后再將查詢到的數據匯總后返回給用戶。
- 冷熱數據分離,根據租戶/用戶的活躍時間,將不活躍用戶(比如最近兩年沒有登陸過)的數據遷移至冷數據庫集群中(副本集較少,配置較低,分片數少的集群),這樣就可以減少主集群的數據量,提高磁盤空間和檢索性能。當不活躍用戶由活躍時,通過后臺腳本或者服務將該用戶的數據再進行回遷即可。
- 架構優化
- 不使用框架,使用原生js進行編碼(對團隊素質和技術能力要求極高)。因為現在的流行框架大多都加入了一些實際業務不需要的代碼,間接的拖慢了服務性能
- 提高硬件配置,比如內存,cpu,帶寬等
- 橫向擴展服務集群,搭配負載均衡策略,提高服務的整體吞吐量和性能
- 使用k8s容器化管理,使用沙箱隔離服務間環境(減少服務間的影響),合理分配每個服務運行環境的pod配置,既提高服務性能還能提高穩定性
- node自身
- 提高質量
- 使用typescript,集成eslint,避免使用any,提前檢查錯誤,減少bug出現率
- 審查代碼,每次代碼版本測試/發布前進行審查,及時對有問題的代碼進行優化和修復
- 核心代碼編寫注釋,方便后期維護和二次開發
- 抽象通用組件,提高代碼復用性,減少冗余代碼
- 編寫測試單元,發版前運行測試單元
- 提高服務高穩定性
- 完善系統監控,比如使用grafana實時監測服務的cpu,內存,硬盤,IO等
- 完善日志分析系統,比如使用業界流行的kibana日志分析平臺
- 完善的日志檢索功能
- 完善的可視化圖標分析
- 完善服務整體高可用性,比如使用k8s生態,rancher,harbor等
- 服務發現和負載均衡
- Kubernetes 可以使用 DNS 名稱或自己的 IP 地址公開容器,如果進入容器的流量很大, Kubernetes 可以負載均衡并分配網絡流量,從而使部署穩定
- 滾動發布
- 每次先關閉一個pod,再重啟一個pod,可以保證服務不中斷的情況下進行系統升級
- 鏡像回滾
- 利用harbor和rancher對鏡像進行管理,可以使用界面或命令進行一鍵回滾
- 自動完成裝箱計算
- Kubernetes 允許你指定每個容器所需 CPU 和內存(RAM)。 當容器指定了資源請求時,Kubernetes 可以做出更好的決策來管理容器的資源
- 自我修復
- Kubernetes 重新啟動失敗的容器、替換容器、殺死不響應用戶定義的 運行狀況檢查的容器
- 密鑰與配置管理
- Kubernetes 允許你存儲和管理敏感信息,例如密碼、OAuth 令牌和 ssh 密鑰。 你可以在不重建容器鏡像的情況下部署和更新密鑰和應用程序配置,也無需在堆棧配置中暴露密鑰
- 更便捷的水平擴展
- 通過rancher等可視化界面,可以快速的對pod進行管理,比如新增,刪除,調整pod的資源等
- 健康檢查
- k8s會對每一個pod做定時的健康檢查,對于非健康的pod,k8s會對它做重啟處理
- 服務發現和負載均衡
- 提高性能
-
常用的排序算法
/*** @event 快速排序* @description * * 快速排序是對冒泡排序的一種改進。它的基本思想是:* 通過一趟排序將要排序的數據分割成獨立的兩部分,* 其中一部分的所有數據都比另外一不部分的所有數據都要小,* 然后再按此方法對這兩部分數據分別進行快速排序,* 整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。* 整個排序過程只需要三步: 1. 找到該數組的基準點(中間數),并創建兩個空數組left和right; 2. 遍歷數組,拿出數組中的每個數和基準點進行比較,如果比基準點小就放到left數組中,如果比基準點大就放到right數組中; 3. 對數組left和right進行遞歸調用*///第一種方式(類似二分法,一直縮小范圍) //先處理leftArr的遞歸,再處理rightArr的遞歸 const arrSortFast = (arr) => {if (arr.length <= 1) {return arr;}const leftArr = [];const rightArr = [];//每次刪除一個數組元素并獲得返回值當作基數,直到數組被刪除到只剩下一個時,直接返回//Math.round 向上取整 3.5取4 3.3取3const baseNumber = arr.splice(Math.round(arr.length / 2), 1)[0];arr.forEach(v => {if (v < baseNumber) {leftArr.push(v);} else {rightArr.push(v);}});const value = arrSortFast(leftArr).concat([baseNumber], arrSortFast(rightArr));return value; };console.log(arrSortFast([2,3,1,4])); /*** 算法解析* 第一階段:* 取出中位數 9* 當前數組為 [5, 3, 6, 10, 2, 4, 7, 1, 8]* 經過左右過濾* 左數組為 [5, 3, 6, 2, 4, 7, 1, 8]* 右數組為 [10]* * 第二階段:使用左數組繼續遞歸* 取出中位數 4* 左數組為 [3, 2, 1]* 右數組為 [5, 6, 7, 8]* * 第二階段繼續:使用左數組繼續遞歸* 取出中位數 2* 左數組為 1* 右數組為3* 由于過濾的只剩下最后一個,所以直接返回* 此時結果為[1, 2, 3]* * 第二階段繼續:使用右數組進行遞歸* 取出中位數7* 左數組為 [5, 6] 超過兩個元素的都會進行再次遞歸 那么此時得到的結果肯定也是[5, 6]* 右數組為 [8]* 此時結果為 [5, 6, 7, 8]* * 此時已經得到過濾后的左右數組分別為[1, 2, 3]和[5, 6, 7, 8],中位數是4* 那么此時結果為[1, 2, 3, 4, 5, 6, 7, 8]* * 當前回到第一階段* [5, 3, 6, 2, 4, 7, 1, 8]過濾后的結果為[1, 2, 3, 4, 5, 6, 7, 8]* 中位數是9 右數組是[10]* 那么此時最終結果為[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]*//*** 冒泡排序法* 相鄰比較*/ ((arr = [4, 3, 5, 4, 1, 3, 2, 0, 3, 88]) => {let length = arr.length;// 單層for循環for (let j = 0; j < length; j++) {if (arr[j] > arr[j + 1]) {let temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}// 在循環到最大值時候重置j(j=-1到上面j++重置為0)這樣可以省了外層for循環if (j == length - 1) {j = -1;length--;}}console.log(arr); })(); /*** 第一階段過濾得到結果* [3, 4, 5, 4, 1, 3, 2, 0, 3, 88, 99]* * 第二階段過濾 此時把長度-1* [3, 4, 5, 1, 3, 2, 0, 3, 4, 88, 99]* * 第三階段過濾* [3, 4, 1, 3, 2, 0, 3, 4, 5, 88, 99]* * 第四階段過濾* [3, 1, 3, 2, 0, 3, 4, 4, 5, 88, 99]* * 以此類推 一直比較 得到結果*//*** 選擇排序* 選擇某一個元素當作最小數依次遍歷* 每次找到最小值和前面的位置進行替換*/ ((arr = [8,9,7,6,4,7]) => {const len = arr.length;let minIndex;let temp;for (let i = 0; i < len; i++) {minIndex = i; //選擇當前下標為最小元素for (let j = i + 1; j < len; j++) {if (arr[minIndex] > arr[j]) { //每一次從i+1個開始遍歷找出比自身小的元素minIndex = j; //將最小數的索引保存}}//接下來的操作類似冒泡,都是互換元素位置temp = arr[i];arr[i] = arr[minIndex];arr[minIndex] = temp;}console.log(arr); })(); -
Http七層協議工作原理,都有哪些狀態碼,分別是什么意思
-
為什么需要協議?
- 如果蘋果,三星,華為都是自己的協議,那么就只能和自己通信,所以就弄一個標準化的協議,所有人來實現,這樣所有人就能互通了
-
四層協議關系
- 應用層:http協議,郵件服務協議
- 傳輸層:TCP協議
- SYN 建立連接
- FIN 關閉連接
- ACK 響應連接
- PSH DATA數據傳輸
- RST 連接重置
- 網絡層:IP協議
- 數據鏈路層:以太網協議
-
端口號范圍0-65535,1024之前不允許用
-
物理層
- 過去:通過家里的貓來連接網線實現電腦間的通信,這個就屬于物理層
- 現在:連接無線網,屬于無線訊號,也是物理層
- 連接上之后傳遞010101的電路信號
-
數據鏈路層
- 是給電路信號分配規則的,過去每個公司都有自己的電路信號規則。
- 后來出現了以太網協議,將電信號歸納成一個數據包,也叫一個幀。
- 每一幀都分成兩部分,標頭head和數據data
- 標頭head包含了一些說明信息,比如發送者mac地址,接收者mac地址和數據類型等
- 而電路信號的通信又依賴網卡,利用電腦間的網卡進行通信
- 以太網協議規定網卡包含mac地址,mac地址是網卡的唯一標識,全球唯一
- 12個16進制的數字表示mac地址
- 前6個16進制數字是廠商編號
- 后6個16進制數字是網卡流水號
- windows使用ipconfig命令查看mac地址 物理地址 比如7C-67-A2-20-AB-5C
- linux使用ifconfig -a命令查看mac地址
- 所以以太網傳遞數據包就必須指定接收者的mac地址才能進行傳輸
- 以太網協議如何根據網卡進行通信的?
- 局域網情況下,也叫子網
- 在以太網內一個數據包的發送,會先廣播給局域網的所有電腦設備網卡,然后每臺電腦都從數據包中取出接收者的mac地址和自己的mac地址進行對比,如果是一樣的,則說明是給自己發送的數據包才進行處理,否則就丟棄數據包
- 如何知道哪些電腦在當前局域網內呢?
- 這就需要依靠網絡層來支持了
- 請看下面的網絡層
- 這就需要依靠網絡層來支持了
- 局域網情況下,也叫子網
- 以太網協議規定網卡包含mac地址,mac地址是網卡的唯一標識,全球唯一
-
網絡層
- 網絡層里有ip協議,ip協議定以的地址就是ip地址。ip地址理由ipv4和ipv6兩種類型,目前廣泛使用的是ipv4,是由32個二進制數字組成,但一般由4個十進制數字來表示,范圍是0.0.0.0到255.255.255.255
- 每臺電腦都有一個ip地址,前24位(前3位十進制數字)代表網絡,后8位(最后1個十進制數字)代表了主機,如果幾臺電腦是一個子網的,那么前3位十進制的數字就是一樣的
- 比如開虛擬機或者連接的都是同一個網線,無線網,則電腦的ip地址分別是192.168.0.180,192.168.0.181,192.168.0.182,192.168.0.183,可以看出192.168.0這三位數字是一樣的就證明大家是一個子網的,最后一個數字就是主機編號
- 但上面的描述不是百分百準備,需要將子網掩碼進行二進制運算才可以真正對比出是不是屬于同一個子網內
- 比如192.168.0.180和192.168.0.181 通過二進制運算后的結果分別式
- 11000000.10101000.0.10110100
- 11000000.10101000.0.10110101
- 然后判斷前三位 如果相同則證明是一個子網
- 最后一位表示主機編號
- 所以通過子網掩碼就可以確定局域網下的電腦,然后就可以互相通信了
- 但如果不在一個子網內?是如何通信呢
- 所以這就需要路由器
- 路由器 也成為網關
- 可以把多個子網給串聯起來
- 根據以太坊協議,網卡間只能在子網內進行通信,但要和其他網卡進行通信,就可以把包發給交換機,交換機再把包進行廣播,路由器(路由器也有mac地址)上的網卡收到后判斷是不是自己的,是自己的話,再通過交換用以太坊協議進行廣播分發
- 所以以太網要和多個子網進行通訊,就需要交換機和路由器
- ARP協議
- 一個局域網內的機器會互相給對方發送自己的mac地址,所以當一個機器要發送數據包時,就可以知道其他電腦的mac地址了
- 交換機,一種工作在數據鏈路成的設備,網關是工作在網絡層
- 總結:子網間的機器如何通信,在數據包中寫上對方的mac地址,通過交換機用以太坊協議進行廣播。如果是跨子網通信,在包里寫上目標和路由器的mac地址,路由器再根據目標地址通過交換機進行廣播,一直持續找到最終mac地址為止
- LAN 局域網
- WAN 廣域網
- WLAN 無線局域網
- 但同一個電腦下運行著qq,微信,視頻等軟件,怎么區分數據包是發給這個ip下的哪一個服務呢?那么就需要有一個端口的概念來進行區分,所以這也是為什么端口要唯一的原因。
- 那么如何將數據包發到某一個ip的某一個端口上?這就需要TCP協議了
-
傳輸層
-
TCP協議簡述
-
存在長連接和短連接
-
短鏈接
-
TCP短連接:client向server發起連接請求,server接到請求,然后雙方建立連接。client向server發送消息,server回應client,然后一次請求就完成了。這時候雙方任意都可以發起close操作,不過一般都是client先發起close操作。上述可知,短連接一般只會在 client/server間傳遞一次請求操作。
短連接的優點是:管理起來比較簡單,存在的連接都是有用的連接,不需要額外的控制手段
-
-
長連接 keepalive
- TCP長連接的情況:client向server發起連接,server接受client連接,雙方建立連接,client與server完成一次請求后,它們之間的連接并不會主動關閉,后續的讀寫操作會繼續使用這個連接。
- 如果一個給定的連接在兩小時內沒有任何動作,服務器就向客戶發送一個探測報文段,根據客戶端主機響應探測4個客戶端狀態
- 客戶主機依然正常運行,且服務器可達。此時客戶的TCP響應正常,服務器將保活定時器復位。
- 客戶主機已經崩潰,并且關閉或者正在重新啟動。上述情況下客戶端都不能響應TCP。服務端將無法收到客戶端對探測的響應。服務器總共發送10個這樣的探測,每個間隔75秒。若服務器沒有收到任何一個響應,它就認為客戶端已經關閉并終止連接。
- 客戶端崩潰并已經重新啟動。服務器將收到一個對其保活探測的響應,這個響應是一個復位,使得服務器終止這個連接。
客戶機正常運行,但是服務器不可達。這種情況與第二種狀態類似。
-
什么時候用長連接或者短連接
- 長連接多用于操作頻繁,點對點的通訊,而且連接數不能太多情況,。每個TCP連接都需要三步握手,這需要時間,如果每個操作都是先連接,再操作的話那么處理速度會降低很多,所以每個操作完后都不斷開,下次處理時直接發送數據包就OK了,不用建立TCP連接。例如:數據庫的連接用長連接, 如果用短連接頻繁的通信會造成socket錯誤,而且頻繁的socket 創建也是對資源的浪費。
- 而像WEB網站的http服務一般都用短鏈接,因為長連接對于服務端來說會耗費一定的資源,而像WEB網站這么頻繁的成千上萬甚至上億客戶端的連接用短連接會更省一些資源,如果用長連接,而且同時有成千上萬的用戶,如果每個用戶都占用一個連接的話,那可想而知吧。所以并發量大,但每個用戶無需頻繁操作情況下需用短連好。
-
-
服務于會話層
-
建立在某個主機的某個端口到另一個主機下某個端口的連接和通信的,這個通信是使用socket來實現,通過socket實現上面的ip尋址,而且還會建立端口間的連接。規定了一套基于端口的通信協議,包括建立連接,發送和讀取消息。
-
大概機制就是在數據包中加入端口號,尋址到ip后,再去尋找監聽該端口號的服務,將數據包發送過去
-
特點
- TCP依靠端口來進行通信 TCP頭有自己的端口 TCPdata有目標的端口
- 傳輸可靠,因為要經歷三次握手和四次揮手
- 可靠性傳輸依賴的是ARQ協議
- 流量控制
-
-
UDP協議簡述
- UDP提供無連接服務
- 不需要維護連接狀態
- 適用于效率高的應用
- 不像TCP那樣每一次發送都需要進行確認
- 性能高,開銷小
- 連接不可靠
-
-
會話層
- 維護兩個節點的傳輸連接,確保點到點傳輸不中斷和管理數據交換等功能
-
表示層
- 提供各種用于應用層數據的編碼和轉換功能,確保一個系統的應用層發送的數據能被另一個系統的應用層識別
- 作用
- 數據編碼和解碼
- 數據加密和解密
- 數據壓縮和解壓縮
- 展示圖片,音頻和視頻等
- 總結
- 表示層從應用層接收數據。這些數據是以字符和數字的形式出現的,表示層將這些數據轉換成為機器可以理解的二進制格式,也就是封裝數據,和格式化數據。例如:將ASCII碼轉化為別的編碼,這個功能稱為“翻譯”。在傳輸數據之前,表示層減少了用來表示原始數據的比特數,這個過程被稱為數據壓縮,它可以是無損或者有損的,數據壓縮減少了存儲原始數據所需的空間,所以它可以在很短的時間內到達目的地,數據壓縮對實時視頻和音頻的傳輸有很大的幫助。
- 為了保持完整性的數據,傳輸前的會給數據加密,而加密和解密是敏感數據的安全保障,在中心端,數據在接受端被加密,數據被解密為SSL協議或者安全套接字。
- 所以,表示層執行三個基本功能:翻譯、壓縮和加密/解密。
-
應用層 http協議(80端口),https(443端口),ftp(21),ssh(22),smtp(25)
-
TCP能傳輸數據了,為什么還需要類似http這種應用層協議?
- 因為如果每個公司都有自己的應用層協議,那么會導致兩端數據無法識別。所以就需要抽象出國際協議,比如http(Hyper Text Transfer Protocol)。
- 它指定了客戶端可能發送給服務器什么樣的消息以及得到什么樣的響應。請求和響應消息的頭以ASCII形式給出;而消息內容則具有一個類似MIME的格式。這個簡單模型是早期Web成功的有功之臣,因為它使開發和部署非常地直截了當
- 因為我們在傳輸數據時,可以只使用TCP/IP協議進行傳輸,但是這樣沒有應用層的參與,會導致兩端無法識別數據內容,這樣傳輸的數據也就沒有意義了。因此如果想讓傳輸的數據有意義,那么就必須要用到應用層的協議,比如http
- web使用http協議作為應用層協議,封裝http文本信息,然后使用tcp協議進行傳輸
-
http長連接 keep-alive
-
若開啟后,在一次http請求中,服務器進行響應后,不再直接斷開TCP連接,而是將TCP連接維持一段時間。在這段時間內,如果同一客戶端再次向服務端發起http請求,便可以復用此TCP連接,向服務端發起請求,并重置timeout時間計數器,在接下來一段時間內還可以繼續復用。這樣無疑省略了反復創建和銷毀TCP連接的損耗
-
啟用HTTP keep-Alive的優缺點:
優點:keep-alive機制避免了頻繁建立和銷毀連接的開銷。 同時,減少服務端TIME_WAIT狀態的TCP連接的數量(因為由服務端進程主動關閉連接)
缺點:若keep-alive timeout設置的時間較長,長時間的TCP連接維持,會一定程度的浪費系統資源。
總體而言,HTTP keep-Alive的機制還是利大于弊的,只要合理使用、配置合理的timeout參數。
-
和TCP的keepalive區別是
- TCP的是為了檢測心跳,保持活躍的
- HTTP的主要是在TCP的保活基礎上重用連接,提高性能的
-
-
-
什么是DNS
- 通常我們訪問網址都需要輸入一個域名,但協議都是根據ip進行尋址的,所以會有一個DNS服務器,輸入一個域名的時候會先發給DNS服務器,DNS服務再告訴你對應的ip地址
-
-
WebSocket面試題
-
websocket和socket的區別
-
什么是socket協議
- socket是應用層與TCP/IP協議通信的中間軟件抽象層,它是一組接口。而websocket協議是一個完整的應用層協議,基于TCP長連接實現的
- Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。
- Socket起源于Unix,而Unix/Linux基本哲學之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關閉close”模式來操作, socket就是該模式的一個實現,socket是一種特殊的文件,一些socket函數就是對其進行的操作(打開、讀/寫IO、關閉)。
- 套接字(socket)是一個抽象層,應用程序可以通過它發送或接收數據,可對其進行像對文件一樣的打開、讀寫和關閉等操作。套接字允許應用程序將I/O插入到網絡中,并與網絡中的其他應用程序進行通信。網絡套接字是IP地址與端口的組合。
-
sockey協議調用流程
- 先從服務器端說起,服務器端先初始化Socket,然后與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然后連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求并處理請求,然后把回應數據發送給客戶端,客戶端讀取數據,最后關閉連接,一次交互結束。
-
socket核心api
-
**socket():**創建套接字。
**bind():**指定本地地址。一個套接字用socket()創建后,它其實還沒有與任何特定的本地或目的地址相關聯。在很多情況下,應用程序并不關心它們使用的本地地址,這時就可以不用調用bind指定本地的地址,而由協議軟件為它們選擇一個。但是,在某個知名端口(Well-known Port)上操作的服務器進程必須要對系統指定本地端口。所以一旦創建了一個套接字,服務器就必須使用bind()系統調用為套接字建立一個本地地址。
-
**listen():**設置等待連接狀態。對于一個服務器的程序,當申請到套接字,并調用bind()與本地地址綁定后,就應該等待某個客戶機的程序來要求連接。listen()就是把一個套接字設置為這種狀態的函數。
-
**connect():**將套接字連接到目的地址。初始創建的套接字并未與任何外地目的地址關聯。客戶機可以調connect()為套接字綁定一個永久的目的地址,將它置于已連接狀態。對數據流方式的套接字,必須在傳輸數據前,調用connect()構造一個與目的地的TCP連接,并在不能構造連接時返回一個差錯代碼。如果是數據報方式,則不是必須在傳輸數據前調用connect。如果調用了connect(),也并不像數據流方式那樣發送請求建連的報文,而是只在本地存儲目的地址,以后該socket上發送的所有數據都送往這個地址,程序員就可以免去為每一次發送數據都指定目的地址的麻煩。
-
**accept():**接受連接請求。服務器進程使用系統調用socket,bind和listen創建一個套接字,將它綁定到知名的端口,并指定連接請求的隊列長度。然后,服務器調用accept進入等待狀態,直到到達一個連接請求。
-
**read()、write():**當服務器與客戶已經建立好連接之后。可以調用網絡I/O進行讀寫操作了,即實現了網咯中不同進程之間的通信
-
-
-
什么是websocket
- HTML5的一種新協議,允許服務器向客戶端傳遞信息,實現瀏覽器和客戶端雙工通信
-
運行原理
-
為什么要用
- 因為http協議請求后都會關閉連接,下次請求的時候又要重新建立連接。但長輪詢過多時會對服務器負載造成較大影響。所以就用websocket協議來完成類似聊天室,實時狀態推送等業務場景功能
-
應用場景
- 實時通信
-
特點
- 與 HTTP 協議有著良好的兼容性。默認端口也是80和443,并且握手階段采用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器
- 數據格式比較輕量,性能開銷小,通信高效
- 沒有同源限制,客戶端可以與任意服務器通信
-
基于TCP再次封裝的另一個協議
-
首先在連接的時候會發送一個協議升級請求,在http請求上加上socket相關的請求頭
- Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits Sec-WebSocket-Key: k1kbxGRqGIBD5Y/LdIFwGQ== Sec-WebSocket-Version: 13 Upgrade: websocket
-
服務端如果支持socket的話,會返回101狀態碼同意協議升級
-
總結概述
- websocket是HTML5新增的一種全雙工通信協議,客戶端和服務端基于TCP握手連接成功后,兩者之間就可以建立持久性的連接,實現雙向數據傳輸。它也是基于TCP協議的,建立連接時也是先通過TCP完成三次握手,然后再發送一個http請求,攜帶協議升級的相關請求標識,比如Upgrade: websocket。服務端同意協議升級并返回一個回包進行確認,這樣websocket就連接上了
- 后續的發送消息就依靠網絡層協議來完成了,交換機,網關等
- websocket是HTML5新增的一種全雙工通信協議,客戶端和服務端基于TCP握手連接成功后,兩者之間就可以建立持久性的連接,實現雙向數據傳輸。它也是基于TCP協議的,建立連接時也是先通過TCP完成三次握手,然后再發送一個http請求,攜帶協議升級的相關請求標識,比如Upgrade: websocket。服務端同意協議升級并返回一個回包進行確認,這樣websocket就連接上了
-
-
WebSocket和Http區別,相比的優缺點
- 總結
- websocket是全雙工協議,客戶端和服務端雙向通信
- websocket協議頭是ws/wss,http是http/https
- websocket只需要連接一次,http需要反復的創建和關閉連接
- websocket不關注接收者的狀態,http需要關注接收者的狀態
- 兩個都是基于socket協議(socket基于TCP協議)
- 相同點
- 都是 TCP 協議;
都使用 Request/Response 模型進行連接的建立;
websocket 是基于 http 的,他們的兼容性都很好;
在連接的建立過程中對錯誤的處理方式相同;
都可以在網絡中傳輸數據。
- 都是 TCP 協議;
- 不同點
- websocket 是持久連接,http 是短連接;
websocket 的協議是以 ws/wss 開頭,http 對應的是 http/https;
websocket 是有狀態的,http 是無狀態的;
websocket 連接之后服務器和客戶端可以雙向發送數據,http 只能是客戶端發起一次請求之后,服務器才能返回數據;
websocket 是可以跨域的;
websocket 連接建立之后,數據的傳輸使用幀來傳遞,不再需要Request消息。
- websocket 是持久連接,http 是短連接;
- 總結
-
為什么用這個技術
- 解決http長輪詢造成的性能和資源浪費問題
-
WebSocket和Socket.io區別
- socket.io是對websocket的再一次封裝
- 相比websocket,它提供了以下接口
- 自動重連
- 自動檢測掉線
- 廣播
- 解決因瀏覽器兼容導致的問題
- 有些瀏覽器不兼容,所以默認還支持了長輪詢的嘗試
- 前后端都要使用socket.io庫
- socket.io是對websocket的再一次封裝
-
socket.io工作原理
- 首先,socket.io通過一個http請求,并且該請求頭中帶有升級協議(Connection:Upgrade、Upgrade:websocket)等信息,告訴服務端準備建立連接,此時,后端返回的response數據
- 當客戶端收到響應之后,scoket.io會根據當前客戶端環境是否支持Websocket。如果支持,則建立一個websocket連接,否則使用polling(xhr、jsonp)長輪詢進行雙向數據通信
- 心跳參數配置
- pingTimeout
- Ping消息超時時間(毫秒),默認20秒,這個時間間隔內沒有接收到心跳消息就會發送超時事件
- 默認是20000,20秒
- pingInterval
- Ping消息間隔(毫秒),默認25秒。客戶端向服務器發送一條心跳檢測
- 默認是25000,25秒
- upgradeTimeout
- 協議升級超時時間(毫秒),默認10秒。HTTP握手升級為ws協議超時時間
- pingTimeout
- 心跳檢測機制
- 概述
- 為了確保客戶端與服務端的長連接正常,有時即使客戶端連接中斷,但是服務端未觸發onclose事件,這就有可能導致無效連接占用。所以需要一種機制,確保兩端的連接處于正常狀態,心跳檢測就是這種機制。客戶端每隔一段時間,會向服務端發送心跳(數據包),服務端也會返回response進行反饋連接正常。
- socket.io與engine.io的一大區別在于,socket.io并不直接提供連接功能,而是在engine.io層提供。
- 概述
-
-
什么是跨域,如何解決
- 跨域的概念
- 瀏覽器從一個域名的網頁去請求另一個域名的資源時,域名、端口、協議任一不同,都是跨域
- 需要同源策略的原因
- 設置同源策略的主要目的是為了安全,如果沒有同源限制,在瀏覽器中的cookie等其他數據可以任意讀取,不同域下的DOM任意操作,ajax任意請求其他網站的數據,包括隱私數據。
- 哪些場景需要跨域
- 需要本地聯調測試環境,本地用的local,測試環境用的ip或者域名
- 多個產品之間需要進行對接
- 如何解決跨域問題
- 請求頭添加以下配置項 nginx和后端服務均可
- Access-Control-Allow-Origin 允許哪些網站的跨域請求
- Access-Control-Allow-Methods 允許的跨域ajax的請求方式
- Access-Control-Allow-Headers 允許在請求中攜帶的頭信息
- jsonp請求
- 事先定義一個用于獲取跨域響應數據的回調函數,并通過沒有同源策略限制的script標簽發起一個請求(將回調函數的名稱放到這個請求的query參數里),然后服務端返回這個回調函數的執行,并將需要響應的數據放到回調函數的參數里,前端的script標簽請求到這個執行的回調函數后會立馬執行,于是就拿到了執行的響應數據。
- 優點
- 它不像XMLHttpRequest對象實現的Ajax請求那樣受到同源策略的限制
- 它的兼容性更好,在更加古老的瀏覽器中都可以運行,不需要XMLHttpRequest或ActiveX的支持
- 并且在請求完畢后可以通過調用callback的方式回傳結果
- 缺點
- 它只支持GET請求而不支持 POST 等其它類型的 HTTP 請求
- 它只支持跨域 HTTP 請求這種情況,不能解決不同域的兩個頁面之間如何進行JavaScript 調用的問題
- 優點
- 事先定義一個用于獲取跨域響應數據的回調函數,并通過沒有同源策略限制的script標簽發起一個請求(將回調函數的名稱放到這個請求的query參數里),然后服務端返回這個回調函數的執行,并將需要響應的數據放到回調函數的參數里,前端的script標簽請求到這個執行的回調函數后會立馬執行,于是就拿到了執行的響應數據。
- 請求頭添加以下配置項 nginx和后端服務均可
- 跨域的概念
-
Nodejs的buffer原理和應用場景
- JavaScript 起初為瀏覽器而設計,沒有讀取或操作二進制數據流的機制。Buffer類的引入,則讓NodeJS擁有操作文件流或網絡二進制流的能力
- 也可以用來請求中的字符串轉換
- 對字符串進行其他類型的編碼解碼等
-
Nodejs的流的應用場景以及運行原理
- 主要用于處理大文件,利用fs模塊中的讀取和寫入流可以操作
- 也可以用stream模塊,但一般fs模塊就滿足了,因為fs內部就繼承了stream模塊
- 解決因文件過大而導致的內存占用高的問題
-
Nodejs await/async原理
- async放在函數前 這個函數會返回promise
- await放在函數前 則會等待這個函數執行完畢
- 要了解await就要先理解generator函數
- Generator 函數是 ES6 提供的一種異步編程解決方案,語法行為與傳統函數完全不同
- generator函數的特性有以下兩點
- 一是,function關鍵字與函數名之間有一個星號;
- 二是,函數體內部使用yield表達式
- generator是es6提供的函數同步執行的方法,使用*號標記函數為generator,內部使用yield暫停表達式,使用next進行放行
-
Nodejs多進程應用場景,如何做負載均衡和進程間通訊
- master進程負責啟動子進程,然后子進程工作
- Worker 進程的數量一般根據服務器的 CPU 核數來定,這樣就可以完美利用多核資源
-
進程和線程的區別
- 線程就可以當做是進程里面的執行的單元,同時它也是這個進程里面的一個能夠調度的實體
-
CommonJs概念
- 由于早期javascript缺少模塊的概念,產生了commonjs規范
- 規范特性
- 通過require引入模塊并應用在當前的上下文
- 模塊內部需要用exports來提供導出
- 相關文章
- Noejs CommonJS模塊機制
-
Nodejs模塊
- es5和es6區別
- es5使用require
- es6使用import
- es6 module屬于編譯時加載,也就是靜態加載,在編譯時就能確定模塊的依賴關系,以及輸入和輸出的變量;commonjs屬于運行時加載,只有代碼在運行時才能確定這些東西。
- es6 module可以做到tree-shaking,也就是可以加載模塊部分用到的內容,commonjs需要加載整個模塊,再取內容。
- es6 module輸出的是值的引用,commonjs輸出的是值的拷貝。
- es6 module中的import是異步加載,commonjs中的require是同步加載
- export和export.default區別
- export.default是默認導出,一個模塊只允許有一個默認導出,重復的會被覆蓋
- export類似module.export,可以導出一個或多個成員
- es5和es6區別
-
ExpressJs
- 概念
- node中上手最簡單,內置功能多的框架
- 特點
- 內置了常用中間件,方便使用
- express.static 提供靜態資源,例如 HTML 文件、圖像等
- exporess.json 使用 JSON 有效負載解析傳入請求
- express.urlencoded 解析帶有 URL 編碼負載的傳入請求
- express.text(基于body-parser) 將請求參數解析為字符串
- express.router 路由文件
- 回調基于callback
- 內置了常用中間件,方便使用
- 概念
-
KoaJs
-
概念
- 下一代的nodejs框架koa 是由 Express 原班人馬打造的,致力于成為一個更小、更富有表現力、更健壯的 Web 框架。使用 koa 編寫 web 應用,通過組合不同的 generator,可以免除重復繁瑣的回調函數嵌套,并極大地提升錯誤處理的效率。koa 不在內核方法中綁定任何中間件,它僅僅提供了一個輕量優雅的函數庫,使得編寫 Web 應用變得得心應手
-
特點
-
洋蔥卷模型
-
概述
- 先由內向外執行,再由外向內執行
-
源碼
-
為什么需要
- 因為express的next在異步情況下的順序不能保持一致,所以需要用await保證順序,就形成了洋蔥卷模型
- 其實就是express的next不是用promise實現的,導致異步無法控制,而koa的next是用promise實現的,再結合流行的await,可以很好的控制中間件的異步順序
-
-
沒有內置任何中間件,比express更輕量
-
基于es6,默認await/async
-
有一個統一上下文遍歷ctx,方便業務處理,不再需要層層向下傳遞。
-
比koa1中的generator更簡單的異步,async/await
-
-
-
EggJs
- 特點
- 支持ts
- 基于koa,內置洋蔥卷模型
- 約定大于配置
- controller
- service
- router
- 內置多進程模型
- node內置了一個模塊cluster 多進程
- 插件機制
- 比起傳統的util文件,插件更適合拓展和維護,一個插件只做一件事。
- 而且插件的約定也是按照egg的目錄結構而來的,一個迷你的egg應用
- 去掉了router和controller
- 而且插件的約定也是按照egg的目錄結構而來的,一個迷你的egg應用
- 比起傳統的util文件,插件更適合拓展和維護,一個插件只做一件事。
- 漸進式開發
- 前期,最初團隊抽象出了一些公共的方法 .util.ts
- 中期,這個通用方法還不是很完善,直接用插件不好維護,但可以先寫成插件的形式
- 通過config/plugin.js來掛載
- 后期,經過一段時間考驗后,該插件已經完善且穩定,所以可以進行單獨的抽離
- 就是單獨抽離出一個npm包,不過包結構要按照egg的格式來
- 終期,已經積累了很多可以復用的插件,并且在多個團隊和項目中可以復用,所以此時可以沉淀到框架中
- 將可復用的插件單獨抽象成一個框架給多個團隊和項目使用
- 特點
-
NestJs
-
特點
- 基于express
- 因為為了設計一個開箱即用的框架,而express內置了很多模塊,所以基于express
- 完全支持typescript
- 類型約束
- 編譯期間有類型檢查,能預知錯誤
- 模塊概念
- 通過模塊將每個應用功能進行解耦。相比express等框架,沒有模塊約束,代碼相比nestjs,維護難度更高
- 異常過濾器
- 全局的異常過濾器,對全局的異常做定制化處理
- 管道
- 對請求參數進行轉換或者驗證
- 守衛
- 用戶權限校驗
- 用戶角色校驗
- 用戶鑒權等
- 更符合現代化編程的中間件模式
- 傳統的express,koa等node框架,通過和路由編碼的方式來進行綁定
- 而nestjs是基于模塊進行中間件配置的形式來綁定中間件
- 攔截器,比如用于請求前后的日志鏈路追蹤,或者請求和返回參數的定制化
- 官方文檔非常完善,每一個模塊都提供了標準的示例
- AOP(Aspect Oriented Programming),面向切面編程
- IOC
- 控制反轉。比如傳統的開發模式是每次都要重新實例化一個對象。但是在nestjs的IOC模式中,只需要將對象注入到構造函數聲明中就行
- 生命周期鉤子函數
- OnModuleInit
- 初始化主模塊依賴處理后調用一次
- OnApplicationBootstrap
- 在應用程序完全啟動并監聽連接后調用一次
- OnModuleDestroy
- 收到終止信號(例如SIGTERM)后調用
- beforeApplicationShutdown
- 在onModuleDestroy()完成(Promise被resolved或者rejected);一旦完成,將關閉所有連接(調用app.close() 方法).
- OnApplicationShutdown
- 連接關閉處理時調用(app.close())
- OnModuleInit
- OOP面向對象思想
- 面向對象編程,通過對象內的各種屬性和方法來完成編程。容易維護和擴展。
- 封裝 將通用的方法抽象到一個util內
- 繼承 擴展類的功能
- super關鍵字用于訪問和調用對象父類上的函數。可以調用父類的構造函數,也可以調用父類的普通函數
- 多態
- 同一操作作用于不同的對象上面,可以產生不同的解釋和不同的執行結果
- 比如兩個方法都有send函數,要在一個函數內進行調用,就要根據不同的方法調用不同的send函數。那么利用多態,直接將類初始化,函數內部只需要執行send方法而不需要關注上層是誰了
- 比如使用抽象類和接口實現
- 面向對象編程,通過對象內的各種屬性和方法來完成編程。容易維護和擴展。
- FP函數式編程
-
函數式編程是一種強調以函數使用為主的軟件開發風格 ,也是一種范式
- 用函數來編程,更好的模塊化
- 函數可以復用
- 低耦合
-
偽代碼
- 函數作為參數執行
- 函數作為返回值執行
-
其他的應用場景
function discount (price, discount) {return price * discount } // 當一個顧客消費了500元 const price = discount(500, 0.1) // $50// 從長遠看,你的每一筆生意都要計算10%的折扣 const price = discount(1500, 0.1) // $150 const price = discount(2000, 0.1) // $200 const price = discount(50, 0.1) // $5 const price = discount(300, 0.1) // $30// 將這個函數柯里化,然后我們就不用每次都寫那0.1了 function discount (discount) {return (price) => {return price * discount} } const tenPercentDiscount = discount(0.1)// 現在,我們只需用商品價格來計算就可以了: tenPercentDiscount(500) // $50// 接下來,有些優惠顧客越來越重要,讓我們稱為vip顧客,然后我們要給20%的折扣,我們這樣來使用柯里化了的discount函數: const twentyPercentDiscount = discount(0.2)// 我們為vip顧客使用0.2調用柯里化discount函數來配置了一個新的函數。這個twentyPercentDiscount函數會被用來計算vip顧客的折扣: twentyPercentDiscount(500) // $100 twentyPercentDiscount(3000) // $600 twentyPercentDiscount(80000) // $16000
編寫可以輕松復用和配置的小代碼塊,就像我們使用npm一樣
舉個例子,你有一家商店,然后你想給你的優惠顧客10%的折扣:避免頻繁調用具有相同參數的函數
function volume (l, w, h) {return l * w * h }// 碰巧你倉庫里的所有物品都是100m高。你會看到你不停地用h=100來調用這個函數: volume(200, 30, 100) // 2003000 volume(32, 45, 100) // 144000 volume(2322, 232, 100) // 53870400// 為了解決這個問題,你把volume函數柯里化(像我們之前做過的): function volume (h) {return (w) => {return (l) => {return l * w * h}} }// 我們能給同類物品定義一個特殊函數: const hCylinderHeight = volume(100) hCylinderHeight(200)(30) // 600000 hCylinderHeight(2322)(232) // 53870400
比如我們有個用來計算體積的函數
-
- 基于express
-
應用場景
- 中大型企業框架
- ts支持,減少團隊代碼后期維護成本
- 更現代的開發模式
- 豐富的插件生態,提高開發效率
- 中大型企業框架
-
自定義注解
- 通過這個包實現reflect-metadata
-
-
V8虛擬機
-
什么是V8虛擬機?說說你對虛擬機的理解
- v8引擎是谷歌瀏覽器用的虛擬機。用于解釋和執行javascript代碼
- 說到底,JavaScript引擎就是用來執行JavaScript代碼用的,為其提供了一個運行時環境,提供解釋/編譯、自動內存管理(GC)、對象模型、核心庫等功能。
-
為什么v8引擎的性能強勁
- 相比較其他的javascript引擎,v8的性能更高
- 熱點代碼緩存
- 增量式的垃圾回收機制
- 隱藏類,對相同屬性的對象通過隱藏類共享,后續訪問只需要根據設定的對象偏移量操作即可。如果位置發生變化,則重新生成隱藏類
- 對隱藏類做緩存,因為獲取隱藏類的地址依然有提升空間,所以就對隱藏類進行緩存,查找的時候先判斷是否是相同的隱藏類,是的話直接使用該緩存結果
- 新版本的v8引入了Sparkplug引擎,提高非熱點代碼的執行效率
- 相比較其他的javascript引擎,v8的性能更高
-
垃圾回收機制
- 64系統為1.4g
- 32系統0.7g(因為32位系統內存最大只支持4g)
- 也可以通過啟動參數來調整
- 32和64區別,內存大小不同,兼容性不同
- 新生代:存活周期短
- 老年代:存貨周期長
- v8默認采用的是分代式垃圾回收機制
- V8垃圾回收機制
- 其他垃圾回收機制
- 標記清除
- 標記清楚在標記階段遍歷堆中的所有對象,并標記活著的對象,在隨后的清除階段中,只清除沒有被標記的對象。這樣設計是因為存活的在對象占用比例小,所以才會更高效
- 優點
- 比較通用和簡單的算法
- 缺點
- 內存不連續
- 復制清除
- 將堆內存一分為二,from和to這兩個空間,分配對象的時候,會先檢查from空間是否還有存活對象,有的話就復制到to空間中,那些非存活對象會被釋放。完成復制后,to和from的空間再進行互換,下次分配的時候繼續執行剛才的步驟。當一個對象檢查多次依然存活,那么它就會晉升到老年代中。判斷內存地址來區分是否被檢查過
- 優點
- 解決內存碎片問題
- 回收效率高
- 缺點
- 將內存縮小為原來的一半,代價高
- 隨著存活對象的比例提升,復制的成本會逐漸提高
- 標記整理
- 解決內存碎片化和復制清除內存利用率低的問題
- 讓所有存活的對象移動到一段,然后直接清理掉另一端的內存。這樣就避免了內存碎片和內存利用率低的問題
- 當存活對象過多時,v8采用增量式遷移,每次移動一小部分進行處理,提高處理效率
- 分代式垃圾回收
- V8 中會把堆分為新生代和老生代兩個區域,新生代中存放的是生存時間短的對象,老生代中存放的生存時間久的對象
- 新生代使用復制清除回收算法
- 老年代使用標記整理算法(因為標記清除會留下不連續的內存碎片),并且v8對老年代的回收做了優化,使用增量標記提高了回收效率
- 標記清除
-
內存結構
-
為什么需要堆棧區
- 程序執行過程中,需要記錄上下文,那么就要把數據記錄到內存中合適的地方,并做好分配和初始化等工作。
-
堆區
- 堆區用來存儲對象,由棧區的對象引用地址來進行調用
- 這樣設計的使對象實體的 運用 更加的靈活、隔離、模塊化
- 靈活:相當于業務數據存放到一個空間足夠的池子,我想實現怎樣的邏輯,需要怎么的業務數據,通過引用的方式直接獲取想要的業務數據。
- 隔離:業務數據和業務邏輯存放在不同的空間,互不干擾。
- 模塊化:通過業務數據和業務邏輯的組合,形成具有特定功能的模塊。
- 這樣設計的使對象實體的 運用 更加的靈活、隔離、模塊化
- 對象內存的管理由垃圾回收機制來處理
- 堆區用來存儲對象,由棧區的對象引用地址來進行調用
-
堆棧區(棧區)
- 棧區用來記錄程序的執行順序,所以只存儲了基本變量類型和對象的引用地址
- 函數調用棧,先進后出
- 原因
- 要是在生活中我們也是按照棧的規則來排順序,這個世界豈不是亂了套了,試想你去銀行排隊取錢,明明你是第一個去,卻等到晚上晚上銀行人員快下班了你才最后處理完業務,恐怕你早就跟人干起來了
- 舉個例子吧:我創建一個函數A,在函數A中調用了另一個函數B!那么堆棧中應該是先把A的壓棧,再把B壓棧!而只有當B這個函數執行完了才會繼續執行A后面的代碼,所以必須先把B彈棧!這就是后進先出!不然的話程序豈不是亂套了!
- 原因
- 主要作用
- 保存函數的局部變量
- 向被調用函數傳遞參數
- 返回函數的返回值
- 保存函數的返回地址。返回地址是指從被調用函數返回后調用者應該繼續執行的指令地址
- 工作流程
- 每個函數在執行過程中都需要使用一塊棧內存用來保存上述這些值,我們稱這塊棧內存為某函數的棧幀(stack frame)。當發生函數調用時,因為調用者還沒有執行完,其棧內存中保存的數據還有用,所以被調用函數不能覆蓋調用者的棧幀,只能把被調用函數的棧幀“push”到棧上,等被調函數執行完成后再把其棧幀從棧上“pop”出去,這樣,棧的大小就會隨函數調用層級的增加而生長,隨函數的返回而縮小
-
棧區和堆區的區別示例
-
以圖書館作為例子,圖書館就是系統 全部 的內存空間,里面的任何東西都是基于圖書館來分配空間。
每一本書就是一個 對象實體,他們存放在書架上,書架就是 堆。假設圖書館有各種各樣的書架,可以滿足所有大小的書本,即使書的大小各異,管理員只需要找到合適的位置存放。
為了方便存取書本,圖書館管理員會為每一本書設計一個編號,并記錄在一個表格上,編號就是對象實體的 內存地址。
那什么是 對象引用 呢,答案就是讀者的每一條借書記錄,每一次的借書行為可以理解為對書本的一次引用。借書的記錄都會登記在一個表格上,這個表格就是 棧。借書記錄僅僅是一段文字,所需要的空間很小,而且大小基本固定,通常情況下用一個本子、一個電子表格、一個數據庫,做一個表格就可以滿足需求。
書本、編號、借書記錄三者的結合,相輔相成,讓整個圖書館的對圖書的管理更加的靈活、方便和規范。
其實,代碼世界中很多的設計原理都是源于我們的生活,又高于生活。設計模式就在我們身邊,無處不在,善于觀察生活中的事物,從生活中領悟設計模式吧
-
-
-
-
都用過哪些NodeJs模塊
- 內置模塊
- cluster
- 操作多進程
- process
- 處理環境變量,監聽全局未捕獲異常,控制進程退出,獲取機器負載信息等
- http
- 給其他服務發起http請求
- fs
- 操作文件的增刪改查
- buffer
- buffer是一個二進制數據流,操作文件,數據流轉換,比如base64編碼解碼等
- stream
- 流,處理大文件,避免大文件占用內存過高的問題
- cluster
- 第三方模塊
- lodash
- 判斷數值是否為空 isEmpty
- 對空字符串 null undefind 0 空數組的判斷都很好用
- 數組去重 uniq
- 用js的話要么自己抽象一個方法,要么用set,lodash自己有封裝好的
- 對數字的高級操作,交集(intersection),補集(xor),過濾(difference),分組(group)等
- 等等等
- 判斷數值是否為空 isEmpty
- bluebird/async 異步流程控制
- 異步串行
- 異步并行
- 同時串并
- 等等等
- moment/dayjs
- 日期處理
- 時間戳轉日期
- 日期轉時間戳
- 獲取某幾個月的時間
- 計算時間差
- 等等等
- 日期處理
- node-xlsx
- xlsx文件的導入導出解析
- ioredis
- 連接redis驅動,操作redis
- mongoose
- 連接mongodb驅動,操作Mongodb
- log4js
- 服務日志記錄和輸出
- qiniu
- 對接第三方云存儲平臺,處理文件的增刪改查
- lodash
- 內置模塊
-
看過哪些模塊的源碼
-
lodash
-
出于好奇,看了isEmpty源碼
-
先判斷是否為空
-
再判斷是否像數組
- 判斷是否擁有length屬性
- 判斷是否是函數
- 用typeof判斷 object和function 此時都會被判定為對象
-
-
-
接下在像數組的情況下繼續進行或的判斷
- 判斷是否是數組,使用Object.prototype.toString.call()判斷,如果是’[object Array]'這個字符串,就是數組
- 不是數組,再判斷是否是字符串
- 用typeof判斷是否是字符串
- 滿足任意一項,則返回length長度,0對于js是false,大于0則證明是有值的
-
如果不是數組也不是字符串,則執行nativeKeys方法
- 先用Object強制轉換,對于undefind,null等無效類型,默認返回空數組,其他的則轉換為對應的string,number,json等類型
- 然后再用Object.keys獲取變量的key列表,返回length
- 0是false,大于0的是true
-
Object.prototype.toString.call()原理
- JavaScript類型檢查
-
和instanceof,type of區別
-
typeof只能判斷基本類型,引用類型都是object
function instance_of(L, R) {//L 表示左表達式,R 表示右表達式 var O = R.prototype; // 取 R 的顯示原型 L = L.__proto__; // 取 L 的隱式原型while (true) { if (L === null) return false; if (O === L) // 當 O 顯式原型 嚴格等于 L隱式原型 時,返回truereturn true; L = L.__proto__; }} -
instanceof判斷構造函數的 prototype 屬性是否出現在某個實例對象的原型鏈上
-
instanceof 的工作原理就是將 s 每一層的 proto 與 F.prototype 做比較
找到相同返回 true
至最后一層仍沒有找到返回 false -
比如判斷 一個字段的類型是否是數組
- 會先拿這個數組的原型鏈__proto__原型鏈對比數組的原型prototype
- 如果一致則返回true,否則繼續對比
- 下一層的原型鏈就是Object的了,如果還是不一樣就繼續對比
- 一直到為null,則返回false
-
但instanceof有個缺點,不能用來判斷是否為Object,因為最終都指向了Object。
-
-
koa-bodyparser
- 平臺服務總是報很多json類的轉換異常,看不到具體的日志,因為沒有執行到中間件就被捕獲了。
- 后來發現只要post請求的body參數不是json類型,該插件就會拋錯,然后被全局異常捕獲。然后單獨把這個包拿出來,只做一個打印,然后由攔截器中間件來處理這個錯誤
什么是原型和原型鏈
- 原型
- 每個對象都有prototype屬性
- prototype包含了對象的屬性和方法
- 原型鏈
- 對象的追蹤過程,函數->對象->null
什么是閉包
-
函數內部保存的變量不隨這個函數調用結束而被銷毀就是閉包
-
閉包的應用場景
- 操作函數內部變量
- 將變量維護在內存中不釋放
- 常見的例子就是遞歸計算和函數式編程
-
閉包的例子
const fun = ()=>{let num = 0;return (v)=>{return num += v;}; }const data5 = fun(); //初始化函數fun并得到函數的匿名函數返回值(這里只初始化了一次) console.log(data5(1)); //1 給匿名函數傳參并得到累加的結果 console.log(data5(1)); //2 由于fun函數未重新初始化,且此時num的值為1,所以累加得2 console.log(data5(1)); //3 與上面雷同
什么是深拷貝和淺拷貝
- 比如基本類型是深拷貝,拷貝的是值
- 對象是淺拷貝,拷貝的是引用地址
- 需要重新分配內存地址才能解決淺拷貝問題
-
常用的方法有
- JSON.parse(JSON.stringify(obj))
- lodash的deepLone方法
- 自定義克隆方法
-
JavaScript作用域
-
分為全局和局部作用域
-
每次代碼執行前會先進行"預編譯"
-
比如有下面一段代碼
-
在執行之前會先預編譯成以下的預編譯代碼
- 生成全局作用域 global object 簡稱go
- 執行到foo的時候 生成foo的函數局部作用域 active object 簡稱ao
- 執行foo內部函數bar的時候,生成bar的函數局部作用域
-
然后先執行ao2,給變量c復制,在當前局部作用域可以找到,然后執行b,發現當前局部作用域沒有,于是就去外層的作用域查找,找到了,執行。然后執行a,發現發現當前局部作用域沒有,于是就去外層的作用域查找,發現也沒有,一直找到全局作用域,找到了執行,沒找到就當作undefind處理
- 其中 這個查找的過程就是作用域鏈,有點類似js的原型鏈
-
JavaScript繼承,更詳細的請查看JS繼承原理
-
es5
-
原型繼承
-
使用prototype重新賦值,然后再擴展新對象的prototype
-
示例
// 繼承 B.prototype=new A() // 擴展 B.prototype.splice = ()=>{};
-
-
call和apply,更詳細的請查看這篇文章
-
用來改變this的指向,將一個對象作為另一個對象的實現
-
示例
function myfunc1(){this.name = 'Lee';this.myTxt = function(txt) {console.log( 'i am',txt );} }function myfunc2(){myfunc1.call(this); }var myfunc3 = new myfunc2(); myfunc3.myTxt('Geing'); // i am Geing console.log (myfunc3.name); // Lee -
其中myfun2中沒有任何實現,只是將myfun1替換了myfun2的實現
-
區別
- call的第二個參數可以是任意類型,而apply必須是數組
-
-
-
es6
-
使用class類,通過extends關鍵字實現繼承
-
類只是一個語法糖,背后其實還是構造函數來實現的
-
原理
-
通過_inherits函數實現
-
先判斷當前對象是否是函數,如果不是函數則報錯類型異常
-
然后就是繼承父類的原型和原型鏈,下面是示例
// 繼承原型鏈 B.prototype._proto_ = A.prototype
-
-
-
super關鍵字用來做什么,更詳細的請查看這篇文章
- 用于訪問和調用對象父類上的函數
-
super為什么在構造函數中必須寫在前面,否則就不能用this?
-
比如下面的代碼
class Person {constructor(name) {this.name = name;}}class PolitePerson extends Person {constructor(name) {this.greetColleagues(); //這行代碼是無效的,后面告訴你為什么super(name);}greetColleagues() {alert('Good morning folks!');}}- 如果允許在調用super之前使用this的話。一段時間后,我們可能會修改greetColleagues,并在提示消息中添加Person的name:
-
但是我們忘記了super()在設置this.name之前先調用了this.greetColleagues()。 所以此時this.name還沒有定義,就會出現出現undefind
-
所以javascript強制在this之前調用super,先完成父類構造函數的構建,再執行子類
-
super的實現也是使用call方法
-
-
bind,call和apply應用場景和區別,更詳細的請查看這篇文章
- 都可以用來改變this指向
- call的第二個參數是任意類型
- apply的第二個參數是數組
- 應用場景
- call和apply多用來實現繼承
- bind多用來改變this指向
2個等于號和3個等于號區別,更詳細的請查看這篇文章
- 2個會進行類型轉換 比如判斷1和’1’ 他們是相等的
- 3個不會進行類型轉換,所有是嚴格判斷
線上cpu高的問題
- 定時器
- 有一天突然線上服務的cpu不間斷的飆升到三四百
- 于是排查代碼,發現在早期的代碼中,有一處每30s批量查詢數據庫(根據指定客戶數,并發在20-30之間),并對返回值進行計算和處理(批量計算客戶的號碼區間,篩選和重組,比如03766848660-03766848889)
- 因為該業務對性能要求不高,所以讓同事把代碼改為分批執行,每次執行十個,就解決了此性能問題
- redis
- 有一天上午十點多收到運維通知,redis告警cpu占用率高,于是就排查最近代碼,發現其他部門有同事,在關鍵業務處使用keys*來查詢所有
- 于是立馬所屬部門同事,給出建議方案進行更改
- 有一些大key,存儲了一個賬戶的所有座席配置(存儲的數據量過大,高大幾m甚至幾十m),頻繁獲取也會導致redis cpu飆升。于是在當天晚上就進行了簡化,只存儲業務需要的字段,對不需要的字段進行剔除
- 有一天上午十點多收到運維通知,redis告警cpu占用率高,于是就排查最近代碼,發現其他部門有同事,在關鍵業務處使用keys*來查詢所有
線上內存高的問題
- 內存泄漏
- 有一天下午,客戶報問題,服務突然不可用了
- 發現node接口服務自動重啟了
- 查看最近代碼發現是因為有人用了global對象,全局緩存。用來給數據庫降低壓力的,但是有一個用戶監控類的緩存沒有清除,導致內存持續升高
- 后來改成放在redis中后解決
- 有一天下午,客戶報問題,服務突然不可用了
- 流出寬帶高
- 系統中提供了對外開放接口,但早期部分接口沒有做分頁,允許查詢所有,導致某一天數據庫的外網出流量特別高,數據庫頻繁告警。所以就立刻做了熱更新,做了默認分頁20,解決了此問題
- 數據庫內存暴漲,導致某節點重啟。同事在對線上服務做腳本處理時(分析線上用戶數據腳本),因為走的是內部鑒權,沒有限制。導致對查詢沒有做好并發控制,瞬間的并發(同時發起100個查詢請求,持續幾分鐘),拖垮了數據庫節點。導致了故障。后續解決方案是統一了鑒權,不再放寬內部請求。
排查內存泄漏的方案是什么
- 看最近代碼是否有以下問題
- 全局對象
- 閉包
- 定時器用過后沒有清除
- 或者使用easy-monitor可視化工具查看某段時間的內存使用情況
數組和鏈表數據結構和區別,更詳細的請查看這篇文章
- 數組 有順序,大小固定,查詢效率高
- 鏈表 無順序,沒有固定大小,插入刪除效率高
什么是緩沖區
- nodejs buffer是一個二進制數據流,在操作文件的時候會用到。處理數據流
Nodejs最近大版本都更新了什么,更詳細的請查看這篇文章
-
官網維護的更新日志
-
https://github.com/nodejs/node/tree/main/doc/changelogs
-
node 10
- 支持http2
- 增加了n-api 支持
- 修復了一些漏洞
-
node 12
- 提供了打印堆快照的工具
- http換成lhttp
- Worker Threads 工作線程
-
node 14
- 支持es module,使用import導入模塊
- 升級v8版本
- 可選鏈,空值合并操作符
- 異步本地存儲。async 調用棧追蹤,由此的來了trace,鏈路追蹤id的插件
-
node 16
- 更新v8版本
- 返回promise的定時器
- Node.js針對不同的平臺提供預構建的二進制文件,給蘋果芯片提供了新的二進制版本
- 其他相關漏洞修復
-
node 18
- 內置fetch,基于undiciundici
- 內置test單元測試模塊
- 更新v8版本
- 其他修復等
js如何判斷數據類型,更詳細的請查看這篇文章
-
typeof,instanceof,Object.prototype.toString.call
-
Object.prototype.toString.call()原理
- 和instanceof相同,但使用的是就近原型鏈,而不會去向上尋找(比如找到了頂級Object)。所以傳入一個數組時,就獲取的是數組的原型鏈__proto__是Array
-
和instanceof,type of區別
-
typeof只能判斷基本類型,引用類型都是object
-
instanceof判斷構造函數的 prototype 屬性是否出現在某個實例對象的原型鏈上
function instance_of(L, R) {//L 表示左表達式,R 表示右表達式 var O = R.prototype; // 取 R 的顯示原型 L = L.__proto__; // 取 L 的隱式原型while (true) { if (L === null) return false; if (O === L) // 當 O 顯式原型 嚴格等于 L隱式原型 時,返回truereturn true; L = L.__proto__; }}-
instanceof 的工作原理就是將 s 每一層的 proto 與 F.prototype 做比較
找到相同返回 true
至最后一層仍沒有找到返回 false -
比如判斷 一個字段的類型是否是數組
- 會先拿這個數組的原型鏈__proto__原型鏈對比數組的原型prototype
- 如果一致則返回true,否則繼續對比
- 下一層的原型鏈就是Object的了,如果還是不一樣就繼續對比
- 一直到為null,則返回false
-
但instanceof有個缺點,不能用來判斷是否為Object,因為最終都指向了Object。
-
-
node如何接受客戶端上傳的文件
- 使用node的multer或者busboy等工具包
- 接受前端的form表單文件
- 然后使用node的stream流寫入文件
箭頭函數,更詳細的請查看這篇文章
- 縮短函數代碼
- 解決this指向問題
單元測試
- mocha框架
- 提高項目質量
- 減少測試成本
- 精準定位bug
TypeScript
-
特點
- 能夠使用最新的esma規范
- js的超集
- 靜態類型化編程
- 提供了注解,更現代化的開發模式
- 編譯期間的類型檢查
-
應用場景
- 中大型企業的最佳選擇
- 使用最新的ECMA規范
- 更健壯的企業級服務
-
缺點
- 增加了類型,提高了開發工作量
- 有一些庫目前還不支持ts,比如缺少.d.ts文件
- 對團隊素質要求高
-
目前用的什么版本,有什么特性
-
目前用的4.6版本
-
特性
-
super關鍵字允許寫在this后面,以前只能寫在前面
-
枚舉類型支持解構
type Action = | { kind: "NumberContents", payload: number } | { kind: "StringContents", payload: string };// 以前結構會報錯 const { kind, payload } = action; if (kind === "NumberContents") {let num = payload * 2// ... } -
更智能的jsdoc,通過注釋也可以看到類型
-
其他的問題修復等
-
-
-
-
經常用哪些類型接口
- type 簡單類型 字符串數字等
- interface 復雜類型 對象等
- Record 根據輸入的索引生成新類型
- Pick 取舊類型中的部分索引類型為新類型
- Omit 提出原類型中的部分屬性,生成新的類型
- Exclude 根據索引移除類型中的key得到新的索引類型
- ReturnType 獲取函數返回值類型
數據庫
Mongodb
-
關系型和非關系型區別
- sql語句不同,mongo的是json類型,而mysql是一個字符串語句,中間用空格間隔
- 關系型支持連表,非關系型不支持
- 非關系型數據結構靈活,比如mongo,都是key,value類型
- 非關系型比關系型更靈活,比如mongo可以隨時的動態增刪字段
-
Mongodb應用場景
- 操作簡單,開發成本低。因為數據結構都是json,尤其和javascript很契合
- 擴展性強。nosql型數據庫不要求強一致的字段結構,可以隨便的增刪字段
- 高性能和吞吐量。傳統型數據庫都有事務,導致速度沒有mongo快
- mongodb分片集群。擴展方便,提高集群性能和高可用性
-
Mongo優缺點
- 優點
- 弱一致性,不要求強一致數據結構
- 高擴展性,任意擴展字段和數據庫集群節點
- mongodb是商業化的,發展穩定
- JSON存儲結構,開發高效
- 更現代化的可視化管理工具,比如MongodbCompase,NoSQLBooster,ClusterControl等
- 缺點
- 表關聯查詢支持弱,在部分復雜業務場景下,開發效率低
- 舊版本不支持事務(新版本3.6支持)
- 因為弱類型,可以更改數據類型,后期維護成本高
- 占用空間大,因為都是bson結構的key,value形式
- 優點
-
Mongo集群是否了解
- 什么是副本集
- 概念
- mongodb的高可用方案
- 應用場景
- 數據備份
- 數據恢復
- 讀寫分離
- 副本集最小可用方案
- 一主兩從
- 都有哪些獨寫策略
- primary 默認 所有的讀操作都從當前副本集主節點
- primaryPreferred 從主節點讀取數據,但是如果主節點不可用了,會從從節點讀取
- secondary 所有讀操作都從副本集的從節點讀取
- secondaryPreferred 從從節點進行讀操作,但是如果從節點都不可用了,從主節點讀取
- nearest 從副本集中延遲最低的成員讀取,不考慮成員的類型
- 概念
- 副本集配置
- 最小配置
- 一主兩從
- 一個主節點
- 一個復制集有且僅有一臺服務器處于Primary狀態,只有主節點才對外提供讀寫服務。如果主節點掛掉,復制集將投票選出一個備節點成為新的主節點
- 兩個備用節點
- 備用節點,復制集允許有多臺Secondary,每個備用節點的數據與主節點的數據是完全同步的。Recovering 恢復中,當復制集中某臺服務器掛掉或者掉線后數據無法同步,重新恢復服務后從其他成員復制數據,這時就處于恢復過程,數據同步后,該節點又回到備用狀態
- 一個仲裁節點
- 該類節點可以不用單獨存在,如果配置為仲裁節點,就主要負責在復本集中監控其他節點狀態,投票選出主節點。該節點將不會用于存放數據。如果沒有仲裁節點,那么投票工作將由所有節點共同進行
- 副本集健康檢測
- 每個成員都需要知道其他成員的狀態:哪個是主節點?哪個可以作為同步源?哪個掛掉了?為了維護集合的最新視圖,每個成員每隔兩秒鐘就會向其他成員發送個心跳請求(heartbeat request)。心跳請求的信息量非常小,用于檢查每個成員的狀態
- 數據同步和恢復
- 每個節點都有自己的oplog日志,該日志記錄了每一個寫操作,備份節點通過該日志同步數據,后續也通過該日志恢復數據
- 副本集搭建
- 更詳細的請查看這篇文章
- 副本集主節點選舉過程
- 觸發選舉的時機
- 副本集被加入了新節點
- 重啟副本集
- 使用rs.stepdown命令(將主節點降低)或使用rs.reconfig命令(重新配置現有副本集)
- 心跳檢測超時,比如主節點或副節點崩潰
- 選舉過程
- 選舉規則是根據票數來決定,票數最高,且獲得了“大多數”成員的投票支持的節點獲勝。“大多數”:假設復制集內投票成員數量為N,則大多數為 N/2 + 1。例如:3個投票成員,則大多數的值是2。當復制集內存活成員數量不足大多數時,整個復制集將無法選舉出Primary,復制集將無法提供寫服務,處于只讀狀態
- 若票數相同,且都獲得了“大多數”成員的投票支持的,節點狀態是健康的,節點優先級高且數據新的節點獲勝。數據的新舊是通過操作日志oplog來對比的,優先級可以通過人工配置
- 仲裁節點
- 提升集群可用性
- 假設現在有三個節點,其中兩個節點崩潰,那么按照N/2+1的選舉規則,3/2+1>1,但此時只有一個節點存活,所以無法進行投票,就導致集群不可用
- 有了仲裁節點后,仲裁節點不存儲數據,只用來選舉。那么你當前的節點數就是2,在兩個節點崩潰的情況下,因為有了仲裁節點的加入,使得集群可以正常投票選舉,繼續提供服務
- 提升集群可用性
- 為什么集群節點通常都要求是奇數而不是偶數
- 主要防止腦裂的情況
- 通常在集群間通信異常的時候,可能會分裂成兩個小集群
- 奇數的情況下,比如5臺機器,由于腦裂分裂成了以下兩組
- A:1節點 B:4節點
- A:2節點 B:3節點
- …
- 因為(總節點數/2)+1的模式,那么總會有一個小集群在選舉后繼續提供服務
- 偶數的情況下,比如4臺機器,由于腦裂分裂成了以下兩組
- A:1節點 B:3節點
- A:2節點 B:2節點
- …
- 因為(總節點數/2)+1的模式,其中第二種情況因為不滿足選舉模式,從而導致無法繼續提供服務
- 通常在集群間通信異常的時候,可能會分裂成兩個小集群
- 主要防止腦裂的情況
- 觸發選舉的時機
- 什么是分片
- 概念
- 把一張表中的數據分割到不同的服務器上進行保存。一般用在特別大量的數據庫集群中,比如一張表的數據幾百個g這種,通過分片到多臺服務器上。比如傳統的副本集,所有的數據都在一個服務器上,分片通過分布式存儲來提高查詢性能。
- 每一個分片集群都有一個主分片,其中包含該數據庫的所有未分片集合,存儲了所有未分片集合的數據
- 在初始化數據庫時,會選擇集群中數據量最少的分片作為主分片
- 組成
- mongos路由,用于將請求轉發給不同的分片,將多個分片的結果進行整合
- config-server,保存集群的元數據,比如分片的路由規則,服務器配置等等
- 分片,存儲數據庫數據,一個分片默認是64m。分片由分片鍵和分片算法組成
- 分片鍵
- 有了分片,但還需要解決數據塊的問題,如果所有的數據庫都存儲在一個分片,那么性能會打折扣。所以出現了分片鍵,分片鍵能夠將數據塊分為多個小塊分別存儲在不同的分片中來提升性能
- 范圍片鍵
- 可以高效的讀取連續范圍內的目標文檔。如果你使用范圍查詢,則可以比較快速的拿到所有的結果值。因為數據所在的數據chunk比較少
- hash片鍵
- 哈希分片在分片集群中提供了更均勻的數據分布,集合中那些具有近似值的文檔,可能會被分到不同的塊上
- 范圍片鍵
- 有了分片,但還需要解決數據塊的問題,如果所有的數據庫都存儲在一個分片,那么性能會打折扣。所以出現了分片鍵,分片鍵能夠將數據塊分為多個小塊分別存儲在不同的分片中來提升性能
- 每一個分片集群有一個主分片
- 每一個分片默認三個副本
- 分片鍵
- 概念
- 分片和副本集的區別
- 副本集
- 硬件和維護成本低
- 發生故障時,備份直接可以直接恢復服務
- 分片
- 維護成本高,因為將數據分部到了很多臺機器上
- 對數據流大的應用來說能顯著提高查詢性能
- 副本集
- 如果一個分片(Shard)停止或很慢的時候,發起一個查詢會怎樣
- 如果一個分片停止了,除非查詢設置了“Partial”選項,否則查詢會返回一個錯誤。如果一個分片響應很慢,MongoDB會等待它的響應
- 數據在什么時候才會擴展到多個分片(shard)里
- MongoDB 分片是基于區域(range)的。所以一個集合(collection)中的所有的對象都被存放到一個塊(chunk)中。只有當存在多余一個塊的時候,才會有多個分片獲取數據的選項。現在,每個默認塊的大小是 64Mb,所以你需要至少 64 Mb 空間才可以實施一個遷移。
- 其他
- 副本集默認每2秒檢測心跳,感知其他節點是否宕機,角色發生轉換等
- 副本集依賴oplog,mongo的寫操作日志來完成數據的同步的
- 副本集的主從同步機制核心是,主節點在完成寫操作后同步到oplog日志中,然后從節點主動拉取oplog日志中的日志
- 什么是副本集
-
都有哪些數據類型
- 和mysql類似,底層存儲的時候會有以下類型
- string
- array
- boolean
- date
- double
- int
- object
- 等等
- 和mysql類似,底層存儲的時候會有以下類型
-
ObjectId如何組成
- 一個24位數的字符串
- 前八位是時間戳
- 后六位是主機標識
- 后四位是進程id
- 后六位是自增的隨機數
- 一個24位數的字符串
-
什么是索引
- 索引是對數據庫表中一列或多列的值進行排序的一種結構,使用索引可快速訪問數據庫表中的特定信息。就好比書的目錄,有了目錄才能更精確的查找,否則只能進行全表搜索
-
常用的索引類型
- 主鍵索引
- 主鍵_id,數據庫默認插入的索引
- 唯一索引
- 針對某一個字段的索引
- 復合索引
- 多字段聯合的索引
- 主鍵索引
-
索引的優缺點
- 優點
- 提升數據查詢性能
- 缺點
- 占用額外的空間
- 維護索引成本
- 優點
-
mongodb索引數據結構和原理
- mongodb使用的是基于二叉樹的B樹
- 因為在大多數場景中,都是讀多寫少。所以相比較傳統的B+樹(只有葉節點存放數據,其余節點用來索引),mongodb的設計是為了更高的查詢性能而來。B樹的每個索引節點都會有Data域。所有對于B樹來說,只要找到對應的索引節點,就可以訪問,所以對于非范圍類查詢性能很高。
- 葉子節點:沒有孩子節點的節點叫作葉子節點
- 非葉子節點:跟葉子節點相反,有孩子節點的節點
- mongodb使用的是基于二叉樹的B樹
-
Mongo查詢計劃
- db.find().explain
- 都看哪些指標?
- stage 直接看出是否使用索引
- FETCH 根據索引位置查找
- IXSCAN 索引掃描
- COLLSCAN 全表掃描
- nReturned 返回了多少
- totalKeysExamined 索引掃描了多少
- totalDocsExamined 文檔掃描了多少
- executionTimeMillis 整體執行時長
- stage 直接看出是否使用索引
- 一般看著這幾個指標就能看出是否用到了索引,或者索引是否設計合理
-
數據庫語句執行過程
- 首選客戶端向服務器發送一個查詢請求
- 服務器先檢查是否有緩存,有緩存則返回,沒有緩存則執行下一個階段
- 緩存的重新發生在數據庫表數據大小,表結構等發生變更時
- 下一個階段
- sql解析
- 檢查sql是否合法,如果find查詢第一個參數指定的操作符不對,比如$in寫成了in,則mongo解析會拋異常
- 預處理
- 生成該sql的最優查詢計劃
- 執行勝出的查詢計劃并齒形
- 將結果返回給客戶端
- sql解析
-
數據庫cpu高的問題
- 瞬間有很多大量的慢查詢,一直沒有處理完,導致cpu飆升
- 是因為內部產品交互通過http,但是沒有控制頻率,導致大量請求瞬間打到服務和數據庫
- 當時解決的方案是
- 通過日志分析平臺,統計最近半小時的請求和頻次
- 查看代碼用到該查詢條件的代碼
- 找到了一個頻次異常的接口
- 找到了對應的代碼
- 鎖定到是內部運營平臺調用釋放客服號碼的接口,這個接口是釋放一個租戶內的所有號碼,少則幾十個,多則幾百上千。運營平臺來調用的時候沒有控制頻率,導致瞬間幾千個請求打過來。數據庫的cpu就飆升了
- 然后聯系到使用運營平臺的人,暫停這個操作(因為系統有記錄操作人)。暫停后,數據庫負載恢復,后續我們的接口增加頻次限制,運營平臺也增加調用接口的頻次控制
- 內部報表統計接口查詢報表,因為報表的數據流大,查詢時間有時候會在幾秒,統計的時候沒有注意頻次,cpu也會飆升
- 瞬間有很多大量的慢查詢,一直沒有處理完,導致cpu飆升
-
數據庫內存高的問題
- 某一天數據庫的內存頻繁告警超過80%,于是先查看數據庫慢查詢(懷疑是查詢的結果集過大),然后鎖定到了幾張表。
- 查找幾個涉及到這個表查詢的業務,導出,對外接口
- 然后就分析導出和對外接口的調用頻次
- 發現有個請求,指定的查詢結果數是1000,平均每秒1-2次請求
- 然后我們內部有一個限流器
- 根據ip限制它一分鐘只能調用20次
- 查詢結果,一次最多100個
- 然后數據庫的內存占用量就恢復了
-
數據庫流量高的問題
- 和上面的問題一樣
-
Mongo底層存儲結構
- mongo使用的是WiredTiger存儲引擎,該引擎使用B樹作為數據結構
- B樹是一種自平衡的搜索,和二叉樹不同的是,二叉樹最多兩個子節點,而B樹是每個節點的關鍵字=節點數-1
- 每個樹節點都存有數據,所以只要找到索引就可以進行訪問,更適合查詢
-
性能優化
在上面"NodeJs如何提升性能和代碼質量"中有提到 -
慢查詢分析
- db.system.profile.find({“millis”:{$gte:8000}}).limit(10).pretty();比如查詢十條時間大于8000毫秒的sql語句并對結果進行美化
- 該慢查詢結果將返回查詢消耗的時間,查詢指定,查詢條件等
- 然后我們可以再使用explain對該查詢條件進行分析,在上面的"Mongo查詢計劃"已提到查詢計劃的關鍵指標
- 在知道具體的慢查詢庫,表和查詢條件后,我們可以查看服務代碼來進行排查和優化
- db.system.profile.find({“millis”:{$gte:8000}}).limit(10).pretty();比如查詢十條時間大于8000毫秒的sql語句并對結果進行美化
Redis
-
和其他傳統數據庫區別
- redis的sql語句和傳統的不同
- redis基于內存內存,省去了磁盤操作,所以性能高
- redis多用于熱點數據查詢,不像傳統數據庫用來存儲大量數據
-
Redis應用場景都有哪些
- 熱點數據緩存
- 緩存數據共享
- 分布式鎖 nx
- 計數器 incr
- 單點登錄
-
數據結構
- string
- 字符串是所有編程語言中最常見和最常用的數據類型,而且也是redis最基本的數據類型之一,而且redis中所有key的類型都是字符串,它是一個由字節組成的序列,在Rediss中是二進制安全的。它是標準的key-value,通常用于存儲字符串、整數和浮點。Value可容納高達512MB的數據
- list
- 列表是一個雙向可讀可寫的管道,其頭部是左側,尾部是右側,一個列表可以最多包含2^32-1個元素,即4294967295個元素
- set
- set是string類型的無序集合,集合中的成員是唯一的,這就意味著集合中不能出現重復的數據,可以在兩個不同的集合中對數據進行比對并取值
- zset
- redis有序集合和集合一樣,也是string類型元素的集合,且不允許重復的成員,不同的是每個元素都會關聯一個double雙精度浮點數類型的分數,redis正是通過該分數來為集合中的成員進行從小到大的排序,有序集合成員是唯一的,但分數卻可以重復,集合是通過hash表實現的,所以添加,刪除,查找的復雜度是O(1),集合中最大的成員數是2^32-1,每個集合可以存儲40多億成員
- hash
- hash是一個string類型的feild和value的映射表,hash特別適合用于存儲對象,redis中每個hash可以存儲40多億鍵值對
- string
-
常用操作語句
- string
- set/get 存儲和獲取key
- mset/mget 批量存儲和獲取多個key
- append 對某個key的值做追加操作
- incr 對數值做遞增操作
- exists 判斷某個key是否存在
- expire 設置過期時間
- ttl 查看過期時間
- persist 取消key的過期時間變為永久
- list
- lpush 生成列表并插入數據
- llen 獲取列表長度
- set
- sadd 生成無序集合
- smembers 獲取無序集合set1的所有數據
- sdiff 差集
- sinter 交集
- sunion 并集
- zset
- zadd 生成有序集合
- zcard 獲取集合長度
- hash
- hset 生成hash key
- hdel 刪除一個hash key的字段
- hgetall 獲取指定hash的所有key,value
- string
-
為什么Redis快
- 基于內存,省去了操作磁盤的時間
- 單線程,減去了多線程的上下文切換和鎖的問題
- 多路復用,類似nodejs事件循環,所有的讀寫都放入事件循環中,內部用多線程來執行
-
常用的數據結構有哪些
- set 簡單字符串存儲
- exists 是否存在指定key
- del 刪除key
- mset 存儲多個鍵值對
- incr 計數器
- expire 過期時間
-
緩存擊穿
- 概念
- 高并發流量,訪問的這個數據是熱點數據,請求的數據在 DB 中存在,但是 Redis 存的那一份已經過期,后端需要從 DB 加載數據并寫到 Redis
- 但由于高并發,可能會把數據庫擊垮,導致服務不可用
- 解決方案
- 熱點key不設置過期時間
- 給熱點key的過期時間設置一個隨機值,避免同一時間過期
- 給一個1-10分鐘的過期時間
- 概念
- 問題場景
- 比如用戶信息
緩存穿透
- 概念
- 意味著有特殊請求在查詢一個不存在的數據,即數據不存在 Redis 也不存在于數據庫。導致每次請求都會穿透到數據庫,緩存成了擺設,對數據庫產生很大壓力從而影響正常服務
- 解決方案
- 緩存空值:當請求的數據不存在 Redis 也不存在數據庫的時候,設置一個缺省值(比如:None)。當后續再次進行查詢則直接返回空值或者缺省值
- 問題場景
- 在緩存擊穿的基礎上,數據庫也查不到,也沒有一個默認值,導致一直查詢
緩存雪崩
- 概念
- 大量的請求無法在 Redis 緩存系統中處理,請求全部打到數據庫,導致數據庫壓力激增
- 解決方案
- 過期時間添加隨機值
- http接口限流
- 服務熔斷降級
- 問題場景
- 大量熱點數據同時過期,導致大量請求需要查詢數據庫并寫到緩存;
- Redis 故障宕機,緩存系統異常
過期策略
- 定期策略
- 當Redis運行到設定的時期時會在具有過期設置的key中隨機測試一些key,并且把其中過期的key從內存中刪除
- 具體來說,Redis 每秒執行 10 次:
- 從具有關聯過期的key集中測試 20 個隨機key
- 刪除所有發現過期的key
- 如果超過 25% 的key已過期,則從步驟 1 重新開始
- 當某個客戶端試圖訪問key時,發現該key已超時會把此key從內存中刪除
內存淘汰策略
- noeviction:當內存使用達到閾值的時候,所有引起申請內存的命令會報錯
- allkeys-lru:在主鍵空間中,優先移除最近未使用的key
- volatile-lru:在設置了過期時間的鍵空間中,優先移除最近未使用的key
- allkeys-random:在主鍵空間中,隨機移除某個key
- volatile-random:在設置了過期時間的鍵空間中,隨機移除某個key
- volatile-ttl:在設置了過期時間的鍵空間中,具有更早過期時間的key優先移除
- 配置方法
- 編輯redis.conf,更改maxmemory-policy配置
- redis默認是noeviction
持久化機制,優缺點
-
RDB
-
概念
- 在指定時間將當前時刻內存中的數據生成一個快照文件(.rdb文件,默認為dump.rdb),并將這個快照文件保存到磁盤上。這樣,即使redis宕機了,下次重啟時也可以通過讀取這個快照文件來恢復數據
-
備份策略
- save 3600 1 -> 3600秒內有1個key被修改,則觸發RDBsave 300 100 -> 300秒內有100個key被修改,則觸發RDBsave 60 10000 -> 60秒內有10000個key被修改,則觸發RDB
-
重寫操作是redis主進程fork一個子進程來處理,避免阻塞主進程
-
redis默認是rbd策略
-
-
AOF
-
概念
- AOF是redis提供的另一種數據持久化方式,它會記錄客戶端對redis服務端的每一次寫操作,并將這些寫操作以redis協議追加保存到后綴為aof的文件末尾。在redis服務器重啟時,會讀取并加載aof文件,達到恢復數據的目的。
-
開啟aof
- 更改reids.conf中的appendonly配置,0為不開啟,1為開啟
-
備份策略
- appendfsync always
- 客戶端對redis服務器的每次寫操作都寫入AOF日志文件。這種方式是最安全的方式,但每次寫操作都進行一次磁盤IO,非常影響redis的性能,所以一般不使用這種方式。
- appendfsync everysec
- 每秒刷新一次緩沖區中的數據到AOF文件。這種方式是redis默認使用的策略,是考慮數據完整性和性能的這種方案,理論上,這種方式最多只會丟失1秒內的數據
- appendfsync no
- redis服務器不負責將數據寫入到AOF文件中,而是直接交給操作系統去判斷什么時候寫入。這種方式是最快的一種策略,但丟失數據的可能性非常大,因此也是不推薦使用的
- appendfsync always
-
AOF重寫
-
背景
- AOF是Redis增量模式的持久化方式,隨著redis的持續運行,會不斷有新的數據寫入AOF文件中,逐漸占用大量磁盤空間,還會降低Redis啟動速度。Redis中有rewrite機制來合并AOF歷史記錄。
- 比如當我們對同一個key做多次寫操作時,就會產生大量針對同一個key操作的日志指令,導致AOF文件會變得非常大,恢復數據的時候會變得非常慢
-
觸發命令 bgrewriteaof
-
觸發策略
- auto-aof-rewrite-percentage 100
- 當文件的大小達到原先文件大小(上次重寫后的文件大小,如果沒有重寫過,那就是redis服務啟動時的文件大小)的兩倍
- auto-aof-rewrite-min-size 64mb
- 文件重寫的最小文件大小,即當AOF文件低于64mb時,不會觸發重寫
- auto-aof-rewrite-percentage 100
-
重寫流程
-
(1)bgrewriteaof觸發重寫,判斷是否存在bgsave或者bgrewriteaof正在執行,存在則等待其執行結束再執行;
(2)主進程fork子進程,防止主進程阻塞無法提供服務;
(3)子進程遍歷Redis內存快照中數據寫入臨時AOF文件,同時會將新的寫指令寫入aof_buf和aof_rewrite_buf兩個重寫緩沖區,前者是為了寫回舊的AOF文件,后者是為了后續刷新到臨時AOF文件中,防止快照內存遍歷時新的寫入操作丟失;
(4)子進程結束臨時AOF文件寫入后,通知主進程;
(5)主進程會將上面的aof_rewirte_buf緩沖區中的數據寫入到子進程生成的臨時AOF文件中;
(6)主進程使用臨時AOF文件替換舊AOF文件,完成整個重寫過程。
-
-
-
-
RDB優缺點
- 優點
- 體積比aof小,rdb更緊湊
- 恢復的速度快,因為rdn是快照型數據,不用重新讀取并寫入內存
- 缺點
- 因為有同步間隔,所以宕機時畢竟存在數據丟失
- 雖然是異步備份,但當redis機器負載高的時候,備份的時間也會加長
- 優點
-
AOF優缺點
- 優點
- 數據備份比RDB可靠,因為可以設置每次寫入追加
- 自動重寫機制,縮小aof文件
- 缺點
- 對redis性能有損耗,畢竟每次寫入都同步
- aof文件體積大,恢復時間長
- 優點
redis高可用
- 主從模式
- 概念
- 和傳統數據庫一樣,一主兩從,用來做數據備份,提高數據庫讀寫能力的
- 應用場景
- 讀寫分離
- 主寫,從讀,降低主節點壓力,提高查詢性能
- 數據備份
- 機器宕機,磁盤損壞等,可以用其他節點數據進行備份
- 提高服務高可用性
- 至少一主兩從,主節點掛了,從節點頂上
- 讀寫分離
- 數據同步策略
- 全量同步
- 發生在Slave初始化階段,這時Slave需要將Master上的所有數據都復制一份
- 1)從服務器連接主服務器,發送SYNC命令;
2)主服務器接收到SYNC命名后,開始執行BGSAVE命令生成RDB文件并使用緩沖區記錄此后執行的所有寫命令;
3)主服務器BGSAVE執行完后,向所有從服務器發送快照文件,并在發送期間繼續記錄被執行的寫命令;
4)從服務器收到快照文件后丟棄所有舊數據,載入收到的快照(快照文件先接收保存到磁盤,最后加載到內存中);
5)主服務器快照發送完畢后開始向從服務器發送緩沖區中的寫命令;
6)從服務器完成對快照的載入,開始接收命令請求,并執行來自主服務器緩沖區的寫命令;
- 增量同步
- Slave初始化后開始正常工作時主服務器發生的寫操作同步到從服務器的過程。 增量復制的過程主要是主服務器每執行一個寫命令就會向從服務器發送相同的寫命令,從服務器接收并執行收到的寫命令。
- Redis增量復制是指Slave初始化后開始正常工作時主服務器發生的寫操作同步到從服務器的過程。
增量復制的過程主要是主服務器每執行一個寫命令就會向從服務器發送相同的寫命令,從服務器接收并執行收到的寫命令
- 全量同步
- 概念
- 哨兵
- 概念
- 用于監控Redis集群中Master狀態的工具,是Redis高可用解決方案,哨兵可以監視一個或者多個redis master服務,以及這些master服務的所有從服務。 某個master服務宕機后,會把這個master下的某個從服務升級為master來替代已宕機的master繼續工作。
- 即使后來之前的master重啟服務,也不會變回master了,而是作為slave從服務
- 應用場景
- 當主服務器宕機時,需要將從服務器手動切換(slaveof no one)到主從服務器,這需要人工干預。
- 作用
- 心跳檢測
- Sentinel會不斷檢查主服務器和從屬服務器是否正常運行
- 通知
- 當受監控的Redis服務器出現問題時,Sentinel會通過API腳本向管理員或其他應用程序發送通知
- redis發布訂閱來完成
- 自動故障轉移
- 當主節點無法正常工作時,Sentinel將啟動自動故障轉移操作。它將與發生故障的主節點處于主從關系的從節點之一升級到新的主節點,并將其他從節點指向新的主節點
- 心跳檢測
- 概念
- 哨兵自動選舉機制
- 沒有哨兵的情況下,主從模式需要人為干預才能夠正常的進行選舉,并且后續的ip更換操作也很繁瑣
- 哨兵每秒會給所有節點發送心跳檢測,如果心跳檢測超時則認定為死忙
- 為了避免在網絡阻塞或者機器負載高的情況下影響單個哨兵的能力,通常會搭建哨兵集群,三個哨兵節點
- 當一個哨兵判斷主節點下線后會通知其他哨兵根據自己掌握的節點狀況進行投票和選舉
- 其投票通過數和mongodb一樣,(N/2)+1,比如三個哨兵,那么閾值為2
- 投票通過后又由誰來進行主從的切換?
- 哨兵內部還會再進行主哨兵選舉,先發現主節點故障的哨兵擁有優先投票權,每一個哨兵只能投票一次
- 哨兵集群數和數據庫集群數類似,都是奇數,為了防止腦裂
redisAOF問題,更詳細的請查看這篇文章
AOF重寫導致的redis子進程崩潰,服務重啟。解決方案是先優化代碼中的大key,延遲重寫時機,保證服務可用,比如超過上一重寫文件的多少倍后再重寫。待redis寫入量沒那么高時,再調整會重寫策略,提前重寫即可。主要是大key占用了不必要的空間。
分布式鎖的使用和問題,解決方案
- 應用場景
- 多個定時器任務掃描,如何避免同一個任務重復處理
- setnx
- 存在返回0
- 不存在返回1
- setex
- 解決定時器問題,設置鎖的同時設置過期時間
什么是緩存重建
- 熱點key大量失效
- key不過期
- 或者惰性創建,訪問的時候,先檢查,有則讀取,沒有則創建
redis是單線程還是多線程,6.0以后多線程解決了什么問題
- 單線程,減去了多線程的上下文切換和鎖的問題
- 多路復用,類似nodejs事件循環,所有的讀寫都放入事件循環中,內部用多線程來執行
- 多線程還是為了解決io性能,引入多線程,每一個線程維護一個事件隊列
redis事務
-
MULTI,EXEC,DISCARD,WATCH 四個命令是 Redis 事務的四個基礎命令。其中:
MULTI,告訴 Redis 服務器開啟一個事務。注意,只是開啟,而不是執行 。
EXEC,告訴 Redis 開始執行事務 。
DISCARD,告訴 Redis 取消事務。
WATCH,監視某一個鍵值對,它的作用是在事務執行之前如果監視的鍵值被修改,事務會被取消。 -
應用場景
- 數據一致性
redis發布訂閱
- 和node事件機制一樣,通過on監聽,發送等
cpu高
- 有一天上午十點多redis告警cpu占用率高,于是就排查最近代碼,發現有人用了keys*來查詢所有
- 當時立馬改代碼升級優化了一些
- 后面還發現有一些大key,存儲了一個賬戶的所有座席配置,頻繁獲取也會導致redis cpu飆升
內存高
- 有一次線上的redis機器內存告警
- 我們先查看目前存在的大key(數據量比較大的key)
- bigkeys命令可以查看,但只能看到String、hash、list、set、zset這五種類型
- 優點是可以在線掃描,不阻塞服務;缺點是信息較少,內容不夠精確。
- 使用redis-rdb-tools工具,它實例上執行bgsave,bgsave會觸發redis的快照備份,導出csv形式的文件,然后就可以進行分析了
- bigkeys命令可以查看,但只能看到String、hash、list、set、zset這五種類型
- 解決方案
- 當時這個大key hset占用了5m的空間,寸的時候是全量存進去。但實際使用只用了幾個字段。所以當時就立刻更改代碼,對這種key進行拆分,只存必要的字段。就解決了這個問題
- 我們先查看目前存在的大key(數據量比較大的key)
性能優化
- key的命名盡量簡短,節省空間,提升搜索性能
- 不要使用keys *,keys *, 這個命令是阻塞的,即操作執行期間,其它任何命令在你的實例中都無法執行。當redis中key數據量小時到無所謂,數據量大就很糟糕了。可以去使用SCAN進行滾動分頁查詢解決。
- 合理的設置key的有效期一些不應該長期存儲的key,比如驗證碼,臨時的token,session等都應該設置有效期,避免長期占用孔間
- 合理的關閉AOF持久化存儲,對于某些不需要進行持久化存儲的業務可以關閉AOF,因為每次AOF重寫都需要占用額外的cpu和內存
- 避免使用大key,比如一個key的值上m甚至幾十m時,嚴重影響查詢性能并且占用空間
- 集群擴展
- 冷熱數據分離
慢查詢分析
- redis慢查詢分析支持兩個參數配置
- slowlog-log-slower-than 預設閾值,單位微妙(默認10000)。超過預設閾值的才會被記錄到慢查詢日志中
- slowlog-max-len 慢查詢日志的列表長度(能記錄的慢日志條數)。一個新的命令滿足慢查詢條件時被插入到這個列表中, 當慢查詢日志列表已處于其最大長度時, 最早插入的一個命令將從列表中移出
- slowlog-log-slower-than 的默認值是 10000 微秒,也就是 10 毫秒。
- slowlog-max-len 的默認值是 128,也就是說慢查詢命令隊列可以保存 128 條慢查詢記錄
- config set slowlog-log-slower-than 100 設置慢查詢閾值為100毫秒
- config set slowlog-max-len 200 設置慢查詢日志保存行數為200
- slowlog get 1 獲取最近一條慢查詢,如果不帶數字則進行滾動獲取127.0.0.1:6379> slowlog get 1 1) 1) (integer) 72) (integer) 16101562323) (integer) 244) 1) "slowlog"2) "get"5) "127.0.0.1:58406"6) ""
-
記錄的慢查詢標號,倒序顯示
-
記錄該命令的時間戳
-
執行命令的耗時,微秒為單位
-
執行的具體命令
-
執行該命令客戶端的 IP 地址和端口號
架構
微服務的優缺點
- 優勢
- 每一個應用都是獨立的服務
- 相比較單點服務,服務的升級和迭代不影響其他服務
- 服務性能提升
- 服務拆分后,體積變小,也沒有其他服務的代碼影響,整體性能提升
- 更適合快速迭代
- 相比較單點服務,每次發布都是一個整體,編譯,打包時間長。微服務編譯和打包時間短
- 單一職責
- 隨著服務和團隊的擴大,單體服務已經不能滿足多個團隊間的協作,因為一個團隊的代碼變動可能影響其他團隊。所以拆分后,每個團隊變更自己的服務,不影響其他人的
- 每一個應用都是獨立的服務
- 劣勢
- 分布式部署后,調用關系變得復雜了,之前都是單點,服務內部就通信即可。現在多個服務都是http和消息中間件調用,維護成本變高
- 測試難度提升,一個服務的接口變動會影響其他調用方
- 運維難度提升,之前只需要升級,監控單點服務。現在多個服務,每一個服務的升級,監控都是更高的成本問題
什么是restful風格
-
簡稱rest,它是一種軟件架構風格、設計風格,而不是標準,只是提供了一組設計原則和約束條件,它主要用于客戶端和服務端交互類的軟件。基于這個風格設計的軟件可以更簡介,更有層次
-
特性
- 資源
- 可以用一個URI(統一資源定位符)指向它,每種資源對應一個特性的URI。要獲取這個資源,訪問它的URI就可以,因此URI即為每一個資源的獨一無二的識別符
- 表現層
- 比如,文本可以用txt格式表現,也可以用HTML格式、XML格式、JSON格式表現,甚至可以采用二進制格式。
- 狀態轉換
- 四個表示操作方式的動詞:GET、POST、PUT、DELETE。他們分別對應四種基本操作:GET用來獲取資源,POST用來新建資源,PUT用來更新資源,DELETE用來刪除資源
- 資源
-
如何設計
-
路徑設計:數據庫設計完畢之后,基本上就可以確定有哪些資源要進行操作,相對應的路徑也可以設計出來。
動詞設計:也就是針對資源的具體操作類型,有HTTP動詞表示,常用的HTTP動詞如下:POST、DELETE、PUT、GET
-
常用的設計模式,應用場景,解決了什么問題
- 單例 業務隔離
- 單例模式可以保證內存里只有一個實例,減少了內存的開銷
- 可以避免對資源的多重占用
- 更專注,只做一件事
- 適配器模式 繼承
- 提高代碼重用性
- 提高代碼的擴展性
- 開放封閉
- 當需要改變一個程序的功能或者給這個程序增加新功能的時候,可以使用增加代碼的方式,盡量避免改動程序的源代碼,防止影響原系統的穩定
- 策略模式
- 避免多重判斷
- 擴展性高
- 工廠模式
- 主要用來初始化對象,規范對象初始化過程,節省對象的初始化成本
什么是DDD設計模式,應用場景,解決了什么問題
- 概念
- 領域驅動設計模式,把業務拆分成細粒度的領域,解決因為項目龐大而導致的耦合性問題
- application 應用層 屬于業務的上層,用來編排業務層
- domain 領域層 實現具體業務處理,service邏輯層
- interface 接口層,接收接口調用
- repository 數據庫倉儲,處理數據庫操作
- infrastructure 基礎設施層,處理常量,通用方法等
- 只做增刪改查 不做任何其他業務
- 引入VO,DTO,DO,PO概念
- VO
- 視圖對象,主要對應界面顯示的數據對象。對于一個 WEB 頁面,或者 SWT、SWING 的一個界面,用一個 VO 對象對應整個界面的值
- DTO
- 數據傳輸對象,接收前端傳遞來的對象值以及接收時的校驗等
- DO
- 領域對象,就是從現實世界中抽象出來的有形或無形的業務實體
- PO
- 持久化對象,它跟持久層(通常是關系型數據庫)的數據結構形成一一對應的映射關系,也就是數據庫實例化對象
- VO
- 應用場景
- 項目邏輯性復雜,規模較大項目適合用DDD模式
- 解決了以下問題
- 傳統的MVC模式的業務層全部由service承擔,這就導致service層代碼臃腫且越來越難維護,模塊間的關系難以梳理,模塊間的耦合度過高,比如一個service方法可能包含,參數校驗,參數解析,業務處理,異步事件,消息隊列,數據庫操作等。有個ddd,那么一個service層就被拆分為基礎設施層,應用層,領域層,倉儲層
- 在劃分領域的基礎上,又對實體對象進行了劃分,分為VO,DTO,DO,PO。業務對象的細分一方面使得業務更加便于理解和區分,也讓業務間的實例化對象隔離,避免污染而造成后續的維護等問題
Kakfa
-
概念
- 業務解耦
- 傳統的http業務,每一個接收方的變動都可能導致推送方的代碼有所變動,久而久之,非常不利于維護。有了消息隊列,那么推送放只負責推送消息,對于接收方如何處理,它不需要關心,從而達到了解耦。
- 削峰
- 傳統模式,并發量大的會導致業務異常,比如數據庫負載過高。而消息隊列會根據對方的消費能力來處理,可能會有短暫的擠壓,但不會導致數據庫負載過高
- 可靠性傳輸
- 傳統http如果沒有重試機制,會導致請求丟失。kafka會保證被消費者接收
- 業務解耦
-
組成
- 生產者 消息發送方
- 實例 kafka服務器, Kafka服務器,負責消息存儲和轉發
- topic 消息類別,Kafka按照topic來分類消息
- 分區 用于提高kakfa吞吐量
- 副本 每個分區有多個副本 默認10個,不能大于broker的數量,用于做主備切換
- 消費者 接受消息
- 消費者組 多個消費者在一個消費組,一個分區的數據只能被一個組內的一個消費者消費。同一個消費者可以處理同一個主題下不同分區的數據
- zookeeper 保證kafka集群的通信,配置和可用性
-
當前使用的是2.8.0版本
-
消費者獲取消息的機制
- 通過拉取的方式
- pull模式消費者自主決定是否批量從broker拉取數據,而push模式在無法知道消費者消費能力情況下,不易控制推送速度,太快可能造成消費者奔潰,太慢又可能造成浪費
- 生產者會發送給訂閱這個主題的分區,如果指定分區,則發給指定分區。如果沒有指定,則輪詢發送。
-
kafka為什么用zookeeper
- 主要用于在集群中不同節點之間進行通信,在 Kafka 中,它被用于提交偏移量,因此如果節點在任何情況下都失敗了,它都可以從之前提交的偏移量中獲取,除此之外,它還執行其他活動,如: leader 檢測、分布式同步、配置管理、識別新節點何時離開或連接、集群、節點實時狀態等等
-
消息同步機制
- ack=0 不關注消費者是否收到消息
- ack=1 只要leader節點收到通知 就算成功(kafka默認選項)
- ack=-1 所有復制節點收到通知才算成功
-
什么是分區
- 如果你創建的topic有5個分區,當你一次性向 kafka 中推 1000 條數據時,這 1000 條數據默認會分配到 5 個分區中,其中每個分區存儲 200 條數據
- 這樣做的目的,就是方便消費者從不同的分區拉取數據,假如你啟動 5 個線程同時拉取數據,每個線程拉取一個分區,消費速度會非常非常快!
- 發送消息時先發給主分區,主分區同步給子分區
- 分配機制
- 1、數據在寫入的時候可以指定需要寫入的分區,如果有指定,則寫入對應的分區
- 2、如果沒有指定分區,但是設置了數據的key,則會根據key的值hash出一個分區
- 3、如果既沒指定分區,又沒有設置key,則會輪詢選出一個分區
- 分配機制
- 消費者從主分區中拉取消息
-
kafka消息順序
- 同一個分區內的消息是有序的
- 多個分區是無序的
-
消費者分區分配策略
- RangeAssignor 范圍分配
- kafka默認的分配策略,假如現在有 10 個分區,3 個消費者,排序后的分區將會是0,1,2,3,4,5,6,7,8,9;消費者排序完之后將會是C1-0,C2-0,C3-0。那么第一個消費者就多消費一個分區
- 默認按照編號排序分配
- RangeAssignor 范圍分配
-
kafka如何跟蹤消費狀態
- 通過offset偏移量來標記消費的位置
-
分區策略
- 輪詢 一次將消息發給該topic下所有分區
- 指定分區
-
kafka為什么性能強
- 利用分區提高集群負載
- 多個分區
- 數據緩存
- 如果在 Cache 中存在該數據且是最新的,則直接將數據傳遞給用戶程序,免除了對底層磁盤的操作,提高了性能
- 批處理
- 控制分批發送
- 利用分區提高集群負載
-
應用場景
- 日志收集
- 對接多方,需要業務解耦
- 用戶登錄退出事件
- 企業賬單
- 通話事件
- 報表事件
-
常見面試題
- kafka中的broker 是干什么的
- broker 是消息的代理,Producers往Brokers里面的指定Topic中寫消息,Consumers從Brokers里面拉取指定Topic的消息,然后進行業務處理,broker在中間起到一個代理保存消息的中轉站。
- kafka中的 zookeeper 起到什么作用,可以不用zookeeper么
- zookeeper 是一個分布式的協調組件,早期版本的kafka用zk做meta信息存儲,consumer的消費狀態,group的管理以及 offset的值。考慮到zk本身的一些因素以及整個架構較大概率存在單點問題,新版本中逐漸弱化了zookeeper的作用。新的consumer使用了kafka內部的group coordination協議,也減少了對zookeeper的依賴,
但是broker依然依賴于ZK,zookeeper 在kafka中還用來選舉controller 和 檢測broker是否存活等等。
- zookeeper 是一個分布式的協調組件,早期版本的kafka用zk做meta信息存儲,consumer的消費狀態,group的管理以及 offset的值。考慮到zk本身的一些因素以及整個架構較大概率存在單點問題,新版本中逐漸弱化了zookeeper的作用。新的consumer使用了kafka內部的group coordination協議,也減少了對zookeeper的依賴,
- Kafka中的ISR、AR又代表什么?ISR的伸縮又指什么
- ISR:In-Sync Replicas 副本同步隊列。AR:Assigned Replicas 所有副本
- kafka中consumer group 是什么概念
- 同樣是邏輯上的概念,是Kafka實現單播和廣播兩種消息模型的手段。同一個topic的數據,會廣播給不同的group;同一個group中的worker,只有一個worker能拿到這個數據。換句話說,對于同一個topic,每個group都可以拿到同樣的所有數據,但是數據進入group后只能被其中的一個worker消費。group內的worker可以使用多線程或多進程來實現,也可以將進程分散在多臺機器上,worker的數量通常不超過partition的數量,且二者最好保持整數倍關系,因為Kafka在設計時假定了一個partition只能被一個worker消費(同一group內)
- kafka的消費者是pull(拉)還是push(推)模式,這種模式有什么好處
- kafka遵循了一種大部分消息系統共同的傳統的設計:producer 將消息推送到 broker,consumer 從broker 拉取消息
- 優點:pull模式消費者自主決定是否批量從broker拉取數據,而push模式在無法知道消費者消費能力情況下,不易控制推送速度,太快可能造成消費者奔潰,太慢又可能造成浪費
- 缺點:如果 broker 沒有可供消費的消息,將導致 consumer 不斷在循環中輪詢,直到新消息到到達。為了避免這點,Kafka 有個參數可以讓 consumer阻塞知道新消息到達(當然也可以阻塞知道消息的數量達到某個特定的量這樣就可以批量發送)
- kafka遵循了一種大部分消息系統共同的傳統的設計:producer 將消息推送到 broker,consumer 從broker 拉取消息
- kafka 如何不消費重復數據?比如扣款,我們不能重復的扣
- 這個問題換種問法,就是kafka如何保證消息的冪等性。對于消息隊列來說,出現重復消息的概率還是挺大的,不能完全依賴消息隊列,而是應該在業務層進行數據的一致性冪等校驗
- 比如你處理的數據要寫庫(mysql,redis等),你先根據主鍵查一下,如果這數據都有了,你就別插入了,進行一些消息登記或者update等其他操作。另外,數據庫層面也可以設置唯一健,確保數據不要重復插入等 。一般這里要求生產者在發送消息的時候,攜帶全局的唯一id
- kafka Stream
- Kafka Streams是一個客戶端程序庫,用于處理和分析存儲在Kafka中的數據,并將得到的數據寫回Kafka或發送到外部系統。可以說是消息轉換器,在消息發送之前進行轉換后再重新發送。可以針對部分業務場景做數據化定制,或者在不改變生產者業務的前提下來轉換消息體
- kafka中的broker 是干什么的
-
集群
- kafka最小可用集群是三個節點,也就是三個broker,和傳統集群類似,一個主節點,兩個從節點
-
消息隊列常見問題
- 消息堆積
- 某天運維監控系統告警,kafka的消息堆積超過閾值200,于是進入系統查看發現某topic下的消息堆積已經達到了220
- 于是查看代碼中接收該topic消息的代碼,先是排查了對第三方接口同步調用的日志,均在200毫秒內返回,又查看企業內部調用,發現有一個數據同步接口延遲最高可達30s
- 聯系到該部分的同事,經過排查發現是因為該接口代碼近期有更新,增加了部分查詢數據和邏輯復雜的運算,導致上層應用消費能力下降,從而導致上層的消息堆積
- 臨時的解決方案是當天該部門回滾代碼,保證服務可用
- 最終解決方案是,該接口只負責接收和存儲事件。具體的業務處理交給內部服務來進行定時的處理
- 上層應用也將部分第三方,其他部門的接口調用改為了異步或超時處理
- 消息重復
- 同樣是上面的問題,同步的延遲導致觸發了kafka的重試,數據庫中被插入了重復數據,同時重復的調用了其他服務的接口,也導致了其他服務存儲了重復的數據
- 后續我們將部分對冪等性要求高的業務統一做了數據校驗重復方案,就解決了此問題
- 消息堆積
Apollo
- 什么是apollo
- 一款可靠的分布式配置管理中心,誕生于攜程框架研發部,能夠集中化管理應用不同環境、不同集群的配置,配置修改后能夠實時推送到應用端,并且具備規范的權限、流程治理等特性,適用于微服務配置管理場景
- 特點,應用場景
- 統一管理不同環境,不同集群的配置
- 配置實時更新
- 版本發布
- 灰度發布
- 權限管理
- 組成
- application 應用
- environment 環境
- cluster 集群
- namespace 命名空間
- 組件
- config service
- admin service
- protal
私有包使用nexus作為倉儲
Docker
- 什么是docker
- 使用go編寫,開源的容器引擎。docker可以讓開發者打包他們的應用和依賴到一個輕量的容器中,然后可以發布到任意的linux平臺。
- dockerfile
- 用來構建docker鏡像的文本文件,包含了構建鏡像的指令和說明等
- 應用場景和優缺點
- 比起傳統sh腳本,docker鏡像更適合自動編譯,打包和發布
- 自動化集成,cicd
- 維護成本低,容錯率高
- 隔離運行環境
- 常用命令
- docker info 查看docker版本信息
- docker images 查看鏡像列表
- docker search 鏡像名稱 搜索鏡像
- docker run 鏡像名稱 運行鏡像
- docker ps -a 查看容器列表
- docker image rm 鏡像名 刪除某一個鏡像
- 你們公司都是如何使用docker的
- 公司內部使用k8s來管理和編排docker。
- 在ci階段,使用dockerfiler完成編譯,打包。然后將鏡像推送到harbor倉儲。到cd階段,上k8s環境更改鏡像id,觸發k8s的更新后,自動滾動發布。
K8s
-
什么是k8s,和docker區別
- 概念
- 一個容器編排工具,可以滾動升級,對運行的容器狀態進行監控,還有健康檢查等
- docker是一個容器部署工具,運行環境隔離,有自己的文件系統等
- 優點
- 運行管理隔離,相互不影響
- 有利于分布式,因為容器輕量,每一個應用都可以當作一個容器
- 缺點
- 隨著容器的增加,容器的管理是個大問題
- 服務升級如何保證不中斷
- 服務運行狀態的監控
- 容器的監控
- 優點
- 概念
-
特點,應用場景和優缺點
- 中大型應用使用k8s比較合適,因為隨著應用體積變大,運維監控成本會變高
-
k8s的組成
- apiserver
- Kubernetes API 服務器驗證并配置 API 對象的數據, 這些對象包括 pods、services、replication、controllers 等。 API 服務器為 REST 操作提供服務,并為集群的共享狀態提供前端, 所有其他組件都通過該前端進行交互
- etcd
- etcd 是兼具一致性和高可用性的鍵值數據庫,可以作為保存 Kubernetes 所有集群數據的后臺數據庫。
- proxy
- 工作節點上的一個網絡代理組件,運行在每個節點上
它維護節點上的網絡規則,實現了Kubernetes Service 概念的一部分 。它的作用是使發往 Service 的流量(通過ClusterIP和端口)負載均衡到正確的后端Pod
- 工作節點上的一個網絡代理組件,運行在每個節點上
- kubelet
- Kubelet組件運行在Node節點上,維持運行中的Pods以及提供kuberntes運行時環境,主要完成以下使命:
1.監視分配給該Node節點的pods
2.掛載pod所需要的volumes
3.下載pod的secret
4.通過docker/rkt來運行pod中的容器
5.周期的執行pod中為容器定義的liveness探針
6.上報pod的狀態給系統的其他組件
7.上報Node的狀態
- Kubelet組件運行在Node節點上,維持運行中的Pods以及提供kuberntes運行時環境,主要完成以下使命:
- apiserver
-
k8s核心概念
- pod
- k8s內的一個容器單元,相當于一個擁有命名空間和存儲卷的docker容器
- deployment
- 用來管理pod,控制pod的增刪改
- service
- 實現pod的網絡通信。因為pod每次變更都會發生ip變動,所以就需要一個入口來屏蔽ip的變動,實現通過service訪問內部pod
- service和pod沒有直接關系,pod通過endpoints暴漏出來,只要pod變更就會同步到endpoints中。service通過endpoints的映射關系來訪問pod
- pod
-
k8s的健康檢查機制都有哪些
- 存活探測器
- 用來探測什么時候重啟定時器
- 就緒探測器
- 用來探測什么時候可以接受請求流量
- 啟動探測器
- 用來探測應用程序何時啟動
- 存活探測器
-
如何實現集群管理
- 開發人員通過rancher可視化平臺,實現對pod的生命周期管理
-
K8S基礎
- K8S的概念
- 是一個開源的,用于管理云平臺中多個主機上的容器化的應用,Kubernetes的目標是讓部署容器化的應用簡單并且高效(powerful),Kubernetes提供了應用部署,規劃,更新,維護的一種機制
- k8s解決了什么問題
- 解決以下問題
- 隨著容器的增加,容器的協調和調度步驟主鍵繁瑣
- 服務升級如何保證不中斷
- 服務運行狀態的監控
- 容器的監控
-
K8S中POD的概念
- k8s內的一個容器單元,相當于一個擁有命名空間和存儲卷的docker容器
-
K8S的命名空間
- 命名空間 namespace 是 k8s 集群級別的資源,可以給不同的用戶、租戶、環境或項目創建對應的命名空間,例如,可以為 test、devlopment、production 環境分別創建各自的命名空間
- 命名空間適用于存在很多跨多個團隊或項目的用戶的場景
- 命名空間 namespace 是 k8s 集群級別的資源,可以給不同的用戶、租戶、環境或項目創建對應的命名空間,例如,可以為 test、devlopment、production 環境分別創建各自的命名空間
-
Deployment無狀態應用的部署
- 編寫一個yml文件,按照k8s的格式定以命名空間,pod標簽,端口號,鏡像id等
-
Service的類型
- clusterip
- 通過集群的內部 IP 暴露服務,選擇該值時服務只能夠在集群內部訪問。 這也是默認的 ServiceType
- NodePort
- 通過每個節點上的 IP 和靜態端口(NodePort)暴露服務。 NodePort 服務會路由到自動創建的 ClusterIP 服務。 通過請求 <節點 IP>:<節點端口>,你可以從集群的外部訪問一個 NodePort 服務。
- LoadBalancer
- 使用云提供商的負載均衡器向外部暴露服務。 外部負載均衡器可以將流量路由到自動創建的 NodePort 服務和 ClusterIP 服務上
- clusterip
-
Service和pod的關系
- service和pod并沒有直接的關系。pod通過endpoints暴露出來,只要pod發生變更,便會同步至endpoints中。有了service和endpoints后,kube-proxy會實時監聽它們的更新和刪除操作,然后更新iptables代理規則,重新生成該service訪問pod的ip和端口映射規則
-
labels標簽
- 當相同類型的資源越來越多,對資源劃分管理是很有必要,此時就可以使用label為資源對象 命名,以便于配置,部署等管理工作,提升資源的管理效率
-
labels selector 標簽選擇器
- Label selector(標簽選擇器)是Kubernetes核心的分組機制,通過label selector客戶端/用戶能夠識別一組有共同特征或屬性的資源對象
- 通過資源對象上定義的Label Selector來篩選要監控的Pod副本的數量,從而實現Pod副本的數量始終符合預期設定的全自動控制流程
- kupe-proxy進程通過Service的Label Selector來選擇對應的Pod,自動建立器每個Service到對應Pod的請求轉發路由表,從而實現Service的智能負載均衡機制
- 通過對某些Node定義特定的Label,并且在Pod定義文件中使用NodeSelector這種標簽調度策略,Kube-scheduler進程可以實現Pod定向調度的特性
- Label selector(標簽選擇器)是Kubernetes核心的分組機制,通過label selector客戶端/用戶能夠識別一組有共同特征或屬性的資源對象
-
Ingress七層負載均衡
- ingress其實類似nginx,通過k8s監聽ingress資源的變化,重新生成nginx.conf文件,然后生效
- 和tcp七層協議一樣,只不過最后通過service完成負載均衡
- ingress其實類似nginx,通過k8s監聽ingress資源的變化,重新生成nginx.conf文件,然后生效
-
K8S深入
-
K8S的資源配額、限制
- 通常nodejs的pod限制在2c4g,但可以通過配置yml文件來達到以下目的
- 達到1.8c的時候擴容一個pod
- 達到3.5g的時候庫容一個pod
- 通常nodejs的pod限制在2c4g,但可以通過配置yml文件來達到以下目的
-
K8S的環境變量
- 在yml配置文件中的env下定義即可
-
K8S配置管理(ConfigMap、Secret等)
- configmap是k8s的一個配置管理組件,可以將配置以key-value的形式傳遞,通常用來保存不需要加密的配置信息,加密信息則需用到Secret,主要用來應對以下場景:
- 使用k8s部署應用,當你將應用配置寫進代碼中,就會存在一個問題,更新配置時也需要打包鏡像,configmap可以將配置信息和docker鏡像解耦。
- 使用微服務架構的話,存在多個服務共用配置的情況,如果每個服務中單獨一份配置的話,那么更新配置就很麻煩,使用configmap可以友好的進行配置共享。
其次,configmap可以用來保存單個屬性,也可以用來保存配置文件
- configmap是k8s的一個配置管理組件,可以將配置以key-value的形式傳遞,通常用來保存不需要加密的配置信息,加密信息則需用到Secret,主要用來應對以下場景:
-
K8S的滾動更新
- 所謂滾動升級,就是在升級過程中,并不一下子啟動所有新版本,是先啟動一臺新版本,再停止一臺老版本,然后再啟動一臺新版本,再停止一臺老版本,直到升級完成
-
K8S的健康檢查
-
存活探測器
- 用來探測什么時候重啟定時器
-
就緒探測器
- 用來探測什么時候可以接受請求流量
-
啟動探測器
- 用來探測應用程序何時啟動
-
K8S的存儲管理(PV、PVC等)
kubernetes 入門實踐-存儲 volume-nfs-pv-pvc
-
K8S的有狀態服務StatefulSet
kubernetes 入門實踐-有狀態的服務 StateFulSet
-
K8S的Job、CronJob
批任務處理,其中CronJon可以處理定時任務 -
使用流水線部署K8S應用
GitHub Actions CI/CD
-
-
K8S進階
-
K8S集群搭建
kubernetes 入門實踐-搭建集群 -
K8S負載均衡
kubernetes 入門實踐-Ingress
其中ingress負責訪問service的策略,而ingress-controller負責負載均衡策略 -
K8S的調度策略(親和、反親和等)
Pod親和性指的是滿足特定條件的的Pod對象運行在同一個node上, 而反親和性調度則要求它們不能運行于同一node -
K8S的存儲(本地存儲、分布式存儲等)
kubernetes 入門實踐-存儲-hostpath
kubernetes 入門實踐-搭建nfs服務器 -
K8S中的負載均衡(ClusterIP、NodePort、L4、L7負載均衡)
-
K8S的彈性伸縮(HPA)
- HPA
- 根據資源利用率或者自定義指標自動調整replication controller, Deployment 或 ReplicaSet,實現部署的水平自動擴縮容,讓部署的規模接近于實際服務的負載。可以使用rancher界面功能,終端命令或者hpa yml配置文件完成
- HPA
-
使用Helm Chart發布應用
- helm是kubernetes生態系統中的一個軟件包管理工具,類似ubuntu的apt,centos的yum或python的pip一樣,專門負責管理kubernetes應用資源;使用helm可以對kubernetes應用進行統一打包、分發、安裝、升級以及回退等操作
-
K8S的日志、監控、告警體系
-
Istio服務網格與流量治理
-
K8S技術演進路線(AKS、ASK、ACI、FaaS等)
-
-
項目
你們線上運行多少機器,都是什么配置,為什么這樣配置
- 舊平臺 我們部門總共20臺,大多都是4c8g的機器,一共14個服務
- 新平臺 ,一共接近30臺服務器,但經過容器化管理后,單位其實是pod。我們pod基本配置都是2c4g的,每個服務兩個pod,部分流量大的pod會擴展到3-4個,除了個別服務,因為要處理導入和導出,所以內存較高。新平臺的服務,我們部門總共有25個
QPS達到多少
- 能打到1000-1200 200+ms
你們登錄加密token怎么做的
- 直接用md5來做的
- 后臺通過加鹽來對比數據庫密碼
單點登錄如何做的
- 用redis存儲單點登錄狀態,第三方發起/loginToken登陸時,通過接口傳遞用戶標記獲取token,然后用token發起單點登錄
說說對sass行業的理解
- 是一種通過網絡提供集中式服務的軟件。
- 不用購買,安裝和維護任何軟件硬件,只需要根據需要。每個月付錢即可。和"租"類似
什么是線程安全和不安全
- 安全
- 有鎖機制,避免了多個線程爭搶資源問題
- 不安全
- 沒有鎖,多個線程可能爭搶資源,導致業務異常。比如多個定時器任務就需要用到鎖。或者相同狀態的業務處理,或者有順序要求的業務處理等
什么是GRPC
- 先看一下rcp和http區別
- RPC 主要用于公司內部的服務調用,性能消耗低,傳輸效率高,實現復雜。
HTTP 主要用于對外的異構環境,瀏覽器接口調用,App 接口調用,第三方接口調用等。
- RPC 主要用于公司內部的服務調用,性能消耗低,傳輸效率高,實現復雜。
- grpc是基于rpc的一個框架
ElasticSearch
- 什么是Elasticsearch
- 基于lucence的搜索引擎,接口風格基于resultful,使用Java開發的
- 分片
- 概念
- 在ES中所有數據的文件塊,也是數據的最小單元塊,整個ES集群的核心就是對所有分片的分布、索引、負載、路由等達到驚人的速度
- 假設 IndexA 有2個分片,我們向 IndexA 中插入10條數據 (10個文檔),那么這10條數據會盡可能平均的分為5條存儲在第一個分片,剩下的5條會存儲在另一個分片中。
- 設置分片的關鍵字是number_of_shards
- 設置分片副本的關鍵字是number_of_replicas
- 設置分片的策略
- 建議:(僅參考)
- 1、每一個分片數據文件小于30GB
- 2、每一個索引中的一個分片對應一個節點
- 3、節點數大于等于分片數
- 概念
- 副本
- 保障系統的高可用性,如果分片異常,副本分片會晉升為分片
- 特點
- 分布式文件存儲
- 和mongodb分片優點類似,將多條數據分布在不同的分片上,提高查詢性能
- 也有master和node節點
- 實時分析的分布式搜索引擎
- 基于倒排索引和lucence
- 高擴展性
- 擴展簡單,只需要一臺機器,根據配置文件指定集群節點加入即可
- 分布式文件存儲
- 優缺點
- 優點
- 高可用性,分布式部署,支持快照備份和恢復
- 和傳統的數據庫集群一樣,都是三個節點
- 類似mongodb的分片機制,把數據存儲在不同的分片中,提高查詢性能和負載承受能力
- 生態完善,部署簡單,數據遷移簡單等
- 根據文檔即可部署
- kibana日志分析
- elasticsearch內置了數據遷移api
- 內置多種分詞器,滿足大部分業務場景
- 完全模糊搜索 ngram
- 中文分詞 IK
- 還有很多其他的
- 搜索性能高
- 每個字段都有索引
- 高可用性,分布式部署,支持快照備份和恢復
- 缺點
- 增加維護成本,畢竟是一個額外的組件
- 不能隨意更改索引結構,對于現有數據的結構,只能通過遷移數據到新索引結構上來解決
- 優點
- 應用場景
- 模糊搜索,全文檢索
- 日志分析,kibana內部集成了elasticsearch
- 運維監控
- 當前用的是2.x版本
- 索引比較大的 工單數據量是一千多萬,占用空間三百多g
- 一千多萬數據量用默認的stander分詞器,占用一百多g。使用ngram后占用三百多g
- 業務中的應用場景
- 客戶手機號和昵稱模糊搜索
- 工單編號和工單備注模糊搜索
- 通話關聯的客戶昵稱和手機號模糊搜索
- 正序和倒排索引區別
- 正序索引
- 概念
- 好比mongodb,搜索一個關鍵字,要掃描表中每一個文檔進行匹配
- 好比在mongo中模糊搜索,包含這個關鍵字的文檔
- 數據庫就會用這個關鍵字逐個匹配表中的文檔,如果有100個文檔,就掃描100次
- 優點
- 直接正序插入,不用對關鍵詞進行拆分,所以插入效率高
- 缺點
- 檢索性能差
- 概念
- 倒排索引
- 概念
- 和正序反過來,比如搜索蘋果手機,那么倒排索引會拆分成蘋,果,手,機這四個詞,每一個詞分別對應了包含該關鍵字的文檔id
- 三要素
- 詞條
- 檢索的關鍵詞,關鍵字key
- 詞典
- 詞條的集合,拆分后的詞組
- 倒排表
- 記錄詞條出現的位置和頻率,每一項是一個倒排項
- 詞條
- 優點
- 檢索性能高
- 缺點
- 插入的效率低,因為要對關鍵字進行拆分,構建索引
- 概念
- 正序索引
- 當前使用的是什么版本 2.x版本
- Elasticsearch基本概念
- 集群
- 節點
- 索引
- 文檔
- 類型
- Elasticsearch如何保證寫一致
- 在操作寫入的時候,es會進行以下步驟確保寫一致性
- 檢查活躍的副本數,這個參數默認為1,也可以自己指定。如果活躍的副本數小于設定的值,則寫入失敗
- 然后等待所有副本寫入成功(中間會有一些重試措施)才返回,但并不是所有都寫入才算成功
- 寫入中,如果有一個副本異常,持續失敗,那么副本會通知主節點,將異常的副本關系移除。被移除的副本不接受任何請求。
- 在操作寫入的時候,es會進行以下步驟確保寫一致性
- Elasticsearch寫一致性策略
- one:只要有1個primary shard是可用的,就可以進行寫操作
- all:必須所有的primary shard和replica shard都是可用的,才可以進行寫操作
- quorum:默認值。要求所有的shard中,必須大部分shard都是可用的,才可以進行寫操作
- int( (shard + number_of_replicas) / 2 ) + 1
- 舉個例子,3個primary shard,number_of_replicas=1,總共有3 + 3 * 1 = 6個shard
- quorum = int( (3 + 1) / 2 ) + 1 = 3
- 所以,要求6個shard中至少有3個shard是active狀態的,才可以執行這個寫操作
- 你們都用到了哪些分詞器
- stander
- ngram
- Elasticsearch基本語法
- 數據結構
- index 索引
- type 類型
- id 字段id
- routing 路由
- body 數據體
- 查詢
- query 查詢器
- should 或條件,至少滿足一項
- filter 過濾器,條件
- term 精準匹配
- match 模糊匹配
- Match_phrase 短語模糊匹配
- size 查詢多少個
- _source 返回指定字段
- 數據結構
- Elasticsearch數據類型
- text:被分析索引的字符串類型
- keyword:不能被分析,只能被精確匹配的字符串類型
- date:日期類型,可以配合format一起使用
- long:長整型,數字類型
- integer:整型,數字類型
- short:短整型,數字類型
- double:雙精度浮點型,數字類型
- Boolean:布爾型,true、false
- array:數組類型
- object:對象類型,json嵌套
- ip:ip類型
- geo_point:地理位置類型
- Elasticsearch使用中遇到的問題
- 默認的stander不能滿足業務場景模糊搜索的需求,然后使用ngram進行改造,設置最小和最小分次數,設置的是1_25。它會逐字分詞,然后使用uniq對重復結果過濾,對于大文本的場景較少,所以再使用limit對結果數進行限制。
- 數據遷移失敗,因為es默認會字符串進行轉換,比如’1.0’會被轉成1,字符串’2022’會被轉換成日期格式,但字符串是一個自由的文本內容,按照es隱式轉換標準會失敗,所以就把這兩個配置項給關閉了numeric_detection 數字轉換,date_detection 日期轉換
- 如何監控Elasticsearch狀態
- elasticsearch-head 開源地址在github
- 常見面試題
- 集群
- 一共13個es節點,接近30個索引,大部分以業務命名,個別的會以日期命名。
- 默認情況下,,Elasticsearch為每個索引創建五個主分片和一個副本。這意味著每個索引將包含五個主分片,每個分片將具有一個副本。
- api
- elasticsearch常用API
- 性能優化
- 對于不需要模糊匹配的字段盡量使用keyword。keyword不進行分詞,直接索引
- 盡量使用term查詢
- 只存儲業務需要字段,節省空間并提升性能
- 減少大文本存儲,雖然es適合大文本檢索,但過大的文本也會造成性能問題
- 使用索引滾動模式,避免一個索引數據過于龐大而造成的性能問題。可以采用按月或按年來進行日期滾動
- 減少搜索結果,避免每次查詢上百上千的數量,過多的查詢結果也會造成性能問題
- 冷卻分離存儲,分兩種級別
- 業務級別,非熱點或核心業務可以存儲冷數據中,這樣熱數據索引空間變小,就能夠提升熱數據的性能
- 用戶級別,將非活躍用戶的數據存儲到冷索引中,那么熱索引的空間變小,自然查詢性能就變高了
- 集群
綜合
該篇是對標了幾個大廠常問和一些偏八股文的問題
TCP和UDP區別
- UDP提供無連接服務
- UDP不需要維護連接狀態
- UDP適用于效率高的應用
- UDP不像TCP那樣每一次發送都需要進行確認
- UDP性能高,開銷小
- UDP連接不可靠
HTTP和HTTPS區別
- HTTPS協議需要CA證書,費用較高;而HTTP協議不需要;
- HTTP協議是超文本傳輸協議,信息是明文傳輸的,HTTPS則是具有安全性的SSL加密傳輸協議;
- 使用不同的連接方式,端口也不同,HTTP協議端口是80,HTTPS協議端口是443;
- HTTP協議連接很簡單,是無狀態的;HTTPS協議是有SSL和HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比HTTP更加安全。
- HTTPS使用了非對稱加密和對稱加密:HTTPS是在HTTP的基礎上增加了SSL層,服務器和客戶端傳輸數據前先采用非對稱加密算法生產一個秘鑰,再用這個秘鑰使用對稱加密算法加密要傳輸的數據,這樣做即保證了秘鑰的安全,有提高了數據加密效率。
HTTP長連接和短鏈接區別
- 長連接多用于操作頻繁,點對點的通訊,而且連接數不能太多情況,。每個TCP連接都需要三步握手,這需要時間,如果每個操作都是先連接,再操作的話那么處理速度會降低很多,所以每個操作完后都不斷開,下次處理時直接發送數據包就OK了,不用建立TCP連接。例如:數據庫的連接用長連接, 如果用短連接頻繁的通信會造成socket錯誤,而且頻繁的socket 創建也是對資源的浪費。
- 而像WEB網站的http服務一般都用短鏈接,因為長連接對于服務端來說會耗費一定的資源,而像WEB網站這么頻繁的成千上萬甚至上億客戶端的連接用短連接會更省一些資源,如果用長連接,而且同時有成千上萬的用戶,如果每個用戶都占用一個連接的話,那可想而知吧。所以并發量大,但每個用戶無需頻繁操作情況下需用短連好。
- http1.1 默認支持長連接(keep-alive),TCP連接之后不會馬山斷開,之后再加載靜態資源,都會基于這個TCP連接。http1.1還保持了host頭部,也支持虛擬主機。而且支持斷點續傳
- 長連接詳解
- 第一次請求網頁時會打開一個TCP連接,去加載css等靜態資源,等這些請求加載完畢后才會釋放連接。當一個請求對于的回包回來時,他卻無法分辨是屬于哪個請求的。所以回包只能按請求順序返回,這就引來了另一個問題-線頭阻塞。
- 長連接詳解
- http 2.0 多路復用。基于TCP連接并行發送多個請求和接受響應,解決http1.1請求串行的性能問題等
TCP長連接和HTTP長連接區別
- TCP的是為了檢測心跳,保持活躍的
- HTTP的主要是在TCP的保活基礎上重用連接,提高性能的
TCP如何保證可靠性傳輸
- ARQ協議(超時重傳協議)
- ARQ 協議也就是超時重傳機制。通過確認和超時機制保證了數據的正確送達,ARQ 協議包含停止等待 ARQ 和連續 ARQ
- 正常傳輸過程
只要 A 向 B 發送一段報文,都要停止發送并啟動一個定時器,等待對端回應,在定時器時間內接收到對端應答就取消定時器并發送下一段報文
瀏覽器輸入網址后都發生了什么
- 輸入www.baiidu.com
- 將域名發送給DNS服務器
- DNS服務器返回一個ip地址
- 然后客戶端會把自己的ip和DNS返回的ip 分別換算成二進制,然后對比前三位數是否在一個子網
- 在一個子網則直接發送
- 不在一個子網,則將數據包發給交換機,通過以太網協議廣播到路由器(網關),路由器再繼續進行尋址,直到找到對應的ip服務器
- 然后把http請求打包到數據包中構成請求報文
- 然后就到了傳輸層,經過TCP協議需要設置接收方的端口。然后把應用層的數據包封裝到TCP數據包中,并加上一個TCP頭部,該頭部包含了接收方的端口號
- 然后就到了網絡層,走ip協議,把tcp協議的頭和數據包放入ip數據包中,再加一個ip頭(包含了本機和接收方的ip地址)
- 然后就到了數據鏈路層,再把ip數據包放入以太網數據包中,頭部存放了本機網卡的mac地址,網關的mac地址。但是以太網的數據包限制大小在1500個字節,如果超出則進行分割,分割后的數據包報頭都包含了mac地址。然后再通過交換機,用以太網協議進行廣播分發
- 直到找到最后的ip服務器。收到分割后的包根據ip頭的序號再講這分割后的包進行合并。然后從ip數據包中找到tcp數據包,再從tcp數據包中找到http數據包進行讀取和處理。然后再把返回的數據封裝成http響應報文并放在http數據包中,再放入tcp數據包,再放入ip數據包,最終封裝成以太網數據包,通過網關尋址轉發回去
HTTP1.0,1.1,2.0的區別
- http1.0 需要指定kepp-alive來保持連接,默認短鏈接,每次請求都要重新建立一次TCP連接,處理完畢后就釋放連接。但這樣的話當網頁內容較多時,比如css,js比較多,那么每次都要重新請求,就很浪費資源
- http1.1 支持長連接(keep-alive),TCP連接之后不會馬山斷開,之后再加載靜態資源,都會基于這個TCP連接。http1.1還保持了host頭部,也支持虛擬主機。而且支持斷點續傳
- 長連接詳解
- 第一次請求網頁時會打開一個TCP連接,去加載css等靜態資源,等這些請求加載完畢后才會釋放連接。當一個請求對于的回包回來時,他卻無法分辨是屬于哪個請求的。所以回包只能按請求順序返回,這就引來了另一個問題-線頭阻塞。
- 長連接詳解
- http 2.0 多路復用。基于TCP連接并行發送多個請求和接受響應,解決http1.1請求串行的性能問題等
- 其實長連接和短連接指的是TCP連接
HTTP三次握手和四次揮手過程
- 三次握手
- 第一次握手(進入同步已發送狀態)
- 向服務器發送連接請求報文段 包含了端口和序列號
- 發送SYN序列號,此時seq=a
- 第二次握手(進入同步收到狀態)
- 確認報文段,標識服務器希望從客戶端這邊接收到數據的序列號
- 服務端返回SYN序列號,其中seq=b,并把ack+1進行返回
- 第三次握手(已進入連接狀態)
- 客戶端收到服務端發送的確認ACK后,還要再次向服務器給出確認,確認報文段的ACK設置為1
- SYN內的seq=b+1
- 三次握手的目的不僅在于通信雙方了解一個連接正在建立,還在于用數據包的選項承載特殊的信息,確保通信雙方是可靠的
- 第一次握手(進入同步已發送狀態)
- 四次揮手
- 第一次揮手(終止等待狀態)
- 客戶端向服務器發出連接釋放的報文段
- 發送 FIN,seq=a
- 第二次揮手(終止等待狀態2)
- 服務區收到連接釋放報文段后立即發出確認
- 服務端返回ACK,seq=b,ack=a+1
- 第三次揮手(最后確認)
- 如果服務器已經沒有向客戶端發送數據,其應用的進程就通知服務器釋放連接
- 服務端繼續返回FIN,seq=b,ack=a+1
- 第四次揮手(時間等待)
- 客戶端收到服務器的連接釋放報文段后,立即對此發出確認
- 這樣就能夠讓TCP重新發送最終的ACK以避免出現丟失的情況,重新發送最終的ACK并不是因為TCP重新傳遞了ACK,而是因為通信的另一方重新傳遞了它的FIN序列號
- 為了保證 B 能收到 A 的確認應答。若 A 發完確認應答后直接進入 CLOSED 狀態,如果確認應答因為網絡問題一直沒有到達,那么會造成 B 不能正常關閉。
- 客戶端發送ACK,seq=b+1
- 第一次揮手(終止等待狀態)
為什么是三次握手而不是兩次
- 可以想象如下場景。客戶端發送了一個連接請求 A,但是因為網絡原因造成了超時,這時 TCP 會啟動超時重傳的機制再次發送一個連接請求 B。此時請求順利到達服務端,服務端應答完就建立了請求。如果連接請求 A 在兩端關閉后終于抵達了服務端,那么這時服務端會認為客戶端又需要建立 TCP 連接,從而應答了該請求并進入 ESTABLISHED 狀態。此時客戶端其實是 CLOSED 狀態,那么就會導致服務端一直等待,造成資源的浪費。
進程,線程,協程區別
-
進程是具有?定獨?功能的程序關于某個數據集合上的?次運?活動,進程是系統進?資源分配和調度的?個獨?單位。每個進程都有??的獨?
內存空間,不同進程通過進程間通信來通信。由于進程?較重量,占據獨?的內存,所以上下?進程間的切換開銷(棧、寄存器、虛擬內存、?件
句柄等)?較?,但相對?較穩定安全。 -
線程是進程的?個實體,是CPU調度和分派的基本單位,它是?進程更?的能獨?運?的基本單位.線程??基本上不擁有系統資源,只擁有?點在運
?中必不可少的資源(如程序計數器,?組寄存器和棧),但是它可與同屬?個進程的其他的線程共享進程所擁有的全部資源。線程間通信主要通過共
享內存,上下?切換很快,資源開銷較少,但相?進程不夠穩定容易丟失數據。 -
協程是?種?戶態的輕量級線程,協程的調度完全由?戶控制。協程擁有??的寄存器上下?和棧。協程調度切換時,將寄存器上下?和棧保存到
其他地?,在切回來的時候,恢復先前保存的寄存器上下?和棧,直接操作棧則基本沒有內核切換的開銷,可以不加鎖的訪問全局變量,所以上下
?的切換?常快。
時間和空間復雜度
- O(1) 沒有循環 一次性執行
- O(logN) 對數型TCP
- O(n)線性 一個循環
- O(n2)平方 for循環嵌套for
- O(n3)立方 for循環嵌套for再嵌套for
- O(2^n)指數
- …
serverless
- 核心概念
- 弱化了儲存和計算之間的聯系。 服務的儲存和計算被分開部署和收費,服務的儲存不再是它本身的一部分,而是演變成了獨立的云服務,這使得計算變得無狀態化,更容易調度和縮擴容,同時也降低了數據丟失的風險
- 代碼的執行不再需要手動分配資源。 我們再也不需要為服務的運行指定需要的資源(比如使用幾臺機器、多大的帶寬、多大的磁盤…),只需要提供一份代碼,剩下的交由serverless平臺去處理就行了
- 按使用量計費。 serverless按照服務的使用量(調用次數、時長等)進行計費,而不是像傳統的serverful服務那樣,按照使用的資源(ECS實例、VM的規格等)計費。
微信云開發技術架構
Serverless入門
總結
以上是生活随笔為你收集整理的NodeJs 面试题 2023的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 融易投3周年庆——欢乐送豪礼
- 下一篇: java自定义窗口,java 自定义窗口