Quantcast
Channel: CNode:Node.js专业中文社区
Viewing all 14821 articles
Browse latest View live

Node应用内存泄漏分析方法论与实战

$
0
0

本文发表于北斗同构github, 转载请注明出处

注: 本文为第12届D2前端技术论坛《打造高可靠与高性能的React同构解决方案》分享内容,已经过数据脱敏处理。

前言

菜鸟物流大市场是菜鸟旗下的一条业务线,可以简单地理解为物流领域的淘宝,是为撮合物流需求方和物流提供方搭建的一个平台。其中搜索页详情页、买家中心等页面是基于beidou同构框架开发的。随着node、react同构等技术越来越广泛地使用, 内存泄漏的事情时有发生,应当引起足够的重视。最近在做菜鸟物流市场的技术支持,就“中奖”了,把实践过程中的经验和心得整理了下,供大家参考。

先介绍几个基本术语:

  • SSR: 服务端渲染, 简而言之就是把页面在服务端渲染好直接返回给浏览器以提升展示性能
  • 同构: 在SSR的基础上, 应用既可以在服务端渲染又可以在浏览器渲染,既一套代码两端运行。
  • Beidou(北斗): 基于eggjs的react同构框架, 开源地址
  • 内存泄漏: 是指程序中己动态分配的堆内存由于某种原因未释放或无法释放,通常是应用层不合理的逻辑代码引起的。
  • OOM: Out Of Memory, 简单地说就是内存消耗完了,分配不出内存了。内存泄漏是导致OOM的最常见的因素。OOM导致的直接后果就是进程Crash掉。
  • RSS: Resident Set Size 实际使用物理内存(包含共享库占用的内存)

案例分析

回到之前说到的菜鸟物流大市场

发现问题

菜鸟物流大市场上线之后,经常收到alimonitor的告警通知,如下图

于是打开了alinode查看慢日志, 果然有不少慢日志记录

分析&验证&排查

分析

当时主要有以下几个现象:

  • 详情页面有时打开很快,有时打开需要4 - 5 秒
  • 重启之后会明显变好, 响应速度很快,
  • 机器负载采样: CPU消耗很低、 内存消耗高达 53.5%

根据当时的现象做了简单的分析并制定了具体的action:

  • 响应很慢 --> 1) 可能HSF接口慢 2) 可能渲染慢 --> action: 分别打点记录日志
  • 时快时慢 --> 可能不同的机器当前状况不一样导致响应速度差别很大–> action: 对比各机器负载情况
  • 重启后速度很快 --> 可能发生了某事件导致了性能变差,重点排查内存泄漏 --> action: 通过alinode堆快照分析
  • CPU低、内存消耗高 --> 极有可能是内存泄漏 --> action: 通过alinode堆快照分析

从上面的推断来看,发生内存泄漏的可能性非常大,但仍然需要通过实际数据进行验证,于是根据制定的action进行数据采集

验证

再次发布之后,采集到了数据:

从上图中可以看出, 随着时间的推移,进程1694的hsf调用耗时始终稳定,但是服务端渲染的时间却逐步飙升到3700多毫秒,然后在某个临界值之后瞬间降低到50毫秒左右。可能是由于某某事件( 猜测是内存泄漏引起OOM )导致了进程崩溃,接下来beidou框架会自动重启进程又恢复良好的状态。打开sandbox一看进程生命周期,果然如此, 进程1694挂了,然后重新启动了一个29649进程。

从上图中也可以看到RSS(实际使用物理内存)高达1880.93MB,至此基本上可以确定是内存泄漏了。查看内存占用曲线,内存呈现锯齿状,先一路飙升,到达零界点之后瞬间下降,如此周而复始。和我们的推断完全一致,这是典型的内存泄漏曲线。

最终结论: 访问速度慢是因为内存泄漏消耗了过多的资源

排查

定位到是内存泄漏之后,还需要进一步排查具体是什么代码导致了内存泄漏。这时候就要用到排查神器 - alinode了。

先创建堆快照:

在分析页面打开对象簇视图, 可以看到里面有大量的Window对象, 搜索下竟然高达390个

采样了几个Window对象,通过GC Root展开,发现挂载了无数个定时器。

分析代码找到了两处定时器的设置,看代码逻辑,该定时器在服务端根本不会被释放。

componentWillMount(){
        let _this = this;
        window.handler = window.setInterval(function(){
            if(typeof AMap){
                _this.renderMap('', AMap);
                window.clearInterval(window.handler);
            }
        }, 300);
    }

注释掉之后在预发验证没有再出现window相关的内存泄漏。

PS.

后来的验证发现,除了定时器的问题,还有另外两处内存泄漏,不再赘述, 贴上其中一处(高德地图)内存泄漏的代码供读者参考

componentWillMount(){
        this.createAmapScript();
    }
    
createAmapScript(){
        let script = document.createElement('script'),
            body = document.getElementsByTagName('body')[0];
        script.type = 'text/javascript';
        script.src = 'https://webapi.amap.com/maps?v=1.3&key=59699a8cfee7c52f58390357cbdbf27d';
        body.appendChild(script);
    }

解决问题

从上述两处代码可以看出,定时器无需在服务端执行, 而高德地图本身就不支持服务端渲染,因此可将二者放到客服端渲染即可。根据react的特性,componentDidMount生命周期函数在服务端不会执行,因此将上述代码从componentWillMount移到componentDidMount中即可。具体修复如下:

通过loadtest在本地压测验证下:

单个进程同样以10个QPS进行施压,对比下可以看出,修复前RT时间一路上升,而修复后RT始终稳定在200毫秒左右。

再看看线上数据, 内存占用率始终稳定,没有出现飙升现象。

至此,打完收工。

方法论

看完了案例,是时候系统化地总结下方法论了。

现象

从刚才的案例中可以看出来,内存泄漏最典型的现象就是内存占用率会随着时间的推移而逐步上升,就算没有流量了,内存占用率也不会下降。而健康的应用是流量上升内存占用会上升,而流量下降之后内存占用率就会回到原水平。

原因

通常造成内存泄漏的有以下几个因素

  • 缓存
  • 队列消费不及时
  • 作用域未释放

本文中的案例就属于作用域未释放

解决方案

  • 本地
    • 通过loadtest压测,观察应用是否健康
    • 如若出现异常,通过node-heapdump对v8堆内存抓取快照, 并通过chrome开发者工具profiles来导入快照进行分析。
  • 线上
    • 通过alimonitor、eagleeye等监控平台监控应用健康度
    • 如若出现异常,通过alinode堆快照排查问题
    • 如若异常难以复现,可以在预发 或者隔离某台线上机器进行压测,压测能够有效放大问题
    • 在压测过程中,通过alinode堆快照排查问题

建议

  • 最重要的一条:开发阶段就压测、开发阶段就压测、开发阶段就压测,重要的事情说三遍。古语云:上医治未病,中医治欲病,下医治已病,说的是医术最高明的医生并不是擅长治病的人,而是能够预防疾病的人。让问题在开发阶段就暴露出来, 而不是等到线上告警了再抢救。
  • 避免在constructor中做事件绑定,建议放到componentDidMount生命周期中
  • 不支持SSR的组件放到componentDidMount中,同理,createElement、appendChild等dom原生操作也放到componentDidMount中
  • 其它详见同构注意事项

腾讯招Web前端开发拉~坐标深圳

$
0
0

岗位职责: 负责腾讯课堂、企鹅辅导等新产品的web前端架构设计和研发; 负责IMWEB team的开源项目建设和工具研发。 岗位要求: 本科以上学历,3年以上相关工作经验; 能够熟练运用 HTML、CSS、JavaScript 构建高性能的web应用程序; 熟悉移动端web开发或hybrid开发模式更佳 理解web标准和兼容性,对可用性相关知识有实际的了解和实践经验; 能够熟练运用至少一款主流的JS框架,具有良好的代码风格、接口设计与程序架构; 掌握至少一门服务器端编程语言,对 OOP 有一定的认识; 思路清晰,具备良好的沟通能力和团队协作精神。

简历请发至 jayccchen@tencent.com

node全栈开发工程师

$
0
0

公司 是一家互联网广告公司 坐标 建外soho西区15号楼7层 职位 全栈开发工程师

岗位职责 1 及时响应产品需求、完成相关内容的开发与设计 2 全面的项目和技术实施能力,能够单独负责项目的需求分析,系统设计,研发和运维

任职要求 1 熟悉 JavaScript, CSS/CSS3, HTML/HTML5 等前端开发技术 2 熟练 div + css 编写兼容适配的代码 3 熟练 微信端公众号跟小程序的开发,并有实际的开发项目跟经验 4 熟悉 React、React-redux、BlazeJs、Vuejs 或其他 MVVM, MVC 前端框架,并有实际上线的开发项目 5 有丰富的后端开发经验,要有 Node.js 的 Express、koa等其他框架的开发经验,能够独立负责后端的逻辑开发 6 扎实的网络基础,熟悉 TCP/IP, HTTP 协议及常见返回状态码 7 熟悉常见 mongodb 与 mysql,熟悉常用的查询操作 8 数据结构清晰、命名规范、编写高质量代码 9 具备良好的学习能力、沟通能力、分析及解决问题能力,优秀的团队协作精神

加分项 1 有复杂项目的后端架构设计经验 2 懂得科学上网,了解前端主流发展方向,经常活跃在高质量技术社区 3 有 Github 账户,提交过代码到自己或他人项目

联系邮箱 peihanchao@shun.tt

辞旧迎新之际, 我给博客换了新装

$
0
0

2015年3月份我用Rails写了第一个比较完整的项目——首个博客系统,现在回过头来看,觉得很多地方都写的比较烂,今年年初的时候我打算重构自己的博客系统,从一开始的想法到完成整件事,经历了长达一年的时间,原因只有一个——懒。

这个博客的诞生是因为平时在浏览其他网站及关注一些 js 库时发现一些特别喜欢的东西,所以才给了我做这件事的动力。其实做完这个项目真正花费的时间并不多,比较难的是要让自己愿意来做这件事。

整个项目做下来,对我来说,最难的不是技术实现,而是为博客每个主题找到合适的背景图片,简直就是大海捞针。别人给我推荐了这个 图片库,经过长时间的寻找,终于找到了几张让我比较满意的的图片。这个 工具不错,有需要的可以收藏一下。

新的博客系统主要功能有:

  • 后端:数据统计,后台文章管理,相册管理,个人简历管理
  • 前端:文章列表,相册展示,时间线等

Demo

项目 Demo 请访问: http://liuzhen.me

项目 github 代码: https://github.com/liuzhenangel/RBlog

核心技术框架

  • Ruby on Rails 5.1.4
  • bootstrap 4
  • font-awesome
  • figaro
  • postgres
  • slim
  • high_voltage
  • carriewave & upyun
  • sidekiq
  • kaminari
  • mina
  • puma
  • lograge
  • simditor
  • turn.js

开发环境准备

第一步, 安装项目依赖

$ bundle install

第二步, 启动服务

$ rails s

第三步, 浏览器访问: http://localhost:3000

结束.

如何发布?

第一步, 配置nginx

先根据项目里的 config/deploy/production.rb, /config/deploy.rb, config/puma.rb, /config/nigix.conf 文件, 修改其中的配置, 然后将 /config/nigix.conf 文件复制到你的服务器上 nginx 所在目录的 /etc/nginx/conf.d 目录下, 命名为 xxx.conf 的文件. 然后重启 nginx.

第二步, 在服务器上初始化

$ mina setup

第三步, 发布

$ mina deploy

学习参考资料

Rails文档: http://edgeguides.rubyonrails.org/api_app.html

使用模板创建Rails项目: https://github.com/80percent/rails-template

ubuntu16.04安装rails: https://gorails.com/setup/ubuntu/16.04

simditor编辑器: http://simditor.tower.im/

startbootstrap-clean-blog前端样式: https://startbootstrap.com/template-overviews/clean-blog/

sb-admin前端样式: https://startbootstrap.com/template-overviews/sb-admin/

turnjs前端样式: http://www.turnjs.com

timeline前端样式: https://github.com/RyanFitzgerald/vertical-timeline

图片库: https://unsplash.com

引荐 Vue.js 项目

项目 Demo 请访问: http://v2ex.liuzhen.me/

项目代码: https://github.com/liuzhenangel/v2ex_frontend

引荐 React.js 项目

项目 Demo 请访问: http://ruby-china.liuzhen.me/

项目代码: https://github.com/liuzhenangel/react-ruby-china

入门到放弃node系列之微信公众号开发实战二(Express篇)

$
0
0

续节

本文首发公众号【一名打字员】

上回书说到,师徒四人途经狮驼国,狮驼国三位国师对唐僧心怀不轨,要与师徒四人进行斗法。咳,跑偏了跑偏了。

大家静一静,上一次最后我们介绍了Express的模板引擎,今天我们接着上次的继续。

庖丁解牛(续)

  • 更换模板引擎

首先我们在 package.json文件中 dependencies节点增加包:

"ejs": "~2.5.2",

执行 npm install编译过后,修改 app.js文件中的模板渲染一行。

//将jade换成ejs
app.set('view engine', 'ejs');

然后将 index.jade换成 index.ejs,因为现在我们已经将模板切换到ejs了。 下面是 index.js文件内容。

<%include header.ejs %>
    <p>Wecolme to <%=title%></p>
<%include footer.ejs %>

现在java程序员是不是看着顺眼了很多呢,这仅仅是个人习惯,仅供参考。

  • 工程目录总览

现在我们回顾一下整体工程的目录结构:

- bin            # 命令文件
- node_modules   # 下载的依赖包
- public         # 静态资源目录
- routes         # 路由文件
- views          # 视图模板文件
  app.js         # 入口文件
  package.json   # 工程依赖配置文件

现在相信大家对整个项目已经有了基本的认识了,接下来我们就开始对微信公众号的集成了。

集成微信公众号

关于微信公众平台的开发文档可以去 官网获取,里面有很详细的介绍,以下配图步骤均来自官方文档: 从文档里面我们可以知道,首先我们必须要在自己公众号的后台进行服务器的配置。

untitled1.png

然后填上我们的服务器配置,由于我们暂时还没有写,所以这里暂时先写上 http://www.dailyguitar.cc/wechat/index

接下来我们就得写对 /wechat/index的处理了。

  • 验证服务端的有效性

微信会对上面我们填写的地址进行有效性的检测,它会用请求我们的地址,我们必须对它请求过来的参数进行解密,然后返回同样的数据给它,否则将无法使用公众平台的相关接口。

首先我们在 index.js中加入如下代码,

/* 微信校验 */
var token="weixin";

router.get('/wechat/index', function(req, res, next) {
  try{
        var signature = req.query.signature;
        var timestamp = req.query.timestamp;
        var nonce = req.query.nonce;
        var echostr = req.query.echostr;
        /*  加密/校验流程如下: */
        //1. 将token、timestamp、nonce三个参数进行字典序排序
        var array = new Array(token,timestamp,nonce);
        array.sort();
        var str = array.toString().replace(/,/g,"");
        //2. 将三个参数字符串拼接成一个字符串进行sha1加密
        var sha1Code = crypto.createHash("sha1");
        var code = sha1Code.update(str,'utf-8').digest("hex");
        //3. 获得加密后的字符串可与signature对比,标识该请求来源于微信
        if(code===signature){
            res.send(echostr);
            console.log(""+echostr);
        }else{
            res.send("error");
        }
    }catch(error){
        console.log("error:"+error);
  	}
});

其中我们用到了一个加密模块 crypto,我们需要手动引用一下,然后我们将项目运行起来,由于微信需要用到域名否则无法进行调试,这里本猿推荐两个内网穿透工具,一个是花生壳一个叫做ngrok,大家可以自己研究研究。

到这里其实与微信的对接已经完成了,在微信后台我们就可以配置成功了,记得别忘了我们的token哟。

  • 处理微信消息

当用户和我们的公众号发生操作的时候,微信会发送post请求到我们配置的URL中,所以我们只需要接收微信发过来的xml数据进行解析,并以xml的格式返回数据即可完成对消息的回复。 所以我们得对 index.js再增加一个post的捕捉。

/* 微信消息处理 */
router.post('/wechat/index', function(req, res, next) {
  try{
        var bodyData;
	    req.on("data",function(data){
	        /*微信服务器传过来的是xml格式的,是buffer类型,
	        	需要通过toString把xml转换为字符串*/
	        bodyData = data.toString("utf-8");

	    });
	    req.on("end",function(){
	        var ToUserName = getXMLNodeValue('ToUserName',bodyData);
	        var FromUserName = getXMLNodeValue('FromUserName',bodyData);
	        var CreateTime = getXMLNodeValue('CreateTime',bodyData);
	        var MsgType = getXMLNodeValue('MsgType',bodyData);
	        var Content = getXMLNodeValue('Content',bodyData);
	        var MsgId = getXMLNodeValue('MsgId',bodyData);
	        console.log(ToUserName);
	        console.log(FromUserName);
	        console.log(CreateTime);
	        console.log(MsgType);
	        console.log(Content);
	        console.log(MsgId);
	        var xml = '<xml><ToUserName>'+FromUserName+'</ToUserName><FromUserName>'+ToUserName+'</FromUserName><CreateTime>'+CreateTime+'</CreateTime><MsgType>'+MsgType+'</MsgType><Content>'+Content+'</Content></xml>';
	        res.send(xml);
    	});
    }catch(error){
        console.log("error:"+error);
  	}
});

/* 获取节点 */
function getXMLNodeValue(node_name,xml){
    var str = xml.split("<"+node_name+">");
    var tempStr = str[1].split("</"+node_name+">");
    return tempStr[0];
}

打开应用,对着自己公众号发送一条消息,很快也会收到一条内容一样的回复,大功告成!

结语

本次实战只为达到简单的操作效果,我们可以对其进行更深层次的处理和封装,具体可以看一看我的开源项目中对微信公众号模块的处理。另外node中有一个比较方便公众号开发的模块 wechat,它提供了很多便捷的方法如支付以及模版消息等模块的支持,有兴趣的朋友可以自行研究一下。

关于mongodb更新数组里的所有对象内容问题。。。。

$
0
0

###文档如下:

{
    name: 4,
    list: [{
        id: "a",
        date: 1504195200000,
        other: "c"
    },{
        id: "b",
        date: 1504195200000,
        other: "c"
    }]
}

如何把list数组下的元素里的other全部一次性更新呢? 尝试了以下方法:不行…

db.getCollection('test').update({'name': 4}, {$set: {'list.$.other': 'a'}}, {multi: true})

不知道为什么?求大神的解决方法,谢谢

Ejs模板在标签中获取json数据

$
0
0

EJS返回数据DATA在前台获取。

如下: <script> var data = <%- JSON.stringify(DATA) %> </script>

发布 scss-flex

$
0
0

发布:https://github.com/vxhly/scss-flex请广大网友提一些意见,以至于再次改进

dowload

npm install scss-flex --save
# or
bower install scss-flex --save

use

<link href="./dist/scss-flex.min.css">
# or
import 'scss-flex/src/scss-flex.scss'

mongoose populate查不到数据

$
0
0

两个js文件,分别是detaillist.js和auditInfo.js,我现在想在detaillist.js中关联查询auditInfo.js的数据,我在detaillist.js中引入了auditInfo.js,但是查询总是为空detaiList.pngaudit.pngservise.png

2018,Node.js 该聊点啥呢?

$
0
0

我想聊聊

  • async/await
  • typescript
  • pwa/ssr/同构
  • 性能分析
  • koa/fastify/egg/think
  • 微服务和rpc

大家帮想想,还有哪些?

Node.js 的可扩展应用模式:CQRS, ES, Onion,各位有什么看法?

$
0
0

https://medium.com/@domagojk/patterns-for-designing-flexible-architecture-in-node-js-cqrs-es-onion-7eb10bbefe17

本文介绍如何使用 CQRS 与 Event Sourcing 模式来开发 Node.js 应用,使用 Onion 架构来组织这些模式,并且使用 TypeScript 来进行静态类型校验。本文首先讨论了何谓灵活架构:业务逻辑与实现分离、独立于数据库,框架,服务等等;然后介绍了 Event Sourcing 与 CQRS 的含义,以及它们是如何组织在洋葱圈模型中的,最后以典型的认证逻辑为例讨论了如何实现 CQRS 模式。

Java和Ruby圈见过很多,但国内落地的到底有多少,我不还不确定,各位有什么看法?

nodejs爬虫如何设置动态ip以及userAgent

$
0
0

前言

在写nodejs爬虫的过程中,原网站可能会对某一时间段内集中访问该页面的ip进行封杀。那么如何动态设置每次爬取使用的ip地址以及浏览器头部信息呢?

动态userAgent

这是我收集到的常用的浏览器头部信息,每次爬取的时候从中随机选取一个,并使用superAgent设置请求头部的User-Agent字段就好了。

userAgent.js

const userAgents = [
  'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12',
  'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)',
  'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20',
  'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6',
  'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER',
  'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0) ,Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9',
  'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
  'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)',
  'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)',
  'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre',
  'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52',
  'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12',
  'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)',
  'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6',
  'Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6',
  'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)',
  'Opera/9.25 (Windows NT 5.1; U; en), Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9',
  'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
]

module.exports = userAgents

app.js

import request from 'superagent'
import userAgents from '../src/userAgent'

async function doRequest(){
	let userAgent = userAgents[parseInt(Math.random() * userAgents.length)]
	request.get('http://www.xxx.com')
    .set({ 'User-Agent': userAgent })
    .timeout({ response: 5000, deadline: 60000 })
    .end(async(err, res) => {
      // 处理数据
    })
}

动态ip

设置动态IP需要用到一个superagent插件—superagent-proxy,除此之外为了避免每次爬取时都去获取一次动态IP的列表,我将爬取到的动态IP列表存放在redis中,并设置10分钟的过期时间。数据过期之后再重新发送获取动态IP的请求。 ps: 这里我使用的动态IP是爬虫网络科技公司提供的免费代理,因为免费所以难免会有些缺陷。有时候使用他的代理ip并不能访问得通,我在后面会做单独的处理。

package.json

{
  "name": "xxx",
  "version": "1.0.0",
  "description": "xxx",
  "main": "arf.js",
  "scripts": {
    "arf": "nodemon src/app.js --exec babel-node --config package.json"
  },
  "keywords": [
    "爬虫"
  ],
  "author": "lidikang",
  "license": "MIT",
  "dependencies": {
    "bluebird": "^3.5.1",
    "cheerio": "^1.0.0-rc.2",
    "eventproxy": "^1.0.0",
    "mongoose": "^4.13.6",
    "mongoose-findorcreate": "^2.0.0",
    "progress": "^2.0.0",
    "redis": "^2.8.0",
    "superagent": "^3.8.1",
    "superagent-proxy": "^1.0.2"
  },
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-stage-2": "^6.24.1",
    "nodemon": "^1.12.4"
  },
  "nodemonConfig": {
    "ignore": [
      "ips.json",
      "docs/*"
    ],
    "delay": "2500"
  }
}

app.js

import request from 'superagent'
import requestProxy from 'superagent-proxy'
import redis from 'redis'
// superagent添加使用代理ip的插件
requestProxy(request)
// redis promise化
bluebird.promisifyAll(redis.RedisClient.prototype)
bluebird.promisifyAll(redis.Multi.prototype)
// 建立mongoose和redis连接
const redisClient = connectRedis()

/**
 * 初始化redis
 */
function connectRedis() {
  let client = redis.createClient(config.REDIS_URL)
  client.on("ready", function(err) {
    console.log('redis连接 √')
  })
  client.on("error", function(err) {
    console.log(`redis错误,${err}  ×`);
  })
  return client
}


/**
 * 请求免费代理,读取redis,如果代理信息已经过期,重新请求免费代理请求
 */
async function getProxyIp() {
  // 先从redis读取缓存ip
  let localIpStr = await redisClient.getAsync('proxy_ips')
  let ips = null
  // 如果本地存在,则随机返回其中一个ip,否则重新请求
  if (localIpStr) {
    let localIps = localIpStr.split(',')
    return localIps[parseInt(Math.random() * localIps.length)]
  } else {
    let ipsJson = (await request.get('http://api.pcdaili.com/?orderid=888888888&num=100&protocol=1&method=1&an_ha=1&sp1=1&sp2=1&format=json&sep=1')).body
    let isRequestSuccess = false
    if (ipsJson && ipsJson.data.proxy_list) {
      ips = ipsJson.data.proxy_list
      isRequestSuccess = true
    } else {
      ips = ['http://127.0.0.1']
    }
    // 将爬取结果存入本地,缓存时间10分钟
    if (isRequestSuccess) {
      redisClient.set("proxy_ips", ips.join(','), 'EX', 10 * 60)
    }
    return ips[parseInt(Math.random() * ips.length)]
  }
}

async function doRequest(){
  let userAgent = userAgents[parseInt(Math.random() * userAgents.length)]
  let ip = await getProxyIp()
  let useIp = 'http://' + ip
  request.get('http://www.xxx.com')
    .set({ 'User-Agent': userAgent })
    .timeout({ response: 5000, deadline: 60000 })
    .proxy(ip)
    .end(async(err, res) => {
      // 处理数据
    })
}

之前说爬虫网络科技的免费ip有些缺陷—代理成功率有些低。这点必须想办法去修复,原理其实很简单,既然一次不成功那我就换个IP再试,直到成功了我才去开始执行解析html的逻辑

async function doRequest(){
  let userAgent = userAgents[parseInt(Math.random() * userAgents.length)]
  let ip = await getProxyIp()
  let useIp = 'http://' + ip
  request.get('http://www.xxx.com')
    .set({ 'User-Agent': userAgent })
    .timeout({ response: 5000, deadline: 60000 })
    .proxy(ip)
    .end(async(err, res) => {
      if (err) {
        console.log(`爬取页面失败,${err},正在重新寻找代理ip... ×`)
        // 如果是代理ip无法访问,另外选择一个代理
        doRequest('http://' + await getProxyIp(), userAgents[parseInt(Math.random() * userAgents.length)])
        return
      }
      // 解析html
      console.log('爬取页面  √')
      await parseDivision(res.text)
    })
}

如果你有啥疑问,欢迎写信到我的邮箱(andyliwr@outlook.com)与我讨论。

原文请查看http://andyliwr.github.io/2017/12/05/nodejs_spider_ip/

用最少代码实现express

写个工具,保持你的Github连击数不中断

$
0
0

Idea源自与同事的一次对话

我:“你Github两个月的commit连击中断了呢” 他:“那天有事出去了一趟,没得敲一行代码” 我:“我写个工具吧” …

先上一个图

screenshot.PNG

然后测试那天,忘记该commit的频率,每秒commit一次。等我下班回来再看的时候,哈,图就成了这样

1.png

已部署到Google Cloud, 而这个是免费一年多的。意味着接下来的一年,commit是不会断的.

最后上Github: https://github.com/axetroy/committing

来一起玩耍吧

为什么mongoose populate 查不到数据???


首届蚂蚁金服体验科技大会

$
0
0

1.png

大会简介

SEE = Seeking Experience & Engineering,意为探索用户体验与工程实践,由蚂蚁金服集团每年举办 1-2 次,包括专业分享、产品展台、Workshop 等内容。希望通过 SEE Conf,能与业界同行一起分享交流体验科技的当前进展,一起探讨切磋体验科技的未来发展,共同努力促进体验设计与技术的开放,让生态繁荣共赢。

首届 SEE Conf将于 2018.01.06 在美丽的浙江杭州举办,届时我们将邀请数百位来自前端、设计、产品等领域的英才,围绕「极致用户体验」和「最佳工程实践」两大主题进行探讨交流。

特邀嘉宾

  • 蚂蚁金服副总裁:胡喜 (阿玺)
  • 美团高级技术总监 / 业务负责人:刘平川 (rank)
  • 饿了么大前端部门负责人:林建锋 (sofish)
  • 百姓网资深前端架构师:贺师俊 (hax)
  • 豆瓣资深前端架构师:张克军 (kejun)
  • 钉钉资深技术专家:石玉磊 (佩玉)
  • 支付宝 UED 负责人:梁山鹰 (隼飞)
  • 蚂蚁金服体验技术部负责人:王保平 (玉伯)

会议日程

大会于 2018.01.06 正式召开,当天日程如下:

  • 09:00 - 09:15:开幕致辞
  • 09:15 - 10:00:蚂蚁中台设计体系 —— Ant Design 3.0 背后的故事
  • 10:00 - 10:45:Developer Experience First —— TWA 的理念与实践
  • 10:45 - 11:30:惊艳的互联网互动体验 —— AntG
  • 11:30 - 13:30:午间休息
  • 13:30 - 14:15:TECH 模型:企业级中后台产品体验度量探索
  • 14:15 - 15:00:AntV,返璞归真幻化万千可视化表达
  • 15:00 - 15:45:蚂蚁开发者工具,服务蚂蚁生态的移动研发 IDE
  • 15:45 - 16:30:轻推转型之门:Ant Design Pro 在企业级产品的探索
  • 16:30 - 16:45:从前端技术到体验科技,暨蚂蚁体验云起航
  • 16:45 - 17:15:圆桌 —— 体验科技的发展及未来

大会看点

这是蚂蚁金服体验科技首次对外发声,有大家熟悉的各个明星产品。有一个产品,过去 14 个月在 GitHub 的 star 数从 4k 飙升至 2w+, 这是蚂蚁设计语言 Ant Design,她是在怎样的业务土壤与团队文化中成长起来的?背后有着怎样的故事?还有一个产品,刚一开源就得到世界级计算机科学家 Leland Wilkinson的肯定,这款技术产品是 G2,其背后更大的蚂蚁可视化 AntV体系是本着怎样的初心在坚持前行?回顾过往,基于 Node.js 的前后端分层这几年在阿里经历了怎样的风雨?什么是 BFF(Backend For Frontend)架构?更进一步的 TWA(Techless Web App)架构是为了解决什么问题?为企业级框架和应用而生的 Egg.js将走向何方?

除此之外,还有最前沿的移动研发 IDE 技术和企业级产品体验度量的探索分享,更有未曾对外曝光,但已适配了千万机型的 Web 互动图形技术 AntG 将首度登台。从前端技术到体验科技,玉伯这位前端圈里的传奇黑侠将为我们讲述的“蚂蚁体验云”究竟是什么。

技术大会不光能听到干货,还能面基好友结交各路大神。除了议程提到的众多明星嘉宾,从目前报名中已经发现数十位重量级人物,不乏知名公司的产品、设计总监,架构师,新锐公司的 CXO 和领域新秀。

另外,蚂蚁金服是个藏龙卧虎的地方,有知名博主,也有知乎网红,有 Node.js、Ruby 等各个技术社区的大拿,还有仙剑主程、艺术插画大师,图形图像、数据可视化专家也会出没会场,有的还当起了会场志愿者在门口热切地期待着你的到来。他们会游走在会场与各个展台之间,也许就坐在你身边,这是面基好友结交大神的好时候,机会难得。

如何报名

访问 SEE Conf · 蚂蚁金服体验科技大会,点击「立即报名」按钮

支付宝扫描下方二维码

实现了一版virtual-dom,大家多多指教

node js 变量缓存问题

$
0
0

请教各位大神一个问题,我现在有个定时任务,5分钟一次,我有个变量存储一个数字,需要缓存这个变量,等到下一次调用这个定时任务的时候用,怎么样来写呢???

关于多核下的redis属性max_clients

TalkingData 开源地理信息可视化框架 inMap

$
0
0

本文作者:TalkingData 可视化工程师 李凤禄

inMap是 TalkingData 可视化团队开源的一款基于 canvas 的大数据可视化库,专注于大数据方向点、线、面的可视化效果展示。目前支持散点、围栏、热力、网格、聚合等方式;致力于让大数据可视化变得简单易用。

GitHub地址:https://github.com/TalkingData/inmap (求Star!)

文档地址:http://inmap.talkingdata.com

特性:

  • 多线程 高性能
  • 多图层叠加
  • 智能的算法
  • 友好的 API
  • 可以自定义主题

效果截图如下:

inMap 是更加智能的地理可视化框架,主要面向从事数据可视化应用相关的工程师和设计师。

  • 底层绘图引擎:目前基于 canvas 2d 提供基础绘图能力,基于 WebGL 的版本正在规划中;
  • 算法:内置了经纬度墨卡托转换、文字避让算法、最佳标记点算法、自动分组标记配色算法等。

inMap 写的每个算法都是为了增强用户体验,追求极致效果,我们希望用 inMap 能够做出伟大的产品。

inMap 接口设计也很友好,希望让开发者通过简单的配置,就能快速构建出优美可视化效果。

文字避让算法介绍:

我们在项目中经常会遇到在地图上打点并标上文字的需求,我们用某流行的可视化库展示出效果如下:

(文字密密麻麻一团,都叠在一块,效果很差,估计会被要被产品经理咬住不放)

inMap 实现了基于文字排版算法,是不是很赞,效果图如下:

是不是很酷啊,inMap 实现了自己的四分位文字排版算法,听起来不明觉厉的样子,别着急,让我慢慢道来。

每一个标记点都有上下左右四个放文字的位子,如果左边放不下,那就放右边试试,还不行就放到下面试试,以此类推,原理就这么简单,哈哈。

实现细节如下:

  • 求出要显示文字的矩形(文字宽高)
  • 创建带有坐标的虚拟文字集合对象、对集合的坐标进行从小到大排序
  • 递归遍历虚拟文字集合、判断是否与其他相交,如果有相交就移动当前文字位子,直到不相交为止。当找不到合适位置时,就选择隐藏当前文字。

后续会输出创造更好的可视化图形和算法,并后续退出 WebGL 版本。

Viewing all 14821 articles
Browse latest View live