一,安装OpenResty
安装步骤:
-
安装开发库
yum install -y pcre-devel openssl-devel gcc --skip-broken -
安装OpenResty仓库
在Linux系统中添加
OpenResty仓库,这样就可以方便未来安装或更新软件包(通过yum check-update命令)yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo如果提示说命令不存在,则先运行
yum install -y yum-utils -
安装OpenResty
yum install -y openresty -
安装opm工具
opm是OpenResty的一个管理工具,可以帮助我们安装一个第三方lua模块。
yum install -y openresty-opm -
目录结构
默认情况下,OpenResty安装目录是
/usr/local/openresty -
配置nginx的环境变量
-
打开配置文件
vi /etc/profile -
在末尾加上
export NGINX_HOME=/usr/local/openresty/nginx export PATH=${NGINX_HOME}/sbin:$PATH -
让配置生效
source /etc/profile
-
三,启动和运行
OpenResty底层是基于Nginx的,查看OpenResty目录的nginx目录,结构与Window中安装的nginx基本一致
所有运行方式与nginx基本一致
#启动nginx
nginx
#重新加载配置
nginx -s reload
#停止
nginx -s stop
四,OpenResty快速入门
我们配置一个Nginx统一接受前端传递的请求,然后发送这个请求到OpenResty集群,将部分转发等逻辑编写到OpenResty集群中。
-
在openresty里面的nginx目录里面的:
nginx.conf的http下面,添加对OpenResty的Lua模块的加载# 加载Lua模块 lua_package_path "/usr/local/openresty/lualib/?.lua;;"; # 加载c模块 lua_package_cpath "/usr/local/openresty/lualib/?.so;;"; -
在
nginx.conf的server下面,添加对/api/item这个路径的监听location /api/item{ #响应类型,这里返回json default_type application/json; #响应数据由lua/item.lua这个文件决定 content_by_lua_file lua/item.lua; } -
在nginx目录下创建lua目录
mkdir lua -
在lua文件夹下创建
item.luatouch lua/item.lua -
编写内容
-- 返回假数据,这里的ngx.say()函数,就是写数据到Response中 ngx.say('{"id":1001,"name":"sala"}') -
重新加载配置
nginx -s reload
五,请求参数处理
六,查询Tomcat
案例:获取请求路径中的商品id信息,根据id向Tomcat查询商品信息
这里要修改item.lua,满足下面的需求:
- 获取请求参数中的id
- 根据id向Tomcat服务发送请求,查询商品信息
- 根据id向Tomcat服务发送请求,查询库存信息
- 组装商品信息,库存信息,序列化为JSON格式并返回
6.1 封装Http查询的函数
Nginx内部发送Http请求:
local resp = ngx.location.capture("/path",{
method = ngx.HTTP_GET, -- 请求方式
args = {a=1,b=2}, -- get方法传参
body = "c=3&d=4" --POST方法传参
})
返回的响应包括:
resp.status:响应的状态码resp.header:响应头resp.body:响应体
注意:这里的path是路径,不包含ip和端口,这个请求会被nginx内部的server监听并处理。
但我们希望这个请求发送到Tomcat服务器,所以还需要编写一个server来对这个路径做反向代理
location /path{
proxy_pass http://192.168.150.1:8081;
}
我们可以把http查询的请求封装为一个函数,放到OpenResty函数库里面,方便后期使用
-
在
/usr/local/openresty/lualib目录下创建common.lua文件vi /usr/local/openresty/lualib/common.lua -
在
common.lua中封装http查询的函数-- 封装函数,发送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 } return _M
6.2 使用封装的Http查询函数
编写item.lua
-- 导入common函数库
local common = require('common')
local read_http=common.read_http
-- 引入cjson处理json的序列化与反序列化
local cjson = require("cjson")
-- 获取路径参数
local id = ngx.var[1]
-- 查询商品信息
local itemJSON = read_http("/item" .. id , nil)
-- 查询库存信息
local stockJSON = read_http("/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.3 根据商品Id对tomcat集群负载均衡
实际过程中,tomcat是集群,我们tomcat集群中存在JVM进程缓存,但是nginx使用的是轮询负载均衡,这样会导致JVM进程缓存的命中率极低,我们需要将针对1001这个查询一直查询指定的一个tomcat服务器,这个时候只需要加上hash $request_uri,他会对1001%tomcat服务器数量,让1001这个id一直访问指定tomcat服务器。
七,Redis缓存预热
上述我们只是完成了对Tomcat集群的查询,但是我们需要优先查询Redis,Redis未命中才查询tomcat
7.1 冷启动与缓存预热
冷启动:服务器刚刚启动时,Redis中并没有缓存,如果所有商品数据都在第一次查询时添加缓存,可能会给数据库带来较大压力
缓存预热:在实际开发中,我们可以利用大数据统计用户访问的热点数据,在项目启动时将这些热点数据提前在查询钱保存到Redis中
7.2 缓存预热实现
-
安装redis
-
在项目中配置redis
-
编写初始化类
@Component public class RedisHandler implements InitalizingBean{ @Autowired private StringRdisTemplate redisTemplate; @Autowired private IItemStockService stockService; private static final ObjectMapper MAPPER=new ObjectMapper(); @Override public void afterPropertiesSet() throw Exception{ //初始化缓存 //1.查询商品信息 List<Item> itemList=itemService.list(); //2.放入缓存 for(Item item : itemList){ //2.1 item序列化为JSON String json =MAPPER.writeValueAsString(item); //2.2 存入redis redisTemplate.opsForValue().set("item:id:"+item.getId(),json); } //3. 查询商品库存信息 List<Stock> itemList=stockService.list(); //4. 放入缓存 for(ItemStock stock : stockList){ //2.1 item序列化为JSON String json =MAPPER.writeValueAsString(stock); //2.2 存入redis redisTemplate.opsForValue().set("item:stock:id:"+stock.getId(),json); } } }
八,查询Redis缓存
OpenResty提供了操作Redis的模块,我们只需要引入该模块就能直接使用:
-
引入Redis模块,并初始化Redis对象
-- 引入redis模块 local redis = require("resty.redis") -- 初始化redis对象 local red=redis:new() -- 设置Redis超时时间 red:set_timeouts(1000,1000,1000) -
封装函数,用来释放Redis链接,其实是放入连接池
-- 关闭Redis连接的工具方法,其实是放入连接池 local function close_redis(red) local pool_max_idle_time = 1000 -- 连接的空闲时间,单位是毫秒 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 -
封装函数,从Redis读取数据并返回
-- 查询redis的方法,ip和port是redis地址,key是查询的key local function read_redis(ip,port,key) -- 获取一个连接 local ok.err = red:connect(ip,port) if not ok then ngx.log(ngx.ERR,"连接redis失败",err) return nill end -- 查询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 -
修改
item.lua,封装一个函数read_data,实现先查询Redis,如果未命中,在查询tomcat -
修改
item.lua,查询商品和库存时都调用read_data这个函数-- 导入common函数库 local common = require('common') local read_http=common.read_http local read_redis=common.read_redis -- 引入cjson处理json的序列化与反序列化 local cjson = require("cjson") -- 获取路径参数 local id = ngx.var[1] -- 封装函数,先查询redis,未命中再查询http local function read_data(key,path,params) -- 查询redis local resp = read_redis("127.0.0.1",6379,key) -- 判断redis是否命中 if not resp then -- Redis查询失败,查询http resp = read_http(path,params) end return resp end -- 查询商品信息 local itemJSON = read_data("/item" .. id , nil) -- 查询库存信息 local stockJSON = read_data("/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))
九,Nginx本地缓存
OpenResty为Nginx提供了shard dict的功能,可以在Nginx的多个worker之间共享数据,实现缓存功能
-
开启共享字典,在nginx.conf的http下添加
# 开启共享字段,也就是本地缓存,名字叫item_cache,大小为150m lua_shared_dict item_cache 150m; -
操作共享字典
-- 获取本地缓存对象 local item_cache = ngx.shared.item_cache -- 存储,指定key,value,过期时间,单位s,默认为0代表永不过期 item_cache:set('key','value',1000) -- 读取 local val = item_cache:get('key')
需求:
- 修改
item.lua中的read_data函数,有限查询本地缓存,未命中再查询Redis,Tomcat - 查询Redis或者Tomcat成功后,将数据写入本地缓存,并设置有效期
- 商品基本信息,有效期30分钟
- 库存信息,有效期1分钟
-- 导入common函数库
local common = require('common')
local read_http=common.read_http
local read_redis=common.read_redis
-- 引入cjson处理json的序列化与反序列化
local cjson = require("cjson")
-- 获取路径参数
local id = ngx.var[1]
-- 封装函数
local 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("127.0.0.1",6379,key)
-- 判断redis是否命中
if not val then
-- Redis查询失败,查询http
val = read_http(path,params)
end
end
-- 查询成功,把数据写入本地缓存
item_cache:set(key,val,expire)
return resp
end
-- 查询商品信息
local itemJSON = read_data("/item" .. id ,1800, nil)
-- 查询库存信息
local stockJSON = read_data("/stock" .. id , 60,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))