sogou/workflow

FAQ(持续更新)

Barenboim opened this issue · 53 comments

项目背景以及解决的问题

C++ Workflow项目起源于搜狗公司的分布式存储项目的通讯引擎,并且发展成为搜狗公司级C++标准,应用于搜狗大多数C++后端服务。项目将通讯与计算和谐统一,帮助用户建立通讯与计算关系非常复杂的高性能服务。但同时用户也可以只把它当成简易的异步网络引擎或并行计算框架来使用。

如何开始使用

以Linux系统为例:

$ git clone https://github.com/sogou/workflow
$ cd workflow
$ make
$ cd tutorial
$ make

然后就可以愉快的运行示例了。每个示例都有对应的文档讲解。如果需要用到kafka协议,请预先安装snappy和lz4,并且:

$ make KAFKA=y
$ cd tutorial
$ make KAFKA=y

另外,make DEBUG=y,可以编译调试版。通过make REDIS=n MYSQL=n UPSTREAM=n CONSUL=n可以裁剪掉一个或多个功能,让库文件减小到最低400KB,更加适合嵌入式开发。

与其它的网络引擎,RPC项目相比,有什么优势

  • 简单易上手,无依赖
  • 性能和稳定性优异benchmark
  • 丰富的通用协议实现
  • 通讯与计算统一
  • 任务流管理

与其它并行计算框架相比,有什么优势

  • 使用简单
  • 有网络

项目原生包含哪些网络协议

目前我们实现了HTTP,Redis,MySQL和kafka协议。除kafka目前只支持客户端以外,其他协议都是client+server。也就是说,用户可以用于构建Redis或MySQL协议的代理服务器。kafka模块是插件,默认不编译。

为什么用callback

我们用C++11 std::function类型的callback和process来包装用户行为,因此用户需要知道自己是在编写异步程序。我们认为callback方式比future或用户态协程能给程序带来更高的效率,并且能很好的实现通信与计算的统一。由于我们的任务封装方式以及std::function带来的便利,在我们的框架里使用callback并没有太多心智负担,反而非常简单明了。

callback在什么线程里调用

项目的一个特点是由框架来管理线程,除了一些很特殊情况,callback的调用线程必然是处理网络收发和文件IO结果的handler线程(默认数量20)或者计算线程(默认数量等于CPU总核数)。但无论在哪个线程里执行,都不建议在callback里等待或执行特别复杂的计算。需要等待可以用counter任务进行不占线程的wait,复杂计算则应该包装成计算任务。
需要说明的是,框架里的一切资源都是使用时分配。如果用户没有用到网络通信,那么所有和通信相关的线程都不会被创建。

为什么我的任务启动之后没有反应

int main(void)
{
    ...
    task->start();
    return 0;
}

这是很多新用户都会遇到的问题。框架中几乎所有调用都是非阻塞的,上面的代码在task启动之后main函数立刻return,并不会等待task的执行结束。正确的做法应该是通过某种方式在唤醒主进程,例如:

WFFaciliies::WaitGroup wait_group(1);

void callback(WFHttpTask *task)
{
    ....
    wait_group.done();
}

int main(void)
{
    WFHttpTask *task = WFTaskFactory::create_http_task(url, 0, 0, callback);
    task->start();
    wait_group.wait();
    return 0;
}

任务对象的生命周期是什么

框架中任何任务(以及SeriesWork),都是以裸指针形式交给用户。所有任务对象的生命周期,是从对象被创建,到对象的callback完成。也就是说callback之后task指针也就失效了,同时被销毁的也包括task里的数据。如果你需要保留数据,可以用std::move()把数据移走,例如我们需要保留http任务中的resp:

void http_callback(WFHttpTask *task)
{
    protocol::HttpResponse *resp = task->get_resp();
    protocol::HttpResponse *my_resp = new protocol::HttpResponse(std::move(*resp));
    /* or
    protocol::HttpResponse *my_resp = new protocol::HttpResponse;
    *my_resp = std::move(*resp);
    */
}

某些情况下,如果用户创建完任务又不想启动了,那么需要调用task->dismiss()直接销毁任务。
需要特别强调,server的process函数不是callback,server任务的callback发生在回复完成之后,而且默认为nullptr。

为什么SeriesWork(串行)不是一种任务

我们关于串并联的定义是:

  • 串行由任务组成
  • 并行由串行组成
  • 并行是一种任务

显然通过这三句话的定义我们可以递归出任意复杂的串并联结构。如果把串行也定义为一种任务,串行就可以由多个子串行组成,那么使用起来就很容易陷入混乱。同样并行只能是若干串行的并,也是为了避免混乱。其实使用中你会发现,串行本质上就是我们的协程。

我需要更一般的有向无环图怎么办

可以使用WFGraphTask,或自己用WFCounterTask来构造。
示例:https://github.com/sogou/workflow/blob/master/tutorial/tutorial-11-graph_task.cc

server是在process函数结束后回复请求吗

不是。server是在server task所在series没有别的任务之后回复请求。如果你不向这个series里添加任何任务,就相当于process结束之后回复。注意不要在process里等待任务的完成,而应该把这个任务添加到series里。

如何让server在收到请求后等一小段时间再回复

错误的方法是在process里直接sleep。正确做法,向server所在的series里添加一个timer任务。以http server为例:

void process(WFHttpTask *server_task)
{
    WFTimerTask *timer = WFTaskFactory::create_timer_task(100000, nullptr);
    server_task->get_resp()->append_output_body("hello");
    series_of(server_task)->push_back(timer);
}

以上代码实现一个100毫秒延迟的http server。一切都是异步执行,等待过程没有线程被占用。

怎么知道回复成功没有

首先回复成功的定义是成功把数据写入tcp缓冲,所以如果回复包很小而且client端没有因为超时等原因关闭了连接,几乎可以认为一定回复成功。需要查看回复结果,只需给server task设置一个callback,callback里状态码和错误码的定义与client task是一样的,但server task不会出现dns错误。

能不能不回复

可以。任何时候调用server task的noreply()方法,那么在原本回复的时机,连接直接关闭。

计算任务的调度规则是什么

我们发现包括WFGoTask在内的所有计算任务,在创建时都需要指定一个计算队列名,这个计算队列名可用于指导我们内部的调度策略。首先,只要有空闲计算线程可用,任务将实时调起,计算队列名不起作用。当计算线程无法实时调起每个任务的时候,那么同一队列名下的任务将按FIFO的顺序被调起,而队列与队列之间则是平等对待。例如,先连续启动n个队列名为A的任务,再连续启动n个队列名为B的任务。那么无论每个任务的cpu耗时分别是多少,也无论计算线程数多少,这两个队列将近倾向于同时执行完毕。这个规律可以扩展到任意队列数量以及任意启动顺序。

为什么使用redis client时无需先建立连接

首先看一下redis client任务的创建接口:

class WFTaskFactory
{
public:
    WFRedisTask *create_redis_task(const std::string& url, int retry_max, redis_callback_t callback);
}

其中url的格式为:redis://:password@host:port/dbnum。port默认值为6379,dbnum默认值为0。
workflow的一个重要特点是由框架来管理连接,使用户接口可以极致的精简,并实现最有效的连接复用。框架根据任务的用户名密码以及dbnum,来查找一个可以复用的连接。如果找不到则发起新连接并进行用户登陆,数据库选择等操作。如果是一个新的host,还要进行DNS解析。请求出错还可能retry。这每一个步骤都是异步并且透明的,用户只需要填写自己的request,将任务启动,就可以在callback里得到请求的结果。唯一需要注意的是,每次任务的创建都需要带着password,因为可能随时有登陆的需要。
同样的方法我们可以用来创建mysql任务。但对于有事务需求的mysql,则需要通过我们的WFMySQLConnection来创建任务了,否则无法保证整个事务都在同一个连接上进行。WFMySQLConnection依然能做到连接和认证过程的异步性。

连接的复用规则是什么

大多数情况下,用户使用框架产生的client任务都是无法指定具体连接。框架会有连接的复用策略:

  • 如果同一地址端口有满足条件的空闲连接,从中选择最近一个被释放的那个。即空闲连接的复用是先进后出的。
  • 当前地址端口没有满足条件的空闲连接时:
    • 如果当前并发连接数小于最大值(默认200),立刻发起新连接。
    • 并发连接数已经达到最大值,任务将得到系统错误EAGAIN。
  • 并不是所有相同目标地址和端口上的连接都满足复用条件。例如不同用户名或密码下的数据库连接,就不能复用。

虽然我们的框架无法指定任务要使用的连接,但是我们支持连接上下文的功能。这个功能对于实现有连接状态的server非常重要。相关的内容可以参考关于连接上下文相关文档。

同一域名下如果有多个IP地址,是否有负载均衡

是的,我们会认为同一域名下的所有目标IP对等,服务能力也相同。因此任何一个请求都会寻找一个从本地看起来负载最轻的目标进行通信,同时也内置了熔断与恢复策略。同一域名下的负载均衡,目标都必须服务在同一端口,而且无法配置不同权重。负载均衡的优先级高于连接复用,也就是说会先选择好通信地址再考虑复用连接问题。

如何实现带权重或不同端口上的负载均衡

可以参考upstream相关文档。upstream还可以实现很多更复杂的服务管理需求。

chunked编码的http body如何最高效访问

很多情况下我们使用HttpMessage::get_parsed_body()来获得http消息体。但从效率角度上考虑,我们并不自动为用户解码chunked编码,而是返回原始body。解码chunked编码可以用HttpChunkCursor,例如:

#include "workflow/HttpUtil.h"

void http_callback(WFHttpTask *task)
{
    protocol::HttpResponse *resp = task->get_resp();
    protocol::HttpChunkCursor cursor(resp);
    const void *chunk;
    size_t size;

    while (cursor.next(&chunk, &size))
    {
        ...
    }
}

cursor.next操作每次返回一个chunk的起始位置指针和chunk大小,不进行内存拷贝。使用HttpChunkCursor之前无需判断消息是不是chunk编码,因为非chunk编码也可以认为整体就是一个chunk。

能不能在callback或process里同步等待一个任务完成

我们不推荐这个做法,因为任何任务都可以串进任务流,无需占用线程等待。如果一定要这样做,可以用我们提供的WFFuture来实现。请不要直接使用std::future,因为我们所有通信的callback和process都在一组线程里完成,使用std::future可能会导致所有线程都陷入等待,引发整体死锁。WFFuture通过动态增加线程的方式来解决这个问题。使用WFFuture还需要注意在任务的callback里把要保留的数据(一般是resp)通过std::move移动到结果里,否则callback之后数据会随着任务一起被销毁。

数据如何在task之间传递

最常见的,同一个series里的任务共享series上下文,通过series的get_context()和set_context()的方法来读取和修改。而parallel在它的callback里,也可以通过series_at()获到它所包含的各个series(这些series的callback已经被调用,但会在parallel callback之后才被销毁),从而获取它们的上下文。由于parallel也是一种任务,所以它可以把汇总的结果通过它所在的series context继续传递。
总之,series是协程,series context就是协程的局部变量。parallel是协程的并行,可汇总所有协程的运行结果。

Workflow和rpc的关系

在我们的架构里,rpc是workflow上的应用,或者说rpc是workflow上的一组协议实现。如果你有接口描述,远程接口调用的需求,一定要试用一下srpc,这是一个把workflow的功能发挥到极致又和workflow完美融合的rpc系统,同时兼容brpc和thrift协议且更快更易用,满足你的任何rpc需求。地址:https://github.com/sogou/srpc

Server的stop()操作完成时机

Server的stop()操作是优雅关闭,程序结束之前必须关闭所有server。stop()由shutdown()和wait_finish()组成,wait_finish会等待所有运行中server task所在series结束。也就是说,你可以在server task回复完成的callback里,继续向series追加任务。stop()操作会等待这些任务的结束。另外,如果你同时开多个server,最好的关闭方法是:

int main()
{
    // 一个server对象不能start多次,所以多端口服务需要定义多个server对象
    WFRedisServer server1(process);
    WFRedisServer server2(process);
    server1.start(8080);
    server2.start(8888);
    getchar(); // 输入回车结束
    // 先全部关闭,再等待。
    server1.shutdown();
    server2.shutdown();
    server1.wait_finish();
    server2.wait_finish();
    return 0;
}

如何在收到某个特定请求时,结束server

因为server的结束由shutdown()和wait_finish()组成,显然就可以在process里shutdown,在main()里wait_finish,例如:

#include <string.h>
#include <atomic>
#include “workflow/WFHttpServer.h”

extern void process(WFHttpTask *task);
WFHttpServer server(process);

void process(WFHttpTask *task) {
    if (strcmp(task->get_req()->get_request_uri(), “/stop”) == 0) {
        static std::atomic<int> flag;
        if (flag++ == 0)
            server.shutdown();
        task->get_resp()->append_output_body(“<html>server stop</html>”);
        return;
    }

    /* Server’s logic */
    //  ....
}

int main() {
    if (server.start(8888) == 0)
        server.wait_finish();

    return 0;
}

以上代码实现一个http server,在收到/stop的请求时结束程序。process中的flag是必须的,因为process并发执行,只能有一个线程来调用shutdown操作。

Server里需要调用非Workflow框架的异步操作怎么办

还是使用counter。在其它异步框架的回调里,对counter进行count操作。

void other_callback(server_task, counter, ...)
{
    server_task->get_resp()->append_output_body(result);
    counter->count();
}

void process(WFHttpTask *server_task)
{
    WFCounterTask *counter = WFTaskFactory::create_counter_task(1, nullptr);
    OtherAsyncTask *other_task = create_other_task(other_callback, server_task, counter);//非workflow框架的任务
    other_task->run();
    series_of(server_task)->push_back(counter);
}

注意以上代码里,counter->count()的调用可能先于counter的启动。但无论什么时序,程序都是完全正确的。

个别https站点抓取失败是什么原因

如果浏览器可以访问,但用workflow抓取失败,很大概率是因为站点使用了TLS扩展功能的SNI。可以通过全局配置打开workflow的客户端SNI功能:

    struct WFGlobalSettings settings = GLOBAL_SETTINGS_DEFAULT;
    settings.endpoint_params.use_tls_sni = true;
    WORKFLOW_library_init(&settings);

开启这个功能是有一定代价的,所有https站点都会启动SNI,相同IP地址但不同域名的访问,将无法复用SSL连接。
因此用户也可以通过upstream功能,只打开对某个确定域名的SNI功能:

#Include "workflow/UpstreamManager.h"

int main()
{
    UpstreamManager::upstream_create_weighted_random("www.sogou.com", false);
    struct AddressParams params = ADDRESS_PARAMS_DEFAULT;
    params.endpoint_params.use_tls_sni = true;
    UpstreamManager::upstream_add_server("www.sogou.com", "www.sogou.com", &params);
    ...
}

上面的代码把www.sogou.com设置为upstream名,并且加入一个同名的server,同时打开SNI功能。

怎么通过代理服务器访问http资源

方法一(只适用于http任务且无法重定向):
可以通过代理服务器的地址创建http任务,并重新设置request_uri和Host头。假设我们想通过代理服务器www.proxy.com:8080访问http://www.sogou.com/ ,方法如下:

task = WFTaskFactory::create_http_task("http://www.proxy.com:8080", 0, 0, callback);
task->set_request_uri("http://www.sogou.com/");
task->set_header_pair("Host", "www.sogou.com");

方法二(通用。但有些代理服务器只支持HTTPS。HTTP还是推荐用方法一):
通过带proxy_url的接口创建http任务:

class WFTaskFactory
{
public:
    static WFHttpTask *create_http_task(const std::string& url,
                                        const std::string& proxy_url,
                                        int redirect_max, int retry_max,
                                        http_callback_t callback);
};

其中proxy_url的格式为:http://user:passwd@your.proxy.com:port/
proxy只能是"http://"开头,而不能是"https://"。port默认值为80。
这个方法适用于http和https URL的代理,可以重定向,重定向时继续使用该代理服务器。

http server是否支持RESTful接口

推荐使用wfrest项目,这是基于workflow的一套RESTful API开发框架,项目地址:https://github.com/wfrest/wfrest

请问 process 是并发调用的还是串行调用的?比如有多个 client 向 server 发送请求,这些请求是由一个 MPMC 的消息队列维护的吗?如果要实现MPSC的效果,是否必须自己后挂一个queue来实现?

请问 process 是并发调用的还是串行调用的?比如有多个 client 向 server 发送请求,这些请求是由一个 MPMC 的消息队列维护的吗?如果要实现MPSC的效果,是否必须自己后挂一个queue来实现?

那必然是并发的。你需要一个串行的process吗?应该用counter可以实现。

请问 process 是并发调用的还是串行调用的?比如有多个 client 向 server 发送请求,这些请求是由一个 MPMC 的消息队列维护的吗?如果要实现MPSC的效果,是否必须自己后挂一个queue来实现?

你可以发个新的issue出来,我给你写个串行process的示例。

@Barenboim DAG要使用CounterTask的原因是什么呢? 比如最简单的CNN模型就是一个串行任务 前一个节点的输出是下一个节点的输入 直接使用serieswork不可以吗? 这种情况下还有必要使用CounterTask不 :)

@Barenboim DAG要使用CounterTask的原因是什么呢? 比如最简单的CNN模型就是一个串行任务 前一个节点的输出是下一个节点的输入 直接使用serieswork不可以吗? 这种情况下还有必要使用CounterTask不 :)

你说的方法是可行的,可以参考一下WFGraphTask,test目录里有测试用例。 Graph的每节点是一个task,但因为task肯定运行在series里,那么是可以把节点变成多个task的串行的。只是这样子的话,好像graph里处理依赖的方式就不太统一了?
目前比较有意思的课题是在dag里怎么传递数据。WFGraphTask目前需要用户自己处理数据传递。

@Barenboim 是的哈 现在的数据传递需要用户自己处理 如果DAG图比较大的话 这个传输数据的过程还比较复杂 而且感觉比较容易有冗余和出错。 WFGraphTask下来研究一哈 感谢:)

完全没接触过大型后端开发,都是单机的功能程序,请问如何向一个计算机小白解释workflow的主要用途和应用场景呢?

完全没接触过大型后端开发,都是单机的功能程序,请问如何向一个计算机小白解释workflow的主要用途和应用场景呢?

完全单机的话,workflow也是一个并行计算,进程通信和异步文件IO工具。你可以试试只用WFGoTask,workflow整体退化成一个有任务依赖关系的线程池。

完全没接触过大型后端开发,都是单机的功能程序,请问如何向一个计算机小白解释workflow的主要用途和应用场景呢?

完全单机的话,workflow也是一个并行计算,进程通信和异步文件IO工具。你可以试试只用WFGoTask,workflow整体退化成一个有任务依赖关系的线程池。

想用来通过workflow源码学习后端开发,正好手边的项目是Windows平台下的,一般后端开发都是Linux,
把workflow跑在Windows上的话,多线程函数、调度、高性能这些特性依然有效吗,是不是都用的Linux里面独有的函数、机制呢?Windows可以达到高性能吗?

完全没接触过大型后端开发,都是单机的功能程序,请问如何向一个计算机小白解释workflow的主要用途和应用场景呢?

完全单机的话,workflow也是一个并行计算,进程通信和异步文件IO工具。你可以试试只用WFGoTask,workflow整体退化成一个有任务依赖关系的线程池。

想用来通过workflow源码学习后端开发,正好手边的项目是Windows平台下的,一般后端开发都是Linux,
把workflow跑在Windows上的话,并发、调度、高性能这些特性依然有效吗,是不是都用的Linux里面独有的机制概念呢?Windows可以达到高性能吗?

实测windows版性能更高,因为iocp性能很好。目前除了文件异步IO任务windows下没有实现,kafka插件没有在windows分枝里加入之外,其它功能是一致的。
不过上线最好还是Linux。Windows版在公司里没有什么大规模的应用,稳定性我们没有底。

完全没接触过大型后端开发,都是单机的功能程序,请问如何向一个计算机小白解释workflow的主要用途和应用场景呢?

完全单机的话,workflow也是一个并行计算,进程通信和异步文件IO工具。你可以试试只用WFGoTask,workflow整体退化成一个有任务依赖关系的线程池。

想用来通过workflow源码学习后端开发,正好手边的项目是Windows平台下的,一般后端开发都是Linux,
把workflow跑在Windows上的话,并发、调度、高性能这些特性依然有效吗,是不是都用的Linux里面独有的机制概念呢?Windows可以达到高性能吗?

实测windows版性能更高,因为iocp性能很好。目前除了文件异步IO任务windows下没有实现,kafka插件没有在windows分枝里加入之外,其它功能是一致的。
不过上线最好还是Linux。Windows版在公司里没有什么大规模的应用,稳定性我们没有底。

请问Windows版本使用的哪个版本的visual studio生成项目的,用的vs2017,生成的项目 ALL_BUILD里面有workflow工程,但是并没有生成这个工程,导致编译INSTALL的时候提示没有 workflow.lib。
写了Windows编译笔记记录了编译问题,现象截图了能帮看下吗
c++-Sogou C++ Workflow-Windows平台编译过程记录

谢谢,编译成功,编译过程流畅,关键就是openssl要单独安装,补充到上面的记录里了。

请问 Windows 版本使用的哪个版本的 visual studio 生成项目的,用的 vs2017,生成的项目 ALL_BUILD 里面有 workflow 工程,但是并没有生成这个工程,导致编译 INSTALL 的时候提示没有 workflow.lib。
写了 Windows 编译笔记记录了编译问题,现象截图了能帮看下吗
c++-Sogou C++ Workflow-Windows 平台编译过程记录

@eatcosmos
VS2017和VS2019都可以的,可以尝试命令行操作下,一般来说,安装好OpenSSL后,在根目录执行下面的两条命令即可:
cmake -B build .
cmake --build build --config [Debug|RelWithDebInfo|Release]

生成的库在"_lib"目录下
链接仅供参考:Windows编译

请问 workflow的可执行文件一般用 systemctl 做守护进程吗,比如 http_echo_server,要确保这个程序一直运行的话,是不是通常加到 systemctl呢?

请问 workflow的可执行文件一般用 systemctl 做守护进程吗,比如 http_echo_server,要确保这个程序一直运行的话,是不是通常加到 systemctl呢?

这个简单的

$ nohup ./http_each_server &

也可以吧?

nohup ./http_each_server &

谢谢,这么简单,还担心中间异常了会不会中断了,所以想守护下
好像一般web服务器都不支持单个请求的阻塞响应,感觉这个这个需求应该很合理,发现workflow可以,逻辑很简单清晰

nohup ./http_each_server &

谢谢,这么简单,还担心中间异常了会不会中断了,所以想守护下
好像一般web服务器都不支持单个请求的阻塞响应,感觉这个这个需求应该很合理,发现workflow可以,逻辑很简单清晰

什么叫单个请求阻塞响应?

什么叫单个请求阻塞响应?

就是分别请求很多文件 /1.txt /2.txt /3.txt ......,这些文件是请求后再动态生成的,需要等几秒后文件生成后再响应请求
网上搜了好像都是需要立即响应,没有提供接口调用文件生成函数
就直接在 process 里加了个循环 判断 abs_path 是否存在,存在就继续 open(),不知道是否合理

什么叫单个请求阻塞响应?

就是分别请求很多文件 /1.txt /2.txt /3.txt ......,这些文件是请求后再动态生成的,需要等几秒后文件生成后再响应请求
网上搜了好像都是需要立即响应,没有提供接口调用文件生成函数
就直接在 process 里加了个循环 判断 abs_path 是否存在,存在就继续 open(),不知道是否合理

方法不合理!你明天重新发个issue,我给你写个正确的方法。不要占用process线程的。

什么叫单个请求阻塞响应?

就是分别请求很多文件 /1.txt /2.txt /3.txt ......,这些文件是请求后再动态生成的,需要等几秒后文件生成后再响应请求
网上搜了好像都是需要立即响应,没有提供接口调用文件生成函数
就直接在 process 里加了个循环 判断 abs_path 是否存在,存在就继续 open(),不知道是否合理

方法不合理!你明天重新发个issue,我给你写个正确的方法。不要占用process线程的。

谢谢,发了个 如何在合理位置添加耗时的 文件异步IO任务 ,明天帮检查个看看怎么更合理的

您好,想问一下如果想要阅读源码的话,有推荐的阅读顺序吗?谢谢!

@ybbetter 你好~感谢关注workflow~

因为每个人的使用场景不同,源码阅读建议从自己需要的场景入手比较好,以下是一些小建议:

  1. 了解源码中基本调用接口:tutorial是根据概念由浅入深的顺序编排的,先根据主页把tutorial试一下,对应的文档也可以先看完,然后看其他主题的文档,了解基本接口;
  2. 了解任务和工厂的关系:找到你平时最常用的一个场景(如果没有的话,可以从最常用的Http协议或其他网络协议入手,看看源码中factory和task的关系;
  3. 根据一个任务的生命周期看基本层次:gdb跟着这个场景看看整体调用流程经过那些层次,具体感兴趣的部分可以单独拿出来细读源码;
  4. 理解异步资源的并列关系:workflow内部多种异步资源是并列的,包括:网络CPU磁盘计时器计数器,可以了解下他们在源码中互相是什么关系;
  5. 底层具体资源的调度和复用实现:对epoll的封装或者多维队列去实现线程任务的调度,底层都有非常精巧的设计,这些可以在了解workflow整体架构之后深入细看。

祝你能够获得更多的思路启发,有任何建设性的提议也欢迎随时与我们交流~

@ybbetter 你好~感谢关注workflow~

因为每个人的使用场景不同,源码阅读建议从自己需要的场景入手比较好,以下是一些小建议:

  1. 了解源码中基本调用接口:tutorial是根据概念由浅入深的顺序编排的,先根据主页把tutorial试一下,对应的文档也可以先看完,然后看其他主题的文档,了解基本接口;
  2. 了解任务和工厂的关系:找到你平时最常用的一个场景(如果没有的话,可以从最常用的Http协议或其他网络协议入手,看看源码中factory和task的关系;
  3. 根据一个任务的生命周期看基本层次:gdb跟着这个场景看看整体调用流程经过那些层次,具体感兴趣的部分可以单独拿出来细读源码;
  4. 理解异步资源的并列关系:workflow内部多种异步资源是并列的,包括:网络CPU磁盘计时器计数器,可以了解下他们在源码中互相是什么关系;
  5. 底层具体资源的调度和复用实现:对epoll的封装或者多维队列去实现线程任务的调度,底层都有非常精巧的设计,这些可以在了解workflow整体架构之后深入细看。

祝你能够获得更多的思路启发,有任何建设性的提议也欢迎随时与我们交流~

可以出一些实际产品中的复杂业务场景的应用demo文档吗,感觉这样结合应用效率会更高。因为没机会接触复杂业务,目前有需求的最复杂业务的就是这个了 Server如何等待一个非本框架的异步事件完成? #328 ,所以其他高级功能都想不出来可以怎么用或者为什么要这么用:
比如异步请求只想到了参考tutorial 文件异步IO任务 里的代码,没想到可以用 计数器 的功能(使用后工作流程更清晰了),前面多亏 @Barenboim 帮写了个demo,发现对计数器可以这么用,不然就while循环看哪里能写的就直接写里面了,这样写逻辑可能就越来越乱了。
不过看了计数器的复杂功能还是没印象,因为想不出来有什么可以应用的复杂场景.

  1. 请问 workflow 和 k8s 什么关系的,一般组合使用的时候是怎样的搭配?
  2. workflow可以完全通过自己组合实现nginx的所有功能吗?
  3. 感觉workflow的设计逻辑很直观朴素,没有复杂新名词新概念,workflow 如何应用于 分布式、机器学习部署等应用场景呢?因为看到readme里说可以用于神经网络?
  • 请问 workflow 和 k8s 什么关系的,一般组合使用的时候是怎样的搭配?
  • workflow可以完全通过自己组合实现nginx的所有功能吗?
  • 感觉workflow的设计逻辑很直观朴素,没有复杂新名词新概念,workflow 如何应用于 分布式、机器学习部署等应用场景呢?因为看到readme里说可以用于神经网络?

1. workflow与k8s的关系

  • workflow是介于业务代码操作系统之间的单机框架,为开发者提供异步调度和任务型的编程范式;
  • k8s是介于pod集群之间的多机系统,为开发者管理容器的部署、扩展和资源共享等;

组合场景:
我们公司内部有开发者使用基于workflow做的rpc系统SRPC,编写自己的业务代码,跑在k8s集群上。
前面说到k8s做了自己集群的管理,所以业务代码需要一层网关去帮你寻址和转发。
目前我们用的网关是envoy,然后做了SRPC协议的扩展,于是可以支持使用SRPC协议的workflow用户把代码跑在k8s上。

2. workflow完全可以通过二次开发实现nginx的所有功能

实际上网络通信只是workflow的其中一种应用场景,但用作web server的时候,我们有和nginx做过同场景下的性能压测。而且我们也自带了upstream模块,通过配置也可以使用多种负载均衡方式对下游发起请求。

nginx有许多我们暂时核心模块所不支持的功能,比如:

  1. 配置即可指定restful逻辑的跳转;
  2. 支持lua代码;

但workflow很多点做得比nginx要更通用友好,比如:

  1. parallel task比nginx的subrequest要更易用、更好控制流程;
  2. 二次开发难度比写一个ngx_module_t要简单;
  3. 连接复用率高(nginx各个进程间连接不能复用;
  4. 计算的切换更顺(计算和通信是并列的异步资源;
  5. 最后很重要的一点,内存边界清晰。交给workflow调度期间用户拿不到task,只有交回给用户的时候(比如callback)才能操作task,然后一切内存会在callback之后被释放干净。而nginx如果对request进行操作,是要求用户维护读过的位置的指针的,用户代码和核心框架代码交织在一起,稍有不慎就会蹦。(别问!问就是写过nginx module蹦过的人...

3. workflow的分布式、机器学习部署的使用场景

感谢对workflow的认可!!!(敲开心 ♪───O(≧∇≦)O────♪
workflow目前是单进程内的框架,如果要做分布式,其实要解决的是你随时希望知道其他人在哪里的问题,所以需要利用workflow去对接服务发现服务治理。目前服务治理已经在workflow自带的命名服务体系解决了,而服务发现可以使用开发者实际的发现系统(比如上述的envoy等)。机器学习可能还是类似你另一个issue里讨论的场景:Server如何等待一个非本框架的异步事件完成?-- Good Question
workflow目前除了并行串行,还可以支持DAG,另外提供了WFGraphTask,所以可以自行组建神经网络

最后~~~
感谢你对于梳理workflow常用场景的建议,我们目前有计划把用到workflow的开发者及实际场景进行汇总,感觉更有说服力和参考价值,你可以到时候查看和提议~

@eatcosmos 如果你可以把这三个问题单独列到一个新issue就更好了,因为这些问题比较独立宽泛,思路可以分享给其他小伙伴,也有助于我后续号召workflow实际开发者场景的汇总~

@holmes1412 谢谢,写的太详细认真了,增长了很多新知识,很多概念都没机会接触过. 问的3个问题都是在网上听说到的高频词,k8s、nginx、分布式、生产环境机器学习...实际没有自己实际用过,等有使用机会了看怎么结合workflow用起来,到时再单独发issue问具体些.
期待 workflow 取代 nginx 成为主流,没实际用过nginx,因为看教程感觉nginx要学的新名词很多有点望而却步,而workflow的思路很朴素,编译简单上手快一键运行demo,重点在业务流/workseries逻辑而不是学习workflow本身的新增概念,既然能实现nginx的所有功能而且效率更高,应该得到更多普及.

请问 workflow能否作为一个C++的微服务框架,通过workflow怎么开发一个微服务处理不同的Get,Post请求?以及如何解析类似于图片格式这样的数据?

请问 workflow能否作为一个C++的微服务框架,通过workflow怎么开发一个微服务处理不同的Get,Post请求?以及如何解析类似于图片格式这样的数据?

这个肯定可以的呀,而且非常方便快捷。但是像解析图片,解析json这类的功能我们是没有提供的,你只需要在process里调用你自己的解析库就可以了。复杂的解析还可以封装成计算任务,使通讯与计算的融合更加高效。

想问一下,workflow 支持不支持功能裁剪.mysql redis kafka之类的代码在嵌入式环境下 都不需要.目前在cmakelists.txt中没有看到相关的内容.

非常好裁减。kafka本来就默认不编译。redis,mysql直接不编译相关.cc和.c文件就可以了,头文件都不用改。

请问 workflow能否作为一个C++的微服务框架,通过workflow怎么开发一个微服务处理不同的Get,Post请求?以及如何解析类似于图片格式这样的数据?

这个肯定可以的呀,而且非常方便快捷。但是像解析图片,解析json这类的功能我们是没有提供的,你只需要在process里调用你自己的解析库就可以了。复杂的解析还可以封装成计算任务,使通讯与计算的融合更加高效。

一个server如何处理不同的路由请求,目前看到的都是一个serverTask对应一个process,所有请求的处理都写在一个process里面么,还是有其它的设计?要是有更多使用实际场景的tutorial就更好了,要不然快速上手还是要对源码进一步深入研究,感谢。

请问 workflow能否作为一个C++的微服务框架,通过workflow怎么开发一个微服务处理不同的Get,Post请求?以及如何解析类似于图片格式这样的数据?

这个肯定可以的呀,而且非常方便快捷。但是像解析图片,解析json这类的功能我们是没有提供的,你只需要在process里调用你自己的解析库就可以了。复杂的解析还可以封装成计算任务,使通讯与计算的融合更加高效。

一个server如何处理不同的路由请求,目前看到的都是一个serverTask对应一个process,所有请求的处理都写在一个process里面么,还是有其它的设计?要是有更多使用实际场景的tutorial就更好了,要不然快速上手还是要对源码进一步深入研究,感谢。

嗯嗯,项目还有好多内容,例示里没有覆盖到,但是只要把文档示例都简单过一遍,主要的功能点和套路都可以了解。更深入的东西就只能看看源码了。
我们的框架没有提供不同前缀的路由功能,我们认为这些和核心无关,用户可以在process的基础上再做封装。

A3426A82-F5A5-4A8F-AB62-FE18CEDB3BF7
为啥这里return的时候程序会崩?调试最后挂在dispatch中的subtask_done(),麻烦求解答,以及workflow中的http协议,有没有提供处理不同格式的请求body的解析?
image

A3426A82-F5A5-4A8F-AB62-FE18CEDB3BF7
为啥这里return的时候程序会崩?调试最后挂在dispatch中的subtask_done(),麻烦求解答,以及workflow中的http协议,有没有提供处理不同格式的请求body的解析?
image

我怀疑你的main函数退出了。server.start()是非阻塞的呀,注意堵住主进程。
尽量发新的issue,不要回复在这里呀。

您好,心中有一些疑惑向您请教,callback方式比future或用户态协程能给程序带来更高的效率是基于什么样的考虑得出的结论,还是某些论文得出的结论?或者如果我想弄明白这些问题,应该怎样按图索骥。

@kernelai 你好呀,其实看看future或者用户态协程的概念就很好理解了。网上评测也有许多,可以到github上找找~具体思路我这里粗浅地帮你梳理一下,仅供参考:

具体问题:异步事件回来了得有个机制来通知我。
假设场景:同时发出10w个请求,而我们吞吐只有1w。问:以下三种机制有什么优劣。

1. future
我发出一个异步请求,基本上是拿着一个future在这get(),等到东西回来了我就能get到。这个很明显,我会浪费一个线程在这里等。

2. 用户态协程
这里仅讨论传统的有栈协程,语言级别提供的支持另说。每个异步请求发出时即需要分配一份栈空间,把寄存器上可能用到的东西全部保存下来。

3. callback
对C语言来说只是一个函数指针,请求发出时由框架来帮你保存具体需要的上下文,请求回来时框架帮你用有限的内部线程去做真正和吞吐相关的事情,资源上是最节省的。

这个话题很大,以上仅抛砖引玉,不可能概括得很全面。欢迎动手尝试一下对比,或者再单独发issue追更~~~

您好,心中有一些疑惑向您请教,callback方式比future或用户态协程能给程序带来更高的效率是基于什么样的考虑得出的结论,还是某些论文得出的结论?或者如果我想弄明白这些问题,应该怎样按图索骥。

您好。你是360的小伙伴吧?之前线下见过的。我也觉得这是个好问题可以单开issue讨论,

@kernelai 你好呀,其实看看future或者用户态协程的概念就很好理解了。网上评测也有许多,可以到github上找找~具体思路我这里粗浅地帮你梳理一下,仅供参考:

具体问题:异步事件回来了得有个机制来通知我。 假设场景:同时发出10w个请求,而我们吞吐只有1w。问:以下三种机制有什么优劣。

1. future 我发出一个异步请求,基本上是拿着一个future在这get(),等到东西回来了我就能get到。这个很明显,我会浪费一个线程在这里等。

2. 用户态协程 这里仅讨论传统的有栈协程,语言级别提供的支持另说。每个异步请求发出时即需要分配一份栈空间,把寄存器上可能用到的东西全部保存下来。

3. callback 对C语言来说只是一个函数指针,请求发出时由框架来帮你保存具体需要的上下文,请求回来时框架帮你用有限的内部线程去做真正和吞吐相关的事情,资源上是最节省的。

这个话题很大,以上仅抛砖引玉,不可能概括得很全面。欢迎动手尝试一下对比,或者再单独发issue追更~~~

感谢您的回答。
背景是最近在调研一些高性能异步框架,其中也看到了seastar。有些初步调研结论我同步到这里,咱们一起讨论下。

您好,心中有一些疑惑向您请教,callback方式比future或用户态协程能给程序带来更高的效率是基于什么样的考虑得出的结论,还是某些论文得出的结论?或者如果我想弄明白这些问题,应该怎样按图索骥。

您好。你是360的小伙伴吧?之前线下见过的。我也觉得这是个好问题可以单开issue讨论,

是的。^~^

@kernelai 你好呀,其实看看future或者用户态协程的概念就很好理解了。网上评测也有许多,可以到github上找找~具体思路我这里粗浅地帮你梳理一下,仅供参考:
具体问题:异步事件回来了得有个机制来通知我。 假设场景:同时发出10w个请求,而我们吞吐只有1w。问:以下三种机制有什么优劣。
1. future 我发出一个异步请求,基本上是拿着一个future在这get(),等到东西回来了我就能get到。这个很明显,我会浪费一个线程在这里等。
2. 用户态协程 这里仅讨论传统的有栈协程,语言级别提供的支持另说。每个异步请求发出时即需要分配一份栈空间,把寄存器上可能用到的东西全部保存下来。
3. callback 对C语言来说只是一个函数指针,请求发出时由框架来帮你保存具体需要的上下文,请求回来时框架帮你用有限的内部线程去做真正和吞吐相关的事情,资源上是最节省的。
这个话题很大,以上仅抛砖引玉,不可能概括得很全面。欢迎动手尝试一下对比,或者再单独发issue追更~~~

感谢您的回答。 背景是最近在调研一些高性能异步框架,其中也看到了seastar。有些初步调研结论我同步到这里,咱们一起讨论下。

欢迎把和seastar的对比新开个issue交流。

为什么源码里面一点注释都不写呢?

为什么源码里面一点注释都不写呢?

确实注释有点少。我们希望大多数接口定义本身就能解释自己。有些我们也加了注释。
实现代码除了特别需求提醒的,一般不会加大段注释。

我想使用httpserver传输大文件,如果client建立一个http_task向server请求一个大文件 ,然后server chunked分块传输,client会接受完所有的chunk后再调用callback 吗?

我想使用httpserver传输大文件,如果client建立一个http_task向server请求一个大文件 ,然后server chunked分块传输,client会接受完所有的chunk后再调用callback 吗?

独立的问题重新发issue吧。

WaitGroup的注释较少,可以介绍一下WaitGroup两个函数done() 和wait()的使用方法么?

WaitGroup的注释较少,可以介绍一下WaitGroup两个函数done() 和wait()的使用方法么?

你好。WaitGroup不是我们的一个核心组件,一般只在main函数里应用,用于防止程序退出。其它任何地方都是全异步的,不存在等待。

WaitGroup本身也只有wait和done两个操作,例如初始化时传3,那么3次done之后,wait的人就通过了,好像也没有太多需要描述的。

多谢回复! 另外,用workflow来处理计算或者计算密集型应用函数,是否可以替代类似OpenMP的多线程编程? 方法上可以把结果矩阵或者向量,通过均分的方法,把大任务划分成多个小任务组成的serial 来start ()完成么? 有数值计算或者高性能多核并行计算类应用,会采用此方法来做性能加速么?适合替代OpenMP么,有优势么? | | Qingchun Xie 15210341746 Best Regards | ---- 回复的原邮件 ---- | 发件人 | @.> | | 发送日期 | 2023年11月09日 22:49 | | 收件人 | sogou/workflow @.> | | 抄送人 | xqch1983 @.>, Comment @.> | | 主题 | Re: [sogou/workflow] FAQ(持续更新) (#170) | WaitGroup的注释较少,可以介绍一下WaitGroup两个函数done() 和wait()的使用方法么? 你好。WaitGroup不是我们的一个核心组件,一般只在main函数里应用,用于防止程序退出。其它任何地方都是全异步的,不存在等待。 WaitGroup本身也只有wait和done两个操作,例如初始化时传3,那么3次done之后,wait的人就通过了,好像也没有太多需要描述的。 — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

你好,具体问题重新发issue吧。workflow代替openMP应该是比较容易的,你的需求可能可以对应到某一类计算模式上。

请问想用workflow来对接 fast-dds,应该怎么入手?

请问想用workflow来对接 fast-dds,应该怎么入手?

你好。这个fast-dds我不太了解。可以重新发个issue出来讨论。

大佬们好,我是一个正在找c++后端实习的大四生,我在做了几个基础的服务器项目之后想进一步学习高级点的项目,就是想问问这个项目应该从哪里入手,因为没有项目结构,所以好像看着有点困难QAQ。

大佬们好,我是一个正在找c++后端实习的大四生,我在做了几个基础的服务器项目之后想进一步学习高级点的项目,就是想问问这个项目应该从哪里入手,因为没有项目结构,所以好像看着有点困难QAQ。

你好。可以按照我们tutorial教程的顺序,把tutorial里的例子都运行一遍,大概就知道玩法了。

我们的文档大多数是使用文档,关于实现原理,可以直接看代码,如果有什么问题随时提issue就可以。

大佬你好,我已经吧tutorial的例子看了一遍了,算是有了一个大概的认知。
目前在看相关功能的实现代码,但是不知道每个功能在哪一个文件夹下,如果能有注释和标识就更好了。
88d09991656ff2229b97dd55d7c9a6e

b0d0f9e8c6fa738a708b7aacf5ad11e