workflow這個C++開源項目值得學習

最近發(fā)現(xiàn)了適合C++開發(fā)者進階的開源項目您访,這個項目的名字叫workflow,項目地址如下:

sogou/workflowgithub.com/sogou/workflow

workflow是搜狗公司的服務器引擎,幾乎搜狗所有的后端C++服務和其他幾十家公司都在使用這個引擎颁湖,每日處理超百億請求俗他。

其實去年在purecpp大會就聽過一個美女大佬介紹過這個項目,當時就感慨這么項目怎么這么牛逼夺溢,但是也沒具體了解抹蚀,直到最近又聽別人提起,我就仔細看了看項目的介紹企垦,又研究了半個月項目的源碼环壤,由衷感慨作者超強的架構能力,明白了自己和架構師之間巨大的差距钞诡,還是得多學習啊郑现,肝湃崩。

本文目錄:

  • 框架有什么特點?
  • 框架能做什么接箫?
  • 我為什么要推薦這個開源項目攒读?

框架有什么特點?

  • 用戶體驗相當好:接口簡潔辛友,支持常用協(xié)議薄扁,使用簡單,具體怎么簡單我下面會介紹废累;
  • 性能好:不單網(wǎng)絡邓梅、磁盤IO、CPU計算等邑滨,workflow著眼于所有異步資源都盡可能全部調(diào)起日缨,有相當充足的測試數(shù)據(jù)證明該框架的性能較目前主流的服務端框架更好;
  • 穩(wěn)定性高:搜狗和其他好多公司都在使用這個引擎掖看,穩(wěn)定性肯定高啊匣距,大家也可以自己去查數(shù)據(jù),我就不貼了哎壳;
  • 支持多種平臺:項目支持Linux毅待、macOS、Windows归榕、Android等操作系統(tǒng)恩静。
  • 解放用戶生產(chǎn)力:用戶接觸到的只有任務(Task)和任務流(series)兩種概念,框架將資源高度封裝蹲坷,用戶無需接觸到線程池驶乾、連接池、文件IO與各種異步通知機制等循签。用戶無需關心內(nèi)部細節(jié)级乐,可以將更多精力用在實現(xiàn)復雜的業(yè)務邏輯上。
  • 設計理念新穎:源碼值得學習县匠,我也是看了這個項目的源碼后才推薦給大家的风科,看完才知道,原來代碼可以這么寫乞旦,繼承可以這么玩贼穆。

框架能做什么?

框架能做的事情很多兰粉,我這里只介紹一些個人認為比較重要的功能故痊,更多功能還需要大家自行解鎖。

輕松的搭建server:不用多說玖姑,服務端框架如果不能搭建server那還玩啥了愕秫,但使用這個框架非常方便慨菱,以http server為例,只需要簡單幾行代碼即可:

#include <stdio.h>
#include "workflow/WFHttpServer.h"

int main() {
    WFHttpServer server([](WFHttpTask *task) {
        task->get_resp()->append_output_body("Hello World!");
    });
    if (server.start(8888) == 0) { // start server on port 8888
        getchar(); // press "Enter" to end.
        server.stop();
    }
    return 0;
}

輕松高效的發(fā)起客戶端請求:項目號稱可作為萬能異步客戶端戴甩,目前支持http符喝,redis,mysql甜孤、websocket和kafka協(xié)議协饲,下面是官方給出的一個mysql的客戶端示例:

int main(int argc, char *argv[]) {
    ...
    WFMySQLTask *task = WFTaskFactory::create_mysql_task(url, RETRY_MAX, mysql_callback);
    task->get_req()->set_query("SHOW TABLES;");
    ...
    task->start();
    ...
}

以往的C++ server需要訪問mysql時,可能使用的是傳統(tǒng)的客戶端缴川。在一個線程下以同步阻塞的方式等待數(shù)據(jù)到來茉稠。如果有多個網(wǎng)絡請求希望并發(fā),那么用戶需要管理多個mysql cli對象二跋。

workflow完美的解決了這一系列問題战惊,把所有這種用戶請求交給內(nèi)部的poller線程統(tǒng)一管理流昏,實現(xiàn)了高效的非阻塞IO行為扎即,提升了server作為客戶端請求數(shù)據(jù)時的性能表現(xiàn)。再也不用擔心這種客戶端行為影響server整體的性能况凉。

再看個使用http協(xié)議的wget示例:

int main(int argc, char *argv[]) {
    WFHttpTask *task; 
    std::string url = argv[1];
    url = "http://" + url;
    task = WFTaskFactory::create_http_task(url, REDIRECT_MAX, RETRY_MAX,
                                           wget_callback);
    protocol::HttpRequest *req = task->get_req();
    req->add_header_pair("Accept", "*/*");
    req->add_header_pair("User-Agent", "Wget/1.14 (linux-gnu)");
    req->add_header_pair("Connection", "close");
    task->start();
    wait_group.wait();
    return 0;
}

首先發(fā)起了http請求谚鄙,在wget_callback中處理http返回的消息體:

void wget_callback(WFHttpTask *task) {
    protocol::HttpRequest *req = task->get_req();
    protocol::HttpResponse *resp = task->get_resp();
    int state = task->get_state();
    int error = task->get_error();

    std::string name;
    std::string value;
    protocol::HttpHeaderCursor req_cursor(req);

    while (req_cursor.next(name, value))
        fprintf(stderr, "%s: %s\r\n", name.c_str(), value.c_str());
    fprintf(stderr, "\r\n");

    /* Print response header. */
    fprintf(stderr, "%s %s %s\r\n", resp->get_http_version(),
                                    resp->get_status_code(),
                                    resp->get_reason_phrase());

    protocol::HttpHeaderCursor resp_cursor(resp);
    while (resp_cursor.next(name, value))
        fprintf(stderr, "%s: %s\r\n", name.c_str(), value.c_str());
    fprintf(stderr, "\r\n");
    /* Print response body. */
    const void *body;
    size_t body_len;

    resp->get_parsed_body(&body, &body_len);
    fwrite(body, 1, body_len, stdout);
    fflush(stdout);

    fprintf(stderr, "\nSuccess. Press Ctrl-C to exit.\n");
}

就這么輕松的完成了wget的功能。

支持自定義協(xié)議client/server:用戶可構建自己的RPC系統(tǒng)刁绒,搜狗有個開源項目srpc就是以這個框架為基礎實現(xiàn)的闷营。

可構建異步任務流:支持串連,支持并聯(lián)知市,支持串并聯(lián)的組合體傻盟,也支持復雜的DAG結構。

異步IO:在Linux系統(tǒng)下可作為文件異步IO工具使用嫂丙,性能超過任何標準調(diào)用娘赴。

通信與計算一體化:多數(shù)框架都著重于網(wǎng)絡IO的效率問題,而計算與任務調(diào)度等需要用戶自己實現(xiàn)跟啤,workflow會自動對任務進行調(diào)度诽表,打通網(wǎng)絡和磁盤等資源,特別適合需要網(wǎng)絡通信的重計算模塊隅肥。

我為什么要推薦這個項目竿奏?

主要就一點:值得學習,適合C++開發(fā)者進階腥放,那具體學習什么泛啸?

學習系統(tǒng)的設計,所謂初級重實現(xiàn)秃症,中級重炫技平痰,高級重設計汞舱。

在作者的設計理念中,一切業(yè)務邏輯皆是任務宗雇,多個任務會組成任務流昂芜,任務流可組成圖,這個圖可能是串連圖赔蒲,可能是并聯(lián)圖泌神,也有可能是串并聯(lián)圖,類似于這種:

[圖片上傳失敗...(image-fc5367-1628426033482)]

也有可能是這種復雜的DAG圖:

[圖片上傳失敗...(image-855834-1628426033481)]

當然圖的層次結構可由用戶自定義舞虱,個人認為框架最牛逼的一點就是支持動態(tài)創(chuàng)建任務流欢际。

使用下面這段代碼可以很直觀友好的構造出圖的結構,你有沒有很好奇這段代碼是怎么實現(xiàn)的矾兜?

WFGraphNode a, b, c, d;
a-->b;
a-->c;
b-->d;
c-->d;

這里先賣個關子损趋,感興趣的自己去看吧,總貼人家代碼也不好椅寺。

我認為項目最值得大家學習的就是架構的設計浑槽,特別任務與任務流的設計,我現(xiàn)在還沒看完代碼返帕,畫不出架構的設計圖(也怕畫錯了)桐玻,只能籠統(tǒng)的說一句牛逼,因為確實驚艷到我了荆萤。

貼一個workflow的關于Task的架構圖:

image

再簡單的貼一個定時器Task的實現(xiàn)代碼給大家看看:

項目里所有的Task都通過工廠創(chuàng)建:

static WFTimerTask *create_timer_task(const std::string& timer_name,
                                          unsigned int microseconds,
                                          timer_callback_t callback);

看下WFTimerTask的設計:

class WFTimerTask : public SleepRequest {
public:
    void start() {
        assert(!series_of(this));
        Workflow::start_series_work(this, nullptr);
    }
    void dismiss() {
        assert(!series_of(this));
        delete this;
    }
protected:
    virtual SubTask *done() {
        if (this->callback)
            this->callback(this);
    }
};

再看下Workflow::start_series_work()的方法:

inline void Workflow::start_series_work(SubTask *first, series_callback_t callback) {
    new SeriesWork(first, std::move(callback));
    first->dispatch();
}

然后是SleepRequest:

class SleepRequest : public SubTask, public SleepSession {
public:
    virtual void dispatch() {
        if (this->scheduler->sleep(this) < 0) {
            this->state = SS_STATE_ERROR;
            this->error = errno;
            this->subtask_done();
        }
    }
protected:
    CommScheduler *scheduler;
    virtual void handle(int state, int error) {
        this->state = state;
        this->error = error;
        this->subtask_done();
    }
};

再看下scheduler中的這個sleep()方法:

int Communicator::sleep(SleepSession *session) {
    struct timespec value;
    if (session->duration(&value) >= 0) {
        if (mpoller_add_timer(&value, session, this->mpoller) >= 0)
            return 0;
    }
    return -1;
}

然后是SubTask:

class SubTask {
public:
    virtual void dispatch() = 0;
private:
    virtual SubTask *done() = 0;
protected:
    void subtask_done();
};

然后是subtask_done()方法的實現(xiàn):

void SubTask::subtask_done() {
    SubTask *cur = this;
    ParallelTask *parent;
    SubTask **entry;
    while (1) {
        parent = cur->parent;
        entry = cur->entry;
        cur = cur->done();
        xxx
        break;
    }
}

然后是SleepSession:

class SleepSession {
private:
    virtual int duration(struct timespec *value) = 0;
    virtual void handle(int state, int error) = 0;
public:
    virtual ~SleepSession() { }
    friend class Communicator;
};

看了這么多源碼镊靴,那WFTimerTask是如何實現(xiàn)的定時功能呢?

我總結了下面幾步:

步驟1:用戶調(diào)用WFTimerTask的start()链韭;

步驟2:start()中調(diào)用Workflow::start_series_work()方法偏竟;

步驟3:start_series_work()中調(diào)用SubTask的dispatch()方法,這個dispatch()方法由SubTask的子類SleepRequest(WFTimerTask的父類)實現(xiàn);

步驟4:SleepRequest類的dispath()方法會調(diào)用scheduler->sleep()方法敞峭;

步驟5:sleep()方法會調(diào)用SleepSession的duration()方法獲取具體sleep的時間踊谋,框架內(nèi)部用了timerfd把超時時間交給操作系統(tǒng),時間到了會通知框架層儡陨,進而觸發(fā)SleepSession中的handle()調(diào)用褪子;

步驟6:handle()的實現(xiàn)中調(diào)用subtask_done()方法;

步驟7:subtask_done()中會調(diào)用SubTask中的done()方法骗村;

步驟8:這個done()方法具體由WFTimerTask覆蓋嫌褪,實現(xiàn)中會調(diào)用到具體時間后觸發(fā)的回調(diào)函數(shù)。

乍一看可能感覺非常麻煩胚股,為什么實現(xiàn)一個普通的定時功能會搞這么多繼承關系笼痛,但你真正看了源碼后就會發(fā)現(xiàn),項目抽象出的所有Task,比如計數(shù)器Task缨伊、文件IOTask摘刑、網(wǎng)絡Task、MySQLTask等刻坊,都是通過這種SubTask枷恕、XXXRequest、XXXSession的形式來實現(xiàn)谭胚,后期再來個XXXTask可以很方便的擴展徐块,這才是優(yōu)秀項目該有的架構,真的佩服灾而。

讀者們胡控,你們可以設計出這么高端的架構嗎?反正我要肝完這個項目旁趟,也推薦給大家一起學習!

小總結

最后總結了該項目中個人認為值得我們學習的地方:

  • 接口的設計:項目的接入極其簡單昼激,幾行代碼就可搭建個client或者server,幾行代碼也可構建出簡單的任務流圖锡搜,可用于處理復雜的業(yè)務邏輯橙困;
  • 架構的設計:項目中的各種類是如何派生的,作者的設計思路是怎么樣的余爆;
  • 網(wǎng)絡通信:項目沒有使用任何網(wǎng)絡框架纷宇,而是使用網(wǎng)絡裸接口進行網(wǎng)絡通信夸盟,我們都知道在大型項目中使用網(wǎng)絡裸接口進行網(wǎng)絡通信需要處理很多異常條件蛾方,這里值得學習一波;
  • 任務流的封裝:為什么可以動態(tài)的構建任務流的串并聯(lián)圖上陕,并在項目內(nèi)部靈活的調(diào)度呢桩砰?
  • 文件I/O:項目號稱內(nèi)部文件I/O操作比標準調(diào)用性能還好,它是怎么做到的释簿?
  • 內(nèi)存的管理:項目沒有使用任何智能指針亚隅,卻能管理好內(nèi)存問題,這是個技術活庶溶,當然煮纵,也得益于這優(yōu)秀的架構設計。

我發(fā)現(xiàn)workflow團隊對這個項目相當重視偏螺,還特意建了個QQ交流群(群號碼是618773193)行疏,對此項目有任何問題都可以在這個群里探討,也方便了我們學習套像,真的不錯酿联。

參考資料:

<u style="text-decoration: none; border-bottom: 1px dashed grey;">https://zhuanlan.zhihu.com/p/358869362</u>

<u style="text-decoration: none; border-bottom: 1px dashed grey;">https://zhuanlan.zhihu.com/p/165638263</u>

項目地址如下:

sogou/workflowgithub.com/sogou/workflow

在訪問GitHub遇到困難時,可使用他們的Gitee官方倉庫:

搜狗開源/workflowgitee.com/sogou/workflow[圖片上傳失敗...(image-45b969-1628426033468)]

感覺這個項目值得學習的話就給人家個star,不要白嫖哈贞让,對項目團隊來說也是一種認可和鼓勵周崭。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市喳张,隨后出現(xiàn)的幾起案子续镇,更是在濱河造成了極大的恐慌,老刑警劉巖销部,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件磨取,死亡現(xiàn)場離奇詭異,居然都是意外死亡柴墩,警方通過查閱死者的電腦和手機忙厌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來江咳,“玉大人逢净,你說我怎么就攤上這事〖咧福” “怎么了爹土?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長踩身。 經(jīng)常有香客問我胀茵,道長,這世上最難降的妖魔是什么挟阻? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任琼娘,我火速辦了婚禮,結果婚禮上附鸽,老公的妹妹穿的比我還像新娘脱拼。我一直安慰自己,他們只是感情好坷备,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布熄浓。 她就那樣靜靜地躺著,像睡著了一般省撑。 火紅的嫁衣襯著肌膚如雪赌蔑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天竟秫,我揣著相機與錄音娃惯,去河邊找鬼。 笑死鸿摇,一個胖子當著我的面吹牛石景,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼潮孽,長吁一口氣:“原來是場噩夢啊……” “哼揪荣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起往史,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤仗颈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后椎例,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挨决,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年订歪,在試婚紗的時候發(fā)現(xiàn)自己被綠了脖祈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡刷晋,死狀恐怖盖高,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情眼虱,我是刑警寧澤喻奥,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站捏悬,受9級特大地震影響撞蚕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜过牙,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一甥厦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抒和,春花似錦矫渔、人聲如沸彤蔽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽顿痪。三九已至镊辕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蚁袭,已是汗流浹背征懈。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留揩悄,地道東北人卖哎。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親亏娜。 傳聞我的和親對象是個殘疾皇子焕窝,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容