技术实践第十二期|关于Node.js的那些事(下)
官方小二*更新于2022-3-31*
亲爱的开发者们,
🙋大家好,欢迎大家阅读:关于Node.js的那些事(下)。本期我们来一起了解关于Node.js的异步编程的优势与难点、内存控制等内容。
🎩开发者们有任何想要探讨的技术内容,欢迎在本帖下方踊跃留言哦~
*作者:llkevin13579*
一. Node.js—异步编程的优势与难点
有异步I/O,必有异步编程。异步编程有它的优势,也有它的难点,下面我们就这门语言异步编程的优势和难点来展开谈谈。
它的优势当然是它的底层机制:非阻塞的I/O模型,它可以令CPU和I/O不会相互影响,对于最近的云计算应用,它更加可以施展才能。利用事件循环,js就像一个分配任务和处理结果的大管家,I/O线程池里的各个I/O线程都是小二,负责兢兢业业地完成分配来的任务,小二和管家之间互不依赖,这令到整个客栈的效率提高不少。所以Node.js很好的解决了I/O的性能问题,而对于CPU密集型的计算就要看管家的能耐了。在使用的过程中,特别是高并发的状况下,需要防止过多地消耗CPU的时间片,这样才能很好的利用node.js的特性。只要合理利用Node的异步模型和V8的高性能,就可以充分发挥CPU和I/O资源的优势。
与此同时,它的难点也出现了,一个是异步的代码编写方法实在是太过奇葩,容易造成回调地狱,后面出现的Promise机制很好的解决了这个问题,Promise通过封装异步调用,实现了正向用例和反向用例的分离以及逻辑处理延迟,Promise带了两个参数--resolve和reject,resolve表示完成态,reject表示失败态。另外还有一个重要的方面,就是在异常处理的方式上面,通常我们用try{...} catch{...} finally{...}, 但这种写法不一定捕捉的是异步方法里的错误,像类似JSON.parse()方法,如果里面传的不是JSON,也会报错,也会进入到catch块,这个时候就很难判断错误的位置。
很多人说XXX语言是世界上最好的语言,其实没有这一说,只有最合适的语言。当我们处理高并发非CPU密集型的数据的时候,可以优先选择Node.js, 这样就可以把Node.js的优势发挥到极致。
二. Node.js之内存控制
一开始的Javascript是在浏览器运行的,一般只运行一些网页,运行的时间特别短,即使内存使用过多或者内存泄露,也只会影响C端用户,但是Node.js越来越多运行在服务器端之后,这个内存的问题也逐渐暴露出来,我们不能忽略它的问题,毕竟会影响到API的性质、用户的体验,所以我们必须好好探究Node.js的内存控制机制。
在Node中通过Javascript使用内存时会发现内存只能使用一部分,64位的系统下大概是1.4GB,32位的系统下大概是0.7GB,这样会导致一个问题,在Node的程序里,无法直接去操作大内存的对象,比如直接读一个2GB的文件。如何查看内存的使用情况,我们可以使用process.memoryUsage()方法,这个方法会两个重要的参数,一个是heapTotal, 另一个是heapUsed, 它们反应的是堆内存的使用情况,每当我们使用const或者let定义变量的时候,就会向堆内存申请空间,直到堆的大小超过V8的限制为止。
既然内存有限,我们如何高效地使用内存呢?在搞清楚这个这个问题之前我们要熟悉两个概念--作用域和闭包。
一个函数内部如果定义了一个变量,那么这个变量就在被调用的时候申请分配内存空间,函数执行结束只会就会被销毁,或者说这一块内存空间被收回,所以这个变量的作用域就在函数被调用之后到函数执行完成,对于全局变量,在程序刚开始运行的时候就分配了空间,到最后退出程序之后才释放空间,这个变量的作用域就相当长了。所以我们尽可能少使用全局变量,尽量将变量的定义放在最内部合适的地方,这样的话对内存的使用就不会过度,也利用高并发的时候处理更多的逻辑。
我们知道,一个函数内部的变量或者函数,在函数外部是无法进行访问的,但如果函数内部return了某些变量或者某些函数,那么它们就会暴露到外部,这个时候函数内部的变量或者函数作用域就不是到函数执行完成了,这样的方法叫做闭包,这样对于内存来说,这个内存长期没有被释放,会大量占用内存,所以说闭包的使用也需要当心,确保使用完成及时释放掉内存,不然会产生性能的问题。
总的来说,我们要善用node的优点,也要多留心里面需要规避的一些问题,这样才能更好的使用node.js。
三. Node.js之网络编程
Node具有事件驱动、无阻塞、单线程等特点,是搭建网络服务器的一个很好的语言,很多编程语言需要专门的服务器来作为容器,例如ASP需要IIS作为服务器,PHP需要Apache或者Nginx环境等等,而对于Node来说,只需要几行代码就可以构建服务器,不需要其他的容器。在这里就需要提到四个重要的概念: TCP, UDP, HTTP, HTTPS。
TCP全名是传输控制协议,是一个面向连接的协议,它在传输的过程中,需要经历三次握手形成会话。第一次握手是建立连接,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认; 第二次是服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),此时SYN_RECV状态;第三次是客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),这个包发完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。在TCP的服务器中有几个服务器的事件和连接事件,当我们搞懂它们的意思才能更好地进行编程,服务器事件有listening、connection、close和error,分别表示监听、连接、关闭和发生错误,而连接事件有data、end、connect、drain、error、close和timeout,分别表示事件传递的数据、服务器或者客户端发送了FIN数据、套接字和服务器端连接成功、任意一端调用write()发送数据、发生异常、套接字完全关闭和连接被闲置。
UDP又称数据包协议,UDP和TCP最大的不同是UDP不是面向连接的,它一旦建立,所有的会话都基于连接完成,他的资源消耗低,处理快速而且灵活,适合在线视频传输等场景,偶尔丢包也影响不大。UDP套接字相对TCP套接字使用起来更简单,它只是一个EventEmitter的实例,而非Stream的实例,事件有四个:message、listening、close、error,分别表示接到携带的数据、开始监听、调用了close()方法和发生了异常。
HTTP的全称是超文本传输协议,HTTP构建在TCP之上,属于应用层协议。http模块将其封装成ServerRequest对象,请求报文头第一行HTTP/1.1被解析之后分解成下面的三个属性:req.method、req.url和req.httpVersion,分别表示请求方法(包括GET、POST、DELETE、PUT等)、链接和http版本1.1。至于响应,它封装了对底层连接的写操作,可以看成一个可写的流对象,通过调用res.write()和res.end()方法实现,res.write()表示正常写数据,res.write()表示发送信号告诉服务器这次响应结束。
HTTPS服务就是在HTTP之上加上TLS/SSL,生成了私钥和证书之后,我们在服务器设置多一个配置,就可以配置一个HTTPS服务器了,CA证书将证书颁发给服务器之后,证书在请求的过程中会被发送到客户端,客户端需要根据证书验证真伪,如果是知名的CA机构,它们的证书一般是预装在浏览器当中的,有了HTTPS可以令到数据在传输的过程中多一份保障。
Node.js提供了相对于底层的网络调用,以及基于事件的编程接口,使得开发者在这些模块上不需要再花过多的时间去研究,令到开发者可以更轻松地构建网络应用。
🎩「大家都想知道的技术干货」👉:
技术实践第九期|前端开发:Vue.js中事件修饰符和按键修饰符的使用
技术实践第八期|前端开发:JavaScript中数组去重的方法汇总