Mastering Azure Serverless Computing
上QQ阅读APP看书,第一时间看更新

Language extensibility

To enable a multilanguage approach in Azure Functions, the runtime is split into two building blocks:

  • The host that has the responsibility to manage the function events
  • The language worker process in which the functions, written in the different programming languages, run.

The following diagram shows the architecture of the aforementioned building blocks:

The two layers communicate with each other using the gRPC (Remote Procedure Call (RPC)), a modern, open source, high-performance RPC framework that can run in any environment and can efficiently connect services in and across data centers. It supports load balancing, tracing, health checking, and authentication.

You can find more information about the gRPC protocol on the official website at https://grpc.io.

This framework was initially developed by Google, and at this moment it is one of the most commonly used frameworks to implement high-performance communications.

In a gRPC scenario, your client application can directly call a method on a server application as if it were a local object, even if the server application is located on another machine (if you come from a .NET background, you can imagine it as a modern remoting technology) and, of course, you can do it if the client and the server are implemented in different languages (for example, C++ for the server and Ruby for the client):

The Azure Functions Runtime and the specific language worker talk to each other using gRPC technology with the following gRPC contract:

service FunctionRpc {
rpc EventStream (stream StreamingMessage) returns (stream StreamingMessage) {}
}

As you can see, every call between the runtime and worker language uses StreamingMessage to bring information about the single call. The StreamingMessage definition looks like the following:

message StreamingMessage {
string request_id = 1;
oneof content {
// Worker signals to host that it has been started
StartStream start_stream = 20;

....

// Host sends invocation information (function id, binding data, parameters) to worker
InvocationRequest invocation_request = 4;

// Worker sends response to host
InvocationResponse invocation_response = 5;

// Structured log from the worker based off the ILogger interface
RpcLog rpc_log = 2;
}
}

For example, the InvocationRequest looks like the following:

// Host requests worker to invoke a Function
message InvocationRequest {
// Unique id for each invocation
string invocation_id = 1;

// Unique id for each Function
string function_id = 2;

// Input bindings (include trigger)
repeated ParameterBinding input_data = 3;

// binding metadata from trigger
map<string, TypedData> trigger_metadata = 4;
}

You can find the full contract definition at https://github.com/Azure/azure-functions-language-worker-protobuf/blob/dev/src/proto/FunctionRpc.proto.

Therefore, implementing a language worker for a particular programming language means implementing the gRPC client and all the event handlers defined in the gRPC contract.

Once you have implemented your own language worker, the runtime life cycle for its activation is the following:

  1. The host starts up and it starts the gRPC server (inside the runtime).
  2. For each function you define in your function app, the runtime creates a language worker. The runtime creates one worker process for each different language (client gRPC), so if the process is already created, the runtime doesn't create it.
  3. Once the worker is started, it connects to the server.
  4. The host and the worker give each other the version information of the capabilities using WorkerInitRequest and WorkerInitResponse messages.
  5. The host sends the function metadata to the worker using a FunctionLoadRequest message and the worker communicates the response to the server using a FunctionLoadResponse message.
  6. Finally, the host asks the client to execute the function with the InvocationRequest message, and the client, after the code execution, responds to the host with InvocationResponse message.

As you can imagine, implementing a language worker is not a simple task, but contract definitions and all of the existing language workers are open source, and you can look at them to understand how they work and how you can create them.

You can find all of the gRPC definitions for language workers and some of the language worker implementations at https://github.com/Azure/azure-functions-host/wiki/Language-Extensibility .