手把手教你使用火焰图查看诊断OpenResty(nginx)的Lua代码性能瓶颈

1,855 阅读4分钟

1、背景

OpenResty是网关、CDN和Waf等产品常用的底层解决方案,在国内外都有大量的使用者。在日常的使用过程中,难免会遇到性能问题。对于性能的问题,我们先要有工具可以观察和排查,才能深入到代码中解决问题,由于我们的业务逻辑大部分都是使用Lua代码实现的,仅仅靠系统级别的工具,比如perf,是观察不到Lua的代码热点在哪里。OpenResty作者春哥提供的了SystemTap脚本,可以用来定位Lua代码的热点。本文将从环境搭建,到代码编译,再到火焰图的输出,完整介绍如何查看Lua代码的火焰图。知名的开源网关ApiSix和Kong都是基于OpenResty之上二次开发,国内很多互联网企业的Waf也是基于OpenResty二次开发,可以说OpenResty的异步非阻塞模型的IO和网络,加上可以热重启Lua代码的机制,可以满足快速迭代、稳定和高性能的需求。

2、 环境搭建

需要提前安装好的工具:

  • vagrant (管理虚拟机的工具,HashiCorp出品)
  • virtualBox (虚拟机工具)

2.1 vagrant启动centos 7

Vagrantfile如下

Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"
  config.vm.box_url = "https://mirrors.ustc.edu.cn/centos-cloud/centos/7/vagrant/x86_64/images/CentOS-7.box"
  config.vm.define "openresty"
  config.vm.synced_folder "codes", "/codes"
  config.vm.provider "virtualbox" do |v|
    v.cpus = 4
    v.memory = 2048
  end
end

box_url指定了centos/7的下载地址,使用国内的源会快很多。另外synced_folder指定了Vagrantfile所在目录下的codes目录挂载到虚拟机的/codes目录中,这样方便本地开发电脑和虚拟机传输文件。

编辑完Vagrantfile后,执行vagrant up就可以拉起一个虚拟机,启动过程中,可能因为挂载本地文件到虚拟机会有告警信息。如果遇到以下问题,可以参考:

  1. 如果启动虚拟机报错:

    mount: unknown filesystem type 'vboxsf'

解决方案: vagrant plugin install vagrant-vbguest

  1. 本地文件不能同步到Centos 7虚拟机的问题解决

    • vagrant ssh 登录到虚拟机内部

    • sudo yum -y install epel-release

    • sudo yum -y update

    • sudo yum install make gcc kernel-headers kernel-devel perl dkms bzip2 -y

    • 随后执行退出虚拟机执行vagrant reload即可

    • 登录机器后,就可以看到挂载点的文件。1这个文件在本地可以编辑

      [vagrant@localhost codes]$ ls 1

当一切的问题解决后,就可以使用vagrant ssh登录到机器中,愉快的使用虚拟机了。

2.2 systemtap工具安装

  • systemtap安装:sudo yum install -y systemtap, 安装完后,还不能直接使用,因此systemtap依赖于内核的debuginfo,因此我们还需要安装内部的debuginfo。
  • 安装debuginfo,由于Centos默认是没有激活debug info的安装库,需要在我们安装的时候指定库:sudo yum --enablerepo=base-debuginfo install -y kernel-debuginfo-$(uname -r)

安装成功后,可以使用sudo stap -v -e 'probe vfs.read {printf("read performed\n"); exit()}'检查前面两个安装步骤是否没问题。

image-20221023233013893

  • 切换到本地开发机器上,使用git下载三个工具,用于生产Lua代码的火焰图

    • stapxx ,用于获取Lua的运行信息: git clone https://github.com/yxudong/stapxx.git, 不是用春哥的文档中的stapxx,是因为他开源的版本不支持gcc 64 bit mode
    • openresty官方提供的systemtap脚本:git clone https://github.com/openresty/openresty-systemtap-toolkit.git
    • 火焰图生成工具: git clone https://github.com/brendangregg/FlameGraph.git

2.3 openresty的编译和运行

虚拟机和systemtap的工具都安装好了,是时候开始编译openresty和运行demo了。我们下载一个2022年5月18日发布的最新版本的openresty,看一下效果:

  • 下载最新源码: wget openresty.org/download/op…

  • 解压:tar -xf openresty-1.21.4.1.tar.gz

  • 安装编译依赖工具 :yum install gcc pcre pcre-devel zlib zlib-devel openssl openssl-devel -y

  • 切换到源码目录,执行编译三部曲

    • ./configure
    • make -j4 ,cpu的个数是4个
    • 安装到机器中sudo make install
  • 检查安装是否成功:sudo /usr/local/openresty/bin/openresty -v,输出

    nginx version: openresty/1.21.4.1

  • 在/etc/bashrc 中加入openresty的路径到PATH中

    PATH=/usr/local/openresty/nginx/sbin:$PATH export PATH

2.3.1 demo

在demo的目录中,创建配置文件和日志的目录:mkdir -p logs conf。接着配置文件conf/nginx.conf 编辑内容如下

worker_processes  auto;
error_log logs/error.log;
events {
    worker_connections 1024;
}
http {
    server {
        listen 8080;
        location / {
            default_type text/html;
            content_by_lua_block {
                ngx.say("<p>hello, world</p>")
            }
        }
    }
}

启动nginx的时候,需要切换到root权限(也不一定需要root,关键是因为pid和端口的问题,需要root就方便很多,demo就不再展开权限相关的问题),统一切换到root用户: sudo su后执行。

启动openresty

nginx -p pwd/ -c conf/nginx.con

启动后,没有任何输出,这个是正常的。Linux的哲学就是 :没报错信息,就是运行正常

接着,我们访问一下本地的8080端口,返回一切正常

[root@localhost vagrant]# curl http://127.0.0.1:8080

hello, world

2.3.2 火焰图生成

conf/nginx.conf文件修改一下,重新reload一下nginx

worker_processes  1;
error_log logs/error.log;
events {
    worker_connections 1024;
}
http {
    server {
        listen 8080;
        location / {
            default_type text/html;
            content_by_lua_block {
                local sha1 = require "resty.sha1"
    local to_hex = require "resty.string".to_hex
                for i = 10,1,-1
    do
         local digest = sha1:new()
       digest:update("some")
       to_hex(digest:final())
    end
                ngx.say("<p>hello, world</p>")
            }
        }
    }
}

使用ab进行压测:ab -c 10 -n 100000000 http://127.0.0.1:8080/,压测启动后,再进行采样,采样这里需要注意,CPU有压力才能采样到数据,所以才需要进行压测。步骤:

  1. 切换stapxx目录: cd /codes/stapxx
  2. 当前路径在到PATH中:export PATH=$PWD:$PATH
  1. 采样:./samples/lj-lua-stacks.sxx --arg time=5 --skip-badvars -x 28183 > tmp.bt,其中-x指定的是nginx worker的PID。

  2. 使用openresty-system-toolkit工具,调整一下tmp.bt的采样结果:

    • cd /codes/openresty-systemtap-toolkit/
    • ./fix-lua-bt ../stapxx/tmp.bt > flame.bt
    • mv flame.bt /codes/FlameGraph/
  3. 切换到火焰图生成工具目录,开始生成火焰图

    • cd /codes/FlameGraph
    • ./stackcollapse-stap.pl flame.bt |./flamegraph.pl > flame.svg
  4. 本地电脑浏览器查看火焰图

    image-20221024004222003

可以看出,to_hex的函数是主要的性能热点。

总结:

本文仅提供了一个Demo,用来指导如何从环境安装到编译,再到例子Demo,一步步的生成火焰图。在真实的业务场景中,会更加复杂,但是万变不离其宗,这个Demo应该足以入门如何查看OpenResty的Lua代码火焰图。

4. 参考

  1. openresty.org/en/getting-…