为OpenResty项目编写自定义Nginx C模块

有些时候,我们需要通过 Lua 代码操作 Nginx 里面的某些状态,但是想要的 API 并不存在于 OpenResty 之内。这时候,可以选择编写一个 Nginx C 模块,然后暴露出可供 Lua 调用的接口。

本文中,我们会分别探讨,如何通过 Nginx 变量或 FFI 的方式去提供 Lua 调用得到的接口。

文中的示例代码可以在 ngx_http_example_or_module 找到。

通过 Nginx 变量提供接口

ngx.var.variable= 在调用的时候,会先查找变量 variable 对应的 handler(一个在 Nginx 内注册的 C 函数),如果 handler 存在,会去调用该 handler。
这意味着,如果我们定义了一个 Nginx 变量和对应的 handler,我们就可以通过在 Lua 代码里调用 ngx.var.variable= 来触发该 handler。

空说无益,先上示例。

在 Nginx 里面我们可以通过 limit_rate 和 limit_rate_after 两个指令来限制响应给客户端的速率。前者决定了限速的多少,后者决定了从什么时候开始限速。当然更多的时候我们需要动态去调整这两个指标。

limit_rate 对应有一个 Nginx 内置的变量, $limit_rate,我们可以修改该变量来达到动态调整的目的。相关的 Lua 代码是 ngx.var.limit_rate = limit_rate。但是并不存在 $limit_rate_after 这样一个变量。

不用担心。因为我们可以自己加上。

// ngx_http_example_or_module.c
// 定义变量和它的 getter/setter
static ngx_http_variable_t  ngx_http_example_or_variables[] = {
    { ngx_string("limit_rate_after"), ngx_http_variable_request_set_size,
      ngx_http_variable_request_get_limit_rate_after,
      offsetof(ngx_http_request_t, limit_rate_after),
      NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 },
    { ngx_null_string, NULL, NULL, 0, 0, 0 }
};

// getter 和 setter 的实现在 GitHub 上的示例代码里有,这里就不贴上了。

通过 FFI 提供接口

不过在大多数情况下,我们并不需要借助变量来间接调用 Nginx C 函数。我们完全可以借助 LuaJIT 的 FFI,直接调用 Nginx C 函数。

lua-resty-murmurhash2 就是一个现成的例子。

下面让我们再看另外一个例子,通过 Lua 代码来获取当前的 Nginx 错误日志等级。

在开发中,我们有时需要在测试环境中通过日志来记录某个 table 的值,比如 ngx.log(ngx.INFO, cjson.encode(res))。

在生产环境里,我们会设置日志等级为 error,这样就不会输出 table 的值。但是日志等级无论是多少,cjson.encode 都是必然会被调用的。

不幸的是,这行代码所在的路径非常热,我们需要避免无谓的 json encode 操作。如果能获取实际的日志等级,判断是否为 error,来决定是否调用 cjson.encode,就能省下这一笔开销。

要实现这一功能,仅需加个获取当前配置的日志等级的 Nginx C 函数和对应的 Lua 接口。

我们可以像这样提供一个 Lua 接口:

-- lib/example_or.lua
...

if not pcall(ffi.typeof, "ngx_http_request_t") then
    ffi.cdef[[
        struct ngx_http_request_s;
        typedef struct ngx_http_request_s  ngx_http_request_t;
    ]]
end

ffi.cdef[[
int ngx_http_example_or_ffi_get_error_log_level(ngx_http_request_t *r);
]]

function _M.get_error_log_level()
    local r = getfenv(0).__ngx_req
    return tonumber(C.ngx_http_example_or_ffi_get_error_log_level(r))
end

对应的 Nginx C 函数很简单:

int
ngx_http_example_or_ffi_get_error_log_level(ngx_http_request_t *r)
{
    ngx_log_t                   *log;
    int                          log_level;

    if (r && r->connection && r->connection->log) {
        log = r->connection->log;

    } else {
        log = ngx_cycle->log;
    }

    log_level = log->log_level;
    if (log_level == NGX_LOG_DEBUG_ALL) {
        log_level = NGX_LOG_DEBUG;
    }

    return log_level;
}

使用时直接拿它跟特定的 Nginx 日志等级常量比较即可:

-- config.lua
-- 目前 Nginx 不支持动态变更日志等级,所以可以把日志等级缓存起来
local example_or = require "lib.example_or"
_M.log_leve = example_or.get_error_log_level()


-- in other file
local config = require "common.config"
local log_level = config.log_level
if log_level >= ngx.WARN then
    -- 错误日志等级是 warn 或者 info 一类
    ngx.log(ngx.WARN, "log a warning event")
else
    -- 错误日志等级是 error 一类
    ngx.log(ngx.WARN, "do not log another warning event")
end

通过指定编译参数缩小Nginx编译的大小

默认的nginx编译选项里居然是用debug模式(-g)的,debug模式会插入很多跟踪和ASSERT之类,造成编译以后一个nginx有好几兆。

百度之后有以下两种方法:

  • 方法一 :

去掉nginx的debug模式编译,编译以后只有375K(nginx-0.5.33, gcc4)。

在 auto/cc/gcc,最后几行有:

# debug
CFLAGS=”$CFLAGS -g”

注释掉或删掉这几行,重新编译即可。

  • 方法二 :

一般来说,默认./configure 生成的makefile文件都将带上-g选项,这对于利用gdb调试nginx是非常必要的,但会debug模式会插入很多跟踪和ASSERT之类,造成编译的nginx文件很大。

编辑去掉objs/Makefile文件中下面这一行的 -g

CFLAGS = -pipe -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g

使用Shell统计分析Nginx网站日志自动屏蔽频繁访问的IP

近来总感觉网站被恶意访问,造成宽带和服务器拖慢,于是决定研究一下之前看到关于分析 Nginx 网站日志,获取频繁访问的 IP 进行自动屏蔽的 Shell 脚本。

一、工作原理

Nginx 配置中,可以通过 allow , deny ip 来进行权限屏蔽。因此,需要创建一个 blockip.conf ,将屏蔽的 IP 保存在里面,并加载至 Nginx 配置中。例子:

allow 1.1.1.1; 
allow 1.1.1.2;
deny all;

全站屏蔽:include blockip.conf; 放到 http {} 语句块。

单站屏蔽:include blockip.conf; 放到对应网站 server{} 语句块。

二、Shell 脚本

注意:相关参数需要自行配置。脚本示例的环境配置是根据 LNMP 安装包而定。

#!/bin/bash
nginxPath=/usr/local/nginx/
wwwPath=/home/wwwlogs/
tail -n50000 $wwwPath/access.log |awk '{print $1,$12}' |grep -i -v -E "google|yahoo|baidu|msnbot|FeedSky|sogou" |awk '{print $1}'|sort|uniq -c|sort -rn |awk '{if($1>1000) print "deny "$2 ";"}' >> $nginxPath/conf/blockip.conf
sort $nginxPath/conf/blockip.conf | uniq -c |awk '{print "deny "$3}' > $nginxPath/conf/blockip.conf
/etc/init.d/nginx reload

三、定时任务

把 Shell 脚本改变权限并添加到定时计划中。

# 比如 Shell 脚本名为 blockip.sh 所在目录为 /root/ 定时任务为每天晚上11点30分执行
30 23 * * * /root/blockip.sh

nginx安装配置naxsi waf防火墙

Naxsi 是第三方 nginx 模块 ,它和 Modsecurity 都是开源 WAF ,但是它们的防御模式不同。 Naxsi 不依赖像防病毒软件这样的签名库,因此不会被“未知”攻击模式所规避(就像我们平常说的主动防御)。Naxsi 和其他 WAF 之间的另一个主要区别就是仅过滤 GET 和 POST 请求。

未分类

我之前一直在用 modsecurity ,效果还不错,但是它对 nginx 支持真的不太好~.~ 。经常会产生大量错误日志,不过这个并不影响它的正常功能,只是看着揪心。让我想更换它的主要原因是 Modsecurity 经常在处理某个请求(正常或不正常)时,会突然导致 CPU 99.9% 以上,这是最不能忍受的。

我们先来简单对比下 Naxsi 和 Modsecurity :

未分类

在日常使用中,可以发现 Modsecurity 具有非常严格的防御规则(误报挺多的),并且规则支持较好(有强大的后台?)。如果你使用 Apache 服务器,推荐使用 Modsecurity WAF。如果你使用的是 Nginx 服务器,建议先尝试使用 Naxsi 。

下面就来在 Centos 下编译安装 Nginx + Naxsi WAF 。Modsecurity 的编译安装在这里(http://www.wuedc.com/nginx-installed-configuration-modsecurity-waf/)。

编译 Nginx + Naxsi

首先先运行:

nginx -V

然后可以看到现有的模块,复制保存一下备用。

configure arguments: --prefix=/usr/local/nginx --user=www --group=www --with-http_stub_status_module --with-http_v2_module --with-http_ssl_module --with-ipv6 --with-http_gzip_static_module --with-http_realip_module --with-http_flv_module --with-openssl=../openssl-1.0.2h --with-pcre=../pcre-8.38 --with-pcre-jit --with-ld-opt=-ljemalloc

下载 Nginx 和 Naxsi

Naxsi 应该使用所有高于 0.8.X 的 Nginx 版本。 Naxsi 版本可以在 https://github.com/nbs-system/naxsi 这里,选择 Branch –> Tags 查看版本号。

下载 Nginx 和 Naxsi ,并解压,然后进入解压后的 Nginx 目录:

wget http://nginx.org/download/nginx-x.x.xx.tar.gz
wget https://github.com/nbs-system/naxsi/archive/x.xx.x.tar.gz
tar xvzf nginx-x.x.xx.tar.gz 
tar xvzf naxsi-x.xx.tar.gz
cd nginx-x.x.xx/

Naxsi 不要求任何特定的依赖,它需要的 libpcre ,libssl ,zlib ,gzip 这些 Nginx 已经集成了。

然后编译(记得在 ./configure 后面加上 –add-module=../naxsi-x.xx/naxsi_src/ 和你之前备份的模块):

./configure --conf-path=/etc/nginx/nginx.conf --add-module=../naxsi-x.xx/naxsi_src/ 
--error-log-path=/var/log/nginx/error.log --http-client-body-temp-path=/var/lib/nginx/body 
--http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-log-path=/var/log/nginx/access.log 
--http-proxy-temp-path=/var/lib/nginx/proxy --lock-path=/var/lock/nginx.lock 
--pid-path=/var/run/nginx.pid --with-http_ssl_module 
--without-mail_pop3_module --without-mail_smtp_module 
--without-mail_imap_module --without-http_uwsgi_module 
--without-http_scgi_module --with-ipv6 --prefix=/usr
make
make install

等待编译完成。Naxsi 安装完成。

nginx/naxsi 基本配置

首先将 naxsi 目录下的 naxsi_core.rules 拷贝至 nginx.conf 所在目录。

http 部分配置

打开 nginx.conf 在 http 部分配置:

http {
 include naxsi_core.rules;  #导入 naxsi 核心规则
 ...
}

server 部分配置

在 nginx.conf 的 server 部分配置:

location / {
   #开启 naxsi
   SecRulesEnabled;
   #开启学习模式
   LearningMode;
   #定义阻止请求的位置
   DeniedUrl "/50x.html"; 
   #CheckRules, 确定 naxsi 何时采取行动
   CheckRule "$SQL >= 8" BLOCK;
   CheckRule "$RFI >= 8" BLOCK;
   CheckRule "$TRAVERSAL >= 4" BLOCK;
   CheckRule "$EVADE >= 4" BLOCK;
   CheckRule "$XSS >= 8" BLOCK;
   #naxsi 日志文件
   error_log /.../foo.log;
   ...
  }
  error_page   500 502 503 504  /50x.html;
  #This is where the blocked requests are going
  location = /50x.html {
  return 418; #I'm a teapot o/
  }

server 完整示例配置

server {
listen 80 default;
access_log /wwwlogs/access_nginx.log combined;
root /www/site;
index index.html index.htm index.php;
location ~ [^/].php(/|$) {        
    SecRulesEnabled; 
    #LearningMode;     
    DeniedUrl "/RequestDenied";
    CheckRule "$SQL >= 8" BLOCK;
    CheckRule "$RFI >= 8" BLOCK;
    CheckRule "$TRAVERSAL >= 4" BLOCK;
    CheckRule "$EVADE >= 4" BLOCK;
    CheckRule "$XSS >= 8" BLOCK;    
    error_log /wwwlogs/foo.log;   
    fastcgi_pass unix:/dev/shm/php-cgi.sock;
    fastcgi_index index.php;
    include fastcgi.conf;
}
location /RequestDenied {
    return 403;
}    
location ~ .*.(gif|jpg|jpeg|png|bmp|swf|flv|ico)$ {
    expires 30d;
    access_log off;
    }
location ~ .*.(js|css)?$ {
    expires 7d;
    access_log off;
    }
}

测试

测试 nginx 配置

/nginx/sbin/nginx -t
nginx: the configuration file /nginx/conf/nginx.conf syntax is ok
nginx: configuration file /nginx/conf/nginx.conf test is successful

重启 nginx

service nginx reload

防御测试

浏览器中打开 http://www.test.com/?a=<>‘ ,出现 403 错误,并且在 foo.log 中出现 NAXSI_FMT 开头的日志。恭喜你 Naxsi 启用成功。

白名单规则

Naxsi 社区提供了一些常用的白名单规则,例如 wordpress 。可以在 https://github.com/nbs-system/naxsi-rules 下载白名单规则。

然后将规则 include 到 server 内的 location 中。重启 nginx 即可。不过目前这些白名单最近的修改日期显示是1年前~.~ ,可根据自身需要添加白名单规则。

详细的白名单规则以及 Naxsi 其他支持,可参考 Naxsi WIKI。

nginx反向代理服务器时 获取用户IP的几种方法

最近刚刚写好个人电商网站,然后忙于找工作,可是自己的网站我想看到访问人数与访问的ip地址。这样可以了解是否有人看到。

最开始的时候查了一下资料,也没有去想其他的问题,直接就开始写代码了,可是发现写好之后,我在本机进行测试,获取到的ip地址都是127.0.0.1,这是有点疑惑,为什么会出现这种问题。我的代码是这样的。

public String addOrUpdate(HttpServletRequest request){
        String ip = request.getRemoteAddr();
        return ip;
    }

网上查了之后说是我这里使用了代理软件,所以只能获取到本地localhost地址,只能是127.0.0.1

然后 网上给出了两种解决办法

  • 方法一
public String getRemortIP(HttpServletRequest request) {  
    if (request.getHeader("x-forwarded-for") == null) {  
        return request.getRemoteAddr();  
    }  
    return request.getHeader("x-forwarded-for");  
}  
  • 方法二
public String getIpAddr(HttpServletRequest request) {  
    String ip = request.getHeader("x-forwarded-for");  
    if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
        ip = request.getHeader("Proxy-Client-IP");  
    }  
    if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
        ip = request.getHeader("WL-Proxy-Client-IP");  
    }  
    if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
        ip = request.getRemoteAddr();  
    }  
    return ip;  
}  

可是经过我的测试两种方法都不管用,不知道是出于什么原因,这时候我在想可能原因在于我使用的nginx设置上面,这个还真在网上找到了答案

  • nginx配置这里需要添加这样一条
location /{
           root   html;
           index  index.html index.htm;
           proxy_pass                  http://backend; 
           proxy_redirect              off;
           proxy_set_header            Host $host;
           proxy_set_header            X-real-ip $remote_addr;
           proxy_set_header            X-Forwarded-For $proxy_add_x_forwarded_for;
        }
  • 然后修改Java代码
String ip = request.getHeader("X-Real-IP");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
  • 这样就可以获取到真实的ip地址了

网上查到给出的解释是这样的

我在查阅资料时,有一本名叫《实战nginx》的书,作者张晏,这本书上有这么一段话“经过反向代理后,由于在客户端和web服务器之间增加了中间层,因此web服务器无法直接拿到客户端的ip,通过$remote_addr变量拿到的将是反向代理服务器的ip地址”。这句话的意思是说,当你使用了nginx反向服务器后,在web端使用request.getRemoteAddr()(本质上就是获取$remote_addr),取得的是nginx的地址,即$remote_addr变量中封装的是nginx的地址,当然是没法获得用户的真实ip的,但是,nginx是可以获得用户的真实ip的,也就是说nginx使用$remote_addr变量时获得的是用户的真实ip,如果我们想要在web端获得用户的真实ip,就必须在nginx这里作一个赋值操作,如下:

proxy_set_header X-real-ip $remote_addr;

其中这个X-real-ip是一个自定义的变量名,名字可以随意取,这样做完之后,用户的真实ip就被放在X-real-ip这个变量里了,然后,在web端可以这样获取:

request.getAttribute("X-real-ip")

这样就明白了吧。

使用Docker配置Nginx环境部署Nextcloud

相关介绍

  • NGINX
    Nginx是一款功能强大的反向代理服务器,支持HTTP、HTTPS、SMTP、IMAP、POP3等协议,它也可以作为负载均衡器、HTTP缓存或是Web服务器。

  • Docker
    Docker是一款轻量级虚拟机软件,他充分利用操作系统本身已有的机制和特性,实现远超传统虚拟机额度轻量级虚拟化。

  • Nextcloud
    Nextcloud是一款功能强大的PHP网盘程序,衍生自著名开源项目ownCloud,拥有美观的Web界面和强大的扩展能力,以及优秀的安全性能。可满足复杂条件下对私有云的需求。

前言

Nextcloud复杂的功能和高安全似乎决定了它的臃肿——但我们不能像对于WordPress一样责怪开发人员。但Nextcloud无后端设计使它在性能落后于Seafile这样的程序,我一直坚持Apache+PHP是Nextcloud最好的搭档,现在我依旧坚持这一点,看看那冗长的Nginx配置文件,而Apache几乎什么都不用做。尽管如此,我还是不得不嫉妒Nginx处理静态文件的优秀性能。

或许以上就是LNMPA诞生的原因吧。但我今天并不准备演示一遍如何搭建LNMPA环境,而是寻找另一种更简易的方法:Nginx+Docker。

其实很简单,不过是将Apache装入容器中而已:

未分类

由此图可以看出,这并没有什么复杂的地方,根本谈不上高深。

那么,将ApacheMySQL等服务放进容器有什么好处呢?

  • 易部署:所使用的都是现成的景象,随时启用,随时删除。
  • 高容错:操作出现任何问题也不会对宿主机有什么影响。
  • 模块化:附加的服务(ONLYOFFICE、Collabora Online、XMPP等)均运行于独立的容器中,互不干扰,增删方便。
  • 免于处理各种复杂的兼容问题。

但我今天就是要简单的问题复杂化,把每一部分都分析透彻。

Docker

Nextcloud在Docker Hub上有已经配置完成的镜像,使用Apache+PHP或是Nginx+FPM,但是不包含MySQL或MariaDB这样的数据库应用,也不直接支持HTTPS访问。

对于缺少的数据库应用,当然可以使用SQLite来应付这个问题,但是,显然不是最佳的解决方案。

最佳的解决方案也不是使用宿主机的数据库服务,而是使用Docker的一个关键功能——容器互联。

容器互联(linking)是一种让多个容器中的应用进行快速交互的方式。它会在源和接受容器中间创建连接关系,接受容器可以通过容器名快速访问到源容器而不用指出具体的IP地址。

举个例子,我们运行一个容器的命令一般是这样的:

dock run -d <container>

可以加上 –name 来为这个容器指定一个名字吗,比如“c1”

docker run -d --name c1 <container>

概念:Docker网桥(Net Bridge)

Docker在创建容器时会默认将容器连接于一个虚拟网桥(docker0)上,这实际上是一个Linux网桥,可以理解为是一个软件交换机(和家里的路由器有几分相像)。它会在挂载其上的接口进行转发,如图:

未分类

docker0可以理解为一个局域网,就像你家的网络与电信服务商之间隔了一个路由器,两个网络之间无法直接访问,除非映射端口。

未分类

(映射端口的操作使用 -p 宿主机端口:容器端口来完成)

如果你操作过路由器上的端口映射功能,这部分会很好理解。

对于docker0内部,每个容器都会分配到一个IP地址,同时,在每个容器内的hosts文件中会记下IP地址与容器的对应关系,这样,如果一个容器想要访问另一个容器,只需要知道容器的ID或者容器名,就像域名一样,而不必获知它的IP地址。

有了网桥,我们就可以将Apache和MySQL分别部署到两个容器中,通过容器名来访问。

数据的操作和持久化

无论是使用Docker,还是Virtualbox亦或是VMware这样的虚拟机软件,实现宿主机和虚拟机之间的文件互访一直是很重要的一件事。这儿我们要用到Docker的数据管理方式之一——数据卷。

  • 数据卷(Data Volumes):容器内数据直接映射到本地主机环境。

数据卷是一个可供容器使用的特殊目录,它会将主机操作系统目录直接映射至容器。

一个典型的例子:我创建了一个带有HTTP服务器的容器,在宿主机上使用Nginx反向代理指向它的请求,为了提高性能,需要分离客户端的动态请求和静态请求。此时,我就可以将容器内的文件映射出来,对于动态请求,Nginx会与容器进行通信,而对于静态请求,Nginx可以直接从本地获得静态文件,提高速度。

提前说一下,上面的例子并不适用于Nextcloud,或者说,我还没找到正确的途径。

1. 在容器内创建一个数据卷

在用Docker run命令的时候,使用 -v 标记可以在容器内创建一个数据卷。标记可重复使用。示例:

docker run -d -P --name web -v /webapp training/webapp python app.py

2. 挂载一个主机目录作为数据卷

格式和映射端口相同, -v 本地目录:容器内目录 (本地目录必须为绝对路径)。

如果想更集中地去管理容器的数据的话,可以使用数据卷容器,不再赘述。

Nginx

Nginx在这里的身份是反向代理服务器,之前我一直将Nginx用作HTTP服务器,现在才正式接触Nginx一直所标榜的功能。

反向代理的配置可以十分简单,直接在server{}中加入:

location / 
{
proxy_pass http://代理地址:端口;
}

以上就是一个反向代理配置,但是这还不够,并且在接下来的时间里就会发现这远远不够。

所谓反向代理,对真实服务器来说无感知,也就是说它并不知道有Nginx这一个的存在。因为对服务端来说,它一直只被一个永户访问,就是反向代理服务器Nginx,而且一直是使用一个URL访问(http://代理地址:端口)。所幸的是,这些状况都是由上方的那简单的配置而导致的,通过添加一些配置信息,就可以解决这个问题:

location / {
         proxy_pass http://代理地址:端口;
         proxy_set_header Host $http_host;
         proxy_set_header X-Forwarded-Proto $scheme;
         proxy_set_header X-Real-IP $remote_addr;
         }

看看,都添加了哪些东西。

  • proxy_set_header Host $http_host; 传递了客户端(相对于Nginx)的URL地址,使得服务端得知访问它所用的URL是外部客户端所访问的真实URL。
  • proxy_set_header X-Real-IP $remote_addr; 获得客户端的真实IP,而不是Nginx的127.0.0.1
  • proxy_set_header X-Forwarded-Proto $scheme; 使服务端能正确识别客户端所用的是HTTP协议还是HTTPS协议

WebSocket代理

现在还有一个棘手的问题,很多Web应用都使用了WebSocket技术,以实现ajax难以实现的一些功能。但在前文中的配置下,Nginx并不会去代理WebSocket请求,Websocket协议是这样的: ws://服务器地址/ 或 wss://服务器地址/。

既然我这儿都把问题说出来了,那肯定就有解决方法咯。

在前文的配置中再加入以下内容:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

这样,我们就得到了一个靠谱的反向代理配置:

location / {
         proxy_pass http://代理地址:端口;
         proxy_set_header Host $http_host;
         proxy_set_header X-Forwarded-Proto $scheme;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header Upgrade $http_upgrade; 
         proxy_set_header Connection $connection_upgrade;
         }

它应该能应付大多数应用了。

等下……

欸?为什么反向代理就高效了?

明明没有啊?

的确没有。

在目前的配置下,我们只是在客户端和服务端之间安排了一个中间人,而且既符合常理也符合实际地说,它变慢了(虽然感觉上微乎其微)。就好比给网站做CDN,做了的全站CDN却没有缓存,所有请求由CDN服务器转发给源服务器,再由源服务器将所有回应全部转发给CDN服务器,实际上增加了中间过程,对响应速度没有丝毫改善。而如果做了CDN缓存,CDN服务器会承担相当一部分请求,回源的请求会大幅减少甚至为0。

同样的,Nginx也是如此,我们得想办法让Nginx自己去根据静态请求返回静态文件,让服务端少为它不擅长的静态文件传输浪费功夫,全心全意地去处理静态请求(Tomcat是个典型的例子,所以我们经常看到把Nginx和Tomcat结合起来用)。

这个解决办法就叫动静分离。

只需要对客户端发来的请求过滤一下,分出其中哪些是动态请求,哪些是静态请求。至于分离方法,可以使用强大正则表达式来匹配URL:

.*.(gif|jpg|png|htm|html|css|js|flv|ico|swf)(.*)

应用到Nginx中:

location / {
         proxy_pass http://代理地址:端口;
         proxy_set_header Host $http_host;
         proxy_set_header X-Forwarded-Proto $scheme;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header Upgrade $http_upgrade; 
         proxy_set_header Connection $connection_upgrade;
         }
location ~ .*.(gif|jpg|png|htm|html|css|js|flv|ico|swf)(.*)
         {
         root /var/www/html/static; #静态文件存放位置
         expires 30d; #缓存30天
         }

这就将动静态请求分离了出来,凡是带有gif、jpg、png……的请求统统从本地 /var/www/html/static目录中获取。而动态请求则发送至服务端。

为什么之前说不适用于Nextcloud呢?

因为Nextcloud的一些静态文件实则是由动态语言实时生成的,比如这个:

https://cloud.orgleaf.com/index.php/css/files/92221bb11c10969dc0aad6345517ad93-merged.css

对于这种请求,Nginx仍然会傻乎乎地到/var/www/html/static里去找,当然找不到。

这种问题也许能用更复制的正则表达式来解决,但有没有更简单的办法呢?

直接设置缓存所有静态请求,这样第二次访问的时候就自动从自身的缓存中获取,缓存中没有的内容就找服务端获取,然后再缓存下来:

location ~ .*.(gif|jpg|png|htm|html|css|js|flv|ico|swf)(.*) {
         proxy_pass http://代理地址:端口;
         proxy_redirect off;         
         proxy_set_header Host $host;
         proxy_cache cache_one;
         proxy_cache_valid 200 302 1h;      
         proxy_cache_valid 301 1d;
         proxy_cache_valid any 1m;
         expires 30d;
        }

另外还要在http{}中加入以下内容:

proxy_temp_path /app/nextcloud/temp_dir;  #临时文件夹
proxy_cache_path /app/nextcloud/cache levels=1:2 keys_zone=cache_one:200m inactive=1d max_size=30g;
                 #     ⇑缓存位置          目录深度⇑                                         ⇑最大体积

好了,现在Nginx的反向代理真的有了它的积极作用。

还有一点——post最大体积

使用Docker和Nginx搭建Nextcloud完成后,我在上传一个约70MB的文件时出现错误,Nextcloud本身没什么动静。看了下console,发现服务器返回错误码413:请求实体太大。

POST的最大体积需要在http{}中的 client_max_body_size 设置,例如:

client_max_body_size 100m;

完成后就可以上传小于100MB的文件了。

Nextcloud

或许我应该把这部分放到Docker里说。

从hub.docker.com获得的官方Nextcloud镜像是不包含数据库服务的,而镜像也阉割了很多常用命令。这样我们就不得不用容器互联来用上数据库了。

如前所述,如果我添加了两个容器,一个运行Nextcloud,另一个运行MySQL,这两个容器默认是在同一网桥上,而我就可以把它们连接起来。

首先运行MySQL容器,我给它起名为db1,然后在MYSQL_ROOT_PASSWORD后面指定root密码为my-secret-pw

docker run --name db1 -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag

然后运行Nextcloud容器,用 –link my-mysql:mysql 把它和MySQL容器连接起来

docker run -d -p 8080:80  --link db1:mysql nextcloud 

官方提供了一个docker-compose文件,可以看出他这儿使用MariaDB作为数据库,命名为db,并在nextcloud服务中连接

version: '2'

volumes:
  nextcloud:
  db:

services:
  db:
    image: mariadb
    restart: always
    volumes:
      - db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=
      - MYSQL_PASSWORD=
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud

  app:  
    image: nextcloud
    ports:
      - 8080:80
    links:
      - db
    volumes:
      - nextcloud:/var/www/html
    restart: always

启动容器后访问Nextcloud容器,填入MySQL/MariaDB的服务器地址时直接填“db”就可以了,因为“db”就是MySQL容器的主机名,hosts文件中已经指明了“db”对应的容器。

数据卷

把数据卷映射至本地,这样我们如果要更改Nextcloud的文件,直接在宿主机上就可以操作了,即前文中所说的方法。

我们一共要映射这三个目录:

nextcloud:/var/www/html          #Nextcloud的程序
data:/var/www/html/data          #Nextcloud的数据目录
config:/var/www/html/config      #Nextcloud的配置文件所在目录
apps:/var/www/html/apps          #Nextcloud应用的目录

具体命令就是这样:

docker run -d nextcloud 
-v nextcloud:/var/www/html 
-v apps:/var/www/html/custom_apps 
-v config:/var/www/html/config 
-v data:/var/www/html/data

注意:冒号前面的nextcloud、apps、config、data要全部替换为本地的绝对路径

OCC命令

使用docker exec命令以在宿主机上为容器执行OCC命令:

docker exec --user www-data CONTAINER_ID php occ

CONTAINER_ID就是运行的Nextcloud容器的ID,如果你给容器命了名,那么也可以是这个容器的名字。

本文到此结束,我也要暂时的疏远Nextcloud/ownCloud了,更广阔的世界还等着我去探索。

Nginx配置文件注释说明

nginx配置文件主要分为六个区域:

  • main 控制子进程的所属用户/用户组、派生子进程数、错误日志位置/级别、pid位置、子进程优先级、进程对应cpu、进程能够打开的文件描述符数目等

  • events 控制nginx处理连接的方式

  • http

  • sever

  • location

  • upstream

实例:

# 运行用户
user www-data;

# 启动进程数,通常设置成和cpu的数量相等
worker_processes 1;

# 全局错误日志
error_log /var/log/nginx/error.log;

# PID文件
pid /var/run/nginx.pid;

events {
  # 使用epoll多路复用模式
  use epoll;   

  # 单个后台worker process进程的最大并发链接数
  worker_connections  1024;
  # multi_accept on; 
}

http {
  # 设定mime类型,类型由mime.type文件定义
  include       /etc/nginx/mime.types;

  # 1 octet = 8 bit
  default_type  application/octet-stream;

  # 设定访问日志
  access_log    /var/log/nginx/access.log;

  # sendfile指令指定nginx是否调用sendfile函数(zero copy方式)来输出文件,对于普通应用,必须设为on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的uptime.
  sendfile        on;

  # 在一个数据包里发送所有头文件,而不一个接一个的发送
  #tcp_nopush     on;

  # 连接超时时间
  keepalive_timeout  65;

  # 作用于socket参数TCP_NODELAY,禁用nagle算法,也即不缓存数据
  tcp_nodelay        on;

  # 开启gzip压缩
  gzip  on;
  gzip_disable "MSIE [1-6].(?!.*SV1)";

  # 设定请求缓冲
  client_header_buffer_size    1k;
  large_client_header_buffers  44k;

  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;

  # 设定负载均衡的服务器列表
  upstream mysvr {
    # weigth参数表示权值,权值越高被分配到的几率越大
    # 本机上的Squid开启3128端口
    server 192.168.8.1:3128 weight=5;
    server 192.168.8.2:80 weight=1;
    server 192.168.8.3:80 weight=6;
  }

server {
  # 侦听80端口
  listen 80;

  # 定义使用www.xx.com访问
  server_name  www.xx.com;

  # 设定本虚拟主机的访问日志
  access_log  logs/www.xx.com.access.log  main;

  # 默认请求
  location / {
    # 定义服务器的默认网站根目录位置
    root   /root;       

    # 定义首页索引文件的名称
    index index.php index.html index.htm;  

    fastcgi_pass  localhost:9000;
      fastcgi_param  SCRIPT_FILENAME  $document_root/$fastcgi_script_name; 
      include /etc/nginx/fastcgi_params;  
    }

    # 定义错误提示页面
    error_page   500 502 503 504 /50x.html;  
      location = /50x.html {
      root   /root;
    }

    # 静态文件,nginx自己处理
    location ~ ^/(images|javascript|js|css|flash|media|static)/ {
      root /var/www/virtual/htdocs;

      # 过期时间30天
      expires 30d;
    }

    # PHP脚本请求全部转发到FastCGI处理,使用FastCGI默认配置
    location ~ .php$ {
      root /root;
      fastcgi_pass 127.0.0.1:9000;
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME /home/www/www$fastcgi_script_name;
      include fastcgi_params;
    }

    # 设定查看Nginx状态的地址
    location /NginxStatus {
      stub_status on;
      access_log on;
      auth_basic "NginxStatus";
      auth_basic_user_file conf/htpasswd;
    }

    # 禁止访问 .htxxx 文件
    location ~ /.ht {
      deny all;
    }
  }
}

nginx结合keepalived实现web服务器高可用方案

keepalived的作用是检测服务器的状态,如果有一台web服务器死机,或工作出现故障,Keepalived将检测到,并将有故障的服务器从系统中剔除,同时使用其他服务器代替该服务器的工作,当服务器工作正常后Keepalived自动将服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的服务器。

安装Keepalived(http://www.keepalived.org/download.html)

1、上传或下载 keepalived到 /usr/local/src 目录

2、解压安装

cd /usr/local/src  
tar -zxvf keepalived-1.2.18.tar.gz  
cd keepalived-1.2.18  
./configure --prefix=/usr/local/keepalived  
make && make install  

3、将 keepalived 安装成 Linux 系统服务(因为没有使用 keepalived 的默认路径安装(默认是/usr/local),安装完成之后,需要做一些工作)

#复制默认配置文件到默认路径  
mkdir /etc/keepalived  
cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/  
#复制 keepalived 服务脚本到默认的地址  
cp /usr/local/keepalived/etc/rc.d/init.d/keepalived /etc/init.d/  
cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/  
ln -s /usr/local/sbin/keepalived /usr/sbin/  
ln -s /usr/local/keepalived/sbin/keepalived /sbin/  

4、设置 keepalived 服务开机启动

chkconfig keepalived on  

5、修改 Keepalived 配置文件

MASTER 节点配置文件(192.168.1.11)

vi /etc/keepalived/keepalived.conf
global_defs {
  ##keepalived自带的邮件提醒需要开启sendmail服务。建议用独立的监控或第三方SMTP
  ##标识本节点的字条串,通常为 hostname
  router_id 192.168.1.11
}
##keepalived会定时执行脚本并对脚本执行的结果进行分析,动态调整vrrp_instance
   的优先级。如果脚本执行结果为0,并且weight配置的值大于0,则优先级相应的增加。
   如果脚本执行结果非0,并且weight配置的值小于 0,则优先级相应的减少。其他情况,
   维持原本配置的优先级,即配置文件中priority对应的值。
vrrp_script chk_nginx {
   script "/etc/keepalived/nginx_check.sh" ## 检测 nginx 状态的脚本路径
   interval 2 ## 检测时间间隔
   weight -20 ## 如果条件成立,权重-20
}
## 定义虚拟路由,VI_1为虚拟路由的标示符,自己定义名称
vrrp_instance VI_1 {
   state MASTER ## 主节点为MASTER,对应的备份节点为BACKUP
   interface eth1 ## 绑定虚拟IP的网络接口,与本机IP地址所在的网络接口相同
   virtual_router_id 11 ## 虚拟路由的ID号,两个节点设置必须一样,建议用IP最后段
   mcast_src_ip 192.168.1.11 ## 本机 IP 地址
   priority 100 ## 节点优先级,值范围0-254,MASTER要比BACKUP高
   nopreempt ## 优先级高的设置 nopreempt 解决异常恢复后再次抢占的问题
   advert_int 1 ## 组播信息发送间隔,两个节点设置必须一样,默认 1s
   ## 设置验证信息,两个节点必须一致
   authentication {
      auth_type PASS
      auth_pass 1111
   }
   ## 将 track_script 块加入 instance 配置块
      track_script {
      chk_nginx ## 执行 Nginx 监控的服务
   }
   ## 虚拟 IP 池, 两个节点设置必须一样
   virtual_ipaddress {
      192.168.1.10  ## 虚拟 ip,可以定义多个
   }
}

BACKUP 节点配置文件(192.168.1.12)  
vi /etc/keepalived/keepalived.conf

global_defs {
  router_id 192.168.1.12
}
vrrp_script chk_nginx {
   script "/etc/keepalived/nginx_check.sh"
   interval 2 
   weight -20 
}
vrrp_instance VI_1 {
   state BACKUP
   interface eth1 
   virtual_router_id 11 
   mcast_src_ip 192.168.1.12
   priority 90 
   advert_int 1 
   authentication {
      auth_type PASS
      auth_pass 1111
   }
   track_script {
      chk_nginx 
   }
   virtual_ipaddress {
      192.168.1.10  
   }
}

6、编写 Nginx 状态检测脚本 /etc/keepalived/nginx_check.sh

脚本:如果nginx停止运行,尝试启动,如果无法启动则杀死本机的keepalived进程,
keepalied将虚拟ip绑定到 BACKUP 机器上。内容如下:

vi /etc/keepalived/nginx_check.sh
!/bin/bash
A=`ps -C nginx –no-header |wc -l`
if [ $A -eq 0 ];then
    /usr/local/nginx/sbin/nginx
    sleep 2
    if [ `ps -C nginx --no-header |wc -l` -eq 0 ];then
        killall keepalived
    fi
fi

7、给脚本赋执行权限

chmod +x /etc/keepalived/nginx_check.sh

8、启动 Keepalived

service keepalived start
Starting keepalived: [ OK ]

9、Keepalived+Nginx的高可用测试

(1)关闭 192.168.1.11 中的 Nginx,Keepalived会将它重新启动

 /usr/local/nginx/sbin/nginx -s stop

(2)关闭 192.168.1.11 中的 Keepalived,VIP 会切换到 192.168.1.12 中

 service keepalived stop

(3)重新启动 192.168.1.11 中的 Keepalived,VIP 又会切回到 192.168.1.11 中来

 service keepalived start

附Keepalived 服务管理命令:

停止:service keepalived stop
启动:service keepalived start
重启:service keepalived restart
查看状态:service keepalived status

利用docker-compose安装lnmp(Nginx mariadb php7.0 )

对于Docker来说,最大的便利就是能快速的搭建起一个个的容器,容器之间可以通过网络和文件来进行通信。

之前我已经将自己的博客使用docker搭建起来了,这里简单记录一下docker-compose文件内容。

我的博客的架构为lnmp,依赖的容器有:

  • Nginx(Port:80)

  • mariadb(Port:3306)

  • wordpress+php7.0-fpm(Port:9000)

  • phpmyadmin(Port:8009)

docker-compose.yml文件内容如下

nginx:
    image: nginx:latest
    ports:
        - '80:80'
    volumes:
        - ./nginx:/etc/nginx/conf.d
        - ./logs/nginx:/var/log/nginx
        - ./jialeens:/var/www/html
    links:
        - wordpress
    restart: always

mysql:
    image: mariadb
    ports:
        - '3306:3306'
    volumes:
        - ./db-data:/var/lib/mysql
    environment:
        - MYSQL_ROOT_PASSWORD=******
    restart: always

wordpress:
    image: wordpress:4.8.0-php7.0-fpm
    ports:
        - '9000:9000'
    volumes:
        - ./jialeens:/var/www/html
    environment:
        - WORDPRESS_DB_NAME=***
        - WORDPRESS_TABLE_PREFIX=wp_
        - WORDPRESS_DB_HOST=mysql
        - WORDPRESS_DB_PASSWORD=*****
    links:
        - mysql
    restart: always
phpmyadmin:
  image: phpmyadmin/phpmyadmin
  links:
    - mysql
  environment:
    PMA_HOST: mysql
    PMA_PORT: 3306
  ports:
    - '8009:80'

Nginx配置文件:

jialeens.com.conf

server {
    listen 80;
    server_name jialeens.com www.jialeens.com;

    fastcgi_buffer_size 64k;
    fastcgi_buffers 4 64k;
    fastcgi_busy_buffers_size 128k;
    fastcgi_temp_file_write_size 128k;
    client_max_body_size 100m;
    root /var/www/html;
    index index.php;

    access_log /var/log/nginx/jialeens-access-http.log;
    error_log /var/log/nginx/jialeens-error-http.log;

    if ($host = 'jialeens.com') {
        return 301 http://www.jialeens.com$request_uri;
    }
    location ~* ^.+.(js|ico|gif|jpg|jpeg|png|html|htm)$ {
       log_not_found off;
       access_log off;
       expires 7d;
    }
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ .php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        fastcgi_pass wordpress:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PHP_VALUE "upload_max_filesize=128M n post_max_size=128M";
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

因为流量不大,所以没做fastcgi的缓存,以后有空再弄吧。

使用Nginx lua把日志实时存储到mysql

准备材料

  • Nginx.1.12安装包 (下载http://nginx.org/en/download.html)

  • mysql 自行安装

  • Lua 相关包
    LuaJIT (下载 http://luajit.org/download.html)
    ngx_devel_kit (下载 https://github.com/simpl/ngx_devel_kit/tags)
    ngx_lua (下载 https://github.com/openresty/lua-nginx-module/tags)
    pcre (下载 https://sourceforge.net/projects/pcre/files/pcre/)
    lua_mysql (下载 https://github.com/openresty/lua-resty-mysql)

安装

LuaJIT 安装

tar zxf LuaJIT-2.0.5.tar.gz
cd LuaJIT-2.0.5
make
make install PREFIX=/home/myself/lj2

pcre 安装

tar -zxvf pcre-8.32.tar.gz
cd pcre-8.32
make
make install

Nginx 安装

export LUAJIT_LIB=/path/to/luajit/lib
export LUAJIT_INC=/path/to/luajit/include/luajit-2.1
./configure --prefix=/opt/nginx 
     --with-ld-opt="-Wl,-rpath,/path/to/luajit-or-lua/lib" 
     --add-module=/path/to/ngx_devel_kit 
     --add-module=/path/to/lua-nginx-module
make
make install

配置文件

user  root;
worker_processes  2;

events {
worker_connections  1024;
}


http{
lua_package_path "/home/oicq/guomm/nginx_lua/lua-resty-mysql-master/lib/?.lua;;"; --重要
lua_shared_dict logs 10m;

init_worker_by_lua_block {
    local delay = 10
    function put_log_into_mysql(premature)      
            local mysql = require "resty.mysql"
            local db, err = mysql:new()
            if not db then
                ngx.log(ngx.ERR,"failed to instantiate mysql: ", err)
                return
            end

            db:set_timeout(1000)
            local ok, err, errcode, sqlstate = db:connect{
                host = "xxx",
                port = 3306,
                database = "database_name",
                user = "username",
                password = "password",
                charset = "utf8",
            }

            if not ok then
                ngx.log(ngx.ERR,"failed to connect: ", err, ": ", errcode, " ", sqlstate)
                return
            end

            -- get data from shared dict and put them into mysql
            local key = "logs"
            local vals = ""
            local temp_val = ngx.shared.logs:lpop(key)
            while (temp_val ~= nil)
            do
                vals = vals .. ",".. temp_val
                temp_val = ngx.shared.logs:lpop(key)
            end

            if vals ~= "" then
                vals = string.sub(vals, 2,-1)
                local command = ("insert into es_visit_record(access_ip,server_ip,access_time,run_time,es_response_time,request_body_byte,run_state,url,post_data) values "..vals)
                ngx.log(ngx.ERR,"command is ",command)
                local res, err, errcode, sqlstate = db:query(command)
                if not res then
                    ngx.log(ngx.ERR,"insert error: ", err, ": ", errcode, ": ", sqlstate, ".")
                    return
                end
            end

            local ok, err = db:close()
            if not ok then
                ngx.log(ngx.ERR,"failed to close: ", err)
                return
            end
            -- decycle call timer to run put_log_into_mysql method, just like crontab
            local ok, err = ngx.timer.at(delay, put_log_into_mysql);
            if not ok then
                ngx.log(ngx.ERR, "failed to create timer: ", err)
                return
            end
    end

    local ok, err = ngx.timer.at(delay, put_log_into_mysql)
    if not ok then
        ngx.log(ngx.ERR, "failed to create timer: ", err)
        return
    end
}


upstream elasticsearch_servers {
    server xxx max_fails=3 fail_timeout=30s;
    server xxx max_fails=3 fail_timeout=30s;
    server xx max_fails=3 fail_timeout=30s;
}

log_format  porxy  '$remote_addr,$upstream_addr,[$time_local],$request,$request_body,$status,$body_bytes_sent,$request_time,$upstream_response_time';

server {
    listen 9202;
    location / {

        proxy_pass http://elasticsearch_servers;

        log_by_lua_block{

            local currentTime = os.date("%Y-%m-%d %H:%M:%S", os.time())
            currentTime = """ .. currentTime .. """

            local req_body = '-'
            if ngx.var.request_body then
                req_body = ngx.var.request_body
                req_body = string.gsub(req_body,"n","")
                --req_body = string.gsub(req_body,"t","")
            end
            req_body = """ .. req_body .. """

            local req_status = 0
            if ngx.var.status then
                req_status = ngx.var.status
            end

            local req_time = 0
            if ngx.var.request_time then
                req_time = ngx.var.request_time
            end

            local req_req = """ .. ngx.var.request .. """
            local remote_addr = """ .. ngx.var.http_x_forwarded_for .. """
            local server_addr = """ .. ngx.var.upstream_addr .. """
            local myparams = ("("..remote_addr..",".. server_addr..","..currentTime..","..ngx.var.request_time .. ",".. ngx.var.upstream_response_time..","..ngx.var.body_bytes_sent..","..ngx.var.status..","..req_req..","..req_body..")")
            local key = "logs"
            local len,err = ngx.shared.logs:rpush(key, myparams)

            if err then
                ngx.log(ngx.ERR,"failed to put log vals into shared dict")
                return
            end

        }
    }
    access_log logs/es_access.log porxy;
}
}

应用场景和日志文件解析

本配置主要解决Nginx向MySQL中实时插入日志的问题。

1、刚开始的时候看了Nginx和mysql的连接模块。比如说nginx-mysql-module,可以连接mysql。但是插入日志时遇到问题,我们知道nginx的执行过程先是location解析并重写阶段,然后是访问权限控制阶段,接着是内容生成阶段,最后是日志记录阶段。mysql访问阶段属于内容生成阶段,所以代理运行的时间和状态,mysql都无法获取的到。因此,这种通过nginx直连mysql的方式无法达到我们的要求。

2、通过lua脚本在日志生成阶段获取信息,然后将数据插入mysql。nginx有一个限制,无法在log阶段访问socket即无法访问mysql,所以无法在log阶段直接将数据存入mysql。但是可以通过运行包含mysql操作的shell脚本来解决这个问题。但是这个方法有两个弊端:

  • 获取到Nginx代理的结果后,每次都要连接mysql并向其插入数据。当并发量大时,mysql端会出现问题。

  • 不向mysql插入数据,整个时间的消耗大约在0.02-0.04s之间。而向mysql插入数据后,整个时间消耗大约在0.4-0.9之间,消耗的时间是原来的10倍。

3、通过lua + ngx.time.at + lua_mysql + lua.share.dict 解决问题。整个过程如下所示:

  • 在nginx启动阶段,ngx.time.at启动一个延时任务。在任务中,每隔一段时间取出nginx内存共享区的log数据,将数据合并,存入mysql,同时再一个相同的延时任务,递归调用。这样就与crontab命令相似。当定时器到期,定时器中的 Lua 代码是在一个“轻线程”中运行的,它与创造它的原始请求是完全分离的,因此不存在大量线程同时运行的情况。

  • 在日志生成阶段,将数据封装并存入nginx的内存共享区。

Mysql 访问权限的问题

不但访问Mysql的Mysql用户需要有操作对应数据库的权限,还需要调用Mysql命令的用户具有访问mysql的权限。授权命令如下:

GRANT ALL PRIVILEGES ON *.* to root@xxx IDENTIFIED BY 'password';

Mysql 编码类型

总的来说,Mysql的数据库对应三种编码。Mysql客户端显示数据的编码,连接Mysql用的编码(即数据存入mysql时,数据的编码),Mysql存储用的编码(字段,表,数据库三种格式可能不同)。不管Mysql存储用的编码是什么,只要Mysql客户端显示数据的编码和连接Mysql用的编码相同,数据就能通过mysql客户端正确显示。