面霸的八月:小米面試記
書接上回,今天敘述小米的面試經(jīng)歷。這里可能有一些技術(shù)理解和技術(shù)方案,歡迎討論。另昨天共計(jì)收入7筆共95元,夠我喝幾杯咖啡了,謝謝所有捐錢的朋友。
如果你心疼我碼字辛苦,有錢朋友錢場,沒錢的請拉朋友來捧個(gè)錢場,捧場鏈接:https://me.alipay.com/chunshengster ,多少不限
小米:運(yùn)維部
在小米是聊了兩個(gè)部門的,首先是運(yùn)維部門,在 @wilbur井源 的熱情招待下,吃了頓大餐,抱歉的是我沒有帶足現(xiàn)金,所以付款時(shí)我無法“客氣”,改天補(bǔ)請。
wilbur井源同兩位同事與我四人邊吃邊聊,我簡單介紹當(dāng)前的網(wǎng)站的服務(wù)結(jié)構(gòu)以及部分業(yè)務(wù)的技術(shù)設(shè)計(jì),比如網(wǎng)站架構(gòu)的分布情況,分布式文件系統(tǒng)fastDFS的使用狀況、Redis和MySQL的一些部署結(jié)構(gòu)和技術(shù),其中尤其對監(jiān)控這件事情我做了詳細(xì)一些的說明(詳見服務(wù)可用性監(jiān)控的一些思考以及實(shí)踐), 中間提到了關(guān)于主動(dòng)監(jiān)控(主動(dòng)監(jiān)控是指通過運(yùn)維和業(yè)務(wù)部門指定監(jiān)控的系統(tǒng)資源、接口、頁面、日志等,主動(dòng)發(fā)現(xiàn)問題,警報(bào)級別較高)、被控監(jiān)控的概念(指通 過JSlib或客戶端lib對于所有的操作尤其是網(wǎng)絡(luò)接口的請求進(jìn)行監(jiān)控,對異常進(jìn)行匯報(bào),通過收集日志的方式進(jìn)行可用性問題的發(fā)現(xiàn))。當(dāng)然,還有必不可 少的是對haproxy的運(yùn)行和優(yōu)化狀況(參見Haproxy配置),MySQL的架構(gòu)及優(yōu)化方式(見MySQL架構(gòu)及運(yùn)維),Redis常見的性能問題(參見redis架構(gòu)及運(yùn)維問題),fastDFS同其他分布式存儲MogileFS、TFS、lusterfs的在功能、運(yùn)維成本上的橫向比較,多IDC圖片cache的部署以及性能優(yōu)化(參見多idc圖片Cache部署),Linux內(nèi)核參數(shù)(參見Linux內(nèi)核配置)和讓我特別自豪的是關(guān)于網(wǎng)卡smp affinity/RPF/RFS的優(yōu)化效果(參考3/4/5)的一些優(yōu)化等。當(dāng)然,這是正經(jīng)的運(yùn)維部門,我闡述了我對“運(yùn)維”工作的理解:60%的分析整理工作加上40%的技能,分析整理能力是做好運(yùn)維的基礎(chǔ)。
井源也詢問了幾個(gè)安全問題,我粗淺的理解是:從系統(tǒng)管理員(SA)的經(jīng)歷來講,做好IT系統(tǒng)規(guī)劃,合理區(qū)分服務(wù)器角色,通過iptables是能夠阻止大多數(shù)接入層非法請求的;對于web業(yè)務(wù)的安全來講,SQL注入、CRSF等攻擊是因?yàn)閷斎胼斎雰?nèi)容的過濾不嚴(yán)格導(dǎo)致的,在開發(fā)的過程中合理使用一些優(yōu)秀框架或lib,也能夠避免大多數(shù)漏洞的產(chǎn)生;有個(gè)比較有意思的話題是關(guān)于溢出的,現(xiàn)在我已經(jīng)不會計(jì)算溢出地址了,在我當(dāng)script boy的時(shí)候研究過一點(diǎn),忘光光了,慚愧……
井源這邊的效率很好,邊吃邊聊的氣氛很放松,不過很多問題都停留在一些思路和效果數(shù)據(jù)上,沒有勾勾畫畫的太多深入的探討。
電商部
大約8點(diǎn)半左右到的電商部門,常規(guī)面試的第一輪都是技術(shù),包括細(xì)節(jié)。面試官是位張姓的team leader。
在這輪面試的過程中,因?yàn)槭窃跁h室,有筆有板,所以我邊講邊寫。大體上介紹了我對web服務(wù)架構(gòu)的理解,我認(rèn)為,web服務(wù)架構(gòu)大體上離不開這樣幾個(gè)層面:接入層(負(fù)載均衡)、業(yè)務(wù)服務(wù)層、數(shù)據(jù)層,一般還會有不少的后臺輔助程序進(jìn)行同步、異步的處理各種不適合在業(yè)務(wù)層融合的服務(wù)單元。 數(shù)據(jù)層可以包括DB、Cache、File等,數(shù)據(jù)層還可能會有很多中間件或代理服務(wù)器用來做數(shù)據(jù)層的負(fù)載均衡或是HA,以及Sharding等。同面試 官詳細(xì)介紹了當(dāng)前服務(wù)的公司在每一層所采用的技術(shù),分別是:haproxy、nginx+php、twemproxy+redis、 MySQL+RedisCache、Varnish+Squid+nginx+fastDFS。
haproxy的服務(wù)器配置是按照100w并發(fā)的目標(biāo)進(jìn)行配置和優(yōu)化的,計(jì)劃100w客戶端連接,考慮每個(gè)客戶端連接可能產(chǎn)生1個(gè)內(nèi)部連接,按照每個(gè)連接消耗4k(此處修正為17K,haproxy的官方數(shù)據(jù),見參考8,感謝 @GNUer 的修正)內(nèi)存來算,大約8G(此處修正為32G)內(nèi)存【這里的計(jì)算還需要再考慮,我擔(dān)心haproxy的每個(gè)連接消耗17k內(nèi)存是包含對內(nèi)部服務(wù)器的連接】,實(shí)際上往往比這個(gè)數(shù)字要大。目前達(dá)到的最大連接數(shù)目測到過16w,在接入層的系統(tǒng)優(yōu)化上分別有:網(wǎng)卡中斷優(yōu)化(參考3/4/5),linux 內(nèi)核參數(shù)優(yōu)化(見linux sysctl.conf配置)。
值得一提的是,我們的haproxy服務(wù)器都是64G內(nèi)存,實(shí)際上遠(yuǎn)遠(yuǎn)永不到這么多,圖片服務(wù)的最外層cache,即Varnish,我們也是部署在haproxy服務(wù)器上的。
在最外層服務(wù)器上,我們每天大約5億+(1-1.5億+的動(dòng)態(tài)請求、3-4億+的圖片請求)的請求量,共計(jì)使用7臺64G的Dell R410,目前看負(fù)載還很低,從系統(tǒng)的各種資源上看,請求量翻倍應(yīng)該是沒有問題的。
在最外層的服務(wù)器配置上,有一個(gè)問題值得注意,即sysctl.conf的配置中,timestamp必須為0,這個(gè)在tcp協(xié)議的擴(kuò)展標(biāo)準(zhǔn)中有提 到,否有nat環(huán)境的客戶端連接有可能產(chǎn)生異常,異常的狀況可以在netstat -s 的輸出中看到。還需要注意的是timestamp=0的情況下,tw_reuse是不生效的。
要保證服務(wù)器能夠接收大并發(fā)的連接請求是件不難的事情,但需要考慮一個(gè)細(xì)節(jié),每接收一個(gè)請求,haproxy就需要至少分配一個(gè)系統(tǒng)的tcp端口請 求后面的業(yè)務(wù)服務(wù)器、cache服務(wù)器,系統(tǒng)一個(gè)ip地址可用的端口數(shù)最多為65535,一般還需要減去1024。值得考慮的是減 小 tw_bucket 的容量,讓系統(tǒng)在tw_bucket滿的狀況下,對tw狀態(tài)的連接進(jìn)行丟棄,以達(dá)到快速回收的目的,tw的默認(rèn)回收時(shí)間的2倍的 MSL。還有一個(gè)方式就是多配置幾個(gè)ip。
還有一個(gè)問題,接入層的服務(wù)器往往會開啟iptables,內(nèi)核中nf的相關(guān)配置也是需要優(yōu)化的,比如 nf_conntrack_max、nf_conntrack_tcp_timeout_established等。
在業(yè)務(wù)層的優(yōu)化有nginx+php(fastcgi連接方式、php-fpm.conf配置中的優(yōu)化), 我的一個(gè)經(jīng)驗(yàn)是,如果nginx同phpcgi運(yùn)行在同一臺服務(wù)器,采用unix socket的方式進(jìn)行fastcgi協(xié)議的交互是效果最快的,比127.0.0.1的回環(huán)地址要快太多。我在08年優(yōu)化過一臺服務(wù)器(Dell 2960,16G內(nèi)存),通過兩個(gè)步驟,將一臺服務(wù)器從900qps,優(yōu)化到6000qps以上,其一是將fastcgi協(xié)議運(yùn)行在unix socket上,其二是合理配置spawn-fcgi的進(jìn)程數(shù)量?,F(xiàn)在基本上phpcgi都是運(yùn)行在php-fpm中的了,其進(jìn)程池邏輯是我最贊賞的功能 之一。
如果nginx和php-fpm不在同一臺服務(wù)器上,可以考慮使用fastcgi_keepalive的配置,實(shí)現(xiàn)nginx同fastcgi服務(wù)器持久連接,以提高效率。
nginx+php-fpm提供的運(yùn)行狀態(tài)非常有意義,nginx的status模塊和php-fpm的status輸出可以告訴我們nginx進(jìn) 程的請求處理狀況,php-fpm的status輸出可以告訴我們php-fpm的進(jìn)程池設(shè)置是否合理。我們目前對這兩個(gè)數(shù)據(jù)通過nagios定期采集, 并繪制成圖表,很有“觀賞價(jià)值”。
php-fpm.conf的配置中還有幾個(gè)參數(shù)對優(yōu)化比較重要,其一是進(jìn)程自動(dòng)重啟的條件pm.max_requests,其二是php-slow log的配置,slow log 是優(yōu)化php代碼的非常重要的信息。在我目前的環(huán)境中,php的慢執(zhí)行日志是通過rsyslog進(jìn)行傳輸并集中分析的,以此反向推進(jìn)開發(fā)對php 代碼的優(yōu)化。
php的服務(wù)器在高并發(fā)的情況下,有可能因?yàn)榉?wù)器本身可提供的端口數(shù)量的限制,無法同redis服務(wù)器建立大量的連接,這時(shí)候可以在 sysctl.conf中配合timestamps=1 加上tw_reuse/tw_recycle的方式,進(jìn)行端口快速回收,以便更好的向數(shù)據(jù)層建立 連接,接入層的haproxy是不可以這樣的。
這一層還涉及到一個(gè)安全問題,就是php代碼被修改并掛馬的狀況,我的解決方案是,將php-fpm的運(yùn)行用戶同php代碼的屬主設(shè)置成不同的用戶,并且保證php-fpm的運(yùn)行用戶不能對php代碼具有寫的權(quán)限。
#p#
數(shù)據(jù)層的情況里,MySQL主從結(jié)構(gòu)以及MHA+keepalived的高可用配置,這個(gè)基本上是看文檔應(yīng)該就能夠理解的。如果是5.6的新版 MySQL,其高可用監(jiān)控可能可以做的更簡單,MySQL官方提供對應(yīng)的工具,只是我還沒有測試。對MHA的監(jiān)控功能,我覺得亮點(diǎn)是MHA對切換過程中 MySQL binlog的獲取和執(zhí)行,在最大程度上避免了數(shù)據(jù)丟失。但是其缺點(diǎn)也是有的,比如:監(jiān)控進(jìn)程在觸發(fā)切換后就停止了,一旦觸發(fā),必須重新啟動(dòng)進(jìn)程再繼續(xù)監(jiān) 控。06年時(shí)我在sina做過一個(gè)叫Trust DMM的項(xiàng)目,通過 DNS、MON加上自己寫的插件,監(jiān)控MySQL主從集群的可用性,可以實(shí)現(xiàn),主庫、主備自動(dòng)切換(缺乏binlog處理的環(huán)節(jié)); 從庫是一組服務(wù)器,如果從庫發(fā)生問題,可以自動(dòng)下線。只是這套系統(tǒng)部署起來比較麻煩。這個(gè)項(xiàng)目曾經(jīng)獲得過sina的創(chuàng)新一等獎(jiǎng)。
我還提到了我認(rèn)為的DBA日常的工作至少應(yīng)該包括:審查并執(zhí)行上線SQL;定期檢查MySQL慢日志并分析,將分析結(jié)果反饋到開發(fā)部門進(jìn)行調(diào)整;定 期審查數(shù)據(jù)庫中索引的效率以及可用性,進(jìn)行優(yōu)化我反饋?,F(xiàn)在做一個(gè)一般水平的DBA已經(jīng)相當(dāng)容易了,對percona的工具了解透徹,已經(jīng)能夠解決非常多 的數(shù)據(jù)庫問題了。
MySQL還有一個(gè)難纏的問題,numa架構(gòu)下,大內(nèi)存服務(wù)器內(nèi)存使用效率的問題,numactl對策略進(jìn)行調(diào)整,如果使用percona的MySQL版本,可以通過 memlock配置對MySQL的Innodb引擎進(jìn)行限制,禁止其使用swap。
MySQL常見的架構(gòu)里,還有一種主從存儲引擎不一致的方式,即主庫采用Innodb引擎,提高并發(fā)寫入的能力,從庫采用Myisam引擎,這種方 式目前我們也在采用。這樣做一是為了獲取更好的讀性能,另外是,Myisam引擎的是可以節(jié)省內(nèi)存的。Myisam在索引數(shù)據(jù)內(nèi)存讀取,數(shù)據(jù)內(nèi)容磁盤讀取 的狀態(tài)下,已經(jīng)可以比較高效的運(yùn)行了,myisam_use_mmap的配置項(xiàng),會讓MySQL將myisam的data文件也mmap到內(nèi)存中,這樣做 既高效,又可以使用mysiam引擎的特性。
數(shù)據(jù)庫主庫要避免一件事情發(fā)生,就是無條件刪除和無條件修改,如“delete from table”以及”update table set xxx=yyyy”等無where條件語句,原則來講是應(yīng)該禁止執(zhí)行的,這樣的權(quán)限不應(yīng)該開放給開發(fā)的同學(xué),甚至DBA都不能無限制的操作。目前我的解決 方案是 sql_safe_updates=1,但這配置是不能夠?qū)憁y.cnf中的,只能啟動(dòng)mysql后進(jìn)入console進(jìn)行配置。
當(dāng)前我們還使用了Redis作為DB,基于主從架構(gòu),跨IDC。目前的問題是,復(fù)制連接斷開后,Redis快照重傳的問題,從庫會在快照替換期間有 短暫的性能抖動(dòng)。 Redis2.8新版本psync的特性應(yīng)該可以改善這個(gè)問題。我們還使用twemproxy,目前部署在每一臺php服務(wù)器上,并監(jiān) 聽unix socket,php使用phpredis的模塊進(jìn)行連接。有效減少三次握手的時(shí)間。temwproxy還有很多其他的優(yōu)秀特性,通過一致性hash做 cache集群,可以有效的避免cache遷移問題。通過其對后端redis的健康監(jiān)控,可以自動(dòng)下線有故障的redis。
還有針對多IDC的圖片存儲和Cache部署情況。目前我們自建的圖片CDN承載網(wǎng)站每天約4億的請求,帶寬最高峰值約1.5G左右,其結(jié)構(gòu)大體上 是中心IDC存儲圖片原圖+SQUID disk cache存儲圖片縮略圖,在外地IDC使用兩級緩存,分別為一層SQUID disk cache(兩臺,做HA),另一層為Varnish cache(最多四臺),實(shí)際上,如果僅考慮work around的狀態(tài),squid cache層基本上也可以不要的。但是,目前這樣的結(jié)構(gòu)可以減少varnish回中心節(jié)點(diǎn)的請求,減少中心機(jī)房帶寬的壓力。這個(gè)結(jié)構(gòu)還算簡 單,varnish在高并發(fā)請求下,有一些資源配置是需要注意的,比如NFILES / VARNISH_MAX_THREADS / nuke_limit 等。
溝通的技術(shù)問題還是非常多的,包括在井源那里提到監(jiān)控框架的事情,也尤其提到了我對rsyslog的優(yōu)化,優(yōu)化后的rsyslog在可靠性方面是非常值得稱贊的(優(yōu)化思路見參考6)
我有一些將電商三面的運(yùn)維運(yùn)維同學(xué)的問題綜合到這里了,有些話重復(fù)的就不再描述。
值得一提的是二面是另一位開發(fā)負(fù)責(zé)人,一看就是個(gè)很有獨(dú)立思考能力的同學(xué),他問了我一個(gè)很有意思的問題,大體的意思是,在系統(tǒng)架構(gòu)方面,有這樣的幾 個(gè)層次,從下往上:使用開源、精通開源,優(yōu)化并修改開源軟件,創(chuàng)造開源軟件。問我自己評價(jià)我是在哪一個(gè)層次的。我認(rèn)真的思考了一下,我應(yīng)該是在第二個(gè)層 次,有些精通的,有些修改過的。
電商四面是時(shí)間最長的,至少有兩個(gè)小時(shí)以上,結(jié)束的時(shí)候已經(jīng)是夜里一點(diǎn)四十了,我覺得電商的老大是應(yīng)該在支付寶里面給我捐一些錢才好的 ,不知道有沒有小米的同學(xué)能夠轉(zhuǎn)告哈 。我們應(yīng)該是談到了非常多的事情,包括秒殺的解決方案,包括對持續(xù)集成和自動(dòng)化測試的理解、對后臺數(shù)據(jù)業(yè)務(wù)類型的開發(fā)中數(shù)據(jù)計(jì)算錯(cuò)誤的理解,時(shí)不時(shí)能夠得到“我們想的很一致”這樣的評價(jià)。
那時(shí)已近半夜,記憶進(jìn)入低效態(tài),一些太瑣碎的事情記不得了,重復(fù)的技術(shù)方案也不再贅述。下面簡單描述一下我對秒殺的解決方案的理解:10w的數(shù)據(jù),從0到10w,不能多賣。目前的問題是,每次到秒殺時(shí)分可能同時(shí)進(jìn)入100w的請求/連接。如何破?
我的方案是:排除user、session等外部依賴服務(wù)的前提下,兩臺ha外面抗并發(fā)連接(后來想這個(gè)無所謂的,不如做成php的服務(wù)器),三臺PHP服務(wù)器(不要使用任何框架,最樸素的純粹PHP代碼),兩臺Redis(最初說了一臺)。具體優(yōu)化狀況如下:
- haproxy優(yōu)化能夠支持百萬并發(fā)連接,這個(gè)很容易了
- nginx優(yōu)化worker connections,優(yōu)化nginx的并發(fā)支持能力和請求隊(duì)列的接收能力
- php-fpm優(yōu)化listen.backlog,優(yōu)化fastcgi請求隊(duì)列的接收能力。
- Redis 假如在秒殺的1分鐘內(nèi),服務(wù)器不出現(xiàn)故障,優(yōu)化redis的最大連接數(shù)
- 優(yōu)化所有服務(wù)器的網(wǎng)卡、sysctl參數(shù)
php的邏輯可以簡單的理解為對redis的某一個(gè)key進(jìn)行incr原子操作,如果返回的當(dāng)前數(shù)值小于等于10w(兩臺redis的情況下應(yīng)小于等于5w),則認(rèn)為中簽。
從我以前看到的數(shù)據(jù)來講,redis的最好狀態(tài)在8w qps。nginx+php在08年時(shí)已經(jīng)優(yōu)化到6000 qps,目前的服務(wù)器設(shè)備(雙核16cpu+64G內(nèi)存)達(dá)到2、3wQps應(yīng)該也是不難的事情(這個(gè)的最新數(shù)據(jù)我不知道)。上述配置至少應(yīng)該能夠在5s 內(nèi)完成10w次redis的incr操作。加上系統(tǒng)各系統(tǒng)對請求隊(duì)列的支持,可以幾乎做到不報(bào)錯(cuò),短暫延遲。
如果考慮1臺redis請求量會很高,可以考慮分片,每臺分5w。
當(dāng)然,這是在僅僅思考不到1分鐘內(nèi)給出的方案,從現(xiàn)在來看,haproxy是可以不要,nginx扛并發(fā)連接的能力也不錯(cuò)。所有的細(xì)節(jié)還需要通過壓 力測試進(jìn)行驗(yàn)證。而實(shí)際情況加上對其他服務(wù)的依賴(我不知到還有哪些,抽絲剝繭去除干擾),方案也會更加復(fù)雜一些。據(jù)電商老大講,實(shí)際情況是,秒殺的服務(wù) 用了十幾臺服務(wù)器,秒殺的時(shí)候偶爾出現(xiàn)一些故障,小米做秒殺的同學(xué),壓力很大哦。
如果你提到要記錄中簽的用戶的uid和中簽號碼,還是redis吧。
(突然wps的linux版崩潰了,只能恢復(fù)到這里,后面的部分內(nèi)容是重寫的,可能有點(diǎn)混亂)
針對剛才的問題,我在白板上畫了個(gè)簡單的架構(gòu)圖:haproxy+nginx/php+redis,haproxy和nginx/php都是可線性 擴(kuò)展的,redis可以通過sharding來實(shí)現(xiàn)擴(kuò)展。理論上講,一個(gè)可擴(kuò)展的架構(gòu)是可以滿足任何性能要求的,更何況如此簡單的邏輯,單機(jī)性能已經(jīng)可以 做到非常高了。
電商王姓負(fù)責(zé)人在問我方案時(shí)問這個(gè)需求會有哪些難點(diǎn)?我看著白板笑笑:目前看,應(yīng)該不存在難點(diǎn)。如果有問題,應(yīng)該看日志和服務(wù)狀態(tài)以及服務(wù)器狀態(tài)。
第四面聊得很頭機(jī),對方幾次想結(jié)束時(shí)都突然冒出來一個(gè)問題,每一個(gè)都會討論比較久,比如后臺的一些計(jì)算操作是否換成java更合適,因?yàn)閖ava可 以更嚴(yán)謹(jǐn)。我說這可能不是語言的問題,而是程序員習(xí)慣和素質(zhì)的問題,如果想換,其實(shí)我倒是更愿意嘗鮮,比如用go,還可能可以同時(shí)滿足性能的問題。
還有突然聊到持續(xù)集成,我坦言,我對持續(xù)集成的理解停留在用工具實(shí)現(xiàn)自動(dòng)測試和發(fā)布這樣的層面上,沒有實(shí)操經(jīng)驗(yàn)。但我個(gè)人的一個(gè)粗淺的認(rèn)知是:持續(xù) 集成的前提是自動(dòng)化測試,自動(dòng)化測試的兩個(gè)難點(diǎn):1,自動(dòng)化測試用例的設(shè)計(jì);2,程序員對自動(dòng)化測試的理解和心理反抗程度。我在目前單位有過短暫的嘗試:專業(yè)的傳統(tǒng)測試人員對測試用例進(jìn)行設(shè)計(jì),程序員接收到的需求應(yīng)該包括正向邏輯的產(chǎn)品需求和測試用例的需求。開發(fā)工作完成的標(biāo)記是:自己寫的測試用例在自己的代碼上完全通過,代表自己一項(xiàng)開發(fā)工作的完成。
說到這里,對方不禁雙手伸出拇指?。ü?/p>
或多或少也還有一些別的話題,我自認(rèn)為那晚像演講一樣很精彩,只不過時(shí)間已過午夜,其他的一些細(xì)節(jié)不太記得了,如果想起或小米參加面試的同學(xué)有提起,我再補(bǔ)充了。
整場小米的面試兩個(gè)部門加起來共計(jì)約7個(gè)小時(shí),這是我經(jīng)歷過的最長時(shí)間的面試了……小米的面試很辛苦,今天碼字也很辛苦,現(xiàn)在已經(jīng)是凌晨1點(diǎn)半了,如果你覺得上面的經(jīng)過對你有所幫助或是有意思,就捧個(gè)錢場或人場吧: http://me.alipay.com/chunshengster