
// 要看的东西好多啊啊啊啊啊啊
一、Node.js部分
A. 模块机制
1. exports、module.exports、export、export default区别
- 涉及到两种规范:CommonJS和ES6
exports、module.exports是CommonJS规范下的,对应require;export、export default是ES6规范下,对应import- exports/module.exports:
module.exports是node模块机制对应的唯一出口对象,exports只是指向这个对象的一个引用,因此使用exports时注意不能断了它和module.exports的联系 - export/export default:二者都相当于把对象添加到
module.exports中,区别在于在module.exports中的key不同,export是开发者自定义的key,export default的key是写死的,就叫default
2. 常用到哪些库(原生模块、第三方库)
- Lodash:用来做数据处理
- Koa:路由框架
- request/request-promise:网络请求
- Log4js:日志打印
- Sequelize:数据库链接
- moment:时间处理
- 内置:fs-文件处理、http:网络请求
3. 模块加载的机制
- 先计算模块路径
- 如果模块在缓存里面,取出缓存
- 加载模块
- 输出模块的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属性
- Node的module中并没有定义这两个属性
__dirname和__filename是new module时传入的
B. 异步I/O
1. 为什么要异步I/O?
- 主要有两个原因:用户体验、资源分配
- 对于用户体验,异步I/O可以消除UI阻塞,快速响应资源
- 对于资源分配,单线程异步I/O可以避免同步执行的I/O阻塞,也可以避免多线程上下文切换带来的状态同步/死锁问题
2. 聊聊Node的异步I/O?
- 事件循环、观察者、请求对象、I/O线程池构成了异步I/O
- 事件循环就是Node进程启动时设置了一个类似
while(true)的循环,每次循环叫做一个Tick,一次Tick的作用就是看有没有要执行的事情,有的话就拿出来执行相关回调,然后继续循环 - 事件循环的过程中如何知道有没有任务呢?向观察者问。观察者负责管理各种待执行的事件
- 请求对象是从javascript发起调用到内核I/O执行完毕中的中间产物,所有的状态都存到这个对象里,包括送入线程池和I/O结束之后的回调处理
3. 简单说一下I/O过程?
- javascript发起,调用node核心模块
- node核心模块调用c++内建模块
- 内建模块通过libuv进行系统调用
4. setTimeout、setInterval、setImmediate、process.nextTick
- 他们都是和I/O无关的异步API
setTimeout和setInterval是定时器,创建时会交给定时器观察者,每次Tick执行时会去查看是否超过定时事件,如果超过了就立刻执行- 定时器是非精确的
process.nextTick和setImmediate都是下一个Tick执行,process.nextTick交给idle观察者,setImmediate交给check观察者。- 每一轮Tick对观察者的检测是有顺序的,idle观察者->I/O观察者->check观察者
- 所以
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性能优化
- 避免使用同步代码,充分利用Node单线程异步I/O的优势
- 关闭Socket池,Node会为http默认开启Socket池,限制了最大为5,如果同一台机器并发请求,将会造成性能瓶颈
- 不要让静态资源使用Node.js
- 客户端渲染
7. Node.js事件循环,和浏览器的区别
- 在Node11版本之后,与浏览器一致
- 浏览器分宏任务和微任务
- 宏任务:script 中代码、setTimeout、setInterval、I/O、UI render
- 微任务:Promise、Object.observe、MutationObserver
- 浏览器EventLoop
- 执行同步代码 -> 执行清空微任务队列
- 从宏任务队列取出一个宏任务执行 -> 执行清空微任务队列
- 重复2
- Node11之前的EventLoop
- 分6个阶段:timers、I/O callback、idle/prepare、poll、check、close callback
- 有四个队列:Timers、I/O、Check、Close依次执行
- 循环:
- 清空Timers Queue、NextTick Queue、Microtask Queue
- 清空I/O Queue、NextTick Queue、Microtask Queue
- 清空Check Queue、NextTick Queue、Microtask Queue
- 清空Close Queue、NextTick Queue、Microtask Queue
C. 异步编程
1. 异步编程的好处
- 基于事件驱动的非阻塞异步I/O模型是Node的基本模型
- 异步编程可以充分发挥CPU和I/O资源
2. 如何避免回调地狱
- 拆解函数
- 使用事件发布/监听模式
- 使用Promise
- 使用generator
- 使用async/await
3. 流程控制库async,自己在项目中常用到哪些方法
- 没有用过,但是知道
- waterfall,瀑布流,上一个函数的返回值是下一个函数的参数
- series,互相没有数据交互,从上到下依次执行
- parallel,并行执行,不会等待
- parallelLimit,限制并发数的parallel
4. async/await、Promise、Generator区别
- async/await是generator的语法糖
- async/await语义更清晰,自带执行器,async函数返回Promise对象
- async用来解决Promise的then调用链长的问题
5. Promise的几种状态、如何实现的、什么情况下catch不到错误
- pending、fulfilled、rejected
- 初始化,状态为pending,调用resolve时,pending=>fulfilled,调用reject时,pending=>rejected
- fulfilled和rejected之间不可转化
- proimse中如果有异步错误,无法被同步catch
- 全局捕获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
- 简单版(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)))
}
}
- 完整版(支持链式调用)
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请求,可以怎么优化?
- 如果没有数据交互,可以使用Promise.all
D. 内存机制
1. V8的垃圾回收机制及内存分配机制
- V8将内存分为新生代和老生代
- 新生代中对象存活时间较短,老生代较长
- 新生代采用Scavenge的算法回收,把区域分成From、To两个semispace,其中To始终闲置,垃圾回收开始时,检测From中的对象,将存活的复制到To中。复制完成后两个空间互换角色
- 因为新生代中对象存活时间较短,比较适合这个算法,如果一个对象多次复制仍然存活,将被放到老生代中
- 老生代使用标记清除算法,每次回收时将所有对象进行标记,然后将死亡对象回收
- 标记清除算法会造成内存片段不连续,因此可以在每次回收时将存活片段进行偏移
- 因为老生代中死亡对象较少,比较适合标记清除算法
2. 什么是内存泄漏,如何排查
- 内存泄漏是指已经不再使用的内存没有被释放
- 如果内存泄漏过多,无用内存会引起服务器相应变慢
- 如果超过系统内存上限或者进程上限,会崩溃
- 常见的内存泄漏原因有全局变量、闭包、事件监听等
- 复现较高的内存泄漏可以在测试环境模拟,偶发的可以生产环境打日志
3. 如何查看V8的内存使用情况
- 使用
process.memoryUsage() - 会返回rss、heapTotal、heapUsed
- rss是给这个进程分配了多少物理内存,包含堆、栈、代码段
- 后两个分别代表内存总申请量和使用量
4. V8的内存限制是多少,为什么V8这样设计
- 64位系统下是1.4GB,32位系统下是0.7GB
- 因为1.5GB的垃圾回收堆内存,V8需要花费50毫秒以上
- 做一次非增量式的垃圾回收甚至要1秒以上
- 这样应用的性能和影响力都会直线下降
E. Buffer
1. 文件乱码如何产生的?
- 比如中文在UTF-8中占3个字节,如果设置每次读取的Buffer长度不是3的整数,将最后一个文字的3个字节拆成两次读取,就会造成乱码
- 一般情况下,只需要设置
setEncoding('utf8')即可解决乱码问题
2. 如何实现大文件读取?
- 使用
require('fs').createReadStream()
3. Buffer如何使用内存?
- 新建Buffer不会占用V8分配的内存
- Buffer属于堆外内存,不是V8分配的
4. Buffer.alloc和Buffer.allocUnsafe的区别
Buffer.allocUnsafe创建的Buffer实例底层内存是未初始化的,Buffer内容未知,可能包含敏感数据Buffer.alloc创建的是以0填充的Buffer实例
5. Buffer的内存分配机制
- Node采用Slab分配机制,以8kb为单位,每次需要申请就申请8kb的内存空间
- 如果Buffer小于8kb:如果Buffer小于当前已申请的空间,就填充进去,否则就新申请一块8kb的空间,把这段Buffer填充进去
- 如果Buffer大于8kb:那么直接用C++的SlowBuffer来给Buffer对象提供空间
- 可能出现1b的Buffer独占8kb空间的情况,比如第一段Buffer是1b,第二段是8kb
F. 进程/线程
1. 简述一下node的多进程架构,如何让单线程的Node充分利用多核CPU机器?
- Node提供了child_process模块,来实现进程的复制
- Node的多进程架构是主从模式
2. 创建子进程的方法有哪些,简单说一下它们的区别
- spawn():执行命令,进程类型任意
- exec():执行命令,可以有一个callback获取子进程的一些情况,可以设置超时时间,进程类型任意
- execFile():执行可执行文件,可以设置超时时间,进程类型任意
- fork():执行node.js文件,父子进程可通信
3. Node.js单线程的应用场景,如何实现高并发
- Node单线程是指主线程一个,底层工作线程有多个
- 主线程处理CPU计算操作,其他线程处理长耗时的I/O操作
- 高并发主要得益于libuv层的事件循环机制,和底层线程池实现
4. 如何实现进程间的状态共享,或者数据共享,聊聊Cluster
5. 实现一个node子进程被杀死,然后自动重启代码的思路
- 在创建子进程的时候就让子进程监听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对比
- Express比较早,生态社区比较丰富,框架体谅大,功能大而全
- Koa比较新,体谅小,可以自由定制加载各个模块
- Koa完全使用洋葱模型,可以将拓展上下文挂载在ctx上,Express使用独立的req、res,没有上下文
- Koa使用await/async语法,Express使用Promise,对异步处理上Koa更人性化,避免了回调嵌套
- Egg在Koa的基础上进行了拓展,集成了一些插件
- Koa没有controller,service,router,Egg加入了这些,并且约定了文件目录结构,更利于工程化的开发,但也有局限
2. Express中间件详解
二、前端部分
A. Javascript篇
1. 有没有用过apply(), bind(), call()这些方法,他们之间的区别
- apply、call、bind三者都是用来改变函数的this对象的指向的
- apply、call、bind三者第一个参数都是this要指向的对象,也就是想指定的上下文
- apply、call、bind三者都可以利用后续参数传参
- bind是返回对应函数,便于稍后调用;apply、call是立即调用
2. 1和new Number(1)有什么区别
- 1是Number类型,值为1
- new Number(1)是把1包装成Number对象,type为object
3. 0.1 + 0.2 != 0.3问题
- 造成原因是,JS在IEEE 754的双精度标准存储浮点数
- 小数表示为
2^(-n)相加,所以除了那些能表示成x/(2^n)的数可以被精确表示以外,其余小数都是以近似值的方式存在 - 因此0.1+0.2近似等于0.3
- 单纯的解决此问题,可以用ES6的
Number.EPSILON,判定差值小于这个就算相等 - 如果统一处理,可以将小数部分转为整数或者字符串处理
4. prototype和__proto__的问题
-
JS中所有的对象都是
Object的实例,并继承Object.prototype的属性和方法,也就是说,Object.prototype是所有对象的爸爸 -
在对象创建时,会有一些预定义的属性
- 定义函数的时候,这个预定义属性就是
prototype,prototype就是一个普通的对象 - 定义普通对象的时候,就会生成一个
__proto__,这个__proto__指向的是这个对象的构造函数的prototype
- 定义函数的时候,这个预定义属性就是
-
简单理解
function B() {} var b = new B() // b.__proto__ === B.prototype
5. New做了什么
- 创建空对象obj
- 设置原型链
- 将fn的this指向这个obj,并执行函数体
- 判断fn的返回值类型,如果是值类型,返回obj。如果是引用类型,就返回这个引用类型的对象
B. 其他
1. MVC/MVP/MVVM模型的区别
- MVC——Model、View、Controller
- Model用来封装数据及数据处理方法
- View用来展示数据
- Controller连接M和V,控制应用程序的流程,处理用户行为和数据上的改变
- MVP——Model、View、Presenter
- Presenter扮演Controller的角色
- Presenter提供接口,让Presenter去更新Model,再通过观察者模式更新View
- MVP解耦View和Model,完全分离视图和模型,职责划分更加清晰
- View不依赖Model,可以将View抽离出来做成组件,只需要提供一系列接口
- MVVM——Model、View、ViewModel
- View通过使用模板语法来声明式的将数据渲染进DOM,当ViewModel对Model进行更新的时候,会通过数据绑定更新到View
- ViewModel进行数据绑定,当Model发生变化,ViewModel就会自动更新;ViewModel变化,Model也会更新
2. 单页面应用/单页面路由
- 什么是单页面应用
- 将所有的活动局限于一个Web页面中
- 在Web页面初始化时加载相应的HTML、JavaScript和CSS
- 一旦页面加载完成了,SPA不会因为用户的操作而进行页面的重新加载或跳转
- 利用JavaScript动态的变换HTML的内容,从而实现UI与用户的交互
- 单页面应用的好处
- 良好的交互体验:内容改变不回加载整个页面,数据获取也是Ajax异步获取,没有切换页面的白屏
- 良好的前后端分离模式:后端不再负责模板渲染、输出页面工作,后端API通用化
- 减轻服务器压力:服务器只用出数据就可以,不用管展示逻辑和页面合成
- 单页面应用的缺点
- 首屏加载慢:如果路由不做处理,那加载首页会将全部组件加载,并向服务器请求全部数据
- 不利于SEO:搜索引擎请求到的html是模型页面而不是最终数据的渲染页面
- 如何解决单页面应用的缺点
- 首屏问题
- 利用router进行懒加载,首页只加载当前路由下的组件
- CDN加速
- 服务端渲染
- SEO问题
- 服务端渲染
- 页面预渲染
- 使用H5 history模式处理路由
- 首屏问题
- 为什么需要单页面路由系统
- 用户使用过程中全程URL不变,多次跳转后误刷新页面会回到最开始
- 不利于SEO,不利于搜索引擎收录
- 单页面路由系统有哪些?实现原理
- hash、history
- hash:
- 以#开头,原本是为了作为锚点,方便用户在文章导航到相应的位置
- hash值改变不会引起页面的刷新
- 用hash值来做单页面应用的路由,并且当url的hash发生变化的时候,可以触发相应hashchange回调函数
- 简单实用,易于理解,但是url+#不美观
- history:
- history路由是基于HTML5规范,在HTML5规范中提供了history.pushState和history.replaceState来进行路由控制
- url更好看,但是后端需要做配置
三、网络通信部分
A. HTTP篇
1. HTTP请求从客户端到服务端流程
- DNS解析
- 建立TCP连接
- 客户端发送HTTP请求
- 服务端响应请求
- 断开TCP连接
2. DNS解析流程
按照以下流程找
- 浏览器缓存
- 操作系统缓存
- 路由缓存
- DNS服务器
- 根域名服务器
3. HTTP版本区别
- HTTP主要分0.9/1.0/1.1/2.0三个版本
- 0.9版本:只有GET请求,服务端返回HTML
- 1.0版本:引入了Content-Type,但是1次TCP连接只能请求一次
- 1.1版本:引入了持久连接,引入了多种请求方法
- 2.0版本:引入了多路复用、头部压缩、服务器推送
- 1.1 vs 1.0:长连接、更丰富的缓存控制、更多状态码、支持断点续传、虚拟主机
4. HTTPS及加密过程
- HTTPS是HTTP套了一个安全的壳子SSL/TLS
- HTTPS大致加密过程:
- 客户端将随机数A + 可选加密方案发送给服务端
- 服务端收到后将随机数B + 选择的加密方案 + 证书发给客户端
- 客户端验证证书有效性(CA),如果有效,通过;无效,报警
- 客户端通过证书中的公钥加密随机数C,发给服务端
- 服务端用私钥解密得到随机数C
- 加密过程结束,之后使用随机数A+B+C的组合session进行加密传输
5. HTTPS使用什么端口?
- 443端口用于身份校验
- 80端口用于数据传输
6. 为什么需要CA机构对证书签名
- 如果不签名会存在中间人攻击的风险
- 签名之后保证了证书里的信息不被篡改,能够验证客户端和服务器端的“合法性”
B. TCP/UDP篇
1. TCP握手过程及原因
太多,看着聊
2. TCP/UDP区别
太多,看着聊
C. API篇
1. RESTful API的理解
- 就是一套编写接口的规范,规定了一些标准的URL格式、返回值、状态码
- 好处:规范、统一、清晰明了
- 坏处:将id放到URL中对性能监控很不利等
2. 说说GraphQL
- 开发人员可以不用写接口文档
- 前端可以按需返回,减少传输量
D. 其他
1. 什么是websocket
- 一个基于TCP连接的持久化网络通信协议
- 服务端可以主动推送信息给客户端,不需要客户端重复的向服务端发请求查询
2. webSocket与传统的http有什么优势
- 客户端与服务器只需要一个TCP连接,比http长轮询更少
- 服务端可以推送数据到客户端
- 更轻量的协议头,减少数据传输量
3. CDN相关
- Content Delivery Network,内容分发网络
- CDN是一组分布在多个不同的地理位置的WEB服务器
- CDN加速就是在用户和服务器之间加一个缓存机制,通过这个缓存机制动态获取IP地址。根据地理位置,让用户到最近的服务器访问
4. 流量劫持相关
- 链路劫持:在用户和服务之间拦截,盗取密码,或者植入广告
- DNS劫持:让用户无法访问真正的服务器或者访问其他IP,可以修改DNS、修改路由器密码
- CDN+运营商混合劫持,可以换一个不带加速的DNS服务器
四、数据库部分
A. 基础篇
1. MySQL和MongoDB对比
- MySQL:
- 关系型数据库
- 根据引擎不同有不同的存储、数据处理方式
- 使用SQL查询
- 支持事务
- MongoDB:
- 非关系型数据库
- 使用虚拟内存+持久化存储
- 基于内存进行数据处理,海量数据处理效率高
- 拥有独特的查询方式
3. 分库分表方法
- 垂直分表
- 大表拆小表
- 将不常用的字段列拆到扩展表中
- 后期维护成本高
- 垂直分库
- 按业务模块划分不同数据库
- 分库之后跨库join、分布式事务可能出问题
- 水平分表
- 将主键进行Hash或者取模后拆分
- 降低单表数据量
- 仍然有库级别的I/O瓶颈
- 水平分库
- 和水平分表原理一样
- 将表分散到不同数据库中
- 突破I/O性能限制
- 跨分片的复杂查询、事务等比较难
B. MySQL篇
1. MySQL存储引擎区别
- MyISAM
- 非事务,适合频繁查询
- 表级锁,不会死锁
- 小数据,小并发
- InnoDB
- 事务,适合增改较多
- 行级锁
- 大数据,大并发
2. MySQL数据表类型
- MyISAM、InnoDB、HEAP、BOB、ARCHIVE、CSV
- MyISAM:成熟稳定、易于管理、快速读取,不支持事务,表级锁
- InnoDB:支持事务、外键、行级锁、占用空间大、不支持全文索引
3. MySQL查询优化方式
- 选择合适引擎
- 优化字段数据类型
- 为搜索字段添加索引
- 避免使用
select * - 固定列表使用ENUM而不是VARCHAR
- 尽可能使用NOT NULL
- 固定长度的表更快
4. 如何设计高并发系统
- 数据库优化:事务隔离级别、SQL语句优化、索引优化
- 使用缓存,减少数据库I/O
- 分布式数据库,分布式缓存
- 服务器负载均衡
5. 锁的优化策略
- 读写分离
- 分段加锁
- 减少锁的持有时间
- 多线程使用相同顺序获取资源
6. E-R图(Entity-Relation)
- 矩形:Entity
- 菱形:Relation
- 椭圆:属性,主键标下划线
7. 索引的底层实现原理和优化
- 经过优化的B+树
- 在所有叶子结点中增加指向下一个叶子结点的指针
- InnoDB建议大部分表使用自增主键作为主索引
8. 什么时候索引不生效
- 以%开头的LIKE语句
- OR前后没有同时使用索引
- 数据类型出现隐式转化
9. SQL语句优化
- WHERE中,表连接写在最前面,可以过滤最大数据记录的条件写在最后
- 用EXISTS代替IN,用NOT EXISTS替代NOT IN
- 避免在索引列使用计算
- 避免在索引列使用IS NULL、IS NOT NULL
- 避免全表扫描,在WHERE和ORDER BY涉及的列上建立索引
- 避免在WHERE中进行NULL判断,将放弃索引
- 避免在WHERE中进行表达式操作,将放弃索引
10. 实践中如何优化MySQL
- SQL语句及索引优化
- 库表结构优化
- 系统配置优化
- 硬件优化
11. MySQL中索引、唯一索引、主键、联合索引的区别
- 索引:特殊的文件,表空间的组成部分,包含数据表里所有记录的引用指针。普通索引的唯一作用就是加快访问速度
- 唯一索引:普通索引可以包含重复值,如果确定各不相同,可以使用UNIQUE定义为唯一索引,保证数据唯一性
- 主键:特殊的唯一索引,PRIMARY KEY
- 联合索引:索引覆盖了多个数据列
12. 索引的优缺点
- 提高查询速度
- 降低增删改速度(因为要操作索引文件)
13. 事务是什么,有什么特性
- 一组数据库操作
- 如果都成功,事务成功,有一个失败,事务回滚
- 四大特性:原子性、隔离型、一致性、持久性
14. 什么时候不适合建立索引
- 查询很少涉及的列
- 重复值较多的列
- 特殊数据类型列,如TEXT
15. MySQL外连接、内连接、自连接区别
- 交叉连接:笛卡尔积,N*M条依次匹配
- 内连接:有条件的交叉连接,只显示匹配的行
- 外连接:不仅显示内连接的行,还显示左、右、两个表的所有行,分别是左外连接、右外连接、全外连接
- 左右外连接可互换,MySQL不支持全外连接
- 自连接:同一张表的连接
16. CHAR、VARCHAR区别
- CHAR(M)是指每个值都是M个字节,小于的话会在右面用空格补齐
- VARCHAR长度可变
- 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
};