前言
最近面试了一些公司,比如蚂蚁、头条等,已经拿到了头条和蚂蚁的offer,个人打算应该是要去蚂蚁了。
下面阿里子弈大佬分享的阿里前端面经很棒,博主也是看了大佬的文章才get offer的:
子弈大佬分享过的问题这里就不赘述了,现将个人面试中遇到的8个比较偏的问题和思路跟大家分享下,希望对大家后续的面试有帮助。
Q1.CDN是什么?CDN的回源机制?CDN与OSS有什么区别?
解题思路:
(1)CDN的定义
CDN的全称是Content Delivery Network,即内容分发网络。 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,已经成为互联网应用的一个必不可少的组成部分。
参考文章:
Q2.webpack如何打包优化?
解题思路:
先使用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解析优化:通过配置include和exclude来减少被处理的文件,还可以配合cacheDirectory来缓存编译后的结果。
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory',
include: [
path.resolve(__dirname, 'src')
],
exclude: /node_modules/
}
]
}
文件解析优化:通过配置resolve选项中的alias、extensions、modules来实现。
alias:创建import或require的别名,加快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。
将打包的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")
})
]
}
参考文章:
Q3.如何实现一套类似vue-cli/create-react-app脚手架?
解题思路:
相关技术:node、webpack、commander、download-git-repo
脚手架需要包含3个核心功能(该脚手架属于青铜版):
- 初始化项目;
- 本地开发;
- 本地打包。
1.初始化项目:xx create <project-name>
在github上建好项目模板仓库
通过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/server的renderToString方法,将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-dom的ReactDOM.hydrate方法替代ReactDOM.render方法,它用于在ReactDOMServer渲染的容器中对HTML的内容进行hydrate操作。React会尝试在已有标记上绑定事件监听器。
import ReactDOM from 'react-dom';
// 绑定Index组件的事件监听到页面
ReactDom.hydrate(<Index />, document.getElementById('root'));
参考文章:
【长文慎入】一文吃透 React SSR 服务端渲染和同构原理
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,每个reducer的state参数都不同,分别对应它管理的那部分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
};
};
}
参考文章:
Q6.redux与vuex的区别?
vuex的流向:
view——>commit——>mutations——>state变化——>view变化(同步操作)
view——>dispatch——>actions——>mutations——>state变化——>view变化(异步操作)
redux的流向:
view——>dispatch——>actions——>reducer——>state变化——>view变化(同步异步一样)
不同点:
- 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内部传入的一个函数,用于把控制权从上一个中间件传到下一个中间件)作为参数,接收请求,处理响应,然后将控制权交给下一个中间件。
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;
}
参考文章:
结语
以上就是博主总结的本次面试的一些题目及心得,觉得有收获的可以关注一波,点赞一波,码字不易,万分感谢。