• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Nginx+lua+openresty精简系列

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

你可以在你的 CentOS 系统中添加 openresty 仓库,这样就可以便于未来安装或更新我们的软件包(通过 yum update 命令)。运行下面的命令就可以添加我们的仓库:

$ sudo yum install yum-utils
$ sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo

然后就可以像下面这样安装软件包,比如 openresty

$ sudo yum install openresty

如果你想安装命令行工具 resty,那么可以像下面这样安装 openresty-resty 包:

$ sudo yum install openresty-resty

命令行工具 opmopenresty-opm 包里,而 restydoc 工具在 openresty-doc 包里头。

列出所有 openresty 仓库里头的软件包:

$ sudo yum --disablerepo="*" --enablerepo="openresty" list available

备注:在使用一般用户切换sudo后提示:sandu is not in the sudoers file. This incident will be reported.,可以按照如下操作:

1.切换到超级用户:$ su - 
2.打开/etc/sudoers文件:$vim /etc/sudoers
3.修改文件内容:
找到“root    ALL=(ALL)       ALL”一行,在下面插入新的一行,内容是“sandu   ALL=(ALL)    ALL”,
然后在vim键入命令“:wq!”保存并退出。
注:这个文件是只读的,不加“!”保存会失败。
4.退出超级用户:$ exit,然后再切换就可以了。
默认5分钟后刚才输入的sodo密码过期,下次sudo需要重新输入密码,如果觉得在sudo的时候输入密码麻烦,把刚才的输入换成如下内容即可: sandu ALL=(ALL) NOPASSWD: ALL

openresty默认安装路径:/usr/local/openresty/

或者采用源码安装方式,比较有针对性的编译各种模块

运行相关命令:

$ sudo systemctl start openresty.service
$ sudo systemctl stop openresty.service
$ sudo systemctl restart openresty.service
$ sudo systemctl reload openresty.service

防火墙放行80端口

2. 配置虚拟主机

#user  nobody;
worker_processes  1;

error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }
        #location / {
        #    default_type text/html;
        #    content_by_lua_block {
        #        ngx.say("<p>hello, world</p>")
        #    }
        #}

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

把上述默认的配置文件改造如下:

#user  nobody;
worker_processes  1;

error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    #default_type  application/octet-stream;
    default_type  text/html;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    gzip  on;
	
	 # server块注释
    #server {
        #listen       80;
        #server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        #location / {
        #    root   html;
        #    index  index.html index.htm;
        #}
        #location / {
        #    default_type text/html;
        #    content_by_lua_block {
        #        ngx.say("<p>hello, world</p>")
        #    }
        #}

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        #error_page   500 502 503 504  /50x.html;
        #location = /50x.html {
        #    root   html;
        #}

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    #}


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}
	 
	 # 新增这一行,在nginx.conf配置文件同目录下新建目录vhosts
    include vhosts/*.conf;

}

1.基于域名绑定虚拟主机

# vhosts/test.conf文件

server {
    listen       80;
    server_name  server1.test.com;

    #charset koi8-r;

    location / {
#       root   html;
#       index  index.html index.htm;
        echo 'server1.test.com';
    }
}

server {
    listen       80;
    server_name  server2.test.com;

    #charset koi8-r;

    location / {
        echo 'server2.test.com';
    }
}

server {
    listen       80;
    server_name  server3.test.com;

    #charset koi8-r;

    location / {
        echo 'server3.test.com';
    }
}

本机/etc/hosts文件增加如下内容:

127.0.0.1   server1.test.com
127.0.0.1   server2.test.com
127.0.0.1   server3.test.com

使用curl命令分别对server1.test.com,server2.test.com,server3.test.com进行访问,出现相应的访问结果。

2.基于端口绑定虚拟主机

# 基于端口绑定的虚拟主机,注意:没有server_name,默认是localhost
server {
    listen       81;

    location / {
        echo 'server1.test.com';
    }
}

server {
    listen       82;

    location / {
        echo 'server2.test.com';
    }
}

server {
    listen       83;

    location / {
        echo 'server3.test.com';
    }
}
[sandu@bogon vhosts]$ curl http://localhost:81
server1.test.com
[sandu@bogon vhosts]$ curl http://localhost:82
server2.test.com
[sandu@bogon vhosts]$ curl http://localhost:83
server3.test.com

3.基于IP绑定虚拟主机

server {
    listen       192.168.0.2:80;

    location / {
        echo 'server1.test.com';
    }
}

server {
    listen       192.168.0.3:80

    location / {
        echo 'server2.test.com';
    }
}

server {
    listen       192.168.0.3:80

    location / {
        echo 'server3.test.com';
    }
}

4.总结

基于域名的配置一般都是针对外网,也就是面对用户的,而后两者配置一般都是基于内网,企业内部的各个系统的跳转多用端口号区别,因为也没有用户去使用ip地址和端口号去访问一个网站,都是通过域名访问的。

3. 配置反向代理

反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。

nginx的负载均衡配置就依赖于反向代理。

反向代理的作用:

  • 网关服务器
  • 负载均衡
  • 请求转发、请求统计
  • 黑白名单拦截
http{
......

    upstream to_local{
        server localhost:81;
        server localhost:82;
    }

    server {
        listen       80;

        location / {
            proxy_pass http://to_local;
        }
    }

    server {
        listen       81;

        location / {
            echo '81,remote_addr=$remote_addr';
        }
    }

    server {
        listen       82;

        location / {
            echo '82,remote_addr=$remote_addr';
        }
    }

}

代码块说明:

  • upstream块,紧跟着后面有一个to_local别名,因为可以配置多个upstream块,所以为了区别需要别名,且在server块使用的时候需要指定该别名。块内是两个server,也就是选择哪台内部服务器展示网站内容的域名,也可以写成 ip:port 形式。也可以只写一个server,相当于请求转发;配置多个就是负载均衡。
  • location内的proxy_pass配置。使用 http:// + upstream别名的形式,也就是当访问某一个域名的时候,location匹配到根路径,然后使用反向代理功能,将该请求‘跳转’到upstream块中,由该块决定请求策略。upstream块发现有两个server可以使用,于是使用轮训策略(默认情况下),依次访问localhost:81和localhost:82

访问效果:

[sandu@bogon conf]$ curl http://localhost
82,remote_addr=127.0.0.1
[sandu@bogon conf]$ curl http://localhost
81,remote_addr=127.0.0.1
[sandu@bogon conf]$ curl http://localhost
82,remote_addr=127.0.0.1
[sandu@bogon conf]$ curl http://localhost
81,remote_addr=127.0.0.1

使用代理的情况,得到的结果却是127.0.0.1,看上去像是Linux系统本机IP,实际上是我运行程序的机器的IP地址

$remote_addr只存储上次请求的ip地址,而不是最原始的客户机ip地址。

解决方案:

使用proxy模块的proxy_set_header配置,将原始客户机ip地址放到请求头里。

server {
    listen       80;

    location / {
        proxy_pass http://to_local;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

一次代理,设置请求头之后可以根据X-Real-IP获取真实客户机ip地址;二次代理则不是真实ip,X-Forwarded-For头里不论几次代理都可以获取到真实ip。

nginx的命令执行并不是按代码的实际书写顺序,而是根据模块来划分。模块的代码执行有先后顺序,若A模块先于B模块执行,那么即便在同一个location中,所属B模块的代码在所属A模块代码之前,依然不会先执行。这就是配置指令执行顺序的问题。同一个模块不同的指令也有不同的执行顺序,有的是在阶段末尾有的则是在阶段开始。

proxy模块还有其他更多的指令配置,一般常见就是设置头和设置反向代理一起使用。

4. 配置负载均衡

负载均衡是为了将海量的请求分发给一台台服务器,让每台服务器处理的请求达到均衡。它是由多台服务以等价地位的方式组成一个服务器集合,也就是集群。每台服务器可以单独对外服务,具备完整的对外服务功能。负载均衡是通过某种技术,让请求均匀的分配给集群中的各个机器,而收到请求的机器可以独立回应客户请求。

负载均衡作用:

  • 缓解单台服务器压力,有效分流降压
  • 高可用,若干台服务器中宕机数个不影响整体使用
  • 提高服务的响应速度
  • 支持在线扩展升级,可临时增加机器以便应对短暂洪流

负载均衡解决方案:

  • 用户手动选择不同的下载线路
  • DNS轮询
  • F5等物理设备
  • nginx等软件:通过OSI的第七层即应用层来实现负载均衡

nginx目前支持6种调度算法:

  • 轮询策略:默认依次轮询

  • ip_hash策略:根据客户机的ip地址进行hash后再取模,nginx可以保证同一个客户机的请求始终分配到同一台服务器上。若被分配到的机器宕机,则会重新分配到其他机器上。

upstream to_local{
    ip_hash;
    server localhost:81;
    server localhost:82;
}
  • 最少连接数策略:使请求分配到最少连接数的机器上
upstream to_local{
    least_conn;
    server localhost:81;
    server localhost:82;
}

upstream 块中 server配置后所支持的参数:

  • weight:权重,常用于机器性能不均衡时,权重越大被访问的几率越大
upstream to_local{
    server localhost:81 weight=1;
    server localhost:82 weight=2;
}
  • down:设置当前server不参与负载均衡,即访问不到
upstream to_local{
    server localhost:81 weight=1;
    server localhost:82 weight=2 down;
}
  • backup:用于其他server全部无法访问或者忙时备用server
upstream to_local{
    server localhost:81;
    server localhost:82 backup;
}
  • max_fails:server允许请求失败的次数,
  • fail_timeout:请求失败次数达到max_fails时,指定server暂停多久,以便检测故障原因
upstream to_local{
    server localhost:81 max_fails=1 fail_timeout=30s;
    server localhost:82 max_fails=1 fail_timeout=30s;
}

反向代理是可以直接书写代理地址的

proxy_pass http://localhost:80

5. nginx缓存

缓存原则:

  • 越近越好
  • 能用自家缓存就不要用别人家的缓存

web缓存:web缓存位于内容源web服务器和客户端之间,当访问一个url时,web服务器会去后端服务器取回要输出的内容,然后当下一个请求到来时,如果访问的是同一个url,web服务器则会直接输出内容给客户端,而不是向后端服务器再次发送请求。

常用的缓存软件比如redis、memcache,是不是web缓存呢?答案是不是的,因为这两者是需要通过应用程序去操作,需要通过建立连接,通过自己的程序也就是代码去控制存储拉取,要在服务器内部进行处理。而web缓存对于同一个url来说,第一次请求会访问服务器,但是第二次请求不到服务器就直接返回了。

nginx的缓存指令在proxy模块中可以查到:

  • proxy_cache_path:nginx缓存是将缓存的内容存储到磁盘和内存两种途径,此指令声明了缓存在硬盘中的存储路径,最大存储量,失效时间等。
  • proxy_cache:nginx缓存是共享的,也就是proxy_cache_path声明的一个nginx缓存可以被其他localtion所共用,所以就有个zone的概念,proxy_cache_path声明zone即缓存的别名,proxy_cache指定该别名表示使用这块缓存。
  • proxy_cache_valid:控制不同响应状态码的HTTP响应的缓存失效时间。比如响应码是200的缓存10s ,404状态则缓存1s。

nginx配置:

http {
    
	......
	
    proxy_cache_path /tmp/cache levels=1:2 keys_zone=myCache:10m max_size=1g inactive=30s use_temp_path=off;
	
    server {
        listen       80;
        add_header X-Cache $upstream_cache_status;
        location / {
            proxy_pass http://127.0.0.1:8088;
            proxy_cache_valid 10s; # 缓存在内存中的存储时间,还可以制定响应码的缓存时间
            proxy_cache myCache; # 设置该location的所有请求都将缓存,并指定缓存zone
        }
    }
}

proxy_cache_path 指令分析:

  • cache/test:缓存数据存储在根目录下的cache/test目录中
  • levels:表示缓存文件在目录中的存储方式,不用关心,最好是1:2就行。
  • keys_zone=myCache:10m:定义一块共享的内存区域,名称叫myCache,大小为10m,用来存储key值和缓存数据的信息,10m大约可以存8000个key。这个10m和缓存在硬盘中的容量不是一个概念。
  • max_size:缓存在硬盘中存储的最大容量,超容则删除最不常用的缓存腾出空间,删除规则也可以手动配置。
  • inactive:表示已缓存数据在硬盘中的最大存储时间,超时就删除,默认10分钟。
  • use_temp_path:是否使用临时存储路径,意思是先有临时存储路径,然后再把临时的存储文件转到真正的存储路径也就是proxy_cache_path中的路径,如果开启则需要配置临时缓存路径,一般不开启。

add_header指令分析:

  • 是headers模块中的指令,意思很明确,就是在HTTP响应的时候添加响应头,key值为 X-Cache,value为$upstream_cache_status$upstream_cache_status是upstream模块中的一个内置变量,它表示HTTP响应的缓存状态。

  • 表示响应是否命中缓存,hit表示命中,miss表示未命中。设置此请求头的原因是缓存是否使用,用肉眼辨别不出来,我们在响应头中加一个是否缓存的标记,通过查看响应头就能看出是否使用了缓存。

实际配置:

http{
	
	......
	
    upstream to_local{
        server localhost:81 weight=1;
        server localhost:82 weight=2 down;
    }

    proxy_cache_path /tmp/cache levels=1:2 keys_zone=myCache:10m max_size=1g inactive=30s use_temp_path=off; # 需要提前建好/tmp/cache目录

    server {
        listen       80;

        add_header X-Cache $upstream_cache_status;

        location / {
            proxy_pass http://to_local;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_cache_valid 10s;
            proxy_cache myCache;
        }
    }

    server {
        listen       81;

        location / {
            root   html;
            index  index.html index.htm; # index.html文件中包含图片
        }
    }
}

效果展示:

  1. 直接访问http://ip:81,没有X-Cache响应头
  2. 访问http://ip,第一次访问时有 X-Cache响应头且状态是miss状态。同时发现/tmp/cache文件夹下多了很多文件夹,二级文件夹,正是levels所配置的1:2,最下级是一个文件,即缓存数据,30s过后消失,文件夹依然存在,说明inactive生效。为了避免浏览器缓存,可以使用ctrl+F5刷新页面,发现缓存文件又出现了。第二次访问,再次查看状态,发现X-Cache的状态已经变为hit ,表示缓存命中。

6. Lua入门

openresty的开发主要是基于lua,Lua 在葡萄牙语里代表美丽的月亮。

Lua 从一开始就是作为一门方便嵌入(其它应用程序)并可扩展的轻量级脚本语言来设计的,因此她一直遵从着简单、小巧、可移植、快速的原则,官方实现完全采用 ANSI C 编写,能以 C程序库的形式嵌入到宿主程序中。

Lua特点:

  • 变量无类型,任何值都可以一个关键字表示
  • 仅提供一种数据结构,table,类似于Map,使用key,value的形式存储数据。
  • 具有远程连接的功能,可以连接数据库,可以连接redis
  • 语法简单

因为已经安装有openrestyle ,所以自带的有lua开发环境,在命令中执行:lua即可。

[sandu@bogon ~]$ lua
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> 

6.1 Lua变量、值类型

lua虽然变量是没有类型的,但是值是有类型的

[sandu@bogon tmp]$ cat hello.lua 
local a = 1
local b = true
local c = "字符串"
local d 

print(type(a))
print(type(b))
print(type(c))
print(type(d))
print(type(print))
[sandu@bogon tmp]$ lua hello.lua 
number
boolean
string
nil
function

首先定义了四个变量,都是用local变量接收,然后输出这些变量的值得类型,type类似于JavaScript中的typeof,能够返回变量或值的类型。

前三个都很好理解了,数值类型,布尔类型,字符串类型,开发者已经不能再熟悉了。nil表示无效值,假如一个变量没有被赋值,则默认为nil,function则表示该值是一个function类型,方法也是这个类型。

lua的变量确实是无类型的,可以任意接收不同的类型的值

  • number数字类型,一般情况下,lua中的数字类型都是双精度的,而luajit会根据上下文将整数存储为整型,用双精度浮点数来存储浮点数。
  • string字符串,单引号或者双引号都可以。但是如果想在字符串里加引号,或者使用其他转义字符,那么就得用 [ ] 大括号了。
  • table数据类型,类似于json数据格式,很实用,会有table表的遍历
[sandu@bogon tmp]$ cat 1.lua 
local corp = {
    web = "www.google.com", 
    telephone = "12345678", 
    staff = {"Jack", "Scott", "Gary"}, 
    100876, 
    100191, 
    [10] = 360, 
    ["city"] = "Beijing" 
}

print(corp.web) 
print(corp["telephone"]) 
print(corp[2]) 
print(corp["city"])
print(corp.staff[1]) 
print(corp[10])
[sandu@bogon tmp]$ lua 1.lua 
www.google.com
12345678
100191
Beijing
Jack
360
[sandu@bogon tmp]$ 
local corp = {
    web = "www.google.com", --索引为字符串,key = "web", -- value = "www.google.com"
    telephone = "12345678", --索引为字符串
    staff = {"Jack", "Scott", "Gary"}, --索引为字符串,值也是一个表
    100876, --相当于 [1] = 100876,此时索引为数字, -- key = 1, value = 100876
    100191, --相当于 [2] = 100191,此时索引为数字
    [10] = 360, --直接把数字索引给出
    ["city"] = "Beijing" --索引为字符串
}
print(corp.web) -->output:www.google.com
print(corp["telephone"]) -->output:12345678
print(corp[2]) -->output:100191
print(corp["city"]) -->output:"Beijing"
print(corp.staff[1]) -->output:Jack
print(corp[10]) -->output:360
  • lua的function是可以赋值给变量的,所以它也是一种数据类型。
# 语法1
function  name()  # 匿名
    代码块
 end

# 语法2
name = function()  # 有名
    代码块
 end
[sandu@bogon tmp]$ cat 2.lua 
function hello()
    return 'hello lua'
end

local a = hello

print(a()) 
[sandu@bogon tmp]$ lua 2.lua 
hello lua
[sandu@bogon tmp]$ 

定义了一个hello方法,然后将hello赋值给a变量,并输出a变量,发现输出了hello lua。

那么为什么要在输出a的时候加个括号呢?因为a是一个变量,且指向的是一个方法即hello,直接输出的话输出的是该方法的地址:function: 0000000000dfd370,加个括号意思就是调用该方法,方法中的代码就会执行并返回一个字符串。

function分为匿名函数和有名函数,刚刚声明的就是一个匿名函数,实际上可以转变为一个有名函数。所以可以直接使用hello这个变量

hello = function ()
    return "hello lua"
end

6.2 Lua语法

  1. 注释使用--两个中横线
--
-- Created by IntelliJ IDEA.
-- Date: 2019/1/25
-- Time: 14:52
-- To change this template use File | Settings | File Templates.
--
  1. 表达式,加减乘除,是非与或,小于大于等于,但是对于lua来说,and和or有点特殊
a and b  如果 a 为 nil,则返回 a,否则返回 b;
a or b  如果 a 为 nil,则返回 b,否则返回 a。

在if判断中,false和Nil都视为假,其他如字符串或者数字都视为真。

  1. 字符串连接使用..两个点
print("Hello " .. "World") -->打印 Hello World
print(0 .. 1) -->打印 01
  1. if语句
a = 90
if a == 100 then
    print("a=100")
elseif a >= 60 then  -- elseif 是连在一起的
    print("a>=60")
    --此处可以添加多个elseif
else
    print("a<60")
end
  1. for循环
local arr = {"a", "b", "c", "d"}
for index, value in ipairs(arr) do
    print("index:", index, " value:", value)
end

# 输出
index:    1    value: a
index:    2    value: b
index:    3    value: c
index:    4    value: d
  1. while循环
-- 计算1+2+3+4+5的值
x = 1
sum = 0
while x <= 5 do
    sum = sum + x
    x = x + 1
end

print(sum) -->output 15

6.3 Lua库

  1. string库
print(string.upper("Hello Lua")) -->output HELLO LUA
print(string.lower("Hello Lua")) -->output hello lua
print(string.len("hello lua")) -->output 9
  1. table库
  2. 日期库

6.4 模块

通过require函数,跨文件使用其他lua代码,模块就是一个lua代码块

定义一个hello.lua文件

--一个模块通常视为一个table表
--其他代码调用该模块就相当于调用table表
--于是返回table,就相当于把代码暴露出去了

local hello={}

local setName = function()
    return "myname"
end

hello.getName = function() --给hello添加一个key值:getName,value就是function
    print(setName())
end

return hello

再定义一个user.lua文件

local helloTable = require("hello")

helloTable.getName()
[sandu@bogon tmp]$ cat hello.lua 
--一个模块通常视为一个table表
----其他代码调用该模块就相当于调用table表
----于是返回table,就相当于把代码暴露出去了


local hello={}

local setName = function()
    return "myname"
end

hello.getName = function() --给hello添加一个key值:getName,value就是function
    print(setName())
end

return hello
[sandu@bogon tmp]$ cat user.lua 
local helloTable = require("hello")

helloTable.getName()
[sandu@bogon tmp]$ lua user.lua  
myname
[sandu@bogon tmp]$

7. openresty企业级应用1

openresty是基于nginx和lua,所以nginx能干的它基本都能干,通常openresty/nginx都会作为请求入口,即所谓的网关,那么是不是就可以在入口处做很多动作,比如ip拦截、黑白名单校验、限流、url拦截等。

  • 网关

数据校验前置,数据过滤前置,缓存前置。请求聚合,AB测试,监控等

  • web应用

可以完全借助openresty写出一个web应用,不依靠其他语言,lua可连接数据库,可连接缓存,再对数据进行处理之后返回。所以具备业务逻辑处理的能力

  • 防火墙

即各种拦截,url、ip、黑白名单。

  • 缓存

由于lua有共享缓存,且nginx有缓存功能,所以openresty也能做缓存服务器。

7.1 常用API介绍

API地址

上面所说的是写在lua脚本中的API,即在脚本中使用的代码,那么肯定有个要写脚本的地方对吧,要从nginx走到lua脚本肯定需要一个桥梁,也就是如何运行lua脚本咯。这个桥梁就是nginx指令了,和proxy_pass、echo、listen指令一样,openresty也有一系列的指令,不仅仅包含进入lua代码片段的指令,还有一些控制指令等。

在directive指令集中,content_by_lua,content_by_lua_block和conten_by_lua_file就是写lua脚本的地方。

7.2 API实战演练

  1. helloworld案例
server{
	listen 80;
	location / {
		content_by_lua_block{
			ngx.say("hello wolrd!")
		}   
	}
}

唯一变化的就是多了一条指令:content_by_lua_block。该指令的意思,就是lua代码块内容,ngx.say就是输出打印,同ngx.print

启动openresty 访问localhost,得到“helloworld”结果。

  1. 获取请求参数
server{
      listen 81;
      charset "utf-8";
      location / {
          content_by_lua_block{
              local requstMethod = ngx.req.get_method()
              ngx.say('请求方法类型:'..requstMethod)
              local requsetData
              if requstMethod == nil then
                  ngx.say("获取请求方法失败!")
              elseif requstMethod == 'POST' then
                  ngx.req.read_body()
                  requsetData = ngx.req.get_post_args()
              elseif requstMethod == 'GET' then
                  requsetData = ngx.req.get_uri_args()
              end

              for k, v in pairs(requsetData) do
                    ngx.say("key = "..k..' value = '..v)
              end
          }
      }
 }
  • 由于要输出中文,避免中文乱码,加了一个charset指令
  • ngx.req.get_method()获取请求类型
  • 两个点 是字符串连接 等于+
  • nil相当于null
  • 获取post参数前需要先读一下请求体,否则读取失败的哦
  • uri_args就是get请求URI上的参数 post_args很简单明了了
  • 最后使用for循环 输出参数值 很简单

使用postman进行验证

  1. get请求

GET ip?a=1&b=2

  1. post请求

POST ip (在Body里添加数据)

  1. 获取请求体
locaiton / {
	content_by_lua_block{
	   local headers = ngx.req.get_headers()
	   for k, v in pairs(headers) do
			ngx.say("headerK = "..k..' value = '..v)
	   end
	}
}
  1. 日志打印
locaiton / {
	content_by_lua_block{
		local headers = ngx.req.get_headers()
		for k, v in pairs(headers) do
			  ngx.log(ngx.INFO,"headerK = "..k..' value = '..v)
		end
	}
}

日志记录保存在openresty安装目录下的nginx/logs目录下,跟nginx日志保存一样

ngx.log为日志API,日志级别设置在第一个参数中表明。

可设置的值有:

ngx.STDERR,ngx.EMERG,ngx.ALERT,ngx.CRIT,ngx.ERR,ngx.WARN,ngx.NOTICE,ngx.INFO,ngx.DEBUG

需要注意的是日志级别不能低于err_log中设置的日志级别,否则无效。

error_log  logs/error.log  error;
  1. 重定向与内部重定向

重定向:ngx.redirect('uri')

server{
    listen 80;
	server_name 172.21.168.120;
    charset "utf-8";
    location /{
        content_by_lua_block{
            ngx.redirect("/request?a=1&b=2")
        }
    }
    location /request {
        content_by_lua_block{
            local requstMethod = ngx.req.get_method()
            ngx.say('请求方法类型:'..requstMethod)
            local requsetData
            if requstMethod == nil then
                ngx.say("获取请求方法失败!")
            elseif requstMethod == 'POST' then
                ngx.req.read_body()
                requsetData = ngx.req.get_post_args()
            elseif requstMethod == 'GET' then
                requsetData = ngx.req.get_uri_args()
            end
            for k, v in pairs(requsetData) do
                  ngx.say("key = "..k..' value = '..v)
            end
        }
    }
}

重定向时也可以指定重定向状态码,默认为302.在浏览器端访问localhost 可以看到url的变化,从localhost重定向到 localhost/request?a=1&b=2,浏览器URL被改写了,也就是重定向的功能。返回结果同B。

访问ip,也就是访问首页,状态302,然后跳转到http://172.21.168.120/request?a=1&b=2,状态200,然后又对http://172.21.168.120/request?a=1&b=2发起一次get请求。

内部重定向:ngx.exec('uri')

浏览器uri不会发生变化,但会有一样的输出结果。

二者区别:

​ 内部重定向不会发出额外的HTTP请求,不涉及新的外部HTTP流量,纯粹是内部的重定向。

​ 调用两者都会终止当前请求的处理,推荐使用return +重定向的方式,强化请求处理在此终止的事实。

  1. 子请求

单个子请求:res = ngx.location.capture('uri')

多个子请求:res,res2 = ngx.location.capture_multi('uri1','uri2',...)

所谓子请求,和重定向不同,它是当前请求分发出去的子请求,就像Java中当前主线程中创建一个带有返回值的线程。它不会终止当前请求,相当于去调用一个方法,然后返回结果。并且子请求只是模仿HTTP接口,没有额外的HTTP/tcp流量,一切都在内部执行,非常高效。

res中包括 4个值

  1. status 子请求响应状态码
  2. header 子请求响应头内容
  3. body 子请求响应体内容
  4. truncated 布尔值,判断body中是否包含截断的数据
server{
    listen 80;
    server_name 172.21.168.120;
    charset "utf-8";
    location /{
        content_by_lua_block{
            local cjson = require('cjson')
            local res = ngx.location.capture("/request?a=1&b=2")
            ngx.say(res.status)
            ngx.say(cjson.encode(res.header))
            ngx.say(cjson.encode(res.body))
            ngx.say(res.truncated)
        }
    }
    location /request {
        content_by_lua_block{
            local requstMethod = ngx.req.get_method()
            ngx.say('请求方法类型:'..requstMethod)
            local requsetData
            if requstMethod == nil then
                ngx.say("获取请求方法失败!")
            elseif requstMethod == 'POST' then
                ngx.req.read_body()
                requsetData = ngx.req.get_post_args()
            elseif requstMethod == 'GET' then
                requsetData = ngx.req.get_uri_args()
            end
            for k, v in pairs(requsetData) do
                  ngx.say("key = "..k..' value = '..v)
            end
        }
    }
}

请求返回结果:

200
{"Content-Type":"text\/html"}
"请求方法类型:GET\nkey = a value = 1\nkey = b value = 2\n"
false

cjson是lua的一个库,就相当于java中的核心API,作用就是处理json数据。

很显然,我们看到 request中的输出 都放进了body体内,很像方法调用。

多个子请求的用法也比较简单,有几个子请求就要几个返回结果,然后对返回结果进行解析即可。

子请求的作用非常之大,在流量拷贝、API聚合、AB测试中占有巨大地位。

8. openresty企业级应用2

8.1 API请求聚合

比如我们在查看淘宝订单详情的时候,要展示订单派件情况的信息、收货人信息、收货人地址信息、商品信息、付款信息、积分信息还有其他的活动信息等等。在这样一个页面中,你会怎么设计接口信息呢?

http {

    ......
	
    upstream user {
        server localhost:81;
    }
    upstream hair {
        server localhost:82;
    }
    server{
        listen 80;
		server_name 172.21.168.120;
        charset 'UTF-8';
        location / {

        }
        location /user/hair {
            content_by_lua_block{
                local cjson = require('cjson')
                ngx.say("请求植发和个人信息")
                local res,res2 = ngx.location.capture_multi{
                    {'/user/tomcat/userInfo'},
                    {'/hair/jetty/hairInfo'}
                }
                local data ={}
                if(res.status == 200) then
                    data['userinfo'] = res.body
                end
                if(res2.status == 200) then
                    data['hairInfo'] = res2.body
                end
                ngx.say(cjson.encode(data))
            }
        }
        location /user {
            rewrite ^/user(.*)$ $1 break;
            proxy_pass http://user;
        }
        location /hair {
            rewrite ^/hair(.*)$ $1 break;
            proxy_pass http://hair;
        }
		
		location /user/tomcat/userInfo {
			echo "/user/tomcat/userInfo!";
        }
		
		location /hair/jetty/hairInfo {
			echo "/hair/jetty/hairInfo";
        }
		
    }
}
  • 针对 /user/hair 这个URI进行处理,当前端请求该URI时,将会被openresty拦截,进入lua代码块处理
  • 使用子请求调用两个URI,然后将结果进行聚合。
  • 另外又定义了两个location 请求路径,/user和/hair,当子请求发起时,根据URI匹配规则,会调用反向代理
  • 需要注意的是,子请求中的URI /user/,/hair/ 前缀只是起到一个过滤和定位的作用,因为要匹配到location。而真正的请求路径是/tomcat/userInfo 和/jetty/hairInfo,所以就需要将原请求改写成真正的请求路径,要不然请求经过反向代理之后是访问不到资源的,故而使用了rewrite指令。
  • rewrite 将A规则 改写为B规则,^/hair(.)$ 是所有匹配 /hair的路径 ,$1是指(.*)括号中的内容,也是要被改写的路径。所以 /hair/jetty/hairInfo 经过改写后 就变成了 /jetty/hairInfo

请求结果:

请求植发和个人信息
{"hairInfo":"\/hair\/jetty\/hairInfo\n","userinfo":"\/user\/tomcat\/userInfo\n"}

8.2 线上数据库升级方案

使用openresty,则可以很方便的进行线上观察,一方面不妨碍老方案的进行,数据仍可以保存到单库单表中;另一方面,通过流量的拷贝,新方案也能获得线上环境的请求,达到100%线上模拟的效果。然后通过一段时间的观察,假若新方案有问题,对老方案也没有任何影响。而且针对流量拷贝的策略也可以做些定制化,按百分比切换、按黑白名单切换、按订单id范围切换或者按userID取模切换等等,可以说是非常的灵活了.

再将策略方案数据放入redis中或其他缓存中存储,不用重启openresty也可做到方案的快速切换,通过更新缓存数据即可。

具体使用原理主要就是子请求的使用:ngx.location.capture_multi{},一个请求老方案,一个请求新方案。

其他的就是数据的解析处理了,根据不同的条件进行不同的方案处理,思路就是这个样子啦!代码量不多,两三百行就可以搞定。

附录:lua API帮助文档

Name

ngx_http_lua_module - Embed the power of Lua into Nginx HTTP Servers.

This module is a core component of OpenResty. If you are using this module, then you are essentially using OpenResty ????

This module is not distributed with the Nginx source. See the installation instructions.

Table of Contents

Status

Production ready.

Version

This document describes ngx_lua v0.10.16, which is not released yet.

Synopsis

 # set search paths for pure Lua external libraries (';;' is the default path):
 lua_package_path '/foo/bar/?.lua;/blah/?.lua;;';

 # set search paths for Lua external libraries written in C (can also use ';;'):
 lua_package_cpath '/bar/baz/?.so;/blah/blah/?.so;;';

 server {
     location /lua_content {
         # MIME type determined by default_type:
         default_type 'text/plain';

         content_by_lua_block {
             ngx.say('Hello,world!')
         }
     }

     location /nginx_var {
         # MIME type determined by default_type:
         default_type 'text/plain';

         # try access /nginx_var?a=hello,world
         content_by_lua_block {
             ngx.say(ngx.var.arg_a)
         }
     }

     location = /request_body {
         client_max_body_size 50k;
         client_body_buffer_size 50k;

         content_by_lua_block {
             ngx.req.read_body()  -- explicitly read the req body
             local data = ngx.req.get_body_data()
             if data then
                 ngx.say("body data:")
                 ngx.print(data)
                 return
             end

             -- body may get buffered in a temp file:
             local file = ngx.req.get_body_file()
             if file then
                 ngx.say("body is in file ", file)
             else
                 ngx.say("no body found")
             end
         }
     }

     # transparent non-blocking I/O in Lua via subrequests
     # (well, a better way is to use cosockets)
     location = /lua {
         # MIME type determined by default_type:
         default_type 'text/plain';

         content_by_lua_block {
             local res = ngx.location.capture("/some_other_location")
             if res then
                 ngx.say("status: ", res.status)
                 ngx.say("body:")
                 ngx.print(res.body)
             end
         }
     }

     location = /foo {
         rewrite_by_lua_block {
             res = ngx.location.capture("/memc",
                 { args = { cmd = "incr", key = ngx.var.uri } }
             )
         }

         proxy_pass http://blah.blah.com;
     }

     location = /mixed {
         rewrite_by_lua_file /path/to/rewrite.lua;
         access_by_lua_file /path/to/access.lua;
         content_by_lua_file /path/to/content.lua;
     }

     # use nginx var in code path
     # CAUTION: contents in nginx var must be carefully filtered,
     # otherwise there'll be great security risk!
     location ~ ^/app/([-_a-zA-Z0-9/]+) {
         set $path $1;
         content_by_lua_file /path/to/lua/app/root/$path.lua;
     }

     location / {
        client_max_body_size 100k;
        client_body_buffer_size 100k;

        access_by_lua_block {
            -- check the client IP address is in our black list
            if ngx.var.remote_addr == "132.5.72.3" then
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end

            -- check if the URI contains bad words
            if ngx.var.uri and
                   string.match(ngx.var.request_body, "evil")
            then
                return ngx.redirect("/terms_of_use.html")
            end

            -- tests passed
        }

        # proxy_pass/fastcgi_pass/etc settings
     }
 }

Back to TOC

Description

This module is a core component of OpenResty. If you are using this module, then you are essentially using OpenResty ????

This module embeds Lua, via LuaJIT 2.0/2.1, into Nginx and by leveraging Nginx's subrequests, allows the integration of the powerful Lua threads (Lua coroutines) into the Nginx event model.

Unlike Apache's mod_lua and Lighttpd's mod_magnet, Lua code executed using this module can be 100% non-blocking on network traffic as long as the Nginx API for Lua provided by this module is used to handle requests to upstream services such as MySQL, PostgreSQL, Memcached, Redis, or upstream HTTP web services.

At least the following Lua libraries and Nginx modules can be used with this ngx_lua module:

Almost all the Nginx modules can be used with this ngx_lua module by means of ngx.location.capture or ngx.location.capture_multi but it is recommended to use those lua-resty-* libraries instead of creating subrequests to access the Nginx upstream modules because the former is usually much more flexible and memory-efficient.

The Lua interpreter or LuaJIT instance is shared across all the requests in a single nginx worker process but request contexts are segregated using lightweight Lua coroutines.

Loaded Lua modules persist in the nginx worker process level resulting in a small memory footprint in Lua even when under heavy loads.

This module is plugged into NGINX's "http" subsystem so it can only speaks downstream communication protocols in the HTTP family (HTTP 0.9/1.0/1.1/2.0, WebSockets, and etc). If you want to do generic TCP communications with the downstream clients, then you should use the ngx_stream_lua module instead which has a compatible Lua API.

Back to TOC

Typical Uses

Just to name a few:

  • Mashup'ing and processing outputs of various nginx upstream outputs (proxy, drizzle, postgres, redis, memcached, and etc) in Lua,
  • doing arbitrarily complex access control and security checks in Lua before requests actually reach the upstream backends,
  • manipulating response headers in an arbitrary way (by Lua)
  • fetching backend information from external storage backends (like redis, memcached, mysql, postgresql) and use that information to choose which upstream backend to access on-the-fly,
  • coding up arbitrarily complex web applications in a content handler using synchronous but still non-blocking access to the database backends and other storage,
  • doing very complex URL dispatch in Lua at rewrite phase,
  • using Lua to implement advanced caching mechanism for Nginx's subrequests and arbitrary locations.

The possibilities are unlimited as the module allows bringing together various elements within Nginx as well as exposing the power of the Lua language to the user. The module provides the full flexibility of scripting while offering performance levels comparable with native C language programs both in terms of CPU time as well as memory footprint. This is particularly the case when LuaJIT 2.x is enabled.

Other scripting language implementations typically struggle to match this performance level.

The Lua state (Lua VM instance) is shared across all the requests handled by a single nginx worker process to minimize memory use.

Back to TOC

Nginx Compatibility

The latest version of this module is compatible with the following versions of Nginx:

  • 1.17.x (last tested: 1.17.1)
  • 1.15.x (last tested: 1.15.8)
  • 1.14.x
  • 1.13.x (last tested: 1.13.6)
  • 1.12.x
  • 1.11.x (last tested: 1.11.2)
  • 1.10.x
  • 1.9.x (last tested: 1.9.15)
  • 1.8.x
  • 1.7.x (last tested: 1.7.10)
  • 1.6.x

Nginx cores older than 1.6.0 (exclusive) are not supported.

Back to TOC

Installation

It is highly recommended to use OpenResty releases which integrate Nginx, ngx_lua, OpenResty's LuaJIT 2.1 branch version, as well as other powerful companion Nginx modules and Lua libraries. It is discouraged to build this module with nginx yourself since it is tricky to set up exactly right. Also, the stock nginx cores have various limitations and long standing bugs that can make some of this modules' features become disabled, not work properly, or run slower. The same applies to LuaJIT as well. OpenResty includes its own version of LuaJIT which gets specifically optimized and enhanced for the OpenResty environment.

Alternatively, ngx_lua can be manually compiled into Nginx:

  1. LuaJIT can be downloaded from the latest release of OpenResty's LuaJIT branch version. The official LuaJIT 2.0 and 2.1 releases are also supported, although the performance will be significantly lower in many cases.
  2. Download the latest version of the ngx_devel_kit (NDK) module HERE.
  3. Download the latest version of ngx_lua HERE.
  4. Download the latest version of Nginx HERE (See Nginx Compatibility)

Build the source with this module:

 wget 'http://nginx.org/download/nginx-1.13.6.tar.gz'
 tar -xzvf nginx-1.13.6.tar.gz
 cd nginx-1.13.6/

 # tell nginx's build system where to find LuaJIT 2.0:
 export LUAJIT_LIB=/path/to/luajit/lib
 export LUAJIT_INC=/path/to/luajit/include/luajit-2.0

 # tell nginx's build system where to find LuaJIT 2.1:
 export LUAJIT_LIB=/path/to/luajit/lib
 export LUAJIT_INC=/path/to/luajit/include/luajit-2.1

 # or tell where to find Lua if using Lua instead:
 #export LUA_LIB=/path/to/lua/lib
 #export LUA_INC=/path/to/lua/include

 # Here we assume Nginx is to be installed under /opt/nginx/.
 ./configure --prefix=/opt/nginx \
         --with-ld-opt="-Wl,-rpath,/path/to/luajit-or-lua/lib" \
         --add-module=/path/to/ngx_devel_kit \
         --add-module=/path/to/lua-nginx-module

 # Note that you may also want to add `./configure` options which are used in your
 # current nginx build.
 # You can get usually those options using command nginx -V

 # you can change the parallism number 2 below to fit the number of spare CPU cores in your
 # machine.
 make -j2
 make install

Back to TOC

Building as a dynamic module

Starting from NGINX 1.9.11, you can also compile this module as a dynamic module, by using the --add-dynamic-module=PATH option instead of --add-module=PATH on the ./configure command line above. And then you can explicitly load the module in your nginx.conf via the load_module directive, for example,

 load_module /path/to/modules/ndk_http_module.so;  # assuming NDK is built as a dynamic module too
 load_module /path/to/modules/ngx_http_lua_module.so;

Back to TOC

C Macro Configurations

While building this module either via OpenResty or with the NGINX core, you can define the following C macros via the C compiler options:

  • NGX_LUA_USE_ASSERT When defined, will enable assertions in the ngx_lua C code base. Recommended for debugging or testing builds. It can introduce some (small) runtime overhead when enabled. This macro was f

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
lua与c之间交互详解(一)发布时间:2022-07-22
下一篇:
ZeroBraneStudio-LuaIDE/editor/debuggerforWindows,MacOSX,andLinux发布时间:2022-07-22
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap