http路由

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

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

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

路由匹配

这里我们先简化一下内容,假设我们已有 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. 参数匹配
  4. 自定义条件匹配
  5. ……

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

使用 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

目录

构建api gateway之 http路由实现的更多相关文章

  1. [转载] 构建微服务:使用API Gateway

    原文: http://mp.weixin.qq.com/s?__biz=MzA5OTAyNzQ2OA==&mid=206889381&idx=1&sn=478ccb35294c ...

  2. 微服务实战(二):使用API Gateway

    微服务实战(一):微服务架构的优势与不足 微服务实战(二):使用API Gateway 微服务实战(三):深入微服务架构的进程间通信 微服务实战(四):服务发现的可行方案以及实践案例 微服务实践(五) ...

  3. 微服务实战-使用API Gateway

    当你决定将应用作为一组微服务时,需要决定应用客户端如何与微服务交互.在单体式程序中,通常只有一组冗余的或者负载均衡的服务提供点.在微服务架构中,每一个微服务暴露一组细粒度的服务提供点.在本篇文章中,我 ...

  4. 使用API Gateway

    http://dockone.io/article/482 [编者的话]本系列的第一篇介绍了微服务架构模式.它讨论了采用微服务的优点和缺点,除了一些复杂的微服务,这种模式还是复杂应用的理想选择. Do ...

  5. 微服务实战(二):使用API Gateway - DockOne.io

    原文:微服务实战(二):使用API Gateway - DockOne.io [编者的话]本系列的第一篇介绍了微服务架构模式.它讨论了采用微服务的优点和缺点,除了一些复杂的微服务,这种模式还是复杂应用 ...

  6. 0601-Zuul构建API Gateway-API gateway简介、基础使用、路由配置、负载配置

    一.API Gateway简介 参看:http://www.cnblogs.com/bjlhx/p/8794437.html 二.zuul简介[路由器和过滤器:Zuul] 在微服务架构的组成部分进行路 ...

  7. 【springcloud】API Gateway 的路由和过滤(Zuul--1)

    转自:https://blog.csdn.net/pengjunlee/article/details/87084646 Zuul是什么? API Gateway 是随着微服务(Microservic ...

  8. 谈谈微服务中的 API 网关(API Gateway)

    前言 又是很久没写博客了,最近一段时间换了新工作,比较忙,所以没有抽出来太多的时间写给关注我的粉丝写一些干货了,就有人问我怎么最近没有更新博客了,在这里给大家抱歉. 那么,在本篇文章中,我们就一起来探 ...

  9. 聊聊 API Gateway 和 Netflix Zuul

    最近参与了公司 API Gateway 的搭建工作,技术选型是 Netflix Zuul,主要聊一聊其中的一些心得和体会. 本文主要是介绍使用 Zuul 且在不强制使用其他 Neflix OSS 组件 ...

  10. 微服务中的 API 网关(API Gateway)

    API 网关(API Gateway)提供高性能.高可用的 API 托管服务,帮助用户对外开放其部署在 ECS.容器服务等云产品上的应用,提供完整的 API 发布.管理.维护生命周期管理.用户只需进行 ...

随机推荐

  1. 最长不下降子序列(线段树优化dp)

    最长不下降子序列 题目大意: 给定一个长度为 N 的整数序列:A\(_{1}\),A\(_{2}\),⋅⋅⋅,A\(_{N}\). 现在你有一次机会,将其中连续的 K 个数修改成任意一个相同值. 请你 ...

  2. 部署grafana+telegraf+influxdb 及 配置 jmeter后端监听

    搞性能测试,可以搭建Grafana+Telegraf+InfluxDB 监控平台,监控服务器资源使用率.jmeter性能测试结果等. telegraf: 是一个用 Go 编写的代理程序,可收集系统和服 ...

  3. i春秋SQLi

    打开题目网页是个很简单的登录网页 先查看源码,抓包 都没找到可用的信息 依我所见这里应该就是一个注入 但是怎么输入都会回显username错误 直到输入admin 尝试admin# Admin'#   ...

  4. java:绘制图形

    java绘图类:Graphics类 绘图是高级程序中必备的技术,在很多方面都能用到,如:绘制闪屏图片,背景图片和组件外观等. 1.Graphics类 Graphics类是所有图形上下文的抽象基类,Gr ...

  5. python-面向对象属性的访问与self的理解

    属性访问 类属性与对象属性 在类中定义的名字,都是类的属性,细说的话,类有两种属性:数据属性和函数属性,可以通过__dict__访问属性的值,比如Person1.__dict__['student'] ...

  6. day 26 form表单标签 & CSS样式表-选择器 & 样式:背景、字体、定位等

    html常用标签 嵌套页面 <!-- 嵌套页面 --> <div> <!-- target属性值可以通过指定的iframe的name属性值, 实现超链接页面,在嵌套页面展 ...

  7. 【SQL进阶】【REPLACE/TIMESTAMPDIFF/TRUNCATE】Day01:增删改操作

    一.插入记录 1.插入多条记录 自己的答案: INSERT INTO exam_record(uid, exam_id, start_time, submit_time, score) VALUES ...

  8. 【Java SE进阶】Day02 Collection、Iterator、泛型

    一.Collection集合 1.概述 数组存元素,集合存对象(类型可以不一样) 2.框架分类 单列:Collection List ArrayList LinkedList Set HashSet ...

  9. 【每日一题】【DFS/回溯】2022年1月1日-113. 路径总和 II

    给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径. 叶子节点 是指没有子节点的节点. 来源:力扣(LeetCode)链接 ...

  10. Kubernetes(k8s)存储管理之数据卷volumes(二):hostPath数据卷

    目录 一.系统环境 二.前言 三.hostPath数据卷 3.1 hostPath数据卷概览 3.2 创建有hostPath卷的pod 一.系统环境 服务器版本 docker软件版本 Kubernet ...