OpenResty

OpenResty

零、从B站崩溃说起

  前不久B站挂了迅速登上全网热搜,问题在于B站使用的作为服务发现模块的OpenResty[1]中的Lua[2]脚本发生了算术运算错误产生死循环导致。而据参考文中[3]介绍

B站在19年9月份从Tengine迁移到了OpenResty,基于其丰富的Lua能力开发了一个服务发现模块,从我们自研的注册中心同步服务注册信息到Nginx共享内存中,SLB在请求转发时,通过Lua从共享内存中选择节点处理请求,用到了OpenResty的lua-resty-balancer模块。到发生故障时已稳定运行快两年时间。

  其实,无论是B站还是360,UPYUN,阿里云,新浪,腾讯网,去哪儿网,酷狗音乐等都是 OpenResty 的深度用户。我们经常用到的 12306 的余票查询功能,或者是京东的商品详情页,这些高流量的背后,其实都是 OpenResty 在默默地提供服务。

一、初窥OpenResty

  OpenResty(又称:ngx_openresty) 是一个基于 nginx的可伸缩的 Web 平台,由中国人章亦春发起,提供了很多高质量的第三方模块。它是一个强大的 Web 应用服务器,Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,更主要的是在性能方面,OpenResty在诞生之初就支持了协程,并基于此实现了同步非阻塞的编程模式,可以快速构造出足以胜任 10K 以上并发连接响应的超高性能 Web 应用系统。

  OpenResty 简单理解成封装了nginx,并且集成了LUA脚本,开发人员只需要简单的其提供了模块就可以实现相关的逻辑,而不再像之前,还需要在nginx中自己编写lua的脚本,再进行调用了。[5]

  OpenResty 项目在如何科学和动态地调试代码上,花费了大量的精力,可以说是达到了极致。openresty-systemtap-toolkitstapxx 这两个 OpenResty 的项目,都基于 systemtap 这个动态调试和追踪工具。使用 systemtap 最大的优势,便是实现活体分析,同时对目标程序完全无侵入。打个比方,systemtap,就像是我们去医院照了个 CT,无痛无感知。更棒的是,systemtap 可以生成直观的火焰图[6]来做性能分析,常见的火焰图类型有 On-CPU(cpu占用时间),Off-CPU(阻塞时间),还有 Memory(内存申请/释放函数调用次数),Hot/Cold(on-CPU和off-CPU结合),Differential 等等。

  火焰图有以下特征(这里以 on-cpu 火焰图为例):

    - 每一列代表一个调用栈,每一个格子代表一个函数
    - 纵轴展示了栈的深度,按照调用关系从下到上排列。最顶上格子代表采样时,正在占用 cpu 的函数。
    - 横轴的意义是指:火焰图将采集的多个调用栈信息,通过按字母横向排序的方式将众多信息聚合在一起。需要注意的是它并不代表时间。
    - 横轴格子的宽度代表其在采样中出现频率,所以一个格子的宽度越大,说明它是瓶颈原因的可能性就越大。
    - 火焰图格子的颜色是随机的暖色调,方便区分各个调用信息。
    - 其他的采样方式也可以使用火焰图, on-cpu 火焰图横轴是指 cpu 占用时间,off-cpu 火焰图横轴则代表阻塞时间。
    - 采样可以是单线程、多线程、多进程甚至是多 host

二、Nginx及Lua

  传统的 Web 服务器,比如 NGINX,如果发生任何的变动,都需要你去修改磁盘上的配置文件,然后重新加载才能生效,这也是因为它们并没有提供 API,来控制运行时的行为。所以,在需要频繁变动的微服务领域,NGINX 虽然有多次尝试,但毫无建树。OpenResty 是由脚本语言 Lua 来控制逻辑的,而动态,便是 Lua 天生的优势。通过 OpenResty 中 lua-nginx-module 模块中提供的 Lua API,可以动态地控制路由、上游、SSL 证书、请求、响应等。甚至更进一步,可以在不重启 OpenResty 的前提下,修改业务的处理逻辑,并不局限于 OpenResty 提供的 Lua API。

1.Nginx

  1. 在 OpenResty 的开发中,我们需要注意下面几点:
  - 要尽可能少地配置 nginx.conf;
  - 避免使用 if、set 、rewrite 等多个指令的配合;
  - 能通过 Lua 代码解决的,就别用 NGINX 的配置、变量和模块来解决。

  2. 每个指令都有自己适用的上下文(Context),也就是 NGINX 配置文件中指令的作用域。最上层的是 main,里面是和具体业务无关的一些指令,另外,上下文是有层级关系的,指令不能运行在错误的上下文中,NGINX 在启动时会检测 nginx.conf 是否合法。
  3. NGINX 不仅可以处理 HTTP 请求 和 HTTPS 流量,还可以处理 UDP 和 TCP 流量。其中,七层的放在 HTTP 中,四层的放在 stream 中。在 OpenResty 里面, lua-nginx-module 和 stream-lua-nginx-module 分别和这俩对应。NGINX 支持的功能,OpenResty 并不一定支持,需要看 OpenResty 的版本号。OpenResty 的版本号是和 NGINX 保持一致的,所以很容易识别。

2.Lua

  1. Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
  2. 应用场景:
    - 游戏开发(游戏外挂原理,有兴趣可以看看)
    - 独立应用脚本
    - Web 应用脚本
    - 扩展和数据库插件如:MySQL Proxy 和 MySQL WorkBench
    - 安全系统,如入侵检测系统
    - redis中嵌套调用实现类似事务的功能
    - web容器中应用处理一些过滤 缓存等等的逻辑,例如nginx。

  3. lua简单代码:运行环境

  4. OpenResty 并没有直接使用 LuaJIT 官方提供的 2.1.0-beta3 版本,而是在此基础上,扩展了自己的 fork: [openresty-luajit2]。这些独有的 API,都是在实际开发 OpenResty 的过程中,出于性能方面的考虑而增加的。为什么不直接使用 Lua,而是要用自己维护的 LuaJIT 呢?其实,最主要的原因,还是 LuaJIT 的性能优势。所谓 LuaJIT 的性能优化,本质上就是让尽可能多的 Lua 代码可以被 JIT 编译器生成机器码,而不是回退到 Lua 解释器的解释执行模式

三、应用

样例:广告缓存的载入和读取:

  1. 分析

    首页门户系统需要展示各种各样的广告数据,针对这种变更频率低的数据,如何提升访问速度?

    通常情况下,首页(门户系统的流量一般非常的高)不适合直接通过mysql数据库直接访问的方式来获取展示。

    ​ a. 首先访问nginx ,我们可以采用缓存的方式,先从nginx本地缓存中获取,获取到直接响应

    ​ b. 如果没有获取到,再次访问redis,我们可以从redis中获取数据,如果有 则返回,并缓存到nginx中

    ​ c. 如果没有获取到,再次访问mysql,我们从mysql中获取数据,再将数据存储到redis中,返回。

    而这里面,我们都可以使用LUA脚本嵌入到程序中执行这些查询相关的业务。如图所示:

  2. 实现

    (1)实现思路-查询数据放入redis中

    定义请求:用于查询数据库中的数据更新到redis中。

    ​ a.连接mysql ,按照广告分类ID读取广告列表,转换为json字符串。

    ​ b.连接redis,将广告列表json字符串存入redis 。

    请求:
    	/update_content
    参数:
    	id  --指定广告分类的id
    返回值:
    	json
    

    请求地址:<http://192.168.211.132/update_content?id=1>

    创建/root/lua目录,在该目录下创建update_content.lua: 目的就是连接mysql 查询数据 并存储到redis中。

    修改/usr/local/openresty/nginx/conf/nginx.conf文件: 添加头信息,和 location信息

    定义lua缓存命名空间,修改nginx.conf,添加如下代码即可:

    请求<http://{ip}/update_content?id=1>可以实现缓存的添加

    (2)实现思路-从redis中获取数据

    实现思路:

    定义请求,用户根据广告分类的ID 获取广告的列表。通过lua脚本直接从redis中获取数据即可。

    请求:/read_content
    参数:id
    返回值:json
    

    如果请求都到redis,redis压力也很大,所以我们一般采用多级缓存的方式来减少下游系统的服务压力。参考基本思路图的实现。

    • 先查询openresty本地缓存 如果 没有

    • 再查询redis中的数据,如果没有

    • 再查询mysql中的数据,但凡有数据 则返回即可。

    在/root/lua目录下创建read_content.lua:

    测试地址:http://{ip}/read_content?id=1,此时会获取分类ID=1的所有广告信息。

参考

  1. OpenResty官网
  2. Lua官网
  3. 2021.07.13 我们是这样崩的
  4. openResty从入门到实战
  5. 2019年黑马项目-畅购商城SpringCloud微服务实战 p73~p84
  6. 程序员精进之路:性能调优利器--火焰图
posted @   快打球去吧  阅读(705)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示