JavaEE鸿蒙应用开发HTML&JS+前端Python+大数据开发人工智能开发电商视觉设计软件测试新媒体+短视频直播运营产品经理集成电路应用开发(含嵌入式)Linux云计算+运维开发C/C++拍摄剪辑+短视频制作PMP项目管理认证电商运营Go语言与区块链大数据PHP工程师Android+物联网iOS.NET

高性能web平台openrestry简介

来源:黑马程序员

浏览6955人

2020.10.20

# 概述

OpenResty® 是一个基于 [Nginx](http://openresty.org/cn/nginx.html) 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

OpenResty® 通过汇聚各种设计精良的 [Nginx](http://openresty.org/cn/nginx.html) 模块(主要由 OpenResty 团队自主开发),从而将 [Nginx](http://openresty.org/cn/nginx.html) 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 [Nginx](http://openresty.org/cn/nginx.html) 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。

OpenResty® 的目标是让你的Web服务直接跑在 [Nginx](http://openresty.org/cn/nginx.html) 服务内部,充分利用 [Nginx](http://openresty.org/cn/nginx.html)的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。

OpenResty 简单理解,就相当于封装了nginx,并且集成了LUA脚本,开发人员只需要简单的其提供了模块就可以实现相关的逻辑,而不再像之前,还需要在nginx中自己编写lua的脚本,再进行调用了。

# 安装

**linux安装openresty:**

1、添加仓库执行命令

yum install yum-utils

yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo

2、执行安装

yum install openresty

3、安装成功后 会在默认的目录如下:

/usr/local/openresty

4、启动openresty

cd /usr/local/openresty/nginx/sbin

./nginx

# 入门程序

配置openresty的nginx配置文件`conf/nginx.conf`。在http模块下添加一个server配置

```lua

 server {

        listen 8080;

        location / {

            default_type text/html;

            content_by_lua_block {

                ngx.say("<p>hello, world</p>")

        }

    }

}

```

重启openrestry

cd /usr/local/openresty/nginx/sbin

./nginx -s reload

输入如下地址,进行访问 http://192.168.200.128:8080  浏览器输出 `hello, world`

# openrestry中的常用Lua API介绍

和一般的Web Server类似,我们需要接收请求、处理并输出响应。而对于请求我们需要获取如请求参数、请求头、Body体等信息;而对于处理就是调用相应的Lua代码即可;输出响应需要进行响应状态码、响应头和响应

内容体的输出。因此我们从如上几个点出发即可

### 接收请求

获取nginx变量:`ngx.var`

```lua

 server {

        listen 8080;

        location / {

        #定义nginx变量

set $b $host;

            default_type text/html;

            content_by_lua_block {

            local var = ngx.var; -- 获取nginx变量

ngx.say("ngx.var.b : ", var.b, "<br/>")

ngx.var.b = 2; -- 设置变量值

ngx.say("ngx.var.b : ", var.b, "<br/>")

ngx.say("<br/>")

            }

        }

    }

```

获取请求头:`ngx.req.get_headers()`

```lua

server {

        listen 8080;

        location / {

        #定义nginx变量

set $b $host;

            default_type text/html;

            content_by_lua_block {

local headers = ngx.req.get_headers()

ngx.say("headers begin", "<br/>")

ngx.say("Host : ", headers["Host"], "<br/>")

ngx.say("user-agent : ", headers["user-agent"], "<br/>")

ngx.say("user-agent : ", headers.user_agent, "<br/>")

ngx.say("=======================================","</br>")

for k,v in pairs(headers) do

if type(v) == "table" then

ngx.say(k, " : ", table.concat(v, ","), "<br/>")

else

ngx.say(k, " : ", v, "<br/>")

end

end

ngx.say("headers end", "<br/>")

ngx.say("<br/>")

            }

        }

    }

```

get请求uri参数:`ngx.req.get_uri_args()`

```lua

 server {

        listen 8080;

        location / {

        #定义nginx变量

set $b $host;

            default_type text/html;

            content_by_lua_block {

ngx.say("uri args begin", "<br/>")

local uri_args = ngx.req.get_uri_args()

ngx.say("param:username=",uri_args['username'], "<br/>")

ngx.say("param:password=",uri_args['password'], "<br/>")

ngx.say("uri args end", "<br/>")

ngx.say("<br/>")

            }

        }

    }

```

post请求参数:ngx.req.get_post_args()

```lua

 server {

        listen 8080;

        location / {

        #定义nginx变量

set $b $host;

            default_type text/html;

            content_by_lua_block {

ngx.say("uri args begin", "<br/>")

-- 获取请求体中的数据

ngx.req.read_body()

local uri_args = ngx.req.get_post_args() --获取key-value格式的数据

ngx.say("param:username=",uri_args['username'], "<br/>")

ngx.say("param:password=",uri_args['password'], "<br/>")

ngx.say("uri args end", "<br/>")

ngx.say("<br/>")

            }

        }

    }

```

其他请求相关的方法:

> 获取请求的http协议版本:`ngx.req.http_version()`

>

> 获取请求方法:`ngx.req.get_method()`

>

> 获取请求头内容:`ngx.req.get_headers()`

>

> 获取请求的body内容体:`ngx.req.get_body_data()`

### 输出响应

```lua

 server {

        listen 8080;

        location / {

            default_type text/html;

            content_by_lua_block {

--写响应头

ngx.header.a = "1"

--多个响应头可以使用table

ngx.header.b = {"2", "3"}

--输出响应

ngx.say("a", "b", "<br/>")

ngx.print("c", "d", "<br/>")

--200状态码退出

return ngx.exit(200)

            }

        }

    }

}

```

响应相关方法:

> ngx.header.xx = yy:输出响应头;

>

> ngx.print():输出响应内容体;

>

> ngx.say():同ngx.print()一样,但是会最后输出一个换行符;

>

> ngx.exit():指定状态码退出;

>

> ngx.send_headers():发送响应状态码,当调用ngx.say/ngx.print时自动发送响应状态码;

>

> ngx.headers_sent( ): 判断是否发送了响应状态码。

重定向

```lua

 server {

        listen 8080;

        location / {

            default_type text/html;

            content_by_lua_block {

ngx.redirect("http://jd.com", 302);  

            }

        }

    }

```

### Nginx全局内存

使用过如Java的朋友可能知道如Ehcache等这种进程内本地缓存,Nginx是一个Master进程多个Worker进程的

工作方式,因此我们可能需要在多个Worker进程中共享数据,那么此时就可以使用ngx.shared.DICT来实现全

局内存共享。

1、首先在nginx.conf的http部分定义一个全局内存,并指定内存大小。

```

#共享全局变量,在所有worker间共享,如下:定义了一个名为shared_data的全局内存,大小为1m

lua_shared_dict shared_data 1m;

```

2、使用全局内存

```lua

 server {

        listen 8080;

        location / {

            default_type text/html;

            content_by_lua_block {

--1、获取全局共享内存变量

local shared_data = ngx.shared.shared_data

--2、获取字典值

local i = shared_data:get("i")

if not i then

i = 1

--3、惰性赋值

shared_data:set("i", i)

ngx.say("lazy set i ", i, "<br/>")

end

--递增

i = shared_data:incr("i", 1)

ngx.say("i=", i, "<br/>")

            }

        }

    }

```

3、全局内存常用方法介绍

**ngx.shared.DICT**

> 获取共享内存字典项对象

>

```

语法:dict = ngx.shared.DICT

dict = ngx.shared[name_var]

其中,DICT和name_var表示的名称是一致的,比如上面例子中,shared_data = ngx.shared.shared_data

就是dict = ngx.shared.DICT的表达形式,

也可以通过下面的方式达到同样的目的:

shared_data = ngx.shared['shared_data']

```

**ngx.shared.DICT:get(key)**

> 获取共享内存上key对应的值。如果key不存在,或者key已经过期,将会返回nil;如果出现错误,那么将会返回nil以及错误信息。

**ngx.shared.DICT:get_stale(key)**

> 与get方法类似,区别在于该方法对于过期的key也会返回,第三个返回参数表明返回的key的值是否已经过期,true表示过期,false表示没有过期。

 **ngx.shared.DICT:set(key, value, exptime?, flags?)**

> “无条件”地往共享内存上插入key-value对,这里讲的“无条件”指的是不管待插入的共享内存上是否已经存在相同的key。

>

> 三个返回值的含义:

>

> success:成功插入为true,插入失败为false

>

> err:操作失败时的错误信息,可能类似"no memory"

>

> forcible:true表明需要通过强制删除(LRU算法)共享内存上其他字典项来实现插入,false表明没有删除共享内存上的字典项来实现插入。

>

>  

>

> exptime参数表明key的有效期时间,单位是秒(s),默认值为0,表明永远不会过期;

>

> flags参数是一个用户标志值,会在调用get方法时同时获取得到

**ngx.shared.DICT.safe_set*(key, value, exptime?, flags?)***

> 与set方法类似,区别在于不会在共享内存用完的情况下,通过强制删除(LRU算法)的方法实现插入。如果内存不足,会直接返回nil和err信息"no memory"

**ngx.shared.DICT.add*(key, value, exptime?, flags?)***

> 与set方法类似,与set方法区别在于不会插入重复的键(可以简单认为add方法是set方法的一个子方法),如果待插入的key已经存在,将会返回nil和和err="exists"

**ngx.shared.DICT.safe_add*(key, value, exptime?, flags?)***

> 与safe_set方法类似,区别在于不会插入重复的键(可以简单认为safe_add方法是safe_set方法的一个子方法),如果待插入的key已经存在,将会返回nil和和err="exists"

**ngx.shared.DICT.replace*(key, value, exptime?, flags?)***

> 与set方法类似,区别在于只对已经存在的key进行操作(可以简单认为replace方法是set方法的一个子方法),如果待插入的key在字典上不存在,将会返回nil和错误信息"not found"

**ngx.shared.DICT.delete*(key)***

> 无条件删除指定的key-value对,其等价于

>

> ngx.shared.DICT:set(key, nil)

**ngx.shared.DICT.incr*(key, value)***

> 对key对应的值进行增量操作,增量值是value,其中value的值可以是一个正数,0,也可以是一个负数。value必须是一个Lua类型中的number类型,否则将会返回nil和"not a number";key必须是一个已经存在于共享内存中的key,否则将会返回nil和"not found".

**ngx.shared.DICT.flush_all*()***

> 清除字典上的所有字段,但不会真正释放掉字段所占用的内存,而仅仅是将每个字段标志为过期。

**ngx.shared.DICT.flush_expired*(max_count?)***

> 清除字典上过期的字段,max_count表明上限值,如果为0或者没有给出,表明需要清除所有过期的字段,返回值flushed是实际删除掉的过期字段的数目。

>

> 注意:

>

> 与flush_all方法的区别在于,该方法将会释放掉过期字段所占用的内存

**ngx.shared.DICT.get_keys*(max_count?)***

> 从字典上获取字段列表,个数为max_count,如果为0或没有给出,表明不限定个数。默认值是1024个

>

> 注意:

>

> 强烈建议在调用该方法时,指定一个max_count参数,因为在keys数量很大的情况下,如果不指定max_count的值,可能会导致字典被锁定,从而阻塞试图访问字典的worker进程。

>

# openresty执行过程分析

Nginx与Lua编写脚本的基本构建块是指令。 指令用于指定何时运行用户Lua代码以及如何使用结果。openresty(Nginx+lua-nginx-module)中各个阶段执行的指令解释及其执行顺序

1603187245414896.png

> init_by_lua*:初始化 nginx 和预加载 lua(nginx 启动和 reload 时执行);*

>

> init_worker_by_lua*:每个工作进程(worker_processes)被创建时执行,用于启动一些定时任务,比如心跳检查,后端服务的健康检查,定时拉取服务器配置等;*

>

> ssl_certificate_by_lua*:对 https 请求的处理,即将启动下游 SSL(https)连接的 SSL 握手时执行,用例:按照每个请求设置 SSL 证书链和相应的私钥,按照 SSL 协议有选择的拒绝请求等;

>

> *set_by_lua*:设置 nginx 变量;

>

> rewrite_by_lua*:重写请求(从原生 nginx 的 rewrite 阶段进入),执行内部 URL 重写或者外部重定向,典型的如伪静态化的 URL 重写;*

>

> access_by_lua*:处理请求(和 rewrite_by_lua 可以实现相同的功能,从原生 nginx 的 access阶段进入);*

>

> content_by_lua*:执行业务逻辑并产生响应,类似于 jsp 中的 servlet;

>

> *balancer_by_lua*:负载均衡;

>

> header_filter_by_lua*:处理响应头;

>

> *body_filter_by_lua*:处理响应体;

>

> log_by_lua:记录访问日志;

备注:`*`表示两种选择,比如 `log_by_lua*` 可以表示`log_by_lua`或`log_by_lua_file`

# nginx_lua常用模块介绍

### json格式化:cjson

```lua

server {

        listen 8080;

        location / {

            default_type text/html;

            content_by_lua_block {

            -- 引入cjson

            local cjson = require("cjson")   

--将lua对象 转为 json字符串

    local obj = {

        id = 1,

        name = "zhangsan",

        age = nil,

        is_male = false,

        hobby = {"film", "music", "read"}

    }

    local str = cjson.encode(obj)

    ngx.say("lua对象到字符串:",str,"</br>")

    ngx.say("--------------------------------</br>");

    --将字符串 转为 lua对象

    str = '{"hobby":["film","music","read"],"is_male":false,"name":"zhangsan","id":1,"age":null}'

    local obj = cjson.decode(str)

    ngx.say("字符串到lua对象:","</br>")

    ngx.say("obj.age ",obj.age,"</br>")

    ngx.say("obj.age == nil ",obj.age == nil,"</br>")

    ngx.say("obj.age == cjson.null ",obj.age == cjson.null,"</br>")

    ngx.say("obj.hobby[1] ",obj.hobby[1],"</br>")

            }

        }

    }

```

### redis客户端:resty.redis

```lua

-- 引入resty.redis库

local redis = require("resty.redis")

-- 定义关闭redis连接的方法

local function close_redis(instance)

    if not instance then

        return

    end

    local ok, error = instance:close()

    if not ok then

        ngx.say(error)

    end

end

-- 创建redis连接

local redis_ip = "127.0.0.1"

local redis_port = "6379"

local redis_instance = redis:new()

redis_instance:set_timeout(1000)

local ok, error = redis_instance.connect(redis_ip, redis_port)

if not ok then

    ngx.say(error)

    return close_redis(redis_instance)

end

-- 执行redis命令

local redis_key = "message"

local redis_value = ngx.md5("hello, world")

ok, error = redis_instance:set(redis_key, redis_value)

if not ok then

    ngx.say(error)

    return close_redis(redis_instance)

end

local message, error = redis_instance:get(redis_key)

if not message then

    ngx.say(error)

    return close_redis(redis_instance)

end

if message == ngx.null then

    message = ""

end

ngx.say(redis_key, ": ", message)

-- 关闭redis连接

close_redis(redis_instance)

```

### mysql客户端:resty.mysql

#### 配置`openresty`连接`mysql`

```lua

-- 引入resty.mysql库

local mysql = require("resty.mysql")

-- 定义关闭连接的方法

local function close_db(instance)

    if not instance then

        return

    end

    instance:close()

end

-- 创建mysql实例对象

local instance, error = mysql:new()

if not instance then

    ngx.say(error)

    return

end

instance:set_timeout(1000)

local properties = {

    host = "127.0.0.1",

    port = 3306,

    database = "wpsmail",

    user = "root",

    password = "123456"

}

-- 连接mysql

local result, error, error_no, sql_state = instance:connect(properties)

if not result then

    ngx.say("error: ", error, ", error_no: ",

        error_no, ", sql_state: ", sql_state)

    return close_db(instance)

end

```

#### 删除表

```bash

local drop_table_sql = "drop table if exists test"

local drop_result, error, error_no, sql_state = instance:query(drop_table_sql)

if not drop_result then

    ngx.say("error: ", error, ", error_no: ", 

        error_no, ", sql_state: ", sql_state)

    return close_db(instance)

end

```

#### 创建表

```bash

local create_table_sql 

= "create table test(id int primary key auto_increment, ch varchar(100))"

local create_result, error, error_no, sql_state = instance:query(create_table_sql)

if not create_result then

    ngx.say("error: ", error, ", error_no: ", 

        error_no, ", sql_state: ", sql_state)

    return close_db(instance)

end

```

#### 插入表数据

```bash

local insert_sql = "insert into test (ch) values('hello')"

local insert_result, error, error_no, sql_state = instance:query(insert_sql)

if not insert_result then

    ngx.say("error: ", error, ", error_no: ", 

        error_no, ", sql_state: ", sql_state)

    return close_db(instance)

end

ngx.say("affected row: ", insert_result.affected_rows, 

", insert id: ", insert_result.insert_id)

```

#### 更新表数据

```bash

local update_table_sql 

    = "update test set ch = 'hello2' where id =" .. insert_result.insert_id

local update_result, error, error_no, sql_state = instance:query(update_table_sql)

if not update_result then

    ngx.say("error: ", error, ", error_no: ", 

        error_no, ", sql_state: ", sql_state)

    return close_db(instance)

end

ngx.say("affected row: ", insert_result.affected_rows)

```

#### 查询表数据

```bash

local select_table_sql = "select id, ch from test"

local select_result, error, error_no, sql_state = instance:query(select_table_sql)

if not select_result then

    ngx.say("error: ", error, ", error_no: ", 

        error_no, ", sql_state: ", sql_state)

    return close_db(instance)

end

for i, row in ipairs(select_result) do

    ngx.say(i, ": ", row)

end

```

# 总结

本文给大家介绍了openresty这一高性能web服务平台的基本使用。包括如何在openresty中处理请求和响应,在openresty是使用nginx的本地缓存(即nginx全局内存)。分析了openresty的整体执行过程,其中**`content_by_lua*`**阶段,是执行业务逻辑并产生响应的阶段,我们的业务代码主要在此阶段编写。同时本文还介绍了如何使用`resty.redis`操作redis,使用`resty.mysql`来操作mysql,以及使用cjson进行数据的json格式化。

# 参考资料

[openresty官网](http://openresty.org/cn/)

《Lua程序设计(第2版)》