萬(wàn)字長(zhǎng)文,20-50K前端工程師部分面試題集錦 - 附答案(收藏!)
現(xiàn)在20-50K的招聘,我們先看看是什么要求???
螞蟻金服招聘要求:

蝦皮招聘:

騰訊:

明源云:

毫無(wú)疑問(wèn),這些公司都是招聘的大前端技術(shù)棧的職位,之前文章提到過(guò)2020年大前端最理想的技術(shù)棧,其實(shí)真的弄得很明白那些,出去面試基本上不會(huì)有什么問(wèn)題。
小提示:如果發(fā)現(xiàn)小公司面試套你的技術(shù)和架構(gòu),迅速結(jié)束,開(kāi)出天價(jià)薪資走人
?下面正式公布部分面試題,以及答案
出于對(duì)各個(gè)公司的尊重,不公布是哪家公司的面試題,以及面試技巧。只公布部分面試題和答案,以及分析問(wèn)題的角度,學(xué)習(xí)方向,面試中考察的不僅僅技術(shù)深度,還有廣度,每個(gè)人不可能技術(shù)面面俱到,前端學(xué)習(xí)的東西太多,忘掉一部分也是正常。記住核心就是關(guān)鍵,這些都是一些基礎(chǔ)面試題,比較通用。
一般面試都會(huì)要做題,據(jù)我經(jīng)驗(yàn)看,一般都是6頁(yè),三張紙。考察的大部分是前端技術(shù)棧,原生Javascript的內(nèi)容,當(dāng)然,有的外企的面試體驗(yàn)更棒,技術(shù)一面規(guī)定是半個(gè)小時(shí),國(guó)內(nèi)公司可能有5輪,甚至6、7輪。
面試題我會(huì)歸納成原生JavaScript、Node.js、React、Vue、通信協(xié)議、運(yùn)維部署、CI自動(dòng)化部署、Docker、性能優(yōu)化、前端架構(gòu)設(shè)計(jì)、后端常見(jiàn)的架構(gòu)等來(lái)分開(kāi)寫
原生JavaScript篇
以下代碼跟我寫的有點(diǎn)不一樣,但是大致差不多,最終都是在紙上手寫實(shí)現(xiàn)
手寫一個(gè)深拷貝:

此處省略了一些其他類型的處理,可以在題目旁注釋、
手寫一個(gè)reduce:

Array.isArray的原理:

手寫一個(gè)的防抖函數(shù):

手寫一個(gè)Promise:
const PENDING = "pending";const FULFILLED = "fulfilled";const REJECTED = "rejected";function MyPromise(fn) {const self = this;self.value = null;self.error = null;self.status = PENDING;self.onFulfilledCallbacks = [];self.onRejectedCallbacks = [];function resolve(value) {if (value instanceof MyPromise) {return value.then(resolve, reject);}if (self.status === PENDING) {setTimeout(() => {self.status = FULFILLED;self.value = value;self.onFulfilledCallbacks.forEach((callback) => callback(self.value));}, 0)}}function reject(error) {if (self.status === PENDING) {setTimeout(function() {self.status = REJECTED;self.error = error;self.onRejectedCallbacks.forEach((callback) => callback(self.error));}, 0)}}try {fn(resolve, reject);} catch (e) {reject(e);}}function resolvePromise(bridgepromise, x, resolve, reject) {if (bridgepromise === x) {return reject(new TypeError('Circular reference'));}let called = false;if (x instanceof MyPromise) {if (x.status === PENDING) {x.then(y => {resolvePromise(bridgepromise, y, resolve, reject);}, error => {reject(error);});} else {x.then(resolve, reject);}} else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {try {let then = x.then;if (typeof then === 'function') {then.call(x, y => {if (called) return;called = true;resolvePromise(bridgepromise, y, resolve, reject);}, error => {if (called) return;called = true;reject(error);})} else {resolve(x);}} catch (e) {if (called) return;called = true;reject(e);}} else {resolve(x);}}MyPromise.prototype.then = function(onFulfilled, onRejected) {const self = this;let bridgePromise;onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };if (self.status === FULFILLED) {return bridgePromise = new MyPromise((resolve, reject) => {setTimeout(() => {try {let x = onFulfilled(self.value);resolvePromise(bridgePromise, x, resolve, reject);} catch (e) {reject(e);}}, 0);})}if (self.status === REJECTED) {return bridgePromise = new MyPromise((resolve, reject) => {setTimeout(() => {try {let x = onRejected(self.error);resolvePromise(bridgePromise, x, resolve, reject);} catch (e) {reject(e);}}, 0);});}if (self.status === PENDING) {return bridgePromise = new MyPromise((resolve, reject) => {self.onFulfilledCallbacks.push((value) => {try {let x = onFulfilled(value);resolvePromise(bridgePromise, x, resolve, reject);} catch (e) {reject(e);}});self.onRejectedCallbacks.push((error) => {try {let x = onRejected(error);resolvePromise(bridgePromise, x, resolve, reject);} catch (e) {reject(e);}});});}}MyPromise.prototype.catch = function(onRejected) {return this.then(null, onRejected);}MyPromise.deferred = function() {let defer = {};defer.promise = new MyPromise((resolve, reject) => {defer.resolve = resolve;defer.reject = reject;});return defer;}try {module.exports = MyPromise} catch (e) {}
promisify原理:
promisify = function(fn) {return function() {var args = Array.from(arguments);return new MyPromise(function(resolve, reject) {fn.apply(null,args.concat(function(err) {err ? reject(err) : resolve(arguments[1]);}));});};};
Redux核心源碼解析:
bindActionCreator源碼解析:
export default function bindActionCreator(actions, dispatch) {let newActions = {};for (let key in actions) {newActions[key] = () => dispatch(actions[key].apply(null, arguments));}return newActions;}
核心:將多個(gè)action和dispatch傳入,合并成一個(gè)全新的actions對(duì)象
combineReducers源碼:
export default combineReducers = reducers => (state = {}, action) => Object.keys(reducers).reduce((currentState, key) => {currentState[key] = reducers[key](state[key], action);return currentState;}, {});
核心:跟上面有點(diǎn)類似,遍歷生成一個(gè)全新的state,將多個(gè)state合并成一個(gè)state
createStore源碼結(jié)合applyMiddleware講述如何實(shí)現(xiàn)處理多個(gè)中間件:
export default function createStore(reducer, enhancer) {if (typeof enhancer !== 'undefined') {return enhancer(createStore)(reducer)}let state = nullconst listeners = []const subscribe = (listener) => {listeners.push(listener)}const getState = () => stateconst dispatch = (action) => {state = reducer(state, action)listeners.forEach((listener) => listener())}dispatch({})return { getState, dispatch, subscribe }}
applyMiddleware:
export default function applyMiddleware(...middlewares) {return (createStore) => (reducer) => {const store = createStore(reducer)let dispatch = store.dispatchlet chain = []const middlewareAPI = {getState: store.getState,dispatch: (action) => dispatch(action)}chain = middlewares.map(middleware => middleware(middlewareAPI))dispatch = compose(...chain)(store.dispatch)return {...store,dispatch}}}
核心:當(dāng)發(fā)現(xiàn)傳入createStore的第二個(gè)或者第三個(gè)參數(shù)存在時(shí)候(這里沒(méi)有像原生redux支持SSR代碼注水,不支持第二個(gè)參數(shù)initState),就去返回它的調(diào)用結(jié)果
整個(gè)Redux這里是最繞的,這里不做過(guò)分的源碼講解,其實(shí)核心就是一點(diǎn):
實(shí)現(xiàn)多個(gè)中間件原理,就是將dispatch當(dāng)作最后一個(gè)函數(shù)傳入,利用compose這個(gè)工具函數(shù),最終實(shí)現(xiàn)多個(gè)中間件同時(shí)起作用,當(dāng)你源碼看得比較多的時(shí)候會(huì)發(fā)現(xiàn),大多數(shù)的源碼是跟redux相似
compose工具函數(shù)實(shí)現(xiàn):
export default function compose(...funcs) {return funcs.reduce((a, b) => (...args) => a(b(...args)));}
核心:其實(shí)就是一個(gè)reduce函數(shù)實(shí)現(xiàn),每次返回一個(gè)新的函數(shù),再將新的參數(shù)傳入
redux下次會(huì)專門出個(gè)文章講解,它的源碼太重要了~
原生JavaScript考察點(diǎn)比較多,這里只列出一部分,還有像結(jié)合TypeScript一起問(wèn)的,組合繼承,對(duì)象創(chuàng)建模式、設(shè)計(jì)模式等,但是那些本次不做講解
Node.js篇幅:
簡(jiǎn)述Node.js的EventLoop:

現(xiàn)場(chǎng)出題,項(xiàng)目里有下面這段代碼,輸出是什么,穩(wěn)定嗎,說(shuō)明原因:
setTimeout(() => {console.log(1);});//----若干代碼邏輯new Promise((resolve, reject) => {resolve();}).then(() => {console.log(2);});
答案:先輸出2,再輸出1,但是不穩(wěn)定。因?yàn)槎〞r(shí)器的執(zhí)行時(shí)間不確定,node.js的輪詢相當(dāng)于一個(gè)定時(shí)器,一直從上往下6個(gè)階段輪詢,此時(shí)如果中間代碼比較耗時(shí),還沒(méi)運(yùn)行到Promise時(shí)候,已經(jīng)輪詢到第一階段,定時(shí)器的回調(diào)就會(huì)被觸發(fā)。
Node.js為什么處理異步IO快?
答:Node 底層采用線程池的原理管理異步 IO,所以我們通常所的 單線程是指 Node 中 JavaScript 的執(zhí)行是單線程的,但 Node 本身是多線程的。Node.js 中異步 IO 是通過(guò)事件循環(huán)的方式實(shí)現(xiàn)的,異步 IO 事件主要來(lái)源于網(wǎng)絡(luò)請(qǐng)求和文件 IO。但是正因?yàn)槿绱耍琋ode.js處理很多計(jì)算密集型的任務(wù),就比較吃力,當(dāng)然有多進(jìn)程方式可以解決這個(gè)問(wèn)題。(自己給自己挖坑)
以前聽(tīng)過(guò)一個(gè)很形象的回答:Java是一百個(gè)服務(wù)員對(duì)應(yīng)一百個(gè)用餐客人,Node是一個(gè)服務(wù)員對(duì)應(yīng)一百個(gè)用餐客人(因?yàn)榭腿瞬恍枰址昼姺?wù),可能只要三分鐘----好像,東哥?)
Node.js有cluster、fork兩種模式多進(jìn)程,那么這兩種情況下,主進(jìn)程負(fù)責(zé)TCP通信,怎樣才可以讓子進(jìn)程共享用戶的Socket對(duì)象?
答案:cluster模式,多實(shí)例、自動(dòng)共享端口鏈接、自動(dòng)實(shí)現(xiàn)負(fù)載均衡。fork模式實(shí)現(xiàn)的多進(jìn)程,單實(shí)例、多進(jìn)程,可以通過(guò)手動(dòng)分發(fā)socket對(duì)象給不同子進(jìn)程進(jìn)行定制化處理、實(shí)現(xiàn)負(fù)載均衡
Node.js多進(jìn)程維護(hù),以及通信方式:
答案:原生的cluster和fork模式都有API封裝好的進(jìn)行通信。如果是execfile這樣形式調(diào)起第三方插件形式,想要與第三方插件進(jìn)行通信,可以自己封裝一個(gè)類似promisyfy形式進(jìn)行通信,維護(hù)這塊,子進(jìn)程可以監(jiān)聽(tīng)到異常,一旦發(fā)現(xiàn)異常,立刻通知主進(jìn)程,殺死這個(gè)異常的子進(jìn)程,然后重新開(kāi)啟一個(gè)子進(jìn)程~
簡(jiǎn)單談?wù)劊?span style="color: rgb(255, 41, 65);">Node.js搭建TCP、restful、websocket、UDP服務(wù)器,遇到過(guò)哪些問(wèn)題,怎么解決的
答案:這里涉及的問(wèn)題比較多,考察全方位的通信協(xié)議知識(shí),需要出個(gè)專題后期進(jìn)行編寫
看你簡(jiǎn)歷上寫,對(duì)koa源碼系統(tǒng)學(xué)習(xí)過(guò),請(qǐng)簡(jiǎn)述核心洋蔥圈的實(shí)現(xiàn):
答案:洋蔥圈的實(shí)現(xiàn),有點(diǎn)類似Promise中的then實(shí)現(xiàn),每次通過(guò)use方法定義中間件函數(shù)時(shí)候,就會(huì)把這個(gè)函數(shù)存入一個(gè)隊(duì)列中,全局維護(hù)一個(gè)ctx對(duì)象,每次調(diào)用next(),就會(huì)調(diào)用隊(duì)列的下一個(gè)任務(wù)函數(shù)。偽代碼實(shí)現(xiàn)~:
use (fn) {// this.fn = fn 改成:this.middlewares.push(fn) // 每次use,把當(dāng)前回調(diào)函數(shù)存進(jìn)數(shù)組}compose(middlewares, ctx){ // 簡(jiǎn)化版的compose,接收中間件數(shù)組、ctx對(duì)象作為參數(shù)function dispatch(index){ // 利用遞歸函數(shù)將各中間件串聯(lián)起來(lái)依次調(diào)用if(index === middlewares.length) return // 最后一次next不能執(zhí)行,不然會(huì)報(bào)錯(cuò)let middleware = middlewares[index] // 取當(dāng)前應(yīng)該被調(diào)用的函數(shù)middleware(ctx, () => dispatch(index + 1)) // 調(diào)用并傳入ctx和下一個(gè)將被調(diào)用的函數(shù),用戶next()時(shí)執(zhí)行該函數(shù)}dispatch(0)}
所以這里說(shuō),源碼看多了會(huì)發(fā)現(xiàn),其實(shí)大都差不多,都是你抄我的,我抄你的,輪子上搭積木
你對(duì)TCP系統(tǒng)學(xué)習(xí)過(guò),請(qǐng)你簡(jiǎn)述下SYN flood攻擊:
小提示:我的TCP是跟張師傅學(xué)習(xí)的,在某金的小冊(cè)上有賣。~推薦購(gòu)買
答案:攻擊方偽造源地址發(fā)送SYN報(bào)文,服務(wù)端此時(shí)回復(fù)syn+ack,但是真正的IP地址收到這個(gè)包之后,有可能直接回復(fù)了RST包,但是如果不回復(fù)RST包,那就更嚴(yán)重了,可能服務(wù)端會(huì)在幾十秒后才關(guān)閉這個(gè)socket鏈接(時(shí)間根據(jù)每個(gè)系統(tǒng)不一樣)

抓包可見(jiàn)~:

TCP可以快速握手嗎?
答案:可以? -- 內(nèi)容來(lái)自? ?張師傅的小冊(cè)

TCP鏈接和UDP的區(qū)別,什么時(shí)候選擇使用UDP鏈接?
http://www.xiuchuang.com/question/4019.html
總結(jié)就是:TCP面向鏈接,UDP面向消息,TCP的ACK作用是確認(rèn)已經(jīng)收到上一個(gè)包,UDP只管發(fā)送,一些無(wú)人機(jī)的操作,就用UDP鏈接,每個(gè)無(wú)人機(jī)就是一個(gè)服務(wù)器,跟地面通訊。
通信協(xié)議還是要系統(tǒng)學(xué)習(xí),通信這里也問(wèn)了大概半個(gè)小時(shí),包括密鑰交換等
看你簡(jiǎn)歷上有寫自己實(shí)現(xiàn)了一個(gè)mini-react,請(qǐng)簡(jiǎn)述實(shí)現(xiàn)原理,以及diff算法實(shí)現(xiàn)
倉(cāng)庫(kù)地址:https://github.com/JinJieTan/mini-react答案:利用了babel,將虛擬dom轉(zhuǎn)換成了我想要的對(duì)象格式,然后實(shí)現(xiàn)了異步setState、component diff 、 element diff 、props 更新等。類似PReact的將真實(shí)dom和虛擬dom對(duì)比的方式進(jìn)行diff,這里結(jié)合代碼講了大概半個(gè)小時(shí)~ 大家可以看源碼,這個(gè)對(duì)于學(xué)習(xí)React是非常好的資料,當(dāng)時(shí)我花了半個(gè)多月學(xué)習(xí)
看你對(duì)Vue的源碼有系統(tǒng)學(xué)習(xí)過(guò),請(qǐng)簡(jiǎn)述下Vue2.x版本的數(shù)據(jù)綁定:

答案:Vue里面的{{}}寫法,?會(huì)用正則匹配后,拿到數(shù)據(jù)跟data里的做對(duì)比-解析指令,觀察數(shù)據(jù)變化是利用defineProperty來(lái)實(shí)現(xiàn),因?yàn)楸O(jiān)聽(tīng)不到數(shù)組的變化,所以尤大大只重寫了6個(gè)數(shù)組API。源碼解析,后面就是拼細(xì)節(jié),主要講一些核心點(diǎn)的實(shí)現(xiàn)。
為什么Vue的nextTick不穩(wěn)定?
答案:Vue的nextTick原理是:
優(yōu)雅降級(jí):
首選promise.then
然后是setImmediate
然后是一個(gè)瀏覽器目前支持不好的API
最后是setTimeout
dom真正更新渲染好的時(shí)間,不能真正確定,不論是框架還是原生,都存在這個(gè)問(wèn)題。所以用nextTick并不能保證拿到最新的dom
談?wù)勀銓?duì)微前端的看法,以及實(shí)踐:
答案:將Vue和React一起開(kāi)發(fā),其實(shí)一點(diǎn)都不難,只要自己能造出Redux這樣的輪子,熟悉兩個(gè)框架原理,就能一起開(kāi)發(fā),難的是將這些在一個(gè)合適的場(chǎng)景中使用。之前看到網(wǎng)上有微前端的實(shí)踐,但是并不是那么完美,當(dāng)然,類似Electron這樣的應(yīng)用,混合開(kāi)發(fā)很正常,微前端并不是只單單多個(gè)框架混合開(kāi)發(fā),更多是多個(gè)框架引入后解決了什么問(wèn)題、帶來(lái)的問(wèn)題怎么解決?畢竟5G還沒(méi)完全普及,數(shù)據(jù)傳輸還是不那么快。過(guò)大的包容易帶來(lái)客戶端的過(guò)長(zhǎng)白屏?xí)r間(自己給自己挖坑)
你有提到白屏?xí)r間,有什么辦法可以減少嗎?都是什么原理
答案: GZIP,SSR同構(gòu)、PWA應(yīng)用、預(yù)渲染、localStorage緩存js文件等、
下面就是細(xì)分拆解答案,無(wú)限的連帶問(wèn)題,這里非常耗時(shí),這些內(nèi)容大都網(wǎng)上能搜到,我這里就不詳細(xì)說(shuō)
其中有問(wèn)到PWA的原理,我的回答是:
?Service Worker 有一套自己的聲明周期,當(dāng)安裝并且處于激活狀態(tài)時(shí)候,網(wǎng)站在https或者localhost的協(xié)議時(shí)候,可以攔截過(guò)濾發(fā)出的請(qǐng)求,會(huì)先把請(qǐng)求克隆一份(請(qǐng)求是流,消費(fèi)就沒(méi)有了),然后判斷請(qǐng)求的資源是否在?Service Worker?緩存中,如果存在那么可以直接從?Service Worker?緩存中取出,如果不存在,那么就真正的發(fā)出這個(gè)請(qǐng)求。
?
看你的技術(shù)棧對(duì)Electron比較熟悉,有使用過(guò)React-native,請(qǐng)你談?wù)勈褂玫母惺埽?/p>
答案:React-native的坑還是比較多,但是目前也算擁有成熟的生態(tài)了,開(kāi)發(fā)簡(jiǎn)單的APP可以使用它。但是復(fù)雜的應(yīng)用還是原生比較好,Electron目前非常受歡迎,它基本上可以完成桌面應(yīng)用的大部分需求,重型應(yīng)用開(kāi)發(fā)也是完全沒(méi)問(wèn)題的,可以配合大量C# C++插件等。?
Node.js的消息隊(duì)列應(yīng)用場(chǎng)景是什么?原理是什么
答案:我們公司之前用的kafka,消息隊(duì)列的核心概念,異步,提供者,消費(fèi)者。例如IM應(yīng)用,每天都會(huì)有高峰期,但是我們不可能為了高峰期配置那么多服務(wù)器,那樣就是浪費(fèi),所以使用消息隊(duì)列,在多長(zhǎng)時(shí)間內(nèi)流量達(dá)到多少,就控制消費(fèi)頻率,例如客戶端是流的提供者,有一個(gè)中間件消費(fèi)隊(duì)列,我們的服務(wù)器是消費(fèi)者,每次消費(fèi)一個(gè)任務(wù)就回復(fù)一個(gè)ACK給消費(fèi)隊(duì)列,消費(fèi)頻率由我們控制,這樣任務(wù)不會(huì)丟失,服務(wù)器也不會(huì)掛。?還有一個(gè)異步問(wèn)題,一個(gè)用戶下單購(gòu)買一件商品,可能要更新庫(kù)存,已購(gòu)數(shù)量,支付,下單等任務(wù)。不可能同步進(jìn)行,這時(shí)候需要異步并行,事務(wù)方式處理。這樣既不耽誤時(shí)間,也能確保所有的任務(wù)成功才算成功,不然沒(méi)有支付成功,但是已購(gòu)數(shù)量增長(zhǎng)了就有問(wèn)題。
此處省略、、、一萬(wàn)字
用戶就是要上傳10個(gè)G的文件,服務(wù)器存儲(chǔ)允許的情況下,你會(huì)怎么處理保證整體架構(gòu)順暢,不影響其他用戶?
答案:我會(huì)準(zhǔn)備兩個(gè)服務(wù)器上傳接口,前端或者原生客戶端上傳文件可以拿到文件大小,根據(jù)文件大小,分發(fā)不同的對(duì)應(yīng)服務(wù)器接口處理上傳,大文件可以進(jìn)行斷點(diǎn)續(xù)傳,原理是md5生成唯一的hash值,將分片的hash數(shù)組先上傳到后端,然后將文件分片上傳,對(duì)比hash值,相同的則丟棄。不一致的話,根據(jù)數(shù)組內(nèi)容進(jìn)行buffer拼接生成文件。
關(guān)于服務(wù)器性能,大文件上傳的服務(wù)器允許被阻塞,小文件的服務(wù)器不會(huì)被阻塞。
談?wù)勀銓?duì)前端、客戶端架構(gòu)的認(rèn)識(shí)?
答案:前端的架構(gòu),首先明確項(xiàng)目的兼容性,面向?yàn)g覽器編程,是否做成PC、移動(dòng)端的響應(yīng)式布局。根據(jù)項(xiàng)目規(guī)模、后期可能迭代的需求制定技術(shù)方案,如果比較重型的應(yīng)用應(yīng)該選用原生開(kāi)發(fā),盡量少使用第三方庫(kù)。
客戶端架構(gòu):是否跨平臺(tái),明確兼容系統(tǒng),例如是否兼容XP ,如果兼容XP就選擇nw.js,再然后根據(jù)項(xiàng)目復(fù)雜度招聘相應(yīng)技術(shù)梯度人員,安排系統(tǒng)學(xué)習(xí)相關(guān)內(nèi)容,招聘人員或者購(gòu)買定制開(kāi)發(fā)相關(guān)原生插件內(nèi)容。
雖然說(shuō)只是談?wù)劊歉杏X(jué)面試的職位越高級(jí)、輪數(shù)越往后,越考驗(yàn)?zāi)愕募軜?gòu)能力,前面考察基礎(chǔ),后面考察你的技術(shù)廣度以及邏輯思維,能否在復(fù)雜的應(yīng)用中保持清醒頭腦,定位性能這類型的細(xì)節(jié)能力。很多人基礎(chǔ)面試面得很好,但是拿不到offer,原因就是沒(méi)有這種架構(gòu)能力,只能自己寫代碼,不能帶領(lǐng)大家學(xué)習(xí)、寫代碼。這也是我在面試時(shí)偶然聽(tīng)到某個(gè)大公司HR之間的對(duì)話,原話是:他面試還可以,看起來(lái)是很老實(shí)(某個(gè)之前的面試者),但是他對(duì)之前項(xiàng)目整體流程并不是那么清楚,連自己做的項(xiàng)目,前后端流程都不清楚,感覺(jué)不合適。
介紹一下Redis,為什么快,怎么做持久化存儲(chǔ),什么叫緩存擊穿?
答案:Redis將數(shù)據(jù)存儲(chǔ)在內(nèi)存中,key-value形式存儲(chǔ),所以獲取也快。支持的key格式相對(duì)于memorycache更多,而且支持RDB快照形式、AOF

1.RDB持久化是指在指定的時(shí)間間隔內(nèi)將內(nèi)存中的數(shù)據(jù)集快照寫入磁盤,實(shí)際操作過(guò)程是fork一個(gè)子進(jìn)程,先將數(shù)據(jù)集寫入臨時(shí)文件,寫入成功后,再替換之前的文件,用二進(jìn)制壓縮存儲(chǔ)。RDB是Redis默認(rèn)的持久化方式,會(huì)在對(duì)應(yīng)的目錄下生產(chǎn)一個(gè)dump.rdb文件,重啟會(huì)通過(guò)加載dump.rdb文件恢復(fù)數(shù)據(jù)。
2、優(yōu)點(diǎn)
1)只有一個(gè)文件dump.rdb,方便持久化;
2) 容災(zāi)性好,一個(gè)文件可以保存到安全的磁盤;
3) 性能最大化,fork子進(jìn)程來(lái)完成寫操作,讓主進(jìn)程繼續(xù)處理命令,所以是IO最大化(使用單獨(dú)子進(jìn)程來(lái)進(jìn)行持久化,主進(jìn)程不會(huì)進(jìn)行任何IO操作,保證了redis的高性能) ;
4)如果數(shù)據(jù)集偏大,RDB的啟動(dòng)效率會(huì)比AOF更高。
1)數(shù)據(jù)安全性低。(RDB是間隔一段時(shí)間進(jìn)行持久化,如果持久化之間redis發(fā)生故障,會(huì)發(fā)生數(shù)據(jù)丟失。所以這種方式更適合數(shù)據(jù)要求不是特別嚴(yán)格的時(shí)候)
2)由于RDB是通過(guò)fork子進(jìn)程來(lái)協(xié)助完成數(shù)據(jù)持久化工作的,因此,如果當(dāng)數(shù)據(jù)集較大時(shí),可能會(huì)導(dǎo)致整個(gè)服務(wù)器停止服務(wù)幾百毫秒,甚至是1秒鐘。
AOF持久化是以日志的形式記錄服務(wù)器所處理的每一個(gè)寫、刪除操作,查詢操作不會(huì)記錄,以文本的方式記錄,文件中可以看到詳細(xì)的操作記錄。她的出現(xiàn)是為了彌補(bǔ)RDB的不足(數(shù)據(jù)的不一致性),所以它采用日志的形式來(lái)記錄每個(gè)寫操作,并追加到文件中。Redis 重啟的會(huì)根據(jù)日志文件的內(nèi)容將寫指令從前到后執(zhí)行一次以完成數(shù)據(jù)的恢復(fù)工作。

2、優(yōu)點(diǎn)
1)數(shù)據(jù)安全性更高,AOF持久化可以配置appendfsync屬性,其中always,每進(jìn)行一次命令操作就記錄到AOF文件中一次。
2)通過(guò)append模式寫文件,即使中途服務(wù)器宕機(jī),可以通過(guò)redis-check-aof工具解決數(shù)據(jù)一致性問(wèn)題。
3)AOF機(jī)制的rewrite模式。(AOF文件沒(méi)被rewrite之前(文件過(guò)大時(shí)會(huì)對(duì)命令進(jìn)行合并重寫),可以刪除其中的某些命令(比如誤操作的flushall))
3、缺點(diǎn)
1)AOF文件比RDB文件大,且恢復(fù)速度慢;數(shù)據(jù)集大的時(shí)候,比rdb啟動(dòng)效率低。
2)根據(jù)同步策略的不同,AOF在運(yùn)行效率上往往會(huì)慢于RDB。
具體可看、
https://baijiahao.baidu.com/s?id=1631700601579800845&wfr=spider&for=pcRedis可以配合session等做服務(wù)端持久化存儲(chǔ)、還介紹了下session的場(chǎng)景,
介紹下緩存擊穿和穿透:
緩存穿透,是指查詢一個(gè)數(shù)據(jù)庫(kù)一定不存在的數(shù)據(jù)。正常的使用緩存流程大致是,數(shù)據(jù)查詢先進(jìn)行緩存查詢,如果key不存在或者key已經(jīng)過(guò)期,再對(duì)數(shù)據(jù)庫(kù)進(jìn)行查詢,并把查詢到的對(duì)象,放進(jìn)緩存。如果數(shù)據(jù)庫(kù)查詢對(duì)象為空,則不放進(jìn)緩存。
緩存擊穿,是指一個(gè)key非常熱點(diǎn),在不停的扛著大并發(fā),大并發(fā)集中對(duì)這一個(gè)點(diǎn)進(jìn)行訪問(wèn),當(dāng)這個(gè)key在失效的瞬間,持續(xù)的大并發(fā)就穿破緩存,直接請(qǐng)求數(shù)據(jù)庫(kù),就像在一個(gè)屏障上鑿開(kāi)了一個(gè)洞。
介紹下你會(huì)用的自動(dòng)化構(gòu)建的方式:
答案
Jenkins自動(dòng)化構(gòu)建
自己搭建Node.js服務(wù)器,實(shí)現(xiàn)Jenkins
Docker配合Travis CI實(shí)現(xiàn)自動(dòng)化構(gòu)建
細(xì)化答案:
Jenkins自動(dòng)化構(gòu)建:
配置,自動(dòng)同步某個(gè)分支代碼,打包構(gòu)建。
自己搭建Node.js服務(wù)器,實(shí)現(xiàn)Jenkins:
自己搭建Node.js的服務(wù)器,在GitLab上指定webhook地址,分支代碼更新觸發(fā)事件,服務(wù)器接受到post請(qǐng)求,里面附帶分支的信息,執(zhí)行自己的shell腳本命令,指定文件夾,構(gòu)建打包。
服務(wù)器上使用Docker-compose指定鏡像,每次代碼推送到gitHub,通過(guò)自己編寫的yml和dockerfile文件構(gòu)建打包,服務(wù)器自動(dòng)拉取最新鏡像并且發(fā)布到正式環(huán)境
代碼實(shí)現(xiàn):
.travis.yml
language: node_jsnode_js:- '12'services:- dockerbefore_install:- npm install- npm install -g parcel-bundlerscript:- parcel build ./index.js- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin- docker build -t jinjietan/mini-react:latest .- docker push jinjietan/mini-react:latest
dockerfile:
FROM nginxCOPY ./index.html /usr/share/nginx/html/COPY ./dist /usr/share/nginx/html/distEXPOSE 80
問(wèn)完Redis,肯定會(huì)問(wèn)數(shù)據(jù)庫(kù),mysql mongodb sqlite都問(wèn)了,這里就暫時(shí)不寫了
數(shù)據(jù)庫(kù)需要系統(tǒng)的學(xué)習(xí),特別是mysql,我這里就不班門弄斧了,推薦某金上面的小孩子的小冊(cè)。零蛋學(xué)mysql(本文沒(méi)有收取任何廣告費(fèi),自己買了看完才推薦給大家)
附帶的一些問(wèn)題:
Linux常見(jiàn)操作
云端部署
等。
當(dāng)然有人會(huì)問(wèn)20-50K的問(wèn)題怎么這么簡(jiǎn)單,因?yàn)槊總€(gè)問(wèn)題都是串起來(lái)的,需要你的知識(shí)面足夠廣,才能一路面下去,直到拿到offer。而且每個(gè)問(wèn)題都是有坑,例如pm2 start 如果不指定參數(shù)到底會(huì)啟動(dòng)多少個(gè)進(jìn)程?? 在云端和自己的電腦上是不一樣的,這些都需要你去有實(shí)際操作經(jīng)驗(yàn)才能應(yīng)對(duì)。??
本文的初衷,不為了面試而背面試題,只是讓大家知道應(yīng)該學(xué)習(xí)什么方向。純粹為了面試去背誦面試題,是很容易被識(shí)破,只有不斷積累學(xué)習(xí),你才會(huì)輕易拿下offer。
座右銘:你想要在外人看來(lái)毫不費(fèi)力,那你就要平時(shí)不斷努力~
最后
如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:
點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)
歡迎加我微信「qianyu443033099」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...
關(guān)注公眾號(hào)「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。

