APISIX源码解析-服务发现-discover【dns】

摘要

当业务量发生变化时,需要对上游服务进行扩缩容,或者因服务器硬件故障需要更换服务器。如果网关是通过配置来维护上游服务信息,在微服务架构模式下,其带来的维护成本可想而知。再者因不能及时更新这些信息,也会对业务带来一定的影响,还有人为误操作带来的影响也不可忽视,所以网关非常必要通过服务注册中心动态获取最新的服务实例信息。架构图如下所示:
在这里插入图片描述
服务启动时将自身的一些信息,比如服务名、IP、端口等信息上报到注册中心;各个服务与注册中心使用一定机制(例如心跳)通信,如果注册中心与服务长时间无法通信,就会注销该实例;当服务下线时,会删除注册中心的实例信息;
网关会准实时地从注册中心获取服务实例信息;
当用户通过网关请求服务时,网关从注册中心获取的实例列表中选择一个进行代理;
常见的注册中心:Eureka, Etcd, Consul, Nacos, Zookeeper 等

关键

  • 初始化的 _M.init_worker() 函数
  • 获取服务实例节点列表的 _M.nodes(service_name) 函数

discover【dns】

关键属性

discovery:
   dns:
     servers:
       - "127.0.0.1:8600"          # 使用 DNS 服务器的真实地址

Embedded control api for debugging

有时我们需要发现客户端在运行调试时将在线数据快照导出到内存中,如果实现该_M. dump_data()功能:

function _M.dump_data()
    return {config = local_conf.discovery.eureka, services = applications}
end

然后你可以调用它的控制api如下:

获取 /v1/discovery/{discovery_type}/dump
例如:
http://127.0.0.1:9090/v1/discovery/eureka/dump

源码实现

dns.lua

function _M.nodes(service_name)
    local host, port = core.utils.parse_addr(service_name)
    core.log.info("discovery dns with host ", host, ", port ", port)
    -- 解析服务地址
    --SRV例子:
    --; under the section of blah.service
    --A       300 IN      A     1.1.1.1
    --B       300 IN      A     1.1.1.2
    --B       300 IN      A     1.1.1.3
    --
    --; name  TTL         type    priority    weight  port
    --srv     86400 IN    SRV     10          60      1980 A
    --srv     86400 IN    SRV     20          20      1981 B
    --注意 B 域名的两条记录均分权重。 对于 SRV 记录,低优先级的节点被先选中,所以最后一项的优先级是负数。
    local records, err = dns_client:resolve(host, core.dns_client.RETURN_ALL)
    if not records then
        return nil, err
    end

    -- 转化成对应的数据
    local nodes = core.table.new(#records, 0)
    for i, r in ipairs(records) do
        if r.address then
            nodes[i] = {host = r.address, weight = r.weight or 1, port = r.port or port}
            if r.priority then
                -- for SRV record, nodes with lower priority are chosen first
                nodes[i].priority = -r.priority
            end
        end
    end

    return nodes
end

-- 初始化客户端
function _M.init_worker()
    local local_conf = config_local.local_conf()
    local ok, err = core.schema.check(schema, local_conf.discovery.dns)
    if not ok then
        error("invalid dns discovery configuration: " .. err)
        return
    end

    local servers = core.table.try_read_attr(local_conf, "discovery", "dns", "servers")

    local opts = {
        hosts = {},
        resolvConf = {},
        nameservers = servers,
        order = {"last", "A", "AAAA", "SRV", "CNAME"},
    }
    -- 使用resty.dns.client初始化客户端
    local client, err = core.dns_client.new(opts)
    if not client then
        error("failed to init the dns client: ", err)
        return
    end

    dns_client = client
end

开放control API “/dump”

apisix.http_control():fetch_control_api_router
function fetch_control_api_router()
    core.table.clear(routes)

    for _, plugin in ipairs(plugin_mod.plugins) do
        local api_fun = plugin.control_api
        if api_fun then
            local api_route = api_fun()
            register_api_routes(routes, api_route)
        end
    end

    local discovery_type = require("apisix.core.config_local").local_conf().discovery
    if discovery_type then
        local discovery = require("apisix.discovery.init").discovery
        local dump_apis = {}
        for key, _ in pairs(discovery_type) do
            local dis_mod = discovery[key]
            -- if discovery module has control_api method, support it
            local api_fun = dis_mod.control_api
            if api_fun then
                local api_route = api_fun()
                local format_route = format_dismod_control_api_uris(key, api_route)
                register_api_routes(routes, format_route)
            end

            local dump_data = dis_mod.dump_data
            -- 增加control API "/dump"
            if dump_data then
                local target_uri = format_dismod_uri(key, "/dump")
                local item = {
                    methods = {"GET"},
                    uris = {target_uri},
                    handler = function()
                        return 200, dump_data()
                    end
                }
                core.table.insert(dump_apis, item)
            end
        end

        if #dump_apis > 0 then
            core.log.notice("dump_apis: ", core.json.encode(dump_apis, true))
            register_api_routes(routes, dump_apis)
        end
    end
.......................

应用流程

初始化 ----> 匹配路由 ----->替换upstream
注意:配置后upstream.service_name, upstream.nodes将不再生效,而是替换为从注册表中获取的“nodes”。

http_init_worker
function _M.http_init_worker()
    local seed, err = core.utils.get_seed_from_urandom()
    if not seed then
        core.log.warn('failed to get seed from urandom: ', err)
        seed = ngx_now() * 1000 + ngx.worker.pid()
    end
    math.randomseed(seed)
    -- for testing only
    core.log.info("random test in [1, 10000]: ", math.random(1, 10000))

    local we = require("resty.worker.events")
    local ok, err = we.configure({shm = "worker-events", interval = 0.1})
    if not ok then
        error("failed to init worker event: " .. err)
    end
    -- 在http启动初始化时,执行服务发现组件初始化方法
    local discovery = require("apisix.discovery.init").discovery
    if discovery and discovery.init_worker then
        discovery.init_worker()
    end
    ................
http_access_phase
......
    router.router_http.match(api_ctx)

    -- run global rule
    plugin.run_global_rules(api_ctx, router.global_rules, nil)
    -- 匹配到对应的路由
    local route = api_ctx.matched_route
    if not route then
        core.log.info("not find any matched route")
        return core.response.exit(404,
                    {error_msg = "404 Route Not Found"})
    end
........
    -- 设置替换路由
    local code, err = set_upstream(route, api_ctx)
    if code then
        core.log.error("failed to set upstream: ", err)
        core.response.exit(code)
    end
........
set_by_route
function _M.set_by_route(route, api_ctx)
    if api_ctx.upstream_conf then
        -- upstream_conf has been set by traffic-split plugin
        return
    end

    local up_conf = api_ctx.matched_upstream
    if not up_conf then
        return 500, "missing upstream configuration in Route or Service"
    end
    -- core.log.info("up_conf: ", core.json.delay_encode(up_conf, true))
    -- 有service_name代表配置了服务发现,discovery_type必然有值
    if up_conf.service_name then
        if not discovery then
            return 500, "discovery is uninitialized"
        end
        if not up_conf.discovery_type then
            return 500, "discovery server need appoint"
        end

        local dis = discovery[up_conf.discovery_type]
        if not dis then
            return 500, "discovery " .. up_conf.discovery_type .. " is uninitialized"
        end
        -- 获取实际发现的上游服务
        local new_nodes, err = dis.nodes(up_conf.service_name)
        if not new_nodes then
            return HTTP_CODE_UPSTREAM_UNAVAILABLE, "no valid upstream node: " .. (err or "nil")
        end

        local same = upstream_util.compare_upstream_node(up_conf, new_nodes)
        -- 如果和上次不一样则覆盖
        if not same then
            up_conf.nodes = new_nodes
            local new_up_conf = core.table.clone(up_conf)
            core.log.info("discover new upstream from ", up_conf.service_name, ", type ",
                          up_conf.discovery_type, ": ",
                          core.json.delay_encode(new_up_conf, true))

            local parent = up_conf.parent
            if parent.value.upstream then
                -- the up_conf comes from route or service
                parent.value.upstream = new_up_conf
            else
                parent.value = new_up_conf
            end
            up_conf = new_up_conf
        end
    end
........................

备注

使用的dns-client (“resty.dns.client”)是由Kong网关开源的项目
https://github.com/Kong/lua-resty-dns-client

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值