35 | OpenResty: A more flexible web server

In the previous lecture, we saw the high-performance web server Nginx, which takes up less resources and has high processing power. It is the first choice for building websites.

Although Nginx has become the undisputed “king” of the web server field, it is not without its shortcomings. After all, it is already 15 years old.

“It is difficult for a person to surpass the times, but the times can easily surpass everyone.” The application scenarios for which Nginx was originally designed have changed, and some of its shortcomings have been exposed.

Nginx’s service management idea continues the popular practice at the time, using static configuration files on disk, so each modification must be restarted to take effect.

This is very fatal when the business changes frequently (such as the popular microservice architecture), especially for websites with thousands of servers. Just adding or deleting a line of configuration requires distributing and restarting all machines. Operation and maintenance is a very big challenge. It takes a lot of time and energy, is very costly, is very inflexible, and is difficult to “respond to needs.”

So, is there such a web server that has the advantages of Nginx but does not have the disadvantages of Nginx? It is lightweight, high-performance, flexible, and dynamically configurable?

This is what I want to talk about today about OpenResty, which is a “better and more flexible Nginx”.

What is OpenResty?

In fact, you are no stranger to OpenResty. The experimental environment of this column was built using OpenResty. After so many courses, you should have some impression of it.

OpenResty was born in 2009 and is now just 10 years old. Its creator was Zhang Yichun, a “god-level” programmer who worked for a certain treasure at the time, and his online name was “agentzh”.

OpenResty is not a brand new web server, but is based on Nginx. It takes advantage of the modular and extensible features of Nginx, develops a series of enhanced modules, and packages and integrates them to form a “one-stop ” Web Development Platform.

Although the core of OpenResty is Nginx, it goes beyond Nginx. The key lies in the ngx_lua module, which embeds the compact and flexible Lua language into Nginx. You can use scripts to operate the internal processes, multiplexing, and stages of Nginx. Processing and other various components.

You must know the benefits of scripting language. It does not require compilation and can be executed as you write it. This eliminates the long development cycle of writing modules in C language. Moreover, OpenResty also perfectly combines Lua’s own coroutines with Nginx’s event mechanism, elegantly realizing the “Synchronous non-blocking” programming paradigm that many other languages do not have, and can easily develop high-performance Web application.

Currently, OpenResty has two branches, namely the open source, free “OpenResty” and the closed source, commercial product “OpenResty +”. It operates through community support, the OpenResty Foundation, OpenResty.Inc, and other external sponsorships. (such as Kong, CloudFlare), are booming.

By the way, the official logo of OpenResty is a seagull with wings spread. The seagull was chosen because “gull” has the same pronunciation as OpenResty. In addition, the shape of this logo also looks like an “OK” gesture with the left hand, which happens to be an “O”.

Dynamic Lua

As mentioned just now, a key module in OpenResty is ngx_lua, which introduces the scripting language Lua for Nginx.

Lua is a relatively “niche” language. Although it has a long history, it is not as famous as PHP, Python, and JavaScript. This is mainly related to its own positioning.

The design goal of Lua is to embed it into other applications and bring “scripting” capabilities to other programming languages. Therefore, its “size” is relatively small and its feature set is limited. It does not pursue “big and comprehensive” but “small”. And beauty”, which is “hidden” behind other applications most of the time, is the “unsung hero”.

You may have played or heard of “World of Warcraft” and “Angry Birds”. They have Lua embedded inside, using Lua to call the underlying interface, acting as a “glue language” to write game logic scripts. Improve development efficiency.

OpenResty chose Lua as its “working language” based on the same considerations. Because Nginx C development is too troublesome, which limits the real strength of Nginx. As the “fastest scripting language”, Lua can be the perfect partner of Nginx, which can simplify development without too much loss in performance.

As a scripting language, Lua also has an important “Hot code loading” feature. It can load data from disk, Redis or any other place without restarting the process, and replace code fragments in memory at any time. This brings about “Dynamic configuration“, which allows OpenResty to never stop and realize real-time updates of configuration and business logic at the microsecond and millisecond level. Compared with Nginx’s second-level restart, it is a huge improvement. improvement.

You can take a look at the “www/lua” directory of the experimental environment, which contains some Lua scripts I wrote to test HTTP features. The codes are very simple and easy to understand, just like ordinary English “reading comprehension”, which is another aspect of Lua. One advantage: easy to learn and easy to use.

Efficient Lua

One of the “secrets” that makes OpenResty run efficiently is its “Synchronous non-blocking” programming paradigm, which you must always keep in mind if you want to develop OpenResty applications.

“Synchronous non-blocking” is essentially a kind of “Multiplexing“. Take the Nginx epoll from the previous lecture for comparison and explanation.

epoll is an operating system-level “multiplexer” that runs in kernel space. OpenResty’s “synchronous non-blocking” is based on Lua’s built-in “Coroutine“, which is an application-level “multiplexing” and runs in user space, so its resource consumption is less.

Every Lua program in OpenResty is scheduled to run by a coroutine. Like Linux’s epoll, whenever blocking may occur, the “coroutine” will immediately switch out and execute other programs. In this way, a single processing process is “blocking”, but the entire OpenResty is “non-blocking”, and multiple programs are “reused” to run in a Lua virtual machine.

The following code is a simple example of reading the body data sent by POST and then sending it back to the client:

ngx.req.read_body() -- synchronous non-blocking (1)

local data = ngx.req.get_body_data()
if data then
    ngx.print("body: ", data) -- synchronous and non-blocking (2)
end

“ngx.req.read_body” and “ngx.print” in the code are the sending and receiving actions of data respectively. Data can only be sent after receiving the data, so it is “synchronous”.

But even if it is not received or cannot be sent due to network reasons, OpenResty will not block and “wait” here, but will make a “mark” and use the waiting CPU time to process other requests until the network becomes available. Then “come back” and continue running when it is readable or writable.

Assuming that the waiting time for sending and receiving data is 10 milliseconds, and the real CPU processing time is 0.1 milliseconds, then OpenResty can process 100 requests at the same time within these 10 milliseconds, instead of blocking and queuing these 100 requests, and processing them in 1000 milliseconds. .

In addition to “synchronous non-blocking”, OpenResty also uses LuaJIT as the “Runtime” of the Lua language to further “exploit potential and increase efficiency.”

LuaJIT is an efficient Lua virtual machine that supports JIT (Just In Time) technology and can instantly compile Lua code into “local machine code”. This eliminates the disadvantages of script language interpretation and running, allowing Lua scripts to run as well as native C The code is just as fast.

In addition, LuaJIT has also added some special enhancements to the Lua language, such as the binary bit operation library bit, the memory optimization library table, and FFI (Foreign Function Interface), which allows Lua to directly call the underlying C function, which is faster than the native stack call. a lot of.

Stage processing

Like Nginx, OpenResty also uses a “pipeline” to process HTTP requests. The underlying operating basis is Nginx’s “staged processing”, but it has its own characteristics.

Nginx’s “pipeline” is composed of C modules, which can only be configured in static files. It is difficult to develop and troublesome to configure (relatively speaking). OpenResty’s “pipeline” is composed of Lua scripts, which can be loaded not only from the disk, but also from Redis and MySQL, and the process of writing and debugging is very convenient and fast.

A picture is drawn below, listing the stages of OpenResty. Compared with Nginx, the stages of OpenResty pay more attention to the processing and processing of HTTP request response messages.

There are several stages in OpenResty that are the same as Nginx, such as rewrite, access, content, and filter. These are all standard HTTP processing.

In these stages, you can use the “xxx_by_lua” instruction to embed Lua code to perform functions such as redirection jumps, access control, response generation, load balancing, and packet filtering. Because of the scripting language features of Lua, there is no need to consider underlying details such as memory allocation, resource recycling and release, and you can focus on writing very complex business logic. The development efficiency is much higher than that of C modules, and it is easy to expand and maintain.

There are two special stages in OpenResty that are different from Nginx.

One is the “init phase“, which is divided into “master init” and “worker init”, which run when the master process and worker process start. The service has not yet been provided at this stage, so it doesn’t matter if it is slower. You can call some blocking interfaces to initialize the server, such as reading disks, MySQL, loading black and white lists or data models, and then putting them into shared memory for runtime use.

The other is the “ssl phase“, which is considered a major initiative of OpenResty, and can dynamically load certificates during the TLS handshake, or send “OCSP Stapling”.

Remember the “SNI extension” mentioned in Lecture 29? Nginx can select certificates to implement HTTPS virtual hosts based on the “server name indication”, but static configuration is very inflexible and requires writing many similar configuration blocks. Although Nginx later added variable support, it had to read the disk for every handshake, which was very inefficient.

In OpenResty, you can use the command “ssl_certificate_by_lua” to write a Lua script, read the SNI name, and obtain the certificate directly from shared memory or Redis. Not only is there no disk read blocking, but the certificate is also fully dynamically configurable and can easily support a large number of HTTPS virtual hosts without modifying the configuration file.

Summary

Nginx relies on static configuration files on disk, and modifications must be restarted to take effect, which lacks flexibility;

OpenResty is based on Nginx and packages many useful modules and libraries. It is a high-performance web development platform;

The working language of OpenResty is Lua, which is small and flexible, has high execution efficiency and supports “code hot loading”;

The core programming paradigm of OpenResty is “synchronous non-blocking”, which uses coroutines and does not require asynchronous callback functions;

OpenResty also uses the “staged processing” working mode, but because all Lua codes are executed in stages, it is very flexible and can achieve various dynamic configurations with external databases such as Redis.