您的位置:首页 > 数据库 > Redis

Nginx+Lua+Mysql+Redis+Pool项目注意点

2015-01-14 14:48 274 查看
1. Nginx进程模型

Nginx采用多进程模型,单Master—多Worker,由Master处理外部信号、配置文件的读取及Worker的初始化,Worker进程采用单线程、非阻塞的事件模型(Event Loop,事件循环)来实现端口的监听及客户端请求的处理和响应,同时Worker还要处理来自Master的信号。由于Worker使用单线程处理各种事件,所以一定要保证主循环是非阻塞的,否则会大大降低Worker的响应能力。



2. Nginx处理Http请求的过程

表面上看,当Nginx处理一个来自客户端的请求时,先根据请求头的host、ip和port来确定由哪个server处理,确定了server之后,再根据请求的uri找到对应的location,这个请求就由这个location处理。实际Nginx将一个请求的处理划分为若干个不同阶段(phase),这些阶段按照前后顺序依次执行,也就是说NGX_HTTP_POST_READ_PHASE在第一个,NGX_HTTP_LOG_PHASE在最后一个。

NGX_HTTP_POST_READ_PHASE,     //0读取请求phase
NGX_HTTP_SERVER_REWRITE_PHASE,//1这个阶段主要是处理全局的(server block)的rewrite
NGX_HTTP_FIND_CONFIG_PHASE,   //2这个阶段主要是通过uri来查找对应的location,然后根据loc_conf设置r的相应变量
NGX_HTTP_REWRITE_PHASE,       //3这个主要处理location的rewrite
NGX_HTTP_POST_REWRITE_PHASE,  //4postrewrite,这个主要是进行一些校验以及收尾工作,以便于交给后面的模块。
NGX_HTTP_PREACCESS_PHASE,     //5比如流控这种类型的access就放在这个phase,也就是说它主要是进行一些比较粗粒度的access。
NGX_HTTP_ACCESS_PHASE,        //6这个比如存取控制,权限验证就放在这个phase,一般来说处理动作是交给下面的模块做的.这个主要是做一些细粒度的access
NGX_HTTP_POST_ACCESS_PHASE,   //7一般来说当上面的access模块得到access_code之后就会由这个模块根据access_code来进行操作
NGX_HTTP_TRY_FILES_PHASE,     //8try_file模块,就是对应配置文件中的try_files指令,可接收多个路径作为参数,当前一个路径的资源无法找到,则自动查找下一个路径
NGX_HTTP_CONTENT_PHASE,       //9内容处理模块
NGX_HTTP_LOG_PHASE            //10log模块

每个阶段上可以注册handler,处理请求就是运行每个阶段上注册的handler。Nginx模块提供的配置指令只会一般只会注册并运行在其中的某一个处理阶段。比如,set指令属于rewrite模块的,运行在rewrite阶段,deny和allow运行在access阶段。

3. 子请求(subrequest)

其实在Nginx 世界里有两种类型的“请求”,一种叫做“主请求”(main request),而另一种则叫做“子请求”(subrequest)。

所谓“主请求”,就是由 HTTP 客户端从 Nginx 外部发起的请求。比如,从浏览器访问Nginx就是一个“主请求”。

而“子请求”则是由 Nginx 正在处理的请求在 Nginx 内部发起的一种级联请求。“子请求”在外观上很像 HTTP 请求,但实现上却和 HTTP 协议乃至网络通信一点儿关系都没有。它是 Nginx 内部的一种抽象调用,目的是为了方便用户把“主请求”的任务分解为多个较小粒度的“内部请求”,并发或串行地访问多个 location 接口,然后由这些 location 接口通力协作,共同完成整个“主请求”。当然,“子请求”的概念是相对的,任何一个“子请求”也可以再发起更多的“子子请求”,甚至可以玩递归调用(即自己调用自己)。当一个请求发起一个“子请求”的时候,按照
Nginx 的术语,习惯把前者称为后者的“父请求”(parent request)。

“子请求”方式的通信是在同一个虚拟主机内部进行的,所以 Nginx 核心在实现“子请求”的时候,就只调用了若干个 C 函数,完全不涉及任何网络或者 UNIX 套接字(socket)通信。我们由此可以看出“子请求”的执行效率是极高的。

4. 协程(Coroutine)

协程类似一种多线程,与多线程的区别有:

1. 协程并非os线程,所以创建、切换开销比线程相对要小。

2. 协程与线程一样有自己的栈、局部变量等,但是协程的栈是在用户进程空间模拟的,所以创建、切换开销很小。

3. 多线程程序是多个线程并发执行,也就是说在一瞬间有多个控制流在执行。而协程强调的是一种多个协程间协作的关系,只有当一个协程主动放弃执行权,另一个协程才能获得执行权,所以在某一瞬间,多个协程间只有一个在运行。

4. 由于多个协程时只有一个在运行,所以对于临界区的访问不需要加锁,而多线程的情况则必须加锁。

5. 多线程程序由于有多个控制流,所以程序的行为不可控,而多个协程的执行是由开发者定义的所以是可控的。

Nginx的每个Worker进程都是在epoll或kqueue这样的事件模型之上,封装成协程,每个请求都有一个协程进行处理。这正好与Lua内建协程的模型是一致的,所以即使ngx_lua需要执行Lua,相对C有一定的开销,但依然能保证高并发能力。

5. ngx_lua原理

ngx_lua将Lua嵌入Nginx,可以让Nginx执行Lua脚本,并且高并发、非阻塞的处理各种请求。Lua内建协程,这样就可以很好的将异步回调转换成顺序调用的形式。ngx_lua在Lua中进行的IO操作都会委托给Nginx的事件模型,从而实现非阻塞调用。开发者可以采用串行的方式编写程序,ngx_lua会自动的在进行阻塞的IO操作时中断,保存上下文;然后将IO操作委托给Nginx事件处理机制,在IO操作完成后,ngx_lua会恢复上下文,程序继续执行,这些操作都是对用户程序透明的。

每个NginxWorker进程持有一个Lua解释器或者LuaJIT实例,被这个Worker处理的所有请求共享这个实例。每个请求的Context会被Lua轻量级的协程分割,从而保证各个请求是独立的。

ngx_lua采用“one-coroutine-per-request”的处理模型,对于每个用户请求,ngx_lua会唤醒一个协程用于执行用户代码处理请求,当请求处理完成这个协程会被销毁。每个协程都有一个独立的全局环境(变量空间),继承于全局共享的、只读的“comman data”。所以,被用户代码注入全局空间的任何变量都不会影响其他请求的处理,并且这些变量在请求处理完成后会被释放,这样就保证所有的用户代码都运行在一个“sandbox”(沙箱),这个沙箱与请求具有相同的生命周期。

得益于Lua协程的支持,ngx_lua在处理10000个并发请求时只需要很少的内存。根据测试,ngx_lua处理每个请求只需要2KB的内存,如果使用LuaJIT则会更少。所以ngx_lua非常适合用于实现可扩展的、高并发的服务。

6. 典型应用

官网上列出:

· Mashup’ing and processing outputs of various nginx upstream outputs(proxy, drizzle, postgres, redis, memcached, and etc) in Lua,

· doing arbitrarily complex access control and security checks in Luabefore requests actually reach the upstream backends,

· manipulating response headers in an arbitrary way (by Lua)

· fetching backend information from external storage backends (likeredis, memcached, mysql, postgresql) and use that information to choose whichupstream backend to access on-the-fly,

· coding up arbitrarily complex web applications in a content handlerusing synchronous but still non-blocking access to the database backends andother storage,

· doing very complex URL dispatch in Lua at rewrite phase,

· using Lua to implement advanced caching mechanism for nginxsubrequests and arbitrary locations.

7. Connection Pool

前面访问redis和memcached的例子中,在每次处理一个请求时,都会和后端的server建立连接,然后在请求处理完之后这个连接就会被释放。这个过程中,会有3次握手、timewait等一些开销,这对于高并发的应用是不可容忍的。这里引入connection pool来消除这个开销。

连接池需要HttpUpstreamKeepaliveModule模块的支持。

配置:

http {

# 需要HttpUpstreamKeepaliveModule

upstream redis_pool {

server 127.0.0.1:6379;

# 可以容纳1024个连接的连接池

keepalive 1024 single;

}

server {

location = /redis {



redis2_pass redis_pool;

}

}

}

这个模块提供keepalive指令,它的context是upstream。我们知道upstream在使用Nginx做反向代理时使用,实际upstream是指“上游”,这个“上游”可以是redis、memcached或是mysql等一些server。upstream可以定义一个虚拟server集群,并且这些后端的server可以享受负载均衡。keepalive 1024就是定义连接池的大小,当连接数超过这个大小后,后续的连接自动退化为短连接。连接池的使用很简单,直接替换掉原来的ip和端口号即可。

有人曾经测过,在没有使用连接池的情况下,访问memcached(使用之前的Memc模块),rps为20000。在使用连接池之后,rps一路飙到140000。在实际情况下,这么大的提升可能达不到,但是基本上100-200%的提高还是可以的。

5. 小结

这里对memcached、redis的访问做个小结。

1. Nginx提供了强大的编程模型,location相当于函数,子请求相当于函数调用,并且location还可以向自己发送子请求,这样构成一个递归的模型,所以采用这种模型实现复杂的业务逻辑。

2. Nginx的IO操作必须是非阻塞的,如果Nginx在那阻着,则会大大降低Nginx的性能。所以在Lua中必须通过ngx.location.capture发出子请求将这些IO操作委托给Nginx的事件模型。

3. 在需要使用tcp连接时,尽量使用连接池。这样可以消除大量的建立、释放连接的开销。

参考:
http://wiki.nginx.org/HttpUpstreamKeepaliveModule http://wiki.nginx.org/HttpRedis2Module http://wiki.nginx.org/HttpMemcModule
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: