Sogou C++ Workflow

repository·master·Indexed 12 days ago

https://github.com/sogou/workflow

An enterprise-level, asynchronous programming engine for high-performance back-end services. It provides a non-blocking execution model combining networking protocols (HTTP, Redis, MySQL, Kafka), computational algorithms, and complex task workflows organized as Series, Parallel, or Directed Acyclic Graphs (DAG). It supports C++11 and runs across Linux, macOS, Windows, and Android.

Tokens
100.9K
Snippets
245
Records
330
Agent score
95%

What's inside Sogou Workflow

  1. What is a Selector task

    master

    A WFSelectorTask is designed for "one-of-many" scenarios where you want to pick the first successful result from multiple asynchronous branches and discard the rest.

    Common use cases include:

    • Sending network requests to multiple downstream services and continuing as soon as any one returns a correct result.
    • Executing a set of complex operations and continuing upon either completion or a global timeout.
    • Parallel computing where the first thread to find an expected result (e.g., an MD5 collision) completes the task.
    • Implementing "backup requests" in network applications by combining a WFSelectorTask with a timer.
  2. What is connection context and when to use it

    master

    Connection context allows you to bind arbitrary data to a specific network connection. This is useful for stateful protocols or business logic where you need to maintain state across multiple requests on the same connection.

    When to use it:

    • Stateful Protocols: When implementing servers for protocols like Redis or MySQL where connection status (like a selected database) matters.
    • Performance Optimization: To reduce data transmission overhead (e.g., caching large HTTP cookies on the server side so they don't need to be sent in every subsequent request on that connection).
    • Lifecycle Management: You can provide a deleter function to be notified and perform cleanup when the connection is closed by the peer.

    Important Limitations:

    • Stateless Protocols: For standard Redis/MySQL client tasks, the framework handles connection state via the URL (username, password, DB ID). You should not use SELECT (Redis) or USE (MySQL) commands; instead, use a different URL to switch databases.
    • Client Tasks: For client tasks, the connection is not determined at creation, so you can only access the connection context within a callback.
  3. What is a WFModuleTask and when to use it

    master

    A WFModuleTask is a specialized task type used to encapsulate a group of tasks into a single logical module. Instead of manually chaining the callback of the last task in a sequence to the next functional block, you can wrap those tasks inside a module.

    Key characteristics:

    • Encapsulation: It provides module-level encapsulation, allowing several tasks to complete a specific function without exposing their internal logic to the parent series.
    • Sub-series: A module contains a sub_series which acts as a normal SeriesWork. Tasks inside the module run within this sub-series.
    • Reduced Coupling: It reduces the coupling between different functional modules by allowing tasks to interact via a shared module context rather than direct task-to-task callbacks.
    • No State/Error Fields: Unlike standard tasks, WFModuleTask does not have its own state or error fields; it relies on the internal sub_series for execution state.
  4. What is a Counter in Workflow

    master
    A Counter is a fundamental task in the Workflow framework that acts as a semaphore without occupying a thread. It is primarily used for workflow control and managing task dependencies. Counters can be either anonymous or named and are used to trigger callbacks when a specific target_value is reached.
  5. What is a 'go task' in Workflow

    master
    A 'go task' is a simplified way to execute computing tasks, inspired by Go's concurrency model. Unlike other task types in the Workflow library, 'go tasks' do not require explicit input or output type definitions. Instead, all data is passed directly through the function's arguments. This makes them highly flexible for running arbitrary functions asynchronously.
  6. What is an upstream and how does it differ from DNS?

    master

    An upstream is a service governance abstraction in Workflow that acts like a domain name but provides advanced management capabilities. While a standard DNS domain name only points to a set of IP addresses, an upstream can:

    • Point to a set of IP addresses or domain names.
    • Include port information in the target objects.
    • Manage and select targets using various mechanisms (weighted random, consistent hash, manual selection).
    • Store large numbers of attributes for each target.
    • Be updated in real-time and in a thread-safe manner (unlike standard DNS).

    In practice, if your application does not need to access the external network, you can replace standard domain names and DNS with upstreams. If the host part of a URL matches a created upstream name, the system will use that upstream's selection logic.

  7. What is Workflow Upstream and how does it differ from DNS?

    master

    In Workflow, an Upstream acts as a local reverse proxy module that provides load balancing, error handling, circuit breaking, and service governance. Unlike DNS resolution, Workflow Upstream offers several advantages:

    • Port Awareness: DNS is not port-aware; Upstream can manage services with the same IP but different ports.
    • Address Types: While DNS only resolves to IPs, Upstream supports ip4, ip6, domain names, and unix-domain-socket.
    • Real-time Updates: DNS is subject to TTL and OS/network caching; Upstream updates are real-time and take effect immediately.
    • Efficiency: Upstream selection is a local computation/lookup, avoiding the higher overhead of DNS resolution.
    • Protocol Agnostic: Unlike Nginx Upstream (which is primarily HTTP/HTTPS), Workflow Upstream is protocol-independent and can be used to access services like MySQL, Redis, or MongoDB.
    • Zero Extra Overhead: It operates in-process, eliminating the need for an external proxy process (like Nginx) and the extra network hop associated with it.
  8. Understand Upstream selection strategies

    master

    When a request's URIHost matches an UpstreamName, the framework selects an address from that Upstream's set of recorded addresses using one of four strategies:

    1. Weight random strategy: Selection is performed randomly based on the weight assigned to each address.
    2. Consistent hash strategy: Uses a standard consistent hashing algorithm. Users can define a custom consistent hash function consistent_hash for the requested URI.
    3. Manual strategy: Uses a user-provided select function for the requested URI. If the selected target is unavailable (blown):
      • If try_another is false: The request returns a failure.
      • If try_another is true: The framework performs a second selection using the standard consistent hash algorithm (users can also define a custom consistent_hash function here).
    4. Main-backup strategy: Prioritizes 'main' servers over 'backup' servers. This strategy can be combined with any of the other three strategies.

    Recommendation: Use the Consistent hash strategy for better fault tolerance and scalability. Use the Manual strategy for complex, custom selection logic.

  9. Understand calculation task queues and scheduling

    master

    Computing tasks do not have priority levels. Instead, scheduling order is influenced by the calculation task queue name (a string passed to the factory).

    Key behaviors:

    • Resource Allocation: Each unique queue name is allocated a small block of resources internally. Do not generate infinite unique names (e.g., using a unique request ID as a queue name).
    • Scheduling Effect: If computing threads are not 100% occupied, queue names have no noticeable effect.
    • Fairness vs. Order:
      • If all tasks use the same name, they are scheduled in the order of submission.
      • If different types of computing tasks use independent names, they are scheduled fairly against each other. This is recommended for complex service flows to prevent one type of computation from starving others.
  10. Implement HTTP business logic with WFHttpServer

    master

    A WFHttpServer requires a processing function of type http_process_t (which is std::function<void (WFHttpTask *)>). Inside this function, you access the request and response objects via the WFHttpTask pointer.

    To handle the request:

    1. Get the request and response objects: server_task->get_req() and server_task->get_resp().
    2. Get the request sequence number (index of the request on the current connection) using server_task->get_task_seq().
    3. Iterate through headers using protocol::HttpHeaderCursor.
    4. Build the response body using append_output_body() or append_output_body_nocopy().
    5. Set status codes, reason phrases, and headers (e.g., Content-Type, Connection).
    void process(WFHttpTask *server_task)
    {
        protocol::HttpRequest *req = server_task->get_req();
        protocol::HttpResponse *resp = server_task->get_resp();
        long seq = server_task->get_task_seq();
        
        // ... logic to read headers and append to response ...
    
        resp->set_status_code("200");
        resp->set_reason_phrase("OK");
        resp->add_header_pair("Content-Type", "text/html");
    
        // Close connection after 10 requests
        if (seq == 9) {
            resp->add_header_pair("Connection", "close");
        }
    }
  11. Define a custom CPU computation task

    master

    To define a custom CPU computation task in Workflow, you must provide three components: an INPUT type, an OUTPUT type, and a routine function. The routine is a function that performs the actual computation, transforming the input into the output.

    The signature for the routine must be: std::function<void (INPUT *, OUTPUT *)> routine.

    Note that while the INPUT pointer is not required to be const, users can provide a function that accepts a const INPUT * if the computation does not modify the input.

    template <class INPUT, class OUTPUT>
    class __WFThreadTask
    {
        ...
        std::function<void (INPUT *, OUTPUT *)> routine;
        ...
    };
  12. Implement a custom protocol with ProtocolMessage

    master

    To create a custom communication protocol, you must inherit from protocol::ProtocolMessage and implement its virtual serialization and deserialization methods. It is highly recommended to also implement move constructors and move assignment operators to support std::move() for better performance.

    Key requirements:

    • Request and Response types: Must be constructible without arguments (default constructor). If retries occur, response objects may be destroyed and re-constructed, so they should ideally be RAII classes.
    • Serialization: Use the encode method to prepare data for sending.
    • Deserialization: Use the append method to process incoming data chunks.
    namespace protocol
    {
    class TutorialMessage : public ProtocolMessage
    {
    private:
        virtual int encode(struct iovec vectors[], int max);
        virtual int append(const void *buf, size_t *size);
        virtual int append(const void *buf, size_t size);
        // ...
    };
    
    using TutorialRequest = TutorialMessage;
    using TutorialResponse = TutorialMessage;
    }