Đa phần các dev quan tâm đến việc các khung công nghệ và công nghệ khác nhau hoạt động như thế nào, nhưng phần lớn các đánh giá benchmark được tìm thấy qua internet đang xem xét chỉ là ví dụ Hello World.

Khi bạn đang xây dựng một ứng dụng thực tế, có nhiều khía cạnh cần xem xét, vì vậy tôi quyết định chạy một điểm chuẩn hoàn chỉnh giữa các khung và công nghệ phổ biến nhất.

Bên cạnh hiệu suất, tôi quan tâm đến việc dễ dàng đạt được các nhiệm vụ cụ thể trong từng khung và chi phí để nhân rộng hiệu suất của ứng dụng

Các ứng cử viên

  • Laravel 5, PHP 7.0, Nginx

  • Lumen 5, PHP 7.0, Nginx (laravel micro service)

  • Express JS 4, Node.js 8.1, PM2

  • Django, Python 2.7, Gunicorn

  • Spring 4, Java, Tomcat

  • .NET Core, Kestrel

Chúng ta đang thử nghiệm điều gì

Chúng ta quan tâm đến số lượng requests/second (kết nối đồng thời) mà mỗi chương trình đạt được trên các cấu hình máy chủ khác nhau cùng với mức độ clean hoặc ngắn gọn của mã.

Thông tin cấu hình máy chủ

Chúng ta cũng quan tâm đến cách mỗi khung quy mô hiệu suất của nó và giá của việc đạt được hiệu suất đó là bao nhiêu. Đây là lý do tại sao chúng ta sẽ kiểm tra chúng trên 3 cấu hình máy chủ khác nhau bằng DigitalOcean:

  • 1 CPU, 512 MB - $ 5 / tháng
  • 4 CPU, 8 GB - $ 80 / tháng
  • 12 CPU, 32 GB - $ 320 / tháng

Chúng ta làm gì

Tôi muốn kiểm tra một ví dụ ứng dụng thực tế để chúng tôi về cơ bản xây dựng API Web REST hiển thị 4 điểm cuối, mỗi điểm có độ phức tạp khác nhau:

  • Hello World - chỉ cần trả lời bằng chuỗi JSON chứa chuỗi Hello World.

  • Tính toán - tính 10.000 số Fibonacci đầu tiên.

  • Danh sách đơn giản - chúng tôi có cơ sở dữ liệu MySQL chứa bảng quốc gia và chúng tôi sẽ liệt kê tất cả các quốc gia.

  • Danh sách phức tạp - chúng tôi thêm bảng người dùng cùng với ánh xạ nhiều-nhiều giữa người dùng và quốc gia và chúng tôi muốn liệt kê tất cả người dùng đã truy cập vào Pháp, cùng với tất cả các quốc gia mà mỗi người truy cập.

Để xây dựng hai điểm cuối cuối cùng, chúng tôi sẽ sử dụng các công cụ mà mỗi khung cung cấp để đạt được mục tiêu của chúng tôi theo cách dễ nhất có thể.

Làm thế nào chúng ta kiểm tra chúng

Để kiểm tra chúng, chúng ta sẽ sử dụng cả hai công cụ đo điểm benchmark wrk và ab để kiểm tra xem chúng ta có nhận được kết quả tương tự hay không và cũng làm thay đổi sự đồng thời của các yêu cầu để mỗi công nghệ có thể đạt được tiềm năng tối đa. Các công cụ này sẽ chạy trên droplet được tạo trên DigitalOcean để chúng không cạnh tranh trên tài nguyên máy chủ với ứng dụng API thực tế. Ngoài ra, máy chủ được sử dụng để đặt các yêu cầu kiểm tra và máy chủ được sử dụng để chạy ứng dụng được liên kết bằng IP riêng của họ, do đó, sẽ không có bất kỳ độ trễ mạng đáng chú ý nào.

Kết quả Benchmark

Dưới đây bạn có thể thấy các kết quả được nhóm theo từng điểm cuối và bạn cũng có thể kiểm tra trên một biểu đồ duy nhất về cách mỗi khung được chia tỷ lệ trên các cấu hình máy chủ khác nhau.

Mã nguồn dùng để test API

Dưới đây là các bộ điều khiển thực tế được sử dụng cho từng khung để đưa ra ý tưởng về cách mã trông như thế nào. Bạn cũng có thể kiểm tra toàn bộ mã có sẵn trên Github.

Laravel và Lumen với 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);
    }
}

Express JS với Node.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 với 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 với 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();
        }
    }
}

Kết luận

Lưu ý rằng trong một ứng dụng trong thế giới thực, hầu hết tất cả các yêu cầu tương tác với cơ sở dữ liệu, không có lựa chọn nào là tệ và tất cả chúng đều có thể xử lý các yêu cầu của hầu hết các ứng dụng web.

Tuy nhiên, hiệu năng của Node.js với Express JS khá đáng chú ý. Nó cạnh tranh với các công nghệ như Java và .NET Core hoặc thậm chí vượt trội hơn chúng và kết hợp với sự đơn giản của Javascript ES6 mà bạn có thể sử dụng với Node.js 8, nó cung cấp rất nhiều khả năng.

Về khả năng mở rộng ứng dụng, hiệu suất tốt nhất trên mỗi chi phí đã đạt được trên máy chủ kích thước trung bình. Thêm 12 nhân và 32 GB bộ nhớ đã giúp quá nhiều. Có thể, trong trường hợp này, cổ chai ở một nơi khác hoặc nó yêu cầu điều chỉnh tốt để mở khóa tiềm năng máy chủ đầy đủ.

Bạn nghĩ sao?

Nếu bạn thấy kết quả thú vị, vui lòng nhấp vào nút so để người khác cũng có thể thấy chúng. Bạn có thể tìm thấy tất cả các mã nguồn ở đây:

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

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