加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

使用ngx_lua构建高并发应用(2)

发布时间:2020-12-14 22:17:34 所属栏目:大数据 来源:网络整理
导读:? 在 之前的文章中,已经介绍了ngx_lua的一些基本介绍,这篇文章主要着重讨论一下如何通过ngx_lua同后端的memcached、redis进行非阻塞通信。 1. Memcached ? ? ? ? 在Nginx中访问Memcached需要模块的支持,这里选用HttpMemcModule,这个模块可以与后端的Memc
? 在 之前的文章中,已经介绍了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?onernget?tworn??
  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. }??
? ? ? ? 这个模块提供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连接时,尽量使用连接池。这样可以消除大量的建立、释放连接的开销。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读