什么是NodeJs
NodeJs优势
NodeJs常见应用场景
和JavaScript的区别
和Java相比的区别
NodeJs的优缺点
NodeJs特性都有哪些
NodeJs事件驱动是什么
NodeJs事件驱动的优缺点
优点
缺点
NodeJs事件驱动原理
事件循环是 Node.js 处理非阻塞 I/O 操作的机制——尽管 JavaScript 是单线程处理的——当有可能的时候,它们会把操作转移到系统内核中去。
既然目前大多数内核都是多线程的,它们可在后台处理多种操作。当其中的一个操作完成的时候,内核通知 Node.js 将适合的回调函数添加到 轮询 队列中等待时机执行
详情请看NodeJs事件循环原理
libuv线程池运行原理
libuv最初是为nodejs编写的跨平台支持库。它是围绕事件驱动的异步 I/O 模型设计的
libuv 提供了一个线程池,可用于运行用户代码并在循环线程中获得通知。此线程池在内部用于运行所有文件系统操作,以及 getaddrinfo 和 getnameinfo 请求。
它的默认大小是 4,但可以在启动时通过将 UV_THREADPOOL_SIZE
环境变量设置为任何值来更改它,最大支持1024个线程
线程池是全局的,并且在所有事件循环中共享。当特定函数使用线程池时(即使用时uv_queue_work()
),libuv 会预先分配并初始化 允许的最大线程数 UV_THREADPOOL_SIZE
。这会导致相对较小的内存开销,但会提高运行时线程的性能
NodeJs如何使用libuv中的线程池
NodeJs事件驱动和浏览器的事件循环区别是什么
浏览器的事件循环队列分为macro(宏任务)队列和 micro(微任务)队列。宏任务队列可以有多个
当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。
比如以下代码就能够很明显的看出浏览器事件循环和nodejs事件循环的区别
function test () {
console.log('start')
setTimeout(() => {
console.log('children2')
Promise.resolve().then(() => {console.log('children2-1')})
}, 0)
setTimeout(() => {
console.log('children3')
Promise.resolve().then(() => {console.log('children3-1')})
}, 0)
Promise.resolve().then(() => {console.log('children1')})
console.log('end')
}
test()
// 以上代码在node11以下版本的执行结果(先执行所有的宏任务,再执行微任务)
// start
// end
// children1
// children2
// children3
// children2-1
// children3-1
// 以上代码在node11及浏览器的执行结果(顺序执行宏任务和微任务)
// start
// end
// children1
// children2
// children2-1
// children3
// children3-1
浏览器线程模型
NodeJs如何提升性能和代码质量
常用的排序算法
/**
* @event 快速排序
* @description
*
* 快速排序是对冒泡排序的一种改进。它的基本思想是:
* 通过一趟排序将要排序的数据分割成独立的两部分,
* 其中一部分的所有数据都比另外一不部分的所有数据都要小,
* 然后再按此方法对这两部分数据分别进行快速排序,
* 整个排序过程可以递归进行,以此达到整个数据变成有序序列。
* 整个排序过程只需要三步:
1. 找到该数组的基准点(中间数),并创建两个空数组left和right;
2. 遍历数组,拿出数组中的每个数和基准点进行比较,
如果比基准点小就放到left数组中,如果比基准点大就放到right数组中;
3. 对数组left和right进行递归调用
*/
//第一种方式(类似二分法,一直缩小范围)
//先处理leftArr的递归,再处理rightArr的递归
const arrSortFast = (arr) => {
if (arr.length <= 1) {
return arr;
}
const leftArr = [];
const rightArr = [];
//每次删除一个数组元素并获得返回值当作基数,直到数组被删除到只剩下一个时,直接返回
//Math.round 向上取整 3.5取4 3.3取3
const baseNumber = arr.splice(Math.round(arr.length / 2), 1)[0];
arr.forEach(v => {
if (v < baseNumber) {
leftArr.push(v);
} else {
rightArr.push(v);
}
});
const value = arrSortFast(leftArr).concat([baseNumber], arrSortFast(rightArr));
return value;
};
console.log(arrSortFast([2,3,1,4]));
/**
* 算法解析
* 第一阶段:
* 取出中位数 9
* 当前数组为 [5, 3, 6, 10, 2, 4, 7, 1, 8]
* 经过左右过滤
* 左数组为 [5, 3, 6, 2, 4, 7, 1, 8]
* 右数组为 [10]
*
* 第二阶段:使用左数组继续递归
* 取出中位数 4
* 左数组为 [3, 2, 1]
* 右数组为 [5, 6, 7, 8]
*
* 第二阶段继续:使用左数组继续递归
* 取出中位数 2
* 左数组为 1
* 右数组为3
* 由于过滤的只剩下最后一个,所以直接返回
* 此时结果为[1, 2, 3]
*
* 第二阶段继续:使用右数组进行递归
* 取出中位数7
* 左数组为 [5, 6] 超过两个元素的都会进行再次递归 那么此时得到的结果肯定也是[5, 6]
* 右数组为 [8]
* 此时结果为 [5, 6, 7, 8]
*
* 此时已经得到过滤后的左右数组分别为[1, 2, 3]和[5, 6, 7, 8],中位数是4
* 那么此时结果为[1, 2, 3, 4, 5, 6, 7, 8]
*
* 当前回到第一阶段
* [5, 3, 6, 2, 4, 7, 1, 8]过滤后的结果为[1, 2, 3, 4, 5, 6, 7, 8]
* 中位数是9 右数组是[10]
* 那么此时最终结果为[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
*/
/**
* 冒泡排序法
* 相邻比较
*/
((arr = [4, 3, 5, 4, 1, 3, 2, 0, 3, 88]) => {
let length = arr.length;
// 单层for循环
for (let j = 0; j < length; j++) {
if (arr[j] > arr[j + 1]) {
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
// 在循环到最大值时候重置j(j=-1到上面j++重置为0)这样可以省了外层for循环
if (j == length - 1) {
j = -1;
length--;
}
}
console.log(arr);
})();
/**
* 第一阶段过滤得到结果
* [3, 4, 5, 4, 1, 3, 2, 0, 3, 88, 99]
*
* 第二阶段过滤 此时把长度-1
* [3, 4, 5, 1, 3, 2, 0, 3, 4, 88, 99]
*
* 第三阶段过滤
* [3, 4, 1, 3, 2, 0, 3, 4, 5, 88, 99]
*
* 第四阶段过滤
* [3, 1, 3, 2, 0, 3, 4, 4, 5, 88, 99]
*
* 以此类推 一直比较 得到结果
*/
/**
* 选择排序
* 选择某一个元素当作最小数依次遍历
* 每次找到最小值和前面的位置进行替换
*/
((arr = [8,9,7,6,4,7]) => {
const len = arr.length;
let minIndex;
let temp;
for (let i = 0; i < len; i++) {
minIndex = i; //选择当前下标为最小元素
for (let j = i + 1; j < len; j++) {
if (arr[minIndex] > arr[j]) { //每一次从i+1个开始遍历找出比自身小的元素
minIndex = j; //将最小数的索引保存
}
}
//接下来的操作类似冒泡,都是互换元素位置
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
console.log(arr);
})();
Http七层协议工作原理,都有哪些状态码,分别是什么意思
为什么需要协议?
四层协议关系
端口号范围0-65535,1024之前不允许用
物理层
数据链路层
网络层
传输层
TCP协议简述
存在长连接和短连接
短链接
TCP短连接:client向server发起连接请求,server接到请求,然后双方建立连接。client向server发送消息,server回应client,然后一次请求就完成了。这时候双方任意都可以发起close操作,不过一般都是client先发起close操作。上述可知,短连接一般只会在 client/server间传递一次请求操作。
短连接的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段
长连接 keepalive
什么时候用长连接或者短连接
服务于会话层
建立在某个主机的某个端口到另一个主机下某个端口的连接和通信的,这个通信是使用socket来实现,通过socket实现上面的ip寻址,而且还会建立端口间的连接。规定了一套基于端口的通信协议,包括建立连接,发送和读取消息。
大概机制就是在数据包中加入端口号,寻址到ip后,再去寻找监听该端口号的服务,将数据包发送过去
特点
UDP协议简述
会话层
表示层
表示层执行三个基本功能:翻译、压缩和加密/解密
。应用层 http协议(80端口),https(443端口),ftp(21),ssh(22),smtp(25)
TCP能传输数据了,为什么还需要类似http这种应用层协议?
http长连接 keep-alive
若开启后,在一次http请求中,服务器进行响应后,不再直接断开TCP连接,而是将TCP连接维持一段时间。在这段时间内,如果同一客户端再次向服务端发起http请求,便可以复用此TCP连接,向服务端发起请求,并重置timeout时间计数器,在接下来一段时间内还可以继续复用。这样无疑省略了反复创建和销毁TCP连接的损耗
启用HTTP keep-Alive的优缺点:
优点:keep-alive机制避免了频繁建立和销毁连接的开销。 同时,减少服务端TIME_WAIT状态的TCP连接的数量(因为由服务端进程主动关闭连接)
缺点:若keep-alive timeout设置的时间较长,长时间的TCP连接维持,会一定程度的浪费系统资源。
总体而言,HTTP keep-Alive的机制还是利大于弊的,只要合理使用、配置合理的timeout参数。
和TCP的keepalive区别是
什么是DNS
WebSocket面试题
websocket和socket的区别
什么是socket协议
sockey协议调用流程
socket核心api
**socket():**创建套接字。
**bind():**指定本地地址。一个套接字用socket()创建后,它其实还没有与任何特定的本地或目的地址相关联。在很多情况下,应用程序并不关心它们使用的本地地址,这时就可以不用调用bind指定本地的地址,而由协议软件为它们选择一个。但是,在某个知名端口(Well-known Port)上操作的服务器进程必须要对系统指定本地端口。所以一旦创建了一个套接字,服务器就必须使用bind()系统调用为套接字建立一个本地地址。
**listen():**设置等待连接状态。对于一个服务器的程序,当申请到套接字,并调用bind()与本地地址绑定后,就应该等待某个客户机的程序来要求连接。listen()就是把一个套接字设置为这种状态的函数。
**connect():**将套接字连接到目的地址。初始创建的套接字并未与任何外地目的地址关联。客户机可以调connect()为套接字绑定一个永久的目的地址,将它置于已连接状态。对数据流方式的套接字,必须在传输数据前,调用connect()构造一个与目的地的TCP连接,并在不能构造连接时返回一个差错代码。如果是数据报方式,则不是必须在传输数据前调用connect。如果调用了connect(),也并不像数据流方式那样发送请求建连的报文,而是只在本地存储目的地址,以后该socket上发送的所有数据都送往这个地址,程序员就可以免去为每一次发送数据都指定目的地址的麻烦。
**accept():**接受连接请求。服务器进程使用系统调用socket,bind和listen创建一个套接字,将它绑定到知名的端口,并指定连接请求的队列长度。然后,服务器调用accept进入等待状态,直到到达一个连接请求。
**read()、write():**当服务器与客户已经建立好连接之后。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信
什么是websocket
运行原理
为什么要用
应用场景
特点
基于TCP再次封装的另一个协议
首先在连接的时候会发送一个协议升级请求,在http请求上加上socket相关的请求头
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: k1kbxGRqGIBD5Y/LdIFwGQ==
Sec-WebSocket-Version: 13
Upgrade: websocket
服务端如果支持socket的话,会返回101状态码同意协议升级
总结概述
WebSocket和Http区别,相比的优缺点
为什么用这个技术
WebSocket和Socket.io区别
socket.io工作原理
socket.io
通过一个http
请求,并且该请求头中带有升级协议(Connection:Upgrade
、Upgrade:websocket
)等信息,告诉服务端准备建立连接,此时,后端返回的response
数据scoket.io
会根据当前客户端环境是否支持Websocket
。如果支持,则建立一个websocket
连接,否则使用polling
(xhr
、jsonp
)长轮询进行双向数据通信socket.io
与engine.io
的一大区别在于,socket.io
并不直接提供连接功能,而是在engine.io
层提供。什么是跨域,如何解决
script
标签发起一个请求(将回调函数的名称放到这个请求的query
参数里),然后服务端返回这个回调函数的执行,并将需要响应的数据放到回调函数的参数里,前端的script
标签请求到这个执行的回调函数后会立马执行,于是就拿到了执行的响应数据。
XMLHttpRequest
对象实现的Ajax
请求那样受到同源策略的限制XMLHttpRequest
或ActiveX
的支持callback
的方式回传结果GET
请求而不支持 POST
等其它类型的 HTTP 请求Nodejs的buffer原理和应用场景
Nodejs的流的应用场景以及运行原理
Nodejs await/async原理
function
关键字与函数名之间有一个星号;yield
表达式Nodejs多进程应用场景,如何做负载均衡和进程间通讯
进程和线程的区别
CommonJs概念
Nodejs模块
ExpressJs
KoaJs
概念
特点
洋葱卷模型
概述
源码
function compose (middleware) {
// ...
return function (context, next) {
// last called middleware #
let index = -1
// 一开始的时候传入为 0,后续会递增
return dispatch(0)
function dispatch (i) {
// 假如没有递增,则说明执行了多次
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
// 拿到当前的中间件
let fn = middleware[i]
if (i === middleware.length) fn = next
// 当 fn 为空的时候,就会开始执行 next() 后面部分的代码
if (!fn) return Promise.resolve()
try {
// 执行中间件,留意这两个参数,都是中间件的传参,第一个是上下文,第二个是 next 函数
// 也就是说执行 next 的时候也就是调用 dispatch 函数的时候
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
为什么需要
没有内置任何中间件,比express更轻量
基于es6,默认await/async
有一个统一上下文遍历ctx,方便业务处理,不再需要层层向下传递。
比koa1中的generator更简单的异步,async/await
缺点
EggJs
NestJs
特点
onModuleDestroy()
完成(Promise被resolved或者rejected);一旦完成,将关闭所有连接(调用app.close() 方法).函数式编程是一种强调以函数使用为主的软件开发风格 ,也是一种范式
伪代码
其他的应用场景
编写可以轻松复用和配置的小代码块,就像我们使用npm一样
举个例子,你有一家商店,然后你想给你的优惠顾客10%的折扣:
function discount (price, discount) {
return price * discount
}
// 当一个顾客消费了500元
const price = discount(500, 0.1) // $50
// 从长远看,你的每一笔生意都要计算10%的折扣
const price = discount(1500, 0.1) // $150
const price = discount(2000, 0.1) // $200
const price = discount(50, 0.1) // $5
const price = discount(300, 0.1) // $30
// 将这个函数柯里化,然后我们就不用每次都写那0.1了
function discount (discount) {
return (price) => {
return price * discount
}
}
const tenPercentDiscount = discount(0.1)
// 现在,我们只需用商品价格来计算就可以了:
tenPercentDiscount(500) // $50
// 接下来,有些优惠顾客越来越重要,让我们称为vip顾客,然后我们要给20%的折扣,我们这样来使用柯里化了的discount函数:
const twentyPercentDiscount = discount(0.2)
// 我们为vip顾客使用0.2调用柯里化discount函数来配置了一个新的函数。这个twentyPercentDiscount函数会被用来计算vip顾客的折扣:
twentyPercentDiscount(500) // $100
twentyPercentDiscount(3000) // $600
twentyPercentDiscount(80000) // $16000
避免频繁调用具有相同参数的函数
比如我们有个用来计算体积的函数
function volume (l, w, h) {
return l * w * h
}
// 碰巧你仓库里的所有物品都是100m高。你会看到你不停地用h=100来调用这个函数:
volume(200, 30, 100) // 2003000
volume(32, 45, 100) // 144000
volume(2322, 232, 100) // 53870400
// 为了解决这个问题,你把volume函数柯里化(像我们之前做过的):
function volume (h) {
return (w) => {
return (l) => {
return l * w * h
}
}
}
// 我们能给同类物品定义一个特殊函数:
const hCylinderHeight = volume(100)
hCylinderHeight(200)(30) // 600000
hCylinderHeight(2322)(232) // 53870400
应用场景
自定义注解
V8虚拟机
什么是V8虚拟机?说说你对虚拟机的理解
为什么v8引擎的性能强劲
垃圾回收机制
内存结构
为什么需要堆栈区
堆区
堆栈区(栈区)
栈区和堆区的区别示例
以图书馆作为例子,图书馆就是系统 全部 的内存空间,里面的任何东西都是基于图书馆来分配空间。
每一本书就是一个 对象实体,他们存放在书架上,书架就是 堆。假设图书馆有各种各样的书架,可以满足所有大小的书本,即使书的大小各异,管理员只需要找到合适的位置存放。
为了方便存取书本,图书馆管理员会为每一本书设计一个编号,并记录在一个表格上,编号就是对象实体的 内存地址。
那什么是 对象引用 呢,答案就是读者的每一条借书记录,每一次的借书行为可以理解为对书本的一次引用。借书的记录都会登记在一个表格上,这个表格就是 栈。借书记录仅仅是一段文字,所需要的空间很小,而且大小基本固定,通常情况下用一个本子、一个电子表格、一个数据库,做一个表格就可以满足需求。
书本、编号、借书记录三者的结合,相辅相成,让整个图书馆的对图书的管理更加的灵活、方便和规范。
其实,代码世界中很多的设计原理都是源于我们的生活,又高于生活。设计模式就在我们身边,无处不在,善于观察生活中的事物,从生活中领悟设计模式吧
都用过哪些NodeJs模块
看过哪些模块的源码
lodash
出于好奇,看了isEmpty源码
先判断是否为空
再判断是否像数组
接下在像数组的情况下继续进行或的判断
如果不是数组也不是字符串,则执行nativeKeys方法
Object.prototype.toString.call()原理
和instanceof,type of区别
typeof只能判断基本类型,引用类型都是object
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {
if (L === null)
return false;
if (O === L) // 当 O 显式原型 严格等于 L隐式原型 时,返回true
return true;
L = L.__proto__;
}
}
instanceof判断构造函数的 prototype 属性是否出现在某个实例对象的原型链上
instanceof 的工作原理就是将 s 每一层的 proto 与 F.prototype 做比较
找到相同返回 true
至最后一层仍没有找到返回 false
比如判断 一个字段的类型是否是数组
但instanceof有个缺点,不能用来判断是否为Object,因为最终都指向了Object。
koa-bodyparser
什么是原型和原型链
什么是闭包
函数内部保存的变量不随这个函数调用结束而被销毁就是闭包
闭包的应用场景
闭包的例子
const fun = ()=>{
let num = 0;
return (v)=>{
return num += v;
};
}
const data5 = fun(); //初始化函数fun并得到函数的匿名函数返回值(这里只初始化了一次)
console.log(data5(1)); //1 给匿名函数传参并得到累加的结果
console.log(data5(1)); //2 由于fun函数未重新初始化,且此时num的值为1,所以累加得2
console.log(data5(1)); //3 与上面雷同
什么是深拷贝和浅拷贝
常用的方法有
const deepClone = (obj) => {
if (obj === null || typeof obj !== 'object') {
return obj;
}
const clonedObj = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
};
JavaScript作用域
分为全局和局部作用域
每次代码执行前会先进行"预编译"
比如有下面一段代码
var a = 1;
function foo(){
var b = 2;
function bar(){
var c = 3;
console.log(c + b + a);
}
bar();
}
foo();
在执行之前会先预编译成以下的预编译代码
GO{
a: undefined,
foo: function foo{...}
}
AO1{
b: undefined,
bar: function bar(){...}
}
AO2{
c: undefined
}
然后先执行ao2,给变量c复制,在当前局部作用域可以找到,然后执行b,发现当前局部作用域没有,于是就去外层的作用域查找,找到了,执行。然后执行a,发现发现当前局部作用域没有,于是就去外层的作用域查找,发现也没有,一直找到全局作用域,找到了执行,没找到就当作undefind处理
JavaScript继承,更详细的请查看JS继承原理
es5
原型继承
使用prototype重新赋值,然后再扩展新对象的prototype
示例
// 继承
B.prototype=new A()
// 扩展
B.prototype.splice = ()=>{};
call和apply,更详细的请查看这篇文章
用来改变this的指向,将一个对象作为另一个对象的实现
示例
function myfunc1(){
this.name = 'Lee';
this.myTxt = function(txt) {
console.log( 'i am',txt );
}
}
function myfunc2(){
myfunc1.call(this);
}
var myfunc3 = new myfunc2();
myfunc3.myTxt('Geing'); // i am Geing
console.log (myfunc3.name); // Lee
其中myfun2中没有任何实现,只是将myfun1替换了myfun2的实现
区别
es6
使用class类,通过extends关键字实现继承
类只是一个语法糖,背后其实还是构造函数来实现的
原理
通过_inherits函数实现
先判断当前对象是否是函数,如果不是函数则报错类型异常
然后就是继承父类的原型和原型链,下面是示例
// 继承原型链
B.prototype._proto_ = A.prototype
super关键字用来做什么,更详细的请查看这篇文章
super为什么在构造函数中必须写在前面,否则就不能用this?
比如下面的代码
class Person {
constructor(name) {
this.name = name;
}
}
class PolitePerson extends Person {
constructor(name) {
this.greetColleagues(); //这行代码是无效的,后面告诉你为什么
super(name);
}
greetColleagues() {
alert('Good morning folks!');
}
}
greetColleagues() {
alert('Good morning folks!');
alert('My name is ' + this.name + ', nice to meet you!');
}
但是我们忘记了super()在设置this.name之前先调用了this.greetColleagues()。 所以此时this.name还没有定义,就会出现出现undefind
所以javascript强制在this之前调用super,先完成父类构造函数的构建,再执行子类
super的实现也是使用call方法
bind,call和apply应用场景和区别,更详细的请查看这篇文章
2个等于号和3个等于号区别,更详细的请查看这篇文章
线上cpu高的问题
线上内存高的问题
排查内存泄漏的方案是什么
数组和链表数据结构和区别,更详细的请查看这篇文章
什么是缓冲区
Nodejs最近大版本都更新了什么,更详细的请查看这篇文章
官网维护的更新日志
https://github.com/nodejs/node/tree/main/doc/changelogs
node 10
node 12
node 14
node 16
node 18
js如何判断数据类型,更详细的请查看这篇文章
typeof,instanceof,Object.prototype.toString.call
Object.prototype.toString.call()原理
和instanceof,type of区别
typeof只能判断基本类型,引用类型都是object
instanceof判断构造函数的 prototype 属性是否出现在某个实例对象的原型链上
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {
if (L === null)
return false;
if (O === L) // 当 O 显式原型 严格等于 L隐式原型 时,返回true
return true;
L = L.__proto__;
}
}
instanceof 的工作原理就是将 s 每一层的 proto 与 F.prototype 做比较
找到相同返回 true
至最后一层仍没有找到返回 false
比如判断 一个字段的类型是否是数组
但instanceof有个缺点,不能用来判断是否为Object,因为最终都指向了Object。
node如何接受客户端上传的文件
箭头函数,更详细的请查看这篇文章
单元测试
TypeScript
特点
应用场景
缺点
目前用的什么版本,有什么特性
目前用的4.6版本
特性
super关键字允许写在this后面,以前只能写在前面
枚举类型支持解构
type Action =
| { kind: "NumberContents", payload: number }
| { kind: "StringContents", payload: string };
// 以前结构会报错
const { kind, payload } = action;
if (kind === "NumberContents") {
let num = payload * 2
// ...
}
更智能的jsdoc,通过注释也可以看到类型
其他的问题修复等
经常用哪些类型接口
自定义装饰器和原理
Mongodb
关系型和非关系型区别
Mongodb应用场景
Mongo优缺点
Mongo集群是否了解
都有哪些数据类型
ObjectId如何组成
什么是索引
常用的索引类型
索引的优缺点
mongodb索引数据结构和原理
Mongo查询计划
数据库语句执行过程
数据库cpu高的问题
数据库内存高的问题
数据库流量高的问题
Mongo底层存储结构
性能优化
在上面"NodeJs如何提升性能和代码质量"中有提到
慢查询分析
Redis
和其他传统数据库区别
Redis应用场景都有哪些
数据结构
常用操作语句
为什么Redis快
常用的数据结构有哪些
缓存击穿
缓存穿透
缓存雪崩
过期策略
内存淘汰策略
持久化机制,优缺点
RDB
概念
备份策略
save 3600 1 -> 3600秒内有1个key被修改,则触发RDB
save 300 100 -> 300秒内有100个key被修改,则触发RDB
save 60 10000 -> 60秒内有10000个key被修改,则触发RDB
重写操作是redis主进程fork一个子进程来处理,避免阻塞主进程
redis默认是rbd策略
AOF
概念
开启aof
备份策略
AOF重写
背景
触发命令 bgrewriteaof
触发策略
重写流程
(1)bgrewriteaof触发重写,判断是否存在bgsave或者bgrewriteaof正在执行,存在则等待其执行结束再执行;
(2)主进程fork子进程,防止主进程阻塞无法提供服务;
(3)子进程遍历Redis内存快照中数据写入临时AOF文件,同时会将新的写指令写入aof_buf和aof_rewrite_buf两个重写缓冲区,前者是为了写回旧的AOF文件,后者是为了后续刷新到临时AOF文件中,防止快照内存遍历时新的写入操作丢失;
(4)子进程结束临时AOF文件写入后,通知主进程;
(5)主进程会将上面的aof_rewirte_buf缓冲区中的数据写入到子进程生成的临时AOF文件中;
(6)主进程使用临时AOF文件替换旧AOF文件,完成整个重写过程。
RDB优缺点
AOF优缺点
redis高可用
slaveof no one
)到主从服务器,这需要人工干预。redisAOF问题,更详细的请查看这篇文章
AOF重写导致的redis子进程崩溃,服务重启。解决方案是先优化代码中的大key,延迟重写时机,保证服务可用,比如超过上一重写文件的多少倍后再重写。待redis写入量没那么高时,再调整会重写策略,提前重写即可。主要是大key占用了不必要的空间。
分布式锁的使用和问题,解决方案
什么是缓存重建
redis是单线程还是多线程,6.0以后多线程解决了什么问题
redis事务
MULTI,EXEC,DISCARD,WATCH 四个命令是 Redis 事务的四个基础命令。其中:
MULTI,告诉 Redis 服务器开启一个事务。注意,只是开启,而不是执行 。
EXEC,告诉 Redis 开始执行事务 。
DISCARD,告诉 Redis 取消事务。
WATCH,监视某一个键值对,它的作用是在事务执行之前如果监视的键值被修改,事务会被取消。
应用场景
redis发布订阅
cpu高
内存高
性能优化
慢查询分析
127.0.0.1:6379> slowlog get 1
1) 1) (integer) 7
2) (integer) 1610156232
3) (integer) 24
4) 1) "slowlog"
2) "get"
5) "127.0.0.1:58406"
6) ""
记录的慢查询标号,倒序显示
记录该命令的时间戳
执行命令的耗时,微秒为单位
执行的具体命令
执行该命令客户端的 IP 地址和端口号
微服务的优缺点
什么是restful风格
简称rest,它是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件,它主要用于客户端和服务端交互类的软件。基于这个风格设计的软件可以更简介,更有层次
特性
如何设计
路径设计:数据库设计完毕之后,基本上就可以确定有哪些资源要进行操作,相对应的路径也可以设计出来。
动词设计:也就是针对资源的具体操作类型,有HTTP动词表示,常用的HTTP动词如下:POST、DELETE、PUT、GET
常用的设计模式,应用场景,解决了什么问题
什么是DDD设计模式,应用场景,解决了什么问题
Kakfa
概念
组成
当前使用的是2.8.0版本
消费者获取消息的机制
kafka为什么用zookeeper
消息同步机制
什么是分区
topic
有5个分区,当你一次性向 kafka 中推 1000 条数据时,这 1000 条数据默认会分配到 5 个分区中,其中每个分区存储 200 条数据kafka消息顺序
消费者分区分配策略
kafka如何跟踪消费状态
分区策略
kafka为什么性能强
应用场景
常见面试题
集群
消息队列常见问题
Apollo
私有包使用nexus作为仓储
Docker
K8s
什么是k8s,和docker区别
特点,应用场景和优缺点
k8s的组成
k8s核心概念
k8s的健康检查机制都有哪些
如何实现集群管理
K8S基础
K8S中POD的概念
K8S的命名空间
Deployment无状态应用的部署
Service的类型
Service和pod的关系
labels标签
labels selector 标签选择器
Ingress七层负载均衡
K8S深入
K8S的资源配额、限制
K8S的环境变量
K8S配置管理(ConfigMap、Secret等)
K8S的滚动更新
K8S的健康检查
存活探测器
就绪探测器
启动探测器
K8S的存储管理(PV、PVC等)
kubernetes 入门实践-存储 volume-nfs-pv-pvc
K8S的有状态服务StatefulSet
kubernetes 入门实践-有状态的服务 StateFulSet
K8S的Job、CronJob
批任务处理,其中CronJon可以处理定时任务
使用流水线部署K8S应用
GitHub Actions CI/CD
K8S进阶
K8S集群搭建
kubernetes 入门实践-搭建集群
K8S负载均衡
kubernetes 入门实践-Ingress
其中ingress负责访问service的策略,而ingress-controller负责负载均衡策略
K8S的调度策略(亲和、反亲和等)
Pod亲和性指的是满足特定条件的的Pod对象运行在同一个node上, 而反亲和性调度则要求它们不能运行于同一node
K8S的存储(本地存储、分布式存储等)
kubernetes 入门实践-存储-hostpath
kubernetes 入门实践-搭建nfs服务器
K8S中的负载均衡(ClusterIP、NodePort、L4、L7负载均衡)
K8S的弹性伸缩(HPA)
使用Helm Chart发布应用
K8S的日志、监控、告警体系
Istio服务网格与流量治理
K8S技术演进路线(AKS、ASK、ACI、FaaS等)
你们线上运行多少机器,都是什么配置,为什么这样配置
QPS达到多少
你们登录加密token怎么做的
单点登录如何做的
说说对sass行业的理解
什么是线程安全和不安全
什么是GRPC
该篇是对标了几个大厂常问和一些偏八股文的问题
TCP和UDP区别
HTTP和HTTPS区别
HTTP长连接和短链接区别
TCP长连接和HTTP长连接区别
TCP如何保证可靠性传输
浏览器输入网址后都发生了什么
HTTP1.0,1.1,2.0的区别
HTTP三次握手和四次挥手过程
为什么是三次握手而不是两次
进程,线程,协程区别
进程是具有⼀定独⽴功能的程序关于某个数据集合上的⼀次运⾏活动,进程是系统进⾏资源分配和调度的⼀个独⽴单位。每个进程都有⾃⼰的独⽴
内存空间,不同进程通过进程间通信来通信。由于进程⽐较重量,占据独⽴的内存,所以上下⽂进程间的切换开销(栈、寄存器、虚拟内存、⽂件
句柄等)⽐较⼤,但相对⽐较稳定安全。
线程是进程的⼀个实体,是CPU调度和分派的基本单位,它是⽐进程更⼩的能独⽴运⾏的基本单位.线程⾃⼰基本上不拥有系统资源,只拥有⼀点在运
⾏中必不可少的资源(如程序计数器,⼀组寄存器和栈),但是它可与同属⼀个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共
享内存,上下⽂切换很快,资源开销较少,但相⽐进程不够稳定容易丢失数据。
协程是⼀种⽤户态的轻量级线程,协程的调度完全由⽤户控制。协程拥有⾃⼰的寄存器上下⽂和栈。协程调度切换时,将寄存器上下⽂和栈保存到
其他地⽅,在切回来的时候,恢复先前保存的寄存器上下⽂和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下
⽂的切换⾮常快。
时间和空间复杂度
serverless
微信云开发技术架构
Serverless入门