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的执行效率变高了,但这么想来也是违背了高并发的特性

抛砖引玉,一起探讨问题。如果你也感兴趣,不妨拿出点时间来写一段程序测试一下,我希望能得到不一样的结果。


26 回复

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倍!

补充说明

  1. 即使测试接口直接返回,不涉及后续操作,Tomcat也比Node.js快了很多。这可能是因为Node.js在处理HTTP请求时,由于其异步非阻塞I/O模型,在某些情况下可能无法充分利用单核CPU的优势,导致性能不如预期。

  2. 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 对比

Node.js可以看成是web服务器

100个并发没有意义,压测至少上千至上万才有会有意义, 就像select在低连线数时,效率可能会比epoll更高。

后面的业务逻辑在生产环境,现在想用node替换一个中间件,看来效果并不是很好。100个并发效率这么低的话,再大的并发也不适用于真实场景了。

你指的成千上万是1秒时间内么?

不上图你说个卵 1, 你的express可能没启用模板编译缓存 2, 你写的代码实在是太挫

你要测试性能 应该要这样 require(‘http’).createServer(function(req, res){ //do sth… }).listen(80); 而不是需要渲染一堆页面, 没有测试的意义

你肯定会反驳… 请随意…

看来没有几个人懂测试。。。

谁告诉你我渲染页面了? 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和Tomcat的性能请用 <pre> var http = require(‘http’); http.createServer(function (req, res) { … }).listen(80); </pre>

如果比node和java的性能 请上代码 或 简单说一下各自用的框架和怎么实现的

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 在处理并发时的不同方式。
回到顶部