腾讯新闻抢金人活动node同构直出渲染方案总结文章,我们对同构直出有了一个整体的认识-out渲染方案在我们的项目中使用。就像我在上一篇文章最后说的:应用技术的难点不在于攻克技术问题,而是能够不断的结合自己的产品体验,发现存在的体验问题,不断用更好的技术方案来优化用户经验有助于整个产品的开发。我们基于产品的体验选择了react同构直出渲染方案,同时也要保证当前方案的易用性和可靠性。比如有多少人可以同时访问我们的服务,当用户数量增加时,我们是否还能保证用户的正常访问,如何保证CPU、内存等正常运行,不被全部占用时间和不能发布等。所以这里要提供我们项目的几个数据:项目一天的访问量是多少,高峰期的访问量是多少,也就是有多少并发用户在那里;我们的单机服务最多支持多少QPS;服务的响应时间,页面和界面的故障率如何;CPU和内存的使用情况,是否存在CPU使用不足或内存泄漏等问题;这些数据都是我们上线前需要知道的。压力测试的重要性凸显出来。我们在上线前进行了充分的测试,可以让我们掌握程序和服务器的运行性能,大概需要申请多少台机器等等。1.初始压力测试这里我们使用autocannon对项目进行压力测试。注意,我们还没有进行任何优化措施,也就是先把问题暴露出来,再有针对性地进行优化。每秒并发60个,持续100秒:autocannon-c60-d100压测后的数据:从图中可以看出,当每秒并发请求量为60时,平均QPS约为266,但是还是有23个请求超时,响应时间还可以,99%的请求都在1817ms内完成。从目前的数据来看,数据处理能力还不够理想,我们还有很大的提升空间。2、解决方案上面的测压数据并不理想,这里需要采取一些措施。2.1内存管理我们在写纯前端的时候,几乎不会关注内存的使用。毕竟在前端开发的过程中,内存的垃圾回收机制是比较完善的,前端页面的生命周期是比较短的。如果真的需要特别注意的话,在IE浏览器中也是很早的,在js和dom交互的时候可能会出现内存泄露。而如果真的泄露了,也只会影响到当前终端用户,其他用户暂时不会受到影响。但是服务器不同。所有用户都将访问当前运行的代码。程序只要有轻微的内存泄漏,在数千次访问下,就会造成内存堆积,垃圾无法回收,最终导致严重的内存泄漏。并导致程序崩溃。为了防止内存泄漏,我们重点关注内存管理的三个方面:V8引擎的垃圾回收机制;内存泄漏的原因;如何检测内存泄漏;Node将JavaScript的主要应用场景扩展到了服务端,所以我们要考虑的细节也和浏览器端不同,需要对每一个资源进行更严谨的安排。总的来说,内存不是你在Node中可以随心所欲使用的东西,但它也不全是坏事。2.1.1V8引擎的垃圾回收机制在V8中,内存主要分为两代:新生代和老年代。新生代中的对象是存活时间相对较短的对象,老年代中的对象是存活时间较长或常驻内存的对象。默认情况下,新生代内存最大值在64位系统上为32MB,在32位系统上为16MB。V8的内存最大值在64位系统上为1464MB,在32位系统上为732MB。为什么要分两代呢?它用于最佳GC算法。新一代GC算法Scavenge速度快,但不适合大数据量;oldgeneration采用Mark-Sweep(标记去除)&Mark-Compact(标记压缩)算法,适用于大数据量,但速度较慢。使用更适合老年代和新生代的算法来优化GC速度。2.1.2内存泄漏的原因内存泄漏的情况很多,比如内存用作缓存、队列、重复事件监听等。内存缓存的时候,一般有一个变量来缓存数据,然后没有过期时间,数据一直被填满。例如下面这个简单的例子:letcached=newMap();server.get('*',(req,res)=>{if(cached.has(req.url)){returncached.get(req.url);}consthtml=app.render(req,res);cached.set(req.url,html);res.send(html);});此外,还有一个闭包。这种内存使用的缺点是没有可用的过期策略,只会让数据越来越多,最终造成内存泄漏。更好的办法是使用第三方的缓存机制,比如redis、memcached等,有很好的过期和淘汰策略。同时,还有一些队列的处理,比如一些日志写操作,当需要写入大量数据时,队列就会堆积起来。这时候我们设置队列的超时策略和拒绝策略,尽快释放一些操作。另一个是事件的重复监控。比如重复监听同一个事件而忘记移除(removeListener),就会造成内存泄漏。这种情况在为多路复用对象添加事件时很容易发生,因此重复的事件监听器可能会收到如下警告:Warning:PossibleEventEmittermemoryleakdetected。11/question添加了听众。使用发射器。setMaxListeners()增加limit2.1.3排查手段我们从内存监控图中可以看出,在用户数基本不变的情况下,内存一直在缓慢上升,说明我们存在内存泄漏。没有被释放。这里我们可以使用node-heapdump等工具来判断,或者更简单一点,使用--inspect命令实现:node--inspectserver.js然后打开chrome链接chrome://inspect查看内存使用情况.通过两次内存抓取对比,发现handleRequestTimeout()方法已经产生,并且每个handle方法中都有无数个回调,无法释放资源。定位使用的axios代码为:'ECONNABORTED',req));}}这里的代码貌似没有问题,是前端处理超时处理的典型解决方案。因为在Nodejs中,io链接会阻塞定时器处理,所以这个setTimeout不会按时触发,会出现超过10s才返回的情况。看来问题已经解决了。巨大的流量和阻塞的连接导致请求堆积。服务器搞不定,CPU也降不下来。通过定位并查看axios的源码:if(config.timeout){//有时候响应会很慢,不响应,connect事件会被事件循环系统阻塞。//计时器回调将被触发,并且在连接之前将调用abort(),然后获取“sockethangup”和代码ECONNRESET。//这时候,如果我们有大量的请求,nodejs会在后台挂掉一些socket。而且这个数字会越来越高。//然后这些被挂起的套接字会一点一点地吞噬CPU。//ClientRequest.setTimeout将在指定的毫秒数被触发,并且可以确保abort()将在连接后被触发。req.setTimeout(config.timeout,functionhandleRequestTimeout(){req.abort();reject(createError('timeoutof'+config.timeout+'msexceeded',config,'ECONNABORTED',req));});}额,我之前使用的版本比较早,跟着我本地使用使用的code不一样,说明已经更新了,然后查看9月16日这个文件的变更历史:这里需要把axios更新到最新版本,经过大量的本地测试,发现high下负载CPU和内存在正常范围内。2.2CacheCache确实是性能优化的好工具。如果服务不够用,缓存会补上。但是缓存的种类很多,我们应该根据项目的实际情况选择合理的缓存使用策略。这里我们使用3层缓存策略。在nginx中可以使用proxy_cache设置缓存的路径和缓存时间,同时可以开启proxy_cache_lock。启用proxy_cache_lock后,当多个客户端请求缓存中不存在的文件(或称其为MISS)时,只允许将这些请求中的第一个发送到服务器。其他请求在第一个请求得到满意的结果后,才拿到缓存中的文件。如果未启用proxy_cache_lock,所有未在缓存中找到的文件请求将直接与服务器通信。但是,该字段的启用应非常谨慎。当访问量过大时,会造成请求的堆积。在处理后续请求之前,您必须等待第一个请求返回并完成。proxy_cache_path/data/cachedkeys_zone=answer:16mlevels=1:2inactive=60m;server{location/{proxy_cacheanswer;proxy_cache_valid1m;}}在业务层面,我们可以开启redis缓存来缓存整个页面或者页面的一部分或者界面等,在穿透nginx缓存的时候,可以开启redis缓存。使用第三方缓存的特点在之前的文章中也有提到:可以共享多个进程,同时减少项目自身对缓存淘汰算法的处理。当前面两层缓存失效时,进入我们的节点服务层。二级缓存机制可以实现不同的缓存策略和缓存粒度。业务需要根据自己的场景选择适合自己业务的缓存。3.效果我们的项目此时的表现如何?autocanon-c1000-d100从图中可以看出,99%的请求都在182ms内完成,平均每秒处理的请求数约为15707。对比我们最初处理200多个请求,性能大幅提升60多倍。蚊子的公众号,欢迎交流:
