採用Locust對grpc協議進行壓測

1.Locust學習

詳情可以參考:Locust官方文檔【中文翻譯】

 

2.grpc協議

詳情可以參考:gRPC 官方文檔中文版V1.0

gRPC 是什麼?

在 gRPC 裏客戶端應用可以像調用本地對象一樣直接調用另一臺不同的機器上服務端應用的方法,使得您能夠更容易地創建分佈式應用和服務。與許多 RPC 系統類似,gRPC 也是基於以下理念:定義一個服務,指定其能夠被遠程調用的方法(包含參數和返回類型)。在服務端實現這個接口,並運行一個 gRPC 服務器來處理客戶端調用。在客戶端擁有一個存根能夠像服務端一樣的方法。

gRPC 客戶端和服務端可以在多種環境中運行和交互 - 從 google 內部的服務器到你自己的筆記本,並且可以用任何 gRPC 支持的語言來編寫。所以,你可以很容易地用 Java 創建一個 gRPC 服務端,用 Go、Python、Ruby 來創建客戶端。此外,Google 最新 API 將有 gRPC 版本的接口,使你很容易地將 Google 的功能集成到你的應用裏。

使用 protocol buffers

gRPC 默認使用 protocol buffers,這是 Google 開源的一套成熟的結構數據序列化機制(當然也可以使用其他數據格式如 JSON)。正如你將在下方例子裏所看到的,你用 proto files 創建 gRPC 服務,用 protocol buffers 消息類型來定義方法參數和返回類型。你可以在 Protocol Buffers 文檔找到更多關於 Protocol Buffers 的資料。

3.採用Locust對grpc協議進行壓測

  • 準備proto文件
# helloworld.proto

syntax = "proto3";
option java_package = "io.grpc.examples";
package helloworld;

// The greeter service definition.
service Greeter {
    // Sends a greeting
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request meesage containing the user'name.
message HelloRequest {
    string name = 1;
}

// The response message containing the greetings
message HelloReply {
    string message = 1;
}

  • 使用 protoc 編譯 proto 文件, 生成 python 語言的實現

注:需要安裝python下的protoc編譯器,安裝命令如下:

# 安裝 python 下的 protoc 編譯器
pip install grpcio-tools

然後生成proto文件,命令如下:

# 編譯 proto 文件
python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. helloworld.proto

解釋:

#python -m grpc_tools.protoc: python 下的 protoc 編譯器通過 python 模塊(module) 實現, #所以說這一步非常省心
#--python_out=. : 編譯生成處理 protobuf 相關的代碼的路徑, 這裏生成到當前目錄
#--grpc_python_out=. : 編譯生成處理 grpc 相關的代碼的路徑, 這裏生成到當前目錄
#-I. helloworld.proto : proto 文件的路徑, 這裏的 proto 文件在當前目錄

注:

比如我想將指定文件夾./grpc_proto/test下helloworld.proto文件編譯結果存放到指定文件夾./grpc_client/test目錄下,可以採用如下寫法:

python -m grpc_tools.protoc -I./grpc_proto/test --python_out=./grpc_client/test/ --grpc_python_out=./grpc_client/test/ helloworld.proto

而不是下面的這種寫法, 這樣的話會在./grpc_client/test多生成兩層文件夾./grpc_proto/test。

python -m grpc_tools.protoc -I. --python_out=./grpc_client/test/ --grpc_python_out=./grpc_client/test/ ./grpc_proto/testhelloworld.proto

正常情況下編譯後生成的2個代碼文件:,分別如下:

  1. helloworld_pb2.py: 用來和 protobuf 數據進行交互
  2. helloworld_pb2_grpc.py: 用來和 grpc 進行交互
  • 編寫helloworld的grpc實現
import sys
import grpc
import time
sys.path.append("..")

from concurrent import futures
from helloworld.helloworld import helloworld_pb2, helloworld_pb2_grpc

_ONE_DAY_IN_SECONDS = 60 * 60 * 24
_HOST = 'localhost'
_PORT = '50052'
class Greeter(helloworld_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        str = request.name
        return helloworld_pb2.HelloReply(message='hello, '+str)

def serve():
    grpcServer = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), grpcServer)
    grpcServer.add_insecure_port(_HOST + ':' + _PORT)
    grpcServer.start()

    try:
        while True:
            time.sleep(_ONE_DAY_IN_SECONDS)
    except KeyboardInterrupt:
        grpcServer.stop(0)

if __name__ == '__main__':
    serve()

在終端啓動,如下:

python helloworld_server.py
  • 編寫Locust文件,實現對端口50052的grpc服務進行壓測,內容如下:
#!/usr/bin/env python 2.7
# -*- coding:utf-8 -*-

import os,sys, argparse
sys.path.append("..")
import grpc
import json
import requests
import time
import random
import string

import helloworld_pb2, helloworld_pb2_grpc
from locust import Locust, TaskSet, task, events
from locust.stats import RequestStats
from locust.events import EventHook


def parse_arguments():
    parser = argparse.ArgumentParser(prog='locust')
    parser.add_argument('--hatch_rate')
    parser.add_argument('--master', action='store_true')
    args, unknown = parser.parse_known_args()
    opts = vars(args)
    return args.host, int(args.clients), int(args.hatch_rate)

#HOST, MAX_USERS_NUMBER, USERS_PRE_SECOND = parse_arguments()
HOST, MAX_USERS_NUMBER, USERS_PRE_SECOND = '127.0.0.1:50052', 1, 10

# 分佈式,需要啓多臺壓測時,有一個master,其餘爲slave
slaves_connect = []
slave_report = EventHook()
ALL_SLAVES_CONNECTED = True
SLAVES_NUMBER = 0
def on_my_event(client_id, data):
    global ALL_SLAVES_CONNECTED
    if not ALL_SLAVES_CONNECTED:
        slaves_connect.append(client_id)
    if len(slaves_connect) == SLAVES_NUMBER:
        print "All Slaves Connected"
        ALL_SLAVES_CONNECTED = True
#        print events.slave_report._handlers
        header = {'Content-Type': 'application/x-www-form-urlencoded'}
import resource

rsrc = resource.RLIMIT_NOFILE
soft, hard = resource.getrlimit(rsrc)
print 'RLIMIT_NOFILE soft limit starts as : ', soft

soft, hard = resource.getrlimit(rsrc)
print 'RLIMIT_NOFILE soft limit change to: ', soft

events.slave_report += on_my_event

class GrpcLocust(Locust):
    def __init__(self, *args, **kwargs):
        super(GrpcLocust, self).__init__(*args, **kwargs)


class ApiUser(GrpcLocust):
    min_wait = 90
    max_wait = 110
    stop_timeout = 100000

    class task_set(TaskSet):
        def getEnviron(self, key, default):
            if key in os.environ:
                return os.environ[key]
            else:
                return default

        def getToken(self):
            consumer_key = self.getEnviron('SELDON_OAUTH_KEY', 'oauthkey')
            consumer_secret = self.getEnviron('SELDON_OAUTH_SECRET', 'oauthsecret')
            params = {}

            params["consumer_key"] = consumer_key
            params["consumer_secret"] = consumer_secret

            url = self.oauth_endpoint + "/token"
            r = requests.get(url, params=params)
            if r.status_code == requests.codes.ok:
                j = json.loads(r.text)
                # print j
                return j["access_token"]
            else:
                print "failed call to get token"
                return None

        def on_start(self):
            # print "on start"
#            self.oauth_endpoint = self.getEnviron('SELDON_OAUTH_ENDPOINT', "http://127.0.0.1:50053")
#            self.token = self.getToken()
            self.grpc_endpoint = self.getEnviron('SELDON_GRPC_ENDPOINT', "127.0.0.1:50052")
            self.data_size = int(self.getEnviron('SELDON_DEFAULT_DATA_SIZE', "784"))

        @task
        def get_prediction(self):
            conn = grpc.insecure_channel(self.grpc_endpoint)
            client = helloworld_pb2_grpc.GreeterStub(conn)
            start_time = time.time()
            name = string.join(random.sample(['a','b','c','d','e','f','g'], 3)).replace(" ","")
            request = helloworld_pb2.HelloRequest(name=name)
            try:
                reply = client.SayHello(request)
            except Exception,e:
                total_time = int((time.time() - start_time) * 1000)
                events.request_failure.fire(request_type="grpc", name=HOST, response_time=total_time, exception=e)
            else:
                total_time = int((time.time() - start_time) * 1000)
                events.request_success.fire(request_type="grpc", name=HOST, response_time=total_time, response_length=0)
  • 啓動Locust,查看結果
locust -f locustFile.py

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章