1.前言:

一般做nginx相关开发的人员都会在nginx里去写lua脚本去处理自定义的一些特殊的业务逻辑

比如说:流量分发,自己用lua去写分发的逻辑,在分发层nginx里去写;

再比如说;要用lua去写多级缓存架构存取的控制逻辑,在应用层nginx里去写的;

还有热点数据的自动降级机制,也是用lua脚本在分发层nginx里去写去写降级机制的;

因为我们要用nginx+lua去开发,所以会选择用最流行的开源方案,就是用OpenResty;

OpenResty:nginx+lua打包在一起,而且提供了包括redis客户端、mysql客户端、http客户端在内的大量的组件;

2. 安装一个Nginx

(1)部署openresty

mkdir -p /usr/servers  #创建目录后续Nginx相关的东西都放到这里
cd /usr/servers/

yum install -y readline-devel pcre-devel openssl-devel gcc  #先安装必要的工具包

wget http://openresty.org/download/ngx_openresty-1.7.7.2.tar.gz  #下载openresty
tar -xzvf ngx_openresty-1.7.7.2.tar.gz  
cd /usr/servers/ngx_openresty-1.7.7.2/
cd bundle/LuaJIT-2.1-20150120/  
make clean && make && make install   #安装openresty
ln -sf luajit-2.1.0-alpha /usr/local/bin/luajit  #结束后执行该命令(系统一般会提示)

cd bundle  
wget https://github.com/FRiCKLE/ngx_cache_purge/archive/2.3.tar.gz   #下载该工具
tar -xvf 2.3.tar.gz  

cd bundle  
wget https://github.com/yaoweibin/nginx_upstream_check_module/archive/v0.3.0.tar.gz #下载工具
tar -xvf v0.3.0.tar.gz  

cd /usr/servers/ngx_openresty-1.7.7.2  

./configure --prefix=/usr/servers --with-http_realip_module  --with-pcre  --with-luajit --add-module=./bundle/ngx_cache_purge-2.3/ --add-module=./bundle/nginx_upstream_check_module-0.3.0/ -j2   #执行配置

make && make install  #安装工具

cd /usr/servers/  
ll  #查看目录结构

/usr/servers/luajit
/usr/servers/lualib
/usr/servers/nginx

/usr/servers/nginx/sbin/nginx -V  #查看Nginx版本

启动nginx: /usr/servers/nginx/sbin/nginx

3.nginx+lua开发的hello world

vi /usr/servers/nginx/conf/nginx.conf #编辑Nginx配置文件

在http部分添加:#注意要添加到{}里边 一般放到靠上的位置
lua_package_path "/usr/servers/lualib/?.lua;;";  
lua_package_cpath "/usr/servers/lualib/?.so;;";  

在/usr/servers/nginx/conf下,创建一个lua.conf  文件内容如下:
server {  
    listen       80;  
    server_name  _;  
}  

在nginx.conf的http部分添加以下 include #注意要添加到{}里边 一般放到靠上的位置
include lua.conf;

验证Nginx配置是否正确-正确无误后再进行下边的操作:
/usr/servers/nginx/sbin/nginx -t

在lua.conf的server部分添加:
location /lua {  
    default_type 'text/html';  
    content_by_lua 'ngx.say("hello world")';  
} 

/usr/servers/nginx/sbin/nginx -t  #验证Nginx配置是否正确-正确无误后再进行下边的操作:
/usr/servers/nginx/sbin/nginx -s reload   让Nginx重新加载配置

http: http://192.168.1.10/lua #浏览器访问测试,IP用Nginx机器的IP

#上边已经配置好了,但是lua脚本放到了Nginx的配置文件中了,不利于维护,我们单独拿出来
vi /usr/servers/nginx/conf/lua/test.lua #新增 test.lua 配置文件,文件内容如下
ngx.say("hello world"); 

修改lua.conf配置文件
location /lua {  
    default_type 'text/html';  
    content_by_lua_file conf/lua/test.lua; 
}

#做完以上配置后进行下边的验证操作
/usr/servers/nginx/sbin/nginx -t  #验证Nginx配置是否正确-正确无误后再进行下边的操作:
/usr/servers/nginx/sbin/nginx -s reload   让Nginx重新加载配置
http: http://192.168.1.10/lua #浏览器访问测试,IP用Nginx机器的IP

如果出现异常-查看异常日志
tail -f /usr/servers/nginx/logs/error.log

4.工程化的nginx+lua项目结构(一般项目开发都有比较好的开发规范)

假设我们的工程项目就叫hello,我们先列出他的整个文件的目录结构
hello                 #所以的当前项目的文件都放到该目录下
    hello.conf        #该项目的Nginx配置文件
    lua               #所有的lua脚本都放到该目录下
      hello.lua       #具体的lua脚本文件
    lualib            #工具包 需要从上一个环节中的/usr/servers/lualib目录拷贝过来
      *.lua
      *.so

我们的工程项目 hello 放在/usr/hello 目录下

/usr/servers/nginx/conf/nginx.conf #编辑该配置文件修改相关参数
worker_processes  2;  
error_log  logs/error.log;  
events {  
    worker_connections  1024;  
}  
http {  
    include       mime.types;  
    default_type  text/html;  
    lua_package_path "/usr/hello/lualib/?.lua;;";  #开头就将lualib拷贝到当前路径下了
    lua_package_cpath "/usr/hello/lualib/?.so;;"; 
    include /usr/hello/hello.conf;                 #当前项目的Nginx配置文件引入进来
}  


/usr/hello/hello.conf  #当前项目的Nginx配置文件如下
server {  
    listen       80;  
    server_name  _;  
  
    location /hello{  
        default_type 'text/html';   
        content_by_lua_file /usr/hello/lua/hello.lua;  #lua配置文件 将上个步骤中的拷贝过来或者重新写入一份均可
    }  
}  


#做完以上配置后进行下边的验证操作
/usr/servers/nginx/sbin/nginx -t  #验证Nginx配置是否正确-正确无误后再进行下边的操作:
/usr/servers/nginx/sbin/nginx -s reload   让Nginx重新加载配置
http: http://192.168.1.10/hello #浏览器访问测试,IP用Nginx机器的IP

5.基于商品id的定向流量分发策略的lua脚本

作为一个流量分发的nginx,会发送http请求到后端的应用nginx上面去,所以要先引入lua http lib包

cd /usr/hello/lualib/resty/   #进入该目录

wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http_headers.lua  

wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http.lua

Lua脚本

local uri_args = ngx.req.get_uri_args()
local productId = uri_args["productId"]

local hosts = {"192.168.31.187", "192.168.31.19"}  #要将流量分发到下属Nginx的IP
local hash = ngx.crc32_long(productId)
local index = (hash % 2) + 1                 #几台机器就写 % 几
backend = "http://"..hosts[index]

local requestPath = uri_args["requestPath"]
requestPath = "/"..requestPath.."?productId="..productId

local http = require("resty.http")
local httpc = http.new()

local resp, err = httpc:request_uri(backend,{
  method = "GET",
  path = requestPath
})

if not resp then
  ngx.say("request error: ", err)
  return
end

ngx.say(resp.body)

httpc:close()

最后需要刷新Nginx使配置生效

/usr/servers/nginx/sbin/nginx -s reload

6.Nginx基于 Lua脚本 +  Nginx template 实现缓存 简单实例

先下载template工具包

cd /usr/hello/lualib/resty/ #进入目录

wget https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/c.lua

mkdir /usr/hello/lualib/resty/html  #创建目录
cd /usr/hello/lualib/resty/html     #进入目录

wget https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/template/html.lua

在Nginx配置文件中 nginx.conf 配置缓存名称 my_cache (放到 server 节点下)

lua_shared_dict my_cache 128m; #规定缓存大小,缓存超过该值会自动失效(LRU原则)

在hello.conf的 server 节点中配置模板位置

set $template_location "/templates";  
set $template_root "/usr/hello/templates";

创建目录:mkdir /usr/hello/templates 编辑模板文件 vi product.html

<html>
  <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>商品详情页</title>
  </head>
<body>
    商品id: {* productId *}<br/>
    商品名称: {* productName *}<br/>
    商品图片列表: {* productPictureList *}<br/>
    商品规格: {* productSpecification *}<br/>
    商品售后服务: {* productService *}<br/>
    商品颜色: {* productColor *}<br/>
    商品大小: {* productSize *}<br/>
    店铺id: {* shopId *}<br/>
    店铺名称: {* shopName *}<br/>
    店铺等级: {* shopLevel *}<br/>
    店铺好评率: {* shopGoodCommentRate *}<br/>
</body>
</html>

Lua脚本 : hello.conf

local uri_args = ngx.req.get_uri_args()  #获取URL参数
local productId = uri_args["productId"]  #获取具体的参数
local shopId = uri_args["shopId"]

local cache_ngx = ngx.shared.my_cache    #拿到Nginx配置的缓存my_cache

local productCacheKey = "product_info_"..productId #拼装缓存Key ..是lua中的连接符
local shopCacheKey = "shop_info_"..shopId

local productCache = cache_ngx:get(productCacheKey) #从缓存中获取数据
local shopCache = cache_ngx:get(shopCacheKey)

if productCache == "" or productCache == nil then  #如果没有从缓存中获取到数据需要从后端服务获取
	local http = require("resty.http") #拿到工具
	local httpc = http.new()           #创建工具对象

	local resp, err = httpc:request_uri("http://192.168.31.179:8080",{ #发送请求到后端服务
  		method = "GET",
  		path = "/getProductInfo?productId="..productId
	})

	productCache = resp.body  #从后端相应结果中拿到数据
	cache_ngx:set(productCacheKey, productCache, 10 * 60) #将数据再放置到Nginx的缓存中,有效期10分钟
end

if shopCache == "" or shopCache == nil then
	local http = require("resty.http")
	local httpc = http.new()

	local resp, err = httpc:request_uri("http://192.168.31.179:8080",{
  		method = "GET",
  		path = "/getShopInfo?shopId="..shopId
	})

	shopCache = resp.body
	cache_ngx:set(shopCacheKey, shopCache, 10 * 60)
end

local cjson = require("cjson")  #拿到JSon工具包
local productCacheJSON = cjson.decode(productCache) #将字符串转换位JSON对象
local shopCacheJSON = cjson.decode(shopCache)

local context = {   #拼装JSON对象 以便往模板文件中赋值
	productId = productCacheJSON.id,
	productName = productCacheJSON.name,
	productPrice = productCacheJSON.price,
	productPictureList = productCacheJSON.pictureList,
	productSpecification = productCacheJSON.specification,
	productService = productCacheJSON.service,
	productColor = productCacheJSON.color,
	productSize = productCacheJSON.size,
	shopId = shopCacheJSON.id,
	shopName = shopCacheJSON.name,
	shopLevel = shopCacheJSON.level,
	shopGoodCommentRate = shopCacheJSON.goodCommentRate
}

local template = require("resty.template") #获取模板工具包
template.render("product.html", context)   #用指定的JSON对象渲染指定的模板