nginx+lua 实现请求流量上报kafka

环境依赖

前面26、27、28讲到的博文环境即可,上报kafka ,只需在应用层nginx上操作(192.168.0.16,192.168.0.17)

请求上报kafka 其实很简单,大致思路是:

  • 下载lua-resty-kafka,提供lua 操作kafka的方法类库
  • lua 获取nginx 请求参数,组装上报对象
  • 上报对象 encode cjson 编码
  • lua kakfa 上报即可

代码实现

  • 引入 lua-resty-kafka 类库
yum install -y unzip
cd /usr/local/servers/ && wget https://github.com/doujiang24/lua-resty-kafka/archive/master.zip
unzip master.zip
cp -rf lua-resty-kafka-master/lib/resty/kafka /usr/local/test/lualib/resty/
/usr/local/servers/nginx/sbin/nginx -s reload
  • lua 获取请求,组装上报对象,encode对象并上报(注意:以下代码只对流量上报代码进行注释,其他代码参考 前面 28 “分发层 + 应用层” 双层nginx 架构 之 应用层实现)
vim /usr/local/test/lua/test.lua

代码如下:

// 引入 kafka 生产者 类库
local producer = require("resty.kafka.producer")
// 引入json 解析类库
local cjson = require("cjson")
// 构造kafka 集群节点 broker
local broker_list = {
    { host = "192.168.0.16", port = 9092},
    { host = "192.168.0.17", port = 9092},
    { host = "192.168.0.18", port = 9092}
}
// 定义上报对象
local log_obj = {}
// 自定义模块名称
log_obj["request_module"] = "product_detail_info"
// 获取请求头信息
log_obj["headers"] = ngx.req.get_headers()
// 获取请求uri 参数
log_obj["uri_args"] = ngx.req.get_uri_args()
// 获取请求body
log_obj["body"] = ngx.req.read_body()
// 获取请求的http协议版本
log_obj["http_version"] = ngx.req.http_version()
// 获取请求方法
log_obj["method"] = ngx.req.get_method()
// 获取未解析的请求头字符串
log_obj["raw_reader"] = ngx.req.raw_header()
// 获取解析的请求body体内容字符串
log_obj["body_data"] = ngx.req.get_body_data()
// 上报对象json 字符串编码
local message = cjson.encode(log_obj)

local uri_args = ngx.req.get_uri_args()
local product_id = uri_args["productId"]
local shop_id = uri_args["shopId"]
// 创建kafka producer 连接对象,producer_type = "async" 异步
local async_producer = producer:new(broker_list, {producer_type = "async"})
// 请求上报kafka,kafka 生产者发送数据,async_prodecer:send(a,b,c),a : 主题名称,b:分区(保证相同id,全部到相同的kafka node 去,并且顺序一致),c:消息(上报数据)
local ok, err = async_producer:send("access-log", product_id, message)
// 上报异常处理
if not ok then
   ngx.log(ngx.ERR, "kafka send err:", err)
   return
end
local cache_ngx = ngx.shared.test_cache
local product_cache_key = "product_info_"..product_id
local shop_cache_key = "shop_info_"..shop_id
local product_cache = cache_ngx:get(product_cache_key)
local shop_cache = cache_ngx:get(shop_cache_key)
if product_cache == "" or product_cache == nil then
      local http = require("resty.http")
      local httpc = http.new()

      local resp, err = httpc:request_uri("http://192.168.0.3:81",{
        method = "GET",
            path = "/getProductInfo?productId="..product_id
      })
      product_cache = resp.body
      cache_ngx:set(product_cache_key, product_cache, 10 * 60)
end
if shop_cache == "" or shop_cache == nil then
      local http = require("resty.http")
      local httpc = http.new()
      local resp, err = httpc:request_uri("http://192.168.0.3:81",{
        method = "GET",
            path = "/getShopInfo?shopId="..shop_id
      })
      shop_cache = resp.body
      cache_ngx:set(shop_cache_key, shop_cache, 10 * 60)
end
local product_cache_json = cjson.decode(product_cache)
local shop_cache_json = cjson.decode(shop_cache)
local context = {
      productId = product_cache_json.id,
      productName = product_cache_json.name,
      productPrice = product_cache_json.price,
      productPictureList = product_cache_json.pictureList,
      productSecification = product_cache_json.secification,
      productService = product_cache_json.service,
      productColor = product_cache_json.color,
      productSize = product_cache_json.size,
      shopId = shop_cache_json.id,
      shopName = shop_cache_json.name,
      shopLevel = shop_cache_json.level,
      shopRate = shop_cache_json.rate
}
local template = require("resty.template")
template.render("product.html", context)
  • 配置nginx DNS resolver实例,避免 DNS 解析失败
vim /usr/local/servers/nginx/conf/nginx.conf

在 http 部分添加以下内容,如下图:

resolver 8.8.8.8

未分类

配置nginx dns resolver
(注:以上操作 nginx 应用服务器(192.168.0.16,192.168.0.17)都需要进行)

  • 配置 kafka advertised.host.name 参数(避免通过机器名无法找到对应的机器)(所有kafka 节点都配置)

advertised.host.name = 本机ip

vim /usr/local/kafka/config/server.properties

未分类

配置advertised.host.name

  • nginx 校验 及 重载
/usr/local/servers/nginx/sbin/nginx -t && /usr/local/servers/nginx/sbin/nginx -s reload
  • 启动kafka(确保 zookeeper 已启动)
cd /usr/local/kafka && nohup bin/kafka-server-start.sh config/server.properties &
  • kafka 中创建 access-log 主题
cd cd /usr/local/kafka && bin/kafka-topics.sh –zookeeper my-cache1:2181,my-cache2:2181,my-cache3:2181 –topic access-log –replication-factor 1 –partitions 1 –create
  • 打开kafka consumer 查看数据
bin/kafka-console-consumer.sh –zookeeper my-cache1:2181,my-cache2:2181,my-cache3:2181 –topic access-log –from-beginning
  • 浏览器请求nginx

未分类

nginx请求

未分类

shell 打开kafka 消费端查看请求上报kafka 数据

完毕,利用nginx + lua 实现请求流量上报kafka就到这里了。

以上就是本章内容,如有不对的地方,请多多指教,谢谢!

nginx+lua在帐号系统中的应用

我们的帐号系统要应用到多个产品里,所以设计的系统需要满足高并发的特性。项目A更新密码,项目B就得下一次触发接口时,自动登出。

我们帐号系统没有使用Oauth2.0,而是采用了简单的JWT(Json Web Token)的方式来处理的用户认证。所以,帐号系统要提供一个验证用户密码修改的API。

这里就不展开讲jwt了。不了解的可以去google。jwt一共三段:xxx.yyy.zzz, 我们把重要的信息放在payload中,也就是yyy的位置,可以通过base64解码,类似于我们在session存的用户信息。payload也可以做加密处理。

payload一般里面会有一些默认的字段,sub代表用户主体,比如用户的id就可以赋值给sub,也可以是手机号。除了公共的字段,我们也可以定义私有字段,比如seq,可以用在单个应用内来处理单设备登录问题。这里我们要增加一个全局的字段表示密码的状态,或者说用户的状态,比如冻结用户,解冻,踢掉用户登录状态。我们先解决密码状态问题,增加一个字段passwd_seq,初始值1。每更新一次密码passwd_seq加一。所有应用内需要认证的接口都需要校验密码状态。所以都会调用该接口(/token)。失效后,返回401,重新登录。

初步调研A项目每日的接口调用次数10w(接口数百个),除了注册占比较低,忽略不计。就也是说/token接口至少会调用10w次一天。

我在自己的电脑上测试。配置如截图:

未分类

redis压测数据:

$ redis-benchmark -t set,get
====== SET ======
  100000 requests completed in 2.65 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

32.18% <= 1 milliseconds
98.80% <= 2 milliseconds
99.62% <= 3 milliseconds
99.71% <= 4 milliseconds
99.79% <= 5 milliseconds
99.93% <= 6 milliseconds
99.95% <= 10 milliseconds
99.95% <= 11 milliseconds
99.96% <= 12 milliseconds
99.97% <= 13 milliseconds
99.98% <= 14 milliseconds
99.99% <= 15 milliseconds
99.99% <= 16 milliseconds
100.00% <= 16 milliseconds
37664.79 requests per second

====== GET ======
  100000 requests completed in 2.60 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

34.93% <= 1 milliseconds
99.62% <= 2 milliseconds
99.91% <= 3 milliseconds
100.00% <= 3 milliseconds
38491.14 requests per second

nginx+lua读写redis接口测试,单核测试。

测试的nginx配置文件如下:

worker_processes  1;   # we could enlarge this setting on a multi-core machine
error_log  logs/error.log warn;

events {
    worker_connections  2048;
}

http {
    lua_package_path 'conf/?.lua;;';

    server {
        listen       8080;
        server_name  localhost;

        #lua_code_cache on;

        location /test {

            access_by_lua_block {
                local jwt = require "resty.jwt"
                local foo = require "foo"

                local err_msg = {
                   x_token = {err = "set x-token in request, please!"},
                   payload = {err = "payload not found"},
                   user_key = {err = "用户配置信息有问题"},
                   password = {err = "密码已修改,请重新登录"},
                   ok = {ok = "this is my own error page content"},
                }

                -- 判断token是否传递
                local req_headers = ngx.req.get_headers()
                if req_headers.x_token == nil then
                   foo:response_json(422, err_msg.x_token)
                   return
                end

                local jwt_obj = jwt:load_jwt(req_headers.x_token)

        local redis = require "resty.redis"
        local red = redis:new()

        red:set_timeout(1000) -- 1 sec

        local ok, err = red:connect("127.0.0.1", 6379)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        -- 请注意这里 auth 的调用过程
        local count
        count, err = red:get_reused_times()
        if 0 == count then
            ok, err = red:auth("test123456")
            if not ok then
            ngx.say("failed to auth: ", err)
            return
            end
        elseif err then
            ngx.say("failed to get reused times: ", err)
            return
        end

                if jwt_obj.payload == nil then
                   foo:response_json(422, err_msg.payload)
                   return
                end
                local sub = jwt_obj.payload.sub
                user_key, err = red:get('user:' .. sub)

                if user_key == ngx.null then
                    foo:response_json(500, err_msg.user_key)
                    return
                elseif tonumber(user_key) > 3  then
                   foo:response_json(401, err_msg.password)
                   return
                else
                   foo:response_json(200, err_msg.ok)
                   return
                end

        -- 连接池大小是200个,并且设置最大的空闲时间是 10 秒
        local ok, err = red:set_keepalive(10000, 200)
        if not ok then
            ngx.say("failed to set keepalive: ", err)
            return
        end
    }
        }
    }
}

上面的配置文件代码格式化,目前没找到合适的工具.

测试结果如下:

$   ab -c10 -n5000 -H 'x-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vYWNjb3VudC1hcGkuc3VwZXJtYW4yMDE0LmNvbS9sb2dpbiIsImlhdCI6MTUwNTQ2Njg5OSwiZXhwIjoxNTA1NDcwNDk5LCJuYmYiOjE1MDU0NjY4OTksImp0aSI6ImJ0TWFISmltYmtxSGVUdTEiLCJzdWIiOjIsInBydiI6Ijg3MTNkZTA0NTllYTk1YjA0OTk4NmFjNThlYmY1NmNkYjEwMGY4NTUifQ.yiXqkHBZrYXuxtUlSiy5Ialle--q_88G32lxUsDZO0k'  http://127.0.0.1:8080/token
This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software:        openresty/1.11.2.5
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /token
Document Length:        175 bytes

Concurrency Level:      10
Time taken for tests:   0.681 seconds
Complete requests:      5000
Failed requests:        0
Non-2xx responses:      5000
Total transferred:      1655000 bytes
HTML transferred:       875000 bytes
Requests per second:    7344.73 [#/sec] (mean)
Time per request:       1.362 [ms] (mean)
Time per request:       0.136 [ms] (mean, across all concurrent requests)
Transfer rate:          2374.13 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.2      1       4
Processing:     0    1   0.4      1       5
Waiting:        0    1   0.4      1       4
Total:          1    1   0.5      1       6

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      1
  80%      1
  90%      2
  95%      2
  98%      3
  99%      4
 100%      6 (longest request)

单核每秒的qps在7k以上(几乎没优化lua代码)。php之前的测试数据在60左右(大家可以实际测试下)。

看到这个比例。单机单核每日的请求量最大上面是604,800k,每天可以处理6亿个请求。

比如我们优化后再测试,nginx上的lua_code_cache开启,同时开启了2个worker, 测试结果如下:

 ab -c10 -n5000 -H 'x-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vYWNjb3VudC1hcGkuc3VwZXJtYW4yMDE0LmNvbS9sb2dpbiIsImlhdCI6MTUwNTQ2Njg5OSwiZXhwIjoxNTA1NDcwNDk5LCJuYmYiOjE1MDU0NjY4OTksImp0aSI6ImJ0TWFISmltYmtxSGVUdTEiLCJzdWIiOjIsInBydiI6Ijg3MTNkZTA0NTllYTk1YjA0OTk4NmFjNThlYmY1NmNkYjEwMGY4NTUifQ.yiXqkHBZrYXuxtUlSiy5Ialle--q_88G32lxUsDZO0k'  http://127.0.0.1:8080/token
This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software:        openresty/1.11.2.5
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /token
Document Length:        175 bytes

Concurrency Level:      10
Time taken for tests:   0.608 seconds
Complete requests:      5000
Failed requests:        0
Non-2xx responses:      5000
Total transferred:      1655000 bytes
HTML transferred:       875000 bytes
Requests per second:    8217.29 [#/sec] (mean)
Time per request:       1.217 [ms] (mean)
Time per request:       0.122 [ms] (mean, across all concurrent requests)
Transfer rate:          2656.18 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.2      1       2
Processing:     0    1   0.2      1       2
Waiting:        0    1   0.2      1       2
Total:          1    1   0.3      1       3

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      1
  80%      1
  90%      1
  95%      1
  98%      2
  99%      3
 100%      3 (longest request)

除了测试用具ab,还可以用Python写的boom或者go写的hey。可以去github下。其他的两个用具测试结果要比ab工具更稳定。

项目的部署工具可以选用walle开源项目(https://github.com/meolu/walle-web),但是不支持docker部署方式,docker一般部署有两种方式,把代码包到docker image或者做目录映射。我基于walle v1.2.0打了一个patch。 如下:

我们项目的开发部署环境可以使用:openresty image ,其实我们可以把这个项目clone下来。做些处理,或者直接继承这个image。

开发的项目最好使用绝对路径引入lua文件。

一般的项目路径如下:

.
├── config
│   ├── domain
│   │   └── nginx_token.conf
│   ├── nginx.conf
│   └── resources.properties.json
├── lua
│   ├── init.lua
│   └── token_controller.lua
└── lualib
    └── luka
        └── response.lua

5 directories, 6 files

感觉lua这种脚本语言还是不错的,在性能上不比编译型语言差多少,但开发效率却高出不少。后期准备把laravel写的那些中间件都改为lua+nginx,各种各样的校验的事交给lua处理会提升不少性能,甚至,某些不复杂的接口也可以改用lua编写。

lua真是个不错的家伙。借助nginx的高性能,让它成为运行最快的脚本语言。开发web 应用目前已经非常方便了。openresty提供了不少库,比如:mysql,redis,memcache,jwt,json等等吧。

Nginx配置调优

nginx运行进程个数

worker_processes  4;

表示启动nginx process数. 一般建议设置为cpu核心数或者核心数*2

cpu亲和力

如果cpu为四核心,设置进程数为4

worker_processes  4;
worker_cpu_affinity 0001 0010 0100 1000;

如果cpu为八核心,设置进程数为8

worker_processes  8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;

此配置项是将nginx每个进程绑定到固定的cpu核心上.不会造成进程频繁地切换cpu而占用更多的资源

nginx打开文件的最大个数

worker_rlimit_nofile 102400;

在修改这个参数之前同时需要修改操作系统限制的最大打开文件数量

ulimit -n       #显示当前系统限制同时打开文件数量
ulimit -n 102400    #临时修改系统限制
vim /etc/security/limits.conf       #修改系统配置文件
* soft nofile 102400
* hard nofile 102400

Nginx事件处理模型

events {
    use epoll;
    worker_connections  10240;
}
  • use:使用的事件模型,网上表示epoll处理效率高,具体也不是很清楚
  • worker_connections:单进程的最大可连接数

连接超时时间

tcp在建立连接之时需要进行三次握手,断开时四次挥手。如果需要不停的加载资源,极大的占用了系统资源与时间。
keepalive选项则是连接建立之后,进行一段时间的等待,减少了握手与挥手的时间损耗。
然而长连接却也占用了系统资源,这个选项的参数设置也是需要根据实际情况来调整

keepalive_timeout  65;
tcp_nodelay on;
client_header_timeout 15;
client_body_timeout 15;
send_timeout 15;
  • keepalived_timeout 客户端连接保持会话超时时间,超过这个时间,服务器断开这个链接
  • tcp_nodelay;也是防止网络阻塞,不过要包涵在keepalived参数才有效
  • client_header_timeout 客户端请求头读取超市时间,如果超过设个时间没有发送任何数据,nginx将返回request time out的错误
  • client_body_timeout 客户端求主体超时时间,超过这个时间没有发送任何数据,和上面一样的错误提示
  • send_timeout 响应客户端超时时间,这个超时时间仅限于两个活动之间的时间,如果超哥这个时间,客户端没有任何活动,nginx关闭连接

上传文件大小限制

http {
    ...
    clinet_max_body_size 10m;
    }

gzip压缩

使用gzip压缩可以给我们节省带宽成本,提高传输速度,但是也会占用cpu资源

nginx需要启用with-http_gzip_static_module才可以使用

gzip on;
gzip_min_length  1k;
gzip_buffers     4 32k;
gzip_http_version 1.1;
gzip_comp_level 9;
gzip_types  text/css text/xml application/javascript;
gzip_vary on;
  • gzip on; #开启压缩功能
  • gzip_min_length 1k; #设置允许压缩的页面最小字节数,页面字节数从header头的Content-Length中获取,默认值是0,不管页面多大都进行压缩,建议设置成大于1K,如果小与1K可能会越压越大。
  • gzip_buffers 4 32k; #压缩缓冲区大小,表示申请4个单位为32K的内存作为压缩结果流缓存,默认值是申请与原始数据大小相同的内存空间来存储gzip压缩结果。
  • gzip_http_version 1.1; #压缩版本(默认1.1,前端为squid2.5时使用1.0)用于设置识别HTTP协议版本,默认是1.1,目前大部分浏览器已经支持GZIP解压,使用默认即可
  • gzip_comp_level 9; #压缩比例,用来指定GZIP压缩比,1压缩比最小,处理速度最快,9压缩比最大,传输速度快,但是处理慢,也比较消耗CPU资源。
  • gzip_types text/css text/xml application/javascript; #用来指定压缩的类型,‘text/html’类型总是会被压缩。
  • gzip_vary on; #vary header支持,改选项可以让前端的缓存服务器缓存经过GZIP压缩的页面,例如用Squid缓存经过nginx压缩的数据

缓存调优

主要是针对图片,css,js这种元素修改机会较少的情况使用。
特别是图片,占用带宽大。

我们列出匹配这些的url

location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$
      {
      expires      30d;
      }
location ~ .*.(js|css)?$
      {
      expires      10d;
      }

expires参数表示缓存时间。

Nginx lua 接收 GET/POST 请求

有时候写一些简单的api 接口如果不想再起个php-fpm ,完全可以通过nginx加lua在nginx 直接实现。

当然前提是你必须给nginx安装 lua-nginx-module 模块。

下面是nginx 的配置及lua 代码:

lua_need_request_body on;
location = /api {
    default_type text/plain;
    content_by_lua_block {
        ngx.req.read_body()
        // 获取Post 数据
        local post = ngx.req.get_post_args()
        // 获取Querystring 数据
        local get = ngx.req.get_uri_args() 
        ngx.say(post.name)
        ngx.say(post.gender)
        ngx.say(get.type)
    }

}

content_by_lua_block 中放lua 代码

打开命令行模拟请求测试一下

curl  -XPOST 'http://test-lua.loc/mm?type=good' -d "name=shanhuhai" -d "gender=1"

nginx 防御ddos

防御DDOS是一个系统工程,攻击花样多,防御的成本高瓶颈多,防御起来即被动又无奈。DDOS的特点是分布式,针对带宽和服务攻击,也就 是四层流量攻击和七层应用攻击,相应的防御瓶颈四层在带宽,七层的多在架构的吞吐量。对于七层的应用攻击,我们还是可以做一些配置来防御的,例如前端是 Nginx,主要使用nginx的http_limit_conn和http_limit_req模块来防御。 ngx_http_limit_conn_module 可以限制单个IP的连接数,ngx_http_limit_req_module 可以限制单个IP每秒请求数,通过限制连接数和请求数能相对有效的防御CC攻击。下面是配置方法:

一. 限制每秒请求数

ngx_http_limit_req_module模块通过漏桶原理来限制单位时间内的请求数,一旦单位时间内请求数超过限制,就会返回503错误。配置需要在两个地方设置:

  • nginx.conf的http段内定义触发条件,可以有多个条件
  • 在location内定义达到触发条件时nginx所要执行的动作

例如:

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s; //触发条件,所有访问ip 限制每秒10个请求
    ...
    server {
        ...
        location  ~ .php$ {
            limit_req zone=one burst=5 nodelay;   //执行的动作,通过zone名字对应
               }
           }
     }

参数说明:

$binary_remote_addr  二进制远程地址
zone=one:10m    定义zone名字叫one,并为这个zone分配10M内存,用来存储会话(二进制远程地址),1m内存可以保存16000会话
rate=10r/s;     限制频率为每秒10个请求
burst=5         允许超过频率限制的请求数不多于5个,假设1、2、3、4秒请求为每秒9个,那么第5秒内请求15个是允许的,反之,如果第一秒内请求15个,会将5个请求放到第二秒,第二秒内超过10的请求直接503,类似多秒内平均速率限制。
nodelay         超过的请求不被延迟处理,设置后15个请求在1秒内处理。

二.限制IP连接数

http {
    limit_conn_zone $binary_remote_addr zone=addr:10m; //触发条件
    ...
    server {
        ...
        location /download/ {
            limit_conn addr 1;    // 限制同一时间内1个连接,超出的连接返回503
                }
           }
     }

三.白名单设置

http_limit_conn和http_limit_req模块限制了单ip单位时间内的并发和请求数,但是如果Nginx前面有lvs或者 haproxy之类的负载均衡或者反向代理,nginx获取的都是来自负载均衡的连接或请求,这时不应该限制负载均衡的连接和请求,就需要geo和map 模块设置白名单:

geo $whiteiplist  {
        default 1;
        10.11.15.161 0;
    }
map $whiteiplist  $limit {
        1 $binary_remote_addr;
        0 "";
    }
limit_req_zone $limit zone=one:10m rate=10r/s;
limit_conn_zone $limit zone=addr:10m;

geo模块定义了一个默认值是1的变量whiteiplist,当在ip在白名单中,变量whiteiplist的值为0,反之为1
如果在白名单中–> whiteiplist=0 –> $limit=”” –> 不会存储到10m的会话状态(one或者addr)中 –> 不受限制
反之,不在白名单中 –> whiteiplist=1 –> $limit=二进制远程地址 –>存储进10m的会话状态中 –> 受到限制

四.测试

使用ab命令来模拟CC攻击,http_limit_conn和http_limit_req模块要分开测试,同时注意 http_limit_conn模块只统计正在被处理的请求(这些请求的头信息已被完全读入)所在的连接。如果请求已经处理完,连接没有被关闭时,是不会 被统计的。这时用netstat看到连接数可以超过限定的数量,不会被阻止。

ab -n 请求数 -c 并发 http://10.11.15.174/i.php

如果被阻止前台会返回503,同时在nginx的error_log中会看到如下错误日志:

被限制连接数:

2015/01/28 14:20:26 [error] 4107#0: *65525 limiting connections by zone "addr", client: 10.11.15.161, server: , request: "GET /i.php?=PHPE9568F35-D428-11d2-A769-00AA001ACF42 HTTP/1.1", host: "10.11.15.174", referrer: "http://10.11.15.174/i.php"

被限制请求数:

2015/01/28 14:18:59 [error] 4095#0: *65240 limiting requests, excess: 5.772 by zone "one", client: 10.11.15.161, server: , request: "GET /i.php?=PHPE9568F34-D428-11d2-A769-00AA001ACF42 HTTP/1.1", host: "10.11.15.174", referrer: "http://10.11.15.174/i.php"

五.其它一些防CC的方法

1、Nginx模块 ModSecurity、http_guard、ngx_lua_waf

  • ModSecurity 应用层WAF,功能强大,能防御的攻击多,配置复杂
  • ngx_lua_waf 基于ngx_lua的web应用防火墙,使用简单,高性能和轻量级
  • http_guard 基于openresty

2、软件+Iptables

  • fail2ban 通过分析日志来判断是否使用iptables拦截
  • DDoS Deflate 通过netstat判断ip连接数,并使用iptables屏蔽

开头说过抗DDOS是一个系统工程,通过优化系统和软件配置,只能防御小规模的CC攻击,对于大规模攻击、四层流量攻击、混合攻击来说,基本上系统和应用软件没挂,带宽就打满了。下面是我在工作中使用过的防御DDOS的方式:

  1. 高防服务器和带流量清洗的ISP 通常是美韩的服务器,部分ISP骨干供应商有流量清洗服务,例如香港的PCCW。通常可以防御10G左右的小型攻击
  2. 流量清洗服务 例如:akamai(prolexic),nexusguard 我们最大受到过80G流量的攻击,成功被清洗,但是费用非常贵
  3. CDN 例如:百度云加速。

linux下nginx不支持中文URL路径的解决方案

未分类

1、确定你的系统是UTF编码

[root@localhost ~]# echo $LAGN
en_US.UTF-8

2、nginx配置文件里默认编码设置为utf-8

server
{
listen 80;
server_name .inginx.com ;
index index.html index.htm index.php;
root /usr/local/nginx/html/inginx.com;
charset utf-8;
}

3、将非UTF-8的文件名转换为UTF-8编码

做法很简单,把文件名都修改成utf8编码就可以了!

安装convmv,由他去转换编码:

yum install convmv -y
convmv -f GBK -t UTF8 -r --notest 目标路径

其中-f是源编码,-t是目标编码,-r是递归处理目录,–notest是不移动,实际上对文件进行改名。

nginx 解决微信文章图片防盗链

解决图片防盗链的方法就是要把请求 头的 referer 去掉就可以。

我们可以用 nginx 反向代理微信图片的链接,并把请求的 referer 去掉。

首先找到微信图片服务器的 IP

$ ping mmbiz.qpic.cn
PING ssd.tcdn.qq.com (110.81.153.148): 56 data bytes
64 bytes from 110.81.153.148: icmp_seq=0 ttl=56 time=10.590 ms
64 bytes from 110.81.153.148: icmp_seq=1 ttl=56 time=8.083 ms
64 bytes from 110.81.153.148: icmp_seq=2 ttl=56 time=10.891 ms
64 bytes from 110.81.153.148: icmp_seq=3 ttl=56 time=10.202 ms
^C
--- ssd.tcdn.qq.com ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 8.083/9.941/10.891/1.100 ms

配置微信图片反向代理

    location ~ /mmbiz_(.*)/ {
        proxy_pass         http://59.57.18.143;

        proxy_set_header   Host             "mmbiz.qpic.cn";
        proxy_set_header   Referer          "";
    }

移除微信图片的域名

使用 sub_filter 移除微信图片的域名

sub_filter "http://mmbiz.qpic.cn" "";
sub_filter_once off;

到这儿以微信文章图片防盗链。

shell 格式化输出nginx的编译参数

命令

nginx -V > nginx.txt
cat -n nginx.txt  | sed -n '5,18p' | awk '{$1="";print $0}' 
| sed 's/^[ ]*//g'  | tr 'n' ',' | sed -n 's/,//gp' | tr " " "n"

结果

configure
arguments:
--user=nginx
--group=nginx
--prefix=/usr/share/nginx
--sbin-path=/usr/sbin/nginx
--conf-path=/etc/nginx/nginx.conf
--error-log-path=/var/log/nginx/error.log
--http-log-path=/var/log/nginx/access.log
--http-client-body-temp-path=/var/lib/nginx/tmp/client_body
--http-proxy-temp-path=/var/lib/nginx/tmp/proxy
--http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi
--http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi
--http-scgi-temp-path=/var/lib/nginx/tmp/scgi
--pid-path=/var/run/nginx.pid
--lock-path=/var/lock/subsys/nginx
--with-http_ssl_module
--with-http_spdy_module
--with-http_v2_module
--with-http_realip_module
--with-http_addition_module
--with-http_xslt_module
--with-http_image_filter_module
--with-http_sub_module
--with-http_dav_module
--with-http_flv_module
--with-http_gzip_static_module
--with-http_secure_link_module
--with-http_degradation_module
--with-http_stub_status_module
--with-debug
--with-http_sysguard_module
--with-http_upstream_check_module
--with-http_lua_module
--with-http_dyups_module
--with-luajit-lib=/usr/lib
--with-luajit-inc=/usr/include/luajit-2.0

解释:

awk '{$1="";print $0}'

输出第2列至最后一列(即排除第一列)

 sed 's/^[ ]*//g'

去掉行首的空格

tr 'n' ',' | sed -n 's/,//gp'

去掉换行符,先把换行符替换成逗号(或者#¥%&等),然后再讲逗号删除。

ngxtop:在命令行实时监控 Nginx 的神器

Nginx网站服务器在生产环境中运行的时候需要进行实时监控。实际上,诸如Nagios, Zabbix, Munin 的网络监控软件是支持 Nginx 监控的。

如果你不需要以上软件提供的综合性报告或者长期数据统计功能,只是需要一种快速简便的办法去监控 Nginx 服务器的请求的话,我建议你采用一个叫 ngxtop 的命令行工具。

你马上就会发现 ngxtop 从界面和名称都借鉴了著名的top命令。ngxtop 是通过分析 Nginx 或者其他的日志文件,使用类似 top 命令的界面实时展示出来的。你可以说你知道的其他高端监控工具,但是在简洁这方面 ngxtop 无疑是最好的。简单就意味着不可替代。

本指南中,我将介绍如何使用 ngxtop 实时监控 Nginx 网站服务器。

Linux 上安装 ngxtop

首先在 Linux 系统中安装依赖库pip(LCTT译注:ngxtop是用python编写的)。

然后使用如下命令安装 ngxtop。

$ sudo pip install ngxtop

ngxtop 使用

基本使用方法如下:

ngxtop [options]
ngxtop [options] (print|top|avg|sum) <var>
ngxtop info

这里是一些通用选项。

  • -l : 指定日志文件的完整路径 (Nginx 或 Apache2)
  • -f : 日志格式
  • –no-follow: 处理当前已经写入的日志文件,而不是实时处理新添加到日志文件的日志
  • -t : 更新频率
  • -n : 显示行号
  • -o : 排序规则(默认是访问计数)
  • -a …, –a …: 添加表达式(一般是聚合表达式如: sum, avg, min, max 等)到输出中。
  • -v: 输出详细信息
  • -i : 只处理符合规则的记录

以下是一些内置变量,他们的含义不言自明。

  • bodybytessend
  • http_referer
  • httpuseragent
  • remote_addr
  • remote_user
  • request
  • status
  • time_local

使用 ngxtop 监控 Nginx

ngxtop 默认会从其配置文件 (/etc/nginx/nginx.conf) 中查找 Nginx 日志的地址。所以,监控 Nginx ,运行以下命令即可:

$ ngxtop

这将会列出10个 Nginx 服务,按请求数量排序。

显示前20个最频繁的请求:

$ ngxtop -n 20

未分类

获取Nginx基本信息:

$ ngxtop info

未分类

你可以自定义显示的变量,简单列出需要显示的变量。使用 “print” 命令显示自定义请求。

$ ngxtop print request http_user_agent remote_addr

未分类

显示请求最多的客户端IP地址

$ ngxtop top remote_addr

未分类

显示状态码是404的请求

$ ngxtop -i 'status == 404' print request status

未分类

除了Nginx,ngtop 还可以处理其他的日志文件,比如 Apache 的访问文件。使用以下命令监控 Apache 服务器:

$ tail -f /var/log/apache2/access.log | ngxtop -f commo

Nginx下使用Fastcgi_cache和ngx_pagespeed优化WordPress性能

今天要给大家分享的是最近明月一直在折腾的一个基于 Nginx 的静态化缓存方法,这个方法不是依托于 WordPress 系统的,而是直接在 WEB 服务器层面生成和调取静态化缓存的,可以说在执行效能上是 WordPress 插件静态化的效能无法比拟的,再配合ngx_pagespeed模块使用 Google 的 PageSpeed 技术压缩优化 js、css、图片等静态文件后,直接让 WordPress 在服务器层面就达到最佳的性能表现。
Nginx下使用Fastcgi_cache和ngx_pagespeed优化WordPress性能

未分类

最近也不知道是咋回事儿了,一直用的好好的 WP-Rocket 2.9.9版突然有一天造成 WordPress 登录失效了,无论如何都无法正常的登录后台,只有停用 WP-Rocket 后才可以正常登录,安装最新版 WP-Rocket 又出现频繁的报错和授权验证失败的问题,没办法只能舍弃这个缓存神器了。这时候想起里以前在【张戈博客】看到一篇【Nginx开启fastcgi_cache缓存加速,支持html伪静态页面】的文章,正好目前已经用的是 Nginx 了,所以就照着此文开始折腾了起来,没有想到竟然一步成功了,嘿嘿!

测试后发现缓存效果相当的“棒”,直接 WEB 服务器层面的缓存静态化表现真的是相当的“彪悍”呀,速度提升实在是太明显了。具体的教程我就不在这里过多的赘述了,大家可以参考【张戈博客】上文章即可了,里面说的是非常的明确了,如果是多站点的话可参考【Nginx配置多站点下的Proxy_cache或Fastcgi_cache缓存加速】,多思考、多实践一定可以成功的。运用 Nginx 配置文件的灵活性可以达到非常好的效果的。

顺便分享一点儿小知识,那就是 Nginx 下fastcgi_cache和proxy_cache的区别:

Nginx的Fastcgi_cache模块与Proxy_cache模块缓存区别

  1. proxy_cache的作用是缓存后端服务器的内容,包括静态的和动态。
  2. fastcgi_cache的作用是缓存fastcgi生成的内容,很多情况是php生成的动态的内容。
  3. proxy_cache缓存减少了nginx与后端通信的次数,节省了传输时间和后端宽带。
  4. fastcgi_cache缓存减少了nginx与php的通信的次数,更减轻了php和数据库(mysql)的压力,这比用memcached之类的缓存要轻松得多。

静态化缓存实现了,发现服务器的负载一直都保持很低,于是又开始考虑在 Nginx 里加入ngx_pagespeed模块来“锦上添花”(特别强调:ngx_pagespeed模块适合独立主机使用,虚拟主机的话不建议使用,因为ngx_pagespeed模块会增加主机CPU的负载的,鱼与熊掌不可兼得呀!),虽然几天以来在 Nginx 添加模块编译的时候尝试了多次失败后终于还是成功了(请参考【Nginx 模块 ngx_pagespeed-1.12.34.2-stable 终于编译成功了】),并且用上 ngx_pagespeed-1.12.34.2-stable 最新版,这个版本支持 Redis 的,好像是可以把一些静态文件比如: js、css、图片等的优化压缩工作放到 Redis 里(可以理解为内存里)来提升优化效能。

未分类

在使用ngx_pagespeed模块后明月以外的发现博客调用的 Google AdSense 广告代码载入时间也提升了不少,甚至很多的外链js、css调用载入速度都有所提升。 CDN 流量好像也节约了一点点,哈哈,真的好意外的效果呀。

还有一个意外惊喜,文章列表页的缩略图片竟然被ngx_pagespeed给转换成 WebP 格式了,哈哈!牛逼呀!

下面分享一下明月目前使用的ngx_pagespeed模块的配置,从这些配置上大家就可以感受到ngx_pagespeed的强大了:

# 启用ngx_pagespeed
pagespeed on;
#指定ngx_pagespeed缓存路径
pagespeed FileCachePath /var/ngx_pagespeed_blog_cache;
# 禁用CoreFilters
pagespeed RewriteLevel PassThrough;
#开启使用Redis
pagespeed RedisServer "host:port";
# X-Header 值,用于判断是否生效
pagespeed XHeaderValue "Powered By www.mydomain.com";
# 过滤规则
#pagespeed RewriteLevel OptimizeForBandwidth;
# 不需过滤的目录或文件
pagespeed Disallow "*/wp-admin/*";
pagespeed Disallow "*/wp-login.php*";
# 启用压缩空白过滤器
pagespeed EnableFilters collapse_whitespace;
 # 启用JavaScript库卸载
 pagespeed EnableFilters canonicalize_javascript_libraries;
# 把多个CSS文件合并成一个CSS文件(比较容易引起主题版面混乱,所以我禁用了
#pagespeed EnableFilters combine_css;
# 把多个JavaScript文件合并成一个JavaScript文件,禁用原因同上,大家可以酌情开启
#pagespeed EnableFilters combine_javascript;
# 删除带默认属性的标签
 pagespeed EnableFilters elide_attributes;
# 改善资源的可缓存性
 pagespeed EnableFilters extend_cache;
# 更换被导入文件的@import,精简CSS文件
 pagespeed EnableFilters flatten_css_imports;
 pagespeed CssFlattenMaxBytes 5120;
# 延时加载客户端看不见的图片
 pagespeed EnableFilters lazyload_images;
# 启用JavaScript缩小机制
 pagespeed EnableFilters rewrite_javascript;
# 启用图片优化机制
 pagespeed EnableFilters rewrite_images;
# 预解析DNS查询
 pagespeed EnableFilters insert_dns_prefetch;
# 重写CSS,首先加载渲染页面的CSS规则
 pagespeed EnableFilters prioritize_critical_css;

如果你用的也是 Nginx 的话记得一定不要忘了Fastcgi_cache和ngx_pagespeed这两个模块哦,这绝对是两个优化 WordPress 的神器呀,这里明月的叹服一下 Nginx 了,真心好牛逼呀,据说还有非常多的神乎其技的功能呢,明月正在努力的折腾学习中,以后要好的收获了我一定会及时分享给大家的。