分佈式任務調度系統V1

分佈式任務調度系統V1目標

初步目標實現,實現任務的下發分配,分佈式任務執行,支持任務分片(在代碼上支持),任務執行記錄。

任務調度系統構思

基於C/S架構實現,基於長連接來管理實現,當前版本的邏輯架構圖如下;

註冊等
註冊等
註冊等
下發任務等
下發任務等
下發任務等
客戶端A
服務端
客戶端B
客戶端C

系統主要是通過自有協議進行通信,採用單個長連接來進行數據交互,Server端主要參考早期版本tornado的設計思路,Client端實現主要參考Kazoo的設計思路。

協議

實現自有的協議,在選擇協議時,二進制協議相對而言性能較高,但由於精力所限還是採用的字節流解析,首先將接受的數據解析成字符串,再根據字符串來進一步解析。基本的消息格式如下;

16\r\ntest\nstatus\t200

16:代表test\nstatus\t200的長度
\r\n: 表示頭部長度信息分隔符
test:表示消息的類型
\n: 表示消息類的內容信息
status: 表示消息裏面的key
\t: 表示消息體裏面的分隔符,分割key與value
200: 表示解析出來的key對應的value

根據該消息格式可進一步解析爲;

length\r\ntype\nkey1\tvalue2\tkey2\tvalue2....

根據不同的類型去定義不同的解析方式,並可靈活擴展不同的參數。

Server端

Server端的實現,主要參考了tornado的早期版本的架構實現,採用了單線程IO複用的架構模式來實現服務端較好的處理性能,在早期版本中該模式可以減少部分高併發模式下一些資源競爭的問題。其中也是分層的抽象爲Server層,數據流Stream層,Application處理層,並通過註冊handler的形式,將不同的處理條件註冊到Application中。

有事件處理
無事件觸發
啓動
服務端
單線程IO複用循環
接受數據進行業務處理
Client端

Client端主要參考了Kazoo的架構的實現的思路,借鑑了其實現的基本思路,即開啓單獨的線程來進行事件監聽,來監聽服務端發送過來的數據,並通過條件變量來實現與主線程之間的數據交互。

響應客戶端請求
接受任務處理
客戶端啓動
監聽事件線程
通過api接口發送數據服務端並等待
處理任務
任務執行完成並彙報結果
繼續執行

時序圖

用戶任務調度中心agent_Aagent_B註冊agent_A信息和Task1,Task3任務註冊成功註冊agent_B信息和Task2,Task3任務註冊成功彙報當前agent_A庫中自行編寫的任務任務彙報成功彙報當前agent_B庫中自行編寫的任務任務彙報成功下發實時任務只執行一次接受任務Task1並執行執行結果返回下發定時任務給所有客戶端Task3任務下發任務Task3下發任務Task3執行結果返回執行結果返回下發定時任務給所有客戶端Task3任務分片任務爲5下發任務Task3,item=0下發任務Task3,item=1執行結果返回執行結果返回下發任務Task3,item=2下發任務Task3,item=3執行結果返回執行結果返回下發任務Task3,item=4執行結果返回用戶任務調度中心agent_Aagent_B

系統演示

項目穿梭們

首先,開啓一個server端,也可以通過taskserver啓動,會監聽默認的端口4546,ip爲127.0.0.1

(venv) wuzideMacBook-Pro:local_work wuzi$ taskserver 
server start listen at 127.0.0.1:4546

此時,編寫兩個文件分別爲task1.py,與task2.py

# task1.py
import sys

sys.path.append("/Users/wuzi/PycharmProjects/distributed_schedule")

from distributed_schedule.tasks import Task, register_task
from distributed_schedule.client import main

import time


class TestTask(Task):

    def execute(self, *args, **kwargs):
        print("TestTask task {0}  {1}".format(args, kwargs))
        time.sleep(10)
        print("TestTask over ")


class TestDTask(Task):

    def execute(self, *args, **kwargs):
        print("TestDTask task {0}  {1}".format(args, kwargs))
        time.sleep(1)
        raise
        print("TestDTask over ")


register_task(TestTask("test_task"))
register_task(TestDTask("testd_task"))

main()

# task2.py
import sys

sys.path.append("/Users/wuzi/PycharmProjects/distributed_schedule")

from distributed_schedule.tasks import Task, register_task
from distributed_schedule.client import main

import time


class Test2Task(Task):

    def execute(self, *args, **kwargs):
        print("Test2Task task {0}  {1}".format(args, kwargs))
        time.sleep(1)
        print("Test2Task over ")


class TestD2Task(Task):

    def execute(self, *args, **kwargs):
        print("TestD2Task task {0}  {1}".format(args, kwargs))
        time.sleep(1)
        print("TestD2Task over ")


register_task(Test2Task("test2_task"))
register_task(TestD2Task("test2d_task"))

main()

此時我們通過類似與api的形式來調用該任務client_api.py

import sys

from distributed_schedule.client import TaskClient, Client
from distributed_schedule.config import set_logging_level

sys.path.append("/Users/wuzi/PycharmProjects/distributed_schedule")

set_logging_level(20)


c = Client("client")
c.start()

tclient = TaskClient(c)


print("start")
res = tclient.submit("test_task", shard=5)
#
print(res)


res = tclient.submit("test_task", mode="all")
print(res)


res = tclient.submit("test2d_task", mode="once")
print(res)




此時我們先啓動task1.py和task2.py

python task1.py --name clienttask1  --role worker


python task2.py --name clienttask2 --role worker


python task1.py  --name="clienttask3" --role worker


此時,就有兩個客戶端執行了task1.py中的任務,即test_task,testd_task任務有兩個可以執行的客戶端即clienttask1和clienttask3。此時執行client_api.py文件。

python client_api.py

start
('broad', 'status\t200\t')
('broad', 'status\t200\t')
('broad', 'status\t200\t')

此時終端任務的展示如下;

(venv) wuzideMacBook-Pro:local_work wuzi$ python task1.py --name clienttask1  --role worker
response ok
response ok
TestTask task ()  {'event_id': '06ac4d14-b1eb-47c7-ab00-535a7587f6eb', 'status': 'doing', 'client_event_id': 'a677403a-a5e2-42b1-b61b-00940388917d', 'item': '0'}
TestTask task ()  {'event_id': '06ac4d14-b1eb-47c7-ab00-535a7587f6eb', 'status': 'doing', 'client_event_id': 'a677403a-a5e2-42b1-b61b-00940388917d', 'item': '2'}
TestTask task ()  {'event_id': '06ac4d14-b1eb-47c7-ab00-535a7587f6eb', 'status': 'doing', 'client_event_id': 'a677403a-a5e2-42b1-b61b-00940388917d', 'item': '4'}
TestTask task ()  {'event_id': '4e8297c1-80ba-4c40-bb84-392849169efa', 'status': 'doing', 'client_event_id': '79d3a627-e5eb-4869-8136-c995b502c1f9'}
TestTask over 
TestTask over 
TestTask over 
TestTask over 

(venv) wuzideMacBook-Pro:local_work wuzi$ python task2.py --name clienttask2 --role worker
response ok
response ok
TestD2Task task ()  {'event_id': 'bd874874-7e44-4ea0-9cfd-0d41024d807a', 'status': 'doing', 'client_event_id': 'eb54c8bf-c979-4f2f-912b-03edbb6c7b14'}
TestD2Task over 



(venv) wuzideMacBook-Pro:local_work wuzi$ python task1.py  --name="clienttask3" --role worker
response ok
response ok
TestTask task ()  {'event_id': '06ac4d14-b1eb-47c7-ab00-535a7587f6eb', 'status': 'doing', 'client_event_id': 'e31ccc68-f0dc-47e0-bfa8-cdf4a9de0003', 'item': '1'}
TestTask task ()  {'event_id': '06ac4d14-b1eb-47c7-ab00-535a7587f6eb', 'status': 'doing', 'client_event_id': 'e31ccc68-f0dc-47e0-bfa8-cdf4a9de0003', 'item': '3'}
TestTask task ()  {'event_id': '4e8297c1-80ba-4c40-bb84-392849169efa', 'status': 'doing', 'client_event_id': 'd804769d-0844-4cff-aa41-beb254bb84e7'}
TestTask over 
TestTask over 
TestTask over 


從終端輸出結果可以看出,test_task被分片執行了五次,分別爲clienttask1中的分片0,2,4和clienttask3中的分片1,3;在test_task通知所有執行的過程中,clienttask1和clienttask3分別執行了一次,最後調用了一次test2d_task任務,該任務就執行了一次。

總結

本文只是簡單編寫的第一版本的分佈式任務,項目上傳到了github傳送門,由於本人水平有限,編寫過程中會出現不少錯誤與問題,並且代碼設計與編寫並不算規範,希望多多批評。本項目測試用例並沒有編寫完成,所以後續會繼續補上,並且第一版架構相對簡單,存在服務單點問題,並且數據並沒有做持久化,後續會會慢慢規劃上。由於本人才疏學淺,如有錯誤請批評指正。

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