首页
Preview

在真实应用中进行Web REST API基准测试

作为一名网络自由职业者,我对不同的框架和技术的表现很感兴趣,但大部分在互联网上找到的基准测试只考虑了“Hello World”示例。

当你构建一个真实的应用程序时,有更多的方面需要考虑,因此我决定在最受欢迎的框架和技术之间运行完整的基准测试。

除了性能外,我还对每个框架中实现特定任务的简易程度以及扩展应用程序性能的成本感兴趣。

候选人是谁

  • Laravel 5、PHP 7.0、Nginx
  • Lumen 5、PHP 7.0、Nginx
  • Express JS 4、Node.js 8.1、PM2
  • Django、Python 2.7、Gunicorn
  • Spring 4、Java、Tomcat
  • .NET Core、Kestrel

我们测试什么

我们对每个框架在不同服务器配置下实现的每秒请求数感兴趣,以及代码的简洁或冗长程度。

服务器配置如何

我们还关心每个框架如何扩展其性能以及实现这种性能的成本。这就是为什么我们将在使用DigitalOcean时在3个不同的服务器配置上对它们进行测试:

  • 1个CPU、512 MB - $5 / 月
  • 4个CPU、8 GB - $80 / 月
  • 12个CPU、32 GB - $320 / 月

我们在构建什么

我们想要测试一个真实的应用程序示例,因此我们将基本上构建一个Web REST API,它公开4个端点,每个端点具有不同的复杂性:

  • Hello World -简单地响应一个包含“Hello World”字符串的JSON。
  • 计算 -计算前10,000个斐波那契数。
  • 简单列表 -我们有一个包含“countries”表的MySQL数据库,并将列出所有国家。
  • 复杂列表 -我们添加了一个“users”表以及用户和国家之间的多对多映射,并希望列出访问法国的所有用户以及每个用户访问的所有国家。 为了构建最后两个端点,我们将使用每个框架提供的工具以最简单的方式实现我们的目标。

我们如何测试它们

为了测试它们,我们将同时使用wrkab HTTP基准测试工具,以检查是否获得类似的结果,并变化请求的并发性,以便每个技术都能达到其最大潜力。

这些工具将在它们自己的 droplet 上运行,这些 droplet 是在DigitalOcean上创建的,因此它们不会与实际的API应用程序竞争服务器资源。

此外,用于放置测试请求的服务器和用于运行应用程序的服务器使用其私有IP链接,因此不会有任何明显的网络延迟。

基准测试结果

下面你可以看到按每个端点分组的结果,你还可以在单个图表上检查每个框架在不同服务器配置上的扩展情况。

API是如何构建的

以下是用于每个框架的实际控制器,以了解代码的外观。你还可以在Github上查看整个代码。

Laravel和Lumen与PHP

<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Controller as BaseController;
use App\User;
use App\Country;

class Controller extends BaseController
{
    public function hello() 
    {
        return response()->json(['hello' => 'world']);
    }

    public function compute()
    {
        $x = 0; $y = 1;
        $max = 10000 + rand(0, 500);

        for ($i = 0; $i <= $max; $i++) {
            $z = $x + $y;
            $x = $y;
            $y = $z;
        }

        return response()->json(['status' => 'done']);
    }

    public function countries()
    {
        $data = Country::all();
        return response()->json($data);
    }

    public function users()
    {
        $data = User::whereHas('countries', function($query) {
                        $query->where('name', 'France');
                    })
                    ->with('countries')
                    ->get();

        return response()->json($data);
    }
}

使用Node.js的Express JS

const Country = require('../Models/Country');
const User = require('../Models/User');

class Controller 
{
    hello(req, res) {
        return res.json({ hello: 'world' });
    }

    compute(req, res) {
        let x = 0, y = 1;
        let max = 10000 + Math.random() * 500;

        for (let i = 0; i <= max; i++) {
            let z = x + y;
            x = y;
            y = z;
        }

        return res.json({ status: 'done' })
    }

    async countries(req, res) {
        let data = await Country.fetchAll();
        return res.json({ data });
    }

    async users(req, res) {
        let data = await User.query(q => {
                q.innerJoin('UserCountryMapping', 'User.id', 'UserCountryMapping.userId');
                q.innerJoin('Country', 'UserCountryMapping.countryId', 'Country.id');
                q.groupBy('User.id');
                q.where('Country.name', 'France');
            })
            .fetchAll({
                withRelated: ['countries']
            })

        return res.json({ data });
    }
}

module.exports = new Controller();

Django与Python

from django.http import JsonResponse
from random import randint
from models import Country, User, UserSerializer, CountrySerializer

def hello(req):
    return JsonResponse({ 'hello': 'world' })

def compute(req):
    x = 0
    y = 1
    max = 10000 + randint(0, 500)

    for i in range(max):
        z = x + y
        x = y
        y = z

    return JsonResponse({ 'status': 'done' })

def countries(req):
    data = Country.objects.all()
    data = CountrySerializer(data, many=True).data

    return JsonResponse({ 'data': list(data) })

def users(req):
    data = User.objects.filter(usercountrymapping__countries__name='France').all()
    data = UserSerializer(data, many=True).data

    return JsonResponse({ 'data': list(data) })

Spring与Java

package app;

import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;

import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import model.Country;
import model.User;

@RestController
public class Controller {
    private SessionFactory sessionFactory;
    
    public Controller(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @RequestMapping(value = "/hello", produces = "application/json")
    public String hello() throws JSONException {
        return new JSONObject().put("hello", "world").toString();
    }
    
    @RequestMapping(value = "/compute", produces = "application/json")
    public String compute() throws JSONException {
        long x = 0, y = 1, z, max;
        Random r = new Random();
        max = 10000 + r.nextInt(500);
        
        for (int i = 0; i <= max; i++) {
            z = x + y;
            x = y;
            y = z;
        }
        
        return new JSONObject().put("status", "done").toString();
    }
    
    @RequestMapping(value = "/countries", produces = "application/json")
    @Transactional
    public List<Country> countries() throws JSONException {
        List<Country> data = (List<Country>) sessionFactory.getCurrentSession()
                .createCriteria(Country.class)
                .list();
        return data;
    }
    
    @RequestMapping(value = "/users", produces = "application/json")
    @Transactional
    public List<User> users() throws JSONException {
        List<User> data = (List<User>) sessionFactory.getCurrentSession()
                .createCriteria(User.class)
                .createAlias("countries", "countriesAlias")
                .add(Restrictions.eq("countriesAlias.name", "France"))
                .list();
        return data;
    }
}

.NET Core

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using dotnet_core.Models;
using dotnet_core.Data;

namespace dotnet_core.Controllers
{
    public class MyController : Controller
    {
        private readonly ApplicationDbContext context;

        public MyController(ApplicationDbContext context)
        {
            this.context = context;
        } 

        [HttpGet]
        [Route("/hello")]
        public IEnumerable<string> hello()
        {
            return new string[] { "hello", "world" };
        }

        [HttpGet]
        [Route("/compute")]
        public IEnumerable<string> compute()
        {
            int x = 0, y = 1, z, max;
            Random r = new Random();
            max = 10000 + r.Next(500);

            for (int i = 0; i <= max; i++) {
                z = x + y;
                x = y;
                y = z;
            }

            return new string[] { "status", "done" };
        }

        [HttpGet]
        [Route("/countries")]
        public IEnumerable<Country> countries()
        {
            return context.Country.ToList();
        }

        [HttpGet]
        [Route("/users")]
        public object users()
        {
            return context.UserCountryMapping
                    .Where(uc => uc.country.name.Equals("France"))
                    .Select(uc => new {
                        id = uc.user.id,
                        firstName = uc.user.firstName,
                        lastName = uc.user.lastName,
                        email = uc.user.email,
                        countries = uc.user.userCountryMappings.Select(m => m.country)
                    })
                    .ToList();
        }
    }
}

结论

考虑到在实际的应用程序中,几乎所有请求都与数据库交互,因此没有一个选择是糟糕的,所有选择都可以处理大多数Web应用程序的要求。

然而,Node.js与Express JS的性能相当显着。它与Java和.NET Core等技术竞争,甚至胜过它们,并且结合了使用Node.js 8本地支持的Javascript ES6的简单性,提供了如此多的功能。

关于应用程序的可扩展性,中型服务器获得了性价比最佳的性能。添加12个核心和32 GB的内存并没有太多帮助。也许,在这种情况下,瓶颈在其他地方,或者需要进行微调才能释放完整的服务器潜力。

你怎么看?

如果你发现结果有趣,请点击💚️按钮,以便其他人也可以看到。你可以在此处找到所有源代码:

https://github.com/mihaicracan/web-rest-api-benchmark

译自:https://medium.com/@mihaigeorge.c/web-rest-api-benchmark-on-a-real-life-application-ebb743a5d7a3

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
阿波
The minute I see you, I want your clothes gone!

评论(0)

添加评论