nodejs

再聊docker和nodejs

月盾
上一篇写到了如何在docker中运行nodejs,运行方式是在docker中安装了pm2来保证node服务宕机重启,这种方式更像是把docker当做虚拟机来使用。其实,既然使用了docker的话就可以不使用pm2来管理进程,因为docker自身可以充当守护进程,在node进程退出时进行重启。只要在启动docker容器时加上–restart=always参数即可。例如:docker run -d --restart=always -p 3000:3000 mynode:1 没有pm2如何开启多进程 使用pm2可以开启多node进程,并且自带负载均衡,但是有个限制,pm2可以开启的进程数是CPU最大核心数。而使用docker的话就不会受限于此了,开启几十个上百个node服务都可以,然后通过nginx实现负载均衡。不过要手动开启几十上百个docker容器那怎么行?让我手动开启3个都很烦了,这时候就需要用到docker编排工具了,比如:Docker Swarm、Kubernetes、docker compose等,可以一键开启多个容器。但是使用编排工具启动docker端口就不确定了,是由编排工具随机开启服务端口的,这又要做到服务注册发现,所以这些工具结合起来使用。 哪一种部署方式支持并发高? 使用jmeter在本机上进行了简单的并发测试,服务端进行简单的10万次hash计算,使用pm2开启4个实例,docker开启5个实例。docker使用Nginx做负载均衡,单次访问响应时间在1.2s~1.4s之间不等,在200个并发的情况下,两种模式响应时间相差不大,docker模式响应时间略占优势,大概快了0.1s。当并发数在300以上时两者的响应时间都有增加,此时docker部署方式出现了响应失败的情况,pm2就比较稳定了,虽然响应时间增加,但是并未出现过响应失败。 所以在单机上低并发docker还是有点优势,如果在高并发情况下还是pm2更稳定一些。(以上测试是单机上进行,准确性并不高)

pm2日志记录和日志分割

月盾
pm2介绍 pm2是nodejs进程管理工具,现在基本是node生产服务器的标准选择,可以帮助我们实现node多进程服务,开启的多个实例自动实现负载均衡。 最重要的是保证node单进程不会因为错误退出,作为守护进程保证nodejs服务不宕机。 总体来说就是有性能监控、自动重启、负载均衡的作用。 pm2-logrotate介绍 pm2本身是可以输出日志文件的,默认的文件路径: error log path │ /home/username/.pm2/logs/app-error-0.log out log path │ /home/username/.pm2/logs/app-out-0.log 但是pm2的日志文件不能自动分割,这会导致一个文件不断变大,不但影响性能,查看这些日志也会带来麻烦。所以需要pm2-logrotate来实现自动分割日志。 安装pm2-logrotate pm2 install pm2-logrotate,是用pm2命令,不是npm命令 pm2-logrotate配置 max_size (默认 10M): 最大为多少时进行分割,例如: 10G, 10M, 10K retain (Defaults to all): This number is the number of rotated logs that are keep at any one time, it means that if you have retain = 7 you will have at most 7 rotated logs and your current one. compress (默认 false): 是否压缩日志 dateFormat (默认 YYYY-MM-DD_HH-mm-ss) : 日志格式 rotateModule (Defaults to true) : Rotate the log of pm2’s module like other apps workerInterval (Defaults to 30 in secs) : You can control at which interval the worker is checking the log’s size (minimum is 1) rotateInterval (Defaults to 0 0 * * * everyday at midnight): This cron is used to a force rotate when executed.

在docker中运行nodejs

月盾
首先看项目目录: 再看Dockerfile文件内容: # 以最新的node为基础镜像 FROM hub.c.163.com/library/node:latest # 工作目录为app WORKDIR /app # 拷贝当前所在项目根目录到app目录 COPY . /app # 全局安装pm2 RUN npm install pm2 -g EXPOSE 8081 #使用pm2启动nodejs,如果没有--no-daemon参数docker启动后就退出 CMD ["pm2-runtime", "dest/server.js", "--no-daemon"] # ENTRYPOINT ["node", "server.js"] 或者在Dockerfile中不添加CMD命令,可以在启动docker时执行命令: docker run --name ks -ti -p 8081:8081 kser:pm2 pm2-runtime dest/server.js 如果是后台运行的docker: docker run --name ks -d -p 8081:8081 kser:pm2 pm2-runtime dest/server.js -d选项是后台运行 需要进入到docker查看pm2运行情况 ,可以通过docker exec -ti ks /bin/sh查看运行的容器内部情况 要不要在docker中使用pm2运行nodejs pm2可以监控nodejs进程,如果进程挂了,可以自动重启 pm2可以设置启动的nodejs进程个数,提高服务性能 pm2可以设置日志记录 pm2可以设置端口,避免端口冲突 docker已经提供了自动重启的功能,可以这样启动nodejs服务: docker run --name ks -d --restart=always -p 8081:8081 kser:pm2 pm2-runtime dest/server.

pm2的fork模式和cluster模式的区别

月盾
pm2的fork模式和cluster模式的区别 fork模式 pm2默认启动的是fork模式,是以单核单进程运行的,在fork模式下可以直接运行coffee-script,PHP,python。 参考:http://pm2.keymetrics.io/docs/tutorials/using-transpilers-with-pm2 运行非js语言必须设置运行模式为fork_mode cluster模式 cluster模式可以根据CPU数量进行实例扩展,可以开启多进程而不需要修改代码。可提高程序性能和可靠性。类似于分布式系统,只不过是在单台机器上开启多实例,而pm2自带负载均衡。 ** 使用方法 ** pm2 start app.js -i max 使用-i参数,max代表最大CPU进程数 也可以使用文件方式: processes.json文件: { "apps" : [{ "script" : "api.js", "instances" : "max", "exec_mode" : "cluster" }] } pm2 start processes.json

推荐在Nodejs使用的java常用技术和工具

月盾
作为Nodejs开发者可能会对java中常用的一些技术工具不太关心,主要原因大概除了语言级别的间隙就是Nodejs相对于java来说比较轻量级,大多用来开发简单系统,用不到其他工具。根据经验来说,开发相同功能的系统,Nodejs的开发周期和代码体量上也会比Java少太多,毕竟java出生年代长,生态丰富,如果不使用几个框架都感觉不是在开发系统。而Nodejs要开发一个web系统基本使用express或koa就差不多够了。所以对于Nodejs开发者来说,分布式,消息队列,远程调用等技术接触就少些。当然,不用这些技术其实也不会有太大影响,但是对于一个有追求有理想的码农来说我们的眼界不应该局限于系统能运行就行。 下面就来介绍一些可以在nodejs中使用的JAVA常用工具和技术。 elasticsearch ElasticSearch(以下简称ES)是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。看见了吧,这就是一个使用JAVA开发的全文搜索引擎,到底有什么用呢?就是可以提供像google和百度一样的搜索功能,就算不需要这样功能,也可以用于管理后台的字段搜索,大家知道数据库的搜索效率比较差,有索引的字段还好,没有的就很慢了,这时ES就可以派上用场了,把数据同步进ES,不论查询列表还是以字段搜索都是极快的,是redis缓存的很好补充。 ELK ELK(ElasticSearch,LogStash,Kibana)是三个工具的组合, Logstash是一款轻量级的日志搜集处理框架,可以方便的把分散的、多样化的日志搜集起来,并进行自定义的处理,然后传输到指定的位置,比如某个服务器或者文件。 Kibana是一个使用nodejs开发的web应用,用于查询和操作ES,就是一个ES的图形界面。 如果nodejs系统布署在多台服务器,那么查看日志是件很头疼的事,你不知道请求发送到哪一台服务器,需要挨个查看,如果服务超过5台,这绝对是噩梦。这时候ELK就是很好的解决方案,LogStash收集每一台服务器的日志统一存到ES中,利用ES的优点,查询任何关键字都很快很方便。 消息中间件(kafka) 拿用户注册为例,需要发送邮件,短信,这两个服务之间本没有关联关系,但我们的一贯作风是用户注册的时候调用邮件服务,短信服务,严谨一点会放在事务中操作,假如一个服务失败可能会让事务回滚,所有操作都失败。这是一种情况,另一种情况是如果要再注册后加积分,那么就得改代码,要是有更多服务要添加就得每次改代码发布,启停服务,不送积分了又要删代码,这就是耦合度太高导致的结果。使用消息中间件不仅能保证服务完整性还可以有效解耦,有兴趣可以去了解kafka,rabbitMQ,roketMQ等消息中间件。 远程调用RPC 通俗的来讲就是两台服务器A和B,A服务器直接调用B服务器上的函数,如果没有一个具体事例很难理解A服务器怎么可能调用到B服务器的函数,感兴趣可下载尝试:https://github.com/yuedun/nodejs-grpc 那么为什么要用rpc呢?A服务器要调用B服务的资源直接用http提供接口不就行了吗?其实http也算是一种远程调用,而且也比较简单直观,但是其效率较低,调用成本高,三次握手耗时,甚至请求头的数据量比请求体还大。那么就需要一种更高效的调用协议了——rpc。为什么需要RPC,而不是简单的HTTP接口 总结:以上的这些工具和技术和语言并没有绑定,java可以使用,nodejs也可以使用,推荐理由:投入成本小,使用收益高。

请求发送到Nodejs服务器以后响应非常慢

月盾
在项目中遇到过这种情况:请求发送后迟迟没有响应,但也并没有报错,在代码中一步步调试都没有发现问题,明明已经走到最后返回数据一步了客户端却没有返回。原因是在中间件中使用了redis或memcache缓存,然后连接缓存服务失败,然后就会一直等待,直到连接缓存超时才会继续执行后续操作。

sequelize外键关联报错SequelizeDatabaseError: Cant write; duplicate key in table #sql-454_d

月盾
Assistance.belongTo(User)会报以下错误: ALTER TABLE `assistance` ADD CONSTRAINT `assistance_user_id_foreign_idx` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE; Unhandled rejection SequelizeDatabaseError: Can't write; duplicate key in table '#sql-454_d' at Query.formatError (/home/hale/workspace/issue-tracking-system/node_modules/sequelize/lib/dialects/mysql/query.js:223:16) at Query.connection.query [as onResult] (/home/hale/workspace/issue-tracking-system/node_modules/sequelize/lib/dialects/mysql/query.js:55:23) at Query.Command.execute (/home/hale/workspace/issue-tracking-system/node_modules/mysql2/lib/commands/command.js:30:12) at Connection.handlePacket (/home/hale/workspace/issue-tracking-system/node_modules/mysql2/lib/connection.js:515:28) at PacketParser.onPacket (/home/hale/workspace/issue-tracking-system/node_modules/mysql2/lib/connection.js:94:16) at PacketParser.executeStart (/home/hale/workspace/issue-tracking-system/node_modules/mysql2/lib/packet_parser.js:77:14) at Socket.<anonymous> (/home/hale/workspace/issue-tracking-system/node_modules/mysql2/lib/connection.js:102:29) at emitOne (events.js:115:13) at Socket.emit (events.js:210:7) at addChunk (_stream_readable.js:264:12) at readableAddChunk (_stream_readable.js:251:11) at Socket.Readable.push (_stream_readable.js:209:10) at TCP.onread (net.

typescript开发sequelize返回ModelInstance或null值无法获取属性值

月盾
从图片中的代码可以看出 let userRecord = await item.getUser();获取到的是Bluebird<UserInstance | null>类型,然后在下面获取对象属性的时候报错,错误信息是: [ts] Object is possibly 'null'. let userRecord: UserInstance | null 说对象可能是null,所以无法获取其中的属性。遇到这种情况请设置typescript的编译选项,tsconfig.json文件中的 "strictNullChecks": true, /* Enable strict null checks. */ 默认是true,即严格null检查,设置为falsse即可。

sequelize.js不能将驼峰camelCased命名转化为下划线underscored命名

月盾
定义Model的时候有这样两个参数: underscored,underscoredAll, Converts all camelCased columns to underscored if true. Will not affect timestamp fields named explicitly by model options and will not affect fields with explicitly set field option 其意思是说转化所有驼峰字段为下划线字段,但实际情况并不是如此。 var Model = sequelize.define<ModelInstance, ModelAttributes>( 'Assistance', { title: Sequelize.STRING, description: Sequelize.STRING, fullName: Sequelize.STRING }, { underscored: true, tableName: 'assistance', charset: 'utf8', collate: 'utf8_unicode_ci' } ); CREATE TABLE IF NOT EXISTS `assistance` (`id` INTEGER NOT NULL auto_increment , `title` VARCHAR(255), `description` VARCHAR(255), `fullName` VARCHAR(255), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; 其中fullName字段在创建表的时候还是和模型中保持一致,并没有转换为full_name,underscored的设置其实只影响到了createdAt和updatedAt等内置字段。 在github上也有相关讨论:https://github.

Unhandled rejection CastError: Cast to ObjectId failed for value catalog at path _id for model Blog

月盾
Unhandled rejection CastError: Cast to ObjectId failed for value "catalog" at path "_id" for model "Blog" at MongooseError.CastError (e:\workspace\yuedun_ts\node_modules\mongoose\lib\error\cast.js:27:11) at ObjectId.cast (e:\workspace\yuedun_ts\node_modules\mongoose\lib\schema\objectid.js:149:13) at ObjectId.SchemaType._castForQuery (e:\workspace\yuedun_ts\node_modules\mongoose\lib\schematype.js:1064:15) at ObjectId.castForQuery (e:\workspace\yuedun_ts\node_modules\mongoose\lib\schema\objectid.js:189:15) at ObjectId.SchemaType.castForQueryWrapper (e:\workspace\yuedun_ts\node_modules\mongoose\lib\schematype.js:1021:15) at cast (e:\workspace\yuedun_ts\node_modules\mongoose\lib\cast.js:269:32) at Query.cast (e:\workspace\yuedun_ts\node_modules\mongoose\lib\query.js:3103:12) at Query._castConditions (e:\workspace\yuedun_ts\node_modules\mongoose\lib\query.js:1144:10) at Query._findOne (e:\workspace\yuedun_ts\node_modules\mongoose\lib\query.js:1346:8) at e:\workspace\yuedun_ts\node_modules\mongoose\node_modules\kareem\index.js:250:8 at e:\workspace\yuedun_ts\node_modules\mongoose\node_modules\kareem\index.js:23:7 at nextTickCallbackWith0Args (node.js:489:9) at process._tickCallback (node.js:418:13) From previous event: at Query.exec (e:\workspace\yuedun_ts\node_modules\mongoose\lib\query.js:2897:17) at Query.then (e:\workspace\yuedun_ts\node_modules\mongoose\lib\query.js:2945:15) at Object.default_1 [as default] (e:\workspace\yuedun_ts\utils\viewer-log.ts:21:10) at e:\workspace\yuedun_ts\app.ts:53:14 at Layer.