6

构建api gateway之 http路由实现 - victor.x.qu

 1 year ago
source link: https://www.cnblogs.com/fs7744/p/17076480.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

http路由

路由是指路由器从一个接口上收到数据包,根据数据包的目的地址进行定向并转发到另一个接口的过程。

而这里的http路由其实等同于web开发中,根据http相关参数(比如url、http method)分配到对应的处理程序。

借用web框架的示意图,其作用如下

route.PNG

这里我们先简化一下内容,假设我们已有 upstream ip、port,现在只需能区分各种请求怎么样对应到这些不同的upstream上,不必关心能否做改写请求啊、熔断啊等等复杂情况

那么怎么实现路由匹配呢?

通常为以下两种方式

  1. 字典+正则表达式

    字典用于匹配精确的结果(比如 url == /login 情况),字典的特性保证这类匹配具有超高性能

    正则表达式用于匹配复杂模糊的结果(比如 url 以 .html 为后缀的所有请求), 当然多项正则表达式只能依次遍历,性能肯定存在问题(为了缓解性能问题,通常会使用缓存做优化)

  2. 前缀树,又称字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。

    其由于插入和查询的效率很高,非常适合路由匹配的情况

    虽然理论hash性能最好,前缀树仍需查找,效率会低一些,

    但毕竟通常开发都会使用比较复杂的路由, 所以效率肯定比上面的 字典+正则表达式 要高很多

路由匹配实践

这里由于篇幅关系,只介绍 字典+正则表达式 简单实现,前缀树 则介绍apisix 中实现的库,毕竟算法要强悍的性能,纯lua实现是不太可靠的,必须得上c才行,这里就避免c的复杂度吧。

字典+正则表达式 简单实现


worker_processes  1;        #nginx worker 数量
error_log logs/error.log;   #指定错误日志文件路径
events {
    worker_connections 1024;
}

http {
    log_format main '$remote_addr [$time_local] $status $request_time $upstream_status $upstream_addr $upstream_response_time';
    access_log logs/access.log main buffer=16384 flush=3;            #access_log 文件配置

    upstream nature_upstream {
        server 127.0.0.1:6699; #upstream 配置为 hello world 服务

        # 一样的balancer
        balancer_by_lua_block {
            local balancer = require "ngx.balancer"
            local upstream = ngx.ctx.api_ctx.upstream
            local ok, err = balancer.set_current_peer(upstream.host, upstream.port)
            if not ok then
                ngx.log(ngx.ERR, "failed to set the current peer: ", err)
                return ngx.exit(ngx.ERROR)
            end
        }
    }

    init_by_lua_block {
        local path_exact = {}  -- 精确匹配字典
        local path_reg = {}    -- 正则遍历集合

        -- 添加路由的方法
        add_router = function(type, path, upstream)
            if type == 'exact' then
                path_exact[path] = upstream
            else
                table.insert(path_reg, {reg = path, upstream = upstream})
            end
        end

        -- 匹配方法,优先精确匹配
        router_match = function()
            local p = ngx.var.uri
            local upstream = path_exact[p]
            if upstream == nil then
                for k, v in pairs(path_reg) do
                    if ngx.re.find(p, v.reg) then
                        return v.upstream
                    end
                end
            end

            return upstream
        end

        -- 添加测试数据
        add_router('exact' , '/aa/d', {host = '127.0.0.1', port = 6698})
        add_router('reg' , '/aa/*', {host = '127.0.0.1', port = 6699})
    }

    server {
		#监听端口,若你的8699端口已经被占用,则需要修改
        listen 8699 reuseport;

        location / {

            # 在access阶段匹配路由
            access_by_lua_block {
                local upstream = router_match()
                if upstream then
                    ngx.ctx.api_ctx = { upstream = upstream }
                else
                    ngx.exit(404)
                end
            }

            proxy_http_version                  1.1;
            proxy_pass http://nature_upstream; #转发到 upstream
        }
    }


    #为了大家方便理解和测试,我们引入一个hello world 服务
    server {
		#监听端口,若你的6699端口已经被占用,则需要修改
        listen 6699;
        location / {
            default_type text/html;

            content_by_lua_block {
                ngx.say("HelloWorld")
            }
        }
    }
}

启动服务并测试

$ openresty -p ~/openresty-test -c openresty.conf #启动
$ curl --request GET 'http://127.0.0.1:8699/aa/d'  #测试精确匹配
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
</body>
</html>
$ curl --request GET 'http://127.0.0.1:8699/aa/xxx'  #测试正则匹配
HelloWorld
$ curl --request GET 'http://127.0.0.1:8699/dd/xxx'  #测试不存的路由
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
</body>
</html>

核心原理其实就是 router_match 这个函数的内容,

不过上述简单实现肯定无法支持以下的一些复杂场景

  1. 正则冲突 (一般会引入优先级顺序支持,或者默认加入的顺序)
  2. host隔离
  3. 自定义条件匹配

所以一个完整的路由实现都很复杂,毕竟支持的场景挺多的

使用 lua-resty-radixtree 路由库

以下内容更新到 openresty-dev-1.rockspec

-- 依赖包
dependencies = {
    "lua-resty-radixtree >= 2.8.2",
}
luarocks install openresty-dev-1.rockspec --tree=deps --only-deps --local

这里只列举变动的部分

http {

    init_by_lua_block {

        -- 初始化路由
        local radix = require("resty.radixtree")
        local r = radix.new({
            {paths = {'/aa/d'}, metadata = {host = '127.0.0.1', port = 6698}},
            {paths = {'/aa/*'}, metadata = {host = '127.0.0.1', port = 6699}}
        })

        -- 匹配路由
        router_match = function()
            local upstream, err = r:match(ngx.var.uri, {})
            if err then
                log.error(err)
            end
            return upstream
        end
    }

}

启动服务并测试

$ openresty -p ~/openresty-test -c openresty.conf #启动
$ curl --request GET 'http://127.0.0.1:8699/aa/d'  #测试精确匹配
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
</body>
</html>
$ curl --request GET 'http://127.0.0.1:8699/aa/xxx'  #测试正则匹配
HelloWorld
$ curl --request GET 'http://127.0.0.1:8699/dd/xxx'  #测试不存的路由
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
</body>
</html>

可以看到效果一样

更多复杂使用请参阅 https://github.com/api7/lua-resty-radixtree

目录


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK