实战系列 动手做nginx插件开发(上)
nginx在反向代理领袖做头把交椅已经有些年头了,今天写篇文章,把自己觉得nginx存在的一些自认识比较优秀架构的点总结一下,后面分享一个开发自己nginx扩展的通用思路。
我使用nginx的年头不可谓不长,但是之前从来没有深入挖掘一下它底层逻辑的想法。早期头脑中关于这厮的概念就是个apache
的替代品,后面使用golang做开发以后,到处都在强调golang协程是如何如何的高性能,跟说nginx高性能如出一辙、甚至有过之而无不及,于是我就准备一探个究竟,于是有了我的某某文章。但是当自己实际部署了很多应用以后,才发现nginx与整个高性能、高可用体系的关系如何之紧密。无论是你用docker、k8s进行容器编排模式的服务部署,还是用传统的一台服务器一台服务器的敲命令、编译、安装进行服务部分。负载均衡、路由入口、反向代理这些场景都少不了nginx身影的出现。
这里要对编写Dockerfile
多说几句。编写一个高性能的nginx类Dockerfile要考虑的东西有很多,nginx作为反向代理提供服务时候,如何让它为支撑系统性能服务的组件的安装及正确的配置就显得尤为重要。比如说针对某些地理区域的访问流量虽然能让数据好看,却不能实际带来成交,反而浪费了带宽,所以,针对于地理域名的流量做限定访问就是可做的、能立竿见影的事,此时配置geoIP
扩展的安装和配置就显得尤其重要。对于经常发起ddos攻击的ip地址,做统一的黑名单管理,blacklist
扩展就显得尤其重要。对于前端界面,针对于资源文件、图片格式的webp
化就能取得加载时间和用户体验上不少的收益,所以高效的图片压缩就显得尤其重要,总之,你要明白每个扩展要解决什么问题,在Dockerfile输出的时候就能获得最大收益。我还见过很多更规范的作法,比如说对于/usr/sbin/www/html
目录,使用monitor
对着目录有着严格的权限检查,也就是你随便改目录下文件的权限及所有者都是不被允许的,monitor.d
目录下配置文件中的配置会声明出对于目录做哪些检查,对于服务做心跳检测,保证服务出现故障能第一时间通知维护者等等,总之这是一个常用常新的过程。
我第一次脚踏实地研究如何高性能部署nginx是某次我上线一个应用以后,由于服务器部署在香港,而且硬件只有2核2G,用google insight跑个性能测试,发现惨不忍睹,于是开始了自己的服务器调优之旅。由于我使用的部署方式是docker swarm,不可避免的,如果想把nginx一些高性能部署所需使用到的扩展引入进来,就需自己编写Dockerfile文件,把类似于gzip、brotli、upsteam、map、geo、whitelist
等好用的扩展引入进来,这过程就是一个不断探索与实践的过程,最终这么一通折腾下来,由于当时部署的是php
程序,所以开启了fastcgi_cache
,由于使用的是mysql
,所以开启了针对mysql
的redis
缓存,同时针对一些类型的数据,也让redis
直接作为响应服务成为一种可能,不必再经过一层php-fpm
的处理,再通过http
响应回数据。当然可能收益最大的部分是因为我配置了pagespeed
扩展,肉眼可见的发现页面响应的效率高了很多,这牵扯到了该扩展对于很多前端资源的优化,比如说常见的使用gzip
或者brotli
对于js、css
和图片进行压缩,针对一些通用资源cdn
化,基本目的都是减少不必要的请求,降低交互网页体积,延迟加载一部分资源,既减轻对服务器的压力,又能给用户依旧不错的交互体验。当然nginx
也只是服务链路的一个环节,与它交互的比如说java虚拟机
、uwsgi/gunicorn
、php-fpm
,再往后的持久化存储mysql、mongodb
,用到的全局缓存redis
、消息队列rocketmq/kafka
各自都有优化的空间,只是本文只重点关注nginx
的部分。
在这过程中也对于nginx
源码进行了一定的梳理,以后再发文章做分享,比较难的部分,我认为就是做nginx
的插件开发了,其它可能都是copy|paste
修修改改的事,但是插件开发是需要有很明确解决什么问题,如何解决问题的方案以及如何实践的落地科学,本文会有所涉及。为了更丝滑的让插件知识介入,先用以下前置知识铺垫一下。
针对于location有以下三种匹配模式:
- 精确匹配,形如
location =/xx
- 前缀匹配
- 正则匹配
这几种匹配规则的关系是,如果能够有精确匹配的话,命中了精确匹配,直接就走该部分的location逻辑,执行结束。
而前缀匹配和正则匹配关系稍微有点复杂,表示上^~
为前缀匹配。~*
或者~
为正则匹配,如果能够命中前缀匹配的话,会按最大匹配长度进行匹配。比如说:
//配置1
location ^~ /others/ {
}
//配置2
location ^~ /others/aa{
}
如果两个规则都能命中的话,那么,会走配置2
,如果同时还配置了正则匹配的话,即前缀匹配和正则匹配都能命中的话,那么,会以^~
中声明的为准,这就是基本的反向代理逻辑。假如你要做个不改变这种逻辑,同时又有自己custom
部分的插件,如果你是nginx
的开发者,你觉得你会用什么方式,给人们多样的自定义需求做支持?
比如说我早期使用wordpress、discuz
的时候,提供了很多内置的hook
点,在这些地方,你可以对输入输出的值拦截并做自定义。而对于框架来说,灵活度更加高了,逻辑随便你来写,但像yii2
框架,像django
框架这些非常成熟的框架,依然会提供一些通用hook
的方式,你只要在自定义类中实现了预设方法,它就能帮你完成一些你不想重复操作的事,比如说自动补全发布人、发布时间,自动发送邮件等。在java
中,如果你想在bean
启动的时候,全局实例化你的应用,比如说使某个全局通信方法有效,此时调用该类下某方向向哪里推送信息立即有效,你绕过DI
原则使这种自定义部分可生效,也需要按照java bean
允许你做添加的步骤。总结下来,也很清晰了,基本上都是提供给你hook
点,或者需要你自己在自定义类中实现已预置的规范
方法和声明,nginx
也不例外。
那该如何开发一个nginx的插件呢?这里以一下简单的hello world
响应为例。首先你要在package的extends目录下创建module相关的目录,配置文件,及执行的逻辑文件,以c后缀结尾。要声明mod_ext_dir,同时要指明配置文件所在的位置,及真实执行文件的位置。ngx_ext_module_hello,还要以编译的方式–add-module让它进行到nginx执行的流程里面,同时主逻辑里面要实现三个类型的方法,一个是ngx_command_t,一个是ngx_module_http_t还有一个是ngx_module_t,任何一个nginx的扩展都要实现对于这几个方法的实现,而这里所谓的实现,就类似于一个hook,借助于这几个方法,nginx在执行的时候,可以把你的扩展的功能类似于扫描到,新的扩展功能就可以按你预计的意图实现某种业务逻辑了。
下面对于这几个参数详细说说。
- ngx_command_t: 要在它的内部指定你的运行规则,这里要注意区分于你自定义模块的
config
文件
在config
文件中,你要指定包括:
- ngx_addon_name=你自定义的扩展名称,以下划线连接多各字符组合
- HTTP_MODULES:同上面一样,上面是指定自定义名称,下面这个这种赋值,相当于标记该扩展属于处理
http
类型的模块- NGX_ADDON_SRCS:指定扩展主逻辑文件的位置,通常相对于
$ngx_addon_srcs
目录
把上述三点,放一块做个demo:
ngx_addon_name=ngx_http_myfilter_module
HTTP_MODULES="$HTTP_MODULES ngx_http_myfilter_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_myfilter_module.c"
- ngx_module_http_t:这会在后面的
ngx_module_t
中调用,如果没有具体要做的逻辑需要指定,按着规范把所有参数都传成NULL
- ngx_module_t:这个是把上面定义的ngx_comand_t函数,ngx_module_http_t函数都在这里指定,作为回调,可以把它理解成是一个nginx主调用流程的一个总之口,放到它里面的东西,运行的时候会被扫描到,才不会被漏执行到。
限于篇幅,(上)篇先分享到这里,给这个nginx
插件开发先在头脑中留下一个大致的轮廓框架,后面部分将以视频的形式进行讲解,敬请期待。