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