资讯专栏INFORMATION COLUMN

qcon行node总结

sydMobile / 2185人阅读

摘要:进程运行中没被回收肯定属于内存泄漏关于这个定义引起讨论是在进程退出或崩溃后的情况。关于异步处理问题不适合处理复杂的状态机解决方案是使用队列结构进行可控的异步并发。

front to back

业务上 : front 面向展示 交互 ; back 功能 服务 数据一致性等等

环境 : front browser webview 单机; back 集群 高并发

思想差异 : front 快速开发 快速渲染 视觉效果 等等 ; back 服务稳定,性能,内存泄漏等等

V8 内存的简介

首先通过memoryUsage可以查看node进程的使用情况,具体介绍如下

process.memoryUsage();   查看node进程内存使用情况  单位是字节
{ rss: 20725760,  (resident set size)进程的常驻内存   
  heapTotal: 7376896,   已申请到的堆内存
  heapUsed: 3881280,  当前使用的堆内存
  external: 8772  ,  c++的内存使用,受v8管理
  }
  

针对上面api使用写了个相关例子

let  showMem=function () {
    var mem=process.memoryUsage();
    var format=function (bytes) {
        return (bytes/1024/1024).toFixed(2)+"MB";
    }
    console.log("Process :heapTotal "+format(mem.heapTotal)+" heapUsed "+ format(mem.heapUsed)+" rss "+format(mem.rss));
    console.log("------------------------------------")
}
var useMem=function () {
    var size=200*1024*1024;    //每次构建200MB对象
    var buffer=new Buffer(size);
    for(var i=0;i

heapTotal和heapUsed变化极小,唯一变化rss值,且该值远超V8内存分配限制(说明:V8的内存限制:64位系统约为1.4GB、32位系统约为0.7GB (这个规定是node源码 中限制的),具体测试可以将上述实例中buffer改成array即可)

说明:node 内存由通过v8进行分配的部分(新生到和老生代)(只有这部分才会受v8垃圾回收的限制)和node进行自行分配的部分(例如buffer)

额外补充

--max-old-space-size 命令就是设置老生代内存空间的最大值

--max-new-space-size 命令则可以设置新生代内存空间的大小

这两个参数只能在node启动时进行设置

下面从gc层面谈论下v8内存,这个可以说很多,我的云笔记中关于java和js的内存使用,分配,gc等整理了好几个系列,下面我用自己的话简单总结下。

关于内存分区有以下两个大类:

new space(特征:对象存活时间短) 又分为from和to两个区域,采用复制算法,空间换时间。如果存活多次,转移到老生代中。

老生代中(对象存活时间长),采用mark-sweep(标记清除)和mark-compact(标记整理),缺点容易形成内存碎片,导致无法分配大对象。v8主要使用mark-sweep,在内存空间不够时,才使用标记整理。老生代又可以细分Old Space(新生代中gc活过2次晋升到这个空间)、Large Object Space(大对象,一般指超过1MB,初始时直接分配到此),Map Space(“隐藏类”的指针,便于快速访问对象成员)、Code Space(机器码,存储在可执行内存中)

关于gc,总的来说在js中不同对象存活时间不同,没有一种算法适应所有场景,内存垃圾进行分代,针对不同分代使用相应高效算法,有些gc算法和java是一样的,复制(空间换时间,new Space),标记清除和标记整理(old space)等等。

补充存活标记依据

全局变量或者有由全局变量出发,可以访问到的对象

正在执行的函数中的局部对象,包括这些局部对象可以访问到的对象。

闭包中引用的对象

关于内存,偏底层语言(例如c)和js以及java不一样

#c代码
 #include 
 void init()
{
int arr[5]={};
for(int i=0;i<5;i++){
    arr[i]=i;
}
}
void test(){
    int arr[5];
    for(int i =0;i<5;i++){
        printf("%d
",arr[i]);
    }
}
int main(int argc,char const *argv[]){
    init();
    test();
    return 0;
}
//  0 1 2 3 4
# java代码
public class Test {

    public static  void  main(String[] args){
            init();
            test();
    }
    public static void init(){
        int[] arr=new int[]{0,1,2,3,4};
//        for(int i=0;i<5;i++){
//            System.out.println(arr[i]);
//        }
    }
    public static  void test(){
        int[] arr=new int[5];
        for(int i=0;i<5;i++){
            System.out.println(arr[i]);
        }
    }

}
//  0 0 0  0 0

上述例子说明c语言堆栈执行过程中,函数执行完堆栈释放,程序员如果不关注释放过程,出现一些问题 ,
而js和java就不会,因为内存自动分配,垃圾自动回收,不用考虑脏数据擦除。

内存泄漏

首先什么是内存泄漏?

对象不再被应用程序使用,但是垃圾回收器却不能移除它们,因为它们正在被引用。

node进程运行中garbage没被回收肯定属于内存泄漏,关于这个定义引起讨论是在node进程退出或崩溃后的情况。我的理解是如果有部分内存比如共享内存(用于进程间通信),如果没被释放,这依然属于内存泄漏,内存泄漏不仅仅只进程层面。

内存泄漏原因有好几种,ppt有的,我不在列举,我在分享过程中有同学提出个疑问,关于exports使用中为什么会导致泄漏,印象比较深刻,可能当时讲的不清楚,下面写个具体例子详细阐述下。

//A.js
var leakAry=[];
exports.leak=function(){
    leakAry.push("leak "+" gcy "+new Date());
}
//B.js
var obj=require("./a");
for(var i=0;i<10;i++){
    obj.leak();
}

在node当中,为了加速module访问,所有module都会被编译缓存,上述代码导致泄漏的原因就是模块上,被缓存局部变量重复访问,因为没初始化,导致内存占用不断增大。

其次平时如何定位内存泄漏具体问题。

var http=require("http");
 var heapdum=require("heapdump");
var leakArray=[];
var leak=function () {
    for(var i=0;i<100000;i++){
        leakArray.push("leak "+Math.random());
    }
   
};
var i=0;
http.createServer(function (req,res) {
    leak(); //泄漏触发位置
    i++;
    res.writeHead(200,{"Content-Type":"text/plain"});
    res.end("hello world gcy"+i);
}).listen(1337);
console.log(process.pid);
console.log("server start  gcy");

-------------------------------------------------------------
for ((i=1;i<=10000;i++));
 do   curl -v --header "Connection: keep-alive" "http://127.0.0.1:1337/"
  done
  批量100和10000个请求,记录heap dump镜像,通过对比视图查看变化比较大地方,通过分析定位具体泄漏位置,展示效果如下图

可以看到有三处对象明显增长的地方,string、concatenated string以及 array 对象增长。点击查看一下对象的引用情况,可以发现原因是leak执行,leakArray没有初始化,导致其里面字符串没有被清除,从而导致内存泄漏。

关于异步处理

问题:不适合处理复杂的状态机

解决方案:是使用队列结构进行可控的异步并发。

关于这一点理解分享中上有人提出可异议,
我的理解是比如逻辑中有大量的promise待处理,一旦此逻辑比较多,我们没法掌控,宏观上没法知晓具体执行情况,但是通过队列,在高并发请求下,大量的状态机promise通过队列
的管理,我们可以做到可控,哪些被消费了,状态机所处某个过程的比例都可以统计,一旦有这些统计信息,我们就可以进行相应的处理。求证。

弱计算

什么是 IO 密集型? 控制器busy

什么是 CPU 密集型? 运算器busy

关于弱计算的分析可以用node生成profile文件,然后通过chrome进行分析或者webstrome自带的v8 profiling进行分析,可以得到一系列函数执行时间统计和调用堆栈过程时间消耗统计,依据这些信息,我们在做相应的优化。图中显示的是例子test2的结果。

部署 child_process
//普通情况,只是作为用法示例
var fork=require("child_process").fork;
var cpus=require("os").cpus();
for(var i=0;i
总结

child_process 模块给node提供了一下几个方法创建子进程
1: spwan(); 启动一个子进程执行命令
2: exec() 启动一个子进程执行命令,与spwan不同的是,他有一个回调函数获知子进程状况
3: fork() 与spwan类似 不同地方在于 在创建子进程的时候只需指定 需要执行的JavaScript文件即可

上面方法很少用了,node当中有cluster模块,简单的几个方法就可以创建集群,其本质基于上面的封装,底层实现原理基于句柄共享。

var index=require("./app");
if (cluster.isMaster)
    for (var i = 0, n = os.cpus().length; i < n; i += 1)
        cluster.fork();
else
    index.app();
-------------------------------------------
//app.js
function app() {
    var server = http.createServer(function(req, res) {
        res.writeHead(200);
        res.end("hello world gcy
");
        console.log(cluster.worker.id);
    });
    server.listen(8088);
}
exports.app=app;

cluster使用中有三个问题
1、round-robin是当前cluster的默认负载均衡处理模式(除了windows平台),自行设定负载算法
可以在cluster加载之后未调用其它cluster函数之前执行:cluster.schedulingPolicy = cluster.SCHED_NONE。

2、进程监控问题
master进程不会自动管理worker进程的生死,如果worker被外界杀掉了,不会自动重启,
只会给master进程发送‘exit’消息,开发者需要自己做好管理。

3、数据共享问题
各个worker进程之间是独立的,为了让多个worker进程共享数据(譬如用户session),
一般的做法是在Node.js之外使用memcached或者redis。

cluster适用于在单台机器上,如果应用的流量巨大,多机器是必然的。这时,反向代理就派上用场了,我们可以用node来写反向代理的服务(比如用 http-proxy )
,好处是可以保持工程师技术栈的统一,不过生产环境,我们用的更多的还是nginx,部分重要配置如下。

nginx做集群

upstream gcy.com{
        server 127.0.0.1:3000 weight=1;
        server 127.0.0.1:3001 weight=2;
        server 127.0.0.1:3002 weight=6;

    }
维护

维护主要做好以下三点

日志

异常处理

第三方依赖管理。

异常处理解释下,有人提出疑问:异常没有被捕获一路冒泡 ,会触发uncaughtException 事件,
如果异常出现之后,没有进行正确的恢复操作可能导致内存泄漏,清理已使用的资源
(文件描述符(清除文件的占用)、句柄(Master传给work的标识)等) 然后 process.exit。

总结

qcon现场听一次,自己分享一次,总结一次,感觉收货蛮大的,有些地方自己加深了理解,分享中front to back开头部分,原先8页太多,本应一笔带过,讲的时候废话太多,原本我只想阐述其中几个关键名词。
分享本身是一个学习,开拓眼界的的过程,切身体会,还是需要自己实践,写具体case。
上述基本上是我讲的过程记录,做个总结,以便回顾。
演示部分链接, demo

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/82870.html

相关文章

  • FEDAY2016之旅

    摘要:前戏补上参会的完整记录,这个问题从一开始我就是准备自问自答的,希望可以通过这种形式把大会的干货分享给更多人。 showImg(http://7xqy7v.com1.z0.glb.clouddn.com/colorful/blog/feday2.png); 前戏 2016/3/21 补上参会的完整记录,这个问题从一开始我就是准备自问自答的,希望可以通过这种形式把大会的干货分享给更多人。 ...

    red_bricks 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<