OpenResty实现动态路由

0需求场景

动态路由可以解决以下场景

(1)隐藏K8s的外部访问Ip端口,对外提供统一的访问host(普通nginx代理即可)

(2)根据不同请求id动态路由到不同后端服务

(3)跟踪请求id,后续相关浏览器的请求都要进行正确路由

 

使用OpenResty+Redis+Lua,官方就有一个基本的样例:Dynamic Routing Based On Redis

 

 

(这里图不是我画的,意思大概是差不多的。图片来源https://www.jianshu.com/p/ac031facc88d) 

 

实际生产需求举个例子,k8s提供了2个jupyter服务的外部访问ip端口如下

jupyter1    ip1:port1

jupyter2    ip2:port2

现在想通过访问 http://loccalhost/jupyter  代理这两个服务,一个路径选择通过在请求header添加参数requestid来区分两个服务,分别用1000和1001表示如下

jupyter1    ip1:port1    1000

jupyter2    ip2:port2    1001

为了实现上表面的需求3代理后续所有jupyter的请求,可以通过在nginx种cookie来实现requestid的传播。

 

1实现方案

前端访问http://localhost/jupyter时添加header参数requestid,nginx在location里读到后种到cookie里,再转发到 http://loccalhost/路径,后续请求cookie里都带上requestid。

根路径location里以requestid为key去redis查询ip端口,后端服务在添加端口映射的时候向redis注册<requestid, ip:port>

server {
    listen       80;
    server_name  localhost;
 
 
    location = /redis {
        internal;
        set_unescape_uri $key $arg_key;
        redis2_query auth 'xxxx';
        redis2_query get $key;
        redis2_pass redisip:redisport;
    }
 
 
    location = /jupyter{
     
        access_by_lua '
            local headers = ngx.req.get_headers()
            local sid = headers["requestid"]
            if sid == nil then
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end
            local cookie_rid = "requestid="..sid
            ngx.header["Set-Cookie"] = {cookie_rid}
            ngx.redirect("/")
        ';
     }
 
 
    location / {
        set $target '';
        access_by_lua '
            local sid = ngx.var.cookie_requestid
            if sid == nil then
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end
 
            local key = sid
            local res = ngx.location.capture(
                "/redis", { args = { key = key } }
            )
 
            if res.status ~= 200 then
                ngx.log(ngx.ERR, "redis server returned bad status: ",
                    res.status)
                ngx.exit(res.status)
            end
 
            if not res.body then
                ngx.log(ngx.ERR, "redis returned empty body")
                ngx.exit(500)
            end
 
            local parser = require "redis.parser"
            local results = parser.parse_replies(res.body,2)
            for i, reply in ipairs(results) do
                if i == 2 then
                    server =  reply[1]
                end
            end           
 
            if server == "" then
                server = "default"
            end
 
            server = server .. ngx.var.request_uri
            ngx.var.target = server
        ';
 
        resolver 8.8.8.8;
        proxy_pass http://$target;
 
        proxy_redirect off;
        proxy_set_header HOST $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Origin "";
         
        proxy_read_timeout 120s;
        proxy_next_upstream error;
        proxy_buffering off;
 
     }
}

注意:

(1)与官方示例不同,这里redis需要验证密码,redis2_query auth一定要写在get前面,不然会报错。

(2)proxy_set_header Origin "";这一行一定要有,不然所有jupyter post请求会404(?)

(扩展:想想还可以进一步通过sessionId从redis中取信息鉴权等)

 

2优化

所有请求都要通过redis服务,相当于多走了一次网络,路由路径不常变化,基于性能考虑可以做一个本地内存的缓存。

ngx.shared.DICT :这个 cache 是 nginx 所有 worker 之间共享的,内部使用的 LRU 算法。步骤如下:

http {
    include       mime.types;
    default_type  application/octet-stream;
 
    sendfile        on;
    keepalive_timeout  65;
 
    lua_shared_dict localcache 5m;
 
    server {
        listen       80;
        server_name  localhost;
 
        location = /redis {
            internal;
            set_unescape_uri $key $arg_key;
            redis2_query auth 'xxxx';
            redis2_query get $key;
            redis2_pass redisip:redisport;
        }
 
        location = /instance {
         
            access_by_lua '
                local headers = ngx.req.get_headers()
                local sid = headers["requestid"]
                if sid == nil then
                    ngx.exit(ngx.HTTP_FORBIDDEN)
                end
                local cookie_rid = "requestid="..sid
                ngx.header["Set-Cookie"] = {cookie_rid}
                ngx.redirect("/")
            ';
         }
 
 
        location / {
            set $target '';
            access_by_lua '
                local sid = ngx.var.cookie_requestid
                if sid == nil then
                    ngx.exit(ngx.HTTP_FORBIDDEN)
                end
 
                local key = sid
                local cache = ngx.shared.localcache
                local server = cache:get(key)
 
                if server == nil then
                    local res = ngx.location.capture(
                        "/redis", { args = { key = key } }
                    )              
 
                    if res.status ~= 200 then
                        ngx.log(ngx.ERR, "redis server returned bad status: ",res.status)
                        ngx.exit(res.status)
                    end
 
                    if not res.body then
                        ngx.log(ngx.ERR, "redis returned empty body")
                        ngx.exit(500)
                    end
 
                    local parser = require "redis.parser"
                    local results = parser.parse_replies(res.body,2)
                    for i, reply in ipairs(results) do
                        if i == 2 then
                            server =  reply[1]
                        end
                    end
 
                    cache:set(key, server, 300)
                end
 
                if server == "" then
                    server = "default"
                end
 
                server = server .. ngx.var.request_uri
                ngx.var.target = server
            ';
 
            resolver 8.8.8.8;
            proxy_pass http://$target;
 
            proxy_redirect off;
            proxy_set_header HOST $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Origin "";
             
            proxy_read_timeout 120s;
            proxy_next_upstream error;
            proxy_buffering off;
 
         }
 
        #....
    }
}

先在http域定义了一个大小为5M的全局缓存,读前先走缓存,没有命中再读redis并将结果写入缓存。

 

posted @ 2022-07-05 14:46  OUYM  阅读(823)  评论(0编辑  收藏  举报