洪志道 洪志道的专栏
浏览 4.13 K
文章 10
订阅 49
NGINX Unit介绍 洪志道 发表于 : 2021-03-07 11:21

介绍NGINX Unit是一个多语言动态应用服务器,同样支持代理和静态文件。 是由NGINX作者Igor Sysoev亲自设计带领团队从零实现的高性能纯C服务器。 Unit的使用对开发者和运维非常友好。关键特性:灵活用HTTP协议的RESTful JSON API更新任意粒度配置 极其强大和易用的路由管理请求 支持所有主流语言:GO, Python, JAVA, Perl, PHP, NodeJS, Ruby 支持负载均衡的代理 支持静态资源服务性能高并发:使用异步事件处理模型 低内存:使用连接和内存复用 每个应用语言的进程管理支持动态和静态分配 对Java, Perl, Python, Ruby支持原生多线程处理安全和稳定性处理请求的进程以非特权方式运行 进程间完全独立 不同的应用完全相互隔离 支持用容器化方式对应用程序进行namespace和file system隔离 支持SSL/TLS社区和文档容易上手 保持一直活跃的开发状态: https://github.com/nginx/unit/ 官方一直更新使用文档:https://unit.nginx.org/完整示例:{ "certificates": { "bundle": { "key": "RSA (4096 bits)", "chain": [ { "subject": { "common_name": "example.com", "alt_names": [ "example.com", "www.example.com" ], "country": "US", "state_or_province": "CA", "organization": "Acme, Inc." }, "issuer": { "common_name": "intermediate.ca.example.com", "country": "US", "state_or_province": "CA", "organization": "Acme Certification Authority" }, "validity": { "since": "Sep 18 19:46:19 2018 GMT", "until": "Jun 15 19:46:19 2021 GMT" } }, { "subject": { "common_name": "intermediate.ca.example.com", "country": "US", "state_or_province": "CA", "organization": "Acme Certification Authority" }, "issuer": { "common_name": "root.ca.example.com", "country": "US", "state_or_province": "CA", "organization": "Acme Root Certification Authority" }, "validity": { "since": "Feb 22 22:45:55 2016 GMT", "until": "Feb 21 22:45:55 2019 GMT" } } ] } }, "config": { "settings": { "http": { "header_read_timeout": 10, "body_read_timeout": 10, "send_timeout": 10, "idle_timeout": 120, "max_body_size": 6291456, "static": { "mime_types": { "text/plain": [ ".log", "README", "CHANGES" ] } }, "discard_unsafe_fields": false } }, "listeners": { "*:8000": { "pass": "routes", "tls": { "certificate": "bundle" } }, "127.0.0.1:8001": { "pass": "applications/drive" }, "*:8080": { "pass": "upstreams/rr-lb" } }, "routes": [ { "match": { "uri": "/admin/*", "scheme": "https", "arguments": { "mode": "strict", "access": "!raw" }, "cookies": { "user_role": "admin" } }, "action": { "pass": "applications/cms" } }, { "match": { "host": "admin.emea-*.*.example.com", "source": "*:8000-9000" }, "action": { "pass": "applications/blogs/admin" } }, { "match": { "host": ["blog.example.com", "blog.*.org"], "source": "*:8000-9000" }, "action": { "pass": "applications/blogs/core" } }, { "match": { "host": "example.com", "source": "127.0.0.0-127.0.0.255:8080-8090", "uri": "/chat/*" }, "action": { "pass": "applications/chat" } }, { "match": { "host": "example.com", "source": [ "10.0.0.0/7:1000", "10.0.0.0/32:8080-8090" ] }, "action": { "pass": "applications/store" } }, { "match": { "host": "wiki.example.com" }, "action": { "pass": "applications/wiki" } }, { "match": { "uri": "/legacy/*" }, "action": { "return": 301, "location": "https://legacy.example.com" } }, { "match": { "scheme": "http" }, "action": { "proxy": "http://127.0.0.1:8080" } }, { "action": { "share": "/www/static/", "fallback": { "proxy": "http://127.0.0.1:9000" } } } ], "applications": { "blogs": { "type": "php", "targets": { "admin": { "root": "/www/blogs/admin/", "script": "index.php" }, "core" : { "root": "/www/blogs/scripts/" } }, "limits": { "timeout": 10, "requests": 1000 }, "options": { "file": "/etc/php.ini", "admin": { "memory_limit": "256M", "variables_order": "EGPCS", "expose_php": "0" }, "user": { "display_errors": "0" } }, "processes": 4 }, "chat": { "type": "external", "executable": "bin/chat_app", "group": "www-chat", "user": "www-chat", "working_directory": "/www/chat/", "isolation": { "namespaces": { "cgroup": false, "credential": true, "mount": false, "network": false, "pid": false, "uname": false }, "uidmap": [ { "host": 1000, "container": 0, "size": 1000 } ], "gidmap": [ { "host": 1000, "container": 0, "size": 1000 } ], "automount": { "language_deps": false, "procfs": false, "tmpfs": false } } }, "cms": { "type": "ruby", "script": "/www/cms/main.ru", "working_directory": "/www/cms/" }, "drive": { "type": "perl", "script": "app.psgi", "threads": 2, "thread_stack_size": 4096, "working_directory": "/www/drive/", "processes": { "max": 10, "spare": 5, "idle_timeout": 20 } }, "store": { "type": "java", "webapp": "/www/store/store.war", "classpath": ["/www/store/lib/store-2.0.0.jar"], "options": ["-Dlog_path=/var/log/store.log"] }, "wiki": { "type": "python", "module": "asgi", "protocol": "asgi", "callable": "app", "environment": { "DJANGO_SETTINGS_MODULE": "wiki.settings.prod", "DB_ENGINE": "django.db.backends.postgresql", "DB_NAME": "wiki", "DB_HOST": "127.0.0.1", "DB_PORT": "5432" }, "path": "/www/wiki/", "processes": 10 } }, "upstreams": { "rr-lb": { "servers": { "192.168.1.100:8080": { }, "192.168.1.101:8080": { "weight": 2 } } } }, "access_log": "/var/log/access.log" }}

点赞 7
0 条评论
编程是门手艺,做一名优秀的将军 洪志道 发表于 : 2020-10-31 12:53

编程是门手艺,NGINX社区的经验分享 一文提过,专业的程序员擅长整体设计和细节处理能力。本文探讨整体设计,尤其是模块化这个技能。全能天才,Fabrice BellardFFmpeg,最强大的流媒体库 QEMU,硬件虚拟化的虚拟机 TCC,迷你CC编译器 QuickJS,100%支持JS语法的C引擎 等等,以上皆出自一人之手,法国天才。 去年QuickJS曾一度刷爆技术圈,NGINX社区的哥们第一时间推荐给我看,并以天才称他。 这软件开拓了我的视野。本文以它为引子探讨我认为非常重要的技能:如何组织代码。NJS,实现语言引擎真难私下问过Fabrice Bellard(给QJS提过patch)开发QJS的历程,答案令人惊叹,他只用了两年的业余时间。参与NJS这几年,才深知实现语言引擎有多复杂。 NJS从17年开始,现在差不多完成40%。但基础已经非常良好,后续功能开发会快速很多。而且常用功能都已经支持,其中我支持了模块化,箭头函数,等常用的。语言解析引入了LL(k)。 看似做了些不错的工作。然而跟QJS比,以台球打个比方。一个长距离很准的选手,90%的球都能打进,看似很厉害。但对一个发力非常厉害的人来说,他可能只需80%的准度,再加良好的走位,就能轻松一杆清台。 提QJS不是妄自菲薄,这样对比很不公平。QJS作者本身就是个JS专家,他都能用JS实现虚拟机。参与NJS的人员,包括Igor都不是真正的JS语法行家,JS的语法着实太庞大。我们平时开发过程中,有个社区外的JS行家对我们帮助非常大,简直就是JS活字典。因此在前期,只能靠着语法手册,然后实现,有些实现跟语法的本质有出入的话,又得重头再来。举个例子,早期实现的apply和call两个语法真是让人吃尽了苦头,这也是我最早参与的,因为修复它的bug,做了重构,然后发现社区的人非常接受这种重构的做法,有种碰到知音的感觉。QuickJS,五万行代码一个文件的软件我会解释这种做法是合理的。此时必须提出来,后面再详加解释。模块化,最好的代码组织方式我在参与NJS时,第一件事就是让它支持模块化编程。NJS刚出来时我就开始关注,后面挺长一段时间,用NJS写代码只能放在一个文件里,这对代码组织是极不友好的。先看下JS的模块化用法: main.js/* 自定义模块 */import foo from ‘foo.js‘;foo.inc();/* 内置模块 */import crypto from ‘crypto‘;var h = crypto.createHash(‘md5‘);var hash = h.update(‘AB‘).digest(‘hex‘);foo.jsvar state = {count:0}function inc() {state.count++;}function get() {return state.count;}export default {inc, get}支持模块化之后,变得非常好用。这个大功能也是NGINX作者Igor亲自帮review和调整的,收获良多。客观讲,JS语法比Lua实在好用太多,NJS目前已经非常稳定,只是功能没那么繁多,推荐轻量应用考虑用NJS,而且社区非常活跃,相信未来可期。 现在轻瞥一下QuickJS的源码。JSContext *JS_NewContext(JSRuntime *rt){JSContext *ctx;ctx = JS_NewContextRaw(rt);if (!ctx)return NULL;JS_AddIntrinsicBaseObjects(ctx);JS_AddIntrinsicDate(ctx);JS_AddIntrinsicEval(ctx);JS_AddIntrinsicStringNormalize(ctx);JS_AddIntrinsicRegExp(ctx);JS_AddIntrinsicJSON(ctx);JS_AddIntrinsicProxy(ctx);JS_AddIntrinsicMapSet(ctx);JS_AddIntrinsicTypedArrays(ctx);JS_AddIntrinsicPromise(ctx);return ctx;}void *JS_GetContextOpaque(JSContext *ctx){return ctx->user_opaque;}void JS_SetContextOpaque(JSContext *ctx, void *opaque){ctx->user_opaque = opaque;}所有源代码扔进一个文件里,我看过不少软件的源码,而且是比较完整的。NGINX, Unit, NJS, Lua等,以个人感观而言,QuickJS是最好的。初看有点凌乱,但细看的话(可能需要很熟悉JS语法),绝对的大师之作。 假如想删除某个语法功能,在QuickJS里可以连续的从某行一直删除到另一行,连续的一块。这在其它软件是不可能做到的,要么多个文件都要删除,要么在一个文件也要删除多个不同的地方。我认为这就是模块化的精髓:高内聚。 学过设计原则的同学想必都知道软件要高内聚,低耦合。我的理解是只要做到了高内聚,低耦合就是自然而然的事情。 举个例子,要实现nginx lua模块。有两个重要的功能:nginx模块相关函数,lua封装相关函数。 过度设计方式:ngx_http_lua_module.c/* nginx模块相关函数 */ngx_http_lua_request.c/* lua封装相关函数 */合理方式ngx_http_lua_module.c/* nginx模块相关函数 *//* lua封装相关函数 */https://github.com/hongzhidao/nginx-lua-module/blob/master/src/ngx_http_lua_module.c 过度设计是一种很容易踩进去的陷井。 讨论1: 如果有更多的功能,比如http subrequest这种功能进来时怎么办? 建议还是放在同一个文件里,不要被代码行数影响。 讨论2: 又有更多的功能,比如http share memory这种功能进来时怎么办? 是可以考虑独立到另一个文件了,原则就是要找到一个信服的理由,新的功能能独立成一个高内聚的模块。有个特征是它往往会有专门的API,比如共享内存操作的get, set等。 换另一个角度看,一个文件的引入本身也是一种成本,而且比函数级别更高。每次的重构都应该带来实质的价值。这是我坚持尽量放同一个文件的原因。我早期提过几次建议,想对njs做类似的事情,后来证明有些是过度设计的。而有些是正确的,比如把njs_vm.c分成njs_vm.c和njs_vmcode.c。一个负责虚拟机,一个负责字节码处理。总结一下: 高内聚是最高准则。 引入新文件成本高于函数,要有实质的价值才做。 不要被代码行数影响。 协作只是一种分工,不能做为破坏高内聚的理由。 再谈设计前面说QuickJS的代码质量非常高,是因为他的设计令人折服。整个QJS的代码行数不到5万,实现了100%的语法,其中还包括非常硬核的大数和正则,都自己造轮子。从整个引擎的实现方面它就做了高度的抽象,而且用的算法非常简单有效。举个例子,JS里对象的属性操作应该是最常用的,比如 a[‘name’]。a和name在语法解析时都是字符串,术语叫token。QJS用一个非常高效的hash实现,将所有JS用到字符串的都包括进去了,代码也很少。typedef struct JSShapeProperty {uint32_t hash_next : 26; /* 0 if last in list */uint32_t flags : 6; /* JS_PROP_XXX */JSAtom atom; /* JS_ATOM_NULL = free property entry */} JSShapeProperty;struct JSShape {uint32_t prop_hash_end[0]; /* hash table of size hash_mask + 1before the start of the structure. */JSGCObjectHeader header;/* true if the shape is inserted in the shape hash table. If not,JSShape.hash is not valid */uint8_t is_hashed;/* If true, the shape may have small array index properties ‘n‘ with 0

点赞 7
1 条评论
编程是门手艺,NGINX社区的经验分享 洪志道 发表于 : 2020-10-28 20:42

前言关于我:NJS核心开发者,Unit贡献者,NGINX专家编程是门手艺是一个系列,分享我在编程上的思考,尤其是在NGINX社区上学到的。编程是逻辑的创造,力求清晰这是我对编程的理解,代码体现的是业务的本质,也可以说成是一种抽象。既然是逻辑,清晰是最最重要的。以Linus在Ted的例子为例:删除列表里的元素。不好的逻辑:遍历列表,找到元素entry。如果entry有没前结点prev,将头部指向entry的next;如果有则将prev的next指向entry的next。好的逻辑:遍历列表,找到元素entry的引用,将引用指向entry的next。虽然两者都能达到目的,但是明显后者更清晰,代码更简洁。不清晰的逻辑往往有更多的特殊处理,反映为代码有更多的if分支。如果你想锻炼更好的逻辑,试试将代码里的if分支优化掉。前几年NGINX作者还在NJS项目时,跟他交集比现在更多。NJS是他亲自设计和实现的纯C JS引擎,它的代码跟NGINX没任何关系。两个软件都有红黑树的实现,NJS的版本是我见过最好红黑树实现,没有之一。我还给他提过一点小改进,我个人很喜欢红黑树这个数据结构。NJS的逻辑更清晰,如果看它实现的话。这种带来的好处是它的API更易用。比较下两者的使用:ngx_rbtree_init(&cache->sh->rbtree, &cache->sh->sentinel, ngx_http_file_cache_rbtree_insert_value);ngx_rbtree_insert(&cache->sh->rbtree, &fcn->node);njs_rbtree_init(&tree, rbtree_unit_test_comparison);njs_rbtree_insert(&tree, &items[i].node);唯一的区别是rbtree_init时,njs版本更简单,nginx版本需要一个叫sentinel的哨子节点。njs的实现逻辑更清晰,将那个特殊处理优化掉了。简而言之,代码应该做到逻辑清晰。也可以理解为更进一步的抽象。良好代码的判断标准@drdrxp提过一种方式,增加行数和删除行数比例为4:1。这个非常直观有效,尤其适用于稳定的项目。对于新项目,这个比例可以提高为3:1,因为新项目往往设计考虑的不够,需要更多的重构动作。以NJS为例,目前算稳定的项目了。xeioex#1xeioex805 commits  忽略数据,有大量的文件重命名重构igorsysoev#2igorsysoev313 commits 58,714 ++ 15,181 --lexborisov#3lexborisov124 commits 29,307 ++ 11,163 --       hongzhidao#4hongzhidao108 commits 12,864 ++ 10,028 --@Igor贡献了整个软件的基础库,实现了早期的JS语法。他的代码行数大约4:1。@xeioex绝对核心,实现了大量的JS功能。@lexborisov语言解析方面的专家,作了很多的改进。@hongzhidao我是第三个参与的,实现部分JS功能,并重构一些基础库。可以看出我跟@lexborisov因为做了更多的重构,增和删的比较很高。这方式是我印象比较深的一种,特别指出来,仅供参考。重构,提升能力最实用的习惯以下是Igor在NGINX上的一个重构例子,就像做了卫生打扫一样,让房间变的更干净了。重构就是这么一种技能,在不改变原来行为的前提,通过内部的优化让代码变的更具扩展性。一定要记住,不能改变原来的行为。重构的目的是让代码更具扩展性,就是为了添加新功能更简单。重构所做的事情可以很多,从小到大有这么几种常见的:1. 细小:改错字;重命名变量、函数和文件等,让代码更具可读性。2. 逻辑优化:比如Linus前面的那个例子,让逻辑更清晰。3. 设计改进:比如NGINX到NJS的红黑树实现。重构会有整一篇幅深入研究,尤其我在社区实践来的。测试用例,不可或缺这里只讨论针对代码的测试用例。以下是我实现Lua版本radix tree写的测试用例。function test_get() local tree = radix_tree.new(); tree:insert(‘foo‘, 1); tree:insert(‘bar‘, 2); tree:insert(‘zoo‘, 3); assert(tree:get(‘bar‘) == 2, ‘get bar‘); assert(tree:get(‘noop‘) == nil, ‘get non‘);endNJS比NGINX好一点的是,基础库比如红黑树之类的都有对应的测试用例,我认为这点非常重要,也是很多项目没做好的,一旦这些实现有问题,就是大规模的雪崩。当然也有很多将私下自己写测试用例的,没有开源出来。前面提的重构,如果没有测试用例就不可能进行。任何改的代码都有可能出问题,怎么保证没问题呢,跑一遍测试用例是最好的方式。在NJS项目里,我跟另外两个经常在修bug和增加功能时,会做一些重构。有时当天发现的bug,如果紧急当天修复,然后发布。敢做这样的事,得益于我们有大量的测试用例。测试用例也会有专门的篇幅继续深入介绍。命名,很大一门学问编程有两大难点,命名和缓存。命名这看起来简单的事情,要做好非常不容易。这课题放在此系列的另一篇中。设计,真正的功夫好的设计能减少代码的实现成本。NGINX代码最复杂的部分应该是buf那块。比如响应是一个流式内容,想对这内容做过滤处理。ssi那个模块就是干这事的。C跟其它语言不一样,几乎都在跟内存打交道。NGINX有内存池,但它只能统一分配和释放。上面举例的内容你需要保存,在NGINX里存放在buf这样的地方,这个buf本身又需要占内存。因此为了复用buf,NGINX做了非常复杂的实现。反观在NJS里,它实现了动态分配的内存池,既然内存可以动态分配和回收,就不需要buf复用了,我估计如果NGINX有这样的实现,代码至少可以简化30%。我以前说过,因为大量的第三方模块,导致NGINX没法引入这样的改进,有点可惜。我在自己的nginx-lua-module里,参考了njs不少好的设计。因此,好的设计可以节省很多的实现成本,也让软件更具健壮性和扩展性。这往往取决于实施者本身的综合能力,所谓天赋。如何真正提升编程能力我认为专业的程序员体现在两方面:整体设计能力和细节处理能力。我最早给社区提交代码时,大约60%多都会被改动调整,整体设计和细节处理的不好。现在90%多都会直接合并进去,甚至不用任何改动的。所以我想实践是最好的方式,有专家帮你review那再好不过了,他会重审你的设计,改进你的细节。实践是最好的方式,接着你要找到好代码或者好项目。自己实现,然后比对,或者找高手帮你看。好的代码或项目不用多,一个就足够了。找你兴趣的,或者用我推荐的。找基础库练手,这是最简单有效的。现在很多公司面试都要先做题,还有很多人在leetcode上刷题的。其实本质都是考核你的代码能力。举个例子,以下是我实现js_import "../.././aa/bb/cc/../d.js" 这样的语法时需要的函数。voidnjs_file_dirname(const njs_str_t *path, njs_str_t *name){ const u_char *p, *end; if (path->length == 0) { goto current_dir; } p = path->start + path->length - 1; /* Stripping basename. */ while (p >= path->start && *p != ‘/‘) { p--; } end = p + 1; if (end == path->start) { goto current_dir; } /* Stripping trailing slashes. */ while (p >= path->start && *p == ‘/‘) { p--; } p++; if (p == path->start) { p = end; } name->start = path->start; name->length = p - path->start; return;current_dir: *name = njs_str_value(".");}这个例子非常好,从命名,注释粒度,API设计都有涉及。这种就非常适合练手。另一种是自己学习和写项目。推荐utopia,1千行的API网关。即将开源。我会经常分享适合练手的好代码,欢迎关注公众号。编程是门手艺,一个分享编程的系列本文是个开篇。命名、重构、测试等更多编程技能在各自的篇幅里。[nginx-lua-module] https://github.com/hongzhidao/nginx-lua-module[utopia] https://github.com/hongzhidao/utopia 

点赞 5
0 条评论
一文看透NGINX开发史 洪志道 发表于 : 2020-10-25 17:42

我的职业生涯大部分时间都在跟NGINX打交道,有足够的经验分享整个NGINX开发史的演进。本文以事后诸葛的角度揭示怎么形成现在这个生态。  此图展示了现如今活跃在NGINX生态的重要开源模块和产品。 一切从C开始 NGINX是纯C实现的软件,源码质量很高。即使不从事NGINX的人也可以将它作为很好的学习软件。作者Igor很早就有支持脚本语言的意图。所以问题就变成了C如何跟脚本语言引擎的结合了。这些主流脚本语言python, php, v8(js), perl, lua都有C的API,考虑到两方面:轻量级和性能,perl成为了当时的NGINX首选。甚至他还实现了一个迷你的SSI模块,自定义的脚本功能。但是从现在看,个人觉得Lua是和C交互最好的语言,天生为C设计的脚本语言,足够轻量,足够快。@agentzh将Lua引入NGINX,怎么看都是正确和极具工程实用的选择。估计NGINX社区也没料到Lua模块能如此成功。但是呢,Igor一直有脚本语言的情节,于是在2015年NJS诞生了,也是纯C的JS引擎。Lua天生能跟C交互,但不意味着它能天生跟服务器软件交互。这里最大的问题是语言虚拟机的实现。简单讲,一个请求会有个vm,非常轻量级,不用担心。对NGINX的每个请求,Lua的vm虽然做了些处理,但还是可以相互影响,如果你想做的话。但是Lua有GC,这也让这问题变的不是很严重。Igor的NJS是专门为NGINX量身定制的JS引擎,还考虑到JS有如此大的用户群体。NJS的每个请求的vm都是相互独立的,不会有任何影响,但是它没有GC。早期GC是列入NJS的计划的,但现在已经变的很遥远了,实现成本太大。这个在 HTTP请求也不是问题,NGINX对每个请求都是统一分配和销毁的。如果用户想在配置上增强NGINX,比如鉴权,NJS是个很好的选择,这也是它的设计初衷。而且NJS通过NGINX的subrequest能跟第三方交互,我们(我是NJS的开发者)最近还打算支持内置的HTTP库。所以想做应用的同学,NJS是个值得入手的选择。 模块化,鱼与熊掌NGINX的模块化机制从第一个版本就有了,但是当时Igor并不是为了第三方考虑的,只是为了方便自己的开发。用NGINX的人大都会为它的模块化机制感到惊叹,谁都可以不用改NGINX源码,只需加入自己的模块,以满足自己的需求。Lua就是最好的例子。 这问题也让Igor很头大,为什么呢?大量的第三方模块,质量参差不齐,它们严重依赖NGINX的API。NGINX是20年前的软件,当时的服务器架构跟如今已经不可同日而语。软件需要进化,就要做重构,但是API不能轻易改。关注NGINX社区的人知道,Igor亲自设计了另一个跟NGINX不同的软件Unit,这软件不会再支持模块化了,这是他们的选择。 所以从短期看,NGINX的模块化让它快速建立了整个生态。从长期看,整个生态也束缚在它的架构上。NGINX最大的问题不具备热加载,这种现在主流软件里已经不是问题,反而在它这里变成很棘手的问题,好在很多以Lua为主的应用可以解决这个问题。  OpenRestyNGINX的C实现和模块化机制,让Lua的引入变得顺其自然。虽然也有其它的语言已经支持,但如今证明只有Lua玩转的最好,或者说OpenResty做的最成功。严格来说,OpenResty里的Lua不能叫Lua。它是LuaJIT,以Lua5.1语法为主的另一个分支,而且看着没有跟进官方Lua的计划。Lua5.1以后的版本在语法上有了不少的改进,类似位运算,性能更是如此。对单纯使用Lua而言,非常推荐Lua5.4。OpenResty让很多开发者大幅提升了开发生产力,并且在它上面衍生了不少开源软件,尤其在API方面,比如Kong和APISIX。很多公司也有内部的自研尝试。  看懂设计不管你是多么熟悉NGINX源码的开发者,还是只是想用它作为应用服务器。脚本语言都是其中的选择之一。从两方面看NGINX:通用功能和业务功能。通用功能:将它扔进NGINX里,如果你能做模块开发,这点尤为重要。它意味着你将享受未来稳定和维护的红利。举个实际例子,我们在开发NJS里,有个querystring的功能,开发需要一定的成本,用JS语言来写会简单很多,但是我们依然选择将它放在JS引擎里。个人觉得类似Lua里的http request这种库,如果原生Lua模块里支持是再好不过了。对NJS,我们会选择放在js模块里,用户可以直接使用,而不用再引入任何库。还有不少的能用功能,比如常用工具函数md5,sha2之类的。业务功能:这个不用多说,维护好业务模块就行了。从整体看,Lua既封装了NGINX的HTTP请求,也提供了独立于请求的功能,比如timer(定时器)和cosocket(跟第三方交互的基础机制)。不管什么模块,也都是基于这两方面进行设计的。我一直推进NJS在这方面的能力,因为目前NJS只能处理请求,但即将引入内置的HTTP库,完全独立于请求的。重复一遍:请求和非请求。我自己的模块,也一直遵守这样的设计,https://github.com/hongzhidao/nginx-lua-module  给开发者的建议如果想学习NGINX,参考这篇 如何高效的学习NGINX对程序员,长期保持自己的竞争力,自研能力是很重要的技能之一。 用Lua作为开发的,不妨自己写个框架,如今API框架是非常好的练手对象。  来乌邦托的世界基于nginx-lua-module的API框架,一千行左右。find . -type f -name ‘*.lua‘ -exec wc -l {} + 109 ./balancer.lua 311 ./conf.lua 78 ./etcd.lua 286 ./http.lua 160 ./jwt.lua 214 ./router.lua 1158 total                                                        utopia架构图 即将开源,Star可以提前发源码[utopia] https://github.com/hongzhidao/utopia 感谢好友xp取寓,分布式研究小院微信公众号:gh_d5b90ac0d668 

点赞 5
0 条评论
一文看透NGINX生态城 洪志道 发表于 : 2020-10-24 18:34

俄罗斯人Igor Sysoev于2004年创建了NGINX并将其开源。如今全球超过25%的网站选择NGINX作为web服务器。尤其对高并发网站来说,NGINX已成为首选。本文将全面探讨整个NGINX的生态。  时间线 NGINX自1995年Apache横空出世以来,几乎成为web服务器的标配,一家独大。系统管理员出身的Igor Syeoev却创建了他强烈个人风格的开源软件。核心代码行数小到5万行不到。  Lua 这里我特别把lua标出来,是因为如今Lua已经成为NGINX的标配,得益于OpenResty。Lua是巴西3个有明显数学特点的人创建的小型语言。时至今日,可以说Lua是与C语言结合最好的语言,没有之一。Lua5.1在其发展史上是一个非常重要的分水岭,但是其性能跟后面的LuaJIT和官方的Lua5.4比,有一定的差距。这也是为什么会有LuaJIT的出现。但是LuaJIT只支持Lua5.1的语言,看着也没有跟进后面版本的计划,这就变成了另一个完全独立的东西,类似tengine之于NGINX。GC一直是脚本语言最重要的特性,Lua5.2, 5.3, 5.4的GC实现一直在进化。国内的Lua骨灰级专家@云风,对Lua5.4有不错的反馈。这个版本应该也是Lua性能最好的一个版本了。 Tengine阿里在NGINX的发展上做出了非常独特的贡献。虽然到了2012年NGINX才真正有了自己的商业公司,但是在2015年之前,相对于如今应用需求这么丰富的背影下,NGINX本身的功能还是偏弱的。在2011年,Tengine fork了NGINX,明显在运维使用方面做了自己独有的改进。 OpenResty 2012年,@agentzh开源了以lua为嵌入语言的模块,极大丰富了NGINX的应用处理能力。它早期支持Lua5.1和LuaJIT,如今已经只支持LuaJIT,并且LuaJIT由OpenResty维护。OpenResty贡献了NGINX生态里最重要的模块,也是最成功的生态。 NGINX时间前进到2015年,NGINX看着开始发力了,不管是软件本身,还是其周边的。支持了stream模块,之前有@yaoweibin tcp proxy module;支持了spdy/http2,这其中google和cloudflare贡献了很大的力量;支持了TLS 1.3;支持更多AIO特性;支持线程池;等等。与此同时,这对Tengine来说,怎么跟进官方是个问题。OpenResty倒是没这方面问题,从一开始他的设计就是不入侵NGNIX源码。 NJS 也是2015年,Igor亲自实现了NJS,一个纯C的JS引擎。不得不说他的代码能力真是硬核。我也有幸成为其中的开发者。Igor实现了ECMAScript 5.1,但现在主流的JS是ECMAScript 6。初期只有他一人开发,实现了非常小的JS子集。后面由@xeioex接手,Igor帮忙Review。对开发者来说,模块化可以说是语言里必不可少的,我也是因为这个需求贡献了NJS的模块化机制实现,后面就上了这条船。目前又加入了一个很有经验的开发者。这也是NGINX的团队风格,人非常少,但是代码质量要求非常高。 Kong 这几年,API网关呼声越来越高。2018年出现的Kong应该是市面上最有份量的API网关之一。APISIX2019年,不到两年的APISIX以轻量级受很多人的欢迎,目前在Apache基金组里,未来可期。 nginx-lua-module 本文作者的模块,支持官方Lua5.4语言。NGINX在直播领域也有它的位置,nginx-rtmp模块作者@arut本身是流媒体的专家。Python是他的个人喜欢的语言。在2016年,他开源了nginx-python模块,其优秀的设计很值得学习。目前他也是以njs为主的nginx-js-module的负责人。nginx-lua-module很大程度上借鉴了nginx-python和njs。个人始终觉得官方Lua语言应该纳入NGINX的生态里,我相信进化的力量,这语言一直保持活跃和进化。当以同社区的极致简单风格维护这个模块。utopia lua实现的API网关框架,1K行左右。  关系网  [lua] https://www.lua.org[Lua 5.4 的改进及 Lua 的版本演进] https://blog.codingnow.com/2018/04/lua_54_nil_in_table.html[nginx] http://nginx.org[tengine] https://github.com/alibaba/tengine[openresty] https://github.com/openresty/lua-nginx-module[njs] https://github.com/nginx/njs[kong] https://github.com/Kong/kong[apisix] https://github.com/apache/apisix[nginx-python-module] https://github.com/arut/nginx-python-module[nginx-lua-module] https://github.com/hongzhidao/nginx-lua-module   

点赞 4
0 条评论
打开NGINX Unit世界 洪志道 发表于 : 2020-08-31 22:08

引子我是NGINX Unit的贡献者,Unit是我非常喜欢的一个开源软件。 我将写一系列Unit的文章分享Unit的世界,相信这个优秀的软件会有非常好的前景。  NGINX Unit是什么?NGINX Unit是一个全新的,由NGINX作者亲自设计,带领NGINX核心团队开发的纯c软件。官方的定义:Unit是一个动态的web和应用服务器。因此它的三大核心为:动态,web和应用。    Unit总体架构  后续会有专门文章分析Unit构架设计,敬请关注。动态动态指两部分,动态配置和应用进程的动态管理。这里只介绍动态配置,这是它最大的亮点之一。 动态配置一直是NGINX软件的缺陷,重新设计的Unit没有这个问题。 简单说,Unit已经没有配置文件。Unit提供了http API接口,所有配置的更新都通过RESTful方式操作。    应用Unit是个多语言应用软件,它支持同时多个语言,甚至同个语言的不同版本,比如python2和python3,php5和php7。NGINX还有个问题,它不支持应用开发。是的,lua模块已经能做非常多的应用了。但是官方想支持更多主流的语言,于是有了这个设计。   webUnit已经支持了static和proxy两个功能,还比较粗糙。相信这些核心功能未来能跟nginx一样完善。其它Unit已经支持TLS,HTTP/2也在计划当中。此外不得不提的是Unit支持了类似容器的名称空间(namespace)和文件系统隔离(file system isolation)。  Unit搭建文件服务器1. 安装> git clone git@github.com:nginx/unit.git && cd unit > ./configure && make  2. 启动> ./build/unitd  3. 配置> cat config.json {         "listeners": {                 "127.0.0.1:80": {                         "pass": "routes"                 }         },         "routes": [                 {                         "action": {                                 "share": "/var/www/"                         }                 }         ] } EOF  > curl -X PUT --data-binary @config.json --unix-socket control.unit.sock http://localhost/config {     "success": "Reconfiguration done." } 4. 访问> curl http://127.0.0.1:80  更多请看官方文档 下篇介绍:Unit架构设计  

点赞 6
1 条评论
TA的热门
本文适合对象:源码开发人员和软件使用人员先说说nginx能吸收的营养知识* http协议和服务器是如何实现的* 网络编程的知识* 简直就是c教科书* 代码阅读能力* 良好的开发习惯* 锻炼逻辑能力* 完全掌握nginx提升自信心深入NGINX源码的学习路径建议尽量只看源码,不看任何其它的资料,但是可以少量参考。如果你是新手,选最初发布版本 nginx0.1.0。(2004年发布)从代码量比较 nginx0.1.0:  4w+  vs  nginx1.19.0: 19w+幸运的是架构几乎没变化。因为模块化是nginx架构的核心,这个在0.1就支持了。不幸运的是这也成了一个问题,举个例子,为什么nginx改配置需要reload呢?有了模块化之后,涌现了很多的第三方模块,有些质量非常不错,推动了社区的发展。有些质量比较低。但不管怎么样,这些大量的第三方模块,让nginx的API不能轻易改动,更不用说架构了。20年前互联网没这么复杂,reload完全不是问题。但是放在现在,长连接业务已经变的很常见,这就成为一个明显的问题了。这里不得不感叹很多厉害人物,在20岁出头就已经有神作了。选好版本后,带着问题从main开始阅读。我按顺便列了些问题:0. 动手编译hello world模块1. 解释`void ****conf_ctx`2. 怎么响应3023. 为什么能输出html4. 为什么reload配置能刷新5, 为什么upgrade能平滑升级6. 解释超时怎么处理的7. 解释proxy怎么工作的为方便大家搭建环境,弄了个仓库,有问题可以建立issue,在时间允许上会回复。https://github.com/hongzhidao/nginx-code-reading记得star,让我知道对多少人有帮助。提示:碰到不会再学习* 如果不熟悉gdb,没关系。配置文件里配置 daemon off; 会printf即可。* 如果不熟悉c语言,没关系。先搞懂指针,剩下看源码时碰到不会再学习。* 如果不熟悉网络编程,也没关系。碰到看不懂再学习。* 其它类似epoll等一样适用。* hello world模块用1.19编译。* nginx 0.1.0有些bug,作了修复。如果你觉得跨过新手级别了,看最新版的源码,同样带着问题,但是要写模块。1. 看官方开发指南https://github.com/baishancloud/nginx-development-guide这是跟另一朋友一起翻译的,openssl社区的成员,杨洋。2. 多写模块,做到写出来的风格跟源码里的看起来一样。模块自己想,能在工作中解决问题最好了。3. 提高代码能力。a. 抄源码是个方式。 b. 找独立的函数,自己实现,然后跟源码里对比。 (问题待整理)如果哪天你觉得对360无死角了,像社区的一样,看nginx应该看的都是不足,试试解决这些不足。如果你是一名使用者,包括运维同学。也想深入nginx怎么办?首先,你已经具备nginx的环境,因为你碰到了问题,想深入nginx能在工作中带来帮助。这已经成功一半了。 进入源码吧,比你想象的简单,这跟学习英语一样,用久就习惯了。举个例子,你想知道map的详细用法。1. 查看官方文档:http://nginx.org/en/docs/ 找到 map 模块,详细阅读。2. 对比源码,看它的逻辑: src/http/modules/ngx_http_map_module.c为什么看源码有效呢?首先,完全可以将c代码当作普通的英语描述(俗称伪代码)其次,你会惊讶于nginx的代码质量,你需要的功能都有专门的一个地方体现。最后,我一直说的,nginx源码非常锻炼人的逻辑能力。这几年业余时间跟NGINX社区的一起写代码,对nginx/njs/unit都算熟悉,仅供参考。下篇待续,整体上欣赏NGINX源码的设计。源码文章只会写一篇,只是为了更快进入源码大门。上面提到的看源码是最好的方式,不想剥夺读者思考的乐趣。
编程是门手艺,NGINX社区的经验分享 一文提过,专业的程序员擅长整体设计和细节处理能力。本文探讨整体设计,尤其是模块化这个技能。全能天才,Fabrice BellardFFmpeg,最强大的流媒体库 QEMU,硬件虚拟化的虚拟机 TCC,迷你CC编译器 QuickJS,100%支持JS语法的C引擎 等等,以上皆出自一人之手,法国天才。 去年QuickJS曾一度刷爆技术圈,NGINX社区的哥们第一时间推荐给我看,并以天才称他。 这软件开拓了我的视野。本文以它为引子探讨我认为非常重要的技能:如何组织代码。NJS,实现语言引擎真难私下问过Fabrice Bellard(给QJS提过patch)开发QJS的历程,答案令人惊叹,他只用了两年的业余时间。参与NJS这几年,才深知实现语言引擎有多复杂。 NJS从17年开始,现在差不多完成40%。但基础已经非常良好,后续功能开发会快速很多。而且常用功能都已经支持,其中我支持了模块化,箭头函数,等常用的。语言解析引入了LL(k)。 看似做了些不错的工作。然而跟QJS比,以台球打个比方。一个长距离很准的选手,90%的球都能打进,看似很厉害。但对一个发力非常厉害的人来说,他可能只需80%的准度,再加良好的走位,就能轻松一杆清台。 提QJS不是妄自菲薄,这样对比很不公平。QJS作者本身就是个JS专家,他都能用JS实现虚拟机。参与NJS的人员,包括Igor都不是真正的JS语法行家,JS的语法着实太庞大。我们平时开发过程中,有个社区外的JS行家对我们帮助非常大,简直就是JS活字典。因此在前期,只能靠着语法手册,然后实现,有些实现跟语法的本质有出入的话,又得重头再来。举个例子,早期实现的apply和call两个语法真是让人吃尽了苦头,这也是我最早参与的,因为修复它的bug,做了重构,然后发现社区的人非常接受这种重构的做法,有种碰到知音的感觉。QuickJS,五万行代码一个文件的软件我会解释这种做法是合理的。此时必须提出来,后面再详加解释。模块化,最好的代码组织方式我在参与NJS时,第一件事就是让它支持模块化编程。NJS刚出来时我就开始关注,后面挺长一段时间,用NJS写代码只能放在一个文件里,这对代码组织是极不友好的。先看下JS的模块化用法: main.js/* 自定义模块 */import foo from ‘foo.js‘;foo.inc();/* 内置模块 */import crypto from ‘crypto‘;var h = crypto.createHash(‘md5‘);var hash = h.update(‘AB‘).digest(‘hex‘);foo.jsvar state = {count:0}function inc() {state.count++;}function get() {return state.count;}export default {inc, get}支持模块化之后,变得非常好用。这个大功能也是NGINX作者Igor亲自帮review和调整的,收获良多。客观讲,JS语法比Lua实在好用太多,NJS目前已经非常稳定,只是功能没那么繁多,推荐轻量应用考虑用NJS,而且社区非常活跃,相信未来可期。 现在轻瞥一下QuickJS的源码。JSContext *JS_NewContext(JSRuntime *rt){JSContext *ctx;ctx = JS_NewContextRaw(rt);if (!ctx)return NULL;JS_AddIntrinsicBaseObjects(ctx);JS_AddIntrinsicDate(ctx);JS_AddIntrinsicEval(ctx);JS_AddIntrinsicStringNormalize(ctx);JS_AddIntrinsicRegExp(ctx);JS_AddIntrinsicJSON(ctx);JS_AddIntrinsicProxy(ctx);JS_AddIntrinsicMapSet(ctx);JS_AddIntrinsicTypedArrays(ctx);JS_AddIntrinsicPromise(ctx);return ctx;}void *JS_GetContextOpaque(JSContext *ctx){return ctx->user_opaque;}void JS_SetContextOpaque(JSContext *ctx, void *opaque){ctx->user_opaque = opaque;}所有源代码扔进一个文件里,我看过不少软件的源码,而且是比较完整的。NGINX, Unit, NJS, Lua等,以个人感观而言,QuickJS是最好的。初看有点凌乱,但细看的话(可能需要很熟悉JS语法),绝对的大师之作。 假如想删除某个语法功能,在QuickJS里可以连续的从某行一直删除到另一行,连续的一块。这在其它软件是不可能做到的,要么多个文件都要删除,要么在一个文件也要删除多个不同的地方。我认为这就是模块化的精髓:高内聚。 学过设计原则的同学想必都知道软件要高内聚,低耦合。我的理解是只要做到了高内聚,低耦合就是自然而然的事情。 举个例子,要实现nginx lua模块。有两个重要的功能:nginx模块相关函数,lua封装相关函数。 过度设计方式:ngx_http_lua_module.c/* nginx模块相关函数 */ngx_http_lua_request.c/* lua封装相关函数 */合理方式ngx_http_lua_module.c/* nginx模块相关函数 *//* lua封装相关函数 */https://github.com/hongzhidao/nginx-lua-module/blob/master/src/ngx_http_lua_module.c 过度设计是一种很容易踩进去的陷井。 讨论1: 如果有更多的功能,比如http subrequest这种功能进来时怎么办? 建议还是放在同一个文件里,不要被代码行数影响。 讨论2: 又有更多的功能,比如http share memory这种功能进来时怎么办? 是可以考虑独立到另一个文件了,原则就是要找到一个信服的理由,新的功能能独立成一个高内聚的模块。有个特征是它往往会有专门的API,比如共享内存操作的get, set等。 换另一个角度看,一个文件的引入本身也是一种成本,而且比函数级别更高。每次的重构都应该带来实质的价值。这是我坚持尽量放同一个文件的原因。我早期提过几次建议,想对njs做类似的事情,后来证明有些是过度设计的。而有些是正确的,比如把njs_vm.c分成njs_vm.c和njs_vmcode.c。一个负责虚拟机,一个负责字节码处理。总结一下: 高内聚是最高准则。 引入新文件成本高于函数,要有实质的价值才做。 不要被代码行数影响。 协作只是一种分工,不能做为破坏高内聚的理由。 再谈设计前面说QuickJS的代码质量非常高,是因为他的设计令人折服。整个QJS的代码行数不到5万,实现了100%的语法,其中还包括非常硬核的大数和正则,都自己造轮子。从整个引擎的实现方面它就做了高度的抽象,而且用的算法非常简单有效。举个例子,JS里对象的属性操作应该是最常用的,比如 a[‘name’]。a和name在语法解析时都是字符串,术语叫token。QJS用一个非常高效的hash实现,将所有JS用到字符串的都包括进去了,代码也很少。typedef struct JSShapeProperty {uint32_t hash_next : 26; /* 0 if last in list */uint32_t flags : 6; /* JS_PROP_XXX */JSAtom atom; /* JS_ATOM_NULL = free property entry */} JSShapeProperty;struct JSShape {uint32_t prop_hash_end[0]; /* hash table of size hash_mask + 1before the start of the structure. */JSGCObjectHeader header;/* true if the shape is inserted in the shape hash table. If not,JSShape.hash is not valid */uint8_t is_hashed;/* If true, the shape may have small array index properties ‘n‘ with 0<= n <= 2^31-1. If false, the shape is guaranteed not to havesmall array index properties */uint8_t has_small_array_index;uint32_t hash; /* current hash value */uint32_t prop_hash_mask;int prop_size; /* allocated properties */int prop_count;JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */JSObject *proto;JSShapeProperty prop[0]; /* prop_size elements */};里面指针还用到负操作, 他是数学行家玩的转。 为什么NJS不能这样呢?依赖,各细节之间相互引用。软件开发中没办法的事情。 还以打球为例,那些走位和发力非常老道的球手,打法往往是简单有效的,不要奇怪为什么有些球不先击打进去,而选择更不好打的,一切在掌握之中。 设计重于实现这是我这两年比较大的体会。以前会觉得有这设计的功夫,早把东西实现好了,而且认为重构能解决一切的设计不足。这是没错的,问题是花了更多的时间在走弯路。 write some code, think, write more, meditate, write a meaningful commit log, take a sleep, think again, and re-read, split/fold/re-write, think, become happy with the final result. 以上是Unit的负责人给的建议,个人觉得这是一种可行有效的方式。NGINX的http2实现就出自他的手笔。对了,NGINX的http3即将完成。有方法才有可行本系列文章都会有实操方法。实践对想提升代码的同学是很有效的方式,我个人觉得学习或写项目是一种方式。 utopia是我写的一个API网关框架,只有一千行代码。里面的一些设计就参考 Unit,尤其是路由部分。我了解他们的设计历程,非常优秀。这是一个非常适合学习的项目。 设计可以聊的实在太多,远不止一文可以讲完,以后会不断的夹杂在其它章节。[nginx-lua-module] https://github.com/hongzhidao/nginx-lua-module [the-craft-of-programming] https://github.com/hongzhidao/the-craft-of-programming 技术问题欢迎issue里交流 [utopia] https://github.com/hongzhidao/utopia 还未开源,关注公众号及时了解更新 
nginx配置结构清晰,层次分明,这得益于整个架构的模块化设计,文本将揭示配置文件如何被处理和应用。整个配置文件解析后的结果如图这样存储。  一、解析的核心机制nginx源码里,ngx_conf_t是解析的关键结构体ngx_conf_handler函数里:/* set up the directive‘s configuration context */conf = NULL;/* direct指令,一般是core类型模块指令,比如 daemon, work_processes */if (cmd->type & NGX_DIRECT_CONF) {    conf = ((void **) cf->ctx)[ngx_modules[i]->index]; /* 直接存储,比如 ngx_core_conf_t *//* main指令,比如 events, http,此时指向它的地址,这样才能分配数组指针,存储属于它的结构体们。 */} else if (cmd->type & NGX_MAIN_CONF) {    conf = &(((void **) cf->ctx)[ngx_modules[i]->index]); /* 参考图片 */} else if (cf->ctx) {    confp = *(void **) ((char *) cf->ctx + cmd->conf);    /* 有移位的,因此http有三个部分,main, srv, conf,这个就为此而设计的,继续下面的sendfile指令 */     if (confp) {        conf = confp[ngx_modules[i]->ctx_index];    }}rv = cmd->set(cf, cmd, conf);比如sendfile指令:{ ngx_string("sendfile"),  NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF                    |NGX_CONF_FLAG,  ngx_conf_set_flag_slot,  NGX_HTTP_LOC_CONF_OFFSET,  /* 这个对应上面理解,正因有这个offset,它才找到loc部分的配置 */  offsetof(ngx_http_core_loc_conf_t, sendfile),  NULL } 二、配置的应用1、最简单形式,direct方式/* 定义获取配置文件的宏 */#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index]ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);if (ccf->master && ngx_process == NGX_PROCESS_SINGLE) {    ngx_process = NGX_PROCESS_MASTER;} 2、稍微复杂点形式,main方式/* 定义获取配置文件的宏,注意指针 */#define ngx_event_get_conf(conf_ctx, module)                                  \             (*(ngx_get_conf(conf_ctx, ngx_events_module))) [module.ctx_index];ngx_epoll_conf_t  *epcf;epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);nevents = epcf->events; 3、不简单的http配置 /* 宏定义,r是什么,稍后解释 */#define ngx_http_get_module_loc_conf(r, module)  (r)->loc_conf[module.ctx_index]ngx_http_log_loc_conf_t  *lcf;lcf = ngx_http_get_module_loc_conf(r, ngx_http_log_module);.../* r是请求,它是这么来的,在ngx_http_init_request函数里(ngx_http_request.c文件)*/r = ...;cscf = addr_conf->default_server;r->main_conf = cscf->ctx->main_conf;r->srv_conf = cscf->ctx->srv_conf;r->loc_conf = cscf->ctx->loc_conf; 还有个要提的,http配置分main, src, loc,下级的配置可以覆盖上级,很明显,上级只是默认设置值而已。 三、重提模块化设计学着用模块化的角度去看nginx的整体设计,一切以模块为核心,配置依赖于模块,即模块本身就携带着它的配置。正因为这样的设计的,配置文件的解析,使用非常简单。避免过多使用全局变量好像成为一种共识,但是在nginx世界里,全局变量可不少,每个模块都是个全局变量,为什么这样设计呢?因为模块之间是有依赖性的,所以需要互相访问。 配置文件解析这块的代码极具借鉴,本文只管窥般分析了配置文件,不能剥夺读者阅读源码的享受,点到即止。
介绍NGINX Unit是一个多语言动态应用服务器,同样支持代理和静态文件。 是由NGINX作者Igor Sysoev亲自设计带领团队从零实现的高性能纯C服务器。 Unit的使用对开发者和运维非常友好。关键特性:灵活用HTTP协议的RESTful JSON API更新任意粒度配置 极其强大和易用的路由管理请求 支持所有主流语言:GO, Python, JAVA, Perl, PHP, NodeJS, Ruby 支持负载均衡的代理 支持静态资源服务性能高并发:使用异步事件处理模型 低内存:使用连接和内存复用 每个应用语言的进程管理支持动态和静态分配 对Java, Perl, Python, Ruby支持原生多线程处理安全和稳定性处理请求的进程以非特权方式运行 进程间完全独立 不同的应用完全相互隔离 支持用容器化方式对应用程序进行namespace和file system隔离 支持SSL/TLS社区和文档容易上手 保持一直活跃的开发状态: https://github.com/nginx/unit/ 官方一直更新使用文档:https://unit.nginx.org/完整示例:{ "certificates": { "bundle": { "key": "RSA (4096 bits)", "chain": [ { "subject": { "common_name": "example.com", "alt_names": [ "example.com", "www.example.com" ], "country": "US", "state_or_province": "CA", "organization": "Acme, Inc." }, "issuer": { "common_name": "intermediate.ca.example.com", "country": "US", "state_or_province": "CA", "organization": "Acme Certification Authority" }, "validity": { "since": "Sep 18 19:46:19 2018 GMT", "until": "Jun 15 19:46:19 2021 GMT" } }, { "subject": { "common_name": "intermediate.ca.example.com", "country": "US", "state_or_province": "CA", "organization": "Acme Certification Authority" }, "issuer": { "common_name": "root.ca.example.com", "country": "US", "state_or_province": "CA", "organization": "Acme Root Certification Authority" }, "validity": { "since": "Feb 22 22:45:55 2016 GMT", "until": "Feb 21 22:45:55 2019 GMT" } } ] } }, "config": { "settings": { "http": { "header_read_timeout": 10, "body_read_timeout": 10, "send_timeout": 10, "idle_timeout": 120, "max_body_size": 6291456, "static": { "mime_types": { "text/plain": [ ".log", "README", "CHANGES" ] } }, "discard_unsafe_fields": false } }, "listeners": { "*:8000": { "pass": "routes", "tls": { "certificate": "bundle" } }, "127.0.0.1:8001": { "pass": "applications/drive" }, "*:8080": { "pass": "upstreams/rr-lb" } }, "routes": [ { "match": { "uri": "/admin/*", "scheme": "https", "arguments": { "mode": "strict", "access": "!raw" }, "cookies": { "user_role": "admin" } }, "action": { "pass": "applications/cms" } }, { "match": { "host": "admin.emea-*.*.example.com", "source": "*:8000-9000" }, "action": { "pass": "applications/blogs/admin" } }, { "match": { "host": ["blog.example.com", "blog.*.org"], "source": "*:8000-9000" }, "action": { "pass": "applications/blogs/core" } }, { "match": { "host": "example.com", "source": "127.0.0.0-127.0.0.255:8080-8090", "uri": "/chat/*" }, "action": { "pass": "applications/chat" } }, { "match": { "host": "example.com", "source": [ "10.0.0.0/7:1000", "10.0.0.0/32:8080-8090" ] }, "action": { "pass": "applications/store" } }, { "match": { "host": "wiki.example.com" }, "action": { "pass": "applications/wiki" } }, { "match": { "uri": "/legacy/*" }, "action": { "return": 301, "location": "https://legacy.example.com" } }, { "match": { "scheme": "http" }, "action": { "proxy": "http://127.0.0.1:8080" } }, { "action": { "share": "/www/static/", "fallback": { "proxy": "http://127.0.0.1:9000" } } } ], "applications": { "blogs": { "type": "php", "targets": { "admin": { "root": "/www/blogs/admin/", "script": "index.php" }, "core" : { "root": "/www/blogs/scripts/" } }, "limits": { "timeout": 10, "requests": 1000 }, "options": { "file": "/etc/php.ini", "admin": { "memory_limit": "256M", "variables_order": "EGPCS", "expose_php": "0" }, "user": { "display_errors": "0" } }, "processes": 4 }, "chat": { "type": "external", "executable": "bin/chat_app", "group": "www-chat", "user": "www-chat", "working_directory": "/www/chat/", "isolation": { "namespaces": { "cgroup": false, "credential": true, "mount": false, "network": false, "pid": false, "uname": false }, "uidmap": [ { "host": 1000, "container": 0, "size": 1000 } ], "gidmap": [ { "host": 1000, "container": 0, "size": 1000 } ], "automount": { "language_deps": false, "procfs": false, "tmpfs": false } } }, "cms": { "type": "ruby", "script": "/www/cms/main.ru", "working_directory": "/www/cms/" }, "drive": { "type": "perl", "script": "app.psgi", "threads": 2, "thread_stack_size": 4096, "working_directory": "/www/drive/", "processes": { "max": 10, "spare": 5, "idle_timeout": 20 } }, "store": { "type": "java", "webapp": "/www/store/store.war", "classpath": ["/www/store/lib/store-2.0.0.jar"], "options": ["-Dlog_path=/var/log/store.log"] }, "wiki": { "type": "python", "module": "asgi", "protocol": "asgi", "callable": "app", "environment": { "DJANGO_SETTINGS_MODULE": "wiki.settings.prod", "DB_ENGINE": "django.db.backends.postgresql", "DB_NAME": "wiki", "DB_HOST": "127.0.0.1", "DB_PORT": "5432" }, "path": "/www/wiki/", "processes": 10 } }, "upstreams": { "rr-lb": { "servers": { "192.168.1.100:8080": { }, "192.168.1.101:8080": { "weight": 2 } } } }, "access_log": "/var/log/access.log" }}
引子我是NGINX Unit的贡献者,Unit是我非常喜欢的一个开源软件。 我将写一系列Unit的文章分享Unit的世界,相信这个优秀的软件会有非常好的前景。  NGINX Unit是什么?NGINX Unit是一个全新的,由NGINX作者亲自设计,带领NGINX核心团队开发的纯c软件。官方的定义:Unit是一个动态的web和应用服务器。因此它的三大核心为:动态,web和应用。    Unit总体架构  后续会有专门文章分析Unit构架设计,敬请关注。动态动态指两部分,动态配置和应用进程的动态管理。这里只介绍动态配置,这是它最大的亮点之一。 动态配置一直是NGINX软件的缺陷,重新设计的Unit没有这个问题。 简单说,Unit已经没有配置文件。Unit提供了http API接口,所有配置的更新都通过RESTful方式操作。    应用Unit是个多语言应用软件,它支持同时多个语言,甚至同个语言的不同版本,比如python2和python3,php5和php7。NGINX还有个问题,它不支持应用开发。是的,lua模块已经能做非常多的应用了。但是官方想支持更多主流的语言,于是有了这个设计。   webUnit已经支持了static和proxy两个功能,还比较粗糙。相信这些核心功能未来能跟nginx一样完善。其它Unit已经支持TLS,HTTP/2也在计划当中。此外不得不提的是Unit支持了类似容器的名称空间(namespace)和文件系统隔离(file system isolation)。  Unit搭建文件服务器1. 安装> git clone git@github.com:nginx/unit.git && cd unit > ./configure && make  2. 启动> ./build/unitd  3. 配置> cat config.json {         "listeners": {                 "127.0.0.1:80": {                         "pass": "routes"                 }         },         "routes": [                 {                         "action": {                                 "share": "/var/www/"                         }                 }         ] } EOF  > curl -X PUT --data-binary @config.json --unix-socket control.unit.sock http://localhost/config {     "success": "Reconfiguration done." } 4. 访问> curl http://127.0.0.1:80  更多请看官方文档 下篇介绍:Unit架构设计