目录
- 一、初始OpenResty
- 1、介绍
- 2、安装
- docker安装
- linux安装
- 3、获取请求参数
- 4、nginx内部发送http请求
- 5、在查询商品的请求中,通过路径占位符的方式,传递了商品id到后台
- nginx配置
- item.lua
- 二、实战
- 1、架构
- 2、nginx配置
- 3、Openresty配置
- 4、common.lua公共模块
- 5、需求 获取请求路径中商品id信息,根据id向redis查询,如果没有向Tomcat查询商品信息
- 6、需求 查询商品时优先查询OpenResty的本地缓存
- 三、代码
- 1、仓库
- 2、数据库和lua文件
一、初始OpenResty
1、介绍
OpenResty是一个机遇Nginx的高性能web平台,用于方便的搭建能过处理超高并发,扩展性极高的动态web应用、Web服务和动态网关。具备下列特点:
- 具备Nginx的完成总怒
- 机遇Lua语言进行扩展,继承了大量精良的Lua库、第三方模块
- 允许使用Lua自定义业务逻辑、自定义库
官方网站:https://openresty.org/cn/
2、安装
docker安装
linux安装
3、获取请求参数
4、nginx内部发送http请求
nginx提供了内部API用以发送http请求:
local res = ngx.location.capture("/path",{
method = ngx.HTTP_GET, #请求方式
args = {a=1,b=2}, #get方式传参数
body = "c=3&d=4" #post方式传参数
});
- res.status :响应状态码
- res.header:响应头,是一个table;用一个标准 Lua 表储子请求响应的所有头信息。如果是"多值"响应头,
这些值将使用 Lua (数组) 表顺序存储。- res.body :响应体数据,它可能被截断。
用户需要检测 res.truncated (截断) 布尔值标记来判断 res.body 是否包含截断的数据。
这种数据截断的原因只可能是因为子请求发生了不可恢复的错误,
例如远端在发送响应体时过早中断了连接,或子请求在接收远端响应体时超时。
注意: 这里的path是路径,并不包含IP和端口。这个请求会被nginx内部的server监听并处理。但是我们洗完跟这个请求发送到Tomcat服务器,所以还需要编写一个server来对这个路径做方向代理:
location /path {
# 这里是tomcat的ip和端口
proxy_pass http://192.168.0.55:8081;
}
5、在查询商品的请求中,通过路径占位符的方式,传递了商品id到后台
http://127.0.0.1:7904/api/item/10001
需求:在OpenResty中接收这个请求,并获取路径中的id信息,拼接到结果的json字符串返回
nginx配置
http {
# 隐藏版本号
server_tokens off;
include mime.types;
default_type application/octet-stream;
underscores_in_headers on;#表示如果header name中包含下划线,则不忽略
sendfile on;
keepalive_timeout 65;
gzip on;
#lua 模块
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
#c模块
lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
server {
listen 80;
server_name 127.0.0.1;
location ~ /api/item/(\d+) {
# 默认的响应类型
default_type application/json;
# 响应结果有lua/item.lua文件来决定
content_by_lua_file lua/item.lua;
}
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
item.lua
重点是从路径中获取id,并拼接到返回值中
-- 获取路径参数
local id = ngx.var[1]
-- 返回结果
ngx.say('{"id":'.. id ..',"name":"SALSA AIR","title":"RIMOWA 21寸托运箱拉杆箱 SALSA AIR系列果绿色 820.70.36.4","price":16900,"image":"https://m.360buyimg.com/mobilecms/s720x720_jfs/t6934/364/1195375010/84676/e9f2c55f/597ece38N0ddcbc77.jpg!q70.jpg.webp","category":"拉杆箱","brand":"RIMOWA","status":1,"stock":null,"sold":null}')
二、实战
1、架构
在openResty先查询redis,如果不存在再发起http请求tomcat
- 先访问7904的nginx,动静分离,
- /api/item/10003转发到OpenResty的80端口
- OpenResty先查本地缓存,再查redis,最后查tomcat,反向代理到/item/10003
2、nginx配置
upstream nginx-cluster{
server 192.168.0.44:80;
}
server {
listen 7904;
server_name localhost;
location /api {
proxy_pass http://nginx-cluster;
}
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
页面
3、Openresty配置
#include /etc/nginx/conf.d/*.conf;
#lua 模块
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
#c模块
lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
# 共享字典,也就是本地缓存,名称叫做:item_cache,大小150m
lua_shared_dict item_cache 150m;
server {
listen 80;
server_name 127.0.0.1;
location /item {
# 这里是tomcat的ip和端口
proxy_pass http://192.168.0.55:8081;
}
location ~ /api/item/(\d+) {
# 默认的响应类型
default_type application/json;
# 响应结果有lua/item.lua文件来决定
content_by_lua_file lua/item.lua;
}
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
4、common.lua公共模块
用来封装http,redis等
-- 引入redis模块
local redis = require('resty.redis')
-- 初始化Redis对象
local red = redis:new()
-- 设置redis超时时间
red:set_timeout(1000, 1000, 1000)
-- 关闭redis连接的工具方法,其实是放入连接池
local function close_redis(red)
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.log(ngx.ERR, "放入redis连接池失败: ", err)
end
end
local function read_redis(ip, port, password, db_index, key)
-- 获取一个连接
local ok, err = red:connect(ip, port)
if not ok then
ngx.log(ngx.ERR, "连接redis失败 : ", err)
return nil
end
-- 密码和选择的库
red:auth(password)
red:select(db_index)
-- 查询redis
local resp, err = red:get(key)
-- 查询失败处理
if not resp then
ngx.log(ngx.ERR, "查询Redis失败: ", err, ", key = " , key)
end
--得到的数据为空处理
if resp == ngx.null then
resp = nil
ngx.log(ngx.ERR, "查询Redis数据为空, key = ", key)
end
close_redis(red)
return resp
end
-- 封装函数,发送http请求,并解析响应
local function read_http(path, params)
local resp = ngx.location.capture(path,{
method = ngx.HTTP_GET,
args = params,
})
if not resp then
-- 记录错误信息,返回404
ngx.log(ngx.ERR, "http not found, path: ", path , ", args: ", args)
ngx.exit(404)
end
return resp.body
end
-- 将方法导出
local _M = {
read_http = read_http,
read_redis = read_redis
}
return _M
5、需求 获取请求路径中商品id信息,根据id向redis查询,如果没有向Tomcat查询商品信息
需要修改item.lua,满足下面的需求
- 获取请求参数中的id
- 先查询redis,如果没有向tomcat查询
- 根据id向Tomcat服务发送请求,查询商品信息
- 根据id向Tomcat服务发送请求,查询库存信息
- 组装商品信息,库存信息,序列化为json格式并返回
item.lua如下:
-- 导入common函数库,注意路径/usr/local/openresty/lualib下开始查找,如果有文件夹层级,前面增加文件夹
local common = require('common')
local read_http = common.read_http
-- 导入redis
local read_redis = common.read_redis
-- 导入cjson库
local cjson = require "cjson.safe"
-- 导入共享词典,本地缓存
local item_cache = ngx.shared.item_cache
-- 封装查询函数
function read_data(key, path, params)
-- 查询redis
local resp = read_redis("192.168.0.44", 6379, "redis@edevp.cn", 0, key)
-- 判断查询结果
if not resp then
ngx.log(ngx.ERR, "redis 查询失败,尝试查询http, key: ", key)
-- redis查询失败,去查询http
resp = read_http(path, params)
end
return resp
end
-- 获取路径参数
local id = ngx.var[1]
-- 查询商品信息
--local itemJSON = read_http("/item/" .. id, nil)
local itemJSON = read_data("item:id:" .. id, "/item/" .. id, nil)
-- 查询库存信息
--local stockJSON = read_http("/item/stock/" .. id, nil)
local stockJSON = read_data("item:stock:id:" .. id, "/item/stock/" .. id, nil)
-- json转换为lua的table
local item = cjson.decode(itemJSON)
local stock = cjson.decode(stockJSON)
-- 组合数据
item.stock = stock.stock
item.sold = stock.sold
-- 把item序列化为json 返回结果
ngx.say(cjson.encode(item))
6、需求 查询商品时优先查询OpenResty的本地缓存
- 修改item.lua中的read_data函数,优先查询本地缓存,未命中时在查询Redis、Tomcat
- 查询Redis或Tomcat成功后,将数据写入本地缓存,并设置有效期
- 商品基本信息,有效期30分钟
- 库存信息,有效期1分钟
-- 封装查询函数
function read_data(key, expire, path, params)
-- 优先查询本地缓存
local val = item_cache:get(key)
if not val then
ngx.log(ngx.ERR, "本地缓存查询失败,尝试查询redis, key: ", key)
-- 查询redis
val = read_redis("192.168.0.44", 6379, "redis@edevp.cn", 0, key)
-- 判断查询结果
if not val then
ngx.log(ngx.ERR, "redis 查询失败,尝试查询http, key: ", key)
-- redis查询失败,去查询http
val = read_http(path, params)
end
-- 查询成功,把数据接入本地缓存
item_cache:set(key, val, expire)
end
-- 返回数据
return val
end
...
...
-- 调用
-- 查询商品信息
-- 30分钟
local itemJSON = read_data("item:id:" .. id, 1800, "/item/" .. id, nil)
-- 查询库存信息
-- 60秒
local stockJSON = read_data("item:stock:id:" .. id, 60, "/item/stock/" .. id, nil)
第一次访问查看nginx的error日志,发下如下
三、代码
1、仓库
2、数据库和lua文件