记一次前端面试记录(头条、蚂蚁)

6,475 阅读9分钟

前言

最近面试了一些公司,比如蚂蚁、头条等,已经拿到了头条和蚂蚁的offer,个人打算应该是要去蚂蚁了。

下面阿里子弈大佬分享的阿里前端面经很棒,博主也是看了大佬的文章才get offer的:

在阿里我是如何当面试官的(持续更新)

面试分享:两年工作经验成功面试阿里P6总结

子弈大佬分享过的问题这里就不赘述了,现将个人面试中遇到的8个比较偏的问题和思路跟大家分享下,希望对大家后续的面试有帮助。

Q1.CDN是什么?CDN的回源机制?CDN与OSS有什么区别?

解题思路:

(1)CDN的定义

CDN的全称是Content Delivery Network,即内容分发网络。 CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。它的作用是减少传播时延,寻找到最近的节点,典型的“以空间换时间”的技术。比如我们访问的淘宝网站的资源大部分都部署到CDN节点上。

举个例子:
假设某个网站服务器都部署在一个地方(北京),那么全国各地的访问都集中到北京访问。从上海到北京和从香港到北京的延时不一样,而且源站容易负载过大挂掉。
解决的方案是在全国各地多个区域中心部署CDN节点,从香港访问就会从香港的CDN节点返回内容,从南京访问就从南京的CDN节点返回内容,这样可以有效减少传播时延,并且大大降低源站的负载。

CDN的访问过程:

CDN访问

1.当用户点击网站页面上的内容URL,经过本地DNS系统解析,DNS系统会最终将域名的解析权交给CNAME指向的CDN专用DNS服务器。

2.CDN的DNS服务器将CDN的全局负载均衡设备IP地址返回用户。

3.用户向CDN的全局负载均衡设备发起内容URL访问请求。

4.CDN全局负载均衡设备根据用户IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求。

5.区域负载均衡设备会为用户选择一台合适的缓存服务器提供服务,选择的依据包括:根据用户IP地址,判断哪一台服务器距用户最近;根据用户所请求的URL中携带的内容名称,判断哪一台服务器上有用户所需内容;查询各个服务器当前的负载情况,判断哪一台服务器尚有服务能力。基于以上这些条件的综合分析之后,区域负载均衡设备会向全局负载均衡设备返回一台缓存服务器的IP地址。

6.全局负载均衡设备把服务器的IP地址返回给用户。

7.用户向缓存服务器发起请求,缓存服务器响应用户请求,将用户所需内容传送到用户终端。如果这台缓存服务器上并没有用户想要的内容,而区域均衡设备依然将它分配给了用户,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉到本地。

(2)CDN的回源机制

当 CDN 缓存服务器中没有符合客户端要求的资源的时候,缓存服务器会请求上一级缓存服务器,以此类推,直到获取到。最后如果还是没有,就会回到我们自己的服务器去获取资源。

回源情况:

1.用户访问时,如节点上无缓存,则会回源拉取资源;

2.CDN节点上的文件过期,会回源拉取资源;

3.若为不缓存文件,用户访问时,会直接回源拉取资源。

(3)OSS与CDN区别

OSS:(Object Storage Service,对象存储服务)对象存储将数据通道(需要访问的数据)和控制通路(元数据,即索引)分离,先根据索引(也就是元数据)找到数据存储的位置,进而通过底层的存储接口来访问数据。通过这种方式,对象存储既有类似块存储的存取性能,也有类似文件存储的共享便利,可谓是鱼与熊掌兼得的存储方式。对象存储主要用来存储图片、音频、视频等非结构化数据。

区别:OSS的核心是存储,以及计算能力,CDN的核心是分发,本身不提供存储的接口,所以一般是两者配合使用。对象存储中存储的资源文件,正好适合CDN做加速。对象存储+CDN,已经成为互联网应用的一个必不可少的组成部分。

oss+cdn

参考文章:

CDN是什么?使用CDN有什么优势?

关于CDN、回源等问题一网打尽

OSS对象存储和CDN傻傻分不清?

Q2.webpack如何打包优化?

解题思路:

先使用webpack-bundle-analyzer分析打包后整个项目中的体积结构,既可以看到项目中用到的所有第三方包,又能看到各个模块在整个项目中的占比。

webpack-bundle-analyzer

1.按需加载

(1)路由按需加载

Vue中路由懒加载,使用() => import(xxx.vue)形式,打包会根据路由自动拆分打包。

import VueRouter from 'vue-router'

Vue.use(VueRouter)

export default new VueRouter {
   routes: [
      {
        path: 'a',
        component: () => import('../views/A.vue')
      },
      {
        path: 'b',
        component: () => import('../views/B.vue')
      }
   ]
}

React中使用React.lazy函数可以像渲染常规组件一样处理动态引入的组件,应在Suspense组件中渲染lazy组件,配合路由更高效。

import { Switch, Route, Redirect } from 'react-router-dom';

const Home = lazy(() => import('../views/Home));
const About = lazy(() => import('../views/About'));
const WrappedComponent = (component) => {
    return (
        <Suspense fallback={<div>Loading...</div>}>
            {component}
        </Suspense>
    );
};

const Main = () => (
  <Switch>
    <Route path="/home" component={WrappedComponent(Home)} />
    <Route exact path="/about" component={WrappedComponent(About)} />
  </Switch>
);

export default Main;

(2)第三方库按需加载

比如使用lodash工具库或者element-ui组件库时,尽量按需加载,避免把整个库打包到项目中去。

// 按需引入lodash需要函数
import get from 'lodash/get';

// 按需引入组件
import { Button } from 'element-ui';
Vue.component(Button.name, Button);

2.文件解析优化

loader解析优化:通过配置includeexclude来减少被处理的文件,还可以配合cacheDirectory来缓存编译后的结果。

module: {
  rules: [
    {
      test: /\.js$/,
      loader: 'babel-loader?cacheDirectory',
      include: [
        path.resolve(__dirname, 'src')
      ],
      exclude: /node_modules/
    }
  ]
}

文件解析优化:通过配置resolve选项中的aliasextensionsmodules来实现。

alias:创建importrequire的别名,加快webpack查找速度。

extensions:自动解析确定的扩展,默认值为:

extensions: [".js", ".json"]

使用此选项会覆盖默认数组。webpack按照模块设定的顺序进行解析,将大部分模块的扩展放在数组前面,可以提升查找速度。

modules:解析模块时应该搜索的目录,通常建议使用绝对路径,避免层层查找祖先目录。

resolve: {
  alias: {
    '@': path.resolve(__dirname, "src")
  },
  extensions: [".js", ".vue"],
  mainFields: ["index", "main"],
  modules: [path.resolve(__dirname, "src"),"node_modules"]
}

3.拆分公共模块

使用splitChunks进行拆包,抽离公共模块。
splitChunks默认配置如下:

splitChunks: {
    // 表示选择哪些 chunks 进行分割,可选值有:async,initial和all
    chunks: "async",
    // 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。
    minSize: 30000,
    // 表示一个模块至少应被minChunks个chunk所包含才能分割。默认为1。
    minChunks: 1,
    // 表示按需加载文件时,并行请求的最大数目。默认为5。
    maxAsyncRequests: 5,
    // 表示加载入口文件时,并行请求的最大数目。默认为3。
    maxInitialRequests: 3,
    // 表示拆分出的chunk的名称连接符。默认为~。如chunk~vendors.js
    automaticNameDelimiter: '~',
    // 设置chunk的文件名。默认为true。当为true时,splitChunks基于chunk和cacheGroups的key自动命名。
    name: true,
    // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块,就分配到该组。模块可以被多个组引用,但最终会根据priority来决定打包到哪个组中。默认将所有来自 node_modules目录的模块打包至vendors组,将两个以上的chunk所共享的模块打包至default组。
    cacheGroups: {
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
        // 
    default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
        }
    }
}

总结下来就是:

  • 1.被复用代码或者来自node_moules文件夹中的模块
  • 2.模块的体积大小必须大于等于30kb才进行拆分
  • 3.当按需加载chunks时,并行请求的最大数量不能超过5
  • 4.初始页面加载时,并行请求的最大数量不能超过3

下面就是把node_modules中的react和moment再进行拆分,避免打包出的vendor包过大。

splitChunks: {  
    chunks: 'all',  
    minSize: 30000,
     minChunks: 1,
    cacheGroups: {    
        lib: {      
            name: 'vendors',      
            test: /[\\/]node_modules[\\/]/,      
            priority: 10,      
            chunks: 'initial' // 只打包初始时依赖的第三方    
        },    
       react: {      
            name: 'react', // 单独将 react 拆包      
            priority: 20,
            test: /[\\/]node_modules[\\/]react[\\/]/,      
            chunks: 'all'    
       },
       moment: {
            name: 'moment', //单独将moment拆包
            priority: 20,
            test: /[\\/]node_modules[\\/]moment[\\/]/
       },
       default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
        }
    }
}

4.DllPlugin和DllReferencePlugin

通常打包过程中,由于第三方库代码不经常改变,我们可以将第三方库的代码跟业务代码抽离。DllPlugin 和 DLLReferencePlugin 可以实现拆分 bundles,并且可以大大提升构建速度,DllPlugin 和 DLLReferencePlugin 都是 webpack 的内置模块。

配置webpack.dll.js,将lodash、jquery、antd抽离出来。

const path = require("path");
const webpack = require("webpack");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");

module.exports = {
  mode: "production",
  entry: {
    lodash: ["lodash"],
    jquery: ["jquery"],
    antd: ["antd"]
  },
  output: {
    filename: "[name].dll.js",
    path: path.resolve(__dirname, "dll"),
    library: "[name]" // name和library保持一致
  },
  plugins: [
    new CleanWebpackPlugin(),
    new webpack.DllPlugin({
      name: "[name]",
      path: path.resolve(__dirname, "manifest/[name].manifest.json")
    })
  ]
};

配置package.json中,新增script打包dll

"scripts": {
  ...,
  "dll": "webpack --config webpack.dll.js"
}

执行npm run dll,生成dll文件和对应的manifest.json。

npm run dll

将打包的dll通过add-asset-html-webpack-plugin添加到html中,再通过DllReferencePlugin把dll引用到需要编译的依赖。

配置webpack.config.js

const manifests = ['antd', 'jquery', 'lodash'];
const dllPlugins = manifests.map(item => {
  return new webpack.DllReferencePlugin({
    manifest: require(`./manifest/${item}.manifest`)
  });
});

module.exports = {
  ...,
  plugins: [
    ...dllPlugins,
    new AddAssetHtmlPlugin({
      filepath: path.resolve(__dirname, "./dll/*.dll.js")
    })
  ]
}

参考文章:

总结几个webpack打包优化的方法

将webpack打包优化到极致_20180619

Q3.如何实现一套类似vue-cli/create-react-app脚手架?

解题思路:

相关技术:node、webpack、commander、download-git-repo

脚手架需要包含3个核心功能(该脚手架属于青铜版):

  • 初始化项目;
  • 本地开发;
  • 本地打包。

1.初始化项目:xx create <project-name>

在github上建好项目模板仓库

react模板

通过commander注册create命令:

const program = require('commander');
const download = require('download-git-repo');

// 注册create命令
program.command('create <app-name>')
  .description('create a new project by react-cli')
  .action((name) => {
    require('../packages/create')(name);
  });

create.js实现下载模板、修改package.json内容和安装依赖包三个功能。

// 修改package.json中的name
function editPackageName(appName) {
  return new Promise((resolve, reject) => {
    const packageJsonPath = path.resolve(process.cwd(), `${appName}/package.json`);
    const packageJson = require(packageJsonPath);
    packageJson.name = appName;
    fs.writeFile(packageJsonPath, JSON.stringify(packageJson), (err) => {
      if (err) {
        return reject(err);
      }
      resolve();
    });
  });
}

// 下载依赖包
function installPackages(appName) {
  const appPath = path.resolve(process.cwd(), appName);
  return new Promise((resolve, reject) => {
    const spinner = ora('安装依赖包');
    spinner.start();
    child_process.exec('npm install', {cwd: appPath}, (err) => {
      spinner.stop();
      if (err) {
        return reject(err);
      }
      successLog('依赖包安装成功');
      console.log(`cd ${appName}`);
      console.log(`npm run start`);
      resolve();
    });
  });
}

// 下载项目模板
function downloadTemplate(appName) {
  return new Promise((resolve, reject) => {
    const spinner = ora('开始生成项目');
    spinner.start();
    download(templateUrl, `./${appName}`, {clone: false}, err => {
      spinner.stop();
      if (err) {
        return reject(err);
      }
      successLog('项目生成成功');
      resolve();
    });
  });
}

async function create(appName) {
  try {
    await downloadTemplate(appName);
    await editPackageName(appName);
    await installPackages(appName);
  } catch (err) {
    errorLog(err);
    process.exit(1);
  }
}

module.exports = create;

2.项目本地开发

项目支持本地开发主要通过webpack-dev-server来实现,先设定好webpack打包配置。

公共配置在webpack.com.config.js中(仅列举部分):

module.exports = {
  entry: {
    app: appIndexJs
  },
  output: {
    filename: "[name].[hash:7].js",
    path: appBuild
  },
  module: {
    rules: [
      {
        test: /\.js[x]?$/, // jsx、js处理
        exclude: /node_modules/,
        use: ["babel-loader"]
      },
      ...
  },
  plugins: [
    ....
  ]   

webpack.dev.config.js配置:

const path = require("path");
const merge = require("webpack-merge");
const baseConfig = require("./webpack.com.config");
const {appBuild} = require("./pathConfig");

module.exports = merge(baseConfig, {
  mode: "development",
  devtool: "cheap-module-eval-source-map",
  devServer: {
    contentBase: appBuild,
    publicPath: '',
    host: "localhost",
    port: 3000,
    open: true, // 自动打开浏览器
    compress: true, // 启用gzip压缩
    hot: true,
    inline: true // 启用内联模式
  }
});

通过commander注册dev命令:

program.command('dev')
  .description('Start app development.')
  .action(() => {
    require('../packages/dev')();
  });

dev.js主要通过调用webpack-dev-server启动本地开发服务器:

// 开发模式下打包
function development() {
  const compiler = webpack(webpackConfig);
  const server = new WebpackDevServer(compiler, {
    contentBase: webpackConfig.devServer.contentBase,
    publicPath: webpackConfig.devServer.publicPath
  });
  server.listen(webpackConfig.devServer.port, (err) => {
    if (err) {
      errorLog(err);
      process.exit(1);
    }
    console.log(`\nApp is running: ${underlineLog('http://localhost:3000/')}`);
  });
};

module.exports = development;

3.本地打包

本地打包主要通过配置webpack,配置如下:

const merge = require("webpack-merge");
const baseConfig = require("./webpack.com.config");

module.exports = merge(baseConfig, {
  mode: "production",
  devtool: "source-map"
});

通过commander注册build命令:

// 注册build命令
program.command('build')
  .description('Build app bundle.')
  .action(() => {
    require('../packages/prod')();
  });

prod.js执行webpack生产打包流程:

#!/usr/bin/env node

const webpack = require('webpack');
const {errorLog, successLog} = require('../utils/index');
const webpackConfig = require('../config/webpack.prod.config');

// 生产模式下打包
function production() {
  webpack(webpackConfig, (err, stats) => {
    if (err) {
      errorLog(err);
      process.exit(1);
    }
    const compiler = webpack(webpackConfig);
    compiler.run((err, stats) => {
      if (err) {
        errorLog(err);
        process.exit(1);
      }
      process.stdout.write(stats.toString({
        colors: true,
        modules: false,
        children: false,
        chunks: false,
        chunkModules: false
      }));

      if (stats.hasErrors()) {
        errorLog('  Build failed with errors.\n');
        process.exit(1);
      }
      successLog('Build completed.');
    });
  });
};

module.exports = production;

react-fe-cli完整代码github.com/dadaiwei/re…

Q4.CSR与SSR区别,如何将CSR与SSR结合起来?

解题思路:

1.SSR

SSR的全称是Server Side Rendering(服务端渲染),也就是渲染的工作放在服务端进行。 浏览器得到完整的结构后就可直接进行 DOM 的解析、构建、加载资源及后续的渲染。

优点:

  • 1.首屏加载快
  • 2.对搜索引擎友好,利于SEO

缺点:

  • 1.访问量较大时,会对服务器造成很大压力
  • 2.页面之间频繁刷新跳转体验不是很好

2.CSR

CSR的全称是Client Side Rendering(客户端渲染),服务器返回初始HTML内容,然后再通过js异步加载数据,完成页面的渲染,比如基于vue或者react开发的SPA应用都是典型的CSR案例。

优点:

  • 1.页面路由放在客户端,页面间切换很快
  • 2.数据渲染放在客户端,大大降低服务器的压力

缺点:

  • 1.首屏渲染较慢,可能会出现白屏现象
  • 2.不利于SEO

3.两者结合的方案

首页基于SSR,后续点击等事件交互基于CSR渲染,可以避免首页加载较慢,又能解决SEO问题。

对于客户端和服务端代码采用同构。

服务端采用react-dom/serverrenderToString方法,将Index组件直出:

const { renderToString}  = require( 'react-dom/server');

const http = require('http');

// Index 组件
class Index extends React.Component{
    constructor(props){
        super(props);
    }

    render(){
        return <h1>{this.props.data.title}</h1>
    }
}

// server服务
http.createServer((req, res) => {
    if (req.url === '/') {
        res.writeHead(200, {
            'Content-Type': 'text/html'
        });
        const html = renderToString(<Index />);
        res.end(html);
    }
}).listen(8080);

客户端使用react-domReactDOM.hydrate方法替代ReactDOM.render方法,它用于在ReactDOMServer渲染的容器中对HTML的内容进行hydrate操作。React会尝试在已有标记上绑定事件监听器。

import ReactDOM from 'react-dom';

// 绑定Index组件的事件监听到页面
ReactDom.hydrate(<Index />, document.getElementById('root'));

参考文章:

【长文慎入】一文吃透 React SSR 服务端渲染和同构原理

React SSR 详解【近 1W 字】+ 2个项目实战

Q5.redux源码设计

解题思路:

概念:
store:状态管理员,获取状态、分发状态。
state:全局状态树,各个组件共享。
action:处理异步、点击、事件等多种类型操作对象,需要包含type。
reducer:不同操作类型对数据处理不同,返回不同state,由reducer来处理actipn。

方法:
dispath:根据传入的action对象type的不同,修改对应的state。
subsciribe:新添加订阅者,返回取消订阅函数。
replaceReducer:替换当前reducer。

store部分,通过createStore返回stroe对象:

export default function createStore(reducer, initialState) {
  let currentState = initialState; //状态树state
  let currentReducer = reducer; // 当前reducer
  let currentListeners = []; // 监听对象数组
  let nextListeners = currentListeners; // 下一个liseners

  // 获取当前state
  function getState() { 
    return currentState;
  }

  // 发布订阅
  function subscribe(listener) { 
    nextListeners.push(listener);
    return unsubscribe() { // 取消订阅
       const index = nextListeners.indexOf(listener); // 取消当前订阅listener
       nextListeners.splice(index, 1);
    }
  }

  // 替换当前reducer
  function replaceReducer(nextReducer) { 
    currentReducer = nextReducer;
    dispatch({ type: 'REPLACE' })
  }

  // 触发action
  function dispatch(action) { 
    currentState = currentReducer(currentState, action);
    for (let i = 0; i < listeners.length; i++) {
        const listener = listeners[i];
        listener();
    }
    return action;
  }

  // 初始化state
  dispatch({ type: 'INIT' });

  return {
    getState,
    subscribe,
    replaceReducer,
    dispath
  }
}

combineReducers:将reducer进行拆分,每个reducer只负责处理一部分state,每个reducerstate参数都不同,分别对应它管理的那部分state数据,最终返回全新的state

function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers);
  // 最终reducer对象
  const finalReducers = {};
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i];
    finalReducers[key] = reducers[key];
  }
  const finalReducerKeys = Object.keys(finalReducers);
  // 所有reducer遍历处理action,返回最终state
  return combination(state = {}, action) {
    // 下一个状态
    const nextState = {};
    // 状态变更标志
    let hasChaged = false;
    for (let j = 0; j < finalReducerKeys.length; j++) {
       const key = finalReducerKeys[j];
       const reducer = finalReducers[key];
       const previousStateForKey = state[key];
       const nextStateForKey = reducer(previousStateForKey, action);
       nextState[key] = nextStateForKey; 
       hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    }
    // 判断返回新状态还是之前的state
    return hasChanged ? nextState : state;
  }
}

applyMiddlewares:增强store,支持redux-thunk或者redux-saga等中间件。

export default function applyMiddleware(...middlewares) {
  return createStore => (reducer, initialState) => {
    var store = createStore(reducer, initialState);
    var dispatch = store.dispatch; //拿到真正的 dispatch
    //将最重要的两个方法 getState/dispatch 整合出来
    var middlewareAPI = {
      getState: store.getState,
      dispatch: action => dispatch(action)
    };
    //依次传递给 middleware,让它们有控制权
    var chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain, dispatch); // 再组合出新的 dispatch

    //返回新的 store 对象,其 dispatch 方法已经被传递了很多层
    //每一层都可以调用 dispatch,也可以调用 next 让下一层考虑调用 dipatch
    //最后一个 next 就是 store.dispatch 本身。
    return {
      ...store,
      dispatch
    };
  };
}

参考文章:

10行代码看尽redux实现

深入到源码:解读 redux 的设计思路与用法

Q6.redux与vuex的区别?

vuex的流向:

view——>commit——>mutations——>state变化——>view变化(同步操作)
view——>dispatch——>actions——>mutations——>state变化——>view变化(异步操作)

vuex流向

redux的流向:
view——>dispatch——>actions——>reducer——>state变化——>view变化(同步异步一样)

redux流向

不同点:

  • 1.vuex以mutations函数取代redux中的reducer,只需在对应的mutation函数里改变state即可。
  • 2.vuex支中的state直接关联到组件实例上,当state变化时自动重新渲染,无需订阅重新渲染函数。redux使用store对象存储整个应用的状态,状态变化时,从最顶层向下传递,每一级都会进行状态比较,从而达到更新。
  • 3.vuex支持action异步处理,redux中只支持同步处理,对于异步处理需要借助于redux-thunk和redux-saga实现。

Q7.express/koa中间件原理

解题思路:

node本身并无“中间件”的概念,在express/koa中的中间件往往是一个函数,接收request(请求对象)、response(响应对象)、next(express内部传入的一个函数,用于把控制权从上一个中间件传到下一个中间件)作为参数,接收请求,处理响应,然后将控制权交给下一个中间件。

node中间件

function middlewareA(req, res, next) {
  next();
}

比如一个自定义打印日志中间件:

const app = express();

const myLogger = function (req, res, next) {
  console.log('LOGGED');
  next(); // 将控制权传递给下一个中间件
};

app.use(myLogger); // 注册中间件

Q8.nginx正向代理、反向代理、负载均衡相关

解题思路:

(1)正向代理

正向代理是为客户端服务的,通过代理客户端的请求,去访问客户端本身无法直接访问的服务器,翻墙工具就是最常见的正向代理。正向代理对于客户端透明,对于服务器端不透明。

正向代理

nginx配置示例:

server {
    resolver 8.8.8.8;       #指定DNS服务器IP地址 
    listen 80;
    location / {
        proxy_pass http://$host;     #设定代理服务器的协议和地址 
        proxy_set_header HOST $host;
        proxy_buffers 256 4k;
        proxy_max_temp_file_size 0k;
        proxy_connect_timeout 30;
        proxy_send_timeout 60;
        proxy_read_timeout 60;
        proxy_next_upstream error timeout invalid_header http_502;
    }
}

(2)反向代理

反向代理是指为服务端服务的,反向代理接收来自客户端的请求,帮助服务器进行请求转发、负载均衡等。通常解决前端跨域问题也是通过反向代理实现的。反向代理对于服务端是透明的,对于客户端是不透明的。

反向代理

nginx配置示例:

server {
    listen       80;
    server_name  xx.a.com; // 监听地址
    location / {
        root html;
        proxy_pass xx.b.com; // 请求转向地址
        index index.html; // 设置默认页
    }
}

(3)负载均衡

负载均衡是指nginx将大量客户端的请求合理地分配到各个服务器上,以达到资服务端资源的充分利用和更快的响应请求。

nginx配置示例:

upstream balanceServer {
    server 1.1.2.3:80;
    server 1.1.2.4:80;
    server 1.1.2.5:80;
}

server {
        server_name  xx.a.com;
        listen 80;
        location /api {
            proxy_pass http://balanceServer;
        }
    }

常用的负载均衡策略:

  • 1.轮询策略
    默认策略,遍历服务器节点列表,逐一分配请求,如果服务器down掉,自动剔除。
  • 2.加权轮询 每台服务器有不同的权重,一般权重越大意味着该服务器的性能越好,可以承载更多的请求。
upstream balanceServer {
    server 1.1.2.3:80 weight=5;
    server 1.1.2.4:80 weight=10;
    server 1.1.2.5:80 weight=20;
}
  • 3.最小连接策略
    将请求优先分配给压力较小的服务器,避免压力大的服务器添加更多的请求。
upstream balanceServer {
    least_conn;
    server 1.1.2.3:80;
    server 1.1.2.4:80;
    server 1.1.2.5:80;
}
  • 4.最快响应时间策略
    将请求有限分配给响应时间最短的服务器。
upstream balanceServer {
    fair;
    server 1.1.2.3:80;
    server 1.1.2.4:80;
    server 1.1.2.5:80;
}
  • 5.客户端ip绑定
    将来自同一个ip的请求永远只分配给一台固定的服务器。
upstream balanceServer {
    ip_hash;
    server 1.1.2.3:80;
    server 1.1.2.4:80;
    server 1.1.2.5:80;
}

参考文章:

从原理到实战,彻底搞懂Nginx(高级篇)

前端开发者必备的 Nginx 知识

结语

以上就是博主总结的本次面试的一些题目及心得,觉得有收获的可以关注一波,点赞一波,码字不易,万分感谢。