TCP 百萬併發 資料連線測試 python+locust

大话性能發表於2024-10-08

TCP 百萬併發 資料連線測試 python+locust

過程筆記和總結

嘗試一、locust 測試百萬 Tcp 併發

另一種方式是使用 jmeter

基礎環境

  • 服務端

虛擬機器:Centos7.2

jdk 1.8

  • 客戶端

虛擬機器: Centos7.2

python : 3.7.3 Anaconda3

locust : 0.14.5


基礎知識:

  • tcp 協議:三次握手進行連線,四次揮手斷開,穩定長連線,比 Http 更佔用資源,比 udp 協議更穩定,保證資料不丟失,但速度比較慢。
  • 每個 tcp 連線大概佔用 4kb 記憶體,且斷開連線後預設兩分鐘之後才會釋放資源
  • linux 開啟檔案限制 (open-files)
  • linux 埠限制
  • tcp-socket 網路程式設計
  • python 中類繼承 super 用法
  • python 中傳送 16 進位制資料包

Q&A

客戶端調優

  1. 客戶端埠限制導致的 tcp 連線不超過 4000,透過修改開啟檔案限制來增加可使用的埠數量,注意修改後需要重啟生效,具體配置參考
--   /etc/sysctl.conf 

# 系統級別最大開啟檔案
fs.file-max = 100000

# 單使用者程序最大檔案開啟數
fs.nr_open = 100000

# 是否重用, 快速回收time-wait狀態的tcp連線
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1

# 單個tcp連線最大快取byte單位
net.core.optmem_max = 8192

# 可處理最多孤兒socket數量,超過則警告,每個孤兒socket佔用64KB空間
net.ipv4.tcp_max_orphans = 10240

# 最多允許time-wait數量
net.ipv4.tcp_max_tw_buckets = 10240

# 從客戶端發起的埠範圍,預設是32768 61000,則只能發起2w多連線,改為一下值,可一個IP可發起差不多6.4w連線。
net.ipv4.ip_local_port_range = 1024 65535
--  /etc/security/limits.conf 

# 最大不能超過fs.nr_open值, 分別為單使用者程序最大檔案開啟數,soft指軟性限制,hard指硬性限制
* soft nofile 100000
* hard nofile 100000
root soft nofile 100000
root hard nofile 100000

服務端調優

  1. 服務端需要接受的連結數需要超過 100w,根據需要修改虛擬機器配置;
  • 最佳化區域性控制代碼限制

檢視 ulimit-n

soft 和 hard 為兩種限制方式,其中 soft 表示警告的限制,hard 表示真正限制,nofile 表示開啟的最大檔案數。整體表示任何使用者一個程序能夠開啟 1000000 個檔案。注意語句簽名有 號 表示任何使用者

--  /etc/security/limits.conf
# 文末加兩行
*hard nofile 1000000
soft nofile 1000000

shutdown -r now 重啟並檢視生效

  • 突破全域性控制代碼的限制
    cat /proc/sys/fs/file-max
    file-max 表示在 linux 中最終所有 x 執行緒能夠開啟的最大檔案數

sudo vi /etc/sysctl.conf

# 在文末增加
fs.file-max=1000000

讓檔案生效

sudo sysctl -p


Locust 分散式說明

因單臺 node 最大 tcp 連線 6w,故測試百萬連線需要對 Locust 做分散式處理;

Locust 分散式注意點:

每臺機器上都要有 client 檔案,且必須相同

slave 虛擬機器環境、Locust 版本、python 版本必須相同,否則預期出現未知錯誤

slave 節點較多時需要並行管理工具 pdsh 的使用嗎,同時啟動多個 slave 節點,減少人工啟停時間成本

遇到的問題

locust 分散式執行問題

單機啟動可傳送 60000,兩臺 120000

叢集啟動傳送 60000 服務端 ip 無法超過 60000 限制

已解決:

locust master 節點不作為負載機進行傳送資料,slave 一個節點最多可以增加 6w 個長連線(linux 共有埠 65535, 一般 1-1023 埠是系統保留的,1024-65535 是使用者使用的,可用埠有 6w 多個)

運維管理工具 pdsh

作用:快速分發命令,啟動 slave 節點

1、 配置 ssh 信任

修改主機名稱 vim /etc/hostname

master 可設為 node0,slave 可設為 node1~16

2、 安裝 pdsh

tar -jxvf **.tag.bz2
./configure --with-ssh --with-rsh --with-mrsh--with-mqshell --with-qshell --with-dshgroups--with-machines=/etc/pdsh/machines --without-pam
make  
make install

客戶端程式碼

## locustfile.py
# coding:utf-8
import time
import random
# from socket import socket, AF_INET, SOCK_STREAM
import socket
from locust import Locust, TaskSet, events, task


class TcpSocketClient(socket.socket):
    # locust tcp client
    # author: ShenDu
    def __init__(self, af_inet, socket_type):
        super(TcpSocketClient, self).__init__(af_inet, socket_type)

    def connect(self, addr):
        start_time = time.time()
        try:
            super(TcpSocketClient, self).connect(addr)
        except Exception as e:
            total_time = int((time.time() - start_time) * 1000)
            events.request_failure.fire(request_type="tcpsocket", name="connect", response_time=total_time, exception=e)
        else:
            total_time = int((time.time() - start_time) * 1000)
            events.request_success.fire(request_type="tcpsocket", name="connect", response_time=total_time,
                                        response_length=0)

    def send(self, msg):
        start_time = time.time()
        try:
            super(TcpSocketClient, self).send(msg)
        except Exception as e:
            total_time = int((time.time() - start_time) * 1000)
            events.request_failure.fire(request_type="tcpsocket", name="send", response_time=total_time, exception=e)
        else:
            total_time = int((time.time() - start_time) * 1000)
            events.request_success.fire(request_type="tcpsocket", name="send", response_time=total_time,
                                        response_length=0)

    def recv(self, bufsize):
        recv_data = ''
        start_time = time.time()
        try:
            recv_data = super(TcpSocketClient, self).recv(bufsize)
        except Exception as e:
            total_time = int((time.time() - start_time) * 1000)
            events.request_failure.fire(request_type="tcpsocket", name="recv", response_time=total_time, exception=e)
        else:
            total_time = int((time.time() - start_time) * 1000)
            events.request_success.fire(request_type="tcpsocket", name="recv", response_time=total_time,
                                        response_length=0)
        return recv_data


class TcpSocketLocust(Locust):
    """
    This is the abstract Locust class which should be subclassed. It provides an TCP socket client
    that can be used to make TCP socket requests that will be tracked in Locust's statistics.
    """
    def __init__(self, *args, **kwargs):

        super(TcpSocketLocust, self).__init__(*args, **kwargs)
        self.client = TcpSocketClient(socket.AF_INET, socket.SOCK_STREAM)
        ADDR = (self.host, self.port)
        self.client.connect(ADDR)


class TcpTestUser(TcpSocketLocust):
    host = "192.168.5.58"
    port = 6667
    min_wait = 100
    max_wait = 1000

    class task_set(TaskSet):
        @task
        def send_data(self):
            data = "7e0200003f000004021895000b00000000000400030158ccaa06cb7" \
                   "9f50095000000001601051654150104000069740202000003020000" \
                   "2504000000002b040000000030010031010b3201467c7e"
            # self.client.send(data.encode("utf-8"))
            self.client.send(bytes.fromhex(data))
            data = self.client.recv(2048).decode("utf-8")
            print(data)


if __name__ == "__main__":
    import os
    os.system("locust -f sendTcp.py")

單節點啟動

locust -f locustfile.py

分散式啟動

# master執行
locust -f locustfile.py --master 
# slave執行 
locust -f locustfile.py --slave --master-host = 127.0.0.1 
# pdsh 同時啟動slave節點, node 可使用ip代替
pdsh -w node[1-16] "locust -f /root/loadtest/locustfile.py --slave --master-host=node0"

python 編寫的簡易服務端

除錯程式使用的服務端程式碼

#coding: utf-8
from __future__ import print_function
from gevent.server import StreamServer
import gevent

# sleeptime = 60
'''
python 編寫的簡單tcp socket server,作為服務端測試使用
'''
def handle(socket, address):
    # print(address)
    # data = socket.recv(1024)
    # print(data)
    while True:
        gevent.sleep(sleeptime)
        try:
            socket.send("ok")
        except Exception as e:
            print(e)

if __name__ == "__main__":
    import sys
    port = 80
    if len(sys.argv) > 2:
        port = int(sys.argv[1])
        sleeptime = int(sys.argv[2])
    else:
        print("需要兩個引數!!")
        sys.exit(1)
    # default backlog is 256
    server = StreamServer(('0.0.0.0', port), handle, backlog=4096)
    server.serve_forever()

宣告:看到過的好文章,轉載分享,若有侵權,請及時聯絡,速刪。

暫無回覆。

相關文章