HTTP的长连接和短连接本质上是TCP长连接和短连接。HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议。IP协议主要解决网络路由和寻址问题,TCP协议主要解决如何在IP层之上可靠的传递数据包,使在网络上的另一端收到发端发出的所有包,并且顺序与发出顺序一致。TCP有可靠,面向连接的特点。
HTTP协议是无状态的,指的是协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。也就是说,打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系。HTTP是一个无状态的面向连接的协议,无状态不代表HTTP不能保持TCP连接,更不能代表HTTP使用的是UDP协议(无连接)。
在HTTP/1.0中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。如果客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源,如JavaScript文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话。
但从 HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头有加入这行代码:
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接要客户端和服务端都支持长连接。
HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,连接的建立是需要三次握手的,而释放则需要4次握手,所以说每个连接的建立都是需要资源消耗和时间消耗的
经典的三次握手四次挥手示意图:
我们模拟一下TCP短连接的情况,client向server发起连接请求,server接到请求,然后双方建立连接。client向server 发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起close操作。为什么呢,一般的server不会回复完client后立即关闭连接的,当然不排除有特殊的情况。从上面的描述看,短连接一般只会在client/server间传递一次读写操作
短连接的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段
接下来我们再模拟一下长连接的情况,client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。
首先说一下TCP/IP详解上讲到的TCP保活功能,保活功能主要为服务器应用提供,服务器应用希望知道客户主机是否崩溃,从而可以代表客户使用资源。如果客户已经消失,使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,则服务器将应远等待客户端的数据,保活功能就是试图在服务 器端检测到这种半开放的连接。
如果一个给定的连接在两小时内没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于以下4个状态之一:
短连接的操作步骤是:建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接
长连接的操作步骤是:建立连接——数据传输...(保持连接)...数据传输——关闭连接
长连接可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。对于频繁请求资源的客户来说,较适用长连接。不过这里存在一个问题,存活功能的探测周期太长,还有就是它只是探测TCP连接的存活,属于比较斯文的做法,遇到恶意的连接时,保活功能就不够使了。在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。
短连接对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。但如果客户请求频繁,将在TCP的建立和关闭操作上浪费时间和带宽。
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,每次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。
而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。
原文链接: http://www.cnblogs.com/0201zcr/p/4694945.html
可以把 WebSocket 看成是 HTTP 协议为了支持长连接所打的一个大补丁,它和HTTP有一些共性,是为了解决 HTTP 本身无法解决的某些问题而做出的一个改良设计。在以前 HTTP 协议中所谓的 keep-alive connection 是指在一次 TCP 连接中完成多个 HTTP 请求,但是对每个请求仍然要单独发 header;所谓的 polling 是指从客户端(一般就是浏览器)不断主动的向服务器发 HTTP 请求查询是否有新数据。这两种模式有一个共同的缺点,就是除了真正的数据部分外,服务器和客户端还要大量交换 HTTP header,信息交换效率很低。它们建立的“长连接”都是伪.长连接,只不过好处是不需要对现有的 HTTP server 和浏览器架构做修改就能实现。
WebSocket 解决的第一个问题是,通过第一个 HTTP request 建立了 TCP 连接之后,之后的交换数据都不需要再发 HTTP request了,使得这个长连接变成了一个真.长连接。但是不需要发送 HTTP header就能交换数据显然和原有的 HTTP 协议是有区别的,所以它需要对服务器和客户端都进行升级才能实现。在此基础上 WebSocket 还是一个双通道的连接,在同一个 TCP 连接上既可以发也可以收信息。此外还有 multiplexing 功能,几个不同的 URI 可以复用同一个 WebSocket 连接。这些都是原来的 HTTP 不能做到的。
]]>Because the cluster mode has an hardcoded interpreter which is node
也就是说 cluster 模式下 interpreter 是写死的使用的node, “exec_interpreter” 参数会被忽略.
通过以下方式通过 pm2 cluster 模式使用 ES6/7 新功能
http://pm2.keymetrics.io/docs/tutorials/using-transpilers-with-pm2#require-hook
新建入口文件index.js, 假设原来的入口文件为app.js
|
|
在对应的 pm2.json 文件中设置, 入口文件为 index.js.
Linux 服务器内部无法正常解析域名, IP访问正常
curl xxx.xxx Could not resolve host: xxx.xxx
可能的原因包括:
1、通过如下指令,检查系统是否正确设置了 dns 服务器。
如果没有 DNS 的配置则需要添加设置,公网服务器可以设置为阿里云如下公共 DNS:
2、检查防火墙 iptables,查看是否有拦截 53 端口的相关规则。
可以先使用命令 service iptables stop 关闭防火墙对比测试。如果存在 iptables 规则,尝试删除 deny 策略或修改规则为 ACCEPT 策略。
3、检查是否开启 dns 缓存服务 nscd:
通过 service nscd status 命令查看服务状态。如果已经开启,尝试使用命令 service nscd stop 关闭服务后再对比测试。
解决办法:
在chrome的地址栏输入:
在打开的页面中,Delete domain栏的输入框中输入:xxxx.xxxxx(要访问的域名),然后点击“delete”按钮,即可完成配置。
然后你可以在Query domain栏中搜索刚才输入的域名,点击“query”按钮后如果提示“Not found”,那么你现在就可以使用http来访问那个网站了!
至于 chrome hsts 的作用看这里
国际互联网工程组织IETE正在推行一种新的Web安全协议HTTP Strict Transport Security(HSTS)
采用HSTS协议的网站将保证浏览器始终连接到该网站的HTTPS加密版本,不需要用户手动在URL地址栏中输入加密地址。
该协议将帮助网站采用全局加密,用户看到的就是该网站的安全版本。
在第一次访问https网站时,网站的回复表头带有「Strict-Transport-Security」,该表头会让浏览器记得,该网站(正确说法是域名)有提供HTTPS安全连线,并于下次连线中强制使用HTTPS,注意是强制喔,不论是点进不带有https的连接,或是你故意在网址里打入网址时使用 http:// 为开头,浏览器都会先强制转换成https再送出请求
]]>#如果文件夹不存在,创建文件夹
if [ ! -d “/myfolder” ]; then
mkdir /myfolder
fi
#shell判断文件,目录是否存在或者具有权限
folder=”/var/www/“
file=”/var/www/log”
if [ ! -x “$folder”]; then
mkdir “$folder”
fi
if [ ! -d “$folder”]; then
mkdir “$folder”
fi
if [ ! -f “$file” ]; then
touch “$file”
fi
if [ ! -n “$var” ]; then
echo “$var is empty”
exit 0
fi
if [ “$var1” = “$var2” ]; then
echo ‘$var1 eq $var2’
else
echo ‘$var1 not eq $var2’
fi
[ -b FILE ] 如果 FILE 存在且是一个块特殊文件则为真。
[ -c FILE ] 如果 FILE 存在且是一个字特殊文件则为真。
[ -d FILE ] 如果 FILE 存在且是一个目录则为真。
[ -e FILE ] 如果 FILE 存在则为真。
[ -f FILE ] 如果 FILE 存在且是一个普通文件则为真。
[ -g FILE ] 如果 FILE 存在且已经设置了SGID则为真。
[ -h FILE ] 如果 FILE 存在且是一个符号连接则为真。
[ -k FILE ] 如果 FILE 存在且已经设置了粘制位则为真。
[ -p FILE ] 如果 FILE 存在且是一个名字管道(F如果O)则为真。
[ -r FILE ] 如果 FILE 存在且是可读的则为真。
[ -s FILE ] 如果 FILE 存在且大小不为0则为真。
[ -t FD ] 如果文件描述符 FD 打开且指向一个终端则为真。
[ -u FILE ] 如果 FILE 存在且设置了SUID (set user ID)则为真。
[ -w FILE ] 如果 FILE 如果 FILE 存在且是可写的则为真。
[ -x FILE ] 如果 FILE 存在且是可执行的则为真。
[ -O FILE ] 如果 FILE 存在且属有效用户ID则为真。
[ -G FILE ] 如果 FILE 存在且属有效用户组则为真。
[ -L FILE ] 如果 FILE 存在且是一个符号连接则为真。
[ -N FILE ] 如果 FILE 存在 and has been mod如果ied since it was last read则为真。
[ -S FILE ] 如果 FILE 存在且是一个套接字则为真。
[ FILE1 -nt FILE2 ] 如果 FILE1 has been changed more recently than FILE2,
or 如果 FILE1 exists and FILE2 does not则为真。
[ FILE1 -ot FILE2 ] 如果 FILE1 比 FILE2 要老, 或者 FILE2 存在且 FILE1 不存在则为真。
[ FILE1 -ef FILE2 ] 如果 FILE1 和 FILE2 指向相同的设备和节点号则为真。
[ -o OPTIONNAME ] 如果 shell选项 “OPTIONNAME” 开启则为真。
[ -z STRING ] “STRING” 的长度为零则为真。
[ -n STRING ] or [ STRING ] “STRING” 的长度为非零 non-zero则为真。
[ STRING1 == STRING2 ] 如果2个字符串相同。
“=” may be used instead of “==” for strict POSIX compliance则为真。
[ STRING1 != STRING2 ] 如果字符串不相等则为真。
[ STRING1 < STRING2 ]
如果 “STRING1” sorts before “STRING2” lexicographically in the current locale则为真。
[ STRING1 > STRING2 ]
如果 “STRING1” sorts after “STRING2” lexicographically in the current locale则为真。
[ ARG1 OP ARG2 ] “OP” is one of -eq, -ne, -lt, -le, -gt or -ge. These arithmetic binary operators return true if “ARG1” is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal to “ARG2”, respectively. “ARG1” and “ARG2” are integers.
]]>英文原文:
When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.
当Node.js启动时会初始化event loop, 每一个event loop都会包含按如下顺序六个循环阶段
┌───────────────────────┐
┌─>│ timers │ (setTimeout(), setInterval())
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │ (setImmediate())
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
注意:
每一个阶段都有一个装有callbacks的fifo queue(队列),当event loop运行到一个指定阶段时,
node将执行该阶段的fifo queue(队列),当队列callback执行完或者执行callbacks数量超过该阶段的上限时,
event loop会转入下一下阶段.
由于这些操作中的任何一个都可以调度更多操作,并且在轮询阶段中处理的新事件由内核排队,所以轮询事件可以在轮询事件被处理的同时排队。因此,长时间运行的回调可以使轮询阶段(poll)的运行时间远远超过计时器的阈值.有关更多详细信息,请参阅定时器和轮询部分。
在事件循环的每次运行之间,Node.js检查是否正在等待任何异步I/O或定时器,如果没有任何异步I/O或定时器,则清除关闭。
计时器指定阈值(threshold),然后执行提供的回调(callback),但是可能不是我们希望它执行的确切时间。也就是说定时器的时间是有误差的.
定时器回调会在指定的时间过后按照预定的时间运行; 但是,操作系统调度或其他回调的运行可能会延迟它们。 注意:从技术上讲,poll阶段控制何时执行定时器。
例子: 假设您计划在100 ms阈值后执行超时,然后异步开始读取需要95 ms的文件:
|
|
当事件循环进入轮询阶段时,它有一个空的队列(fs.readFile()还没有完成),所以它将等待剩余的毫秒数,直到达到最快的定时器的阈值.当它等待95ms传递时,fs.readFile()完成读取文件,并且其需要10ms完成的回调被添加到轮询队列并被执行。 当回调完成时,队列中没有更多的回调,所以事件循环会看到最后一个定时器的阈值已经达到,然后回到定时器阶段执行定时器的回调。在这个例子中,你会看到被调度的定时器和它正在执行的回调之间的总延迟将是105ms。
处理一些系统调用错误,比如网络 stream, pipe, tcp, udp通信的错误callback 例如,如果尝试连接时TCP套接字收到ECONNREFUSED,则某些*nix系统要等待报告错误。这将排队在I/O回调阶段执行。
node 内部调用 忽略
poll阶段有两个主要功能:
当事件循环进入poll阶段并且代码未设置计时器时,会发生以下两种情况之一:
当事件循环进入poll阶段并且轮询队列(poll queue)为空, 事件循环将检查已达到时间阈值的定时器. 如果一个或多个定时器准备就绪, 则事件循环将回到timers阶段以执行这些定时器的回调(callbacks).
check阶段允许代码在poll queue空闲后立即执行回调。也就是setImmediate() 如果poll queue空闲并且代码中已经设置了setImmediate(),则事件循环直接进入到check阶段。 setImmediate()实际上是一个特殊的定时器,它在事件循环的一个单独的阶段中运行。 它使用一个libuv API来调度poll阶段完成后执行的回调。 通常,随着代码的执行,事件循环将最终进入poll阶段,在那里它将等待传入连接,请求等。但是,如果使用setImmediate()设置了回调并且poll queue空闲,将直接进入check阶段,而不是等待poll事件。
setImmediate和setTimeout 是相似的,但取决于它们何时被调用,以不同的方式运行.
setTimeout(fun(), 0) === setTimeout(fun(), 1)
另外, setTimeout(), HTML5中会有最低限制4ms, 这与浏览器有关, Node 中setTimeout通过libuv模块实现, 模块最终调用平台底层的高精度定时器, 并不会有这个限制. 详情(http://www.developerq.com/article/1488429596)
以下例子的输出结果:
结果:(忽略换行)
理论上235401 / 235410
实际由于1ms时间很短, 所以235401
分析:
> setTimeout(func(), 0) === setTimeout(func(), 1), 并不会直接运行, 会将func()放入times queue;
> setImmediate()是check阶段运行, 会将func()放入check queue;
> new Promise() Promise创建就会直接执行, 所以会输出 2; 执行for循环, i === 9999时, resolve()返回一个新的Promise对象, 但是.then() 是异步执行的, 也就是会把下一个.then()放到当前的poll queue中, 等待当前poll阶段执行完, 然后输出3 和 5, 此时poll阶段执行完, 遍历poll queue输出4
> poll 阶段结束, 检查times阶段的定时器是否达到或超过设定的阈值, 如果超过设置的阈值, 则执行times callback 即输出 0, 然后进入check阶段 输出1; 01的输出顺序取决于执行第3步时的时间, 该时间 大于setTimuout()的阈值则先输出0, 否则先输出1; 尝试修改setTimeout(xx, 11), 则本机先输出1;
从技术上来说,它并不是event loop的一部分。相反的,process.nextTick()会把回调塞入nextTickQueue,nextTickQueue将在当前操作完成后处理,不管目前处于event loop的哪个阶段。
看看我们最初给的示意图,process.nextTick()不管在任何时候调用,都会在所处的这个阶段最后,在event loop进入下个阶段前,处理完所有nextTickQueue里的回调。
因为 domain 即将被弃用, 就不重点研究了, 感兴趣的可以看一下相关的参考文章:
https://nodejs.org/api/domain.html#
https://cnodejs.org/topic/516b64596d38277306407936
为了防止 uncaughtException 导致内存泄漏, 当捕捉到 uncaughtException 时, 应该重启服务.
|
|
|
|
JavaScript 自动垃圾收集最常用的方式: 标记清除. 当变量进入环境(函数中声明一个变量)时, 就将这个变量标记为: “进入环境”, 进入环境的变量使用的内存, 在代码执行过程中就有可能会用到, 所以不能回收. 当变量离开环境时, 则将其标记为: “离开环境”, 在下一次垃圾回收时, 清理响应内存.
垃圾收集器咋运行的时候会给存储在内存中的所有变量都加上标记(可以使用任何方式, 重要的是标记策略), 然后去掉环境中正在使用的变量和有被引用的变量的标记, 还有标记的变量会被认为准备删除的变量. 原因是环境中的变量已经无法访问到这些变量了. 最后垃圾收集器清除对应的内存, 销毁那些带标记的值并回收他们所占用的空间.
另外还有JavaScript中不太常见的垃圾收集策略: 引用计数. 引用计数的含义是跟踪记录每个值被引用的次数, 当声明了一个变量并将一个引用类型值赋给该变量时, 则这个值的引用次数就是1, 被引用次数 +1, 取消引用次数-1, 当这个值的引用计数为 0时, 说明这个值不会再被其他变量引用, 可以被销毁了. 当垃圾回收器下次运行时, 会释放对应的内存.
引用计数的策略容易出现循环引用, 简单说就是 ObjectA 和 ObjectB 通过各自属性相互引用, 引用计数永远不会变成0, 对用的内存也就不会被回收也就是内存泄漏. 解决办法就是打破引用环就可以了. JavaScript 中在不使用变量后将变量置为 null, 以防止内存泄漏. OC 和 Swift中 使用就是引用计数的垃圾回收策略, 通过弱引用的方式防止循环引用.
垃圾回收是周期性运行的, 变量数量多垃圾回收的工作量会很大, 垃圾回收的时间间隔会很影响性能.
不使用变量后置为 null.
Node 构建于 V8 引擎之上, JavaScript 进行前端开发时内存管理的问题不会那么明显. 对于后端程序内存管理就很重要了, 长时间运行的程序一旦出现内存泄漏, 最后服务器的内存资源都会被耗尽的.
Nodejs 常驻内存组成:
堆外内存: 不通过 V8 分配, 也不受 V8 管理. (例如: Buffer对象的数据)
栈内存又分为很多区域:
新生代内存区:大多数的对象被分配在这里,这个区域很小但是垃圾回收特别频繁
Cell区、属性Cell区、Map区:存放Cell、属性Cell和Map,每个区域都是存放相同大小的元素,结构简单
但是为了简单就只介绍新老生代的内存管理. (原文找不到了, 只找到了快照 http://webcache.googleusercontent.com/search?q=cache:KwKkZfJ1QycJ:alinode.alicdn.com/blog/37+&cd=19&hl=zh-TW&ct=clnk&gl=us)
老生代内存64位系统下约为1.4G,32位系统下约为0.7G,新生代内存64位系统下约为32MB,32系统下约为16MB
通过
设置新生代内存以及老生代内存来破解默认的内存限制。
新生代垃圾回收
新对象都会被分配到新生代, 当新生代空间不足以分配新对象时, 将触发新生代的垃圾回收.
新生代的对象主要通过 Scavenge 算法进行垃圾回收, 这是一种采用复制的方式实现内存回收的算法.
Sacvenge 算法将新生代的总空间一分为二, 只使用其中一个, 另一个处于闲置, 等待垃圾回收时使用. 使用中的那块空间成为 From, 闲置的空间称为 To. 当新生代触发垃圾回收时, V8 将 From 空间中所有应该存活下来的对象依次复制到 To 空间.
有两种情况不会将对象复制到 To 空间, 而是晋升至老年代:
From 空间所有应该存活的对象都复制完成后, 原本的 From 空间将被释放, 成为闲置空间, 原本 To 空间则成为使用 From 空间, 两个空间进行了角色翻转.
Scavenge 算法优缺点:
Scavenge 是依次连续复制, To 空间不存在内存碎片.
老生代垃圾回收
老生代中的对象都是经历过一个或多次垃圾回收的对象, 而且老生代空间要比新生代大得多(一般老生代空间是新生代空间的40 倍), 使用 Scavenge 算法需要复制的对象太多会导致效率降低, 而且空间利用率也很低, 所以老生代不是采用 Scavenge 算法进行垃圾回收的, 而是采用 标记清除和标记整理.
当老生代触发垃圾回收时, V8 会将需要存活的对象打上标记, 然后将没有标记的对象, 也就是死亡的对象清除, 就完成了一次标记清除.
被清除的对象是不连续的内存空间, 导致老生代产生很多的闲置的内存碎片, 可能并没有足够大连续的空间存储较大的对象, 此时需要解决空间碎片, 也就是内存整理, 标记整理算法就是j将存活的对象移向内存一侧, 使其连续. 因为标记整理算法需要移动大量的内存空间, 执行耗费大量时间和资源, 因此只有当剩余空间无法存储新的大的对象时才会触发标记整理算法
增量标记清除
早期的老生代垃圾回收机制, 采用全停顿, 也就是垃圾回收时程序运行会被暂. 浏览器时代使用内存不多, 卡顿不是很明显, Nodejs 时代后台程序使用大量内存, 全停顿方式很容易带来明显的迟滞, 标记阶段很容易引起卡顿, 因此后期 V8 采用增量标记, 将标记阶段分为若干小步骤, 每个步骤控制在 5ms内, 每执行一段时间标记行为, 就继续运行 JS 程序, 交替进行, 类似于 操作系统的时间片轮转, 提高程序的流畅度.
Node.js 使用 V8 作为 JavaScript 的执行引擎,所以讨论 Node.js 的 GC 情况就等于在讨论 V8 的 GC。在 V8 中一个对象的内存是否被释放,是看程序中是否还有地方持有改对象的引用。
在 V8 中,每次 GC 时,是根据 root 对象 (浏览器环境下的 window,Node.js 环境下的 global ) 依次梳理对象的引用,如果能从root的引用链到达访问,V8就会将其标记为可到达对象,反之为不可到达对象。被标记为不可到达对象(即无引用的对象)后就会被 V8 回收。(http://alinode.aliyun.com/blog/37)
node基于V8构建,通过V8的方式进行分配跟管理js对象。V8对内存的使用有限制(老生代内存64位系统下约为1.4G,32位系统下约为0.7G,新生代内存64位系统下约为32MB,32系统下约为16MB)。在这样的限制下,将导致无法操作大内存对象。如果不小心触碰这个界限,就会造成进程退出。
原因: V8在执行垃圾回收时会阻塞JavaScript应用逻辑,直到垃圾回收结束再重新执行JavaScript应用逻辑,这种行为被称为“全停顿”(stop-the-world)。若V8的堆内存为1.5GB,V8做一次小的垃圾回收需要50ms以上,做一次非增量式的垃圾回收甚至要1秒以上。
通过
设置新生代内存以及老生代内存来破解默认的内存限制。
V8的堆其实并不只是由老生代和新生代两部分构成,可以将堆分为几个不同的区域:
增量式GC
表示垃圾回收器在扫描内存空间时是否收集(增加)垃圾并在扫描周期结束时清空垃圾。
非增量式GC
使用非增量式垃圾收集器时,一收集到垃圾即将其清空。
垃圾回收器只会针对新生代内存区、老生代指针区以及老生代数据区进行垃圾回收。对象首先进入占用空间较少的新生代内存。大部分对象会很快失效,非增量GC直接回收这些少量内存。假如有些对象一段时间内不能被回收,则进去老生代内存区。这个区域则执行不频繁的增量GC,且耗时较长。
|
|
这种比较简单的原因,全局变量直接挂在 root 对象上,不会被清除掉。
|
|
闭包会引用到父级函数中的变量,如果闭包未释放,就会导致内存泄漏。上面例子是 inner 直接挂在了 root 上,从而导致内存泄漏(bigData 不会释放)。
需要注意的是,这里举得例子只是简单的将引用挂在全局对象上,实际的业务情况可能是挂在某个可以从 root 追溯到的对象上导致的。
Node.js 的事件监听也可能出现的内存泄漏。例如对同一个事件重复监听,忘记移除(removeListener),将造成内存泄漏。这种情况很容易在复用对象上添加事件时出现,所以事件重复监听可能收到如下警告:
例如,Node.js 中 Agent 的 keepAlive 为 true 时,可能造成的内存泄漏。当 Agent keepAlive 为 true 的时候,将会复用之前使用过的 socket,如果在 socket 上添加事件监听,忘记清除的话,因为 socket 的复用,将导致事件重复监听从而产生内存泄漏。
原理上与前一个添加事件监听的时候忘了清除是一样的。在使用 Node.js 的 http 模块时,不通过 keepAlive 复用是没有问题的,复用了以后就会可能产生内存泄漏。所以,你需要了解添加事件监听的对象的生命周期,并注意自行移除。
关于这个问题的实例,可以看 Github 上的 issues(node Agent keepAlive 内存泄漏)(https://github.com/nodejs/node/issues/9268)
还有一些其他的情况可能会导致内存泄漏,比如缓存,队列消费不及时等。在使用缓存的时候,得清楚缓存的对象的多少,如果缓存对象非常多,得做限制最大缓存数量处理。还有就是非常占用CPU的代码也会导致内存泄漏,服务器在运行的时候,如果有高 CPU 的同步代码,因为Node.js 是单线程的,所以不能处理处理请求,请求堆积导致内存占用过高。
文中的例子基本都可以很清楚的看出内存泄漏,但是在工作中,代码混合上业务以后就不一定能很清楚的看出内存泄漏了,还是得依靠工具来定位内存泄漏。另外下面是一些避免内存泄漏的方法。
ESLint 检测代码检查非期望的全局变量。
使用闭包的时候,得知道闭包了什么对象,还有引用闭包的对象何时清除闭包。最好可以避免写出复杂的闭包,因为复杂的闭包引起的内存泄漏,如果没有打印内存快照的话,是很难看出来的。
绑定事件的时候,一定得在恰当的时候清除事件。在编写一个类的时候,推荐使用 init 函数对类的事件监听进行绑定和资源申请,然后 destroy 函数对事件和占用资源进行释放。
|
|
|
|
|
|
|
|
stats事件:每次进行全堆垃圾回收时,将触发一次stats事件。这个事件将会传递内存统计信息。
leak事件:如果经过连续5次垃圾回收后,内存仍然没有被释放,意味着内存泄漏的发生。这个时候会触发一个leak事件。
Heap Diffing 堆内存比较 排查内存溢出代码。
下面,我们通过一个例子来演示如何排查定位内存泄漏:
首先我们创建一个导致内存泄漏的例子:
这里我们通过设置一个不断增加且不回被回收的数组,来模拟内存泄漏。
通过使用heap-dump模块来定时纪录内存快照,并通过chrome开发者工具profiles来导入快照,对比分析。
我们可以看到,在浏览器访问 localhost:3000, 并多次刷新后,快照的大小一直在增长,且即使不请求,也没有减小,说明已经发生了泄漏。
接着我们通过过 chrome 开发者工具 profiles, 导入快照。通过设置 comparison,对比初始快照,发送请求,平稳,再发送请求这3个阶段的内存快照。可以发现右侧new中 LeakClass 一直增加。在delta中始终为正数,说明并没有被回收。
小结
针对内存泄漏可以采用植入 memwatch,或者定时上报 process.memoryUsage 内存使用率到 monitor,并设置告警阀值进行监控。
当发现内存泄漏问题时,若允许情况下,可以在本地运行 node-heapdump,使用定时生成内存快照。并把快照通过 chrome Profiles 分析泄漏原因。若无法本地调试,在测试服务器上使用 v8-profiler 输出内存快照比较分析json(需要代码侵入)。
需要考虑在什么情况下开启 memwatch/heapdump。考虑 heapdump 的频度以免耗尽了 CPU。 也可以考虑其他的方式来检测内存的增长,比如直接监控 process.memoryUsage()。
当心误判,短暂的内存使用峰值表现得很像是内存泄漏。如果你的app突然要占用大量的CPU和内存,处理时间可能会跨越数个垃圾回收周期,那样的话 memwatch 很有可能将之误判为内存泄漏。但是,这种情况下,一旦你的app使用完这些资源,内存消耗就会降回正常的水平。所以需要注意的是持续报告的内存泄漏,而可以忽略一两次突发的警报。
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。CommonJS 的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。
上面代码就是 Node 内部加载模块后生成的一个对象。该对象的id属性是模块名,exports属性是模块输出的各个接口,loaded属性是一个布尔值,表示该模块的脚本是否执行完毕。其他还有很多属性,这里都省略了。
以后需要用到这个模块的时候,就会到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
delete require.cache[‘path/to/config’]
config = require(‘path/to/config’))
JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
CommonJS 模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行。一旦出现某个模块被”循环加载”,就只输出已经执行的部分,还未执行的部分不会输出。
让我们来看,Node 官方文档里面的例子。脚本文件a.js代码如下。
12345
exports.done = false;var b = require('./b.js');console.log('在 a.js 之中,b.done = %j', b.done);exports.done = true;console.log('a.js 执行完毕');
上面代码之中,a.js脚本先输出一个done变量,然后加载另一个脚本文件b.js。注意,此时a.js代码就停在这里,等待b.js执行完毕,再往下执行。
再看b.js的代码。
12345
exports.done = false;var a = require('./a.js');console.log('在 b.js 之中,a.done = %j', a.done);exports.done = true;console.log('b.js 执行完毕');
上面代码之中,b.js执行到第二行,就会去加载a.js,这时,就发生了“循环加载”。系统会去a.js模块对应对象的exports属性取值,可是因为a.js还没有执行完,从exports属性只能取回已经执行的部分,而不是最后的值。
a.js已经执行的部分,只有一行。
1
exports.done = false;
因此,对于b.js来说,它从a.js只输入一个变量done,值为false。
然后,b.js接着往下执行,等到全部执行完毕,再把执行权交还给a.js。于是,a.js接着往下执行,直到执行完毕。我们写一个脚本main.js,验证这个过程。
123
var a = require('./a.js');var b = require('./b.js');console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
执行main.js,运行结果如下。
1234567
$ node main.js在 b.js 之中,a.done = falseb.js 执行完毕在 a.js 之中,b.done = truea.js 执行完毕在 main.js 之中, a.done=true, b.done=true
上面的代码证明了两件事。一是,在b.js之中,a.js没有执行完毕,只执行了第一行。二是,main.js执行到第二行时,不会再次执行b.js,而是输出缓存的b.js的执行结果,即它的第四行。
总之,CommonJS 输入的是被输出值的拷贝,不是引用。
另外,由于 CommonJS 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。
12345678910
var a = require('a'); // 安全的写法var foo = require('a').foo; // 危险的写法exports.good = function (arg) { return a.foo('good', arg); // 使用的是 a.foo 的最新值};exports.bad = function (arg) { return foo('bad', arg); // 使用的是一个部分加载时的值};
上面代码中,如果发生循环加载,require(‘a’).foo的值很可能后面会被改写,改用require(‘a’)会更保险一点。
ES6 处理“循环加载”与 CommonJS 有本质的不同。ES6 模块是动态引用,如果使用import从一个模块加载变量(即import foo from ‘foo’),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
请看下面这个例子。
1234567891011
// a.mjsimport {bar} from './b';console.log('a.mjs');console.log(bar);export let foo = 'foo';// b.mjsimport {foo} from './a';console.log('b.mjs');console.log(foo);export let bar = 'bar';
上面代码中,a.mjs加载b.mjs,b.mjs又加载a.mjs,构成循环加载。执行a.mjs,结果如下。
123
$ node --experimental-modules a.mjsb.mjsReferenceError: foo is not defined
上面代码中,执行a.mjs以后会报错,foo变量未定义,这是为什么?
让我们一行行来看,ES6循环加载是怎么处理的。首先,执行a.mjs以后,引擎发现它加载了b.mjs,因此会优先执行b.mjs,然后再执行a.js。接着,执行b.mjs的时候,已知它从a.mjs输入了foo接口,这时不会去执行a.mjs,而是认为这个接口已经存在了,继续往下执行。执行到第三行console.log(foo)的时候,才发现这个接口根本没定义,因此报错。
解决这个问题的方法,就是让b.mjs运行的时候,foo已经有定义了。这可以通过将foo写成函数来解决。
这时再执行a.mjs就可以得到预期结果。
这是因为函数具有提升作用,在执行import {bar} from’./b’时,函数foo就已经有定义了,所以b.mjs加载的时候不会报错。这也意味着,如果把函数foo改写成函数表达式,也会报错。
上面代码的第四行,改成了函数表达式,就不具有提升作用,执行就会报错。
我们再来看 ES6 模块加载器SystemJS给出的一个例子。
12345678910111213
// even.jsimport { odd } from './odd'export var counter = 0;export function even(n) { counter++; return n === 0 || odd(n - 1);}// odd.jsimport { even } from './even';export function odd(n) { return n !== 0 && even(n - 1);}
上面代码中,even.js里面的函数even有一个参数n,只要不等于 0,就会减去 1,传入加载的odd()。odd.js也会做类似操作。
运行上面这段代码,结果如下。
上面代码中,参数n从 10 变为 0 的过程中,even()一共会执行 6 次,所以变量counter等于6。第二次调用even()时,参数n从 20 变为 0,even()一共会执行 11 次,加上前面的 6 次,所以变量counter等于 17。
这个例子要是改写成 CommonJS,就根本无法执行,会报错。
上面代码中,even.js加载odd.js,而odd.js又去加载even.js,形成“循环加载”。这时,执行引擎就会输出even.js已经执行的部分(不存在任何结果),所以在odd.js之中,变量even等于null,等到后面调用even(n-1)就会报错。
- require 是 AMD规范引入方式- import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法
- require是运行时调用,所以require理论上可以运用在代码的任何地方- import是编译时调用,所以必须放在文件开头
- require是赋值过程,其实require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量- import是解构过程,node v8引擎还没有实现import,我们在node中使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语法会被转码为require
]]>
|
|
连接成功则表示mongod已经运行
|
|
service 启动的mongod服务
此时通过/etc/init.d/mongodb启动服务, 默认使用/etc/mongod.conf配置文件
mongod –fork 启动mongod服务
默认也会使用/etc/mongod.conf配置文件, 但是当指定dbpath logpath后以指定为准
mongod.conf文件
|
|
修改对应路径:
本例中前人通过指定logpath dbpath的方式启动服务
知道了原来的数据和日志位置, 那么可以压缩备份原来的数据了, 本例以/data/db/mongodb为新的数据目录
/data/db/log为新的日志目录
|
|
|
|
or
查看新数据目录权限
|
|
直接拷贝 已经是mongodb:mongodb 用户组了
查看日志 访问mongodb-27017.sock 无权限
|
|
|
|
重新启动
重新查看 mongodb-27017.sock 文件
|
|
注: 启动相关日志 还是在/var/log/mongodb
]]>
|
|
终端显示默认配置文件和verdaccio工作端口, 浏览器打开http://localhost:4873/ ,页面如下
default config:
123456
npmjs:url: https://registry.npmjs.orgyarnjs:url: https://registry.yarnpkg.comcnpmjs:url: https://registry.npm.taobao.org
packages: 包访问或发布控制
{regexp}: 包名匹配正则。
access: 访问控制,可选值有$all(用户不限制), $anonymous(用户不限制), $authenticated(所有登录用户), username( 用户名,需指定具体用户,可指定多个用户,用户间空格隔开,如 secret super-secret-area ultra-secret-area)。尽管@all, @anonymous, all, undefined,
publish: 发布控制,配置请参考 access
proxy: 代理控制,设置的值必选现在 uplinks 中定义。
常用的包名正则有:
|
|
包名正则规范通 gitignore 一致,verdaccio 内部使用minimatch实现的,如果需要书写更复杂的正则,可以参考 minimatch 文档。
详情(https://github.com/verdaccio/verdaccio/blob/master/wiki/config.md)
|
|
|
|
|
|
|
|
现在配置org-前缀的包全部私有
只需在配置文件 config.yml 中 package 段添加配置
|
|
这里我们配置了所有org-前缀的包只有注册用户才能访问和发布。
你也可以对 publish 做进一步限制,只有 npmuser 用户才能发布
注意修改配置后要重启 verdaccio
其实加前缀并不是一种很好组织包的方式,npm 提供了更好的名称空间策略 scope
scope 包包名格式:@scope-name/pkg-name
初始化包时指定 scope
我们可以为 scope 绑定一个仓储
这样凡是碰到 scope 为 @org 的包,npm 将自动切换作业仓储到 scope 绑定的仓储,这提供了一种多仓储策略。
scope 私有包配置
|
|
|
|
|
|
|
|
|
|
一、修改时区
二、配置新的时间
三、实现Internet时间同步(这里可以忽略上面两步)
|
|
|
|
|
|
Shadowsocks 是一个 socket5 服务,我们需要使用 Privoxy 把流量转到 http/https 上。
|
|
|
|
|
|
|
|
nginx.conf
这个是nginx的主配置文件,里面包含了当前目录的所有配置文件,
只不过有的是注释状态,需要的时候自行开启(后面几个常用的)
conf.d
这是一个目录,里面可以写我们自己自定义的配置文件,文件结尾一
定是.conf才可以生效(当然也可以通过修改nginx.conf来取消这个限制)
sites-enabled
这里面的配置文件其实就是sites-available里面的配置文件的软
连接,但是由于nginx.conf默认包含的是这个文件夹,所以我们在
sites-available里面建立了新的站点之后,还要建立个软连接到sites-enabled里面才行
sites-available
这里是我们的虚拟主机的目录,我们在在这里面可以创建多个虚拟主机
Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
为什么要配置多站点:
当我们有了一个服务器之后,为了不浪费服务器的资源,我们可以在一个服务器上放置多个网站项目,它们共同使用80端口,通过不同的servername来区分不同的网站项目,在实际上线的项目中,这个servername就是我们的域名啦
具体配置(我们只举例一个,多个重复操作就行):
默认已经有一个站点了,就是defalt,在sites-available里面有一个default文件,就是默认站点的配置,servername是localhost不建议直接修改这个默认站点,我们可以复制一个:
|
|
|
|
正向代理 是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。
主要为了越过局域网内的防火墙实现访问网站
反向代理正好相反,对于客户端而言它就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原始服务器)转交请求,并将获得的内容返回给客户端,就像这些内容原本就是它自己的一样。为了将防火墙后面的服务器提供给Internet用户访问也可以实现负载均衡,动静分离,url策略
前期准备:
修改配置文件:
这样配置:
ps:nginx主服务器也可以当做一个负载均衡服务器,但是由于80端口已经给负载均衡食用了,所有如果我们还使用80端口的话,就会造成一个死循环,我们可以再给主服务器添加一个服务器,并使用不同的8080端口,这样,主服务器也可以当做一个负载均衡的子服务器了,不会造成资源的浪费:
|
|
轮询(默认方式)
每个请求按时间顺序逐一分配到后端服务器,如果后端服务器down掉,能自动剔除
weight
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
|
|
ip_hash
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
|
|
fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。
|
|
url_hash(第三方)
例:在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法
|
|
注意:
定义负载均衡设备的Ip及设备状态
在需要使用负载均衡的server中增加
|
|
动静分离
动静分离也是利用负载均衡的原理来实现的,为了便于管理,我们把ip分配的配置写在conf.d这个文件夹里面:
|
|
里面写上动静分离的分配(以PHP和静态文件为例子):
|
|
|
|
|
|
|
|
|
|
如果要添加的端口并没有服务对应
就要新建一个服务,在/usr/lib/firewalld/services,随便拷贝一个xml文件到一个新名字,比如myservice.xml,把里面的
|
|
short改为想要名字(这个名字只是为了人来阅读,没有实际影响。重要的是修改 protocol和port。修改完保存。我的经验是这是要重启firewalld服务,systemctl restart firewalld.service,否则可能提示找不到刚才新建的service。然后把新建的service添加到
firewalld
|
|
|
|
直接将gitlab自动生成的nginx配置复制到Nginx虚拟主机配置文件夹下
|
|
或者
|
|
|
|
|
|
系统内存不足. 服务启动失败
]]>Shell 的变量以 var=
|
|
|
|
除了花括号之外,$ 符号还可以搭配圆括号使用。基本有两种用法:
command
。当 Shell 变量中保存着内容时,我们就可以按索引和长度截取字符串中的内容。Shell 变量的索引从 0 开始。主要有两种方式:
|
|
Shell 的变量支持从左或者从右删除包含通配符的子串:
|
|
Shell 的变量也支持字符串替换。
|
|
read接受用户从键盘的输入:
|
|
|
|
若在read命中不指定变量,read命令会将它收到的任何数据都放进特殊环境变量REPLY中,
|
|
-s 阻止用户的输入显示在显示器上,(实际上,数据会被显示,只是read命令将文本颜色设置成跟背景颜色一样)
read line 会从文本中读取一行,用cat命令的输出通过管道传给含有read的while命令