前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kong 插件加载机制源码解析(下)

Kong 插件加载机制源码解析(下)

作者头像
poslua
发布2019-08-19 14:51:27
1.7K0
发布2019-08-19 14:51:27
举报
文章被收录于专栏:posluaposlua

4. access

这个阶段就比较重要了,首先要执行的就是 core.access.before(ctx) 这个 hook,主要是完成路由的匹配。不过匹配前需要判断当前路由是否是最新版本,否则的话需要重建路由:

代码语言:javascript
复制
local version, err = singletons.cache:get("router:version", {
  ttl = 0
}, utils.uuid)
if err then
  log(ngx.CRIT, "could not ensure router is up to date: ", err)

elseif router_version ~= version then
  -- router needs to be rebuilt in this worker
  log(DEBUG, "rebuilding router")

  local ok, err = build_router(singletons.dao, version)
  if not ok then
    router_err = err
    log(ngx.CRIT, "could not rebuild router: ", err)
  end
end

接下来就是运行「phase 循环」了,来彻底完成插件生效策略的筛选(因为这个时候已经完成了路由查找,之后通过 API 可以找到 auth 插件,进而确定 Consumer,这也是为什么 auth 插件的优先级普遍比较高的原因)。插件的筛选还是由 get_next 来完成,关键代码如下:

代码语言:javascript
复制
if consumer then
  -- ... 省略若干
  if api then
    plugin_configuration = load_plugin_configuration(api.id, consumer_id, plugin.name)
  end
  if not plugin_configuration then
    plugin_configuration = load_plugin_configuration(nil, consumer_id, plugin.name)
  end
end

if not plugin_configuration then
  -- Search API specific, or global
  if api then
    plugin_configuration = load_plugin_configuration(api.id, nil, plugin.name)
  end
  if not plugin_configuration then
    plugin_configuration = load_plugin_configuration(nil, nil, plugin.name)
  end
end

ctx.plugins_for_request[plugin.name] = plugin_configuration

因此,最终的生效策略优先级就是:api & consumer > consumer > api > global

需要注意的是,这个过程可能会覆盖上个阶段的全局插件,这正是插件生效策略的作用;同时该过程结束之后,当前请求需要启用的插件就已经最终确定,并被缓存在 ctx.plugins_for_request 中,直至该请求生命周期的结束。至于之后阶段运行的「phase 循环」其实就是直接读取的 ctx.plugins_for_request 而已。

另外在这个阶段有个非常巧妙的设计,就是 ctx.delay_response 这个参数。它的原理就是把要执行的 handler wrap 在一个 coroutine 中,如果执行到一个插件需要 ngx.say 来提前执行 Nginx 的 content handler,那么它就会 yield 当前 coroutine,来延迟 content handler 的执行,并跳过之后需要执行的所有插件。这么做主要基于两点:

  • 如果请求被插件拦截就尽快退出「phase」循环。
  • 可以在输出 content 前,做一些自定义的操作。
代码语言:javascript
复制
ctx.delay_response = true

for plugin, plugin_conf in plugins_iterator(singletons.loaded_plugins, true) do
  if not ctx.delayed_response then
    local err = coroutine.wrap(plugin.handler.access)(plugin.handler, plugin_conf)
    if err then
      ctx.delay_response = false
      return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
    end
  end
end

if ctx.delayed_response then
  return responses.flush_delayed_response(ctx)
end

但是目前 Kong 在实现这块的时候也是有缺陷的,就是插件执行过程中如果 ngx.say 被触发,虽然将不会执行接下来的插件,但是依然在运行一个 hot 的迭代。这里其实完全可以避免,就像下面这样:

代码语言:javascript
复制
if not ctx.delayed_response then
  -- ... 省略若干
else
  break
end

执行完插件的 access() handler 之后,就通过 flush_delayed_response 将延迟发送(如果需要的话)的 content 响应给客户端:

代码语言:javascript
复制
if ctx.delayed_response then
  return responses.flush_delayed_response(ctx)
end

如果通过了插件的 access() handler,却没有触发 content handler。那么接下来就是执行 core.access.after 这个 hook 了。其中的关键步骤:

代码语言:javascript
复制
local ok, err, errcode = balancer.execute(ctx.balancer_address)
if not ok then
  if errcode == 500 then
    err = "failed the initial dns/balancer resolve for '" ..
          ctx.balancer_address.host .. "' with: "         ..
          tostring(err)
  end
  return responses.send(errcode, err)
end

主要就是获取 upstream,并完成相关 DNS 的解析,其实这个事儿更应该是由 balancer 阶段来完成。只不过由于 balancer 不允许被 yield,因此就放在了这里。最后请求将会被 proxy_pass 到 kong_upstream,正式进入到 balancer 阶段。

5. balancer

这个阶段不会运行任何插件,当然也不会有「phase 循环」。

这个阶段的主要工作其实就是完成重试逻辑,因为在上个阶段的 core.access.before hook 已经完成了第一次节点的选取,这个阶段只是简单做了下 ngx_balancer.set_current_peerngx_balancer.set_more_tries

重试依然使用的是 balancer.execute,关键步骤为:

代码语言:javascript
复制
if addr.try_count > 1 then
  -- only call balancer on retry, first one is done in `core.access.after` which runs
  -- in the ACCESS context and hence has less limitations than this BALANCER context
  -- where the retries are executed

  -- record failure data
  local previous_try = tries[addr.try_count - 1]
  previous_try.state, previous_try.code = get_last_failure()

  -- Report HTTP status for health checks
  if addr.balancer then
    if previous_try.state == "failed" then
      addr.balancer.report_tcp_failure(addr.ip, addr.port)
    else
      addr.balancer.report_http_status(addr.ip, addr.port, previous_try.code)
    end
  end

  local ok, err, errcode = balancer_execute(addr)
  if not ok then
    ngx_log(ngx_ERR, "failed to retry the dns/balancer resolver for ",
            tostring(addr.host), "' with: ", tostring(err))
    return ngx.exit(errcode)
  end

else
  -- first try, so set the max number of retries
  local retries = addr.retries
  if retries > 0 then
    set_more_tries(retries)
  end
end

值得一提的是 Kong 使用的 Ring-balancer 是自己实现的 lua-resty-dns-client,target 的选取默认使用的是 round-robin 算法,当 upstream 开启了 hash 时,则会换为一致性 hash。

6. header_filter

这个阶段将会接着执行「phase 循环」,只不过经过了 access 阶段之后,当前请求应该被执行的插件已经确定,并被缓存在自身中。这个阶段只是在遍历所有插件时将直接从上面的缓存中查找,并执行相应的 header_filter 方法,而不再经过生效策略的筛选,这当然也是出于性能上的考量。

代码语言:javascript
复制
local ctx = ngx.ctx
core.header_filter.before(ctx)

for plugin, plugin_conf in plugins_iterator(singletons.loaded_plugins) do
  plugin.handler:header_filter(plugin_conf)
end

core.header_filter.after(ctx)

最后执行的 core.header_filter.after hook,用于将 Kong 的处理时间注入到 header 中:

代码语言:javascript
复制
if singletons.configuration.latency_tokens then
  -- balancer 阶段执行时间
  header[constants.HEADERS.UPSTREAM_LATENCY] = ctx.KONG_WAITING_TIME
  -- access 阶段执行时间
  header[constants.HEADERS.PROXY_LATENCY]    = ctx.KONG_PROXY_LATENCY
end

7. body_filter

这个阶段其实和上个阶段差不多,只不过是在「phase 循环」中执行的 handler 为 body_filter 而已。这里就不再赘述了。

代码语言:javascript
复制
local ctx = ngx.ctx
for plugin, plugin_conf in plugins_iterator(singletons.loaded_plugins) do
  plugin.handler:body_filter(plugin_conf)
end

core.body_filter.after(ctx)

8. log

log 阶段依旧和上面的 filter 阶段差别不大:

代码语言:javascript
复制
local ctx = ngx.ctx
for plugin, plugin_conf in plugins_iterator(singletons.loaded_plugins) do
  plugin.handler:log(plugin_conf)
end

core.log.after(ctx)

不过是在 core.log.after hook 中需要更新 balancer 的被动健康检查状况而已。


结语

Kong 通过其插件扩展机制,提供了超越核心平台的额外功能和服务。同时由于插件的启用是基于每请求的,会随着生命周期的结束而被销毁。其被诟病的内存碎片问题,我猜测多少也和这一点的设计有些关系。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-05-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 poslua 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 4. access
  • 5. balancer
  • 6. header_filter
  • 7. body_filter
  • 8. log
  • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档