亿级流量的缓存方案-多级缓存-OpenResty实战

一、初始OpenResty

1、介绍

OpenResty是一个机遇Nginx的高性能web平台,用于方便的搭建能过处理超高并发,扩展性极高的动态web应用、Web服务和动态网关。具备下列特点:

  • 具备Nginx的完成总怒
  • 机遇Lua语言进行扩展,继承了大量精良的Lua库、第三方模块
  • 允许使用Lua自定义业务逻辑、自定义库

官方网站:https://openresty.org/cn/

2、安装

docker安装

https://blog.csdn.net/Blueeyedboy521/article/details/125142843

linux安装

https://blog.csdn.net/Blueeyedboy521/article/details/125141844

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、仓库

仓库:https://gitee.com/edevp/item-service

2、数据库和lua文件

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值