用Node.js开发基于正方教务系统的手机选课神器

对于我们来说,学校的选课系统和网络架构共同组合成了一个黑盒系统,需要对其进行逆向思维的Web开发,因为代码涉及到校内系统的一些不安全因素,也暂时无法整理出一份不包含敏感内容的代码,所以有了本文,分析一些开发这个产品的思路。

我最初的想法是想做一款基于Node为后端的手机选课Webapp,由于学校系统和网络架构的封闭性,给我们带来了无数个坑……

产品最终表现

  • 峰值3880位独立用户在线同时发送选课请求(受限于学校最终的系统负载能力)
  • 发送了27735条选课请求,三天内的5w多次登录请求
  • 在官方系统大面积瘫痪后依然稳定运行
  • 实现免验证码提交
  • 一键选课,将繁琐的网页官方版系统的选课操作浓缩为一步
  • 整合实现WebApp和NativeApp共享,同一套方案,适应多渠道的交互体验
  • 可以快速适配地部署到不同学校的“正方教务系统”

为什么要开发这样一款产品

  • 学校官方系统速度缓慢,并发能力弱,且易出错
  • 能选课的场地限制很多,而智能手机几乎人手一部
  • 可以收集到很多有用的行为数据,提供后续分析利用
  • 官方系统操作体验极差,常年遭到学生吐槽
  • 等等等等。。。

挑战和技术选型

面临的问题

  • 没有学校教务数据库的直接读写权限
  • 每个年级大约有4000-5000人,会同时刷上半小时以上
  • 多终端的接入兼容(内嵌Webview及独立Webapp)
  • 每次提交都要输入验证码?!
  • 开工之后和完工之时才会发现的更严重的坑。。。

技术选型

  • 前端与后端通过RESTful API交互
  • 后端作为中间层,代理用户请求,以更快速的方式将请求转发到选课服务器
  • 3台Centos 6.4跑选课代理服务,分配1台MongoDB进行行为记录,以及1台Watcher机
  • 前端NginxLB,sticky session
  • 项目托管于 bitbucket.org
  • 将用户登录选课系统的cookie集中到服务器上管理
  • Nodejs + cheerio + Mongoose

     

    "dependencies": {
      "express": "3.4.4",
      "ejs": "0.8.5",
      "iconv-lite": "0.2.11", //快速的GB2312 TO UTF-8
      "cheerio": "0.12.4", /** 替代jquery,对jsdom的增强,
                           同时兼容jquery语法,实测解析速度会比jquery快1到1.2倍 **/
      "request": "2.27.0",
      "mongoose": "3.8.1", //将各类行为和访问写入mongo日志
      "pm": "2.2.2", // @朋春 的cluster
      "connect-mongo": "0.4.0" //处理express mongo session
    }

各种的坑和解决方案

坑一

maxSockets

request 在发送http请求时的最大可用agent数量默认限制为5(实际上是 http 对其自己的maxSockets初始为5),导致了在并发情况下,排队等候的请求无法addListener,会报如下错误。

(node) warning: possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit.
Trace: 
    at EventEmitter.<anonymous> (events.js:139:15)
    at EventEmitter.<anonymous> (node.js:385:29)
    at Server.<anonymous> (server.js:20:17)
    at Server.emit (events.js:70:17)
    at HTTPParser.onIncoming (http.js:1514:12)
    at HTTPParser.onHeadersComplete (http.js:102:31)
    at Socket.ondata (http.js:1410:22)
    at TCP.onread (net.js:354:27)

这个warning带来的问题是,新增的http请求一律被拒绝,返回500 Internal Server Error。而其实此时的服务器Load毫无压力。所以增大maxSockets是毋庸置疑的。 之后找到 request 的作者回复的一个issue, @mikeal给到的解决方案是

require('http').globalAgent.maxSockets = Infinity

而实际上呢? 当我们设置到无限制之后,使用JMeter进行压测,发现1000qps的时候,前几秒还能正常处理,之后立马瞬间马上500返回了剩余的所有请求。并且在console里面报错ENOTFOUND或ETIMEOUT(这俩错误是由网络层返回回来的)

俩开发猜了好久,有抓包分析了相关response,发现是遭到了sangfor firewall的封杀。原来是已经超过了允许的每秒单机HTTP请求通过数了(服务器群网段也限啊,真是BT),所以在后来 request 虽然顺利发出了http request,但是从网络层被forbidden。那么,既然猜到是这样的原因,我们就可以采取“保持在允许的单机最大qps,连续发送请求”的方式进行请求了,这样既不会被防火墙从网络层切断,又能保证传输的最高效率。

经过实测,发现设定为150时,是我们当前服务器和网络环境下的最佳值。

http.globalAgent.maxSockets = 150;

坑二

最佳http超时

由于Node中默认的http超时时间为120s之久,大量长时的异步请求不会因为用户刷新或断开浏览器连接而断开,给服务器上造成了很多不必要的资源占用,导致队列排的很满,后过来的请求只能被阻塞。实际表现出的结果是,服务器发送出1000个http请求之后,后来的访问者的请求会被node自动排队到队列的最后,给用户带来的直接感受就是程序仿佛卡死了、很慢很满,于是用户再又刷新,一个新的http请求又出去了,用户一多,造成了很多无用的连接资源占用。因此我们需要寻找一个最佳的超时时间设置,经过我们的实践数据显示

Alt text

根据测试结果设置 request 的默认超时时间,单位为ms

var request = require('request').
defaults({
  timeout: 95000
});

 

其他坑,待更新分享

   更智能地导向到最快且好的选课服务上

   CAS系统的登录和跳转

   让connect-mongo与mongoose更美好地共存

   mongoose查询结果必须JSON.parse()?!

 

Read More

NPM vs Bower 的区别

众所周知,npm(Node Package Manager)是nodejs时代不可或缺的最好的包管理器,现在已经随nodejs官方包同时会安装到你的设备上去。只要给项目书写好package.json放于项目根目录,在重新部署之时只需要执行 

npm install

一行简单的命令,所有相关的依赖就能够自动安装到项目目录下面,并且还能很方便的对不同项目的不同依赖包版本进行良好、统一的管理。

关于NPM的具体使用已经不需更多赘述,可以自行参考这篇文章 http://www.infoq.com/cn/articles/msh-using-npm-manage-node.js-dependence

 

重点来说说NPM和Twitter推出的名为 Bower 的包管理器之间到底有什么样的关系和区别呢?(Bower的官网写到,Bower 是 "A package manager for the web" ,难道说NPM就不是了嘛)。

其实,在实际项目中,NPM和Bower都会被运用进去。并且Bower的安装和升级全都依赖于NPM,使用如下命令就可以全局安装Bower

npm install -g bower

之后你就可以使用

bower install [#]

类似于NPM的方式,对于当前项目进行前端依赖的相关管理。使用起来和NPM一样方便快捷。

其中,与NPM最大的区别在于,NPM主要运用于Node.js项目的内部依赖包管理,安装的模块位于项目根目录下的node_modules文件夹内。而Bower大部分情况下用于前端开发,对于CSS/JS/模板等内容进行依赖管理,依赖的下载目录结构可以自定义。

有人可能会问,为何不用NPM一个工具对前后端进行统一的依赖管理呢? 实际上,因为npm设计之初就采用了的是嵌套的依赖关系树,这种方式显然对前端不友好;而Bower则采用扁平的依赖关系管理方式,使用上更符合前端开发的使用习惯。

不过,现在越来越多出名的js依赖包可以跨前后端共同使用,所以Bower和NPM上面有不少可以通用的内容。实际项目中,我们可以采用NPM作用于后端;Bower作用于前端的组合使用模式。让前后端公用开发语言的同时,不同端的开发工程师能够更好地利用手上的工具提升开发效率。

Read More