OpenResty from entry to proficiency 20-beyond the Web server: privileged process and scheduled tasks

20 | Beyond the Web Server: Privileged Processes and Scheduled Tasks

Hello, I am Wen Ming.

Earlier we introduced OpenResty API, shared dictionary cache and cosocket. The functions they implement are all within the scope of Nginx and Web servers, which can be regarded as providing an implementation with lower development costs and easier maintenance, and provides a programmable Web server.

However, OpenResty doesn't stop there. Let's pick a few today and introduce the functions beyond the Web server in OpenResty. They are scheduled tasks, privileged processes and non-blocking ngx.pipe respectively.

timed task

In OpenResty, we sometimes need to periodically perform certain tasks in the background, such as synchronizing data, cleaning logs, and so on. If you were asked to design, what would you do? The easiest way to think of is to provide an API interface to the outside world, and complete these tasks in the interface; then use the system's crontab to call curl regularly to access this interface, and then realize this requirement in a curve.

However, in this way, not only will there be a sense of fragmentation, but it will also bring higher complexity to operation and maintenance. Therefore, OpenResty provides ngx.timerto solve this kind of needs. You can ngx.timerthink of it as a client request simulated by OpenResty to trigger the corresponding callback function.

In fact, OpenResty's scheduled tasks can be divided into the following two types:

  • ngx.timer.at, used to execute a one-time timing task;
  • ngx.time.every, used to execute fixed-period timing tasks.

Remember the thinking questions I left at the end of the last class? The question is how to break through the limitation that cosocket cannot be used init_worker_by_luain , and this answer is actually ngx.timer.

The following code starts a scheduled task with a delay of 0. It starts the callback function handler, and in this function, uses cosocket to visit a website:

init_worker_by_lua_block {
        local function handler()
            local sock = ngx.socket.tcp()
            local ok, err = sock:connect(“www.baidu.com", 80)
        end

        local ok, err = ngx.timer.at(0, handler)
    }

This way, we bypass the limitation that cosockets cannot be used at this stage.

Going back to the user needs we mentioned at the beginning of this part, ngx.timer.atit does not solve the need to run periodically. In the above code example, it is a one-time task.

So, how to achieve periodic operation? On the surface, based on ngx.timer.atthis API, you have two options:

  • You can use a while true infinite loop in the callback function, sleep for a while after executing the task, and implement the periodic task by yourself;
  • You can also create another new timer at the end of the callback function.

However, before making a choice, we need to clarify one thing: the essence of a timer is a request, although this request is not initiated by the terminal; as for the request, it will exit after completing its own task, and it cannot always Permanent, otherwise it is easy to cause leakage of various resources.

Therefore, the first scheme of using while true to implement periodic tasks by itself is not reliable. Although the second solution is feasible, recursively creating timers is not easy for people to understand.

So, is there a better solution? In fact, the newly added ngx.time.everyAPI behind OpenResty is designed to solve this problem, and it is a solution closer to crontab.

But the fly in the ointment is that after starting a timer, you have no chance to cancel the scheduled task, after all, ngx.timer.cancelit is still a todo function.

At this time, you will face a problem: scheduled tasks run in the background and cannot be canceled; if there are a large number of scheduled tasks, it is easy to exhaust system resources.

Therefore, OpenResty provides lua_max_pending_timersand lua_max_running_timersthese two instructions to limit it. The former represents the maximum value of scheduled tasks waiting to be executed, and the latter represents the maximum value of currently running scheduled tasks.

You can also use the Lua API to get the value of the scheduled task currently waiting to be executed and being executed. The following are two examples:

content_by_lua_block {
            ngx.timer.at(3, function() end)
            ngx.say(ngx.timer.pending_count())
        }

This code will print 1, indicating that there is 1 scheduled task waiting to be executed.

content_by_lua_block {
            ngx.timer.at(0.1, function() ngx.sleep(0.3) end)
            ngx.sleep(0.2)
            ngx.say(ngx.timer.running_count())
        }

This code will print 1, indicating that there is 1 scheduled task running.

privileged process

Then look at the privileged process. We all know that Nginx is mainly divided into master process and worker process, among which, the worker process actually handles user requests. We lua-resty-corecan process.typeget the type of process through the API provided in . For example, you can restyrun the following function with :

$ resty -e 'local process = require "ngx.process"
ngx.say("process type:", process.type())'

You'll see that it doesn't return a result worker, but instead single. This means that restythe started Nginx has only worker processes and no master process. In fact, the same is true. In the implementation restyof , you can see that the following line of configuration shuts down the master process:

master_process off;

OpenResty has expanded on the basis of Nginx and added a privileged process: privileged agent. Privileged processes are special:

  • It does not listen to any port, which means it will not provide any external services;
  • It has the same permissions as the master process, generally speaking, it is rootthe user 's permissions, which allows it to do many tasks that are impossible for the worker process;
  • Privileged processes can only be started in init_by_luathe context ;
  • In addition, the privileged process only makes sense if it runs in init_worker_by_luathe context , because there is no request to trigger, and it will not go to the context of content, accessetc.

Next, let's look at an example of starting a privileged process:

init_by_lua_block {
    local process = require "ngx.process"

    local ok, err = process.enable_privileged_agent()
    if not ok then
        ngx.log(ngx.ERR, "enables privileged agent failed error:", err)
    end
}

After starting the privileged process through this code, and then start the OpenResty service, we can see that there are more privileged processes in the Nginx process:

nginx: master process
nginx: worker process
nginx: privileged agent process

However, if the privilege is only run once in init_worker_by_luathe stage , which is obviously not a good idea, how should we trigger the privileged process?

That's right, the answer lies in the knowledge just mentioned. Since it does not listen to ports, that is, it cannot be triggered by terminal requests, it can only be ngx.timertriggered periodically by using what we just introduced:

init_worker_by_lua_block {
    local process = require "ngx.process"

    local function reload(premature)
        local f, err = io.open(ngx.config.prefix() .. "/logs/nginx.pid", "r")
        if not f then
            return
        end
        local pid = f:read()
        f:close()
        os.execute("kill -HUP " .. pid)
    end

    if process.type() == "privileged agent" then
         local ok, err = ngx.timer.every(5, reload)
        if not ok then
            ngx.log(ngx.ERR, err)
        end
    end
}

The above code implements the function of sending the HUP semaphore to the master process every 5 seconds. Naturally, you can also implement more interesting functions on this basis, such as polling the database to see if there are tasks of privileged processes and execute them. Because the privileged process has root authority, this is obviously a bit of a "backdoor" program.

non-blocking ngx.pipe

Finally we look at the non-blocking ngx.pipe. In the code example just mentioned, we use Lua's standard library to execute the external command line and send the signal to the master process:

os.execute("kill -HUP " .. pid)

This operation is naturally blocked. So, in OpenResty, is there a non-blocking way to call external programs? After all, know that if you're using OpenResty as a complete development platform rather than a web server, this is exactly what you need.

To this end, lua-resty-shellthe library came into being, and using it to call the command line is non-blocking:

$ resty -e 'local shell = require "resty.shell"
local ok, stdout, stderr, reason, status =
    shell.run([[echo "hello, world"]])
    ngx.say(stdout)

This code can be regarded as another way of writing hello world, it calls the system echocommand to complete the output. Similarly, you can use resty.shellinstead of os.executecalling .

We know that lua-resty-shellthe underlying implementation of , relies lua-resty-coreon the [ ngx.pipe ] API in , so lua-resty-shellthis hello wroldexample of using to print out ngx.pipecan be written as follows:

$ resty -e 'local ngx_pipe = require "ngx.pipe"
local proc = ngx_pipe.spawn({"echo", "hello world"})
local data, err = proc:stdout_read_line()
ngx.say(data)'

This is actually lua-resty-shellthe underlying implementation code. You can ngx.pipecheck the documentation and test cases for more usage methods, so I won't go into details here.

write at the end

At this point, I have finished the main content of today. From the above functions, we can see that OpenResty is also trying to move closer to the general platform on the premise of making a better Nginx. We hope that developers can try to unify the technology stack and use OpenResty to solve it. development needs. This is very friendly to operation and maintenance, because only one OpenResty is deployed, and the maintenance cost is lower.

Finally, I leave you with a thought question. Since there may be multiple Nginx workers, the timer will run once in each worker, which is unacceptable in most scenarios. How should we ensure that the timer runs only once?

Welcome to leave a message to talk about your solution, and you are welcome to share this article with your colleagues and friends. We can communicate and make progress together.

Guess you like

Origin blog.csdn.net/fegus/article/details/130740378