技術半月刊(2021年3月上半月刊)

《技術半月刊》

2021年3月上半月刊

前言

《技術半月刊》從2021年3月10日發刊,每半月更新一次,以問答的形勢呈現

問題內容由小熊網上搜索,由高質量微信羣全體成員,每日一問集體回答討論得出,加入請參考技術交流羣加入方式

操作系統

併發和並行的理解?

  • 併發:在一個時間段中多個程序都啓動運行在同一個處理機中,比如線程
  • 並行:假設目前A,B兩個進程,兩個進程分別由不同的 CPU  管理執行,兩個進程不搶佔 CPU  資源且可以同時運行,這叫做並行。單核CPU是僞並行

線程與進程的優缺點

多線程的優點:

  • 更加高效的內存共享。多進程下內存共享不便;
  • 較輕的上下文切換。因爲不用切換地址空間,CR3寄存器和清空TLB。

多進程的優點:

  • 各個進程有自己內存空間,所以具有更強的容錯性,不至於一個集成crash導致系統崩潰;
  • 具有更好的多核可伸縮性,因爲進程將地址空間,頁表等進行了隔離,在多核的系統上可伸縮性更強。

如何提升多線程的效率?

答者:彬

  1. 使用線程池,減少線程的創建銷燬帶來的開銷
  2. 根據不同的線程類型確定線程數量,例如cpu繁瑣的,核線比1:2,I/O繁瑣的1:1
  3. 壓測,看具體項目能喫多少線程
  4. 在線程數無法減少的情況下,根據物理內存調整jvm堆大小,爲線程提供足夠內存空間
  5. 升級物理機的cpu和內存
  6. 單臺機極限的情況下,使用集羣
  7. 最基本的,優化代碼,減低複雜度

小熊補充

  • 儘量使用線程池,從而不用頻繁的創建,銷燬線程;
  • 減少線程之間的同步和通信;
  • 通過Huge Page的方式避免產生大量的缺頁異常;
  • 避免需要頻繁共享寫的數據。

go

線程和協程,有什麼區別

答者:記事本

線程有系統調度,協程有運行時調度

而爲什麼協程可以做到同時創建上萬個,是因爲go的協程初始化資源是4KB空間,比線程輕量級

網上:

區別在於

  1. 一個線程可以多個協程,一個進程也可以單獨擁有多個協程。
  2. 線程進程都是同步機制,而協程則是異步。
  3. 協程能保留上一次調用時的狀態,每次過程重入時,就相當於進入上一次調用的狀態。
  4. 線程是搶佔式,而協程是非搶佔式的,所以需要用戶自己釋放使用權來切換到其他協程,因此同一時間其實只有一個協程擁有運行權,相當於單線程的能力。
  5. 協程並不是取代線程, 而且抽象於線程之上, 線程是被分割的CPU資源, 協程是組織好的代碼流程, 協程需要線程來承載運行, 線程是協程的資源, 但協程不會直接使用線程, 協程直接利用的是執行器(Interceptor), 執行器可以關聯任意線程或線程池, 可以使當前線程, UI線程, 或新建新程.。
  6. 線程是協程的資源。協程通過Interceptor來間接使用線程這個資源。

協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。因此:

協程的好處:

  1. 無需線程上下文切換的開銷
  2. 無需原子操作鎖定及同步的開銷
  3. 方便切換控制流,簡化編程模型

缺點:

  1. 無法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程需要和進程配合才能運行在多CPU上.當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
  2. 進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序

最佳實踐

  1. 線程和協程推薦在IO密集型的任務(比如網絡調用)中使用,而在CPU密集型的任務中,表現較差。
  2. 對於CPU密集型的任務,則需要多個進程,繞開GIL的限制,利用所有可用的CPU核心,提高效率。
  3. 所以大併發下的最佳實踐就是多進程+協程,既充分利用多核,又充分發揮協程的高效率,可獲得極高的性能。

go的切片和數組有什麼區別

定長聲明的是數組,不定長是切片


var arr1 [3]int = [3]int{123}

var slice1 []int = []int{123}

數組拷貝後可以隨便改值,不會對原數組有影響,但切片拷貝是引用,修改新切片會同時修改原切片

管道chan是什麼

一個 channels 是一個通信機制,它可以讓一個 goroutine 通過它給另一個 goroutine 發送值信息,可以理解爲一個隊列,遵循先入先出的原則,同時在代碼級別線程安全

管道比鎖快?爲什麼

go中的chan 是用鎖實現的。所以肯定不會比鎖塊。

redis

zset(sort list) 的數據結構是什麼?

zset 有序且唯一,在跳錶以空間換時間 以冗餘的鏈表換取效率

爲什麼要用跳錶不用B+樹的結構呢?

答者:Shawn

B+樹的每個節點可以存儲多個關鍵字,而Redis是 內存中讀取數據,不涉及IO,因此使用了跳錶

lru和ttl,大量過期時會不會阻塞

不會,因爲redis是閒時清理,可以設置最高佔用cpu,清理是基於概率的,存在部分key總是無法清理的情況在,另外清理key的過程是不會fork子進程

什麼時候會fork子進程

rdb 、aof、主從無盤複製方式傳輸

key清理不乾淨會不會遇到什麼業務上的問題,萬一用到了會發生什麼?通過什麼辦法解決?

如果是lru的話,假如一個key值在以前都沒有被訪問到,然而最近一次被訪問到了,那麼就會認爲它是熱點數據,會更新ttl,不會被淘汰。

優化的話就增大maxmemory-sample,增加每次lru數據的個數,淘汰起來更精確

在redis>4.0版本,有LFU算法,訪問不頻繁的優先淘汰就好了

另外redis有三種刪除策略

惰性刪除,也就是在置換的時候刪除

定時刪除,固定時間段執行刪除操作

定期刪除,和定時刪除一樣,區別會時間期是根據業務來自動取的

另外rdb和aof的持久化策略中,rdb讀取時不會讀取過期數據,aof有rewrite功能,執行行也不會存過期的策略

太頻繁的主動刪除對cpu不友好,惰性刪除對內存不友好,一旦插入大key,會出現cpu使用高峯

bigkey還會出現什麼問題?

網絡阻塞、redis超時、分片內存不均勻導致某些節點佔用內存多

避免bigkey的方法,主要是對 bigkey 進行拆分,拆成多個 key,然後用MGET取回來,再在業務層做合併。

集羣模式沒有mget命令怎麼辦?

再加個map存在key列表,然後並行取

衆所周知 redis 是單線程的(主要讀寫 io 操作 尋址等),爲什麼不設計成多線程的?

Redis的核心是快『基於內存』,主要有以下觀點:由『避免了上下文切換和cpu的競爭,更加無需考慮各種鎖操作,也不會和mysql一樣存在死鎖導致的問題』。

因爲數據是存儲在內存中,內存中的運行非常快,但是如果存在上面的鎖,和上下文切換,可能就不會那麼快了。

有利於開發人員規範代碼,單線程的代碼比多核異步更加清晰明瞭。

單線程雖然有這些好處,但一定會浪費一些多核cpu的性能優勢,如果是你設計會怎麼考慮?

還得看cpu的頻率,如果cpu的頻率低,並且訪問redis的併發很大,那麼單個redis線程分攤到每個cpu上的壓力也是非常可觀的。(一個線程並不是一直都bind到一個固定的核上面的, 其實這也是常遇到的錯誤的認知:單個線程就算用多核的機器也是浪費的觀念)

雖然redis是單線程,如果有需要可以使用多實例來模擬出多線程或者多進程

mysql

myisam和innodb的區別是什麼

答者:狸追

innodb 支持事務和外鍵,最小鎖粒度是行級鎖

myisam 不支持事務和外鍵,最小鎖粒度是表級鎖,間歇鎖

補充

innodb 的索引如果是聚簇索引,葉子節點上保存的是數據和索引,非聚簇索引,節點上保存的是id,而myisam保存的是數據的地址(相當於一個指針)

myisam 的表可以沒有索引,innodb一定要有索引

myisam 會保存總行數,innodb是全表掃描

對於大量更新、插入、刪除,innodb性能上更好,因爲他具備的事務、行級鎖、B+樹等特點,更安全,因爲回滾和崩潰恢復更適合大型應用

經過測試在單進程讀的情況下myisam執行速度比innodb更快,但是多進程讀的時候就失去優勢了

mysql5.5版本之後默認innodb

聚簇索引是什麼?二級索引的數據結構是什麼?

索引按照數據結構來說主要包含B+樹和Hash索引。

聚簇索引:用B+樹保存,節點只包含id索引,葉子節點同時保存索引和數據,這種數據和索引在一起的方式就是聚簇索引,有主鍵使用主鍵,沒有主鍵就用唯一非空索引代替,如果沒有會隱式定義一個主鍵,一張表只能有一個聚簇索引

非聚簇索引:又稱二級索引,保存的是主鍵id值,這一點和myisam保存的是數據地址是不同的。

linux

你怎麼理解操作系統裏的內存碎片,有什麼解決辦法?

內存碎片通常分爲內部碎片和外部碎片:

  1. 內部碎片是由於採用固定大小的內存分區,當一個進程不能完全使用分給它的固定內存區域時就會產生內部碎片,通常內部碎片難以完全避免;

2.外部碎片是由於某些未分配的連續內存區域太小,以至於不能滿足任意進程的內存分配請求,從而不能被進程利用的內存區域。

現在普遍採取的內存分配方式是段頁式內存分配。將內存分爲不同的段,再將每一段分成固定大小的頁。通過頁表機制,使段內的頁可以不必連續處於同一內存區域。

內存使用情況 buff和cache有什麼區別?

  • 都是爲了解決內存和IO設備的讀寫速度不對等的中間緩存,兩個都是內存的一部分
  • cached 把讀取過的數據保存起來,重新讀取的時候命中就不用讀磁盤了,如果沒有命中就會按頻率更新cached
  • buffers 把分散的寫操作集中起來,緩存要輸出到io設備的數據(寫一次就存一下硬盤賊耗時間,都是緩衝一會再一起寫硬盤)
  • 拓展一個shared,是共享內存,可以ipcs來查看

怎麼定位進程cpu佔用大是哪一個函數導致的?


perf top -g -p 246

這裏推薦

優惠口令:Happy2021

什麼是程序的堆空間和棧空間?

回答者:海翔

棧是用來保證程序順序執行的,後入棧的函數先出,完整記錄一個函數(方法)調用從開始到結束所做的一切操作。

堆是用來保存變量和對象的,存儲臨時數據和部分運行時數據。包括函數調用期間產生的臨時變量,程序加載啓動時載入的全局變量等等。堆內存的分配,應該是在臨時變量第一次被使用時分配,全局靜態變量是在類加載時分配。不同的變量有不同的生命週期。而垃圾回收,主要也是針對堆內存空間的調整和釋放

棧和堆都有其空間大小。

當遞歸層級過深時會出現棧溢出異常,就是因爲要保存的方法棧超過了棧可保存的最大數量。而堆內存不足時常常會遇到OOM異常,堆內存不足以存放新生成的對象或變量了。

虛擬地址和物理地址有什麼區別,程序編譯運行後首先申請到的是什麼地址?

因爲位數代表最大尋址能力,32位最大尋址能力是4G所以超過4g的內存條會造成浪費

我們知道線程是cpu調度的最小單元,進程是資源分配的最小單元,每個進程之間的資源是獨立的,互不影響的,這是怎麼實現的呢?

每個進程啓動的時候會有獨立內存空間,稱爲虛擬內存,啓動時爲給每個進程維護一個獨立的頁表做虛擬內存和物理內存的映射

所以不同進程之間的虛擬內存地址可能是相同的,這沒關係,最終映射到的是物理內存不是一個

假如不同進程都訪問某個系統的庫,就不需要加載兩遍到物理內存上,只要映射到同一地址範圍就可以

用到了再分配這種機制叫內存的惰性加載。

虛擬內存尋址是cpu到一個叫mmu的硬件,物理內存尋址是mmu到內存條,mmu相當於是個外包

所以虛擬內存雖然大,不一定全部都存在映射,之前說的堆棧空間也是在虛擬內存中的

Linux 打開文件句柄寫入一個文件時,mv這個文件會發生什麼

mv操作,目標文件的inode將等於源文件的inode;因此正在寫入的文件被mv,數據仍然被寫入到mv後的文件裏,除非重新open

正在寫入的文件被rm後,數據會被寫入到系統緩存中,一直會耗盡所有可用的內存

日誌歸檔和清空有哪些方式

歸檔:logrotate 支持歸檔和刪除長期日誌

代碼級別可以使用滾動日誌組件

如果是手動刪除可以使用cat /dev/null > xxx.log

查詢linux文件被哪些pid 讀寫的命令是什麼

lsof abc.txt 顯示開啓文件abc.txt的進程

lsof -i :22 知道22端口現在運行什麼程序

lsof -c nsd 顯示nsd進程現在打開的文件

lsof -g gid 顯示歸屬gid的進程情況

lsof +d /usr/local/ 顯示目錄下被進程開啓的文件

lsof +D /usr/local/ 同上,但是會搜索目錄下的目錄,時間較長

lsof -d 4 顯示使用fd爲4的進程

lsof -i [i] 用以顯示符合條件的進程情況

最後

如果文中有誤,歡迎指出,我會在閱讀原文指向的博客中更新最新版本

我是小熊,關注我,知道更多不知道的技術

本文分享自微信公衆號 - 編程三分鐘(coding3min)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章