构建api gateway之 http路由实现
http路由
路由是指路由器从一个接口上收到数据包,根据数据包的目的地址进行定向并转发到另一个接口的过程。
而这里的http路由其实等同于web开发中,根据http相关参数(比如url、http method)分配到对应的处理程序。
借用web框架的示意图,其作用如下
路由匹配
这里我们先简化一下内容,假设我们已有 upstream ip、port,现在只需能区分各种请求怎么样对应到这些不同的upstream上,不必关心能否做改写请求啊、熔断啊等等复杂情况
那么怎么实现路由匹配呢?
通常为以下两种方式
字典+正则表达式
字典用于匹配精确的结果(比如 url == /login 情况),字典的特性保证这类匹配具有超高性能
正则表达式用于匹配复杂模糊的结果(比如 url 以 .html 为后缀的所有请求), 当然多项正则表达式只能依次遍历,性能肯定存在问题(为了缓解性能问题,通常会使用缓存做优化)
前缀树
前缀树,又称字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。
其由于插入和查询的效率很高,非常适合路由匹配的情况
虽然理论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
这个函数的内容,
不过上述简单实现肯定无法支持以下的一些复杂场景
- 正则冲突 (一般会引入优先级顺序支持,或者默认加入的顺序)
- host隔离
- 参数匹配
- 自定义条件匹配
- ……
所以一个完整的路由实现都很复杂,毕竟支持的场景挺多的
使用 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路由实现的更多相关文章
- [转载] 构建微服务:使用API Gateway
原文: http://mp.weixin.qq.com/s?__biz=MzA5OTAyNzQ2OA==&mid=206889381&idx=1&sn=478ccb35294c ...
- 微服务实战(二):使用API Gateway
微服务实战(一):微服务架构的优势与不足 微服务实战(二):使用API Gateway 微服务实战(三):深入微服务架构的进程间通信 微服务实战(四):服务发现的可行方案以及实践案例 微服务实践(五) ...
- 微服务实战-使用API Gateway
当你决定将应用作为一组微服务时,需要决定应用客户端如何与微服务交互.在单体式程序中,通常只有一组冗余的或者负载均衡的服务提供点.在微服务架构中,每一个微服务暴露一组细粒度的服务提供点.在本篇文章中,我 ...
- 使用API Gateway
http://dockone.io/article/482 [编者的话]本系列的第一篇介绍了微服务架构模式.它讨论了采用微服务的优点和缺点,除了一些复杂的微服务,这种模式还是复杂应用的理想选择. Do ...
- 微服务实战(二):使用API Gateway - DockOne.io
原文:微服务实战(二):使用API Gateway - DockOne.io [编者的话]本系列的第一篇介绍了微服务架构模式.它讨论了采用微服务的优点和缺点,除了一些复杂的微服务,这种模式还是复杂应用 ...
- 0601-Zuul构建API Gateway-API gateway简介、基础使用、路由配置、负载配置
一.API Gateway简介 参看:http://www.cnblogs.com/bjlhx/p/8794437.html 二.zuul简介[路由器和过滤器:Zuul] 在微服务架构的组成部分进行路 ...
- 【springcloud】API Gateway 的路由和过滤(Zuul--1)
转自:https://blog.csdn.net/pengjunlee/article/details/87084646 Zuul是什么? API Gateway 是随着微服务(Microservic ...
- 谈谈微服务中的 API 网关(API Gateway)
前言 又是很久没写博客了,最近一段时间换了新工作,比较忙,所以没有抽出来太多的时间写给关注我的粉丝写一些干货了,就有人问我怎么最近没有更新博客了,在这里给大家抱歉. 那么,在本篇文章中,我们就一起来探 ...
- 聊聊 API Gateway 和 Netflix Zuul
最近参与了公司 API Gateway 的搭建工作,技术选型是 Netflix Zuul,主要聊一聊其中的一些心得和体会. 本文主要是介绍使用 Zuul 且在不强制使用其他 Neflix OSS 组件 ...
- 微服务中的 API 网关(API Gateway)
API 网关(API Gateway)提供高性能.高可用的 API 托管服务,帮助用户对外开放其部署在 ECS.容器服务等云产品上的应用,提供完整的 API 发布.管理.维护生命周期管理.用户只需进行 ...
随机推荐
- nrf52——DFU升级OTA升级方式详解(基于SDK开发例程)
在我们开始前,默认你已经安装好了一些基础工具,如nrfutil,如果你没有安装过请根据官方中文博客去安装好这些基础工具,连接如下:Nordic nRF5 SDK开发环境搭建(nRF51/nRF52芯片 ...
- Maven 聚合工程的创建
简单场景举例 聚合工程创建示例 说明: 创建 Maven Project:表示创建 maven 项目,new Project 方式创建 创建 Maven Module:表示创建 maven 项目,ne ...
- 云实例初始化工具cloud-init源码分析
源码分析 代码结构 cloud-init的代码结构如下: cloud-init ├── bash_completion # bash自动补全文件 │ └── cloud-init ├── Chan ...
- 版本控制工具Git介绍-01
使用版本控制工具是为了方便团队开发,比如多人共同维护一个项目的时候,用版本控制工具可以很方便的维护项目代码,如果哪天你改了一个版本,出问题了,我们也可以很快的找到你改了什么,这里介绍使用比较多的版本控 ...
- Spring Boot框架下实现Excel服务端导入导出
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.今天 ...
- 详解Native Memory Tracking之追踪区域分析
摘要:本篇图文将介绍追踪区域的内存类型以及 NMT 无法追踪的内存. 本文分享自华为云社区<[技术剖析]17. Native Memory Tracking 详解(3)追踪区域分析(二)> ...
- __init__、__all__
在python中 引用模块包的时候,要先进入此模块的__init__.py中畅游一遍,因此,我们多次需要一个语句的时候,就可以将这些语句写入到__init__.py中: 在使用*号的时候我们可以用__ ...
- AR手势识别交互,让应用更加“得心应手”
现如今, AR技术不断发展,人们不再满足于运用键盘.鼠标等简单器械来实现传统的人机交互模式.随着用户接触机器的多样化,繁琐的操作不但对一些用户有门槛,而且还增加其学习成本:如果能用自然且符合日常生活习 ...
- Servlet层
package com.neu.servlet; import java.io.IOException;import java.io.PrintWriter;import java.util.Arra ...
- 重学c#系列——linq(2) [二十八]
前言 前文提及到了一些基础的linq的基础,那么这一节是一些补充. 正文 关于一个orderby的问题. 比如我们输入两个order by. 这里告诉我们多个order by是没有意义的,如果多个那么 ...