基础知识部分
箭头(lambda)函数和普通函数的区别?
1. 语法:
Lambda函数使用箭头符号(=>)来定义,而普通函数使用function关键字定义。
// Lambda函数
const multiply = (a, b) => a * b;
// 普通函数
function multiply(a, b) {
return a * b;
}
2. this的指向:
在lambda函数中,this的指向是在定义函数时确定的,而在普通函数中,this的指向是在运行时确定的。
3. 参数:
Lambda函数只能使用一个表达式作为函数体,而普通函数可以使用多个语句作为函数体。此外,Lambda函数通常用于简单的回调函数或函数式编程,而普通函数则用于处理更复杂的逻辑。
4. 返回值:
Lambda函数自动返回表达式的结果,而普通函数需要使用return语句明确返回值。
怎么监管和控制异步并发数量?
1. 限制并发数
通过限制并发数,我们可以确保一次只有一定数量的异步操作在运行。这可以避免系统资源的过度使用,并提高系统的性能。以下是一个实现限制并发数的例子:
javascriptCopy code
const urls = ['http://example.com', 'http://google.com', 'http://yahoo.com'];
const limit = 2; // 最多同时处理2个请求
async function fetchUrl(url) {
console.log(`开始获取:${url}`);
await new Promise((resolve) => setTimeout(resolve, 2000)); // 模拟异步操作
console.log(`完成获取:${url}`);
}
async function run() {
const tasks = [];
for (const url of urls) {
tasks.push(fetchUrl(url));
}
await Promise.all(
tasks.slice(0, limit).map(async (task, index) => {
await task;
tasks[limit + index] && tasks[limit + index]();
})
);
}
run();
在这个例子中,我们定义了一个 urls 数组,其中包含三个要获取的 URL。我们将 limit 设置为 2,以限制并发数。在 run 函数中,我们将所有的异步操作添加到一个任务数组中,并使用 Promise.all 方法来等待所有任务完成。同时,我们使用 slice 方法从任务数组中选择前 limit 个任务,并将它们作为 Promise 对象传递给 Promise.all 方法。我们使用 map 方法遍历这些 Promise 对象,并在每个 Promise 对象完成后立即调用数组中下一个任务。
2. 使用第三方库
在Node.js中,有许多第三方库可以帮助我们管理异步操作的并发数量。其中一个常用的库是 async。这个库提供了多个函数,例如 parallelLimit、series 和 waterfall,可以帮助我们管理并发数量。以下是一个使用 async 库的例子:
javascriptCopy code
const async = require('async');
const urls = ['http://example.com', 'http://google.com', 'http://yahoo.com'];
const limit = 2; // 最多同时处理2个请求
function fetchUrl(url, callback) {
console.log(`开始获取:${url}`);
setTimeout(() => {
console.log(`完成获取:${url}`);
callback(null);
}, 2000); // 模拟异步操作
}
async.eachLimit(
urls,
limit,
fetchUrl,
(err) => {
if (err) {
console.error('出错了:', err);
} else {
console.log('所有请求完成!');
}
}
);
在这个例子中,我们使用 async.eachLimit 方法来并发地执行异步操作。我们将 urls 数组作为第一个参数,将 limit 设置为 2,以限制并发数。我们还提供了一个 fetchUrl 函数作为异步操作的执行函数。在异步操作完成后,我们调用回调函数来通知 async 库。
有那些内存泄露的场景? 怎么防范?
在Node.js中,以下是一些可能导致内存泄漏的场景:
1. 不正确的定时器处理
定时器是Node.js中常用的异步处理方式,但是如果不正确地使用定时器可能会导致内存泄漏。例如,在使用 setInterval 时,如果没有正确地清除定时器,它会一直运行,导致内存泄漏。可以使用 clearInterval 或者 clearTimeout 方法来清除定时器,以防止内存泄漏。
2. 事件循环的错误使用
Node.js的事件循环是非常重要的部分,但是如果不正确地使用事件循环可能会导致内存泄漏。例如,如果在事件处理程序中没有正确地注销事件监听器,它们将会继续存在于内存中,即使它们已经不再需要。可以使用 removeListener 或 removeAllListeners 方法来注销事件监听器,以防止内存泄漏。
3. 内存泄漏的第三方库
在Node.js中,使用第三方库也可能导致内存泄漏。这些库可能会持有对内存中对象的引用,导致这些对象不能被垃圾回收。因此,需要使用可靠的第三方库,并遵循它们的最佳实践,以防止内存泄漏。
4. 长时间运行的进程
在长时间运行的进程中,可能会发生内存泄漏。这是因为在运行过程中创建的对象不会被垃圾回收,导致内存占用不断增加。为了避免这种情况,可以定期重启进程,或者使用专门的工具来监视和管理进程。
为了防止内存泄漏,可以采取以下措施:
1. 使用正确的编码实践
使用正确的编码实践可以避免内存泄漏。例如,在事件处理程序中正确地注销事件监听器,以及在定时器中正确地清除定时器等。
2. 使用内存泄漏检测工具
Node.js提供了一些内存泄漏检测工具,例如 heapdump 和 memwatch。这些工具可以帮助检测并诊断内存泄漏,并提供相应的解决方案。
3. 使用合适的垃圾回收机制
Node.js使用V8引擎作为其默认的垃圾回收机制,但是V8也提供了不同的垃圾回收机制。根据不同的场景,可以使用不同的垃圾回收机制,以提高性能并防止内存泄漏。
4. 定期重启
要从100个不同网站获取数据,怎么快速并组装
在Node.js中,可以使用异步编程的方式来并发地获取100个不同网页的数据,并在数据全部获取完毕后进行组装。下面是一些可以使用的技术:
1. 使用Promise
可以使用Promise来异步获取每个网页的数据。Promise可以让我们更加优雅地处理异步操作,并支持链式调用。使用Promise.all()方法可以等待所有Promise完成后再进行组装,从而减少等待时间。
javascriptCopy code
const promises = [];
for (let i = 1; i <= 100; i++) {
const url = `https://example.com/page${i}`;
const promise = fetchData(url);
promises.push(promise);
}
Promise.all(promises).then((results) => {
// 数据获取完毕,进行组装
const data = results.join('');
console.log(data);
}).catch((error) => {
console.error(error);
});
function fetchData(url) {
return new Promise((resolve, reject) => {
// 异步获取数据并调用resolve方法
// 如果发生错误,调用reject方法
});
}
2. 使用async/await
可以使用async/await来简化Promise的使用,让异步操作更加易读和易维护。
javascriptCopy code
async function fetchData() {
const results = [];
for (let i = 1; i <= 100; i++) {
const url = `https://example.com/page${i}`;
const data = await fetch(url);
results.push(data);
}
// 数据获取完毕,进行组装
const data = results.join('');
console.log(data);
}
function fetch(url) {
return new Promise((resolve, reject) => {
// 异步获取数据并调用resolve方法
// 如果发生错误,调用reject方法
});
}
fetchData().catch((error) => {
console.error(error);
});
3. 使用并发请求库
可以使用第三方的并发请求库,例如 request-promise 或 axios 来方便地异步获取数据。这些库可以并发地请求多个网页,从而加快数据获取的速度。
const Promise = require('bluebird');
const rp = require('request-promise');
const urls = [];
for (let i = 1; i <= 100; i++) {
urls.push(`https://example.com/page${i}`);
}
Promise.map(urls, (url) => {
return rp(url);
}).then((results) => {
// 数据获取完毕,进行组装
const data = results.join('');
console.log(data);
}).catch((error) => {
console.error(error);
});
使用这些技术可以加快数据获取的速度,并在最短时间内完成数据获取和组装。但是,需要注意的是同时发起大量请求可能会给目标网站造成负载压力,因此需要适度调整并发量。
怎么增加多核利用率?
Node.js 采用单线程模型,因此在单个 Node.js 进程内只能利用一个 CPU 核心。但是,Node.js 有一些技术可以增大多线程的利用率,包括以下几个方面:
1. Cluster 模块
Node.js 内置的 Cluster 模块可以让多个进程共享同一个端口,从而达到利用多个 CPU 核心的目的。Cluster 模块通过创建子进程的方式来实现并发处理,每个子进程都可以独立处理请求。当有新的请求到来时,Master 进程会将请求分配给其中的一个子进程进行处理。这样可以充分利用 CPU 核心的性能,提高系统的吞吐量。
使用 Cluster 模块需要注意的是,需要在代码中进行一些特殊的处理,例如在 Master 进程中创建子进程,以及在子进程中监听端口和处理请求等。Cluster 模块可以通过 Node.js 内置的 API 来使用,例如 cluster.fork() 可以创建子进程,cluster.isMaster 可以判断当前进程是否为 Master 进程。
2. Worker Threads 模块
Node.js 10.5.0 版本引入了 Worker Threads 模块,可以让 Node.js 在同一个进程内创建多个线程,从而达到利用多个 CPU 核心的目的。Worker Threads 模块与 Cluster 模块类似,但是更加灵活,可以在同一个进程内创建多个线程,也可以在不同的进程中创建线程。Worker Threads 模块提供了一些 API,例如 worker_threads.Worker() 可以创建一个新的 Worker 线程,worker_threads.parentPort 可以获取主线程和子线程之间的通信端口。
使用 Worker Threads 模块需要注意的是,线程之间的通信需要通过消息传递的方式进行,因此需要对消息的格式和处理进行规划和管理。
Worker Threads的使用例子
Worker Threads是Node.js中的多线程解决方案,可以在单个进程中创建多个线程来执行任务,从而提高应用的性能和吞吐量。
以下是使用Worker Threads的基本步骤:
- 在Node.js中引入Worker Threads模块:可以使用require('worker_threads')引入Worker Threads模块。
- 创建Worker Threads:可以使用new Worker()构造函数创建一个Worker Thread,需要传递一个JavaScript文件的路径或代码字符串作为参数。
- 监听Worker Threads的消息事件:可以使用worker.on('message', callback)方法监听Worker Threads发送的消息事件,callback参数接收Worker Thread发送的消息。
- 发送消息给Worker Threads:可以使用worker.postMessage()方法向Worker Thread发送消息。
- 在Worker Threads中处理任务:在Worker Threads中可以编写JavaScript代码处理任务,可以使用workerData属性获取主线程传递的数据,可以使用parentPort.postMessage()方法向主线程发送消息。
以下是一个简单的使用Worker Threads的例子,该例子创建一个Worker Thread来计算斐波那契数列的第n项:
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
function fibonacci(n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
if (isMainThread) {
const worker = new Worker(__filename, {
workerData: 40 // 传递斐波那契数列的项数
});
worker.on('message', (result) => {
console.log(`fibonacci(${workerData}) = ${result}`);
});
} else {
const result = fibonacci(workerData);
parentPort.postMessage(result);
}
在该例子中,主线程创建一个Worker Thread来计算斐波那契数列的第n项,传递了40作为参数。Worker Thread在接收到主线程的消息后,使用fibonacci函数计算斐波那契数列的第n项,并将结果通过parentPort.postMessage()方法发送给主线程,主线程在接收到Worker Thread发送的消息后,输出斐波那契数列的第n项的结果。
需要注意的是,Worker Threads的使用需要考虑到线程之间的通信、数据同步等问题,不当的使用可能会导致应用出现异常或不稳定。
Worker Thread使用的注意事项
- 线程之间的通信:Worker Threads中的线程之间是通过消息进行通信的,因此需要注意消息的格式和内容。在发送和接收消息时,需要确保消息的可靠性和一致性,避免出现数据丢失或不一致的情况。
- 共享内存:在Worker Threads中,线程之间是通过消息传递数据的,因此需要注意共享内存的问题。如果需要共享内存,可以使用SharedArrayBuffer和Atomics API来实现,但需要注意并发访问的问题。
- 资源管理:Worker Threads中的线程是独立的,因此需要注意资源的管理。在使用Worker Threads时,需要确保线程的数量不会超过系统的限制,同时需要注意线程的生命周期和资源的释放。
- 错误处理:在Worker Threads中,线程之间是相互独立的,因此需要注意错误的处理。如果线程出现错误,需要及时捕获并处理,避免影响其他线程和应用的正常运行。
为了规避这些问题,可以遵循以下几点:
- 使用合适的消息格式和内容,确保消息的可靠性和一致性。
- 避免过度使用共享内存,使用SharedArrayBuffer和Atomics API来实现共享内存,并避免并发访问的问题。
- 管理好线程的数量和生命周期,确保资源的充分利用和释放。
- 统一错误处理方式,捕获并处理线程中出现的错误,避免影响其他线程和应用的正常运行。
总之,使用Worker Threads需要注意线程之间的通信、共享内存、资源管理和错误处理等问题,遵循最佳实践可以提高应用的性能和稳定性。
3. 使用第三方库
除了使用内置的 Cluster 和 Worker Threads 模块,也可以使用第三方的库来实现多线程处理。例如 pm2 可以启动多个 Node.js 进程,并通过负载均衡来分配请求,从而达到利用多个 CPU 核心的目的。
总之,Node.js 采用单线程模型,但是可以通过 Cluster 模块、Worker Threads 模块、第三方库等技术来增大多线程的利用率,提高系统的吞吐量和并发处理能力。选择适合自己项目的技术需要根据具体情况进行评估和选择。
ES6新特性
- let 和 const 声明:引入了块级作用域,使用 let 和 const 声明变量和常量,可以避免变量提升和全局变量污染。
- 箭头函数:简化了函数的定义方式,可以省略 function 关键字和 return 语句,同时还绑定了父级作用域的 this 值。
- 类和继承:引入了类的概念,使 JavaScript 变得更像传统的面向对象语言,同时也实现了继承和封装等面向对象的特性。
- 模板字符串:可以使用反引号(`)定义多行字符串和插值表达式,使字符串的拼接和格式化更加方便。
- 解构赋值:可以从数组和对象中解构出需要的值,并将它们赋给变量,使代码更加简洁和易读。
- 默认参数值:可以为函数的参数设置默认值,如果调用函数时没有传递该参数,将使用默认值。
- Rest 参数和扩展运算符:Rest 参数用于捕获函数的多余参数,扩展运算符可以将数组或对象展开为多个参数,使函数的参数处理更加灵活。
- Promise 对象:用于处理异步操作,可以更方便地处理回调函数地繁琐问题,避免了回调地嵌套和地狱。
- async/await:基于 Promise 实现的语法糖,用于简化异步编程,使异步代码看起来像同步代码一样简洁易懂。
- Modules:可以使用 import 和 export 语句来导入和导出模块,避免了全局变量的污染和命名冲突。
以上是 ES6 的一些主要新特性,它们大大改善了 JavaScript 的语言特性和开发体验,使得 JavaScript 的应用范围和功能更加广泛和强大。
ES7以后各版本的新特性
ES7:
- Array.prototype.includes:用于判断数组是否包含指定的元素。
- 指数运算符:用于计算一个数的指数。
ES8:
- async/await:异步函数的改进,可以使用 async/await 更加方便地处理异步操作。
- Object.entries/Object.values:可以分别获取一个对象的所有键值对和所有值。
- String.prototype.padStart/String.prototype.padEnd:用于填充字符串,使其达到指定长度。
- SharedArrayBuffer/Atomics:用于实现共享内存和原子操作,方便地处理多线程问题。
ES9:
- 异步迭代器:用于处理异步迭代操作,实现了 Symbol.asyncIterator 和 for-await-of 语法。
- Promise.prototype.finally:可以为 Promise 添加一个 finally 方法,用于在 Promise 结束时执行一些清理操作。
- Rest/Spread 属性:用于将对象的剩余属性或分散属性分别放入变量中。
ES10:
- Array.prototype.flat/flatMap:用于将多维数组扁平化或对其进行映射操作。
- String.prototype.trimStart/String.prototype.trimEnd:用于去除字符串的首尾空格。
- Object.fromEntries:用于将键值对列表转换为对象。
- Symbol.prototype.description:可以获取 Symbol 对象的描述字符串。
ES11:
- 可选链操作符(Optional Chaining):用于简化深度嵌套属性的访问,防止因为属性不存在而出现错误。
- 空值合并操作符(Nullish Coalescing):用于将一个空值(undefined 或 null)替换为默认值。
- Promise.allSettled:与 Promise.all 类似,但是不会在有一个 Promise reject 时就终止。
ES12:
- 可选的 catch 绑定:可以省略 catch 中的参数,使得代码更加简洁。
- Promise.any:类似于 Promise.race,但是会在有一个 Promise resolve 时就返回,不会等待其他 Promise。
- WeakRef/WeakRef.prototype.deref:用于实现弱引用,可以避免因为循环引用而导致的内存泄漏。
- String.prototype.replaceAll:用于替换所有匹配的字符串。
ES13:
- 可迭代对象扩展:通过 Symbol.iterator 和 Symbol.asyncIterator 可以自定义迭代器和异步迭代器。
- Array.prototype.at:可以通过索引访问数组中的元素,可以避免访问越界和负数索引的问题。
- Promise.allSettled:支持 for-await-of,可以更加方便地遍历异步操作的结果。
ES14:
- Record 和 Tuple 类型:用于表示键值对和元组类型。
- 递归元素的部分解构:可以在解构对象或数组时递归地解构元素。
- Promise.try:用于在 Promise 中执行函数,并捕获可能出现的异常。
- 管道操作符(Pipeline Operator):用于将一个表达式的结果作为下一个表达式的参数。
需要注意的是,ES14 的特性可能还会发生变化,具体的特性列表和发布日期可能会有所调整。
HTTP协议各版本的新特性
- HTTP/0.9:HTTP的最初版本,仅支持GET请求,并且不支持头部信息和响应码等功能。
- HTTP/1.0:支持多种请求方法、HTTP头部信息、状态码等特性。但是每次请求需要建立一次TCP连接,无法复用连接,会导致性能瓶颈。
- HTTP/1.1:引入了持久连接(Keep-Alive),允许复用TCP连接,减少连接建立的开销。还支持管线化(Pipeline)技术,可以在一个连接上发送多个请求,提高传输效率。此外还引入了缓存技术、分块传输编码等特性,使HTTP协议更加灵活和高效。
- HTTP/2:基于SPDY协议,引入了多路复用(Multiplexing)技术,可以在一个TCP连接上同时传输多个请求和响应,避免了HTTP/1.x中的队头阻塞问题。还引入了服务器推送(Server Push)技术,可以在一个请求中推送多个资源,提高页面加载速度。
- HTTP/3:基于QUIC协议,采用UDP协议传输数据,具有更好的传输效率和安全性。引入了0-RTT(零往返时间)技术,可以在连接建立之前发送数据,减少了连接建立的时间。还支持HTTP/2中的所有特性,并提供了更好的容错机制。
需要注意的是,HTTP/1.1和HTTP/2之间的差异很大,HTTP/2并不是HTTP/1.1的增强版,而是一种全新的协议。因此在使用HTTP/2时需要注意一些细节问题。
Nodejs各大版本的新特性
Node.js 0.10:添加了Stream、Buffer、Cluster、C++模块、加密模块等。
Node.js 0.12:新增了支持ECMAScript Internationalization API、更好的Windows支持、更快的HTTP解析、TLS性能优化等特性。
Node.js 4.0:采用了V8 4.5引擎,支持ECMAScript 6特性,包括类、模块、箭头函数等,同时加入了更好的支持IPv6、TLS、HTTP、DNS等的新特性。
Node.js 6.0:采用了V8 5.0引擎,支持更多ECMAScript 6特性,包括Promise、Symbol、Object.assign()等,同时新增了支持Intl、更好的Buffer API等特性。
Node.js 8.0:采用了V8 5.8引擎,支持更多ECMAScript 6特性,包括async/await、Object.values()、Object.entries()等,同时新增了更好的N-API支持、更好的调试和诊断工具等特性。
Node.js 10.0:采用了V8 6.6引擎,支持更多ECMAScript 6特性,包括Rest/Spread属性、Promise.prototype.finally()等,同时新增了更好的安全性、更好的性能、更好的异步API等特性。
Node.js 12.0:采用了V8 7.4引擎,支持更多ECMAScript 6特性,包括Array.prototype.flat()、Array.prototype.flatMap()等,同时新增了更好的默认值支持、更好的安全性、更好的性能、更好的调试工具等特性。
Node.js 14.0:采用了V8 8.1引擎,支持更多ECMAScript 6特性,包括可选链操作符、Nullish合并操作符等,同时新增了更好的模块支持、更好的性能、更好的调试和诊断工具等特性。
Node.js 16.0:采用了V8 9.0引擎,支持更多ECMAScript 6特性,包括String.prototype.replaceAll()、Promise.any()等,同时新增了更好的安全性、更好的性能、更好的调试和诊断工具等特性。
Nodejs 12以后版本的重要特性
Node.js 12:
- HTTP/2支持:HTTP/2协议比HTTP/1.1协议更快、更高效、更安全,Node.js 12新增对HTTP/2协议的支持,让Node.js更加适合构建高性能的Web应用。
- 改进的Esm(ECMAScript Modules)支持:ECMAScript Modules是ECMAScript 6规范中新增的模块机制,Node.js 12中对Esm的支持有了重大的改进,让Node.js更加适合构建现代化的Web应用。
- 长期支持(LTS):Node.js 12是一个长期支持(LTS)版本,将在2022年4月到期,为企业和组织提供稳定的维护和支持。
Node.js 14:
- 改进的性能:Node.js 14通过采用了V8引擎的新特性,提高了性能和吞吐量,让Node.js应用更快、更高效。
- 改进的Worker Threads支持:Worker Threads是Node.js中的多线程解决方案,Node.js 14进一步改进了Worker Threads的支持,让Node.js更加适合高并发场景。
- 改进的N-API支持:N-API是Node.js的Native API,Node.js 14进一步改进了N-API的支持,提供了更好的跨平台兼容性和稳定性。
Node.js 16:
- 更好的性能:Node.js 16采用了最新的V8引擎,进一步提高了性能和吞吐量,让Node.js应用更快、更高效。
- 改进的ECMAScript支持:Node.js 16对ECMAScript 6特性的支持更加完善,包括可选链操作符、Nullish合并操作符等。
深拷贝
function deepClone(target) {
let result, targetType = checkedType(target);
if(targetType === 'Object') {
result = {};
} else if(targetType === 'Array') {
result = [];
} else {
return target;
}
for (let i in target) {
let value = target[i];
if(checkedType(value) ==='Object' || checkedType(value) ==='Array') {
result[i] = deepClone(value);
} else {
result[i] = value;
}
}
return result;
}
排序算法
/**
* @param {number[]} nums
* @return {number[]}
*/
function swap (arr, i, j) {
const temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
// 冒泡
var sortArray = function (arr) {
const len = arr.length
for (let i = 0; i < len; i++) {
for (let j = len - 1; j > i; j--) {
if (arr[j] < arr[j - 1]) {
swap(arr, j, j - 1)
}
}
}
return arr
}
// 选择
var sortArray = function (arr) {
const len = arr.length
for (let i = 0; i < len; i++) {
let minIdx = i, min = arr[i]
for (let j = i + 1; j < len; j++) {
if (arr[j] < min) {
min = arr[j]
minIdx = j
}
}
swap(arr, i, minIdx)
}
return arr
}
// 插入
var sortArray = function (arr) {
const len = arr.length
for (let i = 1; i < len; i++) {
let prev = i - 1
const temp = arr[i]
while(prev >=0 && arr[prev] > temp) {
arr[prev + 1] = arr[prev]
prev -= 1
}
arr[prev + 1] = temp
}
return arr
}
// 快速
var sortArray = function (arr) {
if (arr.length < 2) return arr
const len = arr.length
const left = [], right = []
const mid = arr[0]
for (let i = 1; i < len; i++) {
if (arr[i] < mid) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return sortArray(left).concat(mid, sortArray(right))
}
// 归并
var sortArray = function (arr) {
if (arr.length < 2) return arr
const mid = Math.floor(arr.length / 2)
const left = arr.slice(0, mid)
const right = arr.slice(mid)
return merge(sortArray(left), sortArray(right))
}
function merge (left, right) {
const res = []
const len = left.length + right.length
for (i = 0, li = 0, ri = 0; i < len; i++) {
if (li === left.length) {
res.push(right[ri])
ri++
} else if (ri === right.length) {
res.push(left[li])
li++
} else if (left[li] < right[ri]) {
res.push(left[li])
li++
} else {
res.push(right[ri])
ri++
}
}
return res
}
// 希尔
var sortArray = function (arr) {
const len = arr.length
let gap = Math.floor(len / 2)
while (gap >= 1) {
for (let i = gap; i < len; i++) {
let prev = i - gap
const temp = arr[i]
while(prev >= 0 && arr[prev] > temp) {
arr[prev + gap] = arr[prev]
prev -= gap
}
arr[prev + gap] = temp
}
gap = Math.floor(gap / 2)
}
return arr
}
数据库
Schema优化
- 使用更小的数据类型
- 避免存储NULL
- ID最好选择整数类型,避免散列性
- 字符串变换多的可以使用varchar类型,减少内存碎片,IP可以用32无符号整数存储
- 使用分区表:对于特定类型的表,如历史数据表、日志表等,使用分区表可以提高查询效率,同时避免数据过多导致的性能问题。
- 使用约束:使用约束可以保证数据的一致性和完整性,避免数据错误和异常情况。
- 避免过多的关联查询:关联查询可以获取更多的数据,但也会增加查询复杂度和查询时间,需要权衡利弊。
- 定期维护Schema:定期清理无用数据、优化表结构、更新索引等可以提高查询性能,避免数据异常问题。
索引优化
- 索引选择重复值越少的越好
- 多列单独索引可以通过explain查看是否使用了索引优化,通常不如组合索引有效率
- 根据运行频率调整索引列顺序
- 使用Percona等工具查找冗余的索引
- 前期数据量不大的时候,可以避免过多使用索引,影响写入性能
查询优化
- 复杂查询可能要重构查询方式,多行查询请求合并成一个顺序请求,一个复杂请求拆分成多个子查询增加缓存利用率和减少锁竞争
- count函数,尽量返回近似值,不返回精确值
- 联接查询时,确保on或using子句中有索引
- 优化分页时
- 偏移量很大,最好用覆盖索引扫描,而不是查询所有行
select file_id, description FROM sakila.file ORDER BY title LIMIT 50, 5; #替换为 SELECT film.file_id, film.description FROM sakila.film INNER JOIN (SELECT film_id FROM sakila.film ORDER BY title LIMIT 50, 5) AS lim USING(film_id);- where条件列有索引,可以预先计算出边界值
SELECT file_id, description FROM sakila.file WHERE position BETWEEN 50 AND 54 ORDER BY position;- 分页可以查询最后的自增id来代替偏移值
SELECT * FROM sakila.rental WHERE rental_id < 16030 ORDER BY rental_id DESC LIMIT 20; - 在limit语句上加上sql_calc_found_rows来去掉获取满足的行数。这样会使MySQL查询能及时终止limit查询,而不是扫描所有满足的行。假如每页20条,那么查询21条,只显示20条,有21条才显示下一行。
- 避免使用OR:尽量使用IN或者UNION等方式替代OR,避免全表扫描和性能问题。
- 避免使用LIKE %xxx%:尽量使用LIKE xxx%或者xxx%来代替LIKE %xxx%,避免全表扫描和性能问题。
- 分页查询优化:尽量使用LIMIT来限制返回数据的条数,避免全表扫描和性能问题。
- 适当使用缓存:适当使用缓存可以减少对数据库的查询,提高查询速度。
Redis
Redis 支持哪些数据类型?
Redis 支持的数据类型包括:
- 字符串 (string)
- 散列 (hash)
- 列表 (list)
- 集合 (set)
- 有序集合 (sorted set)
Redis 的持久化方式有哪些?
Redis 支持两种持久化方式:RDB 和 AOF。
- RDB 持久化方式会在指定时间间隔内将内存中的数据快照存储到磁盘上。
- AOF 持久化方式则会将写命令追加到 AOF 文件中,以此记录数据变更操作。
Redis 如何处理并发请求?
Redis 采用单线程模型,所有的请求都在一个线程中依次执行,通过使用异步 I/O 和多路复用等技术,可以处理高并发请求。
Redis 的主从复制模式是怎样的?
Redis 的主从复制模式中,主服务器会将写操作同步到从服务器上,从服务器会在主服务器的基础上进行读操作,以此实现数据备份和负载均衡等功能。
Redis 分布式锁是怎么实现的?
Redis 分布式锁可以通过 setnx 命令来实现,即在 Redis 中创建一个键值对,如果键不存在则创建成功,否则创建失败。在获取锁时,客户端通过 setnx 命令尝试创建一个键值对,如果创建成功则获取到锁,否则获取失败。为了避免死锁,还需要在释放锁时进行判断。
Redis 的过期策略是怎样的?
Redis 使用惰性删除和定期删除相结合的方式进行过期键的删除,即在数据访问时检查键是否过期,如果过期则立即删除,同时也会定期扫描键空间并删除过期键。
Redis 事务是怎么实现的?
Redis 事务可以通过 multi/exec 命令来实现。在事务中,客户端发送的命令不会立即执行,而是被缓存到一个队列中,在执行 exec 命令时一次性执行队列中的所有命令,以此实现事务。
Redis如何实现Pub/Sub模式?
Redis Pub/Sub模式是一种消息发布和订阅模型,它包括两种角色:发布者和订阅者。发布者将消息发送到一个频道(channel),所有订阅该频道的订阅者都会收到该消息。
实现步骤如下:
1)发布者使用PUBLISH命令向指定频道发布消息。
2)订阅者使用SUBSCRIBE命令向指定频道订阅消息。
3)发布者发布消息后,Redis将消息发送给所有已订阅该频道的订阅者。
Redis如何应对缓存穿透、缓存雪崩、缓存击穿等问题?
缓存穿透问题:指访问不存在的数据,导致请求一直穿透到数据库,造成数据库压力过大。
解决方案:
1)在缓存层设置布隆过滤器,过滤掉不存在的数据。
2)使用缓存空对象或缓存穿透标记(例如null或-1),避免重复查询数据库。
3)热点数据预加载,提前将热点数据加载到缓存中。
缓存雪崩问题:指缓存层所有数据在同一时间失效,导致所有请求都落到数据库上,造成数据库压力过大。
解决方案:
1)设置缓存数据过期时间随机化,避免同时失效。
2)设置缓存数据时,使用不同的过期时间。
3)使用多级缓存,将缓存数据分散到不同的缓存节点上。
缓存击穿问题:指高并发访问一个不存在的数据,导致所有请求都落到数据库上,造成数据库压力过大。
解决方案:
1)在缓存层设置布隆过滤器,过滤掉不存在的数据。
2)使用互斥锁或分布式锁,避免同时多个请求同时访问数据库。
3)使用缓存空对象或缓存穿透标记(例如null或-1),避免重复查询数据库。
Redis的主从同步机制是什么?
Redis的主从同步机制是通过Redis复制功能实现的。主节点会将修改操作记录到内存中的AOF文件或者RDB文件中,然后发送给从节点,从节点接收到主节点发送的AOF文件或RDB文件,将其恢复到自己的内存中,保持与主节点的数据一致性。
Redis的主从同步有两种模式:全量复制和增量复制。全量复制是将主节点的所有数据复制到从节点,而增量复制则是只复制主节点新增的数据。