locust+geventhttpclient 性能优化

  上一篇讲述了 Locust 的单进程,多进程,分布式的使用,在压测的时候发现客户机 cpu 基本接近100%,当服务器资源还很空闲,客户机已先达到瓶颈了。下文使用同一台客户机(8核8g)压测网关,对比优化前和优化后的效果。

 

一、locust 自带 httpclient 

locust 自带 client 是 requests 库的,这个库功能很强大,也是最常用的,但性能很一般。

测试脚本:

from locust import HttpUser, TaskSet, task, between

# 任务
class UserTask(TaskSet):
    def on_start(self):
        '''初始化数据'''
        pass

    @task
    def test(self):
        self.client.get("/sz/api2/test")

    def on_stop(self):
        '''销毁数据'''
        pass

#class WebsiteUser(HttpLocust):
#    host = 'https://10.1.62.133'
#    task_set = UserTask
#    wait_time = between(0, 0)
    
# locust 2.15版本去掉了HttpLocust类,使用HttpUser替代
class WebsiteUser(HttpLocust):
    host = 'https://10.1.62.133'
    tasks = [UserTask]
    wait_time = between(0, 0)

压测场景一:

1个进程,100 个 user。平均 qps:625

 CPU 单核占用 100%

 

压测场景二:

 8个进程,100 个 user。平均 qps:4847

 CPU 8核 平均使用率 100%

 

二、locust + geventhttpclient

    geventhttpclient  GitHub 地址:https://github.com/locustio/geventhttpclient 

1)geventhttpclient 中的 httpclient 使用协程实现,性能相对 requests 库提升5~6倍。然后使用 events(事件)重新定义客户端,events(事件)最终会把压测中产生的数据输出到 UI 界面。作为压测脚本,我们要尽可能减少不必要的逻辑。

from locust import HttpLocust, TaskSet, task, between, events
import time,json,os,sys,socket
from locust.exception import LocustError
from geventhttpclient import HTTPClient
from geventhttpclient.url import URL


host = 'http://10.1.62.133'
# 任务
class UserTask(TaskSet):
    def on_start(self):
        '''初始化数据'''
        url = URL(host)
        # 若为https请求,ssl设置为True
        self.http = HTTPClient(url.host,url.port,ssl=False,connection_timeout=20,network_timeout=20)

    @task
    def test(self):
        try:
            start_time = time.time()
            # get 请求
            res = self.http.get("/sz/api2/test")
            # post 请求示例
            # body = json.dumps({"username":"admin","password":"123456"})
            # res = self.http.post("/sz/api2/login",body = body)
            data = res.read()
            end_time = time.time()
            response_time =int((end_time - start_time)*1000)
            response_length = len(data)
            assert json.loads(data)['Error'] == 0

            if res.status_code == 200:
                events.request_success.fire(request_type="GET", name="test_success", response_time = response_time, response_length=response_length)

        except AssertionError:
            end_time = time.time()
            response_time =int((end_time - start_time)*1000)
            events.request_failure.fire(request_type="GET", name="test_failure", response_time=response_time,response_length=0,
                                        exception="断言错误。status_code:{}。接口返回:{}。".format(res.status_code,json.loads(data)))

        except socket.timeout:
            events.locust_error.fire(locust_instance=UserTask, exception='Timeout', tb =sys.exc_info()[2])

        except Exception as e:
            events.locust_error.fire(locust_instance=UserTask, exception='Error:{}。\nstatus_code:{}。\n接口返回:{}。'.format(e,res.status_code,data), tb=sys.exc_info()[2])

    def on_stop(self):
        '''运行结束,关闭http/https连接'''
        self.http.close()

class WebsiteUser(HttpLocust):
    host = host
    task_set = UserTask
    wait_time = between(0, 0)   

Locust 2.15版本已经移除了 events 事件中的 request_success、request_failure 功能,新版统一用 request 替代,异常事件用 user_error 。

from locust import HttpUser, task, events
import time,os,sys,socket
from geventhttpclient import HTTPClient
from geventhttpclient.url import URL


class WebsiteUser(HttpUser):
    abstract = True
    host = 'http://127.0.0.1:8000'
    min_wait = 0
    max_wait = 0

# 任务
class UserTask(WebsiteUser):
    def on_start(self):
        '''初始化数据'''
        url = URL(self.host)
        # 若为https请求,ssl设置为True
        self.http = HTTPClient(url.host, url.port, ssl=False, connection_timeout=20, network_timeout=20)
        self.url = '/ai/book'

    @task
    def test(self):
        try:
            start_time = time.time()
            res = self.http.get(self.url)
            # post 请求示例
            # body = json.dumps({"username":"admin","password":"123456"})
            # res = self.http.post("/sz/api2/login",body = body)
            data = res.read()
            end_time = time.time()
            total_time = end_time - start_time
            print(data)
            assert res.status_code == 200
            events.request.fire(request_type="http", name=self.url, response_time=total_time * 1000,
                                response_length=len(data), response=data)
        except AssertionError:
            events.request.fire(request_type="http", name=self.url, response_time=total_time * 1000,
                                response_length=0, exception=f"状态码错误,response:{data}")
        
        except OSError as e:
            events.user_error.fire(user_instance=UserTask, exception=e, tb=sys.exc_info()[2])
            
        except socket.timeout:
            events.user_error.fire(user_instance=UserTask, exception='Timeout', tb=sys.exc_info()[2])
        
        except Exception as e:
            events.user_error.fire(user_instance=UserTask, exception=e, tb=sys.exc_info()[2])


    def on_stop(self):
        '''销毁数据'''
        self.http.close()



if __name__ == "__main__":
    os.system('locust -f stress_test_http.py --web-port 8090')

 

2)其实还有一种简单的实现方式,官网上有案例,代码和 locust+requests 一样。需要安装geventhttpclient,然后将 WebsiteUser 继承的超类换成 FastHttpLocust类 即可。但实际测试下来不如 案例1 来的高效(差别还蛮大的),原因可能是 events(事件)处理不如自定义效率高。

from locust import TaskSet, task, between
from locust.contrib.fasthttp import FastHttpLocust

# 任务
class UserTask(TaskSet):
    def on_start(self):
        '''初始化数据'''
        pass

    @task
    def test(self):
        self.client.get("/sz/api2/test")

    def on_stop(self):
        '''销毁数据'''
        pass

class WebsiteUser(FastHttpLocust):
    host = 'https://10.1.62.133'
    task_set = UserTask
    wait_time = between(0, 0)

 

 压测场景一:

 1个进程,100 个 user。平均 qps:3948

  CPU 单核占用也接近 100%,看起来是单进程客户端瓶颈了

 

压测场景二:

8个进程,100 个 user。平均 qps:9424

 CPU 8核平均使用率 60% 左右

 

总结一下:

        通过下面表格可以看出,locust + geventhttpclient 性能提升幅度还是挺大的,单进程提升5倍多,开8个slave后 CPU占用在60%。

 httpclient方式 单进程qps CPU使用率(单进程模式) 多进程qps(8个进程) CPU使用率 (多进程模式)
locust + requests  625 100%(单核)  4847  100%(平均)
locust + geventhttpclient  3948 100%(单核)  9424  60%(平均)

 

 

 

 

       

  以下是用 jmeter 在同一台客户机100线程压测结果,平均qps在9000左右,比 locust 稍低。而且 locust 在高并发能力方面比 jmeter 强多了,所以推荐使用 locust 作为压测工具。当然 jmeter 的优势也很多,比如:使用 GUI 创建脚本很方便,各种统计、图形报告很丰富,众多插件支持,这个仁者见仁,智者见智哈。 

 

 

posted @ 2020-03-12 16:17  三只松鼠  阅读(4175)  评论(0编辑  收藏  举报