了解 OpenResty XRay 是如何做到帮助企业定位应用程序存在的问题以及优化其效率的。

了解更多 LIVE DEMO

本教程将逐步演示如何在 OpenResty 应用程序服务的不同 HTTP 请求中共享数据。我们会介绍两种方法:用于在单个工作进程内共享数据的 Lua 模块级变量,以及用于在所有工作进程间共享数据的 lua_shared_dict。本文是 OpenResty 教程系列的一部分——如果你刚接触 OpenResty,请先从 Hello World HTTP 示例开始。

搭建项目

cd ~/
mkdir data-share
cd data-share/
mkdir conf logs lua
tree

截图 1

我们首先准备测试应用程序的目录树。

方法一:Lua 模块级变量

在请求之间共享数据的最简单方法是使用一个名为 lua/my-module.lua 的自定义 Lua 模块。

我们进行以下编辑:

  1. 我们在这个模块的顶层增加一个全局计数器变量。
  2. 然后我们总是在一个名为 main 的模块函数中递增这个计数器。
  3. 并返回新的计数器值。
local _M = {}

local counter = 0

function _M.main()
    counter = counter + 1
    return counter
end

return _M

截图 6

配置 Nginx

现在让我们快速制作 nginx 配置文件,conf/nginx.conf

我们进行以下编辑:

  1. 在这里,我们指定在哪里寻找我们的 Lua 模块。
  2. 然后在本地 8080 端口上定义一个监听服务器。
  3. 而根位置下有 content_by_lua_block
  4. 加载我们的 Lua 模块并调用它的 main 函数。
  5. 并将返回的计数器值作为响应体输出。
worker_processes 1;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/lua/?.lua;;";

    server {
        listen 8080;

        location / {
            default_type text/plain;
            content_by_lua_block {
                local mod = require "my-module"
                local cnt = mod.main()
                ngx.say("counter = ", cnt)
            }
        }
    }
}

截图 13

我们再来检查一下目录树。

tree .

截图 15

看起来不错。

启动与测试

在没有 sudo 的情况下启动这个 OpenResty 应用程序。

openresty -p $PWD/

截图 17

在这里,我们只启用一个工作进程。

ps aux|grep nginx|grep -v tmp

截图 18

使用 curl 向服务器发送 HTTP 请求。

curl 'http://127.0.0.1:8080/'

截图 19

计数器的值是 1。

再来一次:

截图 21

酷,现在是 2。

它只会不断增长。

截图 23

工作原理:upvalue 与 LuaJIT 虚拟机

这个 counter Lua 变量是共享的,因为它是 Lua 模块函数的 upvalue(上值)。

cat lua/my-module.lua

截图 24

而 Lua 模块在全局 LuaJIT 虚拟机中被缓存和共享。

resty -Ilua -e 'require "my-module" print(package.loaded["my-module"].main)'

截图 25

看到了吗?我们也可以直接访问缓存的 Lua 模块。

package.loaded 全局表保存了所有加载的 Lua 模块。

resty -e 'for k, v in pairs(package.loaded) do print(k) end'

截图 27

在这里,大部分的模块都是那些标准的模块。

局限:数据无法跨工作进程共享

这些数据不能在不同的 nginx 工作进程之间共享。

截图 29

这里没有问题,因为只配置了 1 个工作进程。

但通常我们会通过启用多个工作进程来利用多个 CPU 核心。

worker_processes 4;

截图 31

那么每个工作进程都会有自己的计数器。

让我们测试配置,然后重新加载服务器。

openresty -p $PWD/ -t
kill -HUP `cat logs/nginx.pid`

截图 33

现在我们应该有 4 个工作进程。

ps aux|grep nginx|grep -v tmp

截图 34

如何选择合适的方案

这两种共享机制适用于不同的场景:

Lua 模块变量lua_shared_dict
作用域单个工作进程所有工作进程
值类型任意 Lua 类型字符串、数字、布尔值
典型用途配置缓存、单进程 LRU 缓存计数器、限流、共享标志位
HUP 重载后是否保留
重启后是否保留
原子操作不适用支持(incraddset

Lua 模块的方式主要用于 Lua 级别的数据缓存。与 OpenResty 一起提供的 resty.lrucache Lua 模块就是为这样的场景设计的。

restydoc resty.lrucache

截图 36

我们将在以后的教程中介绍这个模块。

方法二:使用 lua_shared_dict 跨工作进程共享数据

要在所有工作进程间共享一个计数器,需要在 http 块中用 lua_shared_dict 声明一块共享内存区域,并通过 ngx.shared.DICT 访问它(想深入了解这些共享内存区域在底层是如何占用内存的,请参阅 OpenResty 和 Nginx 共享内存区是如何消耗内存的):

http {
    lua_shared_dict my_counter 1m;

    server {
        listen 8080;

        location / {
            default_type text/plain;
            content_by_lua_block {
                local dict = ngx.shared.my_counter
                local newval, err = dict:incr("counter", 1)
                if not newval then
                    dict:add("counter", 0)
                    newval = dict:incr("counter", 1)
                end
                ngx.say("counter = ", newval)
            }
        }
    }
}

无论运行多少个工作进程,这个计数器都能正确地递增,并且它的值在 HUP 重载后依然保留。完整的 ngx.shared.DICT API——包括过期时间(ttl)、淘汰行为以及列表操作——我们将在专门的教程中介绍。

restydoc -s lua_shared_dict
restydoc -s ngx.shared.DICT

截图 38

今天我想介绍的就这么多。祝编程愉快! 如果你喜欢这个教程,请订阅这个博客网站和我们的 YouTube 频道B 站频道。谢谢!

本文是 OpenResty 教程系列的一部分,该系列是一份从安装到生产可用技巧的循序渐进的指南。接下来请继续阅读 在 OpenResty 中流式输出 HTTP 响应

关于作者

章亦春是开源 OpenResty® 项目创始人兼 OpenResty Inc. 公司 CEO 和创始人。

章亦春(Github ID: agentzh),生于中国江苏,现定居美国湾区。他是中国早期开源技术和文化的倡导者和领军人物,曾供职于多家国际知名的高科技企业,如 Cloudflare、雅虎、阿里巴巴, 是 “边缘计算“、”动态追踪 “和 “机器编程 “的先驱,拥有超过 22 年的编程及 16 年的开源经验。作为拥有超过 4000 万全球域名用户的开源项目的领导者。他基于其 OpenResty® 开源项目打造的高科技企业 OpenResty Inc. 位于美国硅谷中心。其主打的两个产品 OpenResty XRay(利用动态追踪技术的非侵入式的故障剖析和排除工具)和 OpenResty Edge(最适合微服务和分布式流量的全能型网关软件),广受全球众多上市及大型企业青睐。在 OpenResty 以外,章亦春为多个开源项目贡献了累计超过百万行代码,其中包括,Linux 内核、Nginx、LuaJITGDBSystemTapLLVM、Perl 等,并编写过 60 多个开源软件库。

关注我们

如果您喜欢本文,欢迎关注我们 OpenResty Inc. 公司的博客网站 。也欢迎扫码关注我们的微信公众号:

我们的微信公众号

翻译

我们提供了英文版原文和中译版(本文) 。我们也欢迎读者提供其他语言的翻译版本,只要是全文翻译不带省略,我们都将会考虑采用,非常感谢!