技術(shù)半月刊(2021年3月上半月刊)
《技術(shù)半月刊》
2021年3月上半月刊
前言
《技術(shù)半月刊》從2021年3月10日發(fā)刊,每半月更新一次,以問答的形勢(shì)呈現(xiàn)
問題內(nèi)容由小熊網(wǎng)上搜索,由高質(zhì)量微信群全體成員,每日一問集體回答討論得出,加入請(qǐng)參考技術(shù)交流群加入方式
操作系統(tǒng)
并發(fā)和并行的理解?
并發(fā):在一個(gè)時(shí)間段中多個(gè)程序都啟動(dòng)運(yùn)行在同一個(gè)處理機(jī)中,比如線程 并行:假設(shè)目前A,B兩個(gè)進(jìn)程,兩個(gè)進(jìn)程分別由不同的 CPU 管理執(zhí)行,兩個(gè)進(jìn)程不搶占 CPU 資源且可以同時(shí)運(yùn)行,這叫做并行。單核CPU是偽并行
線程與進(jìn)程的優(yōu)缺點(diǎn)
多線程的優(yōu)點(diǎn):
更加高效的內(nèi)存共享。多進(jìn)程下內(nèi)存共享不便; 較輕的上下文切換。因?yàn)椴挥们袚Q地址空間,CR3寄存器和清空TLB。
多進(jìn)程的優(yōu)點(diǎn):
各個(gè)進(jìn)程有自己內(nèi)存空間,所以具有更強(qiáng)的容錯(cuò)性,不至于一個(gè)集成crash導(dǎo)致系統(tǒng)崩潰; 具有更好的多核可伸縮性,因?yàn)檫M(jìn)程將地址空間,頁表等進(jìn)行了隔離,在多核的系統(tǒng)上可伸縮性更強(qiáng)。
如何提升多線程的效率?
答者:彬
使用線程池,減少線程的創(chuàng)建銷毀帶來的開銷 根據(jù)不同的線程類型確定線程數(shù)量,例如cpu繁瑣的,核線比1:2,I/O繁瑣的1:1 壓測(cè),看具體項(xiàng)目能吃多少線程 在線程數(shù)無法減少的情況下,根據(jù)物理內(nèi)存調(diào)整jvm堆大小,為線程提供足夠內(nèi)存空間 升級(jí)物理機(jī)的cpu和內(nèi)存 單臺(tái)機(jī)極限的情況下,使用集群 最基本的,優(yōu)化代碼,減低復(fù)雜度
小熊補(bǔ)充
盡量使用線程池,從而不用頻繁的創(chuàng)建,銷毀線程; 減少線程之間的同步和通信; 通過Huge Page的方式避免產(chǎn)生大量的缺頁異常; 避免需要頻繁共享寫的數(shù)據(jù)。
go
線程和協(xié)程,有什么區(qū)別
答者:記事本
線程有系統(tǒng)調(diào)度,協(xié)程有運(yùn)行時(shí)調(diào)度
而為什么協(xié)程可以做到同時(shí)創(chuàng)建上萬個(gè),是因?yàn)間o的協(xié)程初始化資源是4KB空間,比線程輕量級(jí)
網(wǎng)上:
區(qū)別在于
一個(gè)線程可以多個(gè)協(xié)程,一個(gè)進(jìn)程也可以單獨(dú)擁有多個(gè)協(xié)程。 線程進(jìn)程都是同步機(jī)制,而協(xié)程則是異步。 協(xié)程能保留上一次調(diào)用時(shí)的狀態(tài),每次過程重入時(shí),就相當(dāng)于進(jìn)入上一次調(diào)用的狀態(tài)。 線程是搶占式,而協(xié)程是非搶占式的,所以需要用戶自己釋放使用權(quán)來切換到其他協(xié)程,因此同一時(shí)間其實(shí)只有一個(gè)協(xié)程擁有運(yùn)行權(quán),相當(dāng)于單線程的能力。 協(xié)程并不是取代線程, 而且抽象于線程之上, 線程是被分割的CPU資源, 協(xié)程是組織好的代碼流程, 協(xié)程需要線程來承載運(yùn)行, 線程是協(xié)程的資源, 但協(xié)程不會(huì)直接使用線程, 協(xié)程直接利用的是執(zhí)行器(Interceptor), 執(zhí)行器可以關(guān)聯(lián)任意線程或線程池, 可以使當(dāng)前線程, UI線程, 或新建新程.。 線程是協(xié)程的資源。協(xié)程通過Interceptor來間接使用線程這個(gè)資源。
協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時(shí),將寄存器上下文和棧保存到其他地方,在切回來的時(shí)候,恢復(fù)先前保存的寄存器上下文和棧。因此:
協(xié)程的好處:
無需線程上下文切換的開銷 無需原子操作鎖定及同步的開銷 方便切換控制流,簡化編程模型
缺點(diǎn):
無法利用多核資源:協(xié)程的本質(zhì)是個(gè)單線程,它不能同時(shí)將 單個(gè)CPU 的多個(gè)核用上,協(xié)程需要和進(jìn)程配合才能運(yùn)行在多CPU上.當(dāng)然我們?nèi)粘K帉懙慕^大部分應(yīng)用都沒有這個(gè)必要,除非是cpu密集型應(yīng)用。 進(jìn)行阻塞(Blocking)操作(如IO時(shí))會(huì)阻塞掉整個(gè)程序
最佳實(shí)踐
線程和協(xié)程推薦在IO密集型的任務(wù)(比如網(wǎng)絡(luò)調(diào)用)中使用,而在CPU密集型的任務(wù)中,表現(xiàn)較差。 對(duì)于CPU密集型的任務(wù),則需要多個(gè)進(jìn)程,繞開GIL的限制,利用所有可用的CPU核心,提高效率。 所以大并發(fā)下的最佳實(shí)踐就是多進(jìn)程+協(xié)程,既充分利用多核,又充分發(fā)揮協(xié)程的高效率,可獲得極高的性能。
go的切片和數(shù)組有什么區(qū)別
定長聲明的是數(shù)組,不定長是切片
var arr1 [3]int = [3]int{1, 2, 3}
var slice1 []int = []int{1, 2, 3}
數(shù)組拷貝后可以隨便改值,不會(huì)對(duì)原數(shù)組有影響,但切片拷貝是引用,修改新切片會(huì)同時(shí)修改原切片
管道chan是什么
一個(gè) channels 是一個(gè)通信機(jī)制,它可以讓一個(gè) goroutine 通過它給另一個(gè) goroutine 發(fā)送值信息,可以理解為一個(gè)隊(duì)列,遵循先入先出的原則,同時(shí)在代碼級(jí)別線程安全
管道比鎖快?為什么
go中的chan 是用鎖實(shí)現(xiàn)的。所以肯定不會(huì)比鎖塊。
redis
zset(sort list) 的數(shù)據(jù)結(jié)構(gòu)是什么?
zset 有序且唯一,在跳表以空間換時(shí)間 以冗余的鏈表換取效率

為什么要用跳表不用B+樹的結(jié)構(gòu)呢?
答者:Shawn
B+樹的每個(gè)節(jié)點(diǎn)可以存儲(chǔ)多個(gè)關(guān)鍵字,而Redis是 內(nèi)存中讀取數(shù)據(jù),不涉及IO,因此使用了跳表
lru和ttl,大量過期時(shí)會(huì)不會(huì)阻塞
不會(huì),因?yàn)閞edis是閑時(shí)清理,可以設(shè)置最高占用cpu,清理是基于概率的,存在部分key總是無法清理的情況在,另外清理key的過程是不會(huì)fork子進(jìn)程
什么時(shí)候會(huì)fork子進(jìn)程
rdb 、aof、主從無盤復(fù)制方式傳輸
key清理不干凈會(huì)不會(huì)遇到什么業(yè)務(wù)上的問題,萬一用到了會(huì)發(fā)生什么?通過什么辦法解決?
如果是lru的話,假如一個(gè)key值在以前都沒有被訪問到,然而最近一次被訪問到了,那么就會(huì)認(rèn)為它是熱點(diǎn)數(shù)據(jù),會(huì)更新ttl,不會(huì)被淘汰。
優(yōu)化的話就增大maxmemory-sample,增加每次lru數(shù)據(jù)的個(gè)數(shù),淘汰起來更精確
在redis>4.0版本,有LFU算法,訪問不頻繁的優(yōu)先淘汰就好了
另外redis有三種刪除策略
惰性刪除,也就是在置換的時(shí)候刪除
定時(shí)刪除,固定時(shí)間段執(zhí)行刪除操作
定期刪除,和定時(shí)刪除一樣,區(qū)別會(huì)時(shí)間期是根據(jù)業(yè)務(wù)來自動(dòng)取的
另外rdb和aof的持久化策略中,rdb讀取時(shí)不會(huì)讀取過期數(shù)據(jù),aof有rewrite功能,執(zhí)行行也不會(huì)存過期的策略
太頻繁的主動(dòng)刪除對(duì)cpu不友好,惰性刪除對(duì)內(nèi)存不友好,一旦插入大key,會(huì)出現(xiàn)cpu使用高峰
bigkey還會(huì)出現(xiàn)什么問題?
網(wǎng)絡(luò)阻塞、redis超時(shí)、分片內(nèi)存不均勻?qū)е履承┕?jié)點(diǎn)占用內(nèi)存多
避免bigkey的方法,主要是對(duì) bigkey 進(jìn)行拆分,拆成多個(gè) key,然后用MGET取回來,再在業(yè)務(wù)層做合并。
集群模式?jīng)]有mget命令怎么辦?
再加個(gè)map存在key列表,然后并行取
眾所周知 redis 是單線程的(主要讀寫 io 操作 尋址等),為什么不設(shè)計(jì)成多線程的?
Redis的核心是快『基于內(nèi)存』,主要有以下觀點(diǎn):由『避免了上下文切換和cpu的競(jìng)爭,更加無需考慮各種鎖操作,也不會(huì)和mysql一樣存在死鎖導(dǎo)致的問題』。
因?yàn)閿?shù)據(jù)是存儲(chǔ)在內(nèi)存中,內(nèi)存中的運(yùn)行非???,但是如果存在上面的鎖,和上下文切換,可能就不會(huì)那么快了。
有利于開發(fā)人員規(guī)范代碼,單線程的代碼比多核異步更加清晰明了。
單線程雖然有這些好處,但一定會(huì)浪費(fèi)一些多核cpu的性能優(yōu)勢(shì),如果是你設(shè)計(jì)會(huì)怎么考慮?
還得看cpu的頻率,如果cpu的頻率低,并且訪問redis的并發(fā)很大,那么單個(gè)redis線程分?jǐn)偟矫總€(gè)cpu上的壓力也是非??捎^的。(一個(gè)線程并不是一直都bind到一個(gè)固定的核上面的, 其實(shí)這也是常遇到的錯(cuò)誤的認(rèn)知:單個(gè)線程就算用多核的機(jī)器也是浪費(fèi)的觀念)
雖然redis是單線程,如果有需要可以使用多實(shí)例來模擬出多線程或者多進(jìn)程
mysql
myisam和innodb的區(qū)別是什么
答者:貍追
innodb 支持事務(wù)和外鍵,最小鎖粒度是行級(jí)鎖
myisam 不支持事務(wù)和外鍵,最小鎖粒度是表級(jí)鎖,間歇鎖
補(bǔ)充
innodb 的索引如果是聚簇索引,葉子節(jié)點(diǎn)上保存的是數(shù)據(jù)和索引,非聚簇索引,節(jié)點(diǎn)上保存的是id,而myisam保存的是數(shù)據(jù)的地址(相當(dāng)于一個(gè)指針)
myisam 的表可以沒有索引,innodb一定要有索引
myisam 會(huì)保存總行數(shù),innodb是全表掃描
對(duì)于大量更新、插入、刪除,innodb性能上更好,因?yàn)樗邆涞氖聞?wù)、行級(jí)鎖、B+樹等特點(diǎn),更安全,因?yàn)榛貪L和崩潰恢復(fù)更適合大型應(yīng)用
經(jīng)過測(cè)試在單進(jìn)程讀的情況下myisam執(zhí)行速度比innodb更快,但是多進(jìn)程讀的時(shí)候就失去優(yōu)勢(shì)了
mysql5.5版本之后默認(rèn)innodb
聚簇索引是什么?二級(jí)索引的數(shù)據(jù)結(jié)構(gòu)是什么?
索引按照數(shù)據(jù)結(jié)構(gòu)來說主要包含B+樹和Hash索引。
聚簇索引:用B+樹保存,節(jié)點(diǎn)只包含id索引,葉子節(jié)點(diǎn)同時(shí)保存索引和數(shù)據(jù),這種數(shù)據(jù)和索引在一起的方式就是聚簇索引,有主鍵使用主鍵,沒有主鍵就用唯一非空索引代替,如果沒有會(huì)隱式定義一個(gè)主鍵,一張表只能有一個(gè)聚簇索引
非聚簇索引:又稱二級(jí)索引,保存的是主鍵id值,這一點(diǎn)和myisam保存的是數(shù)據(jù)地址是不同的。
linux
你怎么理解操作系統(tǒng)里的內(nèi)存碎片,有什么解決辦法?
內(nèi)存碎片通常分為內(nèi)部碎片和外部碎片:
內(nèi)部碎片是由于采用固定大小的內(nèi)存分區(qū),當(dāng)一個(gè)進(jìn)程不能完全使用分給它的固定內(nèi)存區(qū)域時(shí)就會(huì)產(chǎn)生內(nèi)部碎片,通常內(nèi)部碎片難以完全避免;
2.外部碎片是由于某些未分配的連續(xù)內(nèi)存區(qū)域太小,以至于不能滿足任意進(jìn)程的內(nèi)存分配請(qǐng)求,從而不能被進(jìn)程利用的內(nèi)存區(qū)域。
現(xiàn)在普遍采取的內(nèi)存分配方式是段頁式內(nèi)存分配。將內(nèi)存分為不同的段,再將每一段分成固定大小的頁。通過頁表機(jī)制,使段內(nèi)的頁可以不必連續(xù)處于同一內(nèi)存區(qū)域。
內(nèi)存使用情況 buff和cache有什么區(qū)別?
都是為了解決內(nèi)存和IO設(shè)備的讀寫速度不對(duì)等的中間緩存,兩個(gè)都是內(nèi)存的一部分 cached 把讀取過的數(shù)據(jù)保存起來,重新讀取的時(shí)候命中就不用讀磁盤了,如果沒有命中就會(huì)按頻率更新cached buffers 把分散的寫操作集中起來,緩存要輸出到io設(shè)備的數(shù)據(jù)(寫一次就存一下硬盤賊耗時(shí)間,都是緩沖一會(huì)再一起寫硬盤) 拓展一個(gè)shared,是共享內(nèi)存,可以ipcs來查看
怎么定位進(jìn)程cpu占用大是哪一個(gè)函數(shù)導(dǎo)致的?
perf top -g -p 246

這里推薦
什么是程序的堆空間和棧空間?
回答者:海翔
棧是用來保證程序順序執(zhí)行的,后入棧的函數(shù)先出,完整記錄一個(gè)函數(shù)(方法)調(diào)用從開始到結(jié)束所做的一切操作。
堆是用來保存變量和對(duì)象的,存儲(chǔ)臨時(shí)數(shù)據(jù)和部分運(yùn)行時(shí)數(shù)據(jù)。包括函數(shù)調(diào)用期間產(chǎn)生的臨時(shí)變量,程序加載啟動(dòng)時(shí)載入的全局變量等等。堆內(nèi)存的分配,應(yīng)該是在臨時(shí)變量第一次被使用時(shí)分配,全局靜態(tài)變量是在類加載時(shí)分配。不同的變量有不同的生命周期。而垃圾回收,主要也是針對(duì)堆內(nèi)存空間的調(diào)整和釋放
棧和堆都有其空間大小。
當(dāng)遞歸層級(jí)過深時(shí)會(huì)出現(xiàn)棧溢出異常,就是因?yàn)橐4娴姆椒3^了棧可保存的最大數(shù)量。而堆內(nèi)存不足時(shí)常常會(huì)遇到OOM異常,堆內(nèi)存不足以存放新生成的對(duì)象或變量了。
虛擬地址和物理地址有什么區(qū)別,程序編譯運(yùn)行后首先申請(qǐng)到的是什么地址?
因?yàn)槲粩?shù)代表最大尋址能力,32位最大尋址能力是4G所以超過4g的內(nèi)存條會(huì)造成浪費(fèi)
我們知道線程是cpu調(diào)度的最小單元,進(jìn)程是資源分配的最小單元,每個(gè)進(jìn)程之間的資源是獨(dú)立的,互不影響的,這是怎么實(shí)現(xiàn)的呢?
每個(gè)進(jìn)程啟動(dòng)的時(shí)候會(huì)有獨(dú)立內(nèi)存空間,稱為虛擬內(nèi)存,啟動(dòng)時(shí)為給每個(gè)進(jìn)程維護(hù)一個(gè)獨(dú)立的頁表做虛擬內(nèi)存和物理內(nèi)存的映射
所以不同進(jìn)程之間的虛擬內(nèi)存地址可能是相同的,這沒關(guān)系,最終映射到的是物理內(nèi)存不是一個(gè)
假如不同進(jìn)程都訪問某個(gè)系統(tǒng)的庫,就不需要加載兩遍到物理內(nèi)存上,只要映射到同一地址范圍就可以
用到了再分配這種機(jī)制叫內(nèi)存的惰性加載。
虛擬內(nèi)存尋址是cpu到一個(gè)叫mmu的硬件,物理內(nèi)存尋址是mmu到內(nèi)存條,mmu相當(dāng)于是個(gè)外包
所以虛擬內(nèi)存雖然大,不一定全部都存在映射,之前說的堆??臻g也是在虛擬內(nèi)存中的
Linux 打開文件句柄寫入一個(gè)文件時(shí),mv這個(gè)文件會(huì)發(fā)生什么
mv操作,目標(biāo)文件的inode將等于源文件的inode;因此正在寫入的文件被mv,數(shù)據(jù)仍然被寫入到mv后的文件里,除非重新open
正在寫入的文件被rm后,數(shù)據(jù)會(huì)被寫入到系統(tǒng)緩存中,一直會(huì)耗盡所有可用的內(nèi)存
日志歸檔和清空有哪些方式
歸檔:logrotate 支持歸檔和刪除長期日志
代碼級(jí)別可以使用滾動(dòng)日志組件
如果是手動(dòng)刪除可以使用cat /dev/null > xxx.log
查詢linux文件被哪些pid 讀寫的命令是什么
lsof abc.txt 顯示開啟文件abc.txt的進(jìn)程
lsof -i :22 知道22端口現(xiàn)在運(yùn)行什么程序
lsof -c nsd 顯示nsd進(jìn)程現(xiàn)在打開的文件
lsof -g gid 顯示歸屬gid的進(jìn)程情況
lsof +d /usr/local/ 顯示目錄下被進(jìn)程開啟的文件
lsof +D /usr/local/ 同上,但是會(huì)搜索目錄下的目錄,時(shí)間較長
lsof -d 4 顯示使用fd為4的進(jìn)程
lsof -i [i] 用以顯示符合條件的進(jìn)程情況
最后
如果文中有誤,歡迎指出,我會(huì)在閱讀原文指向的博客中更新最新版本
我是小熊,關(guān)注我,知道更多不知道的技術(shù)
