动态路由选路二(多模式适配)

需求

  • 在实现了动态路由选路后,也实现了基于tag的选路,同时还能进行降级
  • 基于此,现在出现了其他需求,需要基于现有的模式下,实现部署的服务模式改为精简版,而非微服务版,这样就需要全量替换原先的key-value键值对
  • 服务的精简版和微服务版,普通模式下,需要配置两套nginx的conf,如何将两套进行融合,同时能够快速的切换,是此需求的重点

思路

  • 由于入口仍然使用 ^~ /api/xx 进入,所以微服务版本和精简版本是无法同时存在的,只能进行手动或半自动切换
  • 为了解决此需求,需要设置一个开关,通过开关来进行选择使用哪种模式进行动态路由的转发
  • 当有了开关,就可以为后续扩展留好口子,保证以后的复杂业务和需求

实现方式

路由模式切换

  • 具体做法是,在 redis 中塞入一个键值对,value值是integer,从0开始,每个value对应一种模式
ECHO =========dynamic_routing===========
HSET  dynamic_routing  type  0    # 微服务模式
HSET  dynamic_routing  type  1    # 精简模式
# ..........................      # 可扩展

键值对调整

  • 由于不同模式下,路由对应的后端服务名会变化,所以整体插入redis的键值对也需要全量调整
  • 键值对需要根据业务需求,准确的进行调整并多次测试

精简版模式下的key-value, dynamic_api_hash_share.txt

ECHO =========dynamic_routing===========
HSET  dynamic_routing  type  1

ECHO =========app===========
HMSET  app  standardOpen  dmo-apaas-rest-share.default.svc.cluster.local/lego-standard-openapi-rest

微服务版模式下的key-value,dynamic_api_hash.txt

ECHO =========dynamic_routing===========
HSET  dynamic_routing  type  0

ECHO =========app===========
HMSET  app  standardOpen  dmo-lego-standard-openapi

支持动态调整,只是要 redis 中导入 key-value 就能够动态切换

cat dynamic_api_hash.txt | redis-cli -h xxxx -p 6379
cat dynamic_api_hash_share.txt | redis-cli -h xxxx -p 6379

Lua代码适配

  • 基本逻辑是从 redis 中获取 dynamic_routing 中 type的值
  • 根据type值,进行条件判断,走对应的代码逻辑
  • 精简模式下目前不管tag,不做降级

Dynamic_location_routing.lua

  • 由于从redis中获取的value包括host和一部分path,所以在传给target后,proxy_pass,不会带上之前的uriargs,所以target最后需要将这两个参数拼接,使用ngx.var.request_uri,此参数包含path和args
-- 基于nginx和lua的懂动态路由选路
-- 根据 location 第二段 /api/xxx ,来转发到对应名称的后端FQDN
-- 动态路由支持微服务版 0 和精简版 1 (支持其余自定义模板)
-- 支持路由根据userTag进行选路
-- 支持路由降级

-- yunjie.xiao@clickpaas.com

-- 局部变量
-- 降级默认地址
local degrade_target = "dmo-apaas-gateway.default.svc.cluster.local"

-- 分割字符串,局部函数
local function split( str,reps )
  local resultStrList = {}
  string.gsub(str,'[^'..reps..']+',function ( w )
      table.insert(resultStrList,w)
  end)
  return resultStrList
end

-- 通过请求uri,提取接口字符串,并从redis里获取对应的值
local function get_value()
  local uri_data = ngx.var.uri
  local key = split(ngx.var.host, '.')[1]
  local field = split(uri_data, '/')[2]
  local db = 0
  local res = ngx.location.capture("/redis_hget", { args = { key = key , field = field , db = db } })

  -- redis取值状态判断
  if res.status ~= 200 then
      ngx.log(ngx.ERR, "【redis server returned bad status】: ", res.status)
      ngx.exit(res.status)
  end

  -- redis取值内容非空判断
  if not res.body then
      ngx.log(ngx.ERR, "【redis returned empty body】")
      ngx.exit(500)
  end

  -- redis解析,多值返回 (redis无密码)
  local parser = require "redis.parser"
  local results = parser.parse_replies(res.body, 2)
  for i, result in ipairs(results) do
    if i == 2 then
      local server = result[1]
      local typ = result[2]
      -- 检查结果类型
      if typ ~= parser.BULK_REPLY or not server then
          ngx.log(ngx.ERR, "【bad redis response】: ", res.body)
          ngx.exit(500)
      end
      return server
    end
  end
end

local function get_target(server_type, server)
  -- 判断动态路由使用哪种模式(微服务版 0 和精简版 1 )
  ngx.log(ngx.INFO, "【server_type_tonumber】:", tonumber(server_type))
  if tonumber(server_type) == 1 then
    -- 精简版
    ngx.var.target = server .. ngx.var.request_uri
    return
  else
    local default_fqdn = ".default.svc.cluster.local"
    local target = server .. default_fqdn
    -- cookie_userTag
    local upstream = require "ngx.upstream"
    local get_servers = upstream.get_servers
    local userTag = ngx.var.cookie_userTag
    -- cookie_userTag 检测
    if( userTag ~= nil ) and ( userTag ~= "" ) then
      ngx.log(ngx.INFO, "【userTag】: ", userTag)
      local cookie_target = server .. "-" .. userTag .. default_fqdn
      ngx.log(ngx.INFO, "【cookie_target】: ", cookie_target)
      local servers, err = get_servers(cookie_target)
      if( servers ~= nil ) then
        ngx.var.target = cookie_target
        return
      else
        ngx.log(ngx.ERR, "【failed to get servers in upstream】: ", err)
        -- 降级
        ngx.var.target = degrade_target
        return
      end
    else
      ngx.var.target = target
      return
    end
  end
end

-- 从redis中获取动态路由模式
local key_type = "dynamic_routing"
local field_type = "type"
local db_type = 0
local res_type = ngx.location.capture("/redis_hget", { args = { key = key_type , field = field_type , db = db_type } })

-- redis取值状态判断
if res_type.status ~= 200 then
    ngx.log(ngx.ERR, "【tengine_lua  get redis type , returned bad status】: ", res_type.status)
    ngx.exit(res_type.status)
end

-- redis取值内容非空判断
if not res_type.body then
    ngx.log(ngx.ERR, "【tengine_lua get redis type , returned empty body】")
    ngx.exit(500)
end

-- redis解析,多值返回,返回动态路由的模式(微服务版0和精简版1) (redis无密码)
local parser_type = require "redis.parser"
local results_type = parser_type.parse_replies(res_type.body, 2)
for x, result_type in ipairs(results_type) do
  if x == 2 then
    local server_type = result_type[1]
    local typ_type = result_type[2]
    -- 检查结果类型
    if typ_type ~= parser_type.BULK_REPLY or not server_type then
        ngx.log(ngx.ERR, "【tengine_lua bad redis response】: ", res_type.body)
        ngx.exit(500)
    end
    local server = get_value()
    get_target(server_type, server)
  end
end

ngx.log(ngx.INFO, "【target】: ", ngx.var.target)

nginx 配置

server {
   listen 80;
   
   location = /redis_hget {
       internal;
       set_unescape_uri $key $arg_key;
       set_unescape_uri $field $arg_field;
       set_unescape_uri $db $arg_db;
       redis2_query select $db;
       redis2_query hget $key $field;
       redis2_pass 127.0.0.1:6379;
   }

   # Dynamic_location_routing
   location ^~ /api/ {
       set $target '';
       access_by_lua_file lua/Dynamic_location_routing.lua;
       proxy_pass http://$target;
    }

FAQ

  • 对于多种模式下的调整,在编译tengine时,无需新增resty的依赖
  • 实现动态路由切换,只需要更换redis的key-value值,调整方便
  • 动态切换,无需软重启nginx,平滑过度
  • 动态切换时间短,秒级切换,时间取决于key-value插入redis的时间
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值