openresty开发

nginx请求

  1. ngx.exec:nginx跳转;跳转到其他的location中执行。但仅限nginx内部的location。
  2. ngx.redirect:和nginx.exec相似,但支持外部跳转。
  3. ngx.location.capture_multi:并发请求;但仅限nginx内部的location,会缓冲整个请求到内存中。
    1. 发起请求时,get参数可以用table或者转义字符串,body不可
      ngx.location.capture(
      '/print_param',
      {
      method = ngx.HTTP_POST,
      args = {a = 1, b = '2&1'},
      body = ngx.encode_args({c = 3, d = '4&'})
      }

  4. http包中multi方法:概念上与ngx.location.capture_multi相似,但支持外部接口。

读取请求参数:


  POST:

  1.ngx.req.read_body() -- 解析 body 参数之前一定要先读取 body

  ngx.req.get_post_args()

  2.get_body_data()

  这两种都可以获取post参数,第二种需要开启 lua_need_request_body on或先调用ngx.req.read_body() ;

 

  GET:
  ngx.req.get_uri_args()

 由于 Nginx 是为了解决负载均衡场景诞生的,所以它默认是不读取 body 的行为,会对 API Server 和 Web Application 场景造成一些影响。根据需要正确读取、丢弃 body 对 OpenResty 开发是至关重要的。

 ngx.say 或 ngx.print区别:

ngx.say 会对输出响应体多输出一个 \n,两者均为异步输出

共享变量:

 ngx.ctx仅在当前请求内共享变量,每个请求,包括子请求,都有一份自己的 ngx.ctx 表,相对独立。(类似java threadLocal)

ngx.var

 缓存:

  1.Lua shared dict

lua_shared_dict my_cache 128m;

  2.Lua LRU cache

shared.dict 使用的是共享内存,每次操作都是全局锁,如果高并发环境,不同 worker 之间容易引起竞争。所以单个 shared.dict 的体积不能过大。
lrucache 是 worker 内使用的,由于 Nginx 是单进程方式存在,所以永远不会触发锁,效率上有优势,并且没有 shared.dict 的体积限制,内存上也更弹性,但不同 worker 之间数据不同享,同一缓存数据可能被冗余存储。

IO密集型与CPU密集型:

1.IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)

2.计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等

Nginx与Apache对于高并发处理上的区别。
对于Apache,每个请求都会独占一个工作线程,当并发数到达几千时,就同时有几千的线程在处理请求了。这对于操作系统来说,占用的内存非常大,线程的上下文切换带来的cpu开销也很大,性能就难以上去,同时这些开销是完全没有意义的。
对于Nginx来讲,一个进程只有一个主线程,通过异步非阻塞的事件处理机制,实现了循环处理多个准备好的事件,从而实现轻量级和高并发。

为何推荐worker的个数为cpu的个数?
因为更多的worker数,只会导致进程相互竞争cpu资源,从而带来不必要的上下文切换

Nginx比较Apache:事件驱动适合于IO密集型服务,多进程或线程适合于CPU密集型服务

1)Nginx更主要是作为反向代理,而非Web服务器使用。其网络模式是事件驱动(select、poll、epoll)。
2)事件驱动的本质还是IO事件,应用程序在多个IO句柄间快速切换,实现所谓的异步IO。
3)事件驱动服务器,最适合做的就是这种IO密集型工作,如反向代理,它在客户端与WEB服务器之间起一个数据中转作用,纯粹是IO操作,自身并不涉及到复杂计算。
4)反向代理用事件驱动来做,显然更好,一个工作进程就可以run了,没有进程、线程管理的开销,CPU、内存消耗都小。
5)当然,Nginx也可以是多进程 + 事件驱动的模式,几个进程跑libevent,不需要Apache那样动辄数百的进程数。
6)Nginx处理静态文件效果也很好,那是因为静态文件本身也是磁盘IO操作,处理过程一样。至于说多少万的并发连接,这个毫无意义。

我随手写个网络程序都能处理几万7)的并发,但如果大部分客户端阻塞在那里,就没什么价值。

再看看Apache或者Resin这类应用服务器,之所以称他们为应用服务器,是因为他们真的要跑具体的业务应用,如科学计算、图形图像、数据库读写等。

它们很可能是CPU密集型的服务,事件驱动并不合适。

1)例如一个计算耗时2秒,那么这2秒就是完全阻塞的,什么event都没用。想想MySQL如果改成事件驱动会怎么样,一个大型的join或sort就会阻塞住所有客户端。
2)这个时候多进程或线程就体现出优势,每个进程各干各的事,互不阻塞和干扰。当然,现代CPU越来越快,单个计算阻塞的时间可能很小,但只要有阻塞,

事件编程就毫无优势。所以进程、线程这类技术,并不会消失,而是与事件机制相辅相成,长期存在。
总结之,事件驱动适合于IO密集型服务,多进程或线程适合于CPU密集型服务,它们各有各的优势,并不存在谁取代谁的倾向


如果在请求中,需要做一些跟返回终端无关的操作,比如日志推送、数据统计等。可以通过

异步执行:

ngx.eof()  
它可以即时关闭连接,把数据返回给终端,后面的代码操作还会运行。
需要注意的是,你不能任性的把阻塞的操作加入代码,即使在 ngx.eof()之后。 虽然已经返回了终端的请求,但是,Nginx 的 worker 还在被你占用。所以在 keep alive 的情况下,本次请求的总时间,会把上一次 eof() 之后的时间加上。 如果你加入了阻塞的代码,Nginx 的高并发就是空谈。

 定时器:

timer ,每个 timer 都运行在独立的协程里,所以无论time执行多少次,counter变量每次都是1。

如果要在 timer 的每次触发中共享变量,你有两个选择:

  1. 通过函数参数,把每个变量都传递一遍。
  2. 把要共享的变量当作模块变量。

(当然也可以选择在 init_worker_by_lua* 里面、ngx.timer.* 外面定义真正的全局变量,不过不太推荐罢了)

init_worker_by_lua_block {
    local delay = 5
    local handler
    handler = function()
        counter = counter or 0
        counter = counter + 1
        ngx.log(ngx.ERR, counter)
        local ok, err = ngx.timer.at(delay, handler)
        if not ok then
            ngx.log(ngx.ERR, "failed to create the timer: ", err)
            return
        end
    end
    local ok, err = ngx.timer.at(delay, handler)
    if not ok then
        ngx.log(ngx.ERR, "failed to create the timer: ", err)
        return
    end
}
限速
  1. limit_rate 限制响应速度
  2. limit_conn 限制连接数
  3. limit_req 限制请求数

ngx.shared.DICT 的实现是采用红黑树实现,当申请的缓存被占用完后如果有新数据需要存储则采用 LRU 算法淘汰掉“多余”数据。
使用长连接keepalive提升性能,连接redis等。

 

posted @ 2020-03-24 23:28  konami  阅读(614)  评论(0编辑  收藏  举报