憨憨松的卑微全栈复习(未完待续...)

593 阅读23分钟

// 要看的东西好多啊啊啊啊啊啊

一、Node.js部分

A. 模块机制

1. exports、module.exports、export、export default区别

  1. 涉及到两种规范:CommonJS和ES6
  2. exportsmodule.exports是CommonJS规范下的,对应requireexportexport default是ES6规范下,对应import
  3. exports/module.exportsmodule.exports是node模块机制对应的唯一出口对象,exports只是指向这个对象的一个引用,因此使用exports时注意不能断了它和module.exports的联系
  4. export/export default:二者都相当于把对象添加到module.exports中,区别在于在module.exports中的key不同,export是开发者自定义的key,export default的key是写死的,就叫default

2. 常用到哪些库(原生模块、第三方库)

  1. Lodash:用来做数据处理
  2. Koa:路由框架
  3. request/request-promise:网络请求
  4. Log4js:日志打印
  5. Sequelize:数据库链接
  6. moment:时间处理
  7. 内置:fs-文件处理、http:网络请求

3. 模块加载的机制

  1. 先计算模块路径
  2. 如果模块在缓存里面,取出缓存
  3. 加载模块
  4. 输出模块的exports属性

4. 请介绍一下node里的模块是什么

Node中,每个文件模块都是一个对象,按照如下方式定义

function Module(id, parent) {	
  this.id = id;	
  this.exports = {};	
  this.parent = parent;	
  this.filename = null;	
  this.loaded = false;	
  this.children = [];	
}	
module.exports = Module;	
var module = new Module(filename, parent);

每一个模块都是一个Module实例

5. 说说 __dirname, __filename属性

  1. Node的module中并没有定义这两个属性
  2. __dirname__filenamenew module时传入的

B. 异步I/O

1. 为什么要异步I/O?

  1. 主要有两个原因:用户体验、资源分配
  2. 对于用户体验,异步I/O可以消除UI阻塞,快速响应资源
  3. 对于资源分配,单线程异步I/O可以避免同步执行的I/O阻塞,也可以避免多线程上下文切换带来的状态同步/死锁问题

2. 聊聊Node的异步I/O?

  1. 事件循环、观察者、请求对象、I/O线程池构成了异步I/O
  2. 事件循环就是Node进程启动时设置了一个类似while(true)的循环,每次循环叫做一个Tick,一次Tick的作用就是看有没有要执行的事情,有的话就拿出来执行相关回调,然后继续循环
  3. 事件循环的过程中如何知道有没有任务呢?向观察者问。观察者负责管理各种待执行的事件
  4. 请求对象是从javascript发起调用到内核I/O执行完毕中的中间产物,所有的状态都存到这个对象里,包括送入线程池和I/O结束之后的回调处理

3. 简单说一下I/O过程?

  1. javascript发起,调用node核心模块
  2. node核心模块调用c++内建模块
  3. 内建模块通过libuv进行系统调用

4. setTimeout、setInterval、setImmediate、process.nextTick

  1. 他们都是和I/O无关的异步API
  2. setTimeoutsetInterval是定时器,创建时会交给定时器观察者,每次Tick执行时会去查看是否超过定时事件,如果超过了就立刻执行
  3. 定时器是非精确的
  4. process.nextTicksetImmediate都是下一个Tick执行,process.nextTick交给idle观察者,setImmediate交给check观察者。
  5. 每一轮Tick对观察者的检测是有顺序的,idle观察者->I/O观察者->check观察者
  6. 所以process.nextTick优先级比setImmediate高,同时可以强势插入

5. 手写EventEmitter

class EventEmit {
  construct() {
    this.eventMap = {}
  }
  on(name, cb) {
    if (!this.eventMap[name]) {
      this.eventMap[name] = [cb]
    } else {
      this.eventMap[name].push(cb)
    }
  }
  off(name, cb) {
    if (this.eventMap[name]) {
      const index = this.eventMap[name].indexOf(cb)
      this.eventMap[name].splice(index, 1)
    }
  }
  trigger(name, ...args) {
    if (this.eventMap[name] && this.eventMap[name].length) {
      this.eventMap[name].forEach(cb => {
        cb(args)
      })
    }
  }
  once(name, fn) {
    if (!this.eventMap[name]) {
      this.on(name, (...args) => {
        fn(args)
        this.off(name, fn)
      })
    }
  }
}

6. Node.js性能优化

  1. 避免使用同步代码,充分利用Node单线程异步I/O的优势
  2. 关闭Socket池,Node会为http默认开启Socket池,限制了最大为5,如果同一台机器并发请求,将会造成性能瓶颈
  3. 不要让静态资源使用Node.js
  4. 客户端渲染

7. Node.js事件循环,和浏览器的区别

  1. 在Node11版本之后,与浏览器一致
  2. 浏览器分宏任务和微任务
  3. 宏任务:script 中代码、setTimeout、setInterval、I/O、UI render
  4. 微任务:Promise、Object.observe、MutationObserver
  5. 浏览器EventLoop
    1. 执行同步代码 -> 执行清空微任务队列
    2. 从宏任务队列取出一个宏任务执行 -> 执行清空微任务队列
    3. 重复2
  6. Node11之前的EventLoop
    1. 分6个阶段:timers、I/O callback、idle/prepare、poll、check、close callback
    2. 有四个队列:Timers、I/O、Check、Close依次执行
    3. 循环:
      1. 清空Timers Queue、NextTick Queue、Microtask Queue
      2. 清空I/O Queue、NextTick Queue、Microtask Queue
      3. 清空Check Queue、NextTick Queue、Microtask Queue
      4. 清空Close Queue、NextTick Queue、Microtask Queue

C. 异步编程

1. 异步编程的好处

  1. 基于事件驱动的非阻塞异步I/O模型是Node的基本模型
  2. 异步编程可以充分发挥CPU和I/O资源

2. 如何避免回调地狱

  1. 拆解函数
  2. 使用事件发布/监听模式
  3. 使用Promise
  4. 使用generator
  5. 使用async/await

3. 流程控制库async,自己在项目中常用到哪些方法

  1. 没有用过,但是知道
  2. waterfall,瀑布流,上一个函数的返回值是下一个函数的参数
  3. series,互相没有数据交互,从上到下依次执行
  4. parallel,并行执行,不会等待
  5. parallelLimit,限制并发数的parallel

4. async/await、Promise、Generator区别

  1. async/await是generator的语法糖
  2. async/await语义更清晰,自带执行器,async函数返回Promise对象
  3. async用来解决Promise的then调用链长的问题

5. Promise的几种状态、如何实现的、什么情况下catch不到错误

  1. pending、fulfilled、rejected
  2. 初始化,状态为pending,调用resolve时,pending=>fulfilled,调用reject时,pending=>rejected
  3. fulfilled和rejected之间不可转化
  4. proimse中如果有异步错误,无法被同步catch
  5. 全局捕获promise异常:
process.on('unhandledRejection', (reason, promise) => {
  console.log('Unhandled Rejection at:', promise, 'reason:', reason);
  // Application specific logging, throwing an error, or other logic here
});

6. 手写使用promise控制最大并发数

笔者很垃圾,还没自己写

// From: https://juejin.cn/post/6844904116095811592#heading-47
/*
  限制最大并发数
  在 // ... 处填写代码,可以输出结果为 2,1,4,3
*/
const ConcurrentCount = 2 // 最大并发数
class Task {
  constructor () {
    this.count = 0
    this.taskList = []
  }
  addTask(promiseCreate) {
    const taskMeta = (promise) => {
      this.count ++;
      if(this.count <= ConcurrentCount) {
        promise().then((rs) => {
          console.log(rs)
        }).finally(() => {
          this.count--
          if (this.taskList.length > 0) {
            this.count--;
            taskMeta(this.taskList.shift())
          }
        })
      } else {
        this.taskList.push(promise)
      }
    }
    taskMeta(promiseCreate)
  }
}

let timeOut = ((log, n) => {
  return () => {
    return new Promise((rs) => {
      setTimeout(() => {
        rs(log)
      }, n)
    })
  }
})

const task = new Task()
task.addTask(timeOut('1', 1000))
task.addTask(timeOut('2', 300))
task.addTask(timeOut('3', 1200))
task.addTask(timeOut('4', 200))
// From: dpp
class Scheduler {
  constructor(limit) {
    this.queue = [];
    this.count = 0;
    this.LIMIT = limit || 2;
  }

  add(promiseCreator) {
    this.count++;
    if (this.count <= this.LIMIT) {
      return promiseCreator().then(() => {
        this.count--;
        this.queue.length && this.queue.shift()();
      });
    }
    return new Promise((resolve) => {
      this.queue.push(resolve);
    }).then(() => {
      return promiseCreator().then(() => {
        this.count--;
        this.queue.length && this.queue.shift()();
      });
    });
  }
}

const timeout = (time) =>
  new Promise((resolve) => {
    setTimeout(resolve, time);
  });

const scheduler = new Scheduler(2);
const addTask = (time, order) => {
  scheduler.add(() => timeout(time)).then(() => console.log(order));
};

const run = () => {
  addTask(1000, "1");
  addTask(500, "2");
  addTask(300, "3");
  addTask(400, "4");
};

run();

7. 手写Promise.all

Promise.all = function(arr){
    return new Promise((resolve,reject) => {
        if(!Array.isArray(arr)){
            throw new TypeError(`argument must be a array`)
        }
        var length = arr.length;
        var resolveNum = 0;
        var resolveResult = [];
        for(let i = 0; i < length; i++){
            arr[i].then(data => {
                resolveNum++;
                resolveResult.push(data)
                if(resolveNum == length){
                    return resolve(resolveResult)
                }
            }).catch(data => {
                return reject(data)
            })
        }
    })
}

8. 手写Promise.retry

Promise.retry = function(fn, times, delay) {
    return new Promise(function(resolve, reject){
        var error;
        var attempt = function() {
            if (times == 0) {
                reject(error);
            } else {
                fn().then(resolve)
                    .catch(function(e){
                        times--;
                        error = e;
                        setTimeout(function(){attempt()}, delay);
                    });
            }
        };
        attempt();
    });
};

9. 手写一个promiseify包装器

function promisify(fn,context){
  return (...args) => {
    return new Promise((resolve,reject) => {
        fn.apply(context,[...args,(err,res) => {
            return err ? reject(err) : resolve(res)
        }])
    })
  }
}

10. 手写实现Promise

  1. 简单版(resolve、reject、then)
class PromiseSimple {
  callbackList = []
  rejectList = []
  constructor(fn) {
      fn(this._resolve.bind(this), this._reject.bind(this))
  }
  then(onFulfilled) {
      this.callbackList.push(onFulfilled)
  }
  catch(onFulfilled) {
      this.rejectList.push(onFulfilled)
  }
  _resolve(value){
      setTimeout(() => this.callbackList.forEach(func => func(value)))
  }
  _reject(value){
      setTimeout(() => this.rejectList.forEach(func => func(value)))
  }
}
  1. 完整版(支持链式调用)
function Promise(excutor) {
  var self = this
  self.onResolvedCallback = []
  function resolve(value) {
    setTimeout(() => {
      self.data = value
      self.onResolvedCallback.forEach(callback => callback(value))
    })
  }
  excutor(resolve.bind(self))
}
Promise.prototype.then = function(onResolved) {
  var self = this
  return new Promise(resolve => {
    self.onResolvedCallback.push(function() {
      var result = onResolved(self.data)
      if (result instanceof Promise) {
        result.then(resolve)
      } else {
        resolve(result)
      }
    })
  })
}

11. 将一个同步callback包装成promise形式

nodeGet(param, function (err, data) { })
// 转化成promise形式
function nodeGetAysnc(param) {
return new Promise((resolve, reject) => {
  nodeGet(param, function (err, data) {
    if (err !== null) return reject(err)
    resolve(data)
  })
})}

12. async里面有多个await请求,可以怎么优化?

  1. 如果没有数据交互,可以使用Promise.all

D. 内存机制

1. V8的垃圾回收机制及内存分配机制

  1. V8将内存分为新生代和老生代
  2. 新生代中对象存活时间较短,老生代较长
  3. 新生代采用Scavenge的算法回收,把区域分成From、To两个semispace,其中To始终闲置,垃圾回收开始时,检测From中的对象,将存活的复制到To中。复制完成后两个空间互换角色
  4. 因为新生代中对象存活时间较短,比较适合这个算法,如果一个对象多次复制仍然存活,将被放到老生代中
  5. 老生代使用标记清除算法,每次回收时将所有对象进行标记,然后将死亡对象回收
  6. 标记清除算法会造成内存片段不连续,因此可以在每次回收时将存活片段进行偏移
  7. 因为老生代中死亡对象较少,比较适合标记清除算法

2. 什么是内存泄漏,如何排查

  1. 内存泄漏是指已经不再使用的内存没有被释放
  2. 如果内存泄漏过多,无用内存会引起服务器相应变慢
  3. 如果超过系统内存上限或者进程上限,会崩溃
  4. 常见的内存泄漏原因有全局变量、闭包、事件监听等
  5. 复现较高的内存泄漏可以在测试环境模拟,偶发的可以生产环境打日志

3. 如何查看V8的内存使用情况

  1. 使用process.memoryUsage()
  2. 会返回rss、heapTotal、heapUsed
  3. rss是给这个进程分配了多少物理内存,包含堆、栈、代码段
  4. 后两个分别代表内存总申请量和使用量

4. V8的内存限制是多少,为什么V8这样设计

  1. 64位系统下是1.4GB,32位系统下是0.7GB
  2. 因为1.5GB的垃圾回收堆内存,V8需要花费50毫秒以上
  3. 做一次非增量式的垃圾回收甚至要1秒以上
  4. 这样应用的性能和影响力都会直线下降

E. Buffer

1. 文件乱码如何产生的?

  1. 比如中文在UTF-8中占3个字节,如果设置每次读取的Buffer长度不是3的整数,将最后一个文字的3个字节拆成两次读取,就会造成乱码
  2. 一般情况下,只需要设置setEncoding('utf8')即可解决乱码问题

2. 如何实现大文件读取?

  1. 使用require('fs').createReadStream()

3. Buffer如何使用内存?

  1. 新建Buffer不会占用V8分配的内存
  2. Buffer属于堆外内存,不是V8分配的

4. Buffer.alloc和Buffer.allocUnsafe的区别

  1. Buffer.allocUnsafe创建的Buffer实例底层内存是未初始化的,Buffer内容未知,可能包含敏感数据
  2. Buffer.alloc创建的是以0填充的Buffer实例

5. Buffer的内存分配机制

  1. Node采用Slab分配机制,以8kb为单位,每次需要申请就申请8kb的内存空间
  2. 如果Buffer小于8kb:如果Buffer小于当前已申请的空间,就填充进去,否则就新申请一块8kb的空间,把这段Buffer填充进去
  3. 如果Buffer大于8kb:那么直接用C++的SlowBuffer来给Buffer对象提供空间
  4. 可能出现1b的Buffer独占8kb空间的情况,比如第一段Buffer是1b,第二段是8kb

F. 进程/线程

1. 简述一下node的多进程架构,如何让单线程的Node充分利用多核CPU机器?

  1. Node提供了child_process模块,来实现进程的复制
  2. Node的多进程架构是主从模式

2. 创建子进程的方法有哪些,简单说一下它们的区别

  1. spawn():执行命令,进程类型任意
  2. exec():执行命令,可以有一个callback获取子进程的一些情况,可以设置超时时间,进程类型任意
  3. execFile():执行可执行文件,可以设置超时时间,进程类型任意
  4. fork():执行node.js文件,父子进程可通信

3. Node.js单线程的应用场景,如何实现高并发

  1. Node单线程是指主线程一个,底层工作线程有多个
  2. 主线程处理CPU计算操作,其他线程处理长耗时的I/O操作
  3. 高并发主要得益于libuv层的事件循环机制,和底层线程池实现

4. 如何实现进程间的状态共享,或者数据共享,聊聊Cluster

www.jianshu.com/p/2e04bca6d…

5. 实现一个node子进程被杀死,然后自动重启代码的思路

  1. 在创建子进程的时候就让子进程监听exit事件,如果被杀死就重新fork一下
var createWorker = function(){	
    var worker = fork(__dirname + 'worker.js')	
    worker.on('exit', function(){	
        console.log('Worker' + worker.pid + 'exited');	
        // 如果退出就创建新的worker	
        createWorker()	
    })	
}

G. 框架

1. Express、Koa、Egg对比

  1. Express比较早,生态社区比较丰富,框架体谅大,功能大而全
  2. Koa比较新,体谅小,可以自由定制加载各个模块
  3. Koa完全使用洋葱模型,可以将拓展上下文挂载在ctx上,Express使用独立的req、res,没有上下文
  4. Koa使用await/async语法,Express使用Promise,对异步处理上Koa更人性化,避免了回调嵌套
  5. Egg在Koa的基础上进行了拓展,集成了一些插件
  6. Koa没有controller,service,router,Egg加入了这些,并且约定了文件目录结构,更利于工程化的开发,但也有局限

2. Express中间件详解

zhuanlan.zhihu.com/p/87079561

二、前端部分

A. Javascript篇

1. 有没有用过apply(), bind(), call()这些方法,他们之间的区别

  1. apply、call、bind三者都是用来改变函数的this对象的指向的
  2. apply、call、bind三者第一个参数都是this要指向的对象,也就是想指定的上下文
  3. apply、call、bind三者都可以利用后续参数传参
  4. bind是返回对应函数,便于稍后调用;apply、call是立即调用

2. 1和new Number(1)有什么区别

  1. 1是Number类型,值为1
  2. new Number(1)是把1包装成Number对象,type为object

3. 0.1 + 0.2 != 0.3问题

  1. 造成原因是,JS在IEEE 754的双精度标准存储浮点数
  2. 小数表示为2^(-n)相加,所以除了那些能表示成 x/(2^n)的数可以被精确表示以外,其余小数都是以近似值的方式存在
  3. 因此0.1+0.2近似等于0.3
  4. 单纯的解决此问题,可以用ES6的Number.EPSILON,判定差值小于这个就算相等
  5. 如果统一处理,可以将小数部分转为整数或者字符串处理

4. prototype和__proto__的问题

  1. JS中所有的对象都是Object的实例,并继承Object.prototype的属性和方法,也就是说,Object.prototype是所有对象的爸爸

  2. 在对象创建时,会有一些预定义的属性

    1. 定义函数的时候,这个预定义属性就是prototypeprototype就是一个普通的对象
    2. 定义普通对象的时候,就会生成一个__proto__,这个__proto__指向的是这个对象的构造函数的prototype
  3. 简单理解

    function B() {}
    var b = new B()
    
    // b.__proto__ === B.prototype
    

5. New做了什么

  1. 创建空对象obj
  2. 设置原型链
  3. 将fn的this指向这个obj,并执行函数体
  4. 判断fn的返回值类型,如果是值类型,返回obj。如果是引用类型,就返回这个引用类型的对象

B. 其他

1. MVC/MVP/MVVM模型的区别

  1. MVC——Model、View、Controller
    1. Model用来封装数据及数据处理方法
    2. View用来展示数据
    3. Controller连接M和V,控制应用程序的流程,处理用户行为和数据上的改变
  2. MVP——Model、View、Presenter
    1. Presenter扮演Controller的角色
    2. Presenter提供接口,让Presenter去更新Model,再通过观察者模式更新View
    3. MVP解耦View和Model,完全分离视图和模型,职责划分更加清晰
    4. View不依赖Model,可以将View抽离出来做成组件,只需要提供一系列接口
  3. MVVM——Model、View、ViewModel
    1. View通过使用模板语法来声明式的将数据渲染进DOM,当ViewModel对Model进行更新的时候,会通过数据绑定更新到View
    2. ViewModel进行数据绑定,当Model发生变化,ViewModel就会自动更新;ViewModel变化,Model也会更新

2. 单页面应用/单页面路由

  1. 什么是单页面应用
    1. 将所有的活动局限于一个Web页面中
    2. 在Web页面初始化时加载相应的HTML、JavaScript和CSS
    3. 一旦页面加载完成了,SPA不会因为用户的操作而进行页面的重新加载或跳转
    4. 利用JavaScript动态的变换HTML的内容,从而实现UI与用户的交互
  2. 单页面应用的好处
    1. 良好的交互体验:内容改变不回加载整个页面,数据获取也是Ajax异步获取,没有切换页面的白屏
    2. 良好的前后端分离模式:后端不再负责模板渲染、输出页面工作,后端API通用化
    3. 减轻服务器压力:服务器只用出数据就可以,不用管展示逻辑和页面合成
  3. 单页面应用的缺点
    1. 首屏加载慢:如果路由不做处理,那加载首页会将全部组件加载,并向服务器请求全部数据
    2. 不利于SEO:搜索引擎请求到的html是模型页面而不是最终数据的渲染页面
  4. 如何解决单页面应用的缺点
    1. 首屏问题
      1. 利用router进行懒加载,首页只加载当前路由下的组件
      2. CDN加速
      3. 服务端渲染
    2. SEO问题
      1. 服务端渲染
      2. 页面预渲染
      3. 使用H5 history模式处理路由
  5. 为什么需要单页面路由系统
    1. 用户使用过程中全程URL不变,多次跳转后误刷新页面会回到最开始
    2. 不利于SEO,不利于搜索引擎收录
  6. 单页面路由系统有哪些?实现原理
    1. hash、history
    2. hash:
      1. 以#开头,原本是为了作为锚点,方便用户在文章导航到相应的位置
      2. hash值改变不会引起页面的刷新
      3. 用hash值来做单页面应用的路由,并且当url的hash发生变化的时候,可以触发相应hashchange回调函数
      4. 简单实用,易于理解,但是url+#不美观
    3. history:
      1. history路由是基于HTML5规范,在HTML5规范中提供了history.pushState和history.replaceState来进行路由控制
      2. url更好看,但是后端需要做配置

三、网络通信部分

A. HTTP篇

1. HTTP请求从客户端到服务端流程

  1. DNS解析
  2. 建立TCP连接
  3. 客户端发送HTTP请求
  4. 服务端响应请求
  5. 断开TCP连接

2. DNS解析流程

按照以下流程找

  1. 浏览器缓存
  2. 操作系统缓存
  3. 路由缓存
  4. DNS服务器
  5. 根域名服务器

3. HTTP版本区别

  1. HTTP主要分0.9/1.0/1.1/2.0三个版本
  2. 0.9版本:只有GET请求,服务端返回HTML
  3. 1.0版本:引入了Content-Type,但是1次TCP连接只能请求一次
  4. 1.1版本:引入了持久连接,引入了多种请求方法
  5. 2.0版本:引入了多路复用、头部压缩、服务器推送
  6. 1.1 vs 1.0:长连接、更丰富的缓存控制、更多状态码、支持断点续传、虚拟主机

4. HTTPS及加密过程

  1. HTTPS是HTTP套了一个安全的壳子SSL/TLS
  2. HTTPS大致加密过程:
    1. 客户端将随机数A + 可选加密方案发送给服务端
    2. 服务端收到后将随机数B + 选择的加密方案 + 证书发给客户端
    3. 客户端验证证书有效性(CA),如果有效,通过;无效,报警
    4. 客户端通过证书中的公钥加密随机数C,发给服务端
    5. 服务端用私钥解密得到随机数C
    6. 加密过程结束,之后使用随机数A+B+C的组合session进行加密传输

5. HTTPS使用什么端口?

  1. 443端口用于身份校验
  2. 80端口用于数据传输

6. 为什么需要CA机构对证书签名

  1. 如果不签名会存在中间人攻击的风险
  2. 签名之后保证了证书里的信息不被篡改,能够验证客户端和服务器端的“合法性”

B. TCP/UDP篇

1. TCP握手过程及原因

太多,看着聊

2. TCP/UDP区别

太多,看着聊

C. API篇

1. RESTful API的理解

  1. 就是一套编写接口的规范,规定了一些标准的URL格式、返回值、状态码
  2. 好处:规范、统一、清晰明了
  3. 坏处:将id放到URL中对性能监控很不利等

2. 说说GraphQL

  1. 开发人员可以不用写接口文档
  2. 前端可以按需返回,减少传输量

D. 其他

1. 什么是websocket

  1. 一个基于TCP连接的持久化网络通信协议
  2. 服务端可以主动推送信息给客户端,不需要客户端重复的向服务端发请求查询

2. webSocket与传统的http有什么优势

  1. 客户端与服务器只需要一个TCP连接,比http长轮询更少
  2. 服务端可以推送数据到客户端
  3. 更轻量的协议头,减少数据传输量

3. CDN相关

  1. Content Delivery Network,内容分发网络
  2. CDN是一组分布在多个不同的地理位置的WEB服务器
  3. CDN加速就是在用户和服务器之间加一个缓存机制,通过这个缓存机制动态获取IP地址。根据地理位置,让用户到最近的服务器访问

4. 流量劫持相关

  1. 链路劫持:在用户和服务之间拦截,盗取密码,或者植入广告
  2. DNS劫持:让用户无法访问真正的服务器或者访问其他IP,可以修改DNS、修改路由器密码
  3. CDN+运营商混合劫持,可以换一个不带加速的DNS服务器

四、数据库部分

A. 基础篇

1. MySQL和MongoDB对比

  1. MySQL:
    1. 关系型数据库
    2. 根据引擎不同有不同的存储、数据处理方式
    3. 使用SQL查询
    4. 支持事务
  2. MongoDB:
    1. 非关系型数据库
    2. 使用虚拟内存+持久化存储
    3. 基于内存进行数据处理,海量数据处理效率高
    4. 拥有独特的查询方式

3. 分库分表方法

  1. 垂直分表
    1. 大表拆小表
    2. 将不常用的字段列拆到扩展表中
    3. 后期维护成本高
  2. 垂直分库
    1. 按业务模块划分不同数据库
    2. 分库之后跨库join、分布式事务可能出问题
  3. 水平分表
    1. 将主键进行Hash或者取模后拆分
    2. 降低单表数据量
    3. 仍然有库级别的I/O瓶颈
  4. 水平分库
    1. 和水平分表原理一样
    2. 将表分散到不同数据库中
    3. 突破I/O性能限制
    4. 跨分片的复杂查询、事务等比较难

B. MySQL篇

1. MySQL存储引擎区别

  1. MyISAM
    1. 非事务,适合频繁查询
    2. 表级锁,不会死锁
    3. 小数据,小并发
  2. InnoDB
    1. 事务,适合增改较多
    2. 行级锁
    3. 大数据,大并发

2. MySQL数据表类型

  1. MyISAM、InnoDB、HEAP、BOB、ARCHIVE、CSV
  2. MyISAM:成熟稳定、易于管理、快速读取,不支持事务,表级锁
  3. InnoDB:支持事务、外键、行级锁、占用空间大、不支持全文索引

3. MySQL查询优化方式

  1. 选择合适引擎
  2. 优化字段数据类型
  3. 为搜索字段添加索引
  4. 避免使用select *
  5. 固定列表使用ENUM而不是VARCHAR
  6. 尽可能使用NOT NULL
  7. 固定长度的表更快

4. 如何设计高并发系统

  1. 数据库优化:事务隔离级别、SQL语句优化、索引优化
  2. 使用缓存,减少数据库I/O
  3. 分布式数据库,分布式缓存
  4. 服务器负载均衡

5. 锁的优化策略

  1. 读写分离
  2. 分段加锁
  3. 减少锁的持有时间
  4. 多线程使用相同顺序获取资源

6. E-R图(Entity-Relation)

  1. 矩形:Entity
  2. 菱形:Relation
  3. 椭圆:属性,主键标下划线

7. 索引的底层实现原理和优化

  1. 经过优化的B+树
  2. 在所有叶子结点中增加指向下一个叶子结点的指针
  3. InnoDB建议大部分表使用自增主键作为主索引

8. 什么时候索引不生效

  1. 以%开头的LIKE语句
  2. OR前后没有同时使用索引
  3. 数据类型出现隐式转化

9. SQL语句优化

  1. WHERE中,表连接写在最前面,可以过滤最大数据记录的条件写在最后
  2. 用EXISTS代替IN,用NOT EXISTS替代NOT IN
  3. 避免在索引列使用计算
  4. 避免在索引列使用IS NULL、IS NOT NULL
  5. 避免全表扫描,在WHERE和ORDER BY涉及的列上建立索引
  6. 避免在WHERE中进行NULL判断,将放弃索引
  7. 避免在WHERE中进行表达式操作,将放弃索引

10. 实践中如何优化MySQL

  1. SQL语句及索引优化
  2. 库表结构优化
  3. 系统配置优化
  4. 硬件优化

11. MySQL中索引、唯一索引、主键、联合索引的区别

  1. 索引:特殊的文件,表空间的组成部分,包含数据表里所有记录的引用指针。普通索引的唯一作用就是加快访问速度
  2. 唯一索引:普通索引可以包含重复值,如果确定各不相同,可以使用UNIQUE定义为唯一索引,保证数据唯一性
  3. 主键:特殊的唯一索引,PRIMARY KEY
  4. 联合索引:索引覆盖了多个数据列

12. 索引的优缺点

  1. 提高查询速度
  2. 降低增删改速度(因为要操作索引文件)

13. 事务是什么,有什么特性

  1. 一组数据库操作
  2. 如果都成功,事务成功,有一个失败,事务回滚
  3. 四大特性:原子性、隔离型、一致性、持久性

14. 什么时候不适合建立索引

  1. 查询很少涉及的列
  2. 重复值较多的列
  3. 特殊数据类型列,如TEXT

15. MySQL外连接、内连接、自连接区别

  1. 交叉连接:笛卡尔积,N*M条依次匹配
  2. 内连接:有条件的交叉连接,只显示匹配的行
  3. 外连接:不仅显示内连接的行,还显示左、右、两个表的所有行,分别是左外连接、右外连接、全外连接
  4. 左右外连接可互换,MySQL不支持全外连接
  5. 自连接:同一张表的连接

16. CHAR、VARCHAR区别

  1. CHAR(M)是指每个值都是M个字节,小于的话会在右面用空格补齐
  2. VARCHAR长度可变
  3. CHAR一般存储具有近似长度的比如,MD5、手机号、身份证

五、缓存部分

A. Redis篇

1. 用没用过Redis,为什么用Redis

1. Redis的几种数据类型

2. Redis主要用到哪些API

3. 缓存在项目中的具体应用

4. Redis如何拓展(集群)

5. 缓存更新的问题,当大量缓存同时过期时如何处理,缓存雪崩的问题

6. Redis的持久化

7. Redis为什么这么快、单线程

8. 分布式任务锁

六、消息队列部分

A. RabbitMQ

1. RabbitMQ的几种应用场景

2. RabbitMQ交换机有几种类型,*号与#号的区别,举个例子

3. RabbitMQ的订阅发布

七、分布式/微服务部分

八、数据结构/算法部分

A. 数组/链表

1. 快速排序

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArray = function(nums) {
    const quicksort = (arr, begin, end) => {
        if (begin >= end) { return }
        let left = begin, right = end
        while(left < right) {
            while (arr[right] >= arr[begin] && left < right) { right-- }
            while (arr[left] <= arr[begin] && left < right) { left++ }
            [arr[left], arr[right]] = [arr[right], arr[left]]
        }
        [arr[begin], arr[right]] = [arr[right], arr[begin]]

        quicksort(arr, begin, right - 1)
        quicksort(arr, right + 1, end)
    }
    quicksort(nums, 0, nums.length - 1)
    return nums
};

九、Golang基础

十、其他

1. 常用的设计模式

2. 如何保证代码质量