首发于葡葡萄萄
rust async 的威力

rust async 的威力

Go 程序员都知道,Gin 是一个优秀的框架,性能也不错,最近有时间折腾 rust ,正好拿 rust 做个对比。

从一个简单的程序开始:获取所有用户,即从数据库查询所有用户并以 json 格式返回,涉及到的 SQL 语句很简单:SELECT * from users

我们先来看一看 Gin 的性能。

gin

从 wrk 压测的结果看,并发 200 的时候,发现有 39 个 socket errors,虽然 Requests/sec 较高,但是 P99 Latency 有将近 200ms。

$ wrk -t12 -c200 -d30s --latency 'http://localhost:8044/users'
Running 30s test @ http://localhost:8044/users
  12 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    23.27ms   37.23ms 522.07ms   89.88%
    Req/Sec     1.28k   210.01     2.04k    69.86%
  Latency Distribution
     50%    9.35ms
     75%   23.02ms
     90%   61.12ms
     99%  185.32ms
  460547 requests in 30.03s, 1.34GB read
  Socket errors: connect 0, read 39, write 0, timeout 0
Requests/sec:  15336.57
Transfer/sec:     45.66MB

actix-web with web::block

web::block 把数据库操作这种重头戏交给额外的线程池来做,看看它的性能如何。

$ wrk -t12 -c200 -d30s --latency 'http://localhost:8080/users'
Running 30s test @ http://localhost:8080/users
  12 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    17.21ms    9.58ms 143.62ms   83.57%
    Req/Sec     0.97k   165.38     1.34k    69.94%
  Latency Distribution
     50%   14.53ms
     75%   20.91ms
     90%   28.48ms
     99%   51.47ms
  346792 requests in 30.04s, 1.00GB read
  Socket errors: connect 0, read 33, write 0, timeout 0
Requests/sec:  11545.85
Transfer/sec:     34.18MB

虽然 Requests/sec 为 11545,不及 Gin (15336),但是 P99 Latency 降到不到 Gin 的三分之一(51ms)。

async pg

我们都知道 db 操作是比较耗时,我们可以通过 deedpool 来管理 postgres 的连接,把上述放在 actix::web::block 中的数据库操作变成 async

$ wrk -t12 -c200 -d30s --latency 'http://localhost:9999/users'
Running 30s test @ http://localhost:9999/users
  12 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    11.91ms    1.52ms  37.31ms   81.02%
    Req/Sec     1.35k   115.16     1.57k    72.22%
  Latency Distribution
     50%   11.50ms
     75%   12.54ms
     90%   13.76ms
     99%   17.14ms
  483408 requests in 30.02s, 1.12GB read
  Socket errors: connect 0, read 18, write 0, timeout 0
Requests/sec:  16103.34
Transfer/sec:     38.33MB

可以看到,P99 Latency 达到了惊人的 17ms,Requests/sec 达到 16103。

对于 async pg,还有什么理由不用呢?

代码

gin

package main

import (
	"encoding/json"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq"
	"github.com/pkg/errors"
)

type DB struct {
	*sqlx.DB
}

func NewDB(dbType, dsn string, maxOpen, maxIdle int) *DB {
	db, err := sqlx.Connect(dbType, dsn)
	if err != nil {
		panic("fail to connect database,error:" + err.Error())
	}
	db.SetMaxOpenConns(maxOpen)
	db.SetMaxIdleConns(maxIdle)
	return &DB{db}
}

type User struct {
	ID        uint64     `db:"id" json:"id"`
	FirstName string     `db:"first_name" json:"first_name"`
	LastName  string     `db:"last_name" json:"last_name"`
	Gender    string     `db:"gender" json:"gender"`
	Birthday  *time.Time `db:"birthday" json:"birthday"`
}

func GetUsers(ext sqlx.Ext) ([]User, error) {
	feeds := []User{}
	sql := `SELECT * from users`
	if err := sqlx.Select(ext, &feeds, sql); err != nil {
		return feeds, errors.WithMessage(err, "GetUsers")
	}
	return feeds, nil
}

func HandleUsers(ctx *HandlerContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		defer r.Body.Close()
		db := ctx.DB
		feeds, err := GetUsers(db)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		b, err := json.Marshal(feeds)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write(b)
	}
}

func main() {
	db := NewDB("postgres", "postgres://localhost/actix-postgres-test?sslmode=disable", 30, 10)
	r := gin.New()
	handlerCtx := &HandlerContext{
		DB: db,
	}
	r.GET("/users", gin.WrapF(HandleUsers(handlerCtx)))
	s := &http.Server{
		Addr:           ":8044",
		Handler:        r,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	s.ListenAndServe()
}

actix-web with web::block

handler

#[get("/users")]
async fn get_users(
    pool: web::Data<DbPool>,
) -> Result<HttpResponse, Error> {
    let conn = pool.get().expect("couldn't get db connection from pool");
    // use web::block to offload blocking Diesel code without blocking server thread
    let users = web::block(move || dao::get_users(&conn))
        .await
        .map_err(|e| {
            eprintln!("{}", e);
            HttpResponse::InternalServerError().finish()
        })?;
    Ok(HttpResponse::Ok().json(users))
}

db

use diesel::prelude::*;
use crate::models;

/// Run query using Diesel to query all users and return the result.
pub fn get_users(conn: &PgConnection) -> Result<Vec<models::User>, diesel::result::Error> {
    use crate::schema::users::dsl::*;
    let allusers = users.load::<models::User>(conn)?;
    Ok(allusers)
}

actix-web with async pg

handler

use crate::{db, errors::MyError, models::User};
use actix_web::{web, Error, HttpResponse};
use deadpool_postgres::{Client, Pool};

pub async fn get_users(
    db_pool: web::Data<Pool>,
) -> Result<HttpResponse, Error> {
    let client: Client = db_pool.get().await.map_err(|err| MyError::PoolError(err))?;
    let users = db::get_users(&client).await?;
    Ok(HttpResponse::Ok().json(users))
}

db

use crate::{errors::MyError, models::User};
use deadpool_postgres::Client;
use tokio_pg_mapper::FromTokioPostgresRow;

pub async fn get_users(client: &Client) -> Result<Vec<User>, MyError> {
    let _stmt = "SELECT * from users";
    let stmt = client.prepare(&_stmt).await.unwrap();
    let users = client
        .query(
            &stmt, &[],
        )
        .await?
        .iter()
        .map(|row| User::from_row_ref(row).unwrap())
        .collect::<Vec<User>>();
    Ok(users)
}

微信搜索「葡葡萄萄」关注我获得最新文章

发布于 2020-02-21 15:36