分享IT技术,分享生活感悟,热爱摄影,热爱航天。
经过多年的积累和完善,Nginx已经是和Apache一样成为了一种最为常见的Web服务器,其因其使用基于事件的io模型能够承受大量的连接数,同时只占用很小的内存,灵活的配置文件以及丰富的第三方模块得到了越来越多的应用。
同时目前Nginx + PHP-FPM已经基本上取代了Apache + Mod-PHP成为了PHP运行环境的主流。但存在一个比较常见的误区,就是Nginx + PHP-FPM的性能要远远高于Apache + Mod-PHP,但事实并不是这样,本质上影响性能的是PHP的执行,而且PHP-FPM的运行模型和Apache基本上是类似的——都是prefork的方式,而这才是性能的瓶颈。个人感觉Nginx + PHP-FPM性能好于Apache主要是由于默认安装的Apache加载了大量无用的模块,同时如果没有做动态静态分离,Nginx在处理静态内容时会有很大的优势。
作为Web服务器,最为常用的配置就是URL的重写,即Rewrite配置,Nginx的Rewrite相对Apache感觉更加简单,同时使用的是PCRE正则书写起来也更加容易一些。这里主要说几个常见的问题
一般使用过Apache Rewrite,都习惯写成
location / { if (!-e $request_filename) { rewrite (.*) /index.php?q=$1 last; } }
而如果使用了新版本的Nginx则建议使用try_files语法
location / { try_files $uri $uri/ /index.php?q=$uri&$args; }
经常会遇到将整个域名跳转到另外一个域名并且uri不变的情况,一般较为常见的写法是
location / { rewrite ^/(.*)$ http://xxx.com/$1 permanent; }
而较好的写法是不使用正则表达式
location / { return 301 $scheme://xxx.com$request_uri; }
break是当匹配了当前的Rewrite规则后不再执行后面紧跟的Rewrite规则,但会进行执行当前location中的其它配置,而last则是当匹配了当前的Rewrite规则后不再执行后面紧跟的Rewrite规则同时跳出当前的location,根据重写后的uri再进行一次新的location匹配。
例如
location /app/ { rewrite ^/app/new/(.*)$ /newapp/$1 break; proxy_pass http://backend; }
当请求匹配了当前的Rewrite规则后,会继续执行后面的proxy_pass指令,而代理请求的request_uri会使用Rewrite以后的值。而如果像如下改了last
location /app/ { rewrite ^/app/new/(.*)$ /newapp/$1 last; proxy_pass http://backend; }
则如果匹配了当前的Rewrite规则后,会跳出当前的location,寻找能匹配/newapp/的location,如果没有则会进入location /,特别注意的是如果rewrite后还到当前的location,则使用last特别容易导致死循环,一定要特别注意,例如以下会造成Rewrite的死循环而导致Nginx出现500错误
location /app/ { rewrite ^/app/(.*)$ /app/new/$1 last; proxy_pass http://backend; }
会在uri中无限的增加/new/
Nginx的配置是原生不能支持if的嵌套的,但可以通过变通的方法实现类似的效果——将一个变量进行多次赋值,通过最后的结果再进行判断
set $_v ""; if ($request_method = "POST") { set $_v "1"; } if ($cookie_L = "") { set $_v "2$_v"; } if ($v = "21") { return 403; }
Nginx还有一个很大的用处就是将一些后段服务通过相应的模块之间暴露给前段使用,即将一种其它的协议HTTP化,最为常见的是原声的Memcached模块,例如以下的配置可以使得/message/?mkey=xxxx直接读取出对应的内容,如果mkey是服务端传给客户端的一个加密的字符串,则也可以保证数据的安全性
location /message/ { set $memcached_key $arg_mkey; memcached_pass memcache; error_page 404 = @cache_miss; } location @cache_miss { internal; proxy_pass http://backend; } upstream memcache { hash $memcached_key consistent; server 127.0.0.1:11211; server 127.0.0.1:11212; server 127.0.0.1:11213; }
类似的模块还有很多如支持MySQL的Drizzle模块,支持MongoDB的Mongo模块,支持Redis的Redis2和HTTP Redis模块,都可以将后段服务HTTP化,从而省去PHP的操作过程从能得到极高的性能。
Nginx的Lua模块是得Nginx能够通过配置就实现各种定制化的功能,例如需要根据多个条件进行判断的Rewrite,对一些请求参数的分析或过滤,其能够通过简单的Lua代码就能够实现一个C模块才能实现的功能,同时也有万全可以接受的性能。这里需要注意的有两点,首先rewrite_by_lua的优先级要低于Nginx原生的Rewrite,因此如下的配置不能得到想要的结果
location / { set $a 12; # create and initialize $a set $b ''; # create and initialize $b rewrite_by_lua 'ngx.var.b = tonumber(ngx.var.a) + 1'; if ($b = '13') { rewrite ^ /bar redirect; break; } }
由于最后的原生Rewrite会优先执行,尽管其写在最后面,因此建议书写配置的使用永远将rewrite_by_lua写在最后,避免对配置文件的错误理解,同时如果一定要实现以上的效果,则可以将最后的原生Rewrite改用Lua书写,如下
location / { set $a 12; # create and initialize $a set $b ''; # create and initialize $b rewrite_by_lua ' ngx.var.b = tonumber(ngx.var.a) + 1; if ngx.var.b == 13 then ngx.redirect("/bar"); end '; }
此外set_by_lua的代码中不要运行过于复杂的操作,因为其会阻塞Nginx的事件循环,可能会严重影响Nginx的性能。
MongoDB作为NoSQL数据库中功能最多也是最为接近传统关系数据库的的一种,有着较为广泛的应用,特别是基于文档的无Schema数据存储方式以及基于Replica Set的复制和故障转移机制,相比传统的关系数据库有着明显的优势。然而在之前的使用过程中发现,由于MongoDB采用mmap的方式将数据的缓存工作交给操作系统处理,导致缓存的效率较低,并且当数据量远大于内存后性能下降非常明显,经常出现慢查询导致崩溃的情况。
而在MongoDB 3.0中其增加类似MySQL存储引擎的概念,除了默认的mmapv1,增加了wiredTiger,其自身能够对数据进行缓存,类似InnoDB,而根据官方的测试说其性能要明显好于LevelDb和InnoDB,具体可以参考http://www.wiredtiger.com/。
根据官方提供的升级文档,只有2.6以上的版本可以直接升级到3.0,否则需要先升级到2.6再进行升级。如果只是升级mongod的版本,则升级过程非常简单,只需要下载最新版本的二进制包,暂时停止mongod,再替换掉老的二进制文件后重新启动即可,数据文件格式是可以向下兼容的,然后升级后不能再进行降级,这一点需要特别注意,条件允许的情况下做好数据的备份也是很有必要的。另外如果使用了Replica Set的复制可以先升级一个SECONDARY,然后再用相同的方法升级其他的SECONDARY,升级PRIMARY时则将其强制将为SECONDARY再进行升级操作,如此可以做到无间断的升级,MongoDB的Replica Set复制允许不同的实例之间存在版本的差异。
切换PRIMARY时,连接到当前的PRIMARY,并在mongo shell中执行一下操作即可强制进行PRIMARY的故障转移
rs.stepDown();
执行后当前的PRIMARY会被切换为SECONDARY,输出如下
upgrade:PRIMARY> rs.stepDown(); 2015-10-14T21:45:30.800+0800 I NETWORK DBClientCursor::init call() failed 2015-10-14T21:45:30.808+0800 E QUERY Error: error doing query: failed at DBQuery._exec (src/mongo/shell/query.js:83:36) at DBQuery.hasNext (src/mongo/shell/query.js:240:10) at DBCollection.findOne (src/mongo/shell/collection.js:187:19) at DB.runCommand (src/mongo/shell/db.js:58:41) at DB.adminCommand (src/mongo/shell/db.js:66:41) at Function.rs.stepDown (src/mongo/shell/utils.js:1006:15) at (shell):1:4 at src/mongo/shell/query.js:83 2015-10-14T21:45:30.810+0800 I NETWORK trying reconnect to 127.0.0.1:27017 (127.0.0.1) failed 2015-10-14T21:45:30.811+0800 I NETWORK reconnect 127.0.0.1:27017 (127.0.0.1) ok upgrade:SECONDARY>
然而只是更新mongod的版本带来的提升是比较有限的,升级的主要目的还是将数据该用新的wiredTiger引擎进行存储,更换存储引擎的方法可以类似MySQL中使用的数据格式升级的方法,即dump出和存储引擎无关的原始数据后更换存储引擎后再进行数据的导入即可完成数据格式的升级。
mongodump -o backup #修改配置后 mongorestore backup
然而使用这种升级的方法需要在操作的过程中中断服务,适用于小数据量同时没有使用Replica Set复制的情况。而如果使用了Replica Set复制就可以类似使用上面提到的升级mongod版本的方法进行。首先停止一个SECONDARY的mongod,修改数据文件的目录和使用的存储引擎。
storage: journal: enabled: true # dbPath: "/opt/mongo/3.0/data1-mmapv1" dbPath: "/opt/mongo/3.0/data1-wiredTiger" directoryPerDB: true # engine: "mmapv1" engine: "wiredTiger" wiredTiger: engineConfig: cacheSizeGB: 1 directoryForIndexes: true
重新启动后即开始自动恢复数据,恢复的数据将使用新的存储引擎,依次进行处理,处理到PRIMARY节点的时候使用上面类似的方法强制故障转移后进行处理即可。
在使用MongoDB较老的版本时发现如果oplog已经不完整,则增加新的节点的恢复过程会失败,即新的节点数据为空,而在3.0从2.6版本恢复的过程中没有遇到这一问题,官方的文档说恢复的过程是新节点会从其他的节点查询数据进行恢复,似乎是不再依赖于完整的oplog,感觉也是一个非常大的改进。
MongoDB能够进行的配置比较有限,新版本开始使用yaml文件作为配置文件,不过依然可以兼容以前类似ini方式的配置文件,一个简单的配置文件如下
systemLog: destination: file path: "/opt/mongo/3.0/log/mongod.1.log" logAppend: true storage: journal: enabled: true dbPath: "/opt/mongo/3.0/data1" directoryPerDB: true engine: "wiredTiger" wiredTiger: engineConfig: cacheSizeGB: 1 directoryForIndexes: true processManagement: fork: true net: bindIp: 127.0.0.1 port: 27017 setParameter: enableLocalhostAuthBypass: false replication: replSetName: "upgrade" oplogSizeMB: 1
之前在使用上也遇到一个问题——在Replica Set模式下,所有的查询都会走PRIMARY,而SECONDARY几乎完全没有作用,根据官方文档的说法为了保证数据的强一致性,在没有特殊声明的情况下客户端驱动会默认从PRIMARY进行查询,可以在客户端中做相应的设定使得驱动支持将一些查询分配到SECONDARY。如PHP中的操作方式如下
<?php $m = new MongoClient(); // Prefer the nearest server in the "east" data center also used for reporting, // but fall back to a server in the "west" data center $m->setReadPreference(MongoClient::RP_NEAREST, array( array('dc' => 'east', 'use' => 'reporting'), array('dc' => 'west'), )); ?>
RP_NEAREST文档中的解释是优先选用延迟最低的节点,然后在优先选用拥有对应tag值的节点。