OpenResty是一个基于Nginx的高性能Web平台,用于方便地搭建能够处理高并发、扩展性极高的动态Web应用、Web服务和动态网关。具备下列特点:
官方网站:https://openresty.org/cn/
略
商品详情页面目前展示的是假数据,在浏览器的控制台可以看到查询商品信息的请求:
Request URL:http://localhost/api/item.10001
Request Method:GET
Status Code:502 Bad Gateway
Remote Address:127.0.0.1:80
Referrer Policy:strict-orgin-when-cross-origin
而这个请求最终被反向代理到虚拟机的OpenResty集群:
upstream nginx-cluster{
server 192.168.150.101:8081; #nginx-cluster集群,就是将来的nginx业务集群
}
server {
listen 80;
server_name localhost;
location /api {
proxy_pass http://nginx-cluster; #监听/api路径,反向代理到nginx-cluster集群
}
}
需求:在OpenResty中接收这个请求,并返回一段商品的假数据。
1.在nginx.conf的http下面,添加对OpenResty的Lua模块的加载:
# 加载lua模块
lua_package_path lua_package_path "/usr/local/openresty/lualib/?.lua;;"
# 加载c模块
lua_package)cpath "/usr/local/openresty/lualib/?.lua;;"
2.在nginx.conf的server下面,添加对/api/item这个路径的监听
location /api/item {
# 默认的响应类型-*
default_type application/json;
# 响应结果由 lua/item.lua 这个文件来决定
content_by_lua_file lua/item/lua;
}
1.在nginx目录创建文件夹:lua
nginx]# pwd
/usr/local/openresty/nginx
nginx]#mkdir lua
2.在lua文件夹下,新建文件:item.lua
nginx]#pwd
/usr/local/openresty/nginx
nginx]#touch lua/item/lua
3.内容如下:
-- 返回假数据,这里的ngx.say()函数,就是写数据到Response中
ngx.say('{"id":1001,"name":"SALSA AIR"}')
4.重新加载配置
nginx -s reload
OpenResty提供了各种API用来获取不同类型的请求参数:
参数格式 | 参数示例 | 参数解析代码示例 |
---|---|---|
路径占位符 | /item/1001 | # 1.正则表达式匹配: location ~ /item/(\d+) {content_by_lua_file lua/item.lua;} --2、匹配到的参数会存入ngx、var数组中 – 可以用角标获取 local id = ngx.var[1] |
请求头 | id:1001 | –获取请求头,返回值是table类型 local headers = ngx.req.get_headers() |
Get请求参数 | ?id=1001 | –获取GET请求参数,返回值是table类型 local getParams= ngx.req.get_uri_args() |
Post表单参数 | id=1001 | –读取请求头 ngx.req.read_body() – 获取POST表单参数,返回值是table类型 local postParams = ngx.req.get_post_args() |
JSON参数 | {“id”:1001} | – 读取请求头 ngx.req.read_body() – 获取bogy中的json参数,返回值是string类型 local jsonBody = ngx.req.get_body_data() |
这里要修改item.lua,满足下面的需求:
1.获取请求参数中的id
2.根据id向Tomcat服务发送请求,查询商品信息
3.根据id向Tomcat服务发送请求,查询库存信息
4.组装商品信息、库存信息,序列化为JSON格式并返回
nginx提供了内部API用以发送http请求:
local resp = ngx.location.capture("/path",{
method = ngx.HTTP_GET, -- 请求方式
args = {a=1,b=2}, -- get方式传参数
body = "c=3&d=4" -- post方式传参数
})
返回的响应内容包括:
注意:这里的path是路径,并不包含IP和端口。这个请求会被nginx内部的server监听并处理。
但是我们希望这个请求发送到Tomcat服务器,所以还需要编写一个server来对这个路径做反向代理:
location /path {
# 这里是windows电脑的ip和java服务端口,需要确保Windows防火墙处于关闭状态
proxy_pass http://192.168.150.1:8081;
}
我们可以把http查询的请求封装为一个函数,放到OpenResty函数库中,方便后期使用。
1.在/usr/local/openresty/lualib目录下创建common.lua文件:
vi /usr/local/openresty/lualib/common.lua
2.在common.lua中封装http查询的函数
-- 封装函数,发送http请求,并解析响应
local function read_http(path, params)
local resp = ngx.location.capture(pathm{
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
刚才已经把http查询的请求封装为一个函数,放到OpenResty函数库中,接下来就可以使用这个库了。
-- 引入自定义工具模块
local common = require("common")
local read_http = common.read_http
-- 获取路径参数
local id = ngx.var[1]
-- 根据id查询商品
local itemJSON = read_http("/item/" .. id,nil)
-- 根据id查询商品库存
local itemStockJSON = read_http("/item/stock/" .. id,nil)
查询到的是商品、库存的json格式数据,我们需要将两部分数据组装,需要用到JSON处理函数库。
OpenResty提供了一个cjson的模块用来处理JSON序列化和反序列化。
官方地址:https://github.com/openresty/lua-cjson/
local cjson = require "cjson"
local obj = {
name = 'jack'
age = 21
}
local json = cjson.encode(obj)
反序列化:
local json = '{"name": "jack", "age": 21}'
-- 反序列化
local obj = cjson.decode(json);
print(obj.name)
item.lua
-- 导入common函数库
local common = require('common')
local read_http = common.read_http
-- 导入cjson库
local cjson = require('cjson')
-- 获取路径参数
local id = ngx.var[1]
-- 查询商品信息
local itemJSON = read_http("/item/" .. id,nil)
-- 查询库存信息
local stockJSON = read_http("/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))
# 反向代理配置,将/item路径的请求处理到tomcat集群
location /item {
proxy_pass http://tomcat-cluster}
# tomcat集群配置
upstream tomcat-cluster{
hash $request_uri;
server 192.168.150.1:8081;
server 192.168.150.1:8082;
}
冷启动:服务刚刚启动时,Redis中并没有缓存,如果所有商品数据都在第一次查询时添加缓存,可能会给数据库带来较大压力
缓存预热:在实际开发中,可以利用大数据统计用户访问的热点数据,在项目启动时将这些热点数据提前查询并保存在Redis中。
我们数据量较少,可以在启动时将所有数据都放入缓存中。
1.利用Docker安装Redis
docker run --name redis -p 6379:6379 -d redis redis=server --appendonly yes
2.在item-service服务中引入Redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.配置Redis地址
spring:
redis:
host: 192.168.150.101
4.编写初始化类
@Component
public class RedisHandle implements InitializingBean {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private IItemService itemService;
@Autowired
private IItemStockService stockService;
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public void afterPropertiesSet() throws Exception {
// 初始化缓存
//1.查询商品信息
List<Item> itemList = itemService.list();
//2.放入缓存
for(Item item:itemList) {
//2.1item序列化为JSON
String json = MAPPER.writeValueAsString(item);
//2.2存入redis
redisTemplate.opsForValue().set("item:id:" + item.getId(), json);
}
//3.查询商品库存信息
List<ItemStock> stockList = stockService.list();
//4.放入缓存
for (ItemStock stock : stockList) {
//2.1item序列化为JSON
String json = MAPPER.writeValueAsString(stock);
//2.2存入redis
redisTemplate.opsForValue().set("item:stock:id:" + stock.getId(), json);
}
}
}
OpenResty提供了操作Redis的模块,我们只要引入该模块就能直接使用:
-- 引入redis模块
local redis = require("resty.redis")
-- 初始化Redis对象
local red = redis:new()
-- 设置Redis超时时间
red:set_timeouts(1000,1000,1000)
-- 关闭redis连接的工具方法,其实是放入连接池
local function close_redis(red)
local pool_max_idle_time = 10000 -- 连接的空闲时间,单位是毫秒
local pool_size = 100 -- 连接池大小
local ok, err = redis:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.log(ngx.ERR,"放入Redis连接池失败:",err)
end
end
OpenResty提供了操作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 nil
end
-- 查询redis
local resp, err = redis:get(key)
-- 查询失败处理
if not resp then
ngx.log(ngx.ERR, "查询Redis失败:",err,", key = ",key)
end
-- 得到的数据为空处理
if resp == ngx.null then
resp = null
ngx.log(ngx.ERR,"查询Redis数据为空,key = ", key)
end
close_redis(red)
return resp
end
需求:
-- 封装函数,先查询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, parms)
end
return resp
end
OpenResty为Nginx提供了shard dict的功能,可以在nginx的多个worker之间共享数据,实现缓存功能。
# 共享字典,也就是本地缓存,名称叫做: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')
需求:
-- 导入common函数库
local common = require('common')
local read_http = common.read_http
local read_redis = common.read_redis
-- 导入cjson库
local cjson = require('cjson')
-- 导入共享词典,本地缓存
local item_cache = ngx.shared.item_cache
-- 封装函数
local function read_data(key,expire,path,params)
-- 查询本地缓存
local val = item_cahche:get(key)
if not val then
ngx.log("本地缓存查询失败,尝试查询Redis,key:",key)
-- 查询redis
var= read_redis("127.0.0.1",6379,key)
-- 判断查询结果
if not val then
ngx.log("redis查询失败,尝试查询http,key:",key)
-- redis查询失败,查询http
val = read_http(path, parms)
end
end
-- 查询成功,把数据写入到本地缓存
item_cache:set(key, val, expire)
-- 返回数据
return val
end
-- 获取路径参数
local id = ngx.var[1]
-- 查询商品信息
local itemJSON = read_data("item:id:" .. id,1800, "/item/" .. id,nil)
-- 查询库存信息
local stockJSON = read_data("item:stock:id:" .. id,60,"/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))