Openresty
一、What is Openresty?
High Performance Web Platform Based on Nginx and LuaJIT.
基于Nginx和LuaJIT的高性能Web平台;
我们知道,Nginx 是一个高性能的web服务器;
OpenResty是一个成熟的Web应用程序服务器,它捆绑了标准的nginx核心、许多第三方的nginx模块以及这些模块的大多数外部依赖项,总之Openresty只是新增了一些模块插件对nginx的增强,nginx本身拥有的功能不变,依旧可以使用;
Openresty核心作者:章亦春;
另外还有一位开发人员:https://github.com/chaoslawful
OpenResty是一个基于 Nginx与Lua的高性能Web平台,其内部集成了大量精良的Lua 库、第三方模块以及大多数的依赖项,用于方便地搭建能够处理超高并发、扩展性极高的动态Web应用、Web服务和动态网关;
OpenResty通过汇聚各种设计精良的Nginx模块,从而将Nginx有效地变成一个强大的通用Web应用平台,让Web开发人员和后端工程师可以使用 Lua 脚本语言调用Nginx支持的各种C以及Lua模块,快速构建出足以胜任10K乃至1000K以上单机并发连接的高性能Web应用系统;
OpenResty的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅可以HTTP请求,也可以对远程后端 MySQL、PostgreSQL、Memcached 以及 Redis 等进行请求;
默认情况下,有一些组件没有启用,如果需要启动,在安装configure的时候需要通过 --with-xxxx的方式启用;
Openresty应用场景包括提供 负载均衡、请求路由、安全认证、服务鉴权、流量控制、模板渲染、日志监控、服务网关(kong、Orange)等;
Openresty应用也很广,奇虎360,京东、百度、魅族、知乎、优酷、新浪这些互联网公司都在使用;
LuaJIT(https://luajit.org/) Luajit即Lua Just-In-Time,也就是Lua运行时编译器,可以说是lua的一个高效版,Lua (www.lua.org)是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中而开发的一个脚本语言,从而为应用程序提供灵活的扩展和定制功能;
Lua是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo三人所组成的研究小组于1993年开发的;
二、Linux的Lua运行环境
yum install readline-devel -y
curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz
tar zxf lua-5.3.5.tar.gz
cd lua-5.3.5
make linux test
Lua环境是可以选的,不安装也不影响安装Openresty;
测试创建一个 HelloWorld.lua 文件:
print("Hello World!")
执行以下命令:
$ lua HelloWorld.lua
输出结果:
Hello World!
三、Openresty运行环境
中文官网:https://openresty.org/cn (免费开源版)
英文官网:https://openresty.org/en
商业服务版:https://openresty.com.cn/cn/
源码Github:https://github.com/openresty/
1、下载安装
下载:https://openresty.org/cn/download.html
openresty-1.15.8.3.tar.gz
1、Centos7,安装依赖库:yum install pcre-devel openssl-devel gcc curl -y
2、解压 tar -zxvf openresty-1.15.8.3.tar.gz
3、切换到解压后的目录cd openresty-1.15.8.3
4、执行配置./configure --prefix=/usr/local/openresty
查看帮助 ./configure --help
5、执行编译 make
6、执行安装 make install
2、启动与关闭
启动与关闭和Nginx完全一样,Openresty是Nginx的加强版;
切换到 /usr/local/openresty/nginx/sbin
目录下
启动执行:./nginx
关闭执行:./nginx -s quit 或者 ./nginx -s stop
测试配置文件:
/usr/local/openresty/nginx/sbin/nginx -c /usr/local/openresty/nginx/conf/nginx.conf -t
指定配置文件启动:
./nginx -c /usr/local/openresty/nginx/conf/nginx.conf
/usr/local/openresty/nginx/sbin/nginx -c /usr/local/openresty/nginx/conf/nginx.conf -t
四、Openresty使用
1、基于Openresty开发http服务;
Openresty是基于Nginx的,openresty = nginx + 一些 nginx 模块的集合,Nginx的各种功能或应用场景的实现,我们是采用在nginx.conf配置文件中完成的,那么openresty其实也是在nginx.conf配置文件中完成我们需要的功能,也就是说我们使用openresty,其实是面向nginx.conf配置文件增加配置和编写一些lua编程;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("<p>hello, Openresty</p>")
}
}
Nginx共11个处理阶段,而相应的处理阶段是可以做插入式处理,即可插拔式架构,可以在http、server、location几个范围进行配置:
2、Openresty连接MySQL
以下是连接mysql的一个lua脚本代码:
local function close_db(db)
if not db then
return
end
db:close()
end
local mysql = require("resty.mysql")
local db, err = mysql:new()
if not db then
ngx.say("new mysql error : ", err)
return
end
db:set_timeout(1000)
local props = {
host = "127.0.0.1",
port = 3306,
database = "test",
user = "root",
password = "123456"
}
local res, err, errno, sqlstate = db:connect(props)
if not res then
ngx.say("connect to mysql error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
local drop_table_sql = "drop table if exists test"
res, err, errno, sqlstate = db:query(drop_table_sql)
if not res then
ngx.say("drop table error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
local create_table_sql = "create table test(id int primary key auto_increment, ch varchar(100))"
res, err, errno, sqlstate = db:query(create_table_sql)
if not res then
ngx.say("create table error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
local insert_sql = "insert into test (ch) values('hello')"
res, err, errno, sqlstate = db:query(insert_sql)
if not res then
ngx.say("insert error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
res, err, errno, sqlstate = db:query(insert_sql)
ngx.say("insert rows : ", res.affected_rows, " , id : ", res.insert_id, "<br/>")
local update_sql = "update test set ch = 'hello2' where id =" .. res.insert_id
res, err, errno, sqlstate = db:query(update_sql)
if not res then
ngx.say("update error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
ngx.say("update rows : ", res.affected_rows, "<br/>")
local select_sql = "select id, ch from test"
res, err, errno, sqlstate = db:query(select_sql)
if not res then
ngx.say("select error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
for i, row in ipairs(res) do
for name, value in pairs(row) do
ngx.say("select row ", i, " : ", name, " = ", value, "<br/>")
end
end
ngx.say("<br/>")
local ch_param = ngx.req.get_uri_args()["ch"] or ''
local query_sql = "select id, ch from test where ch = " .. ngx.quote_sql_str(ch_param)
res, err, errno, sqlstate = db:query(query_sql)
if not res then
ngx.say("select error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
for i, row in ipairs(res) do
for name, value in pairs(row) do
ngx.say("select row ", i, " : ", name, " = ", value, "<br/>")
end
end
local delete_sql = "delete from test"
res, err, errno, sqlstate = db:query(delete_sql)
if not res then
ngx.say("delete error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
ngx.say("delete rows : ", res.affected_rows, "<br/>")
3、Openresty连接Redis
以下是连接Redis的lua脚本代码;
local function close_redis(red)
if not red then
return
end
local pool_max_idle_time = 10000 --毫秒
local pool_size = 100 --连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.say("set keepalive error : ", err)
end
end
local redis = require("resty.redis")
local red = redis:new()
red:set_timeout(1000)
local ip = "127.0.0.1"
local port = 6379
local ok, err = red:connect(ip, port)
if not ok then
ngx.say("connect to redis error : ", err)
return close_redis(red)
end
local res, err = red:auth("123456")
if not res then
ngx.say("set msg error : ", err)
return close_redis(red)
end
ok, err = red:set("msg", "hello world")
if not ok then
ngx.say("set msg error : ", err)
return close_redis(red)
end
local resp, err = red:get("msg")
if not resp then
ngx.say("get msg error : ", err)
return close_redis(red)
end
if resp == ngx.null then
resp = ''
end
ngx.say("msg : ", resp)
close_redis(red)
4、Openresty动态模板渲染
Lua有很好的生态,也就是有很多第三方开发库,比如Redis、Memcached、MySQL、Http客户端、JSON、模板引擎等,一些常见的Lua库可以在github上搜索https://github.com/search?utf8=%E2%9C%93&q=lua+resty
下面我们要介绍的一款Lua比较常用的模板引擎库:lua-resty-template
Github地址:https://github.com/bungle/lua-resty-template
我们开发中网页数据通常是动态的,如果全部是静态的可以直接生成html页面即可,如果是动态web网页,那么就需要动态页面渲染技术,像早些年的jsp、servlet就能开发动态网页,同时像Java领域的模板技术freemarker、velocity、thymeleaf等也可以实现动态网页,这些技术是在服务端渲染好然后向客户端输出html进行展示,而Openresty也有许多模板引擎,比如lua-resty-template,可以渲染很复杂的页面;
如果学习过JavaEE中的servlet和JSP的话,应该知道JSP模板最终会被翻译成Servlet来执行,而lua-resty-template模板引擎中的模板页面可以认为是JSP,其最终会被翻译成Lua代码,然后通过ngx.say输出;
lua-resty-template主要包括:
模板位置:模板在哪里;
模板数据:变量值的输出;
模板代码片段:在模板中执行代码片段,完成如if/else、for等逻辑或者调用对象的函数/方法;
模板注释:解释代码片段的含义;
模板include:在一个模板中包含另一个模板片段;
其他:lua-resty-template还提供了不需要解析片段、页面布局、可复用代码块、宏指令等支持;
开发操作步骤:
首先需要两个东西:
1、模板 data.html (模板页面,里面使用表达式语言展示数据)
2、Lua代码 data.lua (从其他地方获取数据,然后渲染数据到模板上)
data.lua代码如下:
local template = require "resty.template"
local content = {
data = "Hello, Openresty.",
by = "by cat"
}
template.render("data.html", content)
data.html模板页面代码如下:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to OpenResty!</title>
</head>
<body>
<p>
{{data}}
{{by}}
</P>
</body>
</html>
3、然后在nginx.conf中配置:
#指定模板位置
set $template_root /usr/local/openresty/nginx/html/template/;
location / {
#root html;
#index index.html index.htm;
default_type text/html;
#content_by_lua_block {
#ngx.say("<p>hello, Openresty</p>")
#}
#content_by_lua_file /usr/local/openresty/nginx/conf/lua/message.lua;
#渲染模板的lua脚本
content_by_lua_file /usr/local/openresty/nginx/conf/lua/data.lua;
}
4、访问:http://127.0.0.1/ 即可看到被渲染好数据的模板页面;
五、Openresty实现灰度发布
灰度发布(又叫金丝雀发布)是互联网产品发布常用的一种方式,顾名思义,就是在黑和白之间平滑过渡的一种产品发布方式,产品发布者根据某种规则,让一部分用户继续使用原来的产品功能,另一部分用户逐渐启用新功能,在过渡过程中,可能新版本会产生一些问题,可以对新版本做进一步的完善,待灰度发布完成后,所有的用户都将使用新的产品功能;
金丝雀发布**:就是将一部分流量引导到新版本,另一部分流量还是走老版本**;
灰度发布:可以按流量百分比、根据请求参数,根据请求ip等规则进行;
OpenResty为什么能实现灰度发布? OpenResty 有一个非常重要的因素是,对于每一个请求,Openresty 会把请求分为11个不同的阶段,从而可以让第三方模块通过插队行为来实现不同阶段的自定义行为,而这样的机制能够让我们非常方便的设计像api 网关之类的场景应用;(相当于java里面的filter、interceptor、aop、拦截过滤等作用)
一个请求的11个阶段分别如下图所示:
灰度发布的lua脚本代码示例:
beijing_env = 'beijing';
guangzhou_env = 'guangzhou';
flow_rate = 50; --流量比例
local uri_args = ngx.req.get_uri_args();
local num = math.random(100);
if (num <= flow_rate) then
ngx.exec("@"..beijing_env);
else
ngx.exec("@"..guangzhou_env);
end
灰度发布的lua脚本代码示例
ngx.header.content_type="text/html;charset=utf8"
---关闭redis函数
local function close_redis(red)
if not red then
return;
end
local pool_max_idle_time = 10000; --毫秒
local pool_size = 100; --连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size);
if not ok then
ngx.say("set keepalive error : ", err);
end
end
--导入相关依赖库
local redis = require("resty.redis");
local red = redis:new();
red:set_timeout(5000);
local ip = "127.0.0.1";
local port = 6379;
local ok, err = red:connect(ip, port);
if not ok then
ngx.say("connect to redis error : ", err);
return close_redis(red);
end
local res, err = red:auth("123456");
if not res then
ngx.say("redis auth error : ", err);
return close_redis(red);
end
--从redis中检查是否存在即将更新的upstream主机
oldversion = redis:get('oldversion')
newversion = redis:get('newversion')
redis:close()
--注意返回的数据类型来判断
if oldversion == "0" then
ngx.exec("@newversion")
elseif newversion == "0" then
ngx.exec("@oldversion")
else
ngx.exec("@all")
end
---关闭redis函数
local function close_redis(red)
if not red then
return;
end
local pool_max_idle_time = 10000; --毫秒
local pool_size = 100; --连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size);
if not ok then
ngx.say("set keepalive error : ", err);
end
end
灰度发布的lua脚本代码示例:
local redis = require ("resty.redis");
local red = redis:new();
red:set_timeout(5000);
local ip = "127.0.0.1";
local port = 6379;
local ok, err = red:connect(ip, port);
if not ok then
ngx.say("connect to redis error : ", err);
return close_redis(red);
end
local res, err = red:auth("123456");
if not res then
ngx.say("redis auth error : ", err);
return close_redis(red);
end
local_ip = ngx.var.remote_addr;
local ip_lists = red:get("gray");
if string.find(ip_lists, local_ip) == nil then
ngx.exec("@prod");
else
ngx.exec("@pre");
end
--关闭redis
close_redis(red);
六、Openresty实现黑名单
Openresty实现黑名单
– 加入限制的IP黑名单 (暂时是把ip写死在代码中的,可以查redis动态获取)
local blacklist = {
["192.168.0.11"] = true,
["110.128.16.92"] = true,
["114.116.23.18"] = true
}
–ip也可以查redis实现,不写死
local ip = ngx.var.remote_addr;
if blacklist[ ip ] then
--return ngx.exit( ngx.HTTP_FORBIDDEN )
return ngx.redirect('/50x.html');
end
通过这个例子,我们可以总结和发现:
限流
权限认证
比如网关里面能做的通用的工作等;
都可以采用Openresty来完成;