diff --git a/docs/.vuepress/config/sidebar.ts b/docs/.vuepress/config/sidebar.ts index 79c6451e6..128a5edc1 100644 --- a/docs/.vuepress/config/sidebar.ts +++ b/docs/.vuepress/config/sidebar.ts @@ -17,6 +17,7 @@ import {KoaSidebar} from "../../manuscripts/server-end/framework/koa/koa.sidebar import {ExpressSidebar} from "../../manuscripts/server-end/framework/express/express.sidebar"; import {DesignPatternsSidebar} from "../../manuscripts/server-end/design-patterns/designPatterns.sidebar"; import {SequelizeOrmSidebar} from "../../manuscripts/server-end/orm/sequelize/sequelizeOrm.sidebar"; +import {TypeormSidebar} from "../../manuscripts/server-end/orm/typeorm/typeorm.sidebar"; export default { "/manuscripts/front-end": FrontEndSidebar, @@ -32,6 +33,7 @@ export default { "/manuscripts/server-end/base": BaseSidebar, "/manuscripts/server-end/node-learn": NodeLearnSidebar, "/manuscripts/server-end/orm/sequelize": SequelizeOrmSidebar, + "/manuscripts/server-end/orm/typeorm": TypeormSidebar, "/manuscripts/develop-skill": DevelopSkillSidebar, // "/manuscripts/solo-algorithm": soloAlgorithmSidebar, "/manuscripts/read-books": ReadBooksSidebar, diff --git a/docs/manuscripts/battle-interview/images/event-loop-info.png b/docs/manuscripts/battle-interview/images/event-loop-info.png new file mode 100644 index 000000000..1ee0ad639 Binary files /dev/null and b/docs/manuscripts/battle-interview/images/event-loop-info.png differ diff --git a/docs/manuscripts/battle-interview/images/event-loop.png b/docs/manuscripts/battle-interview/images/event-loop.png new file mode 100644 index 000000000..a7299c65a Binary files /dev/null and b/docs/manuscripts/battle-interview/images/event-loop.png differ diff --git "a/docs/manuscripts/battle-interview/problems/Node\351\235\242\350\257\225.md" "b/docs/manuscripts/battle-interview/problems/Node\351\235\242\350\257\225.md" index 2a128dff9..18af0f33c 100644 --- "a/docs/manuscripts/battle-interview/problems/Node\351\235\242\350\257\225.md" +++ "b/docs/manuscripts/battle-interview/problems/Node\351\235\242\350\257\225.md" @@ -7,109 +7,113 @@ permalink: /manuscripts/battle-interview/node.html **同步和异步关注的是消息通信机制。** -- 同步:在发起一个调用后,在没有得到结果前,该调用不返回,知道调用返回,才往下执行,也就是说调用者等待被调用方返回结果。 +- `同步`:在发起一个调用后,在没有得到结果前,该调用不返回,直到调用返回,才往下执行,**调用者等待被调用方返回结果**。 +- `异步`:在发起一个调用后,调用就直接返回,不等待结果,继续往下执行,而执行的结果是由被调用方通过状态、通知等方式告知调用方,流程不阻塞 -- 异步:在发起一个调用后,调用就直接返回,不等待结果,继续往下执行,而执行的结果是由被调用方通过状态、通知等方式告知调用方,典型的异步编程模型比如 Node.js +**阻塞和非阻塞,关注的是在等待结果时,线程的状态。** -**阻塞和非阻塞,关注的是在等待结果时,线程的状态**。 +- `阻塞`:在等待调用结果时,线程挂起了,不往下执行 +- `非阻塞`:在等待调用结果时,当前线程继续往下执行 -- 阻塞:在等待调用结果时,线程挂起了,不往下执行 -- 非阻塞:与上面相反,当前线程继续往下执行 +![](../images/event-loop-info.png) -Nodejs事件循环中细分为这六个阶段,依次如下: +`Nodejs`事件循环中细分为这六个阶段,依次如下: -- Timers: 定时器 Interval Timoout 回调事件,将依次执行定时器回调函数 -- Pending: 一些系统级回调将会在此阶段执行 -- Idle,prepare: 此阶段"仅供内部使用" -- Poll: IO回调函数,这个阶段较为重要也复杂些, -- Check: 执行 setImmediate() 的回调 -- Close: 执行 socket 的 close 事件回调 +- `Timers`: 定时器 `Interval TimeOut` 回调事件,将依次执行定时器回调函数 +- `I/O回调`: 一些系统级回调将会在此阶段执行 +- Idle,prepare: 此阶段"仅供内部使用" +- `Poll`: IO回调函数,这个阶段较为重要、也复杂些 +- `Check`: 执行 `setImmediate()` 的回调 +- `Close`: 执行 `socket` 的 `close` 事件回调 -**关于 process.nextTick ,这个事件的优先级要高于其他微队列的事件,所以对于需要立即执行的回调事件可以通过该方法将事件放置到微队列的起始位置。** +**关于`process.nextTick`,这个事件的优先级要高于其他微队列的事件, +对于需要立即执行的回调事件可以通过该方法将事件放置到微队列的起始位置。** 例如: ```js Promise.resolve().then(function () { - console.log('promise1') + console.log('promise1') }) process.nextTick(() => { + console.log('nextTick') + process.nextTick(() => { console.log('nextTick') process.nextTick(() => { + console.log('nextTick') + process.nextTick(() => { console.log('nextTick') - process.nextTick(() => { - console.log('nextTick') - process.nextTick(() => { - console.log('nextTick') - }) - }) + }) }) + }) }) // 执行结果 // nextTick=>nextTick=>nextTick=>timer1=>promise1 - ``` +![](../images/event-loop.png) + 开发需要关系的阶段 与我们开发相关的三个阶段分别是 Timers Poll Check -- Timers :执行定时器的回调,但注意,**在 node 11 前,连续的几个定时器回调会连续的执行,而不是像浏览器那样,执行完一个宏任务立即执行微任务。** -- Check :这个阶段执行 setImmediate() 的回调,这个事件只在 nodejs 中存在。 -- Poll :上面两个阶段的触发,其实是在 poll 阶段触发的,poll 阶段的执行顺序是这样的。 +- `Timers` :执行定时器的回调,注意: + **在 Node 11 前,连续的几个定时器回调会连续地执行,而不是像浏览器那样,执行完一个宏任务立即执行微任务。** +- `Check` :这个阶段执行 setImmediate() 的回调,这个事件只在 nodejs 中存在。 +- `Poll` :上面两个阶段的触发,其实是在 poll 阶段触发的,poll 阶段的执行顺序是这样的。 ->先查看 check 阶段是否有事件,有的话执行 ->执行完 check 阶段后,检查 poll 阶段的队列是否有事件,若有则执行 ->poll 的队列执行完成后,执行 check 阶段的事件 +> 先查看 check 阶段是否有事件,有的话执行 +> 执行完 check 阶段后,检查 poll 阶段的队列是否有事件,若有则执行 +> poll 的队列执行完成后,执行 check 阶段的事件 -在 nodejs 中也是有宏任务和微任务的, nodejs 中除了多了 process.nextTick ,宏任务、微任务的分类都是一致的。 +在 `Node.js` 中也是有宏任务和微任务的, `Node.js` 中除了多了 `process.nextTick` ,宏任务、微任务的分类都是一致的。 ## Node 跟 Chrome 有什么区别? -   -- 架构一样,都是基于事件驱动的异步架构! +- 架构一样,都是基于事件驱动的异步架构! - 浏览器主要是通过事件驱动来服务页面交互。 - -- node 主要是通过事件驱动来服务 I/O - -- node 没有HTML,WebKit和显卡等等的UI技术支持 +- `Node.js` 主要是通过事件驱动来服务 I/O +- `Node.js` 没有HTML、WebKit和显卡等等的UI技术支持 ## Cookies如何防范XSS攻击? -XSS(Cross-Site Scripting,跨站脚本攻击)是指攻击者在返回的HTML中插入JavaScript脚本。为了减轻这些攻击,需要在HTTP头部配置set-cookie: - -- HttpOnly - 这个属性可以防止cross-site scripting,因为它会禁止Javascript脚本访问cookie。 +XSS(Cross-Site Scripting,跨站脚本攻击)是指攻击者在返回的HTML中插入JavaScript脚本。 +为了减轻这些攻击,需要在HTTP头部配置`set-cookie`参数,例如: -- secure - 这个属性告诉浏览器仅在请求为HTTPS时发送cookie。 +- `HttpOnly` : 可以防止cross-site scripting,因为它会禁止Javascript脚本访问cookie。 +- `secure` : 表明浏览器仅在请求为`HTTPS`时发送`Cookie`信息。 -结果应该是这样的: +结果应该是这样的: -Set-Cookie: sid=; HttpOnly. 使用Express的话,cookie-session默认配置好了。 +`Set-Cookie: sid=; HttpOnly` 使用`Express`框架的话,`cookie-session`默认配置好了。 ## 常考问题 ### 1. 图片懒加载是如何实现的?(字节跳动) -将图片的所有src均指向一个小图片或者设为空 真正的src放在data-src中,监听滚动事件,用户浏览到该图片时 将src真实值从data-src中拷贝到src中去 +将图片的所有src均指向一个小图片或者设为空 真正的src放在data-src中,监听滚动事件,用户浏览到该图片时 +将src真实值从data-src中拷贝到src中去 > 可以结合cdn去做,一张图片在服务端存两种大小 -用户频繁滚动页面---->节流(允许用户在n秒内触发一次懒加载) +用户频繁滚动页面 ----> 节流(允许用户在n秒内触发一次懒加载) -对应的经常一起出现的有---->防抖(触发的时间与上次触发的时间只差不到1s,都不执行--->间隔时间要大于1s再执行) [参考](https://www.jianshu.com/p/dd0e90a2c440) +对应的经常一起出现的有---->防抖(触发的时间与上次触发的时间只差不到1s,都不执行---> +间隔时间要大于1s再执行) [参考](https://www.jianshu.com/p/dd0e90a2c440) 先说,防抖怎么防? > 防抖函数可以通过闭包 + 定时器实现。 ```js - -// 防抖函数 -function debounce(func, wait) { +/** + * 防抖函数 + */ +function debounce(func, wait) { let timeout = null - return function() { + return function () { // 确定 终止那个setTimeOut()方法 clearTimeout(timeout) - - timeout = setTimeout(function() { + + timeout = setTimeout(function () { func() // 高级【推荐】 func.apply(this, arguments) @@ -118,20 +122,21 @@ function debounce(func, wait) { } // 实际执行的方法 -function getData() { - // ... ajax +function getData() { + // ... ajax } + // 调用 documentElement.addEventListener('keyup', debounce(getData, 1000)) ``` > setTimeout()方法的返回值是一个唯一的数值,这个数值有什么用呢? -> -> 如果你想要终止setTimeout()方法的执行,那就必须使用 clearTimeout()方法来终止,而使用这个方法的时候,系统必须知道你到底要终止的是哪一个setTimeout()方法(因为你可能同时调用了好几个 setTimeout()方法),这样clearTimeout()方法就需要一个参数,这个参数就是setTimeout()方法的返回值(数值),用这个数值来唯一确定结束哪一个setTimeout()方法。 - -说白了,就是利用全局变量做标记,然后延时处理 +> 如果你想要终止setTimeout()方法的执行,那就必须使用 clearTimeout() +> 方法来终止,而使用这个方法的时候,系统必须知道你到底要终止的是哪一个setTimeout()方法(因为你可能同时调用了好几个 +> setTimeout()方法),这样clearTimeout()方法就需要一个参数,这个参数就是setTimeout()方法的返回值(数值) +> ,用这个数值来唯一确定结束哪一个setTimeout()方法。 -参考lodash的实现: +说白了,就是利用全局变量做标记,然后延时处理。[参考lodash的实现](https://www.lodashjs.com/docs/lodash.debounce) 再来说说节流(一段时间内只触发一次请求加载),两种实现方式: @@ -139,11 +144,13 @@ documentElement.addEventListener('keyup', debounce(getData, 1000)) - 时间差 ```js -// 基于定时器实现 +/** + * 基于定时器实现 + */ function throttle(func, wait) { let timeout - return function() { + return function () { if (!timeout) { timeout = setTimeout(() => { timeout = null @@ -152,12 +159,15 @@ function throttle(func, wait) { } } } -// 基于时间差实现 + +/** + * 基于时间差实现 + */ function throttle(func, wait) { let previous = 0 // 函数闭包 - return function() { + return function () { let now = +new Date() // 剩余时间=理论等待时间-实际等待时间 let remain = wait - (now - previous) @@ -170,27 +180,25 @@ function throttle(func, wait) { } ``` -看完后,我觉得有点单例模式的感觉... - -参考lodash节流函数: +看完后,我觉得有点单例模式的感觉... [参考lodash节流函数](https://www.lodashjs.com/docs/lodash.throttle) 闭包有三个特性: -- 1.函数嵌套函数 -- 2.函数内部可以引用外部的参数和变量 -- 3.参数和变量不会被垃圾回收机制回收 +- 函数嵌套函数 +- 函数内部可以引用外部的参数和变量 +- 参数和变量不会被垃圾回收机制回收 闭包的好处: -- 1.希望一个变量长期驻扎在内存中 -- 2.避免全局变量的污染 -- 3.私有成员的存在 +- 希望一个变量长期驻扎在内存中 +- 避免全局变量的污染 +- 私有成员的存在 参考: ### 2.了解TS吗 -- 有清晰的函数参数和接口属性,便于修改 +- 有清晰的函数参数和接口类型,便于修改 - 会有静态检查 - 会生成API文档 - 配和编译器,提示充分 @@ -198,87 +206,94 @@ function throttle(func, wait) { ### 3.简单介绍下koa -Koa本质上是调用一系列的中间件,来处理对应的请求,并决定是否传递到下一个中间件去处理 +`Koa`本质上是调用一系列的中间件,来处理对应的请求,并决定是否传递到下一个中间件去处理 -> compose 是一个工具函数,Koa.js 的中间件通过这个工具函数组合后,按 app.use() 的顺序同步执行,也就是形成了 洋葱圈 式的调用。 参考: +> compose 是一个工具函数,Koa.js 的中间件通过这个工具函数组合后,按 app.use() 的顺序同步执行,也就是形成了 洋葱圈 式的调用。 +> 参考: -- 初始化koa实例后,我们会用use方法来加载中间件(middleware),**会有一个数组来存储中间件**,use调用顺序会决定中间件的执行顺序。 +- 初始化`Koa`实例后,我们会用use方法来加载中间件(middleware),**会有一个数组来存储中间件**,use调用顺序会决定中间件的执行顺序。 - 每个中间件都是一个函数(不是函数将报错),接收两个参数,第一个是ctx上下文对象,另一个是next函数(由koa-compose定义) - 在建立好http服务器后,会调用**koa-compose模块**对middleware中间件数组进行处理。 -> 原理就是:会从middleware数组中取第一个函数开始执行,中间件函数中调用next方法就会去取下一个中间件函数继续执行。每个中间件函数执行完毕后都会返回一个promise对象。(ps:调用next方法并不是表示当前中间件函数执行完毕了,调用next之后仍可以继续执行其他代码) +> 原理就是:会从middleware数组中取第一个函数开始执行,中间件函数中调用next方法就会去取下一个中间件函数继续执行。 +> 每个中间件函数执行完毕后都会返回一个promise对象。 +(ps:调用next方法并不是表示当前中间件函数执行完毕了,调用next之后仍可以继续执行其他代码) 参考: -Compose 是一种基于 Promise 的流程控制方式,可以通过这种方式对异步流程同步化,解决之前的嵌套回调和 Promise 链式耦合。 +`Compose` 是一种基于 `Promise` 的流程控制方式,可以通过这种方式对异步流程同步化,解决之前的嵌套回调和 `Promise` 链式耦合。 ### 4.node多线程怎么管理 -**Node.js 只支持单线程**。但是可以开启多进程充分利用多核 CPU, -单个 Node.js 实例运行在单个线程中。 为了充分利用多核系统,有时需要启用一组 Node.js 进程去处理负载任务。可以使用node.js原生的cluster (集群)模块创建共享服务器端口的子进程,cluster 模块支持两种分发连接的方法。 +**`Node.js` 只支持单线程**。但是可以开启多进程充分利用多核 `CPU`, +单个 `Node.js` 实例运行在单个线程中。 为了充分利用多核系统,有时需要启用一组 `Node.js` +进程去处理负载任务。可以使用`Node.js`原生的`cluster`(集群)模块创建共享服务器端口的子进程,`cluster` 模块支持两种分发连接的方法。 -第一种方法(也是除 Windows 外所有平台的默认方法)是循环法,由主进程负责监听端口,接收新连接后再将连接循环分发给工作进程,在分发中使用了一些内置技巧防止工作进程任务过载。 +- 第一种方法:(也是除 Windows 外所有平台的默认方法)循环法,由主进程负责监听端口,接收新连接后再将连接循环分发给工作进程, + 在分发中使用了一些内置技巧防止工作进程任务过载。 -第二种方法是,主进程创建监听 socket 后发送给感兴趣的工作进程,由工作进程负责直接接收连接。 +- 第二种方法是:主进程创建监听 socket 后发送给感兴趣的工作进程,由工作进程负责直接接收连接。 -理论上第二种方法应该是效率最佳的。 但在实际情况下,由于操作系统调度机制的难以捉摸,会使分发变得不稳定。 可能会出现八个进程中有两个分担了 70% 的负载。 -**潜在问题: 因为每个进程的内存都是独立的,为了在多进程中共享数据,原来可能存储在内存中的数据,例如 token 等不能再存储在内存中,应该存储在 redis 等缓存中,以便保证不同的进程都可以访问该数据;** +理论上第二种方法应该是效率最佳的。 但在实际情况下,由于操作系统调度机制的难以捉摸,会使分发变得不稳定。 可能会出现八个进程中有两个分担了 +70% 的负载。 -对应到egg的多线程模型 +**潜在问题: 因为每个进程的内存都是独立的,为了在多进程中共享数据,原来可能存储在内存中的数据,例如 token 等不能再存储在内存中,应该存储在 +redis 等缓存中,以便保证不同的进程都可以访问该数据;** ### 4.node路由是什么 -node中的路由由自己的框架处理,通过分析url路径分发到相应控制器中,一个路由对应的是一个或多个负责请求调用的js文件,里面包括业务逻辑(拦截,捕获,处理) +`Node.js`中的路由由自己的框架处理,通过分析url路径分发到相应控制器中,一个路由对应的是一个或多个负责请求调用的js文件,里面包括业务逻辑(拦截、捕获、处理) -路由是一组映射关系,分析URL将访问的内容映射到实际的action或者controller上。 +路由是一组映射关系,分析URL将访问的内容映射到实际的`Action`或者`Controller`上。 ### 5.Node的Event Loop 【重要】 -### 6.介绍一下node中间件 +### 6.介绍一下Node中间件 + +中间件就是请求`req`和响应`res`之间的一个函数应用。 -中间件就是请求req和响应res之间的一个应用,请求浏览器向服务器发送一个请求后, -服务器直接通过request定位属性的方式得到通过request携带过去的数据,就是用户输入的数据和浏览器本身的数据信息, -这中间就一定有一个函数将这些数据分类做了处理,最后让request对象调用使用, -这个处理函数就是我们所所得中间插件。 +请求浏览器向服务器发送一个请求后,服务器直接通过request定位属性的方式得到通过request携带过去的数据,就是用户输入的数据和浏览器本身的数据信息, +这中间就一定有一个函数将这些数据分类做了处理,最后让request对象调用使用,这个处理函数就是我们所所得中间插件。 -比如生活中的租客和房主,中间需要一个中介来搭桥,这个中介就类似于中间件。一般来说,中间件用来封装底层细节,组织基础功能,分离基础设施和业务逻辑 +例如,生活中的租客和房主,中间需要一个中介来搭桥,这个中介就类似于中间件。一般来说,中间件用来封装底层细节,组织基础功能,分离基础设施和业务逻辑 ### 7.数组去重的方法有哪些 -- 遍历数组,indexof过滤,再push -- reduce方法 -- filter方法 -- 利用集合Set元素不能重合,定义集合,解构后成新数组 +- 遍历数组,`indexof()`方法过滤,再`push()`方法 +- `reduce()`方法 +- `filter()`方法 +- 利用集合`Set`元素不能重合,定义集合、解构后成新数组 ```js -let arr = [1,1,2,3,4,5,5,6]; +let arr = [1, 1, 2, 3, 4, 5, 5, 6]; // 1. indexOf -function newArr(array){ - //一个新的数组 - var ar = []; - //遍历当前数组 - for(var i = 0; i < array.length; i++){ - //如果临时数组里没有当前数组的当前值,则把当前值push到新数组里面 - // 判断是否包含,返回角标 - if (ar.indexOf(array[i]) == -1){ - ar.push(array[i]) - }; - } - return ar; +function newArr(array) { + //一个新的数组 + var ar = []; + //遍历当前数组 + for (var i = 0; i < array.length; i++) { + //如果临时数组里没有当前数组的当前值,则把当前值push到新数组里面 + // 判断是否包含,返回角标 + if (ar.indexOf(array[i]) == -1) { + ar.push(array[i]) + } + } + return ar; } + let arr2 = newArr(arr); // 2. reduce 参考:https://www.runoob.com/jsref/jsref-reduce.html -let arr2 = arr.reduce(function(ar,cur) { +let arr2 = arr.reduce(function (ar, cur) { // includes数组是否包含 true|false - if(!ar.includes(cur)) { + if (!ar.includes(cur)) { ar.push(cur) } return ar -},[]) +}, []) // 3. filter -let arr2 = arr.filter(function(item,index) { +let arr2 = arr.filter(function (item, index) { // indexOf() 方法可返回某个指定的 字符串值 在字符串中首次出现的位置 return arr.indexOf(item) === index }) @@ -286,18 +301,16 @@ let arr2 = arr.filter(function(item,index) { let arr2 = [...new Set(arr)] ``` -参考: - ### 8.判断类型有哪些方法 -- typeof:可以判断出`string`,`number`,`boolean`,`undefined`,`symbol` +- `typeof`:可以判断出`string`,`number`,`boolean`,`undefined`,`symbol` 但判断 typeof(null) 时值为 `object`; 判断数组和对象时值均为 `object` -- instanceof:原理是 构造函数的 prototype 属性是否出现在对象的原型链中的任何位置 -- Object.prototype.toString.call():常用于判断浏览器内置对象,对于所有基本的数据类型都能进行判断 -- Array.isArray():只能用于判断是否为数组 -- constructor +- `instanceof`:原理是 构造函数的 prototype 属性是否出现在对象的原型链中的任何位置 +- `Object.prototype.toString.call()`:常用于判断浏览器内置对象,对于所有基本的数据类型都能进行判断 +- `Array.isArray()`:只能用于判断是否为数组 +- `constructor`构造函数 -## 参考资料 +## 参考 - - diff --git "a/docs/manuscripts/battle-interview/problems/\344\271\220\350\247\202\351\224\201\345\222\214\346\202\262\350\247\202\351\224\201.md" "b/docs/manuscripts/battle-interview/problems/\344\271\220\350\247\202\351\224\201\345\222\214\346\202\262\350\247\202\351\224\201.md" index e2f15369a..ed2f36686 100644 --- "a/docs/manuscripts/battle-interview/problems/\344\271\220\350\247\202\351\224\201\345\222\214\346\202\262\350\247\202\351\224\201.md" +++ "b/docs/manuscripts/battle-interview/problems/\344\271\220\350\247\202\351\224\201\345\222\214\346\202\262\350\247\202\351\224\201.md" @@ -5,27 +5,21 @@ permalink: /manuscripts/battle-interview/lock.html # 乐观锁和悲观锁 -```mindmap -root(锁) - (乐观锁) - (悲观锁) -``` - ## 乐观锁的实现 - 版本号机制 - CAS算法 -都是采用预期值和原来的值进行比较,相同则允许操作 +都是采用预期值和原来的值进行比较,相同则允许操作。 ### 什么场景下需要使用锁? 在多节点部署或者多线程执行时,同一个时间可能有多个线程更新相同数据,产生冲突,这就是并发问题。这样的情况下会出现以下问题: -- 更新丢失(分两类):一个事务更新数据后,被另一个更新数据的事务覆盖。 -- 脏读:一个事务读取另一个事物未提交的数据,即为脏读。 -- 幻读:B事务读取了两次数据,在这两次的读取过程中A事务添加了数据,B事务的这两次读取出来的集合不一样 -- 不可重复读(Nonrepeatable Read):B事务读取了两次数据,在这两次的读取过程中A事务修改了数据,B事务的这两次读取出来的数据不一样 +- `更新丢失`(分两类):一个事务更新数据后,被另一个更新数据的事务覆盖。 +- `脏读`:一个事务读取另一个事物未提交的数据,即为脏读。 +- `幻读`:B事务读取了两次数据,在这两次的读取过程中A事务添加了数据,B事务的这两次读取出来的集合不一样 +- `不可重复读`:B事务读取了两次数据,在这两次的读取过程中A事务修改了数据,B事务的这两次读取出来的数据不一样 针对并发引入并发控制机制,即加锁。 @@ -37,46 +31,55 @@ root(锁) ### 版本号控制 -使用version版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可以采取**丢弃和再次尝试**的策略。 +使用`version`版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可以采取**丢弃和再次尝试**的策略。 ### CAS算法 -compare and swap (比较进行交换),CAS操作包含三个操作数: +`Compare And Swap` : 先比较再进行交换,`CAS`算法包含三个操作数: -- 内存位置(V) -- 预期原值(A) -- 新值(B) +- `内存位置(V)` +- `预期原值(A)` +- `新值(B)` -如果`内存位置的值V`与`预期原值A`相匹配,那么处理器会将该位置值更新为`新值B`。否则,处理器不做任何操作,会循环比较直到相等,整个比较赋值操作是一个`原子操作`。 +如果`内存位置的值V`与`预期原值A`相匹配,那么处理器会将该位置值更新为`新值B` +。否则,处理器不做任何操作,会循环比较直到相等,整个比较赋值操作是一个`原子操作`。 -有一点像在缓存中,添加标记去顶当前数据版本号,与预期原值进行比较 +**有一点像在缓存中,添加标记去顶当前数据版本号,与预期原值进行比较。** CAS缺点: -(1)循环时间开销大:当内存地址V与预期值B不相等时会一直循环比较直到相等; +- `循环时间开销大`:当内存地址V与预期值B不相等时会一直循环比较直到相等; + +- 只能保证一个共享变量的原子操作 -(2)只能保证一个共享变量的原子操作;【jvm知识未理解】 +如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,就能说明它的值没有被其他线程修改过吗? -(3)如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那么我们就能说明它的值没有被其他线程修改过吗?很明显不是,因为在这段时间内它的值可能被改为其他值,然后又被改回A,那CAS操作就会认为它从来没被改过,这个问题就被称为 CAS 操作的“ABA” 问题; +> 很明显不是,因为在这段时间内它的值可能被改为其他值, +> 然后又被改回A,那CAS操作就会认为它从来没被改过,这个问题就被称为`CAS算法`操作的`ABA`问题; ABA问题------> 互斥同步比原子类更加高效 ### 数据库隔离级别 -参考: +> 参考: **读未提交(Read Uncommitted)** -在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别是最低的隔离级别,虽然拥有超高的并发处理能力及很低的系统开销,但很少用于实际应用。因为采用这种隔离级别只能防止第一类更新丢失问题,不能解决脏读,不可重复读及幻读问题。 +在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别是最低的隔离级别,虽然拥有超高的并发处理能力及很低的系统开销,但很少用于实际应用。 +因为采用这种隔离级别只能防止第一类更新丢失问题,不能解决脏读,不可重复读及幻读问题。 **读已提交(Read Committed)** -这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别可以防止脏读问题,但会出现不可重复读及幻读问题。 +这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。 +这种隔离级别可以防止脏读问题,但会出现不可重复读及幻读问题。 **可重复读(Repeatable Read)** -这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。这种隔离级别可以防止除幻读外的其他问题。 +这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。 +**这种隔离级别可以防止除幻读外的其他问题。** **可串行化(Serializable)** -这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读、第二类更新丢失问题。在这个级别,可以解决上面提到的所有并发问题,但可能导致大量的超时现象和锁竞争,通常数据库不会用这个隔离级别,我们需要其他的机制来解决这些问题:乐观锁和悲观锁 +这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读、第二类更新丢失问题。 +在这个级别,可以解决上面提到的所有并发问题,但可能导致大量的超时现象和锁竞争。 +**通常数据库不会用这个隔离级别,需要其他的机制来解决这些问题:乐观锁和悲观锁** diff --git "a/docs/manuscripts/battle-interview/problems/\346\216\245\345\217\243\345\271\202\347\255\211.md" "b/docs/manuscripts/battle-interview/problems/\346\216\245\345\217\243\345\271\202\347\255\211.md" index adf0adf0f..ae7db9cb8 100644 --- "a/docs/manuscripts/battle-interview/problems/\346\216\245\345\217\243\345\271\202\347\255\211.md" +++ "b/docs/manuscripts/battle-interview/problems/\346\216\245\345\217\243\345\271\202\347\255\211.md" @@ -5,13 +5,13 @@ permalink: /manuscripts/battle-interview/interface-idempotent.html # 接口幂等 -> 参考: +为了防止上述情况的发生,我们需要提供一个防护措施,对于同一笔支付信息如果我其中某一次处理成功了, +我虽然又接收到了消息,但是这时我不处理了,即保证接口的**幂等性** -为了防止上述情况的发生,我们需要提供一个防护措施,对于同一笔支付信息如果我其中某一次处理成功了,我虽然又接收到了消息,但是这时我不处理了,即保证接口的 **幂等性** +**幂等**是一个数学与计算机学概念,常见于抽象代数中。在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。 +幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。 -> 幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。 -> -> 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的,更复杂的操作幂等保证是利用唯一交易号(流水号)实现. +例如,`setTrue()`函数就是一个幂等函数,无论多次执行,其结果都是一样的,更复杂的操作幂等保证是利用唯一交易号(流水号)实现。 幂等性的核心特点:**任意多次执行所产生的影响均与一次执行的影响相同** @@ -23,58 +23,59 @@ permalink: /manuscripts/battle-interview/interface-idempotent.html ## 幂等性实现方式 -前端处理 +前端处理: -> 对于和web端交互的接口,我们可以在前端拦截一部分,例如防止表单重复提交,按钮置灰、隐藏、不可点击等方式。 +对于和web端交互的接口,我们可以在前端拦截一部分,例如防止表单重复提交,按钮置灰、隐藏、不可点击等方式。 后端处理(重要): -- Token机制:前端重复连续多次点击的情况,请求前先获取token -- 数据库去重表:利用唯一索引---->系统容错性不高,瘫痪 -- Redis实现: 过期唯一key -- **状态机**:假设当前状态是已支付,这时候如果支付接口又接收到了支付请求,则会抛异常或拒绝此次处理。 - ---- - -参考: +- `Token机制`:前端重复连续多次点击的情况,请求前先获取token +- `数据库去重表`:利用唯一索引---->系统容错性不高,瘫痪 +- `Redis实现`: 过期唯一key +- `状态机`:假设当前状态是已支付,如果支付接口又接收到了支付请求,则会抛异常或拒绝此次处理。 ## 什么情况下需要保证接口的幂等性 -在增删改查4个操作中,尤为注意就是增加或者修改, - -- A: 查询操作,查询对于结果是不会有改变的,查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select是天然的幂等操作 - -- B: 删除操作,删除一次和多次删除都是把数据删除。注意:可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个,在不考虑返回结果的情况下,删除操作也是具有幂等性的 +在增删改查4个操作中,特别注意增加操作和修改修改: -- C: 更新操作,修改在大多场景下结果一样,但是如果是增量修改是需要保证幂等性的,如下例子: - - 把表中id为XXX的记录的A字段值设置为1,这种操作不管执行多少次都是幂等的 - - 把表中id为XXX的记录的A字段值增加1,这种操作就不是幂等的 - -- D: 新增操作,增加在重复提交的场景下会出现幂等性问题,如以上的支付问题 +- `查询操作`:查询对于结果是不会有改变的,查询一次和查询多次,在数据不变的情况下,查询结果是一样的。`select`操作是天然的幂等操作 +- `删除操作`:删除一次和多次删除都是把数据删除。注意:可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条 , + 返回结果多个,在不考虑返回结果的情况下,删除操作也是具有幂等性的 +- `更新操作`:修改在大多场景下结果一样,但是如果是增量修改是需要保证幂等性的,例如: + - 把表中`id`为`XXX`的记录的A字段值设置为1,这种操作不管执行多少次都是幂等的 + - 把表中`id`为`XXX`的记录的A字段值增加1,这种操作就不是幂等的 +- `新增操作`:增加在重复提交的场景下会出现幂等性问题,例如:支付时重复请求问题 ## 为什么需要幂等 -概念: - -> 幂等性是系统的接口对外一种承诺(而不是实现),承诺只要调用接口成功, 外部多次调用对系统的影响是一致的。这里的多次调用强调是指接口一致,参数一致的情况。 +**幂等性是系统的接口对外一种承诺(而不是实现),承诺只要调用接口成功,外部多次调用(接口一致、参数一致)对系统的影响是一致的。** -幂等是为了保证重试机制不会带来数据重复,数据不一致等异常情况。 -不管在单机还是分布式系统中,都存在因为网络抖动无法收到成功应答;重试补偿机制;用户无意识点击发起多次请求(业务意义上其实是同一条数据记录)等情况带来的相同参数多次调用相同接口的情况。 +幂等是为了保证重试机制不会带来数据重复,数据不一致等异常情况。不管在单机还是分布式系统中,都存在因为: -## 如何做到幂等 +- 网络抖动无法收到成功应答; +- 重试补偿机机 +- 用户无意识点击多次发起请求(业务意义上其实是同一条数据记录) -1、有些接口本身就是幂等的,例如查询接口,当然这是在查询数据没有做变更的情况下;删除操作一般会带有待删除数据的唯一标识,最终结果都是删除,也是幂等的。 +等情况带来的相同参数、多次调用相同接口的情况。 -2、对于创建新数据的情况,采用唯一业务单号,业务上的唯一条件约束。 +## 如何做到幂等 -3、在执行更新操作的时候,可以先查再决定是否更新,但这因为是非原子操作,所以在分布式系统中会存在问题。 +- 有些接口本身就是幂等的,例如: + - 查询接口,在查询数据没有做变更的情况下,查询操作是幂等的 + - 删除操作一般会带有待删除数据的唯一标识,最终结果都是删除,也是幂等的 -- 多版本控制,更新时候更新版本小于更新版本的数据 update t_xxx set name=#{newName} where version<#{version} +- 对于创建新数据的情况,采用唯一业务单号,业务上的唯一条件约束。 -- 状态机控制,增加状态字段,状态可能是有先后顺序(例如订单状态总是从待付款到已付款),也可能各个状态互相可以转化(例如与第三方同步数据,同步状态可能从更新待同步直接就变成删除待同步了) +- 在执行更新操作的时候,可以先查再决定是否更新,但这因为是非原子操作,所以在分布式系统中会存在问题。 + - `多版本控制`,更新操作时增加版本条件进行更新,例如: `update t_xxx set name=? where version< ?` + - `状态机控制`,增加状态字段,状态可能是有先后顺序,例如:订单状态总是从待付款到已付款。 + 也可能各个状态互相可以转化,例如:与第三方同步数据,同步状态可能从更新待同步直接就变成删除待同步了 + - `定义去重表`,先在去重表中插入,成功执行后续操作 + - `悲观锁`,`select for update` -- 定义去重表,先在去重表中插入,成功执行后续操作 +对外提供接口为了支持幂等调用,接口有两个字段必须传: -- 悲观锁,select for update +- 来源source字段 +- 来源方序列号seq字段 -4、对外提供服务的接口如何做到幂等,对外提供接口为了支持幂等调用,接口有两个字段必须传,一个是来源source,一个是来源方序列号seq,这个两个字段在己方系统里面作为唯一标识符,后续在己方系统中使用以上的方法来保证幂等。 +这个两个字段在己方系统里面作为`唯一标识符`,后续在己方系统中使用以上的方法来保证幂等。 diff --git a/docs/manuscripts/server-end/orm/sequelize/Reame.md b/docs/manuscripts/server-end/orm/sequelize/Reame.md index b064756a0..daa02567a 100644 --- a/docs/manuscripts/server-end/orm/sequelize/Reame.md +++ b/docs/manuscripts/server-end/orm/sequelize/Reame.md @@ -5,15 +5,27 @@ permalink: /manuscripts/server-end/orm/sequelize # SequelizeORM +![](./images/sequelize-logo.png) + +`Sequelize` 是适用于 `Oracle`、`Postgres`、`MySQL`、`MariaDB`、`SQLite` 和 `SQL Server` +等的现代数据库,支持 `TypeScript` 和 `Node.js`的`ORM`框架。具有可靠的事务支持、关系、急切和延迟加载、读取复制等功能。 + +## 基础教程 + - 快速入门 - 数据库连接 -- 模型 +- 表模型 +- 模型实例 - 数据类型 -- 实例 -- 索引 -- 事物操作 +- 查询操作 +- OP运算符 - 钩子函数 -- 关联关系 +- 索引 +- 事物 + +## 最佳实践 + +- CURD操作 ## 参考 diff --git a/docs/manuscripts/server-end/orm/sequelize/images/sequelize-logo.png b/docs/manuscripts/server-end/orm/sequelize/images/sequelize-logo.png new file mode 100644 index 000000000..2c112b154 Binary files /dev/null and b/docs/manuscripts/server-end/orm/sequelize/images/sequelize-logo.png differ diff --git a/docs/manuscripts/server-end/orm/typeorm/Readme.md b/docs/manuscripts/server-end/orm/typeorm/Readme.md index 5aa6e3769..88e2e3030 100644 --- a/docs/manuscripts/server-end/orm/typeorm/Readme.md +++ b/docs/manuscripts/server-end/orm/typeorm/Readme.md @@ -4,3 +4,18 @@ permalink: /manuscripts/server-end/typeorm --- # TypeORM + +![](./images/typeorm-logo.png) + +`TypeORM` 是一个`ORM`框架,可以运行在 `NodeJS`、`Browser`、`Cordova`、`PhoneGap`、`Ionic`、`React Native`、`Expo` 和 `Electron` +平台上,可以与 `TypeScript` 和 `JavaScript`一起使用。 + +`TypeOrm`目标是始终支持最新的 `JavaScript` 特性并提供额外的特性以帮助开发任何使用数据库的应用程序。 + +不同于现有的所有其他 `JavaScript ORM`框架,`TypeOrm`可以以最高效的方式编写高质量的、松耦合的、可扩展的、可维护的应用程序。 + +## 基础教程 + +## 最佳实践 + +## 参考 diff --git a/docs/manuscripts/server-end/orm/typeorm/images/typeorm-logo.png b/docs/manuscripts/server-end/orm/typeorm/images/typeorm-logo.png new file mode 100644 index 000000000..9a34d21f3 Binary files /dev/null and b/docs/manuscripts/server-end/orm/typeorm/images/typeorm-logo.png differ diff --git "a/docs/manuscripts/server-end/orm/typeorm/tutorial/\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/manuscripts/server-end/orm/typeorm/tutorial/\345\277\253\351\200\237\345\205\245\351\227\250.md" new file mode 100644 index 000000000..02e722431 --- /dev/null +++ "b/docs/manuscripts/server-end/orm/typeorm/tutorial/\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -0,0 +1,6 @@ +--- +title: 快速入门 +permalink: /manuscripts/server-end/orm/typeorm/quick-start.html +--- + +# 快速入门 diff --git a/docs/manuscripts/server-end/orm/typeorm/typeorm.sidebar.ts b/docs/manuscripts/server-end/orm/typeorm/typeorm.sidebar.ts new file mode 100644 index 000000000..b6cce1db8 --- /dev/null +++ b/docs/manuscripts/server-end/orm/typeorm/typeorm.sidebar.ts @@ -0,0 +1,12 @@ +export const TypeormSidebar = [ + { + text: '基础教程', + prefix: 'tutorial', + children: [ + { + text: '快速入门', + link: '快速入门.md' + } + ] + } +] diff --git "a/docs/manuscripts/server-end/orm/typeorm/\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/manuscripts/server-end/orm/typeorm/\345\277\253\351\200\237\345\205\245\351\227\250.md" deleted file mode 100644 index 1c8978d40..000000000 --- "a/docs/manuscripts/server-end/orm/typeorm/\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: 快速入门 -permalink: /manuscripts/server-end/typeorm/quick-start.html ---- - -# 快速入门