您的位置:首页 > 运维架构 > Nginx

nginx_lua案例分析:动态路由实现

2015-08-17 09:28 886 查看
    这里的路由指的是在web开发中,访问路径以及具体实现内容的映射。比如,/a映射到某个具体的页面,这个就称之为一个路由。而动态路由,顾名思义就是动态添加这种路由映射关系。

    在nginx中,通过rewrite和proxy_pass来实现路由映射或者说反向代理,但是这种关系按照传统的配置必须写死在配置文件中,然后通过快速"无缝"重启nginx。虽说是无缝,但是其繁琐的配置和枯燥的重启操作还是无法避免。

    最近,在github上看到个项目ceryx,是nginx结合lua进行动态路由的映射的,也就是上面所说的映射关系,用lua来管理,虽然是比较简单的实现,但是可以分析学习下。该项目通过用redis的<source,target>结构来保存这种映射关系,这样在nginx中可以快速获得这种关系,以便做出处理,同时,采用HTTP的形式暴露对redis这种路由关系进行管理的接口。

from ceryx.db import RedisRouter

resource_fields = {
'source': fields.String,
'target': fields.String,
}

parser = reqparse.RequestParser()
parser.add_argument(
'source', type=str, required=True, help='Source is required'
)
parser.add_argument(
'target', type=str, required=True, help='Target is required'
)

router = RedisRouter.from_config()

def lookup_or_abort(source):
"""
Returns the target for the given source, or aborts raising a 404
"""
try:
return {'source': source, 'target': router.lookup(source)}
except RedisRouter.LookupNotFound:
abort(
404, message='Route with source {} doesn\'t exist'.format(source)
)

class Route(Resource):
"""
Resource describing a single Route. Supports GET, DELETE and PUT. The
format is the following:
```
{
"source": "[source]",
"target": "[target]"
}
```
"""

@marshal_with(resource_fields)
def get(self, source):
"""
Fetches the route with the given source
"""
route = lookup_or_abort(source)
return route

@marshal_with(resource_fields)
def delete(self, source):
"""
Deletes the route with the given source
"""
route = lookup_or_abort(source)
router.delete(source)
return route, 204

@marshal_with(resource_fields)
def put(self, source):
"""
Creates or updates the route with the given source, pointing to the
given target
"""
args = parser.parse_args()
router.insert(args['source'], args['target'])
return args, 201

    上述的代码,是进行路由管理的http api,当然,这个不是我们分析的重点。先来看一下,在这个项目里面,对nginx的配置是怎样的
upstream fallback {
server www.something.com;
}

server {
listen 80;
default_type text/html;

location / {
set $container_url "fallback";
resolver 8.8.8.8;

# Lua files
access_by_lua_file lualib/router.lua;//切入点

# Proxy configuration
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect ~^(http://[^:]+):\d+(/.+)$ $2;
proxy_redirect / /;

# Upgrade headers
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

proxy_pass http://$container_url$request_uri; }
...
}可以简单的看到,这个配置相当的常见,跟普通的反向代理并没有什么不同,真正的切入点在于access_by_lua_file里面的router.lua代码。
local container_url = ngx.var.container_url//拿到配置文件中的container_url
local host = ngx.var.host //拿到请求的时候的host,比如我们请求http://www.xxx.com/a.html 那这里的host就是www.xxx.com

-- Check if key exists in local cache
local cache = ngx.shared.ceryx
local res, flags = cache:get(host) //直接在nginx cache中拿host对应的路由映射,如果存在则直接返回结果
if res then
ngx.var.container_url = res
return
end

local redis = require "resty.redis" // redis的连接代码 每次都会连接redis,
local red = redis:new()<span style="white-space:pre"> </span>//<span style="font-family: Arial, Helvetica, sans-serif;">这个操作比较相对比较耗时 所以接下来的操作才会在本地cache中存对应的关系</span>
red:set_timeout(100) -- 100 ms
local redis_host = os.getenv("CERYX_REDIS_HOST")
if not redis_host then redis_host = "127.0.0.1" end
local redis_port = os.getenv("CERYX_REDIS_PORT")
if not redis_port then redis_port = 6379 end
local res, err = red:connect(redis_host, redis_port)

-- Return if could not connect to Redis
if not res then
return
end

-- Construct Redis key
local prefix = os.getenv("CERYX_REDIS_PREFIX")
if not prefix then prefix = "ceryx" end
local key = prefix .. ":routes:" .. host

-- Try to get target for host
res, err = red:get(key)
if not res or res == ngx.null then
-- Construct Redis key for $wildcard
key = prefix .. ":routes:$wildcard"
res, err = red:get(key)
if not res or res == ngx.null then
return
end
ngx.var.container_url = res
return
end

-- Save found key to local cache for 5 seconds
cache:set(host, res, 5) // 在redis取出的映射关系存到redis的cache中避免下次继续连redis操作

ngx.var.container_url = res可以看出,这个项目分享的内容,并不尽人意,只是简单的提供了一种思路,如何去实现动态的proxy_pass,在这个基础上我们可以进行对url rewrite的扩展。另外,这里的host对应的routeHost 如果只是IP,那样的话,会造成proxy_pass的时候后端的单点,也就是没有应用到upstream,没办法进行负载均衡。但是如果routeHost的值是upstream的话,则,在配置文件中,依然要写死,所以,没有办法做到完全意义上的动态路由。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  redis lua nginx