使用ngx_lua构建高并发应用

Posted 一拳超超人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用ngx_lua构建高并发应用相关的知识,希望对你有一定的参考价值。

        在之前的文章中,已经介绍了ngx_lua的一些基本介绍,这篇文章主要着重讨论一下如何通过ngx_lua同后端的memcached、Redis进行非阻塞通信。

1. Memcached

        在nginx中访问Memcached需要模块的支持,这里选用HttpMemcModule,这个模块可以与后端的Memcached进行非阻塞的通信。我们知道官方提供了Memcached,这个模块只支持get操作,而Memc支持大部分Memcached的命令。

        Memc模块采用入口变量作为参数进行传递,所有以$memc_为前缀的变量都是Memc的入口变量。memc_pass指向后端的Memcached Server。

  配置:

[plain]  view plain  copy  print ?
  1. #使用HttpMemcModule  
  2. location = /memc   
  3.     set $memc_cmd $arg_cmd;  
  4.     set $memc_key  $arg_key;  
  5.     set $memc_value $arg_val;  
  6.     set $memc_exptime $arg_exptime;  
  7.           
  8.     memc_pass '127.0.0.1:11211';  
  9.   

        输出:

[plain]  view plain  copy  print ?
  1. $ curl  'http://localhost/memc?cmd=set&key=foo&val=Hello'  
  2. $ STORED  
  3. $ curl  'http://localhost/memc?cmd=get&key=foo'  
  4. $ Hello  
        这就实现了memcached的访问,下面看一下如何在lua中访问memcached。

  配置:

[plain]  view plain  copy  print ?
  1. #在Lua中访问Memcached  
  2. location = /memc   
  3.     internal;   #只能内部访问  
  4.     set $memc_cmd get;  
  5.     set $memc_key  $arg_key;  
  6.     memc_pass '127.0.0.1:11211';  
  7.   
  8. location = /lua_memc   
  9.     content_by_lua '  
  10.         local res = ngx.location.capture("/memc",   
  11.             args =  key = ngx.var.arg_key   
  12.         )  
  13.         if res.status == 200 then  
  14.             ngx.say(res.body)  
  15.         end  
  16.     ';  
  17.   
        输出:

[plain]  view plain  copy  print ?
  1. $ curl  'http://localhost/lua_memc?key=foo'  
  2. $ Hello  
        通过lua访问memcached,主要是通过子请求采用一种类似函数调用的方式实现。首先,定义了一个memc location用于通过后端memcached通信,就相当于memcached storage。由于整个Memc模块时非阻塞的,ngx.location.capture也是非阻塞的,所以整个操作非阻塞。

2. Redis

        访问redis需要HttpRedis2Module的支持,它也可以同redis进行非阻塞通行。不过,redis2的响应是redis的原生响应,所以在lua中使用时,需要解析这个响应。可以采用LuaRedisModule,这个模块可以构建redis的原生请求,并解析redis的原生响应。

        配置:

[plain]  view plain  copy  print ?
  1. #在Lua中访问Redis  
  2. location = /redis   
  3.     internal;   #只能内部访问  
  4.     redis2_query get $arg_key;  
  5.     redis2_pass '127.0.0.1:6379';  
  6.    
  7. location = /lua_redis  #需要LuaRedisParser  
  8.     content_by_lua '  
  9.         local parser = require("redis.parser")  
  10.         local res = ngx.location.capture("/redis",   
  11.             args =  key = ngx.var.arg_key   
  12.         )  
  13.         if res.status == 200 then  
  14.             reply = parser.parse_reply(res.body)  
  15.             ngx.say(reply)  
  16.         end  
  17.     ';  
  18.   
        输出:

[plain]  view plain  copy  print ?
  1. $ curl  'http://localhost/lua_redis?key=foo'  
  2. $ Hello  
        和访问memcached类似,需要提供一个redis storage专门用于查询redis,然后通过子请求去调用redis。

3. Redis Pipeline

        在实际访问redis时,有可能需要同时查询多个key的情况。我们可以采用ngx.location.capture_multi通过发送多个子请求给redis storage,然后在解析响应内容。但是,这会有个限制,Nginx内核规定一次可以发起的子请求的个数不能超过50个,所以在key个数多于50时,这种方案不再适用。

        幸好redis提供pipeline机制,可以在一次连接中执行多个命令,这样可以减少多次执行命令的往返时延。客户端在通过pipeline发送多个命令后,redis顺序接收这些命令并执行,然后按照顺序把命令的结果输出出去。在lua中使用pipeline需要用到redis2模块的redis2_raw_queries进行redis的原生请求查询。

        配置:

[plain]  view plain  copy  print ?
  1. #在Lua中访问Redis  
  2. location = /redis   
  3.     internal;   #只能内部访问  
  4.   
  5.     redis2_raw_queries $args $echo_request_body;  
  6.     redis2_pass '127.0.0.1:6379';  
  7.    
  8.       
  9. location = /pipeline   
  10.     content_by_lua 'conf/pipeline.lua';  
  11.    
        pipeline.lua

[plain]  view plain  copy  print ?
  1. -- conf/pipeline.lua file  
  2. local parser = require(‘redis.parser’)  
  3. local reqs =    
  4.     ‘get’, ‘one’, ‘get’, ‘two’   
  5.   
  6. -- 构造原生的redis查询,get one\\r\\nget two\\r\\n  
  7. local raw_reqs =   
  8. for i, req in ipairs(reqs)  do  
  9.       table.insert(raw_reqs, parser.build_query(req))  
  10. end  
  11. local res = ngx.location.capture(‘/redis?’..#reqs,  body = table.concat(raw_reqs, ‘’) )  
  12.       
  13. if res.status and res.body then  
  14.        -- 解析redis的原生响应  
  15.        local replies = parser.parse_replies(res.body, #reqs)  
  16.        for i, reply in ipairs(replies)  do   
  17.           ngx.say(reply[1])  
  18.        end  
  19. end  
        输出:

[plain]  view plain  copy  print ?
  1. $ curl  'http://localhost/pipeline'  
  2. $ first  
  3.   second  

4. Connection Pool

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

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

        配置:

[plain]  view plain  copy  print ?
  1. http   
  2.     # 需要HttpUpstreamKeepaliveModule  
  3.     upstream redis_pool   
  4.         server 127.0.0.1:6379;  
  5.         # 可以容纳1024个连接的连接池  
  6.         keepalive 1024 single;  
  7.       
  8.       
  9.     server   
  10.         location = /redis   
  11.             …  
  12.             redis2_pass redis_pool;  
  13.           
  14.       
  15. nginx+lua+redis构建高并发应用(转)

    在高并发环境下该如何构建应用级缓存

    使用Redis构建全局并发锁

    Redis如何实现高并发分布式锁?

    nginx+ngx_lua支持WAF防护功能

    怎么实现springMVC 多线程并发