模擬上游服務,使用指令碼推送訊息給 Kafka 的解析

大海發表於2024-08-09

壓測場景

驗證上游資料量不斷加大的情況下,GOV API 的資料傳送與接收,功能保持正常,介面不存在明顯的錯誤率。

指令碼程式碼

import time
import asyncio
from concurrent.futures import ThreadPoolExecutor
import time
from DataMock import  KafkaMsgMock
from kafka import KafkaProducer
from Percentages import Percentage
import time, random, queue
from threading import Thread
import threading
import json
import ast
import datetime
from confluent_kafka import Producer

mocInstance = KafkaMsgMock()
percIns = Percentage()
task_list = []
global total
total = 0
global msglist
msglist = []
global wait
wait = 0
global resource
resource = 0
global previous_time
previous_time = int(time.time())


# 此處定義期望的傳送速率
maxlimted = 2790
minlimted = 2760

# kafka連線資訊
conf = {
    'bootstrap.servers': 'XXXXX',
    'security.protocol': 'XXXXX',
    'sasl.mechanisms': 'XXXXX',
    'sasl.username': 'XXXXX',
    'sasl.password': 'XXXXX'
} 
producer = Producer(conf)    

# 建立預設任務因子
def task_paras():
    #task_list.append({"controller": "LCE", "msgType": 'fault', "count": 3500})
    #task_list.append({"controller": 'LCE', "msgType": 'alarm', "count": 1500})
    #task_list.append({"controller": 'LCE', "msgType": 'serviceModeChange', "count": 5000})
    for m_Type in ['alarm','fault','serviceModeChange','movementData']:
        for contr_type in ['LCE','GCE','STEP','ESC','KSE','DTU']:
            count = percIns.calculate(controller = contr_type, msgType = m_Type, total = 10000)
            if count > 0:
                task_list.append({"controller" : contr_type, "msgType" : m_Type, "count" : count})

# 定義非同步呼叫裝飾器
def async_call(func):
    async def wrapper(*args, **kwargs):
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(None, func, *args, **kwargs)
    return wrapper

async def utcTime():
    '''
    make utc time for timestemp
    '''
    return datetime.datetime.utcnow().isoformat()[0:23] + 'Z'

# 生成資料函式修正
@async_call
def comon_msg(controller, msgType, count):
    return mocInstance.comon_msg(controller=controller, msgType=msgType, count=count)

@async_call
def kce_msg(msgType, count):
    return mocInstance.kce_msg(msgType=msgType, count=count)

@async_call
def kcecpuc_msg(msgType, count):
    return mocInstance.kcecpuc_msg(msgType=msgType, count=count)

# 傳送資料非同步函式修正
async def send(msg, producer):
    global previous_time, total, wait
    try:
        topic, data = msg
        data['Param']['Timestamp'] = await utcTime()

        if wait > 0:
            await asyncio.sleep(wait*0.001)           
        producer.produce("gov-"+ topic, json.dumps(data).encode('utf-8'))
        total += 1

        curr_time = int(time.time())
        if curr_time != previous_time:
            previous_time = curr_time

    except Exception as e:
        print(f"Error sending message '{msg}': {e}")  # 新增異常日誌


# 傳送資料批處理修正
async def send_batched(msgs, producer):
    await asyncio.gather(*[send(msg, producer) for msg in msgs])
    producer.flush() 

# 請求訊息函式修正
async def request_msg():

    global msglist
    while True:
        if len(msglist) < 300000:
            for unit in task_list:
                if unit['controller'] == 'KCE':
                    msglist += await kce_msg(unit['msgType'], unit['count'])
                elif unit['controller'] == 'KCECPUC':
                    msglist += await kcecpuc_msg(unit['msgType'], unit['count'])
                else:
                    msglist += await comon_msg(unit['controller'], unit['msgType'], unit['count'])
            random.shuffle(msglist)
        await asyncio.sleep(0.01)

# 傳送資料邏輯修正
async def send_data(producer):
    while True:
        global msglist,resource
        if len(msglist) > 0:

            if resource > 15000: resource = 15000
            if len(msglist) > (1000 + int(resource)):
                msgs    = msglist[:(1000 + int(resource))]
                msglist = msglist[(1000 + int(resource)):]
            else:
                msgs    = msglist
                msglist = msglist[len(msglist):-1]

            await send_batched(msgs, producer)
        await asyncio.sleep(0.01)

# 統計函式修正
async def statistics():
    start_time = ''
    global previous_time
    previous_time = int(time.time())
    time.sleep(1)
    while 1:
        global total
        global wait
        global msglist
        global resource

        if total != 0:
            curr_time =int(time.time())
            if start_time == '':
                start_time = curr_time
            if (curr_time - start_time) == 0:
                continue
            if curr_time != previous_time:
                speed = int( total / (curr_time - start_time))
                if speed > maxlimted:
                    if wait <= 100000: 
                        if (speed/maxlimted) > 1.5:
                             wait += 500
                             if resource > 100 : resource -= 100                     
                        elif (speed/maxlimted) > 1.15:
                             wait += 100
                             if resource > 20 : resource -= 20 
                        elif (speed/maxlimted) > 1.10:
                             wait += 50 
                             if resource > 15 : resource -= 15
                        elif (speed/maxlimted) > 1.05:
                             wait += 20 
                             if resource > 10 : resource -= 10
                        elif (speed/maxlimted) > 1.03:
                             wait += 10
                             if resource > 5 : resource -= 5
                        elif (speed/maxlimted) > 1.015:
                             wait += 1
                             if resource >= 1 : resource -= 1
                elif speed < minlimted:
                    if wait >= 0 :
                        if (speed/minlimted) < 0.6:
                            if wait> 500 :
                                wait -= 500
                            else:
                                wait = 0
                            resource += 100
                        elif (speed/minlimted) < 0.85:
                            if wait> 200 :
                                wait -= 200
                            else:
                                wait = 0
                            resource += 50
                        elif (speed/minlimted) < 0.90:
                            if wait> 100 : 
                                wait -= 100 
                            else:
                                wait = 0
                            resource += 20
                        elif (speed/minlimted) < 0.95:
                            if wait> 50 : 
                                wait -= 50 
                            else:
                                wait = 0
                            resource += 10
                        elif (speed/minlimted) < 0.97:
                            if wait> 10 : 
                                wait -= 10 
                            else:
                                wait = 0
                            resource += 5
                        elif (speed/minlimted) < 0.998:
                            if wait>= 1 :wait -= 1
                            resource += 1
                    else:
                        wait = 0
                print('[%s] 傳送訊息流速 %s個/秒, 傳送總數 %s個, 速率減速補償-%.2f, 增速補償%s, 佇列餘量%s' % (time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())),speed,total,wait*0.01, resource, len(msglist),))
                previous_time = int(time.time())
        await asyncio.sleep(0.001)

# 主函式修正
async def main():
    producer = Producer(conf)  # 假設 KafkaProducer 是一個非同步版本的 Kafka 生產者
    task_paras()  # 初始化任務引數
    print("執行限速區間, 每秒 %s - %s" % (minlimted, maxlimted))
    await asyncio.gather(request_msg(), send_data( producer), statistics())

# 執行主函式
asyncio.run(main())

指令碼解析

這段 Python 指令碼是一個生產者程式,用於向 Kafka 訊息佇列傳送訊息。它使用了asyncio庫來實現非同步操作,以及confluent_kafka庫來與 Kafka 進行互動。下面是對指令碼的詳細解釋:

  1. 匯入模組

    • time:用於時間相關的操作。
    • asyncio:用於編寫單執行緒併發程式碼,使用協程。
    • ThreadPoolExecutor:用於建立執行緒池。
    • KafkaMsgMock:自定義模組,用於生成模擬的 Kafka 訊息。
    • KafkaProducer:Kafka 生產者類(未在指令碼中直接使用,可能是匯入錯誤)。
    • Percentage:自定義模組,可能用於計算百分比或相關比例。
    • queue:用於建立執行緒安全的佇列。
    • threading:用於執行緒相關的操作。
    • json:用於處理 JSON 資料。
    • ast:用於處理 Python 抽象語法樹。
    • datetime:用於處理日期和時間。
  2. 全域性變數

    • mocInstanceKafkaMsgMock的例項,用於生成訊息。
    • percInsPercentage的例項,用於計算比例。
    • task_list:任務列表,儲存任務引數。
    • total:記錄傳送訊息的總數。
    • msglist:儲存生成的訊息。
    • wait:控制傳送速率的等待時間。
    • resource:資源計數,可能用於控制批次傳送的大小。
    • previous_time:記錄上一次傳送訊息的時間。
  3. Kafka 配置

    • conf:包含 Kafka 叢集的連線資訊,包括伺服器地址、安全協議、SASL 認證資訊。
  4. 生產者例項

    • producer:使用配置conf建立的 Kafka 生產者例項。
  5. 任務引數建立函式

    • task_paras:建立任務引數,根據Percentage例項計算每種訊息型別和控制器的傳送數量。
  6. 非同步呼叫裝飾器

    • async_call:裝飾器,用於將同步函式轉換為非同步函式。
  7. 生成資料函式

    • comon_msgkce_msgkcecpuc_msg:這些函式使用mocInstance生成不同型別和數量的訊息。
  8. 傳送資料非同步函式

    • send:非同步傳送單條訊息到 Kafka。
    • send_batched:非同步批次傳送訊息。
  9. 請求訊息函式

    • request_msg:不斷生成訊息並存入msglist
  10. 傳送資料邏輯

    • send_data:從msglist中取出訊息併傳送。
  11. 統計函式

    • statistics:統計傳送速率,並根據速率調整傳送策略。
  12. 主函式

    • main:初始化任務引數,啟動請求訊息、傳送資料和統計的協程。
  13. 執行主函式

    • 使用asyncio.run(main())啟動程式。

整體來看,這個指令碼的目的是模擬向 Kafka 傳送不同型別和數量的訊息,同時控制傳送速率以滿足特定的效能測試需求。指令碼使用了非同步程式設計來提高效率,並透過全域性變數來共享狀態。

好的,讓我們更細緻地一步步解析這個指令碼。我會盡量簡化和解釋每個部分的作用,幫助你更好地理解。

1. 匯入必要的庫

指令碼開始部分匯入了多個 Python 模組,這些模組提供了時間處理、非同步程式設計、執行緒管理、JSON 處理等功能。

2. 初始化全域性變數

指令碼定義了一些全域性變數,比如total(用來記錄傳送的訊息總數)、msglist(用來儲存待傳送的訊息)、wait(用來控制傳送速率的延遲)等。

3. Kafka 連線配置

conf字典包含了連線到 Kafka 叢集所需的配置資訊,比如伺服器地址、安全協議、認證資訊等。

4. 建立 Kafka 生產者例項

使用conf配置資訊,建立了一個 Kafka 生產者例項producer,用於傳送訊息到 Kafka。

5. 定義任務引數

task_paras函式用於初始化傳送任務,它會根據Percentage類計算出每種訊息型別和控制器的傳送數量,並將這些任務新增到task_list

6. 非同步呼叫裝飾器

async_call裝飾器是一個高階功能,它允許我們將普通的函式轉換為非同步函式,這樣就可以在等待某些操作(比如 I/O 操作)完成時釋放執行權,提高程式效率。

7. 生成訊息的函式

comon_msgkce_msgkcecpuc_msg函式用於生成不同型別和數量的訊息。這些函式被async_call裝飾器裝飾,使其成為非同步函式。

8. 傳送訊息的非同步函式

send函式是一個非同步函式,它負責將單個訊息傳送到 Kafka。send_batched函式則是批次傳送訊息的非同步函式。

9. 請求訊息和傳送資料的邏輯

request_msg函式不斷生成訊息並新增到msglistsend_data函式則從msglist中取出訊息併傳送。

10. 統計和調整傳送速率

statistics函式負責統計當前的傳送速率,並根據預設的上限和下限調整傳送速率。如果傳送速率過快,它會增加wait的值,從而減慢傳送速率;如果傳送速率過慢,它會減少wait的值,加快傳送速率。

11. 主函式

main函式是程式的入口點,它初始化任務引數,然後啟動三個協程:request_msgsend_datastatistics。這三個協程分別負責生成訊息、傳送訊息和統計傳送速率。

12. 啟動程式

最後,使用asyncio.run(main())啟動整個程式。

總結

這個指令碼的核心是模擬向 Kafka 傳送訊息的過程,同時透過非同步程式設計和速率控制來滿足特定的效能測試需求。它使用了asyncio庫來實現非同步操作,confluent_kafka庫來與 Kafka 進行互動,並透過自定義的裝飾器和函式來生成和傳送訊息。

下面,使用更簡單的語言和例子來解釋這個指令碼。我們可以把指令碼比作一個餐廳的廚房,它需要準備和傳送食物訂單。

1. 匯入工具(就像廚房裡的工具和食材)

  • time:計時器,用來知道現在幾點。
  • asyncio:特殊技能,讓廚師可以同時做很多事情。
  • ThreadPoolExecutor:多工處理,就像多個廚師同時工作。
  • KafkaMsgMock:食材供應商,提供模擬的食物。
  • Producer:點餐機,用來傳送食物訂單。
  • 其他匯入:各種調料和工具,幫助廚房運作。

2. 設定廚房(初始化全域性變數)

  • mocInstance:食材供應商的一個例項。
  • percIns:計算器,用來計算每種食物的比例。
  • task_list:選單列表,記錄要準備哪些食物。
  • total:訂單總數,記錄一共要準備多少份食物。
  • msglist:待處理的訂單,等待送出的食物。
  • wait:等待時間,如果太忙了,可能需要讓顧客等一下。
  • resource:資源,比如廚師的數量或者食材的存量。
  • previous_time:上次送出食物的時間。

3. 連線點餐機(Kafka 連線資訊)

  • conf:點餐機的設定,包括點餐機的地址、安全方式、使用者名稱和密碼。

4. 建立點餐機例項(Producer)

  • producer:根據設定conf建立的點餐機例項。

5. 準備選單(任務引數建立函式)

  • task_paras:決定今天要準備哪些食物,每種食物要準備多少份。

6. 快速處理技能(非同步呼叫裝飾器)

  • async_call:讓廚師可以快速處理任務,比如快速準備食物。

7. 準備食物(生成資料函式)

  • comon_msgkce_msgkcecpuc_msg:這些是準備不同食物的方法。

8. 傳送食物訂單(傳送資料非同步函式)

  • send:將準備好的食物透過點餐機傳送出去。
  • send_batched:如果有多個訂單,可以一次性傳送多個食物。

9. 持續準備食物(請求訊息函式)

  • request_msg:廚師不斷地準備食物,直到廚房關閉。

10. 傳送食物(傳送資料邏輯)

  • send_data:廚師根據當前的情況,決定傳送多少食物。

11. 監控和調整(統計函式)

  • statistics:監控廚房的工作情況,如果太忙了就減慢速度,如果不忙就加快速度。

12. 開始營業(主函式)

  • main:啟動廚房,開始準備食物和傳送訂單。

13. 啟動廚房(執行主函式)

  • asyncio.run(main()):告訴廚房開始工作。

這個指令碼就像一個自動化的廚房,它可以自動準備食物、傳送訂單,並且根據工作量自動調整速度。

如果覺得我的文章對您有用,請隨意打賞。您的支援將鼓勵我繼續創作!
打賞支援
暫無回覆。

相關文章