🔬
OpenResty 最佳实践
  • 序
  • 入门篇
  • Lua 入门
    • Lua 简介
    • Lua 环境搭建
    • Lua 编辑器选择
    • 基础数据类型
    • 表达式
    • 控制结构
      • if/else
      • while
      • repeat
      • for
      • break,return 和 goto
    • Lua 函数
      • 函数的定义
      • 函数的参数
      • 函数返回值
      • 全动态函数调用
    • 模块
    • String 库
    • Table 库
    • 日期时间函数
    • 数学库函数
    • 文件操作
  • Lua 高阶
    • 元表
    • 面向对象编程
    • 局部变量
    • 判断数组大小
    • 非空判断
    • 正则表达式
    • 虚变量
    • 抵制使用 module() 定义模块
    • 调用代码前先定义函数
    • 点号与冒号操作符的区别
    • module 是邪恶的
    • FFI
    • 什么是 JIT
  • Nginx
    • Nginx 新手起步
    • location 匹配规则
    • 静态文件服务
    • 日志
    • 反向代理
    • 负载均衡
    • 陷阱和常见错误
  • OpenResty
    • 环境搭建
      • Windows 平台
      • CentOS 平台
      • Ubuntu 平台
      • Mac OS X 平台
    • Hello World
    • 与其他 location 配合
    • 获取 uri 参数
    • 获取请求 body
    • 输出响应体
    • 日志输出
    • 简单 API Server 框架
    • 使用 Nginx 内置绑定变量
    • 子查询
    • 不同阶段共享变量
    • 防止 SQL 注入
    • 如何发起新 HTTP 请求
    • 如何完成 bit 操作
      • 一,复习二进制补码
      • 二,复习位运算
      • 三,LuaJIT 和 Lua BitOp Api
      • 四,位运算算法实例
      • 五,Lua BitOp 的安装
  • LuaRestyRedisLibrary
    • 访问有授权验证的 Redis
    • select+set_keepalive 组合操作引起的数据读写错误
    • redis 接口的二次封装(简化建连、拆连等细节)
    • redis 接口的二次封装(发布订阅)
    • pipeline 压缩请求数量
    • script 压缩复杂请求
    • 动态生成的 lua-resty-redis 模块方法
  • LuaCjsonLibrary
    • json 解析的异常捕获
    • 稀疏数组
    • 空 table 编码为 array 还是 object
  • PostgresNginxModule
    • 调用方式简介
    • 不支持事务
    • 超时
    • 健康监测
    • SQL 注入
  • LuaNginxModule
    • 执行阶段概念
    • 正确的记录日志
    • 热装载代码
    • 阻塞操作
    • 缓存
    • sleep
    • 定时任务
    • 禁止某些终端访问
    • 请求返回后继续执行
    • 调试
    • 请求中断后的处理
    • 我的 lua 代码需要调优么
    • 变量的共享范围
    • 动态限速
    • shared.dict 非队列性质
    • 正确使用长链接
    • 如何引用第三方 resty 库
    • 典型应用场景
    • 怎样理解 cosocket
    • 如何安全启动唯一实例的 timer
    • 如何正确的解析域名
  • LuaRestyDNSLibrary
    • 使用动态 DNS 来完成 HTTP 请求
  • LuaRestyLock
    • 缓存失效风暴
  • OpenResty 与 SSL
    • HTTPS 时代
    • 动态加载证书和 OCSP stapling
    • TLS session resumption
  • 测试
    • 代码静态分析
    • 单元测试
    • 代码覆盖率
    • API 测试
    • 性能测试
    • 持续集成
    • 灰度发布
      • 分流引擎设计
      • 控制台开发
      • 向运维平台发展
  • Web 服务
    • API 的设计
    • 数据合法性检测
    • 协议无痛升级
    • 代码规范
    • 连接池
    • C10K 编程
    • TIME_WAIT 问题
    • 与 Docker 使用的网络瓶颈
  • 火焰图
    • 什么是火焰图
    • 什么时候使用
    • 如何安装火焰图生成工具
    • 如何定位问题
    • 拓展阅读
    • FAQ
Powered by GitBook
On this page
  • 搭配 resolver 指令
  • 进阶玩法
  • 高阶玩法
  • 最终的演示例子如下
  • 如何使用 /etc/hosts 自定义域名
Edit on GitHub
  1. LuaNginxModule

如何正确的解析域名

最近经常有朋友反映在使用一个域名地址时发现无法被正确解析。

比如在使用 MySQL 实例时某些云会给一个私有的域名搭配自有的 nameserver 使用。

local client = mysql:new()
client:connect({
    host = "rdsmxxxxxx.mysql.rds.xxxx.com",
    port = 3306,
    database = "test",
    user = "test",
    password = "123456"
})

以上代码在直接使用时往往会报一个无法解析的错误。那么怎么在 OpenResty 中使用域名呢?

搭配 resolver 指令

我们可以直接在 Nginx 的配置文件中使用 resolver 指令直接设置使用的 nameserver 地址。

官方文档中是这么描述的:

Syntax: resolver address ... [valid=time] [ipv6=on|off];
Default: —
Context: http, server, location

一个简单的例子:

resolver 8.8.8.8 114.114.114.114 valid=3600s;

不过这样的问题在于 nameserver 被写死在配置文件中,如果使用场景比较复杂或有内部 DNS 服务时维护比较麻烦。

进阶玩法

我们的代码常常运行在各种云上,为了减少维护成本,我采用了动态读取本机 /etc/resolv.conf 文件的方法。

废话不说,让我们一睹为快。

local pcall = pcall
local io_open = io.open
local ngx_re_gmatch = ngx.re.gmatch

local ok, new_tab = pcall(require, "table.new")

if not ok then
    new_tab = function (narr, nrec) return {} end
end

local _dns_servers = new_tab(5, 0)

local _read_file_data = function(path)
    local f, err = io_open(path, 'r')

    if not f or err then
        return nil, err
    end

    local data = f:read('*all')
    f:close()
    return data, nil
end

local _read_dns_servers_from_resolv_file = function()
    local text = _read_file_data('/etc/resolv.conf')

    local captures, it, err
    it, err = ngx_re_gmatch(text, [[^nameserver\s+(\d+?\.\d+?\.\d+?\.\d+$)]], "jomi")

    for captures, err in it do
        if not err then
            _dns_servers[#_dns_servers + 1] = captures[1]
        end
    end
end

_read_dns_servers_from_resolv_file()

通过上述代码我们成功动态拿到了一组 nameserver 的地址,下面就可以通过 resty.dns.resolver 大杀四方了。

local require = require
local ngx_re_find = ngx.re.find
local lrucache = require("resty.lrucache")
local resolver = require("resty.dns.resolver")
local cache_storage = lrucache.new(200)

local _is_addr = function(hostname)
    return ngx_re_find(hostname, [[\d+?\.\d+?\.\d+?\.\d+$]], "jo")
end

local _get_addr = function(hostname)
    if _is_addr(hostname) then
        return hostname, hostname
    end

    local addr = cache_storage:get(hostname)

    if addr then
        return addr, hostname
    end

    local r, err = resolver:new({
        nameservers = _dns_servers,
        retrans = 5,     -- 5 retransmissions on receive timeout
        timeout = 2000,  -- 2 sec
    })

    if not r then
        return nil, hostname
    end

    local answers, err = r:query(hostname, {qtype = r.TYPE_A})

    if not answers or answers.errcode then
        return nil, hostname
    end

    for i, ans in ipairs(answers) do
        if ans.address then
            cache_storage:set(hostname, ans.address, 300)
            return ans.address, hostname
        end
    end

    return nil, hostname
end

ngx.say(_get_addr("www.baidu.com"))
ngx.say(_get_addr("192.168.0.1"))

我这边把解析的结果放入了 lrucache 缓存了 5 分钟,你们同样可以把结果放入 shared 中来减少 worker 查询次数。

高阶玩法

现在我们已经实现了自缓存体系的 DNS 查询,如果搭配 resty.http 就会达到更好的效果。

一个简单的例子是,通过解析 uri 得到 hostname、port、path,把 hostname 扔给自缓存 DNS 获取结果。

发起 request 请求,addr + port connect 之,设置 header 的 host 为 hostname,path 等值来实现 IP 直接访问等高阶用法。

这里就不过多的阐述了。

最终的演示例子如下

local client = mysql:new()
client:connect({
    host = _get_addr(conf.mysql_hostname),
    port = 3306,
    database = "test",
    user = "test",
    password = "123456"
})

如何使用 /etc/hosts 自定义域名

还有些同学可能会在 hosts 文件中自定义域名和 IP,这时候 resolve 是无法正常解析的。

这个时候可以借助 dnsmasq 这个服务来缓存我们的 DNS 结果,hosts 的定义可以被该服务识别。

需要在 Nginx 的配置文件中,设置 resolver 为 dnsmasq 服务的监听地址即可。

Previous如何安全启动唯一实例的 timerNextLuaRestyDNSLibrary

Last updated 2 years ago

See:

http://hambut.com/2016/09/09/how-to-resolve-the-domain-name-in-openresty/