应用层网关调研与基础测试

背景

故事的开始还是源于现阶段各个平台之间有些许的接口调用,但是各个平台之间又相对比较独立,并没有将各个平台整合成一组微服务的需求。现在面临的问题就是各个平台直接都有互相调用的需求,如果各个平台有业务升级的情况下,不一定能察觉到是否会影响到对提供给其他系统使用的API,而且还有一个问题就是大家好像都不是太清楚哪些平台调用了哪些接口,某个平台对外提供了哪些接口,而且在一些脚本使用的过程中如果部署的系统迁移而需要重新修改访问的地址,基于这些原因考虑搭建一个openapi相关的服务,主要是来管理与监控各个调用链,本次就先测试对比一下openapi的技术模型的选择。

技术对比

由于本人的技术栈相对比较局限,针对一些管理性能要求不高的项目用Python,golang通常都是用于不紧急的项目开发,会一丢丢的rust(入门水平)。本次的对比测试主要选用golang,Python。

本次测试的环境是一台八核,6G内存的虚拟机(虚拟机不要太较真性能)。

服务器端性能测试对比
Flask测试

测试代码如下;

from flask import Flask, jsonify

import requests


app = Flask(__name__)


@app.route('/api/test_api/')
def hello_world():
    return jsonify({"detail": "ok"})


if __name__ == '__main__':
    app.run()

虽然代码简单了点,但是我们用gunicorn来部署测试;

gunicorn -w 8 -b 0.0.0.0:5001 flask_server:app -k gevent

压测结果

wrk -t4 -c1000 -d60s -T60s --latency http://127.0.0.1:5001/api/test_api/
Running 1m test @ http://127.0.0.1:5001/api/test_api/
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   225.39ms   89.72ms 864.75ms   71.70%
    Req/Sec     1.11k   138.23     2.50k    70.48%
  Latency Distribution
     50%  199.30ms
     75%  269.94ms
     90%  358.11ms
     99%  453.80ms
  265775 requests in 1.00m, 43.85MB read
Requests/sec:   4423.37
Transfer/sec:    747.31KB

可以看出在开启多进程服务的情况下,qps达到了4423。并且性能响应基本上在毫秒级别。

Tornado测试

tornado测试代码如下;

from tornado.ioloop import IOLoop
import tornado.web
import tornado.httpserver
import aiohttp
from tornado.platform.asyncio import AsyncIOMainLoop
import asyncio


class MainHandler(tornado.web.RequestHandler):
    async def get(self):
        self.write({"detail": "ok"})


if __name__ == "__main__":
    app = tornado.web.Application([
        (r"/api/test_api/", MainHandler),
    ])
    server = tornado.httpserver.HTTPServer(app)
    server.bind(5002, '127.0.0.1')
    server.start(8)
    AsyncIOMainLoop().install()
    IOLoop.current().start()

启动脚本并压测

wrk -t4 -c1000 -d60s -T60s --latency http://127.0.0.1:5002/api/test_api/
Running 1m test @ http://127.0.0.1:5002/api/test_api/
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   207.38ms   57.48ms 468.47ms   67.41%
    Req/Sec     1.20k   298.66     2.13k    68.44%
  Latency Distribution
     50%  205.08ms
     75%  239.01ms
     90%  280.76ms
     99%  367.11ms
  286385 requests in 1.00m, 59.54MB read
Requests/sec:   4764.90
Transfer/sec:      0.99MB

从数据上来看比flask部署之后达到的qps相比还是要高大约三百左右的qps,在本运行的实例代码中,开启了八个工作进程来进行处理任务,并且在flask的部署过程中也选用了异步io,在work进程数相同的情况下,qps相差不多也算是情理之中。

golang测试

在golang的测试就不选用现成的web框架了,直接通过http库来测试。

package main

import (
    "fmt"
		"encoding/json"
    "log"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
        var res map[string]string
        res = make(map[string]string)
        res["detail"] = "ok"
        js, err := json.Marshal(res)
        if err != nil {
                fmt.Println("erro ", err)
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
        }
        w.Header().Set("Content-type", "application/json")
        w.Write(js)
}

func main() {
    http.HandleFunc("/api/test_api/", handler)
    log.Fatal(http.ListenAndServe(":5003", nil))
}

运行并压测;

/wrk -t4 -c1000 -d60s -T60s --latency http://127.0.0.1:5003/api/test_api/
Running 1m test @ http://127.0.0.1:5003/api/test_api/
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    20.91ms   19.55ms 316.02ms   83.64%
    Req/Sec    13.48k     2.66k   23.70k    69.61%
  Latency Distribution
     50%   14.44ms
     75%   26.04ms
     90%   46.21ms
     99%   94.57ms
  3221474 requests in 1.00m, 377.89MB read
Requests/sec:  53597.81
Transfer/sec:      6.29MB

这样对比来看qps达到了53597,性能相对比较强悍。不可否认在服务端方面go的性能还是比较厉害的。通过三种服务端的测试来看,在Python中按照常见的部署模式来部署flask和tornado进行压测,测试数据基本上在四千多左右,在golang中仅用库来实现服务qps就达到五万了。

应用层API网关测试

使用的openapi说到底目前也是利用应用层来转发的,此时我们依然来通过不同的方法来测试。

在本次的测试用,默认测试的API就是刚刚测试的golang作为后端服务,只不过部署的端口更改为8084.

Flask测试

测试代码如下

from flask import Flask, jsonify

import requests


app = Flask(__name__)


@app.route('/api/test_api/')
def hello_world():
    resp = requests.get("http://192.168.10.205:8084/api/test_api/")
    return jsonify(resp.json())

if __name__ == '__main__':
    app.run()

作为网关服务主要向后端转发请求并获取返回请求并将结果返回。此时的部署方式仍然如下,其中192.168.10.205:8084就是刚刚启动的golang的后端;

gunicorn -w 8 -b 0.0.0.0:5001 flask_gateway:app -k gevent

进行压测

wrk -t4 -c1000 -d60s -T60s --latency http://127.0.0.1:5001/api/test_api/
Running 1m test @ http://127.0.0.1:5001/api/test_api/
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.45s   534.19ms   4.31s    86.27%
    Req/Sec   174.11     49.82   356.00     68.96%
  Latency Distribution
     50%    1.29s
     75%    1.48s
     90%    2.44s
     99%    3.24s
  41188 requests in 1.00m, 6.80MB read
Requests/sec:    685.89
Transfer/sec:    115.88KB

通过压测结果可知,对应的sqs为675左右。

Tornado测试

tornado测试的时候选用异步编程的方式来进行测试

from tornado.ioloop import IOLoop
import tornado.web
import tornado.httpserver
import aiohttp
from tornado.platform.asyncio import AsyncIOMainLoop
import asyncio


class MainHandler(tornado.web.RequestHandler):
    async def get(self):
        async with aiohttp.ClientSession() as session:
            async with session.get('http://192.168.10.205:8084/api/test_api/') as resp:
                text = await resp.text()
        self.write(text)


if __name__ == "__main__":
    app = tornado.web.Application([
        (r"/api/test_api/", MainHandler),
    ])
    server = tornado.httpserver.HTTPServer(app)
    server.bind(5002, '127.0.0.1')
    server.start(8)
    AsyncIOMainLoop().install()
    IOLoop.current().start()

此时运行该脚本并压测

wrk -t4 -c1000 -d60s -T60s --latency http://127.0.0.1:5002/api/test_api/
Running 1m test @ http://127.0.0.1:5002/api/test_api/
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.59s     4.26s   21.13s    86.18%
    Req/Sec   295.72    209.62     1.00k    59.10%
  Latency Distribution
     50%  736.92ms
     75%    1.19s
     90%    9.51s
     99%   17.92s
  60957 requests in 1.00m, 12.27MB read
  Non-2xx or 3xx responses: 1283
Requests/sec:   1014.86
Transfer/sec:    209.11KB

压测的数据显示大约的qps在一千左右,但是在测试过程中会出现连接耗尽的情况,主要是session没有复用导致每次都重新创建。

golang测试
package main

import (
        "fmt"
        "io/ioutil"
        "net/http"
)


func TestHandler(w http.ResponseWriter, r *http.Request){
        // 访问其他api
        resp, err := http.Get("http://192.168.10.205:8084/api/test_api/")
        if err != nil {
                fmt.Println("error ", err)
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
        }
        defer resp.Body.Close()
        body, _ := ioutil.ReadAll(resp.Body)
        w.Header().Set("Content-type", "application/json")
        w.Write(body)

}

func main(){
        http.HandleFunc("/api/test_api/", TestHandler)
        http.ListenAndServe(":5003", nil)
}

压测结果如下,同样在压测过程中也会报连接耗尽的情况。

wrk -t4 -c1000 -d60s -T60s --latency http://127.0.0.1:5003/api/test_api/
Running 1m test @ http://127.0.0.1:5003/api/test_api/
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.21s     3.11s   18.58s    81.84%
    Req/Sec   705.42      0.99k    3.09k    72.16%
  Latency Distribution
     50%  181.07ms
     75%    3.85s
     90%    7.26s
     99%   11.84s
  89857 requests in 1.00m, 11.21MB read
  Non-2xx or 3xx responses: 4312
Requests/sec:   1495.52
Transfer/sec:    191.13KB

在三种情况下的压测结果表明,flask的网关转发效果较差,只有六百多,在tornado和golang的压测过程中,会出现connect: cannot assign requested address的错误,就是可用的连接不够用了,这再一定程度上影响了压测的结果,不过这个问题不在本次的讨论范围里面,因为在这次压测的代码里面就没有优化每次访问api时重用连接的情况。以tornado为例,我们修改成复用连接的情况看一下效果是否会好一些(只是举例有一定代码上可以优化的空间)

from tornado.ioloop import IOLoop
import tornado.web
import tornado.httpserver
import aiohttp
from tornado.platform.asyncio import AsyncIOMainLoop
import asyncio

session_global = None


class MainHandler(tornado.web.RequestHandler):
    async def get(self):
        global session_global
        if session_global is None:
            session = aiohttp.ClientSession()
            session_global = session
        resp = await session_global.get("http://192.168.10.205:8084/api/test_api/")
        self.write(await resp.text())


if __name__ == "__main__":

    app = tornado.web.Application([
        (r"/api/test_api/", MainHandler),
    ])
    server = tornado.httpserver.HTTPServer(app)
    server.bind(5002, '127.0.0.1')
    server.start(8)
    AsyncIOMainLoop().install()
    IOLoop.current().start()

然后压测一下

wrk -t4 -c1000 -d60s -T60s --latency http://127.0.0.1:5002/api/test_api/
Running 1m test @ http://127.0.0.1:5002/api/test_api/
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   434.61ms  109.19ms   1.23s    67.93%
    Req/Sec   568.01    170.52     1.27k    70.68%
  Latency Distribution
     50%  434.11ms
     75%  506.77ms
     90%  572.08ms
     99%  718.92ms
  135647 requests in 1.00m, 27.17MB read
Requests/sec:   2257.32
Transfer/sec:    462.93KB

可以看出如果优化了连接的话,qps上升了大约一倍。假如再修改一下golang版本的复用问题;

package main

import (
        "time"
        "fmt"
        "io/ioutil"
        "net/http"
)

var netClient *http.Client

func TestHandler(w http.ResponseWriter, r *http.Request){
        // 访问其他api
        resp, err := netClient.Get("http://192.168.10.205:8084/api/test_api/")
        if err != nil {
                fmt.Println("error ", err)
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
        }
        defer resp.Body.Close()
        body, _ := ioutil.ReadAll(resp.Body)
        w.Header().Set("Content-type", "application/json")
        w.Write(body)

}

func main(){
        netClient = &http.Client{Transport: &http.Transport{MaxIdleConnsPerHost: 300},
                Timeout: time.Duration(30) * time.Second}
        http.HandleFunc("/api/test_api/", TestHandler)
        http.ListenAndServe(":5003", nil)
}

此时再压测;

wrk -t4 -c1000 -d60s -T60s --latency http://127.0.0.1:5003/api/test_api/
Running 1m test @ http://127.0.0.1:5003/api/test_api/
  4 threads and 1000 connections



  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    78.84ms   34.58ms 430.97ms   76.15%
    Req/Sec     3.20k   441.89     4.69k    71.33%
  Latency Distribution
     50%   87.34ms
     75%   99.63ms
     90%  110.86ms
     99%  155.67ms
  763434 requests in 1.00m, 89.55MB read
Requests/sec:  12703.00
Transfer/sec:      1.49MB

可以看到优化之后的qps能够达到一万两千多,当然这个优化只是针对单个后端服务的访问,并没有太大的实用意义。

其中这三个版本对应的情况可能不太一样,Flask在当做应用层网关转发的时候,主要就是通过阻塞IO去访问后端的接口的,虽然在Flask接受请求的时候利用的异步IO,但是接受到请求之后又同步阻塞去请求这样会影响性能,而在Tornado的版本中,则全部都使用了异步IO来进行数据的处理,这样通过全部异步操作来提升响应速度,从压测结果来看Tornado的异步版本相比Flask提升了快一倍,最后查看的golang的版本在代码上无需异步处理,运行的过程中就是全部异步处理,想过也是比较好,响应比Tornado快了好几倍,如果考虑最后的连接复用的情况下,快了大概有6倍。通过不同版本的测试对比,可以看出如果做网关或者服务在并发要求较高的情况下,选择golang是比较好的选择。

总结

本文主要是对比了再服务端,特别是Python中Flask和Tornado的常规的部署情况下的一个压测情况,并且在应用层网关的压测过程中对比了一下,API网关设计的过程中的基本情况,对比了Python的阻塞转发,异步转发,最后也压测了golang版本的情况。初步来看的话,如果选择设计API网关如果性能要求很高可以选择golang这个方向来继续调研,如果针对内部系统qps不高、追求开发效率的话,Python也是一个备选方案。由于本人才疏学浅,如有错误请批评指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值