柏竹 柏竹
首页
后端
前端
  • 应用推荐
关于
友链
  • 分类
  • 标签
  • 归档

柏竹

奋斗柏竹
首页
后端
前端
  • 应用推荐
关于
友链
  • 分类
  • 标签
  • 归档
  • Java基础

  • JavaWeb

  • 拓展技术

    • Java设计模式
    • Java序列化
    • Stream流
    • Lombok简化开发应用
    • JavaZip解压缩
    • Lua应用
    • OpenResty
      • 概述
      • 应用
        • 安装
        • 配置
        • 启用
        • 简单应用
      • 请求参数处理
      • 请求发送
      • JOSN转Table
      • Redis通信
      • Nginx本地缓存
      • Lua实现完整应用
    • C++快速上手
    • PHP快速上手
    • SpEL表达式
    • velocity模板引擎
    • Flyway数据库版本管理工具
    • 企业项目实战问题
  • 框架技术

  • 数据库

  • 数据结构

  • Spring

  • SpringMVC

  • SpringBoot

  • SpringClound

  • Ruoyi-Vue-Plus

  • 后端
  • 拓展技术
柏竹
2023-04-03
目录

OpenResty

# 概述

OpenResty 是一个基于 Nginx 和 Lua 的 Web 应用服务器 , 集成了 LuaJIT 编程环境 , 可以通过 Lua 语言来扩展 Nginx 的功能 , 在Nginx中实现简单业务 , 从而提高 性能 和 并发抗压能力 !!

了解Lua语法 : 传送门跳转

# 应用

# 安装

安装开发者库

yum install -y pcre-devel openssl-devel gcc --skip-broken

安装OpenResty仓库

在Linux系统中 添加 openresty 仓库

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

# 配置

配置环境变量

进入 vim /etc/profile 文件 , 在最后行添加以下两行语句

export NGINX_HOME=/usr/local/openresty/nginx
export PATH=${NGINX_HOME}/sbin:$PATH

NGINX_HOME:后面是OpenResty安装目录下的nginx的目录

生效配置

source /etc/profile

Nginx配置

进入 vim /usr/local/openresty/nginx/conf/nginx.conf 文件 , 更改配置 (官方注释比较多 , 可直接覆盖以下代码)

#user  nobody;
worker_processes  1;
error_log  logs/error.log;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    
    #lua 模块
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";
    #c模块
    lua_package_cpath "/usr/local/openresty/lualib/?.so;;";

    server {
        listen       8081;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

提示

  • 开放进入的端口是 8081

  • 模块代码 , 有些代码需要依赖 .

    # lua模块
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";
    # c模块
    lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
    

# 启用

# 启动nginx
nginx
# 重新加载配置
nginx -s reload
# 停止
nginx -s stop

访问 http://<Ip>:<端口> 即可

提示

防火墙/安全组 端口保持开放!

# 简单应用

在 openresty 根目录中的 nginx里配置 nginx.conf 文件 , 以下是简单实例 模拟 lua脚本响应 :

location /example {
    # 以下是 lua代码 注释可能不一样
    content_by_lua_block {
        -- 异常打印 , ngx.var.args变量 包含请求URI中的查询参数
        ngx.log(ngx.ERR, "query parameters: ", ngx.var.args)

        -- 响应客户端
        ngx.header.content_type = "application/json"
        ngx.say('{"msg": "Hello, OpenResty!"}')

        -- 检查异常并处理
        local status = ngx.status
        -- ngx.HTTP_BAD_REQUEST 400 以上的为错误码
        if status >= ngx.HTTP_BAD_REQUEST then
            ngx.exit(status)
        end
    }
}

访问 http://<Ip>:<端口>/example 即可

# 请求参数处理

OpenResty提供了各种API进行获取不同类型的请求参数 :

参数形式 参数示例
路径占位符 localhst/item/1001
请求头 请求头 Authorization : token
Get请求参数 localhst?id=1001
Post请求参数 表单 id = 1001
Json请求参数 {"id":1001}

路径占位符

# nginx正则配置 1. 正则表达式匹配获取
location ~ /item/(\d+) {
    content_by_lua_file lua/item.lua
} 
# lua脚本获取 2. 匹配参数存到 数组中
local id = ngx.var[1];

请求头

--- lua脚本 , 获取 请求头信息 , table类型 
local headers = ngx.req.get_headers();

Get请求参数

-- lua脚本 , 获取 Get请求参数 , table类型
local getParams = ngx.req.get_uri_args();

Post请求参数

-- lua脚本 , 获取 Post请求参数 , table类型
local postParams = ngx.req.get_post_args();

Json请求参数

-- lua脚本 , 读取请求体
ngx.req.read_body();
-- lua脚本 , 获取 Jons请求参数 , string类型
local jsonBody = ngx.req.get_body_data();

# 请求发送

Ngxin提供的内部API进行发送HTTP请求

请求结构 : (捕获请求)

local resp = ngx.location.capture("/path",{
    -- 请求方式
	method = ngx.HTTP_GET,
	-- get请求参数
    args = {a=1,b=2},
	-- post请求参数
    body = "c=3&d=4"
})
--[[ resp 结构 , table类型 , 分别包含 状态码;响应头;响应体
resp : {
	status,
	header,
	body
}
]]--

提示

/path 是不包含IP和端口 , 请求被内部nginx的server监听并处理 . 可通过反向代理实现即可!!!

# 反向代理
location /path{
    proxy_pass http://<ip>:<port>;
}

封装发送请求

在 nginx 加载lua模块路径 , 创建 common.lua文件

/usr/local/openresty/lualib/common.lua

-- 封装函数,发送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

加载缘故

OpenResty的lua模块加载代码 (前面的nginx也配置有)

#lua 模块
lua_package_path "/usr/local/openresty/lualib/?.lua;;";

应用示例 :

应用示例 点击展开
--[[
请求入口 : (反向代理)
/item/{id}
/item/stock/{id}
]]--

-- 导入 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);

-- 反序列化 转table(map)
local stock = cjson.decode(stockJSON);
local item = cjson.decode(itemJSON);

-- 组合数据
item.stock = stock.stock;
item.sold = stock.sold;

-- 序列化 转json
ngx.say(cjson.encode(item));

反向代理配置

# 请求接受反向代理出口
location /item {
    # 个人应用测试的 Mock远端测试
    proxy_pass https://mock.apifox.cn/m1/2492136-0-default/item;
}

location ~ /api/item/(\d+) {
    default_type application/json;
    # 该响应结果由 lua/item.lua 文件决定
    content_by_lua_file lua/item.lua;

    # 捕获Lua错误并将其记录到OpenResty错误日志中
    log_by_lua_block {
	    ngx.log(ngx.ERR, "Lua error: ", tostring(ngx.var.lua_error));
    }
}

Nginx内部运作过程

外部请求(https:xxx.cn/api/item/{id}) -> lua脚本请求(/item) -> toncat接收

# JOSN转Table

OpenResty 提供 cjson的模块用来处理 JONS的序列化/反序列化 功能

官网 : https://github.com/openresty/lua-cjson/ (opens new window)

引入模块

local cjson = require("cjson");

序列化

local obj = {
    name = "sans",
    age = 20
}
-- 序列化 , string类型
local json = cjson.enchod(obj);

反序列化

local json = "{'name':'Sans', 'age':20}"
-- 反序列化 , table 类型
local obj = cjson.decode(json);
print(obj.name);
print(obj.age);

# Redis通信

OpenResty 提供 Redis模块 里面包含有很多API , 可直接使用

引入模块

-- 导入 redis模块
local reids = require("resty.redis");
--- 初始化Redis对象
local red = redis.new();
--- 超时时间
red:set_timeouts(1000, 1000, 1000);

封装 Redis

在 nginx 加载lua模块路径 , 创建 common.lua文件

/usr/local/openresty/lualib/common.lua

Redis封装 点击展开
-- 关闭redis连接的工具方法 , 其实是放入连接池
local function close_redis(red)
    -- 连接空闲时间 , 单位ms
    local pool_max_idle_time = 10000;
    --连接池大小
    local pool_size = 100;
    -- 将red放入连接池中
    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 (这三个参数都不陌生) 
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 authRes, err = red:auth("123123")
    if not authRes then
            ngx.say("认证失败 , 密码错误: ", err)
        return
    end

    -- 选择第二个数据库
    local selectRes, err = red:select(1)
        if not selectRes then
            ngx.say("无法选择该库连接 : ", err)
        return
    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

Redis应用

Redis应用 点击展开
-- 导入 common模块
local common = require('common');
local read_http = common.read_http;
local read_redis = common.read_redis;
-- 导入 cjson模块
local cjson = require("cjson");

-- 封装查询参数
function read_data(key, path, params)
    -- 查询redis
    local resp = read_redis("127.0.0.1", 6379, key);
    if not resp then
        ngx.log(ngx.ERR, "redis未查到 , 尝试http请求");
        resp = read_http(path, params);
    end
    return resp;
end

-- 获取参数
local id = ngx.var[1];

-- 查询缓存
local itemJSON = read_data("item:" .. id, "/item/" .. id, nil);

...

# Nginx本地缓存

OpenResty 为Nginx 提供了 共享词典(shard dict) 的功能 , 可在多个不同请求之间共享字典数据 , 从而实现 缓存/节流 等场景

启动共享字典

在 Nginx配置 中通过 lua_shared_dict指令 创建共享字典

http {
	# 创建 共享字典,也就是本地缓存 , 
    # 字典名称 : item_cache , 大小150m(150MB)
	lua_shared_dict item_cache 150m;
}

读写共享字典

在 Lua脚本 中通过 ngx.shared.<字典名称>模块 读写共享字典

-- 获取本地 字典对象
local item_cache = ngx.shared.item_cache;
-- 存储 , 指定 key,value,过期时间(单位s)0代表永久
item_cache:set('key','value', 1000);
-- 读取
local val = item_cache.get('key');

提示

共享字典的缓存数据一定一定要设置过期时间 , 根据业务情况设置时长

共享内存池

在 Nginx配置 可将多个字典中的内存共享在一个字典中 , 通过 lua_shared_dict_zone实现 将多个字典绑定在同一个共享内存池中

http {
    lua_shared_dict dict1 10m;
    lua_shared_dict dict2 20m;

    lua_shared_dict my_pool 30m;
    lua_shared_dict_zone my_pool {
        dict1 10m;
        dict2 20m;
    }
}

计数器

在 Lua中 通过 incr和decr方法控制 增加/减少 指定键的值

# var +1 
incr(var , 1);
# var -1
decr(var , 1);

直接上应用场景比较好理解 , 请求限流 , 指定限制流量1000次流量

http {
    # 创建字典 req_count_dict
    lua_shared_dict req_count_dict 10m;

    server {
        location /api {
            # 设置该变量 $req_count 自增1
            set $req_count $lua_shared_dict:req_count_dict:incr($uri, 1);
            if ($req_count > 1000) {
                # 返回 429状态码 表示限流
                return 429;
            }
            ...
        }
    }
}

关键代码

set $req_count $lua_shared_dict:req_count_dict:incr($uri, 1);

先定义了一个 $req_count 变量 , 然后使用 lua_shared_dict指令 访问字典 req_count_dict , 使用 incr方法 对当前请求的$uri对应的计数器加 1 , 并将结果赋值给 $req_count 变量

# Lua实现完整应用

通过 实现多级缓存 OpenResty

架构图 :

多层缓存 , 分别说明缓存层级 : (可以根据情况优化)

  1. OpenResty Nginx 字典 本地缓存
  2. Redis缓存
  3. JVM缓存 (集群需要依赖 负载均衡的 Hash分配策略)

笔记

以上实现目的是为了突破 Tomcat 接收压力瓶颈问题 , 从而选举优化方案!

Nginx配置

nginx.conf 点击展开
#user  nobody;
worker_processes  1;
error_log  logs/error.log;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    #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       8081;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }

        # 请求响应反向代理出口 
        # 以下我采用内网穿透测试 , 一般情况采用 集群形式
        location /item {
            proxy_pass http://bozhu.freehk.svipss.top;
        }

        location ~ /api/item/(\d+) {
            default_type application/json;
            # 该响应结果由 lua/item.lua 文件决定
            content_by_lua_file lua/item.lua;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

Lua脚本

item.lua 点击展开
-- 导入 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;

-- 封装查询参数
function read_data(key, expire, path, params)
    -- 先从字典查询本地缓存
    local resp = item_cache:get(key);
    if not resp then
        ngx.log(ngx.ERR, "Nginx 缓存未查到 , 尝试 Redis");
        -- redis查询
        resp = read_redis("127.0.0.1", 6379, key);
        if not resp then
            ngx.log(ngx.ERR, "Redis 未查到 , 尝试 HTTP");
            -- http请求
            resp = read_http(path, params);
        end
    end
    -- 写入本地缓存
    item_cache:set(key, resp, expire);
    return resp;
end

-- 获取参数
local id = ngx.var[1]; 

-- 查询缓存 (本地缓存30min)
local itemJSON = read_data("item:" .. id, 1800,"/item/" .. id, nil);
-- 查询库存 (本地缓存 1min)
local stockJSON = read_data("item:stock:" .. id, 60, "/item/stock/" .. id, nil);

-- 反序列化 转table(map)
local stock = cjson.decode(stockJSON);
local item = cjson.decode(itemJSON);
-- 组合数据
item.stock = stock.stock;
item.sold = stock.sold;
-- 序列化 转json
ngx.say(cjson.encode(item));

封装工具模块

common.lua 点击展开
-- 导入 redis模块
local redis = require("resty.redis");
--- 初始化Redis对象
local red = redis.new();
--- 超时时间
red:set_timeouts(1000, 1000, 1000);

-- 关闭redis连接的工具方法 , 其实是放入连接池
local function close_redis(red)
    -- 连接空闲时间 , 单位ms
    local pool_max_idle_time = 10000;
    --连接池大小
    local pool_size = 100;
    -- 将red放入连接池中
    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 (这三个参数都不陌生) 
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 authRes, err = red:auth("panzer")
    if not authRes then
            ngx.say("Redis 认证失败: ", err)
        return
    end

    -- 选择第二个数据库
    local selectRes, err = red:select(1)
        if not selectRes then
            ngx.say("Redis [1]库不存在", err)
        return
    end

    -- 查询redis
    local resp, err = red:get(key)
    -- 查询失败处理
    if not resp then
        ngx.log(ngx.ERR, "Redis key不存在 , kye: ", key, err)
    end
    --得到的数据为空处理
    if resp == ngx.null then
        resp = nil
        ngx.log(ngx.ERR, "Redis val为nil , 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 查不到 , path: ", path , " , args: ", args)
        ngx.exit(404)
    end
    return resp.body
end
-- 将方法导出
local _M = {  
    read_http = read_http,
    read_redis = read_redis
}
return _M
#OpenResty#Nginx

← Lua应用 C++快速上手→

最近更新
01
HTTPS自动续签
10-21
02
博客搭建-简化版(脚本)
10-20
03
ruoyi-vue-plus-部署篇
07-13
更多文章>
Theme by Vdoing | Copyright © 2019-2024 | 桂ICP备2022009417号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式