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. onps栈移植说明(1)——onps栈的配置及裁剪

    onps栈的移植涉及几个部分:1)系统配置及裁剪:2)基础数据类型定义:3)RTOS适配层实现:4)编写网卡驱动并注册网卡.本文作为onps栈移植的指导性文件将给出一般性的移植说明及建议,具体的移植样 ...

  2. 2022春每日一题:Day 34

    题目:lowbit求和 (没有找到哪个公开题库有这个题) 题意:求数组中任意一对数的异或和的lowbit的总和. 对于异或,二进制位中两个数相等则为0,反之为1,而且此题是要求lowbit,那我们利用 ...

  3. Web安全Day1 - SQL注入、漏洞类型

    Web安全Day1 - SQL注入.漏洞类型 1. SQL注入 1.1 漏洞简介 1.2 漏洞原理 1.3 漏洞危害 2. SQL漏洞类型 2.1 区分数字和字符串 2.2 内联SQL注入 2.3 报 ...

  4. 7 STL-deque

    ​ 重新系统学习c++语言,并将学习过程中的知识在这里抄录.总结.沉淀.同时希望对刷到的朋友有所帮助,一起加油哦!  生命就像一朵花,要拼尽全力绽放!死磕自个儿,身心愉悦! 写在前面,本篇章主要介绍S ...

  5. 自动注册实体类到EntityFramework Core上下文,并适配ABP及ABP VNext

    继上篇文章(EF Core懒人小技巧之拒绝DbSet)之后,最近笔者把这个小功能单独封装成一个扩展方法并开源,欢迎交流和Star~ GitHub: EntityFrameworkCore.Extens ...

  6. MQ系列8:数据存储,消息队列的高可用保障

    MQ系列1:消息中间件执行原理 MQ系列2:消息中间件的技术选型 MQ系列3:RocketMQ 架构分析 MQ系列4:NameServer 原理解析 MQ系列5:RocketMQ消息的发送模式 MQ系 ...

  7. python关于error: invalid command 'bdist_wheel报错的解决

    看了很多解决办法,大部分在扯去下载一个 .whl 源文件然后在pip 安装,经过我亲自测试执行完这句即可解决! pip3 install wheel

  8. Java内存马的学习总结

    1.前置知识 Java Web三大组件 Servlet Servlet是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中 ...

  9. MyBatis02:流程分析、注解、代理dao实现CRUD、参数深入、传统DAO、配置

    今日内容 回顾 mybatis的自定义.分析和环境搭建 完善基于注解的mybatis mybatis的curd(基于代理dao的方式)※ mybatis的参数深入及结果集的深入 mybatis中基于传 ...

  10. Spring Boot回顾

    一.概述 1.Spring的优缺点 优点 无需开发ELB,通过IOC和AOP,就可以使用POJO(简单的Java对象)实现ELB的功能 缺点: 依赖管理导入Maven耗时耗力 注解繁琐 2.Sprin ...