Node.js真的有高并发优势吗?看看Node.js和Tomcat的并发测试结果
Node.js真的有高并发优势吗?看看Node.js和Tomcat的并发测试结果
同一套业务逻辑,实现一个webservice中间接口,中间涉及memcached和mogodb的一些操作。 分别在Node.js和JAVA平台实现,java代码部署在Tomcat 7.0上,用Apache jmeter进行压力测试。 得到的测试结果很是出乎意料,Node.js的高并发优势为什么没有体现出来呢???
**操作系统:**CentOS 6.4(虚拟机) **内存:**1.5G **CPU:**单核
并发数 100 **ramp-up period(in seconds)**1 执行次数 10
以下是测试结果: Lable #Sample Average Median 90%Line Min Max Error% Throughput KB/sec Node.js HTTP请求 1000 333 369 485 1 956 0.0 183.3180568285976 40.995932630614114 Tomcat HTTP请求 1000 48 9 188 2 563 0.0 183.4862385321101 58.414564220183486
可以看到Node.js的平均执行时间为333毫秒,Tomcat的执行时间为48毫秒,Tomcat比Node.js快了近7倍!
补充1:即使是测试接口直接返回,不涉及后续的操作,Tomcat也比Node.js快了很多,求各位大神给个解释。
补充2:修改jmeter 的 ramp-up period的测试条件,比如这个值增大(如10秒),node.js的执行效率变高了,但这么想来也是违背了高并发的特性
抛砖引玉,一起探讨问题。如果你也感兴趣,不妨拿出点时间来写一段程序测试一下,我希望能得到不一样的结果。
Node.js真的有高并发优势吗?
测试背景
在同一套业务逻辑下,实现一个web service中间接口,该接口涉及memcached和mongodb的一些操作。分别在Node.js和Java平台上实现,Java代码部署在Tomcat 7.0上,使用Apache JMeter进行压力测试。
测试环境
- 操作系统: CentOS 6.4(虚拟机)
- 内存: 1.5G
- CPU: 单核
测试配置
- 并发数: 100
- ramp-up period (in seconds): 1
- 执行次数: 10
测试结果
Lable | #Sample | Average | Median | 90%Line | Min | Max | Error% | Throughput | KB/sec |
---|---|---|---|---|---|---|---|---|---|
Node.js HTTP请求 | 1000 | 333 | 369 | 485 | 1 | 956 | 0.0 | 183.3180568285976 | 40.995932630614114 |
Tomcat HTTP请求 | 1000 | 48 | 9 | 188 | 2 | 563 | 0.0 | 183.4862385321101 | 58.414564220183486 |
结果分析
从上述数据可以看出,Node.js的平均执行时间为333毫秒,而Tomcat的执行时间仅为48毫秒,Tomcat比Node.js快了近7倍!
补充说明
-
即使测试接口直接返回,不涉及后续操作,Tomcat也比Node.js快了很多。这可能是因为Node.js在处理HTTP请求时,由于其异步非阻塞I/O模型,在某些情况下可能无法充分利用单核CPU的优势,导致性能不如预期。
-
当
ramp-up period
增大时(例如设置为10秒),Node.js的执行效率会提高。这是因为增大ramp-up period
使得每个请求之间的间隔增加,从而减少了并发压力,这与高并发特性相矛盾。
示例代码
Node.js 示例代码
const http = require('http');
const memcached = require('memcached');
const MongoClient = require('mongodb').MongoClient;
const server = http.createServer(async (req, res) => {
try {
const memcachedClient = new memcached('localhost:11211');
const db = await MongoClient.connect('mongodb://localhost:27017/test', { useNewUrlParser: true, useUnifiedTopology: true });
// 模拟memcached操作
memcachedClient.get('key', (err, data) => {
if (err) throw err;
console.log(data);
});
// 模拟mongodb操作
const collection = db.collection('documents');
const doc = await collection.findOne({ name: 'test' });
console.log(doc);
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World\n');
} catch (error) {
console.error(error);
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Internal Server Error\n');
}
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
Java 示例代码(使用Spring Boot)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/test")
public String test() {
return "Hello World";
}
}
总结
尽管Node.js在理论上具备高并发优势,但在特定环境下(如单核CPU)可能并不明显。为了更好地利用Node.js的优势,可以考虑优化代码逻辑、合理分配资源以及使用多核处理器等方法。
执行次数 10 是啥意思
100个线程循环执行10次
你确定你写的测试对?
呵呵
业务逻辑没有问题,返回结果正确。
有何高见?
上代码吧 还有测试工具
代码不方便,框架用的是express,日志模块log4js,启动的时候初始化mecached和mongodb连接池,后续的就是业务逻辑操作了。解释了好多次了,即使把业务逻辑去掉,单纯测试接口返回,性能都差了不少。
你这比较对象首先就有问题, 你应该用nginx 跟 Tomcat 比. Node.js 应该跟 java 对比
omg
Node.js可以看成是web服务器
100个并发没有意义,压测至少上千至上万才有会有意义, 就像select在低连线数时,效率可能会比epoll更高。
后面的业务逻辑在生产环境,现在想用node替换一个中间件,看来效果并不是很好。100个并发效率这么低的话,再大的并发也不适用于真实场景了。
你指的成千上万是1秒时间内么?
看来没有几个人懂测试。。。
谁告诉你我渲染页面了? app.post("/",function(req, res){ //时间戳校验 res.json({“status”:“error”,“code”:“C_0008”,“msg”:“您的时间戳超时”}); }) 好吧,我就写个时间戳校验太挫了,占用了node大部分的处理时间。
请指教
我的意思是你可能100个并发node要300ms, tomcat只要50ms 但1000个并发 node可能500ms tomcat却要300ms 10000个并发 node可能1000ms tomcat却要3000ms 当然我数据是随意写的 只是想表达如果你只是少量并发,用甚么框架不会有太大差异。 你不能因此推论更高并发也必定会有相同结果
node.js框架用的是express,日志模块log4js,启动的时候初始化mecached和mongodb连接池,后续的就是业务逻辑操作. java是SpringMVC+mybatis。 测试过程有memcached和mongodb操作,也有独立的接口操作,不涉及数据库操作。 代码不方便发。
帮顶。我也挺感兴趣的,因为毕竟无业务的纯压node是没有意义的,用在业务场景上才有效。当然,业务背景也不能过于复杂,否则就没有可比性了。 感觉这更像2套方案的比较,跟node还是tomcat没有太直接的关系。 建议去掉memcached和mybatis,且后端都用mysql。这样缓存能减少更多,也就排除掉更多的因素了。
我来发个方便发代码的 机器是笔记器i5 2g内存 win7 nodejs express2.5 代码
app.get('/bench',function(req,res){
var sql="";
var i=0;
do {
sql=‘select * from user ‘;
sql+=’ left join userinfo’;
sql+=’ using(uid) ‘;
sql+=’ where age> 20 and salary > 3000’;
}while (i++ < 30);
res.write(sql);
res.end();
});
ab -n 10000 -c 200 http://localhost:3000/bench
Server Software: Server Hostname: localhost Server Port: 3000
Document Path: /bench Document Length: 83 bytes
Concurrency Level: 200 Time taken for tests: 32.205495 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 1810000 bytes HTML transferred: 830000 bytes Requests per second: 310.51 [#/sec] (mean) Time per request: 644.110 [ms] (mean) Time per request: 3.221 [ms] (mean, across all concurrent requests) Transfer rate: 54.87 [Kbytes/sec] received
Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 1.6 2 40 Processing: 27 630 177.1 537 1148 Waiting: 5 326 210.2 305 1042 Total: 27 632 177.6 537 1150 tomcat8
public class RequestBench extends HttpServlet {
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
int i=0;
StringBuilder sb;
do {
sb = new StringBuilder();
sb.append(“select * from user “);
sb.append(” left join userinfo”);
sb.append(" using(uid) “);
sb.append(” where age> 20 and salary > 3000");
} while (i++ < 30);
out.println(sb);
}
@Override
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
doGet(request, response);
}
}
ab -n 10000 -c 200 http://localhost:8080/examples/bench/
Server Software: Apache-Coyote/1.1 Server Hostname: localhost Server Port: 8080
Document Path: /examples/bench/ Document Length: 85 bytes
Concurrency Level: 200 Time taken for tests: 38.352093 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 2510000 bytes HTML transferred: 850000 bytes Requests per second: 260.74 [#/sec] (mean) Time per request: 767.042 [ms] (mean) Time per request: 3.835 [ms] (mean, across all concurrent requests) Transfer rate: 63.91 [Kbytes/sec] received
Connection Times (ms) min mean[+/-sd] median max Connect: 0 2 1.8 2 70 Processing: 28 750 218.5 693 1313 Waiting: 7 393 251.2 367 1272 Total: 30 752 219.2 695 1315 有服务器的可以把代码放到服务器上重复验证一下测试结果
其实我有这样的测试方案,因为在res.json返回前会有一些校验,最先是校验时间戳,我只要时间戳控制一下就不会走会面的业务流程了,包括memcached、mongodb、mybatis等。 但是就单纯的测试校验返回node的性能也不是很高。
不太了解ab测试,不知道这200个并发10000个请求是否跟jmeter一样在准备线程的时候有个ramp-up period参数,即线程的创建时间,如果这个时间值越大,node的性能就能高一些,就像补充2里提到的,这违背了node.js高并发的特性。
从你提供的测试结果来看,Node.js 并未展现出预期中的高并发性能优势。这可能与多种因素有关,包括但不限于操作系统的资源限制、硬件配置以及具体的测试方法。
以下是一些可能的原因及改进建议:
1. 硬件资源
- 单核 CPU 和低内存配置:Node.js 是单线程模型,适合于处理大量 I/O 密集型任务。但是,在单核 CPU 和 1.5GB 内存的限制下,可能无法充分发挥其异步非阻塞的优势。相比之下,Java 的多线程机制在该环境下表现更好。
2. 测试环境
- JMeter 配置:
ramp-up period
参数设置对测试结果影响较大。如果ramp-up period
过短,可能导致请求堆积,影响性能。你提到增加ramp-up period
后 Node.js 表现有所改善,这表明 JMeter 在短时间内的请求调度可能存在问题。
3. 示例代码
为了更好地理解性能差异,我们可以比较两者的实现方式。
Node.js 示例
const http = require('http');
const memcached = require('memcached');
const MongoClient = require('mongodb').MongoClient;
const server = http.createServer(async (req, res) => {
const memcachedClient = new memcached('localhost:11211');
const dbUrl = 'mongodb://localhost:27017/test';
try {
const [memcachedResult] = await memcachedClient.get('key');
const db = await MongoClient.connect(dbUrl);
const collection = db.db().collection('testCollection');
const dbResult = await collection.findOne({});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ memcachedResult, dbResult }));
} catch (err) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end(err.message);
}
});
server.listen(3000, () => console.log('Server running at http://localhost:3000/'));
Java 示例(使用 Spring Boot)
@RestController
public class MyController {
@Autowired
private MemcachedClient memcachedClient;
@Autowired
private MongoTemplate mongoTemplate;
@GetMapping("/api")
public ResponseEntity<?> handleRequest() {
String memcachedResult = (String) memcachedClient.get("key");
MyDocument dbResult = mongoTemplate.findById("id", MyDocument.class);
return ResponseEntity.ok(Map.of("memcachedResult", memcachedResult, "dbResult", dbResult));
}
}
总结
- 如果希望 Node.js 发挥出高并发的优势,建议使用多核服务器,并优化操作系统的 I/O 调度策略。
- 另外,考虑使用 Node.js 的集群模块来利用多核 CPU。
- 对比代码实现,注意 Node.js 和 Java 在处理并发时的不同方式。