监听yaml配置
接下来我们试试从yaml文件中读取配置,当然这不是动态配置分发的好方式
别急,一口吃不成大胖子
这里其实会为大家介绍不少东西:
- 如何引入第三方库以及配置openresty
- lua 文件读取
- yaml 库
- openresty init_worker
- openresty timer
- privileged agent
- 代码触发 openresty 热重启
创建yaml配置文件
手动创建conf.yaml文件
$ touch conf.yaml
以下内容写入 conf.yaml
upstream:
a8699:
host: '127.0.0.1'
port: 6699
非常简单地一个upstream配置, 如果大家配置都是这样简单,是不是nginx用起来会舒服(不过现实总是那么不简单)
接下来我们来实现如何读取它呢
引入yaml库
我们将使用 luarocks 这个lua包管理工具,其介绍在luarocks介绍,这里不再赘述,如不了解请一定要查阅。
首先我们手动创建luarocks文件
$ touch openresty-dev-1.rockspec
以下内容写入 openresty-dev-1.rockspec
package = "openresty"
version = "dev-1"
-- 自己编写第三方库时,如果希望别人远端仓库下载当前源码编译可以配置 source ,比如下git的配置
source = {
url = "git+ssh://git@github.com:fs7744/nature.git",
branch = "main",
}
-- 包描述
description = {
homepage = "https://github.com/fs7744/nature",
maintainer = "Victor.X.Qu"
}
-- 依赖包
dependencies = {
"lua-tinyyaml >= 1.0",
}
-- 当前包如何编译,这里列举openresty常遇见的一些参数
build = {
type = "make",
build_variables = {
CFLAGS="$(CFLAGS)",
LIBFLAG="$(LIBFLAG)",
LUA_LIBDIR="$(LUA_LIBDIR)",
LUA_BINDIR="$(LUA_BINDIR)",
LUA_INCDIR="$(LUA_INCDIR)",
LUA="$(LUA)",
OPENSSL_INCDIR="$(OPENSSL_INCDIR)",
OPENSSL_LIBDIR="$(OPENSSL_LIBDIR)",
},
install_variables = {
INST_PREFIX="$(PREFIX)",
INST_BINDIR="$(BINDIR)",
INST_LIBDIR="$(LIBDIR)",
INST_LUADIR="$(LUADIR)",
INST_CONFDIR="$(CONFDIR)",
},
}
这里不使用 luarocks init
是因为其默认项目结构包含了太多我们不需要的东西,为了不干扰大家,就说明了。
为了方便大家测试,我们将yaml 依赖包安装到当前目录下
$ luarocks install openresty-dev-1.rockspec --tree=deps --only-deps
# 得到以下执行结果
Missing dependencies for openresty dev-1:
lua-tinyyaml >= 1.0 (not installed)
openresty dev-1 depends on lua-tinyyaml >= 1.0 (not installed)
Installing https://luarocks.org/lua-tinyyaml-1.0-0.rockspec
Cloning into 'lua-tinyyaml'...
remote: Enumerating objects: 13, done.
remote: Counting objects: 100% (13/13), done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 13 (delta 0), reused 8 (delta 0), pack-reused 0
Unpacking objects: 100% (13/13), 10.32 KiB | 330.00 KiB/s, done.
Note: switching to 'b130c2b375d4560d5b812e1a9c3a60b2a0338d65'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
No existing manifest. Attempting to rebuild...
lua-tinyyaml 1.0-0 is now installed in /root/openresty-test/deps (license: MIT License)
Stopping after installing dependencies for openresty dev-1
# 查看目录可以看到完整的包结构
$ tree deps/
deps/
├── lib
│ └── luarocks
│ └── rocks-5.1
│ ├── lua-tinyyaml
│ │ └── 1.0-0
│ │ ├── doc
│ │ │ ├── LICENSE
│ │ │ └── README.md
│ │ ├── lua-tinyyaml-1.0-0.rockspec
│ │ └── rock_manifest
│ └── manifest
└── share
└── lua
└── 5.1
└── tinyyaml.lua
在openresty中引入当前目录依赖包
大家可以基于上一节中的配置文件修改,(这来不列举完整的内容,避免篇幅过长)
在其中添加如下配置
# 在 stream 下面添加
stream {
# $prefix 为 启动openresty 时的参数 -p prefix
# 下面比较长的路径是将各种情况列举处理,基本满足了一般liunx环境下依赖包搜索的大部分可能性
lua_package_path "$prefix/deps/share/lua/5.1/?.lua;$prefix/deps/share/lua/5.1/?/init.lua;$prefix/?.lua;$prefix/?/init.lua;;./?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/openresty/luajit/share/lua/5.1/?.lua;/usr/local/openresty/luajit/share/lua/5.1/?/init.lua;";
lua_package_cpath "$prefix/deps/lib64/lua/5.1/?.so;$prefix/deps/lib/lua/5.1/?.so;;./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/openresty/luajit/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so;";
# 开启 lua code 缓存
lua_code_cache on;
}
在init 时机读取yaml配置
stream {
init_by_lua_block {
-- 文件读取比较长,这里用函数避免影响大家理解
local function read_all(path)
local open = io.open
local close = io.close
local file, data, err
file, err = open(path, "rb")
if not file then
return nil, "open: " .. path .. " with error: " .. err
end
data, err = file:read("*all")
if err ~= nil then
file:close()
return nil, "read: " .. path .. " with error: " .. err
end
file:close()
return data
end
local yaml_path = ngx.config.prefix()..'/conf.yaml'
-- 读取 yaml 文件
local content, err = read_all(yaml_path)
-- 转换yaml数据为lua table结构
local yaml = require("tinyyaml")
local conf = yaml.parse(content)
-- 设置到全局变量中
upstreams = conf.upstream
}
server {
preread_by_lua_block {
-- 这里故意加了个a前缀以区分之前的测试代码
local upstream = upstreams['a'..tostring(ngx.var.server_port)]
}
}
}
启动服务并测试,与之前保持一样效果
$ openresty -p ~/openresty-test -c openresty.conf #启动
$ curl http://localhost:8699 -i #测试
HTTP/1.1 200 OK
Date: Fri, 16 Dec 2022 05:19:34 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
HelloWorld
$ curl http://localhost:8689 -i #测试没有upstrem的情况
curl: (56) Recv failure: Connection reset by peer
如何监控yaml文件变动呢?
大家都知道我们不可能像指挥家里小朋友帮我们打酱油一样随意指挥系统在文件变化时通知我们的程序。
不过我们可以安排我们的程序定时检查文件是否被修改了
定时任务api
openresty 里面 可以做定时任务的api 为 ngx.timer.every
我们试一试
stream {
init_by_lua_block {
ngx.timer.every(1, function()
ngx.log(ngx.ERR, 'hello ngx.timer.every')
end)
}
启动服务并测试
$ openresty -p ~/openresty-test -c openresty.conf -g 'daemon off;' #启动
nginx: [error] init_by_lua error: init_by_lua:31: no request
stack traceback:
[C]: in function 'every'
init_by_lua:31: in main chunk
# 很不幸,报错了
为什么呢?
其实大家在查阅 openresty api 文档时,总是会看到有很长一个 context 列表,里面全是一些执行阶段
ngx.timer.every
syntax: hdl, err = ngx.timer.every(delay, callback, user_arg1, user_arg2, …)
context: init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*
Similar to the ngx.timer.at
API function, but
…..
这是openresty以及nginx设计带来的限制,只允许在对应执行阶段运行相应的api
简单来说:
为了高性能,openresty 利用nginx的事件通知机制 + 协程 避免了自身执行的阻塞,
但是大家写代码总是喜欢同步阻塞的思路,为了减少你等我,我等你等死锁情况以及nginx在一些宝贵的执行阶段对资源、时间非常“小气”,所以限制了很多api的使用
比如 init 阶段, nginx的事件通知机制并未完全建立,所以很多api无法使用
总之这些限制出于性能与安全的考虑。(详细可以参考openresty)
我们换到 init_worker
stream {
init_worker_by_lua_block {
ngx.timer.every(1, function()
ngx.log(ngx.ERR, 'hello ngx.timer.every')
end)
}
启动服务
$ openresty -p ~/openresty-test -c openresty.conf #启动
$ cat logs/error.log
# 可以看到每秒都有两个执行log,这由于我们启动了2个worker,所以init_worker_by_lua_block被执行了两次
2022/12/17 15:55:45 [error] 2784#2784: *19 stream [lua] init_worker_by_lua:3: hello ngx.timer.every, context: ngx.timer
2022/12/17 15:55:45 [error] 2785#2785: *20 stream [lua] init_worker_by_lua:3: hello ngx.timer.every, context: ngx.timer
2022/12/17 15:55:46 [error] 2784#2784: *21 stream [lua] init_worker_by_lua:3: hello ngx.timer.every, context: ngx.timer
2022/12/17 15:55:46 [error] 2785#2785: *22 stream [lua] init_worker_by_lua:3: hello ngx.timer.every, context: ngx.timer
如果我们想只有一个 worker 处理,我们也可以通过 判断ngx.worker.id()
实现,
不过更加推荐以下的特权进程方式
特权进程
特权进程 是为了不影响 worker 处理用户真实请求,而大家总有额外处理其他东西的需求,比如我们这里的监听文件变化,不需要每个worker都监听
特权进程很特别:
它不监听任何端口,这就意味着不会对外提供任何服务;
它拥有和 master 进程一样的权限,一般来说是 root 用户的权限,这就让它可以做很多 worker 进程不可能完成的任务;
特权进程只能在 init_by_lua 上下文中开启;
我们改动一下代码
stream {
init_by_lua_block {
-- 开启特权进程
local process = require("ngx.process")
local ok, err = process.enable_privileged_agent()
if not ok then
log.error("failed to enable privileged_agent: ", err)
end
}
init_worker_by_lua_block {
-- 限制只允许特权进程执行 hello ngx.timer.every
if require("ngx.process").type() ~= "privileged agent" then
return
end
ngx.timer.every(1, function()
ngx.log(ngx.ERR, 'hello ngx.timer.every')
end)
}
}
启动服务
$ openresty -p ~/openresty-test -c openresty.conf #启动
$ cat logs/error.log
# 可以看到只有每秒一个执行log
2022/12/17 16:13:50 [error] 3094#3094: *7 stream [lua] init_worker_by_lua:7: hello ngx.timer.every, context: ngx.timer
2022/12/17 16:13:51 [error] 3094#3094: *8 stream [lua] init_worker_by_lua:7: hello ngx.timer.every, context: ngx.timer
2022/12/17 16:13:52 [error] 3094#3094: *9 stream [lua] init_worker_by_lua:7: hello ngx.timer.every, context: ngx.timer
监听文件变化
实现如下
stream {
init_worker_by_lua_block {
-- 限制只允许特权进程执行 文件监听
if require("ngx.process").type() ~= "privileged agent" then
return
end
-- 对比变化占位变量
local yaml_change_time = nil
ngx.timer.every(1, function()
-- 获取文件属性
local lfs = require("lfs")
local yaml_path = ngx.config.prefix()..'/conf.yaml'
local attributes, err = lfs.attributes(yaml_path)
if not attributes then
ngx.log(ngx.ERR, "failed to fetch ", yaml_path, " attributes: ", err)
return
end
-- 对比变化时间
local last_change_time = attributes.change
if yaml_change_time == last_change_time then
return
end
yaml_change_time = last_change_time
ngx.log(ngx.ERR, yaml_path,' change at ', yaml_change_time)
end)
}
}
启动服务
$ openresty -p ~/openresty-test -c openresty.conf #启动
# 我们修改几次conf.yaml文件后
$ cat logs/error.log
# 可以看到每次变化时都有log输出
2022/12/17 16:21:11 [error] 3211#3211: *4 stream [lua] init_worker_by_lua:23: /root/openresty-test//conf.yaml change at 1671247838, context: ngx.timer
2022/12/17 16:21:21 [error] 3211#3211: *14 stream [lua] init_worker_by_lua:23: /root/openresty-test//conf.yaml change at 1671265280, context: ngx.timer
如何通知配置变化呢?
由于我们监听yaml文件是在特权进程,无法直接更新worker进程内的upstream数据
当然我们可以通过一些进程间通信机制实现,
但这里篇幅关系,我们先直接最简单办法,让 openresty 热重启,重新读取配置
实现如下:
stream {
init_worker_by_lua_block {
-- 对比变化占位变量
local yaml_change_time = nil
ngx.timer.every(1, function()
-- 省略代码
yaml_change_time = last_change_time
-- 发送热重启信号
local signal = require('resty.signal')
signal.kill(process.get_master_pid(), signal.signum("HUP"))
end)
}
}
在启动服务后,
直接测试,可以得到与之前相反的结果
$ curl http://localhost:8699 -i #测试, yaml配置还有upstrem的情况
HTTP/1.1 200 OK
Date: Sat, 17 Dec 2022 08:39:35 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
HelloWorld
$ curl http://localhost:8689 -i #测试 yaml配置没有upstrem的情况
curl: (56) Recv failure: Connection reset by peer
我们修改conf.yaml
upstream:
a8689:
host: '127.0.0.1'
port: 6699
直接测试,可以得到与之前相反的结果
$ curl http://localhost:8699 -i #测试, yaml配置已经没有upstrem的情况
curl: (56) Recv failure: Connection reset by peer
$ curl http://localhost:8689 -i #测试 yaml配置已经有upstrem的情况
HTTP/1.1 200 OK
Date: Sat, 17 Dec 2022 08:39:59 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
HelloWorld
完整的例子在: https://github.com/fs7744/nature/blob/main/docs/demo/yaml
小结
我们完成一个基本的tcp代理动态配置的实现,
也同时也简单了解了
- 如何引入第三方库以及配置openresty
- lua 文件读取
- yaml 库
- openresty init_worker
- openresty timer
- privileged agent
- 代码触发 openresty 热重启
以下针对一些特别介绍,大家要想搞懂openresty,一定要阅读和理解
- luarocks介绍
- curl
- openresty log
- lua file操作
- 理解openresty 执行阶段
- 理解openresty 不同进程
- 理解openresty timer
- 热重启机制
- 进程间通讯
目录
监听yaml配置的更多相关文章
- 【oracle常见错误】oracle监听程序配置/“ORA-12541: TNS: 无监听程序”
问题描述 在用PL/SQL Developer连接Oracle 11g时报错“ORA-12541: TNS: 无监听程序”,如下图所示.可以按照如下的步骤进行解决. 解决方案 监听程序配置 从开始菜单 ...
- ORA-12541:TNS:无监听程序 配置Oracle Myeclipse无法连接上 花费一天时间解决掉的
背景:自己机子做oracle服务器,其他机子可以ping得通我的机子,但是jdbc就是连不上,后来用plsql连出现无监听程序.... 我昨天重新安装Oracle后,用PL/SQL Developer ...
- Oracle Net Manager 的使用方法(监听的配置方法)
一,在服务端配置oracle端口 win+R 输入netca 弹出如下窗口后 选择监听程序配置,点击下一步 二.配置端口后使用Telnet工具调试端口是否联通 在命令行输入telnet 服务器ip ...
- Spring整合ActiveMQ及多个Queue消息监听的配置
消息队列(MQ)越来越火,在java开发的项目也属于比较常见的技术,MQ的相关使用也成java开发人员必备的技能.笔者公司采用的MQ是ActiveMQ,且消息都是用的点对点的模式.本文记录了实 ...
- .net core Kestrel宿主服务器自定义监听端口配置
在.net core的web程序中,除了可以在项目中硬编码服务器的监听端口外,还可以在外部通过json文件配置. 方法如下: 第一步:在项目中新建一个名为Hosting.json的文件.当然,文件名可 ...
- 多线程消息监听容器配置[ 消费者spring-kafka配置文件]
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...
- oracle 监听服务配置
最近在red hat 6.6虚拟机上安装了Oracle 11gR2数据库,安装完毕,使用没有问题,通过主机也可以访问到虚拟机上的数据库.然而,在重新启动虚拟机后,主机无法访问到数据库,提示错误: PS ...
- oracle-11g-R2监听文件配置
客户端连接oracle数据库时出现如下错误: Listener refused the connection with the following error: ORA-, TNS:listener ...
- 免Oracle客户端程序监听程序配置
Oracle默认安装时,监听程序和tnsnames程序中的监听方式都是默认的localhost,但免客户端的程序是连接不上的.这时需要: 1.将listener中的(HOST = localhost) ...
- Windows8安装Oracle11.2.0.1-0624,附带 DBCA建库、netca创建监听、配置PLSQL、定义客户端的环境变量 NLS_LANG、定义客户端的环境变量 TNS_ADMIN01
Windows8安装Oracle11.2.0.1 操作系统:Windows 8 企业版 64bit Oracle:11. ...
随机推荐
- C#使用迭代器显示公交车站点
public static IList<object> items = new List<object>();//定义一个泛型对象,用于存储对象 /// <summary ...
- Liunx常用操作(三)-如何忽略大小写查找
1.vim 中的查找 搜索文件内容时加上 /c 参数可以忽略搜索字符的大小写 正常搜索:/helloworld 忽略操作:/helloworld/c 2.find 查找 使用find命令搜索文件时如果 ...
- MySQL 及调优
存储引擎的种类 MySQL 中存在多种存储引擎,比如: InnoDB 支持事务: 支持外键: 同时支持行锁和表锁. 适用场景:经常更新的表,存在并发读写或者有事务处理的业务场景. MyISAM 支持表 ...
- spring-transaction源码分析(3)Transactional事务失效原因
问题概述 在Transactional方法中使用this方式调用另一个Transactional方法时,拦截器无法拦截到被调用方法,严重时会使事务失效. 类似以下代码: @Transactional ...
- Java之利用openCsv将csv文件导入mysql数据库
前两天干活儿的时候有个需求,前台导入csv文件,后台要做接收处理,mysql数据库中,项目用的springboot+Vue+mybatisPlus实现,下面详细记录一下实现流程. 1.Controll ...
- 基于AHB_BUS SRAM控制器的设计-01
基于AHB Bus SRAM控制器的设计 1.课程目标 接到一个需求要设计SRAM或者I-cache等,需要问后端要一个Memory Memory Compiler是由后端工程师完成的,Memory ...
- 解决windows系统电脑内存占用过高,一开机就是60%70%80%90%?
1.问题 windows系统电脑内存占用过高,一开机就是60%70%80%90%? 2.解决方式 主要是虚拟内存一直没有及时释放导致的 先贴上B站视频链接:解决windows系统电脑内存占用过高 这里 ...
- UEditor 添加在线管理图片删除功能 (转载)
第一,需要添加一个 php 文件来实现删除功能,文件添加到: ueditor\php\action_delete.php 代码内容: <?php /*---------------------- ...
- [转帖]TiDB 5.1 Write Stalls 应急文档
https://tidb.net/blog/ac7174dd#4.%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E5%87%BA%E7%8E%B0%E4%BA%86%20w ...
- [转帖]RFC1180
[译] RFC 1180:朴素 TCP/IP 教程(1991) 译者序 本文翻译自 1991 年的一份 RFC(1180): A TCP/IP Tutorial. 本文虽距今将近 20 年,但内容并未 ...