Introduction to the

Nginx’s high performance is recognized by the industry, and its share in the global server market has been increasing year by year in recent years. It has also been widely used in well-known Internet companies in China. Alibaba also expanded and built the famous Tengine based on Nginx. OpenResty is a dynamic Web platform built by Chinese zhang Yichun based on Nginx and LuaJIT. LuaJIT is a real-time compiler of Lua programming language. Lua is a powerful, dynamic, lightweight programming language. The language is designed to be embedded in applications to provide flexible extension and customization, and OpenResty is an extensible Web platform that extends Nginx using Lua. Currently OpenResty is mostly used in API gateway development, but it can also be used to replace Nginx in reverse proxy and load balancing scenarios.

The architecture of OpenResty is composed

As mentioned above, The bottom layer of OpenResty is based on Nginx and LuaJIT, so OpenResty inherits the multi-process architecture of Nginx, and each Worker process is obtained by fork the Master process. In fact, The LuaJIT virtual machine in the Master process is also forked. All coroutines in the same Worker share the LuaJIT virtual machine, and Lua code is executed in this virtual machine. At the same time, each Worker process can only handle one user’s request, that is, only one coroutine is running.

Nginx

Since Nginx uses an event-driven model to process requests, each Worker process should have an exclusive CPU. In practice, we often configure the number of Worker processes to be the same as the number of CPU cores, and bind each Worker process to a certain CPU core, so that the CPU cache on each CPU core can be better used, the hit ratio of cache invalidity can be reduced, and the performance of request processing can be improved.

LuaJIT

In fact, OpenResty initially uses standard Lua by default, and only uses LuaJIT since version 1.5.8.1. The reason behind this is that LuaJIT has great performance advantages over standard Lua.

First, LuaJIT’s runtime environment includes aJIT compiler that directly generates machine code, in addition to a Lua interpreter implemented in assembly. To begin with, LuaJIT is like standard Lua. Lua code is compiled into bytecode, which is interpreted and executed by LuaJIT’s interpreter. However, LuaJIT’s interpreter executes bytecode while recording some runtime statistics, such as the actual number of runs per Lua function call entry and the actual number of executions per Lua loop. When these times exceed some random threshold, the corresponding Lua function entry or the corresponding Lua loop is considered hot enough to trigger the JIT compiler to work. The JIT compiler tries to compile the corresponding Lua code path, starting at the entry point of the heat function or at some point in the heat cycle. The process of compiling is to convert LuaJIT bytecode into LuaJIT defined intermediate code (IR) and then regenerate it into machine code of the target machine. This process is similar to the PRINCIPLE of THE JIT compiler in Java. In fact, they are the same kind of optimization methods to improve the efficiency of the program. The so-called underlying technologies are all the same and can be learned by analogy.

Second, LuaJIT is also tightly integrated with FFI (Foreign Function Interface, which cannot be used as a separate module), allowing you to call external C functions and use C data structures directly from within Lua code. FFI does the Lua/C binding by parsing normal C declarations. The JIT compiler accesses C data structures from Lua code and generates the same code as the C compiler. Unlike function calls bound through the classic Lua/C API, calls to C functions can be inline in JIT-compiled code, so the FFI approach is not only simple, but also performs better than the traditional Lua/C API approach.

Here is a simple example of a call:

local ffi = require("ffi") ffi.cdef[[ int printf(const char *fmt, ...); ]] ffi.C.printf("Hello %s!" , "world")Copy the code

In just a few short lines of code, you can directly call the C printf function in Lua and print Hello World! . Similarly, we can use FFI to call C functions of NGINX and OpenSSL to accomplish more functions.

How OpenResty works

OpenResty is a high-performance Web platform based on Nginx, so its efficient operation is inseparable from Nginx.

Nginx processes HTTP requests in 11 phases, as we can see from the source ngx_http_core_module.h:

typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0,

    NGX_HTTP_SERVER_REWRITE_PHASE,

    NGX_HTTP_FIND_CONFIG_PHASE,
    NGX_HTTP_REWRITE_PHASE,
    NGX_HTTP_POST_REWRITE_PHASE,

    NGX_HTTP_PREACCESS_PHASE,

    NGX_HTTP_ACCESS_PHASE,
    NGX_HTTP_POST_ACCESS_PHASE,

    NGX_HTTP_PRECONTENT_PHASE,

    NGX_HTTP_CONTENT_PHASE,

    NGX_HTTP_LOG_PHASE
} ngx_http_phases;
Copy the code

Coincidentally, OpenResty also has 11 *_by_lua instructions, which are closely related to the 11 execution phases of NGINX. Directives are the basic building blocks of Nginx scripting in Lua, specifying when user-written Lua code will run and how the results will be used. The following figure shows the order of execution of the different instructions, which helps clarify the logic of the script we wrote.

Among them, init_by_lua is executed only when the Master process is created, and init_worker_by_lua is executed only when each Worker process is created. Other *_by_lua directives are triggered by terminal requests and are repeatedly executed.

The timing and use of each OpenResty directive are described below.

Embed Lua code during Nginx startup

Init_by_lua * : Lua code called immediately at the Lua VM level when Nginx parses the configuration file (the Master process). Usually in the init_by_lua* phase, we can preload Lua modules and common read-only data to take advantage of the operating system’s COW (Copy on write) feature to save some memory. However, the init_by_lua phase is not able to perform HTTP requests for remote configuration information, which is somewhat inconvenient for initialization.

Init_worker_by_lua * : It is called when the Nginx Worker process is started. Generally, in the init_worker_by_lua phase, we will perform some scheduled tasks, such as dynamic awareness of capacity expansion of upstream service nodes and health check, etc. For the problem that HTTP requests cannot be executed in the init_by_lua phase, It can also be performed in a scheduled task during this phase.

Lua code is embedded in OpenSSL when it handles the SSL protocol

Ssl_certificate_by_lua * : Using the SSL_CTX_set_cert_cb feature of the OpenSSL library (requires version 1.0.2E and above), add Lua code to the code that verifies SSL certificates for downstream clients, Can be used to set up an SSL certificate chain and corresponding private key for each request and to perform SSL handshake flow control without blocking in this context.

Embed Lua code in 11 HTTP phases

Set_by_lua * : Add Lua code to the script directive in the Nginx official ngx_http_rewrite_module module, because ngx_http_rewrite_module does not support non-blocking I/O in its directives, So the Lua API needed to generate the current Lua “light Threads “will not work at this stage. Because the Nginx event loop will be blocked during code execution in this phase, you need to avoid time-consuming operations in this phase, which is generally used to set variables with faster and less code execution.

Rewrite_by_lua * : Adds Lua code to the rewrite phase of the 11 phases, and executes the Lua code for each request as a separate module. The Lua code at this stage can make API calls and execute as a newly generated coroutine in a separate global environment (that is, a sandbox). Many functions can be implemented in this phase, such as invoking external services, forwarding, redirection processing, and so on.

Access_by_lua * : Adds Lua code to the access phase of the 11 phases for execution. Similar to rewrite_BY_lua *, it also executes Lua code for each request as a separate module. The Lua code at this stage can make API calls and execute as a newly generated coroutine in a separate global environment (that is, a sandbox). Generally used for access control and permission verification.

Content_by_lua * : Performs Lua code exclusively for each request in the 11-stage Content phase to generate the returned content. Be careful not to use this directive and other content processing directives in the same location. For example, this directive and the proxy_pass directive should not be used in the same location.

Log_by_lua * : Lua code is added to the log phase of the 11 phases and executed in the log phase. It does not replace the access log of the current request, but runs before it and is generally used for request statistics and logging.

Embed Lua code during load balancing

Balance_by_lua * : Adds Lua code to the init_upstream callback method of the reverse proxy module that generates upstream service addresses for upstream load balancing control. This Lua code execution context does not support yield, so possible yield Lua apis (such as Cosockets and “Light Threads “) are disabled in this context. However, we can generally get around this limitation by doing this in an early processing phase (such as access_by_lua*) and passing the results into this context via ngx.ctx.

Embed Lua code when filtering responses

Header_filter_by_lua * : Embed Lua code in the response header filtering phase for response header filtering processing.

Body_filter_by_lua * : The Lua code is embedded in the response packet body filtering phase for response body filtering processing. Note that this phase may be called more than once in a request, as the response body may be passed as a block. Therefore, the Lua code specified in this directive can also be run multiple times over the lifetime of a single HTTP request.

Quick OpenResty experience

After understanding the structure and basic working principles of OpenResty, we used a simple example to start OpenResty, based on our working Mac system.

Install OpenResty

$ brew tap openresty/brew
$ brew install openresty
Copy the code

Creating a Working Directory

$ mkdir ordemo
$ cd ordemo
$ mkdir logs/ conf/
Copy the code

Create the nginx configuration file

In the conf working directory, create the nginx configuration file nginx.conf.

error_log logs/error.log debug; pid logs/nginx.pid; events { worker_connections 1024; } http { access_log logs/access.log server { listen 8080; location / { content_by_lua ' ngx.say("Welcome to OpenResty!" ) '; }}}Copy the code

Start the service

$CD ordemo $openresty -p 'PWD' -c conf/nginx.conf # Stop service $openresty -p 'PWD' -c conf/nginx.conf -s stopCopy the code

If no error is reported, OpenResty has been started successfully. Requests can be made using the browser or curl command:

$curl -i 127.0.0.1:8080 HTTP/1.1 200 OK Server: openresty/1.19.3.1 Date: Tue, 29 Jun 2021 08:55:51 GMT content-type: text/plain Transfer-Encoding: chunked Connection: keep-alive Welcome to OpenResty!Copy the code

This is the simplest OpenResty based service development process, with only Lua code embedded in the Content phase of the 11 phases of the Nginx HTTP request, directly generating the request response body.

The application of OpenResty in products

Currently, the infrastructure team has developed a traffic routing component (API-Route) based on OpenResty for remote multi-live and small object projects. The component mainly identifies the user ID in the request, performs dynamic routing according to the routing rules, and also realizes gray diversion based on client IP and user ID. More roles will be assumed in the future as planned.

The simple Demo above is not very simple, do you remember the programming language introduction Demo Hello World? Hello World may seem simple, but the execution behind it is not! Likewise, OpenResty is not as simple as it seems! There e are so many cultural and technical details behind it. I know.

Finally, we welcome those students who are interested in OpenResty to communicate with us.

Reference and study list

Nginx core knowledge 150 lectures

OpenResty goes from entry-level to actual combat

OpenResty website

OpenResty API

awesome-resty

By Mr. Guo

Focus on object technology, hand in hand to the cloud of technology