biglittleant

不被嘲笑的梦想,是不值得去实现的

0%

假设nginx服务器的IP地址:192.168.56.11
当我们访问:http://192.168.56.11/test/test.html

如果proxy_pass 后端包含反斜线,后端服务器得到的URL为:192.168.56.12:8002/test.html
如果proxy_pass 后端不包含反斜线时,后端服务器得到的URL为:
192.168.56.12:8002/test/test.html

实例

proxy_pass 包含反斜线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
worker_processes  1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream test {
server 192.168.56.12:8002 weight=1 max_fails=2 fail_timeout=10s;
}
server {
listen 80;
location /test {
proxy_pass http://test/;
}

后端的access访问日志

- - [07/Nov/2016:10:33:38 +0000] "GETHTTP/1.0" 404 571 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36" "-"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
```

> URI的`/test/`被切割了。

## proxy_pass 后端不包含反斜杠

```shell
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream test {
server 192.168.56.12:8002 weight=1 max_fails=2 fail_timeout=10s;
}
server {
listen 80;
location /test {
proxy_pass http://test;
}

后端的access访问日志

- - [07/Nov/2016:10:37:49 +0000] "GETHTTP/1.0" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36" "-"
1

URL 是完整得URI。

简介:upsync模块是用来解决配置文件修改后,reload nginx进程造成性能下降的问题。

处理流程
它的功能是拉取 consul 的后端 server 的列表,并更新 Nginx 的路由信息。此模块不依赖于任何第三方模块。consul 作为 Nginx 的 db,利用 consul 的 KV 服务,每个 Nginx work 进程独立的去拉取各个 upstream 的配置,并更新各自的路由。

实战

给nginx打补丁包

这步可以不做,如果不做,编译的时候删除这个模块

1
2
3
git clone https://github.com/xiaokai-wang/nginx_upstream_check_module
## 打补丁包
patch -p0 < /usr/local/src/nginx_upstream_check_module-master/check_1.9.2+.patch

下载nginx-upsync-module源码

1
2
git clone https://github.com/weibocom/nginx-upsync-module.git

1
2
3
4
5
6
7
8
下载nginx源码
wget 'http://nginx.org/download/nginx-1.10.1.tar.gz'
tar -xzvf nginx-1.10.1.tar.gz
cd nginx-1.10.1/
开始编译
./configure --prefix=/data/app/nginx-1.10.1 --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module --add-module=/usr/local/src/nginx-upsync-module-master/ --add-module=/usr/local/src/nginx_upstream_check_module-master/
make
make install

启动consul

1
2
3
wget https://releases.hashicorp.com/consul/0.6.4/consul_0.6.4_linux_amd64.zip
unzip consul_0.6.4_linux_amd64.zip
./consul agent -advertise=x.x.x.x -client=0.0.0.0 -dev

创建nginx配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
mkdir -p /usr/local/nginx/conf/servers
/usr/local/nginx/conf/nginx.conf
events {
worker_connections 4096; ## Default: 1024
}

http {
upstream test {
# fake server otherwise ngx_http_upstream will report error when startup
server 127.0.0.1:11111;

# all backend server will pull from consul when startup and will delete fake server
upsync 127.0.0.1:8500/v1/kv/upstreams/test upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off;
upsync_dump_path /usr/local/nginx/conf/servers/servers_test.conf;
}

upstream bar {
server 127.0.0.1:8090 weight=1 fail_timeout=10 max_fails=3;
}

server {
listen 8080;

location = /proxy_test {
proxy_pass http://test;
}

location = /bar {
proxy_pass http://bar;
}

location = /upstream_show {
upstream_show;
}

}
}

测试

1
2
3
4
5
for i in `seq 3`;do mkdir html$i/test -p && echo $i >html$i/test/test.html; done;

docker run -d -p 8001:80 -v /root/html1/:/usr/share/nginx/html nginx
docker run -d -p 8002:80 -v /root/html2/:/usr/share/nginx/html nginx
docker run -d -p 8003:80 -v /root/html3/:/usr/share/nginx/html nginx

添加服务

1
2
3
4
curl -X PUT -d '{"weight":1, "max_fails":2, "fail_timeout":10}' http://127.0.0.1:8500/v1/kv/upstreams/test/192.168.56.12:8001
curl -X PUT -d '{"weight":1, "max_fails":2, "fail_timeout":10}' http://127.0.0.1:8500/v1/kv/upstreams/test/192.168.56.12:8002
curl -X PUT -d '{"weight":1, "max_fails":2, "fail_timeout":10}' http://127.0.0.1:8500/v1/kv/upstreams/test/192.168.56.12:8003

查看conf/servers/servers_test.conf 文件中是否有内容

1
2
3
4
cat conf/servers/servers_test.conf
server 192.168.56.12:8003 weight=1 max_fails=2 fail_timeout=10s;
server 192.168.56.12:8002 weight=1 max_fails=2 fail_timeout=10s;
server 192.168.56.12:8001 weight=1 max_fails=2 fail_timeout=10s;

或者浏览器打开http://192.168.56.11:8080/upstream_show?test
显示内容如下:

1
2
3
4
Upstream name: test; Backend server count: 3
server 192.168.56.12:8003 weight=1 max_fails=2 fail_timeout=10s;
server 192.168.56.12:8002 weight=1 max_fails=2 fail_timeout=10s;
server 192.168.56.12:8001 weight=1 max_fails=2 fail_timeout=10s;

总结
此模块只修改upstream 中的缓存信息,不能修改或添加其他配置

测试中遇到的问题
在添加服务时出现如下错误,导致服务添加不能实时进行,大约需要3分钟左右时间。

consul日志:

1
2
3
4
5
6
7
8
2016/03/22 05:34:42 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (149.023µs) from=127.0.0.1:38853
2016/03/22 05:34:43 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (146.759µs) from=127.0.0.1:38854
2016/03/22 05:34:45 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (149.853µs) from=127.0.0.1:38855
2016/03/22 05:34:46 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (111.46µs) from=127.0.0.1:38856
2016/03/22 05:34:48 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (142.696µs) from=127.0.0.1:38857
2016/03/22 05:34:48 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (112.089µs) from=127.0.0.1:38858
2016/03/22 05:34:49 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (114.29µs) from=127.0.0.1:38859
2016/03/22 05:34:50 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (148.245µs) from=127.0.0.1:38860

nginx日志

1
2
3
4
5
6
7
8
9
10
11
2016/03/22 05:35:09 [error] 18879#0: recv() failed (104: Connection reset by peer)
2016/03/22 05:35:09 [error] 18879#0: upsync_recv: recv error with upsync_server: 127.0.0.1:8500
2016/03/22 05:35:10 [error] 18879#0: recv() failed (104: Connection reset by peer)
2016/03/22 05:35:10 [error] 18879#0: upsync_recv: recv error with upsync_server: 127.0.0.1:8500
2016/03/22 05:35:11 [error] 18879#0: recv() failed (104: Connection reset by peer)
2016/03/22 05:35:11 [error] 18879#0: upsync_recv: recv error with upsync_server: 127.0.0.1:8500
2016/03/22 05:35:13 [error] 18879#0: recv() failed (104: Connection reset by peer)
2016/03/22 05:35:13 [error] 18879#0: upsync_recv: recv error with upsync_server: 127.0.0.1:8500
2016/03/22 05:35:13 [error] 18879#0: recv() failed (104: Connection reset by peer)
2016/03/22 05:35:13 [error] 18879#0: upsync_recv: recv error with upsync_server: 127.0.0.1:8500
2016/03/22 05:35:14 [error] 18879#0: recv() failed (104: Connection reset by peer)

问题现象
当添加一个服务时,出现此问题,新增服务不能及时添加到负载中,不影响运行正常的服务。 此时再往consul继续添加一个服务时,可能会导致此错误终止,并能成功添加当前两条服务记录。

帮助文档

官方github地址
nginx_upstream_check_module

rewrite这个模块允许使用正则表达式重写URI(需PCRE库),并且可以根据相关变量重定向和选择不同的配置。
如果这个指令在server字段中指定,那么将在被请求的location确定之前执行,如果在指令执行后所选择的location中有其他的重写规则,那么它们也被执行。如果在location中执行这个指令产生了新的URI,那么location又一次确定了新的URI。
这样的循环可以最多执行10次,超过以后nginx将返回500错误。

1
2
3
格式:	rewrite regex replacement [flag];
默认参数: —
作用域 : server, location, if
  • regex: 支持正则表达式,字符串等。
  • replacement:匹配规则后的重定向。
  • [flag]的参数包括:
    • last :表示完成rewrite,浏览器地址栏URL地址不变
    • break;本条规则匹配完成后,终止匹配,不再匹配后面的规则,浏览器地址栏URL地址不变
    • redirect:返回302临时重定向,浏览器地址会显示跳转后的URL地址,如果替换字段用http://开头则被使用
    • permanent:返回301永久重定向,浏览器地址栏会显示跳转后的URL地址

last 和 break的区别 break匹配完成不继续匹配,last匹配完成后,会使用新的URL继续向下匹配。

1
2
3
4
5
6
7
8
9
10
11
12
server {
location / {
rewrite /last/ /1.html last;
rewrite /break/ /1.html break;
}

location = /1.html {
return 400;
}
}
访问/last/时重写到/1.html,然后使用新的uri再匹配,正好匹配到locatoin = /q.html然后返回了400
访问/break时重写到/1.html,由于返回了break,则直接停止了

实例一:将 /work/* 重写为* 、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
upstream work {
server 192.168.56.12:8080;
server 192.168.56.11:8080;
}
server {
listen 80;
server_name blog.biglittleant.cn;

location /work/ {
rewrite "^/work/(.+)" /$1 break;
proxy_pass http://work;
}
location / {
root html;
index index.html index.htm;
}

实例二:补充官方的例子

1

if模块

1
2
3
格式:	if (condition) { ... }
默认值: —
作用域: server, location

if判断如果结果为true,那么在大括号中指定的模块指令将被执行,并且请求被分配在if指令内部的配置。 if指令内的配置继承自先前的配置级别。条件可以是以下任何一个:变量名;如果变量的值为空字符串或“0”,则为false;在版本1.0.1之前,以“0”开头的任何字符串被视为false值。

  • 使用“=”和“!=”运算符将变量与字符串进行比较;
  • 使用“〜”(区分大小写匹配)和“〜*”(区分大小写匹配)运算符匹配正则表达式。
  • 正则表达式中的括号包含起来以后在$ 1 .. $ 9变量中重用的捕获。负运算符“!〜”和“!〜*”也可用。
  • 如果正则表达式包含“}”或“;”字符,则整个表达式应用单引号或双引号括起来。
  • 使用“-f”和“!-f”运算符检查文件存在;
  • 使用“-d”和“!-d”运算符检查目录存在;
  • 使用“-e”和“!-e”运算符检查文件,目录或符号链接是否存在;
  • 使用“-x”和“!-x”运算符检查可执行文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if ($http_user_agent ~ MSIE) {
rewrite ^(.*)$ /msie/$1 break;
}

if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
set $id $1;
}

if ($request_method = POST) {
return 405;
}

if ($slow) {
limit_rate 10k;
}

if ($invalid_referer) {
return 403;
}

return模块

1
2
3
4
5
6
格式:	return code [text];
return code URL;
return URL;
默认值: —
作用域: server, location, if

使用nginx做负载均衡的两大模块:

  • upstream 定义负载节点池。
  • location 模块 进行URL匹配。
  • proxy模块 发送请求给upstream定义的节点池。

相关功能模块讲解

upstream模块解读

nginx 的负载均衡功能依赖于 ngx_http_upstream_module模块,所支持的代理方式有 proxy_pass(一般用于反向代理),fastcgi_pass(一般用于和动态程序交互),memcached_pass,proxy_next_upstream,fastcgi_next_pass,memcached_next_pass 。

upstream 模块应该放于http{}标签内。

模块写法:

1
2
3
4
5
6
7
upstream backend {
ip_hash;
server backend1.example.com weight=5;
server backend2.example.com:8080;
server backup1.example.com:8080 backup;
server backup2.example.com:8080 backup;
}

实例一:

1
2
3
4
5
6
7
8
9
10
11
upstream dynamic {
zone upstream_dynamic 64k;

server backend1.example.com weight=5;
server backend2.example.com:8080 fail_timeout=5s slow_start=30s;
server 192.0.2.1 max_fails=3;
server backend3.example.com resolve;

server backup1.example.com:8080 backup;
server backup2.example.com:8080 backup;
}

语法解释:
nginx默认支持四种调度算法

  • 轮询(rr),每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器故障,故障系统自动清除,使用户访问不受影响。
  • 轮询权值(weight),weight值越大,分配到的访问几率越高,主要用于后端每个服务器性能不均的情况。
  • ip_hash,每个请求按访问IP的hash结果分配,这样来自同一个IP的固定访问一个后端服务器,主要解决动态网站session共享的问题。
  • url_hash,按照访问的URL的hash结果来分配请求,是每个URL定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率,nginx本身不支持,如果想使用需要安装nginx的hash软件包。
  • fair,这个算法可以依据页面大小和加载时间长短智能的进行负载均衡,也就是根据后端服务器的响应时间来分配请求,相应时间短的优先分配,默认不支持,如果想使用需要安装upstream_fail模块。
  • least_conn 最少链接数,那个机器连接数少就分发。

server模块的写法 server IP 调度状态

server指令指定后端服务器IP地址和端口,同时还可以设定每个后端服务器在负载均衡调度中的状态。

  • down 表示当前的server暂时不参与负载均衡。
  • backup 预留的备份服务器,当其他所有的非backup服务器出现故障或者忙的时候,才会请求backup机器,因为这台集群的压力最小。
  • max_fails 允许请求失败的次数,默认是1,当超过最大次数时,返回proxy_next_upstream模块定义的错误。0表示禁止失败尝试,企业场景:2-3.京东1次,蓝汛10次,根据业务需求去配置。
  • fail_timeout,在经历了max_fails次失败后,暂停服务的时间。京东是3s,蓝汛是3s,根据业务需求配置。常规业务2-3秒合理。

例:如果max_fails是5,他就检测5次,如果五次都是502.那么,他就会根据fail_timeout 的值,等待10秒,再去检测。

server 如果接域名,需要内网有DNS服务器,或者在负载均衡器的hosts文件做域名解析。server后面还可以直接接IP或IP加端口。

location 模块解读

location作用:基于一个指令设置URI。
基本语法:

1
2
3
4
Syntax:	location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }
Default: —
Context: server, location
  • = 精确匹配,如果找到匹配=号的内容,立即停止搜索,并立即处理请求(优先级最高)
  • ~ 区分大小写
  • ~* 不区分大小写
  • ^~ 只匹配字符串,不匹配正则表达式
  • @ 指定一个命名的location,一般用于内部重定义请求,location @name {…}

官方的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
location = / {
[ configuration A ]
}
location / {
[ configuration B ]
}
location /documents/ {
[ configuration C ]
}
location ^~ /images/ {
[ configuration D ]
}
location ~* \.(gif|jpg|jpeg)$ {
[ configuration E ]
}

测试用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
location / {
return 401;
}
location = / {
return 402;
}
location /documents/ {
return 403;
}
location ^~ /images/ {
return 404;
}
location ~* \.(gif|jpg|jpeg)$ {
return 500;
}

测试结果(重点看):

1
2
3
4
5
6
7
8
9
10
[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/
402
[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/index.html
401
[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/documents/document.html
403
[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/images/1.gif
404
[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/dddd/1.gif
500

结果总结:
匹配的优先顺序,=>^~(匹配固定字符串,忽略正则)> 完全相等>~*>>/

proxy_pass 模块解读

proxy_pass 指令属于ngx_http_proxy_module 模块,此模块可以将请求转发到另一台服务器。

写法:

1
proxy_pass http://localhost:8000/uri/;

实例一:

1
2
3
4
5
6
7
8
9
10
11
12
13
upstream blog_real_servers {
server 10.0.0.9:80 weight=5;
server 10.0.0.10:80 weight=10;
server 10.0.0.19:82 weight=15;
}
server {
listen 80;
server_name blog.etiantian.org;
location / {
proxy_pass http://blog_real_servers;
proxy_set_header host $host;
}
}
  • proxy_set_header:当后端Web服务器上也配置有多个虚拟主机时,需要用该Header来区分反向代理哪个主机名,proxy_set_header host $host;
  • proxy_set_header X-Forwarded-For :如果后端Web服务器上的程序需要获取用户IP,从该Header头获取。proxy_set_header X-Forwarded-For $remote_addr;

配置后端服务器接收前端真实IP

配置如下:

1
2
3
4

log_format commonlog '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

rs_apache节点的httpd.conf配置

1
2
3
4
LogFormat "\"%{X-Forwarded-For}i\" %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{U
ser-Agent}i\"" combined修改日志记录
apache
LogFormat "\"%{X-Forwarded-For}i\" %l %u %t \"%r\" %>s %b" common

proxy_pass相关的优化参数

  • client_max_body_size 10m; 允许客户端请求的最大的单个文件字节数

  • client_body_buffer_size 128k; 缓冲区代理缓冲用户端请求的最大字节数 可以理解为先保存到本地再传给用户

  • proxy_connect_timeout 600; 跟后端服务器连接的超时时间_发起握手等候响应超时时间

  • proxy_read_timeout 600; 连接成功后_等候后端服务器响应时间_其实已经进入后端的排队之中等候处理

  • proxy_send_timeout 600; 后端服务器回传数据时间,就是在规定时间之内后端服务器必须传完所有的数据

  • proxy_buffer_size 8k; 代理请求缓存区,这个缓存区间会保存用户的头信息以供Nginx进行规则处理,一般只要设置能保存下头信息即可

  • proxy_buffers 4 32k; 同上 告诉Nginx保存单个页面使用的空间大小,假设网页大小平均在32k以下的话。

  • proxy_busy_buffers_size 64k; 如果系统很忙的时候可以申请更大的proxy_buffers 官方推荐(proxy_buffers*2)

  • proxy_max_temp_file_size 1024m; 当 proxy_buffers 放不下后端服务器的响应内容时,会将一部分保存到硬盘的临时文件中,这个值用来设置最大临时文件大小,默认1024M,它与 proxy_cache 没有关系。大于这个值,将从upstream服务器传回。设置为0禁用。

  • proxy_temp_file_write_size 64k; proxy缓存临时文件的大小 proxy_temp_path(可以在编译的时候)指定写到哪那个目录。

一个完整的nginx实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@lb01 conf]# cat nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
#blog lb by oldboy at 201303
upstream blog_real_servers {
server 10.0.0.9:80 weight=1 max_fails=1 fail_timeout=10s;
server 10.0.0.10:80 weight=1 max_fails=2 fail_timeout=20s;

}
server {
listen 80;
server_name blog.etiantian.org;
location / {
proxy_pass http://blog_real_servers;
include proxy.conf;
}
}
}
1
2
3
4
5
6
7
8
9
[root@lb01 conf]# cat proxy.conf
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k;

扩展补充

只允许使用GET,HEAD,POST方法去请求

1
2
3
4
## Only allow these request methods ##
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return 444;
}

实战

根据URI及location实现动静分离。

最终实现:

  1. /static/的URL都去访问10.0.0.9。
  2. /dynamic/的URL都去访问10.0.0.10。
  3. 图片这些静态文件去访问10.0.0.9。
  4. /upload/的URL都去访问10.0.0.10。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

[root@lb01 conf]# cat nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
#blog lb by oldboy at 201303

upstream static_pools {
server 10.0.0.9:80;
}
upstream dynamic_pools {
server 10.0.0.10:80;
}
upstream upload_pools {
server 10.0.0.9:80;
}

server {
listen 80;
server_name blog.biglittleant.cn;

location / {
proxy_pass http://static_pools;
include proxy.conf;
}

location /static/ {
proxy_pass http://static_pools;
include proxy.conf;
}

location ~* \.(gif|jpg|jpeg)$ {
proxy_pass http://static_pools;
include proxy.conf;
}

location /dynamic/ {
proxy_pass http://dynamic_pools;
include proxy.conf;
}
location /upload/ {
proxy_pass http://upload_pools;
include proxy.conf;
}
}
}

实现苹果手机和安卓手机访问不同的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
listen 80;
server_name blog.etiantian.org;
location / {
if ($http_user_agent ~* "android")
{
proxy_pass http://android_pools;
}
if ($http_user_agent ~* "iphone")
{
proxy_pass http://iphone_pools;
}
proxy_pass http://pc_pools;
include extra/proxy.conf;
}
access_log off;
}

参考文档

nginx-proxy_pass官网

死磕nginx系列–配置文档解读

nginx配置文件结构图

nginx配置文件结构

nginx配置文件主要分为四个部分:

  • main(全局设置)

  • server(主机设置)

  • upstream(负载均衡服务器设置)

  • location(URL匹配特点位置的设置)

    server部分的指令主要用于指定主机和端口,upstream指令主要用于负载均衡和设置一系列的后端服务器,location部分用于匹配网页位置位置。
    关系如下:
    server继承main,location继承server,upstream即不会继承其他设置也不会被继承。

main全局配置

nginx在运行时与具体业务功能(比如http服务或者email服务代理)无关的一些参数,比如工作进程数,运行的身份等。

1
2
3
4
5
6
user  www www;
worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000;
error_log /data/logs/nginx_error.log crit;
pid /usr/local/webserver/nginx/nginx.pid;
worker_rlimit_nofile 65535;
  • user www www; # 指定nginx进程使用什么用户启动
  • worker_processes 4; #指定启动多少进程来处理请求,一般情况下设置成CPU的核数,如果开启了ssl和gzip更应该设置成与逻辑CPU数量一样甚至为2倍,可以减少I/O操作。使用grep ^processor /proc/cpuinfo | wc -l查看CPU核数。
  • worker_cpu_affinity 0001 0010 0100 1000; #在高并发情况下,通过设置将CPU和具体的进程绑定来降低由于多核CPU切换造成的寄存器等现场重建带来的性能损耗。如worker_cpu_affinity 0001 0010 0100 1000; (四核)。
  • error_log /data/logs/nginx_error.log crit; error_log是个主模块指令,用来定义全局错误日志文件。日志输出级别有debug、info、notice、warn、error、crit可供选择,其中,debug输出日志最为最详细,而crit输出日志最少。
  • pid /usr/local/webserver/nginx/nginx.pid; # 指定进程pid文件的位置。
  • worker_rlimit_nofile 65535; ##用于指定一个nginx进程可以打开的最多文件描述符数目,这里是65535,需要使用命令“ulimit -n 65535”来设置。

events模块

1
2
3
4
events{
use epoll;
worker_connections 65536;
}
  • use epoll;use是个事件模块指令,用来指定Nginx的工作模式。Nginx支持的工作模式有select、poll、kqueue、epoll、rtsig和/dev/poll。其中select和poll都是标准的工作模式,kqueue和epoll是高效的工作模式,不同的是epoll用在Linux平台上,而kqueue用在BSD系统中。对于Linux系统,epoll工作模式是首选。在操作系统不支持这些高效模型时才使用select。

  • worker_connections 65536; 每一个worker进程能并发处理(发起)的最大连接数(包含与客户端或后端被代理服务器间等所有连接数)。nginx作为反向代理服务器,计算公式 最大连接数 = worker_processes * worker_connections/4,所以这里客户端最大连接数是1024,这个可以增到到8192都没关系,看情况而定,但不能超过后面的worker_rlimit_nofile。当nginx作为http服务器时,计算公式里面是除以2。进程的最大连接数受Linux系统进程的最大打开文件数限制,在执行操作系统命令ulimit -n 65536worker_connections的设置才能生效。

http服务器

1
2
3
4
5
6
http
{
include mime.types;
default_type application/octet-stream;
#charset gb2312;
}
  • include是个主模块指令,实现对配置文件所包含的文件的设定,可以减少主配置文件的复杂度。类似于Apache中的include方法。
  • default_type属于HTTP核心模块指令,这里设定默认类型为二进制流,也就是当文件类型未定义时使用这种方式,例如在没有配置PHP环境时,Nginx是不予解析的,此时,用浏览器访问PHP文件就会出现下载窗口。
  • charset gb2312; 指定客户端编码格式。

客户端head缓存的设置

1
2
3
4
5
6
7
8
9
10
11
12
server_names_hash_bucket_size 128;
client_header_buffer_size 32k;
large_client_header_buffers 4 128k;
client_max_body_size 8m;
client_max_body_size 10m;
client_body_buffer_size 128k;
sendfile on ;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65 :
client_body_timeout 60s;
send_timeout 60s;
  • server_names_hash_bucket_size 128; 服务器名字的hash表大小
  • client_header_buffer_size 32k; 用来指定来自客户端请求头的header buffer 大小。对于大多数请求,1K的缓存已经足够了,如果自定义了消息头或有更大的cookie,可以增大缓存区大小。
  • large_client_header_buffers 4 128k; 用来指定客户端请求中较大的消息头的缓存最大数量和大小,4为个数,128k为大小,最大缓存为4个128KB。
  • client_max_body_size 8m; 客户端请求的最大的单个文件字节数。
  • client_max_body_size 10m; 允许客户端请求的最大单文件字节数。如果有上传较大文件,请设置它的限制值。
  • client_body_buffer_size 128k; 缓冲区代理缓冲用户端请求的最大字节数
  • sendfile on ; 开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,减少用户空间到内核空间的上下文切换。对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。开启 tcp_nopush on;tcp_nodelay on; 防止网络阻塞。
  • keepalive_timeout 65 : 长连接超时时间,单位是秒,这个参数很敏感,涉及浏览器的种类、后端服务器的超时设置、操作系统的设置,可以另外起一片文章了。长连接请求大量小文件的时候,可以减少重建连接的开销,但假如有大文件上传,65s内没上传完成会导致失败。如果设置时间过长,用户又多,长时间保持连接会占用大量资源。
  • client_body_timeout 60s; 用于设置客户端请求主体读取超时时间,默认是60s。如果超过这个时间,客户端还没有发送任何数据,nginx将返回Request time out(408)错误。
  • send_timeout : 用于指定响应客户端的超时时间。这个超时仅限于两个连接活动之间的时间,如果超过这个时间,客户端没有任何活动,Nginx将会关闭连接。

FastCGI相关参数是为了改善网站的性能:减少资源占用,提高访问速度。下面参数看字面意思都能理解。

1
2
3
4
5
6
7
8
9
10
11
12
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;
fastcgi_cache TEST;
fastcgi_cache_path /usr/local/nginx/fastcgi_cache levels=1:2 keys_zone=TEST:10m inactive=5m;
fastcgi_cache_valid 200 302 1h;
fastcgi_cache_valid 301 1d;
fastcgi_cache_valid any 1m;
  • fastcgi_connect_timeout 300; 指定连接到后端FastCGI的超时时间。
  • fastcgi_send_timeout 300; 指定向FastCGI传送请求的超时时间,这个值是已经完成两次握手后向FastCGI传送请求的超时时间。
  • fastcgi_read_timeout 300; 指定接收FastCGI应答的超时时间,这个值是已经完成两次握手后接收FastCGI应答的超时时间。
  • fastcgi_buffer_size 64k; 用于指定读取FastCGI应答第一部分需要多大的缓冲区,这个值表示将使用1个64KB的缓冲区读取应答的第一部分(应答头),可以设置为fastcgi_buffers选项指定的缓冲区大小。
  • fastcgi_buffers 4 64k; 指定本地需要用多少和多大的缓冲区来缓冲FastCGI的应答请求。如果一个PHP脚本所产生的页面大小为256KB,那么会为其分配4个64KB的缓冲区来缓存;如果页面大小大于256KB,那么大于256KB的部分会缓存到fastcgi_temp指定的路径中,但是这并不是好方法,因为内存中的数据处理速度要快于硬盘。一般这个值应该为站点中PHP脚本所产生的页面大小的中间值,如果站点大部分脚本所产生的页面大小为256KB,那么可以把这个值设置为“16 16k”、“4 64k”等。
  • fastcgi_busy_buffers_size 128k; 默认值是fastcgi_buffers的两倍。
  • fastcgi_temp_file_write_size 128k; 表示在写入缓存文件时使用多大的数据块,默认值是fastcgi_buffers的两倍。
  • fastcgi_cache TEST; 表示开启FastCGI缓存并为其指定一个名称。开启缓存非常有用,可以有效降低CPU的负载,并且防止502错误的发生。但是开启缓存也会引起很多问题,要视具体情况而定。
  • fastcgi_cache_path /usr/local/nginx/fastcgi_cache levels=1:2 keys_zone=TEST:10m inactive=5m; FastCGI缓存指定一个文件路径、目录结构等级、关键字区域存储时间和非活动删除时间。
  • fastcgi_cache_valid 200 302 1h; 用来指定应答代码的缓存时间。实例中的值表示将200和302应答缓存一个小时,将301应答缓存1天,其他应答均缓存1分钟。

gzip模块设置

1
2
3
4
5
6
7
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 6;
gzip_types text/html text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
gzip_vary on;
  • gzip on; 开启gzip压缩输出
  • gzip_min_length 1k; 最小压缩文件大小,页面字节数从header头的Content-Length中获取。默认值为0,不管多大页面都压缩,建议设置成大于1K的字节数,小于1K可能会越压越大。
  • gzip_buffers 4 16k; 压缩缓冲区,表示申请四个16K的内存作为压缩结果流缓存,默认是申请与原始数据大小相同的内存空间来存储gzip压缩结果。
  • gzip_http_version 1.1; 用于设置识别HTTP协议版本,默认是1.1,目前主流浏览器都已成指出。(默认1.1,前端如果是squid2.5请使用1.0)
  • gzip_comp_level 6; 压缩等级,1压缩比最小,处理速度最快,9压缩比最大,传输速度快,但是消耗CPU资源。
  • gzip_types text/plain application/x-javascript text/css application/xml; 压缩类型,默认就已经包含text/html,所以下面就不用再写了,写上去也不会有问题,但是会有一个warn。
  • gzip_vary on; 和http头有关系,会在响应头加个 Vary: Accept-Encoding ,可以让前端的缓存服务器缓存经过gzip压缩的页面,例如,用Squid缓存经过Nginx压缩的数据。
  • gzip_proxied any; Nginx作为反向代理的时候启用,决定开启或者关闭后端服务器返回的结果是否压缩,匹配的前提是后端服务器必须要返回包含”Via”的 header头。
  • limit_zone crawler $binary_remote_addr 10m; 开启限制IP连接数的时候需要使用

nginx 配置虚拟主机

配置虚拟主机流程:

  1. 复制一段完整的server标签段,到结尾。注意:要放在http的结束大括号前,也就是server标签段放入http标签。
  2. 更改server_name 及对应网页的root根目录。
  3. 检查配置文件语法,平滑重启服务。
  4. 创建server_name 对应网页的根目录,并且建立测试文件,如果没有index首页会出现403错误。
  5. 对客户端server_name 的主机做host 解析或DNS配置。并检查(ping)。
  6. 浏览器访问,或者在Linux客户端做host解析,用wget或curl 访问。

http服务上支持若干虚拟主机。每个虚拟主机一个对应的server配置项,配置项里面包含该虚拟主机相关的配置。在提供mail服务的代理时,也可以建立若干server。每个server通过监听地址或端口来区分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server
{
listen 80 default;
server_name _;
index index.html index.htm index.php;
root /data/htdocs/www;
#server_name_in_redirect off;
location ~ .*\.(php|php5)?$
{
#fastcgi_pass unix:/tmp/php-cgi.sock;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fcgi.conf;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
}
location ~ .*\.(js|css)?$
{
expires 1h;
}
}
  • listen 80; 监听端口,默认80,小于1024的要以root启动。可以为listen *:80、listen 127.0.0.1:80等形式。
  • server_name blog.biglittleant.cn; 服务器名,如localhost、www.example.com,可以通过正则匹配。
  • root /var/www/html 定义服务器的默认网站根目录位置。如果locationURL匹配的是子目录或文件,root没什么作用,一般放在server指令里面或/下。
  • index index.jsp index.html index.htm 定义路径下默认访问的文件名,一般跟着root放。

location

关于location匹配规则的写法,参考死磕nginx系列–使用nginx做负载均衡

proxy_pass http:/backend

请求转向backend定义的服务器列表,即反向代理,对应upstream负载均衡器。也可以proxy_pass http://ip:port。

1
2
3
4
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;

这四个暂且这样设,如果深究的话,请参考死磕nginx系列–使用nginx做负载均衡

其它

访问控制 allow/deny

Nginx 的访问控制模块默认就会安装,而且写法也非常简单,可以分别有多个allow,deny,允许或禁止某个ip或ip段访问,依次满足任何一个规则就停止往下匹配。如:

1
2
3
4
5
6
7
8
9
location /nginx-status {
stub_status on;
access_log off;
# auth_basic "NginxStatus";
# auth_basic_user_file /usr/local/nginx-1.6/htpasswd;
allow 192.168.10.100;
allow 172.29.73.0/24;
deny all;
}

我们也常用 httpd-devel 工具的 htpasswd 来为访问的路径设置登录密码:

1
2
3
4
5
6
# htpasswd -c htpasswd admin
New passwd:
Re-type new password:
Adding password for user admin
# htpasswd htpasswd admin //修改admin密码
# htpasswd htpasswd sean //多添加一个认证用户

这样就生成了默认使用CRYPT加密的密码文件。打开上面nginx-status的两行注释,重启nginx生效。

列出目录 autoindex

Nginx默认是不允许列出整个目录的。如需此功能,打开nginx.conf文件,在location,server 或 http段中加入如下参数:

1
2
3
4
5
6
location /images {
root /var/www/nginx-default/images;
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
}
  • autoindex on; 运行列出目录内容。另外两个参数最好也加上去。
  • autoindex_exact_size off; 默认为on,显示出文件的确切大小,单位是bytes。改为off后,显示出文件的大概大小,单位是kB或者MB或者GB。
  • autoindex_localtime on; 默认为off,显示的文件时间为GMT时间。改为on后,显示的文件时间为文件的服务器时间。

完整得通用配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
user  www www;
worker_processes 2;
error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
pid logs/nginx.pid;
events {
use epoll;
worker_connections 2048;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
# tcp_nopush on;
keepalive_timeout 65;
# gzip压缩功能设置
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 6;
gzip_types text/html text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
gzip_vary on;

# http_proxy 设置
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 75;
proxy_send_timeout 75;
proxy_read_timeout 75;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
proxy_temp_path /usr/local/nginx/proxy_temp 1 2;
# 设定负载均衡后台服务器列表
upstream backend {
#ip_hash;
server 192.168.10.100:8080 max_fails=2 fail_timeout=30s ;
server 192.168.10.101:8080 max_fails=2 fail_timeout=30s ;
}
# 很重要的虚拟主机配置
server {
listen 80;
server_name itoatest.example.com;
root /apps/oaapp;
charset utf-8;
access_log logs/host.access.log main;
#对 / 所有做负载均衡+反向代理
location / {
root /apps/oaapp;
index index.jsp index.html index.htm;
proxy_pass http://backend;
proxy_redirect off;
# 后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;

}
#静态文件,nginx自己处理,不去backend请求后端的服务
location ~* /download/ {
root /data/app/nginx/downloads;

}
location ~ .*\.(gif|jpg|jpeg|bmp|png|ico|txt|js|css)$
{
root /data/app/nginx/images;
expires 7d;
}
location /nginx_status {
stub_status on;
access_log off;
allow 192.168.10.0/24;
deny all;
}
location ~ ^/(WEB-INF)/ {
deny all;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
## 其它虚拟主机,server 指令开始
}

参考

优化Nginx中FastCGI参数的实例

nginx 功能介绍

简介

Nginx因为它的稳定性、丰富的模块库、灵活的配置和低系统资源的消耗而闻名.业界一致认为它是Apache2.2+mod_proxy_balancer的轻量级代替者,不仅是因为响应静态页面的速度非常快,而且它的模块数量达到Apache的近2/3。对proxy和rewrite模块的支持很彻底,还支持mod_fcgi、ssl、vhosts ,适合用来做mongrel clusters的前端HTTP响应。
nginx和Apache一样使用模块化设计,nginx模块包括内置模块和第三方模块,其中内置模块中包含主模块和事件模块。

nginx处理请求逻辑图
Nginx模块的HTTP请求和响应过程

nginx可以提供的服务

  1. WWW web 服务.
  2. 负载均衡 (反向代理)
  3. web cache(web 缓存)

nginx 的优点

  1. 高并发。静态小文件
  2. 占用资源少。2万并发、10个线程,内存消耗几百M。
  3. 功能种类比较多。web,cache,proxy。每一个功能都不是特别强。
  4. 支持epoll模型,使得nginx可以支持高并发。
  5. nginx 配合动态服务和Apache有区别。(FASTCGI 接口)
  6. 利用nginx可以对IP限速,可以限制连接数。
  7. 配置简单,更灵活。

nginx应用场合

  1. 静态服务器。(图片,视频服务)另一个lighttpd。并发几万,html,js,css,flv,jpg,gif等。
  2. 动态服务,nginx——fastcgi 的方式运行PHP,jsp。(PHP并发在500-1500,MySQL 并发在300-1500)。
  3. 反向代理,负载均衡。日pv2000W以下,都可以直接用nginx做代理。
  4. 缓存服务。类似 SQUID,VARNISH。

主流web服务产品对比说明

Apache-特性

  1. 2.2版本本身稳定强大,据官方说:其2.4版本性能更强。
  2. prefork模式取消了进程创建开销,性能很高。
  3. 处理动态业务数据时,因关联到后端的引擎和数据库,瓶颈不在与Apache本身。
  4. 高并发时消耗系统资源相对多一些。
  5. 基于传统的select模型。
  6. 扩展库,DSO方法,

nginx-特性

  1. 基于异步IO模型,(epoll,kqueue),性能强,能够支持上万并发。
  2. 对小文件支持很好,性能很高(限静态小文件1M)。
  3. 代码优美,扩展库必须编译进主程序。
  4. 消耗代码资源比较低。
  5. lighttpd(百度贴吧,豆瓣)
  6. 基于异步IO模式,性能和nginx相近。
  7. 扩展库是SO模式,比nginx要灵活。
  8. 通过差距(mod_secdownload)可实现文件URL地址加密。

web服务产品性能对比测试

静态数据性能对比
  1. 处理静态文件Apache性能比nginx和lighttpd要差。
  2. nginx在处理小文件优势明显。
  3. 处理静态小文件(小于1M),nginx和lighttpd比Apache更有优势,lighttpd最强。
    动态数据性能对比
  4. 处理动态内容三者相差不大,主要取决于PHP和数据库的压力。
  5. 当处理动态数据时,三者差距不大,从测试结果看,Apache更有优势一点。这是因为处理动态数据能力取决于PHP和后端数据的提供服务能力。也就是说瓶颈不在web服务器上。
  6. 一般PHP引擎支持的并发参考值300-1000,JAVA引擎并发300-1000,数据库的并发300-1000.
为什么nginx的总体性能比Apache高。
  1. nginx使用最新的epoll和kqueue网络IO模型,而Apache使用床头的select模式。
  2. 目前Linux下能够承受高并发访问的squid、Memcached 都采用的是epoll网络IO模型。

如何选择WEB服务器:

静态业务:高并发、采用nginx,lighttpd,根据自己的掌握程度或公司的要求。
动态业务:采用nginx和Apache均可。
既有静态业务又有动态业务:nginx或Apache,不要多选要单选。
动态业务可以由前端代理(haproxy),根据页面元素的类型,向后转发相应的服务器进行处理。
思想:我们工作都不要追求一步到位,满足需求的前提下,先用,然后逐步完善。
提示:nginx做web(Apache,lighttpd)、反向代理(haproxy,lvs,nat)及缓存服务器(squid)也是不错的。
最终建议:对外的业务nginx,对内的业务Apache(yum httpd mysql-server php)。

nginx实战过程

安装依赖包

nginx安装依赖GCC、openssl-devel、pcre-devel和zlib-devel软件库
Pcre全称(Perl Compatible Regular Expressions),中文perl兼容正则表达式,pcre官方站点

1
2
yum install  pcre pcre-devel -y
yum install openssl openssl-devel -y

开始编译

使用./configure --help 查看各个模块的使用情况,使用--without-http_ssi_module的方式关闭不需要的模块。可以使用--with-http_perl_modules方式安装需要的模块。
查看编译参数中已经开启的选项

编译命令

1
2
3
4
5
./configure --prefix=/application/nginx-1.6.2 --user=nginx --group=nginx  --with-http_ssl_module  --with-http_stub_status_module

useradd nginx -M -s /sbin/nologin
make && make install
ln -s /application/nginx1.6.2/ /application/nginx

测试nginx配置文件是否正常

1
2
3
/application/nginx/sbin/nginx -t
#nginx: the configuration file /application/nginx-1.6.2/conf/nginx.conf syntax is ok
#nginx: configuration file /application/nginx-1.6.2/conf/nginx.conf test is successful

启动nginx服务器

1
2
3
4
/application/nginx/sbin/nginx -t  ##检查配置文件
/application/nginx/sbin/nginx ##确定nginx服务
netstat -lntup |grep nginx ## 检查进程是否正常
curl 192.168.56.12 ## 确认结果

nginx其他命令

1
2
3
4
5
6
7
nginx -s signal
signal:
stop — fast shutdown
quit — graceful shutdown
reload — reloading the configuration file
reopen — reopening the log files
用来打开日志文件,这样nginx会把新日志信息写入这个新的文件中

/data/app/nginx/sbin/nginx -V 查看已经编译的参数。

使用kill命令操作nginx。格式:kill -信号 PID

信号名称

  • TERM,INT 快速关闭
  • QUIT 优雅的关闭,保持吸纳有的客户端连接
  • HUP 重启应用新的配置文件
  • USR1 重新打开日志文件
  • USR2 升级程序
  • WINCH 优雅的关闭工作进程

例子

1
2
kill -QUIT  `cat /data/app/nginx/nginx.pid`
kill -HUP `cat /data/app/nginx/nginx.pid`

nginx配置文件

配置基础配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
user  www www;
worker_processes 8;
error_log /data/logs/nginx_error.log crit;
pid /usr/local/webserver/nginx/nginx.pid;
#Specifies the value for maximum file descriptors that can be opened by this process.
worker_rlimit_nofile 65535;
events
{
use epoll;
worker_connections 65535;
}
http
{
include mime.types;
default_type application/octet-stream;
#charset gb2312;
server_names_hash_bucket_size 128;
client_header_buffer_size 32k;
large_client_header_buffers 4 32k;
client_max_body_size 8m;
sendfile on;
tcp_nopush on;
keepalive_timeout 60;
tcp_nodelay on;
include gzip.conf;
include blog.biglittle.cn.conf;

gzip.conf文件内容

1
2
3
4
5
6
7
8
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/css application/xml;
gzip_vary on;
#limit_zone crawler $binary_remote_addr 10m;

blog.biglittle.cn.conf文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server
{
listen 80 default;
server_name blog.biglittleant.cn;
index index.html index.htm index.php;
root /data/nginx/biglittleant/blog;
#server_name_in_redirect off;
location ~ .*\.(php|php5)?$
{
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
}
location ~ .*\.(js|css)?$
{
expires 1h;
}
}

fastcgi.conf文件内容

1
2
3
4
5
6
7
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;

nx access日志配置

access_log日志配置

access_log用来定义日志级别,日志位置。语法如下:
日志级别: debug > info > notice > warn > error > crit > alert > emerg

1
2
3
4
语法格式:	access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
access_log off;
默认值 : access_log logs/access.log combined;
作用域 : http, server, location, if in location, limit_except

实例一:

1
access_log /spool/logs/nginx-access.log compression buffer=32k;

log_format 定义日志格式

1
2
3
语法格式:	log_format name [escape=default|json] string ...;
默认值 : log_format combined "...";
作用域 : http

实例一:

1
2
3
4
5
log_format compression '$remote_addr - $remote_user [$time_local] '
'"$request" $status $bytes_sent '
'"$http_referer" "$http_user_agent" "$gzip_ratio"';

access_log /spool/logs/nginx-access.log compression buffer=32k;

常见的日志变量

  • $remote_addr, $http_x_forwarded_for 记录客户端IP地址
  • $remote_user 记录客户端用户名称
  • $request 记录请求的URL和HTTP协议(GET,POST,DEL,等)
  • $status 记录请求状态
  • $body_bytes_sent 发送给客户端的字节数,不包括响应头的大小; 该变量与Apache模块mod_log_config里的“%B”参数兼容。
  • $bytes_sent 发送给客户端的总字节数。
  • $connection 连接的序列号。
  • $connection_requests 当前通过一个连接获得的请求数量。
  • $msec 日志写入时间。单位为秒,精度是毫秒。
  • $pipe 如果请求是通过HTTP流水线(pipelined)发送,pipe值为“p”,否则为“.”。
  • $http_referer 记录从哪个页面链接访问过来的
  • $http_user_agent 记录客户端浏览器相关信息
  • $request_length 请求的长度(包括请求行,请求头和请求正文)。
  • $request_time 请求处理时间,单位为秒,精度毫秒; 从读入客户端的第一个字节开始,直到把最后一个字符发送给客户端后进行日志写入为止。
  • $time_iso8601 ISO8601标准格式下的本地时间。
  • $time_local 通用日志格式下的本地时间。

open_log_file_cache

使用open_log_file_cache来设置日志文件缓存(默认是off)。

  • max:设置缓存中的最大文件描述符数量,如果缓存被占满,采用LRU算法将描述符关闭。
  • inactive:设置存活时间,默认是10s
  • min_uses:设置在inactive时间段内,日志文件最少使用多少次后,该日志文件描述符记入缓存中,默认是1次
  • valid:设置检查频率,默认60s
  • off:禁用缓存
1
2
3
4
语法格式:	open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];
open_log_file_cache off;
默认值: open_log_file_cache off;
作用域: http, server, location

实例一

1
open_log_file_cache max=1000 inactive=20s valid=1m min_uses=2;

nginx日志调试技巧

设置 Nginx 仅仅记录来自于你的 IP 的错误

当你设置日志级别成 debug,如果你在调试一个在线的高流量网站的话,你的错误日志可能会记录每个请求的很多消息,这样会变得毫无意义。

events{...}中配置如下内容,可以使 Nginx 记录仅仅来自于你的 IP 的错误日志。

1
2
3
events {
debug_connection 1.2.3.4;
}
调试 nginx rewrite 规则

调试rewrite规则时,如果规则写错只会看见一个404页面,可以在配置文件中开启nginx rewrite日志,进行调试。

1
2
3
4
server {
error_log /var/logs/nginx/example.com.error.log;
rewrite_log on;
}

rewrite_log on; 开启后,它将发送所有的 rewrite 相关的日志信息到 error_log 文件中,使用 [notice] 级别。随后就可以在error_log 查看rewrite信息了。

使用location记录指定URL的日志
1
2
3
4
5
6
server {
error_log /var/logs/nginx/example.com.error.log;
location /static/ {
error_log /var/logs/nginx/static-error.log debug;
}
}

配置以上配置后,/static/ 相关的日志会被单独记录在static-error.log文件中。

nx监控

常用的监控参数

开启状态页

1
2
3
4
5
6
7
#设定查看Nginx状态的地址
location /NginxStatus {
stub_status on;
access_log off;
# auth_basic "NginxStatus";
# auth_basic_user_file conf/htpasswd;
}
  • stub_status on; 表示开启stubStatus的工作状态统计功能。
  • access_log off; 关闭access_log 日志记录功能。
  • auth_basic “NginxStatus”; auth_basic 是nginx的一种认证机制。
  • auth_basic_user_file conf/htpasswd; 用来指定密码文件的位置。

    配置登录密码

1
2
3
yum install -y httpd-tools
/usr/local/apache/bin/htpasswd -c /data/app/nginx/conf/htpasswd biglittleant
New password:

完成后会在/data/app/nginx/conf/目录下生成htpasswd文件。

访问URL

1
2
3
4
5
# curl http://127.0.0.1/NginxStatus
Active connections: 11921
server accepts handled requests
11989 11989 11991
Reading: 0 Writing: 7 Waiting: 42
  • active connections – 活跃的连接数量
  • server accepts handled requests — 总共处理了11989个连接 , 成功创建11989次握手, 总共处理了11991个请求
  • Reading — 读取客户端的连接数.
  • Writing — 响应数据到客户端的数量
  • Waiting — 开启 keep-alive 的情况下,这个值等于 active – (reading+writing), 意思就是 Nginx 已经处理完正在等候下一次请求指令的驻留连接.

    编写zabbix监控脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
nginx_status_fun(){
NGINX_PORT=$1
NGINX_COMMAND=$2
nginx_active(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| grep 'Active' | awk '{print $NF}'
}
nginx_reading(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| grep 'Reading' | awk '{print $2}'
}
nginx_writing(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| grep 'Writing' | awk '{print $4}'
}
nginx_waiting(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| grep 'Waiting' | awk '{print $6}'
}
nginx_accepts(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| awk NR==3 | awk '{print $1}'
}
nginx_handled(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| awk NR==3 | awk '{print $2}'
}
nginx_requests(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| awk NR==3 | awk '{print $3}'
}
case $NGINX_COMMAND in
active)
nginx_active;
;;
reading)
nginx_reading;
;;
writing)
nginx_writing;
;;
waiting)
nginx_waiting;
;;
accepts)
nginx_accepts;
;;
handled)
nginx_handled;
;;
requests)
nginx_requests;
esac
}

nx优化

nginx内核优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
net.ipv4.tcp_fin_timeout = 2
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_keepalive_time = 600
net.ipv4.ip_local_port_range = 4000 65000
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.tcp_max_tw_buckets = 36000
net.ipv4.route.gc_timeout = 100
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_synack_retries = 1
net.core.somaxconn = 16384
net.core.netdev_max_backlog = 16384
net.ipv4.tcp_max_orphans = 16384
#以下参数是对iptables防火墙的优化,防火墙不开会提示,可以忽略不理。
net.ipv4.ip_conntrack_max = 25000000
net.ipv4.netfilter.ip_conntrack_max=25000000
net.ipv4.netfilter.ip_conntrack_tcp_timeout_established=180
net.ipv4.netfilter.ip_conntrack_tcp_timeout_time_wait=120
net.ipv4.netfilter.ip_conntrack_tcp_timeout_close_wait=60
net.ipv4.netfilter.ip_conntrack_tcp_timeout_fin_wait=120

报错汇总

问题1:编译报错

1
2
3
4
./configure: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using —without-http_rewrite_module(伪静态)
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre=<path> option
1
yum install  pcre pcre-devel -y

问题2: 提示找不到libpcre.so.1

解决:

  1. find / -name libpcre.so*
  2. 将找到的路径 追加到 /etc/ld.so.conf
  3. ldconfig 生效。
  4. ln -s /ser/local/lib/libpcre.so.l /lib64
  5. 或编译时指定源码的安装路径:--with-pcre=/data/tools/pcre-8.33
  6. 最终解决方案 yum install pcre-devel -y 不会出现上述报错。

问题3:启动nginx报错

1
2
3
4
5
6
7
[root@centos6 tools]# /application/nginx/sbin/nginx
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] still could not bind()

解决办法:(因为开启了Apache服务)

1
2
3
4
5
6
7
[root@nfs-client application]# lsof -i :80
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
httpd 35833 www 4u IPv6 129886 0t0 TCP *:http (LISTEN)
httpd 35834 www 4u IPv6 129886 0t0 TCP *:http (LISTEN)
httpd 98511 root 4u IPv6 129886 0t0 TCP *:http (LISTEN)
[root@nfs-client application]# /application/apache/bin/apachectl stop
[root@nfs-client application]# /application/nginx/sbin/nginx ##重新启动nginx。

扩展阅读:

nginx全局变量

  • $args :这个变量等于请求行中的参数,同$query_string
  • $content_length : 请求头中的Content-length字段。
  • $content_type : 请求头中的Content-Type字段。
  • $document_root : 当前请求在root指令中指定的值。
  • $host : 请求主机头字段,否则为服务器名称。
  • $http_user_agent : 客户端agent信息
  • $http_cookie : 客户端cookie信息
  • $limit_rate : 这个变量可以限制连接速率。
  • $request_method : 客户端请求的动作,通常为GET或POST。
  • $remote_addr : 客户端的IP地址。
  • $remote_port : 客户端的端口。
  • $remote_user : 已经经过Auth Basic Module验证的用户名。
  • $request_filename : 当前请求的文件路径,由root或alias指令与URI请求生成。
  • $scheme : HTTP方法(如http,https)。
  • $server_protocol : 请求使用的协议,通常是HTTP/1.0或HTTP/1.1。
  • $server_addr : 服务器地址,在完成一次系统调用后可以确定这个值。
  • $server_name : 服务器名称。
  • $server_port : 请求到达服务器的端口号。
  • $request_uri : 包含请求参数的原始URI,不包含主机名,如:”/foo/bar.php?arg=baz”。
  • $uri : 不带请求参数的当前URI,$uri不包含主机名,如”/foo/bar.html”。
  • $document_uri : 与$uri相同。

例子:

1
2
3
4
5
6
7
8
9
访问链接是:http://localhost:88/test1/test2/test.php
网站路径是:/var/www/html

$host:localhost
$server_port:88
$request_uri:http://localhost:88/test1/test2/test.php
$document_uri:/test1/test2/test.php
$document_root:/var/www/html
$request_filename:/var/www/html/test1/test2/test.php

web服务器事件处理模型

select

select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。

poll

poll在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。

epoll

直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

nginx -s reload 过程:

nginx主进程读取配置文件,如果发现配置文件变更,会创建一个新的主进程,然后同时旧的进程,及旧的子进程关闭,旧进程会拒绝新的连接,服务到自己的连接结束,然后关闭。

Apache select模型和 nginx epoll 模型对比讲解

Nginx的高并发得益于其采用了epoll模型,与传统的服务器程序架构不同,epoll是linux内核2.6以后才出现的。下面通过比较Apache和Nginx工作原理来比较。

传统Apache都是多进程或者多线程来工作,假设是多进程工作(prefork),apache会先生成几个进程,类似进程池的工作原理,只不过这里的进程池会随着请求数目的增加而增加。对于每一个连接,apache都是在一个进程内处理完毕。具体是 recv(),以及根据 URI 去进行磁盘I/O来寻找文件,还有 send()都是阻塞的。其实说白了都是 apche 对于套接字的I/O,读或者写,但是读或者写都是阻塞的,阻塞意味着进程就得挂起进入sleep状态,那么一旦连接数很多,Apache必然要生成更多的进程来响应请求,一旦进程多了,CPU对于进程的切换就频繁了,很耗资源和时间,所以就导致apache性能下降了,说白了就是处理不过来这么多进程了。其实仔细想想,如果对于进程每个请求都没有阻塞,那么效率肯定会提高很多。

Nginx采用epoll模型,异步非阻塞。对于Nginx来说,把一个完整的连接请求处理都划分成了事件,一个一个的事件。比如accept(), recv(),磁盘I/O,send()等,每部分都有相应的模块去处理,一个完整的请求可能是由几百个模块去处理。真正核心的就是事件收集和分发模块,这就是管理所有模块的核心。只有核心模块的调度才能让对应的模块占用CPU资源,从而处理请求。拿一个HTTP请求来说,首先在事件收集分发模块注册感兴趣的监听事件,注册好之后不阻塞直接返回,接下来就不需要再管了,等待有连接来了内核会通知你(epoll的轮询会告诉进程),cpu就可以处理其他事情去了。一旦有请求来,那么对整个请求分配相应的上下文(其实已经预先分配好),这时候再注册新的感兴趣的事件(read函数),同样客户端数据来了内核会自动通知进程可以去读数据了,读了数据之后就是解析,解析完后去磁盘找资源(I/O),一旦I/O完成会通知进程,进程开始给客户端发回数据send(),这时候也不是阻塞的,调用后就等内核发回通知发送的结果就行。整个下来把一个请求分成了很多个阶段,每个阶段都到很多模块去注册,然后处理,都是异步非阻塞。异步这里指的就是做一个事情,不需要等返回结果,做好了会自动通知你。

select/epoll的特点

select的特点:select 选择句柄的时候,是遍历所有句柄,也就是说句柄有事件响应时,select需要遍历所有句柄才能获取到哪些句柄有事件通知,因此效率是非常低。但是如果连接很少的情况下, select和epoll的LT触发模式相比, 性能上差别不大。
这里要多说一句,select支持的句柄数是有限制的, 同时只支持1024个,这个是句柄集合限制的,如果超过这个限制,很可能导致溢出,而且非常不容易发现问题, 当然可以通过修改linux的socket内核调整这个参数。
epoll的特点:epoll对于句柄事件的选择不是遍历的,是事件响应的,就是句柄上事件来就马上选择出来,不需要遍历整个句柄链表,因此效率非常高,内核将句柄用红黑树保存的。
对于epoll而言还有ET和LT的区别,LT表示水平触发,ET表示边缘触发,两者在性能以及代码实现上差别也是非常大的。

可以举一个简单的例子来说明Apache的工作流程,我们平时去餐厅吃饭。餐厅的工作模式是一个服务员全程服务客户,流程是这样,服务员在门口等候客人(listen),客人到了就接待安排的餐桌上(accept),等着客户点菜(request uri),去厨房叫师傅下单做菜(磁盘I/O),等待厨房做好(read),然后给客人上菜(send),整个下来服务员(进程)很多地方是阻塞的。这样客人一多(HTTP请求一多),餐厅只能通过叫更多的服务员来服务(fork进程),但是由于餐厅资源是有限的(CPU),一旦服务员太多管理成本很高(CPU上下文切换),这样就进入一个瓶颈。

再来看看Nginx得怎么处理?餐厅门口挂个门铃(注册epoll模型的listen),一旦有客人(HTTP请求)到达,派一个服务员去接待(accept),之后服务员就去忙其他事情了(比如再去接待客人),等这位客人点好餐就叫服务员(数据到了read()),服务员过来拿走菜单到厨房(磁盘I/O),服务员又做其他事情去了,等厨房做好了菜也喊服务员(磁盘I/O结束),服务员再给客人上菜(send()),厨房做好一个菜就给客人上一个,中间服务员可以去干其他事情。整个过程被切分成很多个阶段,每个阶段都有相应的服务模块。我们想想,这样一旦客人多了,餐厅也能招待更多的人。

不管是Nginx还是Squid这种反向代理,其网络模式都是事件驱动。事件驱动其实是很老的技术,早期的select、poll都是如此。后来基于内核通知的更高级事件机制出现,如libevent里的epoll,使事件驱动性能得以提高。事件驱动的本质还是IO事件,应用程序在多个IO句柄间快速切换,实现所谓的异步IO。事件驱动服务器,最适合做的就是这种IO密集型工作,如反向代理,它在客户端与WEB服务器之间起一个数据中转作用,纯粹是IO操作,自身并不涉及到复杂计算。反向代理用事件驱动来做,显然更好,一个工作进程就可以run了,没有进程、线程管理的开销,CPU、内存消耗都小。

所以Nginx、Squid都是这样做的。当然,Nginx也可以是多进程 + 事件驱动的模式,几个进程跑libevent,不需要Apache那样动辄数百的进程数。Nginx处理静态文件效果也很好,那是因为静态文件本身也是磁盘IO操作,处理过程一样。至于说多少万的并发连接,这个毫无意义。随手写个网络程序都能处理几万的并发,但如果大部分客户端阻塞在那里,就没什么价值。

再看看Apache或者Resin这类应用服务器,之所以称他们为应用服务器,是因为他们真的要跑具体的业务应用,如科学计算、图形图像、数据库读写等。它们很可能是CPU密集型的服务,事件驱动并不合适。例如一个计算耗时2秒,那么这2秒就是完全阻塞的,什么event都没用。想想MySQL如果改成事件驱动会怎么样,一个大型的join或sort就会阻塞住所有客户端。这个时候多进程或线程就体现出优势,每个进程各干各的事,互不阻塞和干扰。当然,现代CPU越来越快,单个计算阻塞的时间可能很小,但只要有阻塞,事件编程就毫无优势。所以进程、线程这类技术,并不会消失,而是与事件机制相辅相成,长期存在。

总言之,事件驱动适合于IO密集型服务,多进程或线程适合于CPU密集型服务,它们各有各的优势,并不存在谁取代谁的倾向。

相关参考

nginx-日志高级技巧
nginx-官方文档
nginx优化
查看网站排名

kafka-python-client-example

安装kafka-python

pip安装

1
2
pip install kafka-python

源码安装

1
2
3
4
5
6
7
8
9
10
### pip
git clone https://github.com/dpkp/kafka-python
pip install ./kafka-python
### Setuptools
git clone https://github.com/dpkp/kafka-python
easy_install ./kafka-python
### setup
git clone https://github.com/dpkp/kafka-python
cd kafka-python
python setup.py install

如果想启用压缩功能需要额外安装以下两个模块

1
2
pip install lz4tools
pip install xxhash

使用方法

kafka生产端

第一步:连接到服务器端

1
2
3
4
5
6
from kafka import KafkaProducer
from kafka.errors import KafkaError

## 连接到服务器端
producer = KafkaProducer(bootstrap_servers=['192.168.56.12:9092'])

第二步:发送一个简单的消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
## 默认发送普通的消息
datenow = datetime.datetime.now().strftime('%Y-%m-%d:%H-%M-%s')
my_bytes = bytes(source=datenow,encoding='utf-8')
future = producer.send('topic1', my_bytes) ##消息必须是二进制格式

### OR 延时发送,并获取相关参数
try:
record_metadata = future.get(timeout=10)
except KafkaError:
# Decide what to do if produce request failed...
#log.exception()
pass

# Successful result returns assigned partition and offset
print (record_metadata.topic) ##打印写到那个topic上了。
print (record_metadata.partition) ## 打印消息所在的分区。
print (record_metadata.offset) ## 打印消息的位置

第三步:发送json格式的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# produce keyed messages to enable hashed partitioning
producer.send('my-topic', key=b'foo', value=b'bar')

# encode objects via msgpack
producer = KafkaProducer(value_serializer=msgpack.dumps) ##msgpack为自定义json格式。
producer.send('msgpack-topic', {'key': 'value'})

# produce json messages
producer = KafkaProducer(value_serializer=lambda m: json.dumps(m).encode('utf-8'),
bootstrap_servers=['192.168.56.12:9092'])
producer.send('json-topic1', {'key': 'value'})

# produce asynchronously
for _ in range(100):
producer.send('my-topic', b'msg')

# block until all async messages are sent
producer.flush() ##锁住进程,直到所有消息发送完毕,在执行下一步。

# configure multiple retries
producer = KafkaProducer(retries=5)

kafka消费端

kafka 实时消费程序

只消费新写入的消息,不消费旧消息。

1
2
3
4
5
6
7
8
9
10
11
12
from kafka import KafkaConsumer

# To consume latest messages and auto-commit offsets
consumer = KafkaConsumer('my-topic',
group_id='my-group', ## 定义一个组,group中记录office_set的位置。
bootstrap_servers=['localhost:9092'])
for message in consumer:
# message value and key are raw bytes -- decode if necessary!
# e.g., for unicode: `message.value.decode('utf-8')`
print ("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition,
message.offset, message.key,
message.value))

kafka消息早期的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    consumer = KafkaConsumer('topic1',
auto_offset_reset='earliest',
enable_auto_commit=False,
bootstrap_servers=['192.168.56.12:9092'])

for message in consumer:
# message value and key are raw bytes -- decode if necessary!
# e.g., for unicode: `message.value.decode('utf-8')`
print ("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition,
message.offset, message.key,
message.value))

### 结果
topic1:0:0: key=None value=b'11-16-19:11-2016-00'
topic1:0:1: key=None value=b'11-16-19:11-2016-02'
topic1:0:2: key=None value=b'11-16-19:11-2016-03'
topic1:0:3: key=None value=b'11-16-19:11-2016-03'
topic1:0:4: key=None value=b'2016-11-19:11-05-1479524731'


自定义分析结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    consumer = KafkaConsumer('json-topic1',
value_deserializer=lambda m: json.loads(m.decode('utf-8')),
auto_offset_reset='earliest', ## or latest。
enable_auto_commit=False, ## 如果设置为False,不会自动提交office_set的位置。
bootstrap_servers=['192.168.56.12:9092'])

for message in consumer:
# message value and key are raw bytes -- decode if necessary!
# e.g., for unicode: `message.value.decode('utf-8')`
print ("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition,
message.offset, message.key,
message.value))

### 结果
json-topic1:0:0: key=None value={'key': 'value'}

其他参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 如果1s没有数据,就退出。
KafkaConsumer(consumer_timeout_ms=1000)

# 使用正则去匹配topic。
consumer = KafkaConsumer()
consumer.subscribe(pattern='^awesome.*')

# 开启多个客户端去消费消息。
# Use multiple consumers in parallel w/ 0.9 kafka brokers
# typically you would run each on a different server / process / CPU
consumer1 = KafkaConsumer('my-topic',
group_id='my-group',
bootstrap_servers='my.server.com')
consumer2 = KafkaConsumer('my-topic',
group_id='my-group',
bootstrap_servers='my.server.com')

Example

  • 将文件a.txt的内容写入到kafka中。
  • 消费者定义个my-group的组去消费kafka中的数据。

第一步编写一个生产者,生产消息。

1
2
3
4
5
6
7
from kafka import KafkaProducer

producer = KafkaProducer(bootstrap_servers=['192.168.56.12:9092'])
with open('a.txt','rb') as file:
for n in file:
future = producer.send('topic1', n)
producer.flush()

第一步编写一个消费者,消费消息

1
2
3
4
5
6
7
8
9
10
11
12
13
from kafka import KafkaConsumer

consumer = KafkaConsumer('topic1',
group_id='my-group',
bootstrap_servers=['192.168.56.12:9092'])

for message in consumer:
# message value and key are raw bytes -- decode if necessary!
# e.g., for unicode: `message.value.decode('utf-8')`
print ("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition,
message.offset, message.key,
message.value))

帮助文档

kafka-python官方参考

kafka简介

Apache Kafka是由Apache软件基金会开发的一个开源消息系统项目,由Scala写成。Kafka最初是由LinkedIn开发,并于2011年初开源。该项目的目标是为处理实时数据提供一个统一、高通量、低等待的平台。
Kafka是一个分布式的、分区的、多复本的日志提交服务。它通过一种独一无二的设计提供了一个消息系统的功能。

kafka应用场景

  • Kafka可以应用于消息系统,比如,当下较为热门的消息推送,这些消息推送系统的消息源,可以使用Kafka作为系统的核心组建来完成消息的生产和消息的消费。
  • 然后是网站的行迹,我们可以将企业的Portal,用户的操作记录等信息发送到Kafka中,按照实际业务需求,可以进行实时监控,或者做离线处理等。
  • 一个是日志收集,类似于Flume套件这样的日志收集系统,但Kafka的设计架构采用push/pull,适合异构集群,Kafka可以批量提交消息,对Producer来说,在性能方面基本上是无消耗的,而在Consumer端中,我们可以使用HDFS这类的分布式文件存储系统进行存储。

相关术语

  • Kafka维护按类区分的消息,称为主题(topic)
  • 生产者(producer)向kafka的主题发布消息
  • 消费者(consumer)向主题注册,并且接收发布到这些主题的消息
  • kafka以一个拥有一台或多台服务器的集群运行着,每一台服务器称为broker
  • 从高层来说,生产者(producer)通过网络发消息到kafka集群,而kafka集群则以下面这种方式对消费者进行服务。

kafka实战

1
2
3
4
5
6
7
cd /usr/local/src/
wget https://www.apache.org/dyn/closer.cgi?path=/kafka/0.10.1.0/kafka_2.11-0.10.1.0.tgz
tar -xzf kafka_2.11-0.10.1.0.tgz
mv kafka_2.11-0.10.1.0 /data/app/
ln -s /data/app/kafka_2.11-0.10.1.0 /data/app/kafka
/data/app/kafka/bin/zookeeper-server-start.sh config/zookeeper.properties
/data/app/kafka/bin/kafka-server-start.sh config/server.properties

kafka单节点实战

编辑zookeeper配置文件

1
vim /data/app/kafka/config/zookeeper.properties

编辑kafka配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
vim /data/app/kafka/config/server.properties
broker.id=0
port=9092
advertised.host.name=10.0.2.150
num.network.threads=3

num.io.threads=8
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600
log.dirs=/tmp/kafka-logs
num.partitions=1
num.recovery.threads.per.data.dir=1
log.retention.hours=168
log.segment.bytes=1073741824
log.retention.check.interval.ms=300000
log.cleaner.enable=false
zookeeper.connect=localhost:4188
zookeeper.connection.timeout.ms=6000

#这三个必须得配置
#这个是配置PRODUCER/CONSUMER连上来的时候使用的地址(必须得配置)
advertised.host.name=192.168.56.12
#设置KAFKA LOG路径
log.dirs=$KAFKA_HOME/logs/kafka-logs
#设置ZOOKEEPER的连接地址
zookeeper.connect=192.168.56.12:2181

启动kafka服务

1
sh /data/app/kafka/bin/kafka-server-start.sh /data/app/kafka/config/server.properties >/dev/null 2>&1 &

kafka集群实战

创建三个配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cat >/data/app/kafka/config/server.properties-0<<EOF
broker.id=0
port=9090
advertised.host.name=10.0.2.150
num.network.threads=3
num.io.threads=8
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600
log.dirs=/tmp/kafka-logs-0
num.partitions=1
num.recovery.threads.per.data.dir=1
log.retention.hours=168
log.segment.bytes=1073741824
log.retention.check.interval.ms=300000
log.cleaner.enable=false
zookeeper.connect=127.0.0.1:2880,127.0.0.1:2881,127.0.0.1:2882
zookeeper.connection.timeout.ms=6000
EOF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat >/data/app/kafka/config/server.properties-1<<EOF
broker.id=1
port=9091
advertised.host.name=10.0.2.150
num.network.threads=3
num.io.threads=8
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600
log.dirs=/tmp/kafka-logs-1
num.partitions=1
num.recovery.threads.per.data.dir=1
log.retention.hours=168
log.segment.bytes=1073741824
log.retention.check.interval.ms=300000
log.cleaner.enable=false
zookeeper.connect=127.0.0.1:2880,127.0.0.1:2881,127.0.0.1:2882
zookeeper.connection.timeout.ms=6000
EOF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat >/data/app/kafka/config/server.properties-2<<EOF
broker.id=2
port=9092
advertised.host.name=10.0.2.150
num.network.threads=3
num.io.threads=8
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600
log.dirs=/tmp/kafka-logs-2
num.partitions=1
num.recovery.threads.per.data.dir=1
log.retention.hours=168
log.segment.bytes=1073741824
log.retention.check.interval.ms=300000
log.cleaner.enable=false
zookeeper.connect=127.0.0.1:2880,127.0.0.1:2881,127.0.0.1:2882
zookeeper.connection.timeout.ms=6000
EOF

启动kafka集群

1
2
3
./kafka-server-start.sh /data/app/kafka/config/server.properties >/dev/null 2>&1 &
./kafka-server-start.sh /data/app/kafka/config/server.properties-1 >/dev/null 2>&1 &
./kafka-server-start.sh /data/app/kafka/config/server.properties-2 >/dev/null 2>&1 &

创建一个topic 一个分区,三个主机

1
2
[root@localhost kafka]# ./bin/kafka-topics.sh --create --zookeeper 127.0.0.1:2880  --replication-factor 3 --partitions 1 --topic topics
Created topic "topics".
  • –topic指定topic name
  • –partitions指定分区数,这个参数需要根据broker数和数据量决定,正常情况下,每个broker上两个partition最好;
  • –replication-factor指定partition的replicas数,建议设置为2;
  • KAFKA有几个,replication-factor就填几个

查看topics的信息

1
2
3
4
[root@localhost kafka]# ./bin/kafka-topics.sh --describe --zookeeper 127.0.0.1:2881 --topic topics
Topic:topics PartitionCount:1 ReplicationFactor:3 Configs:
Topic: topics Partition: 0 Leader: 1 Replicas: 1,0,2 Isr: 1,0,2
[root@localhost kafka]#
  • Leader:负责处理消息的读和写,Leader是从所有节点中随机选择的。
  • Replicas:列出了所有的副本节点,不管节点是否在服务中。
  • Isr:是正在服务中的节点

kafka创建topic

1
./bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test

kafka管理

kafka配置管理

  • advertised.host.name=eventbus1.kafka
  • num.network.threads=3 broker处理消息的最大线程数,一般情况下数量为cpu核数
  • num.io.threads: Kafka broker 处理磁盘 IO 的线程数
  • socket.receive.buffer.bytes: socket的接收缓冲区大小
  • socket.send.buffer.bytes: socket的发送缓冲区大小
  • socket.request.max.bytes=104857600 socket请求的最大数值
  • log.dirs=/var/log/kafka 消息日志存放位置
  • num.partitions: 创建 topic 如果不指定分区数时的默认值
  • log.flush.interval.messages=1000 表示每当消息记录数达到1000时flush一次数据到磁盘
  • log.flush.interval.ms=1000 表示每间隔1000毫秒flush一次数据到磁盘
  • log.retention.bytes: topic 每个分区的最大文件大小
  • log.retention.hours: 消息保留的最大时间
  • log.segment.bytes =102410241024 topic的分区是以一堆segment文件存储的,这个控制每个segment的大小,会被topic创建时的指定参数覆盖
  • log.retention.check.interval.ms=30000000 文件大小检查的周期时间
  • auto.create.topics.enable: 自动创建 topic
  • default.replication.factor: 创建 topic 如果不指定复制因子时的默认值
  • delete.topic.enable: 是否支持删除 topic
  • message.max.bytes: 消息的最大尺寸
  • num.replica.fetchers: 从分区 Leader 复制消息的线程数
  • queued.max.requests: 等待 IO 线程处理的请求队列最大数,若是等待 IO 的请求超过这个数值,就会停止接受外部消息
  • zookeeper.connect = localhost:2181 zookeeper集群的地址,可以是多个用逗号分割 hostname1:port1,hostname2:port2,hostname3:port3
  • zookeeper.session.timeout.ms=6000 ZooKeeper的最大超时时间,就是心跳的间隔,若是没有反映,那么认为已经死了,不易过大
  • zookeeper.connection.timeout.ms =6000 ZooKeeper的连接超时时间
  • zookeeper.sync.time.ms =2000 ZooKeeper集群中leader和follower之间的同步时间

这里配置broker的时候,每台机器上的broker保证唯一,从0开始。如:在另外2台机器上分别配置broker.id=1,broker.id=2

kafka配置优化

网络和io操作线程配置优化

1
2
3
4
5
6
7
8
9
# broker处理消息的最大线程数
num.network.threads=xxx
# broker处理磁盘IO的线程数
num.io.threads=xxx

建议配置:
一般num.network.threads主要处理网络io,读写缓冲区数据,基本没有io等待,配置线程数量为cpu核数加1.

num.io.threads主要进行磁盘io操作,高峰期可能有些io等待,因此配置需要大些。配置线程数量为cpu核数2倍,最大不超过3倍.

log数据文件刷新策略

1
2
3
4
5
6
为了大幅度提高producer写入吞吐量,需要定期批量写文件。
建议配置:
# 每当producer写入10000条消息时,刷数据到磁盘
log.flush.interval.messages=10000
# 每间隔1秒钟时间,刷数据到磁盘
log.flush.interval.ms=1000

日志保留策略配置

当kafka server的被写入海量消息后,会生成很多数据文件,且占用大量磁盘空间,如果不及时清理,可能磁盘空间不够用,kafka默认是保留7天。

1
2
3
4
5
6
7
log.retention.hours=72
# 保留三天,也可以更短

log.segment.bytes=1073741824
# 段文件配置1GB,有利于快速回收磁盘空间,重启kafka加载也会加快(如果文件过小,则文件数量比较多,
# kafka启动时是单线程扫描目录(log.dir)下所有数据文件)

开启自动创建配置:

1
2
auto.create.topics.enable=true
使用程序直接往kafka中相应的topic发送数据,如果topic不存在就会按默认配置进行创建。

创建topic

1
sh kafka-topics.sh --create --topic topic --replication-factor 1 --partitions 1 --zookeeper localhost:4188

KAFKA有几个,replication-factor就填几个

我们查看该Topic的相关信息

1
kafka-topics.sh --zookeeper localhost:4188 --topic topic --describe

查看都有哪些topic

1
./bin/kafka-topics.sh --list --zookeeper localhost:2181

模拟数据的生产和消费

使用producer生产消息

1
2
3
4
[root@localhost bin]# sh kafka-console-producer.sh --broker-list localhost:9092 --sync --topic topic
[2015-12-10 13:54:40,460] WARN Property topic is not valid (kafka.utils.VerifiableProperties)
hello
你好

使用consumer去消费消息

1
2
3
[root@localhost bin]# sh kafka-console-consumer.sh --zookeeper 10.0.2.150:4188 --topic topic --from-beginning
hello
你好

修改topic的partition

1
2
3
4
## 通过kafka-topics.sh工具的alter命令,将topic_test的partitions从12增加到20

./bin/kafka-topics.sh –zookeeper 192.168.2.225:2183/config/mobile/mq –alter –partitions 20 –topic topic_test

修改kafka的分片配置

操作步骤如下:

操作,是指手动写扩充replicas的配置文件,然后使用工具进行操作

查看topic的详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
lizhitao@users-MacBook-Pro-2:~$ ./bin/kafka-topics.sh –zookeeper 192.168.2.225:2183/config/mobile/mq –describe –topic test.example
Topic:test.example PartitionCount:12 ReplicationFactor:1 Configs:
Topic: test.example Partition: 0 Leader: 0 Replicas: 0 Isr: 0
Topic: test.example Partition: 1 Leader: 1 Replicas: 1 Isr: 1
Topic: test.example Partition: 2 Leader: 2 Replicas: 2 Isr: 2
Topic: test.example Partition: 3 Leader: 0 Replicas: 0 Isr: 0
Topic: test.example Partition: 4 Leader: 1 Replicas: 1 Isr: 1
Topic: test.example Partition: 5 Leader: 2 Replicas: 2 Isr: 2
Topic: test.example Partition: 6 Leader: 0 Replicas: 0 Isr: 0
Topic: test.example Partition: 7 Leader: 1 Replicas: 1 Isr: 1
Topic: test.example Partition: 8 Leader: 2 Replicas: 2 Isr: 2
Topic: test.example Partition: 9 Leader: 0 Replicas: 0 Isr: 0
Topic: test.example Partition: 10 Leader: 1 Replicas: 1 Isr: 1
Topic: test.example Partition: 11 Leader: 2 Replicas: 2 Isr: 2

修改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
将原有replicas为[0]扩充为[0,4], [1]扩充为[1,5],[2]扩充为[2,3]
[sankuai@data-kafka01 kafka]$ cat partitions-to-move.json
{
“partitions”:
[
{
“topic”: “test.example”,
“partition”: 0,
“replicas”: [0,4]
},
.....
{
“topic”: “test.example”,
“partition”: 11,
“replicas”: [2,3]
}
],
“version”:1
}

执行

1
./bin/kafka-reassign-partitions.sh –zookeeper 192.168.2.225:2183/config/mobile/mq –reassignment-json-file partitions-to-move.json –execute

检查修改情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[sankuai@data-kafka01 kafka]$ ./bin/kafka-topics.sh –zookeeper 192.168.2.225:2183/config/mobile/mq –describe –topic test.example
Topic:test.example PartitionCount:12 ReplicationFactor:2 Configs:
Topic: test.example Partition: 0 Leader: 0 Replicas: 0,4 Isr: 0,4
Topic: test.example Partition: 1 Leader: 1 Replicas: 1,5 Isr: 1,5
Topic: test.example Partition: 2 Leader: 2 Replicas: 2,3 Isr: 2,3
Topic: test.example Partition: 3 Leader: 0 Replicas: 0,4 Isr: 0,4
Topic: test.example Partition: 4 Leader: 1 Replicas: 1,5 Isr: 1,5
Topic: test.example Partition: 5 Leader: 2 Replicas: 2,3 Isr: 2,3
Topic: test.example Partition: 6 Leader: 0 Replicas: 0,4 Isr: 0,4
Topic: test.example Partition: 7 Leader: 1 Replicas: 1,5 Isr: 1,5
Topic: test.example Partition: 8 Leader: 2 Replicas: 2,3 Isr: 2,3
Topic: test.example Partition: 9 Leader: 0 Replicas: 0,4 Isr: 0,4
Topic: test.example Partition: 10 Leader: 1 Replicas: 1,5 Isr: 1,5
Topic: test.example Partition: 11 Leader: 2 Replicas: 2,3 Isr: 2,3

kafka性能优化

1
2
3
4
5
####  配置jmx服务
kafka server中默认是不启动jmx端口的,需要用户自己配置
vim bin/kafka-run-class.sh
#最前面添加一行
JMX_PORT=8060

kafka监控和告警

通过使用,个人总结以上三种监控程序的优缺点:

Kafka Web Console:监控功能较为全面,可以预览消息,监控Offset、Lag等信息,但存在bug,不建议在生产环境中使用。

Kafka Manager:偏向Kafka集群管理,若操作不当,容易导致集群出现故障。对Kafka实时生产和消费消息是通过JMX实现的。没有记录Offset、Lag等信息。

KafkaOffsetMonitor:程序一个jar包的形式运行,部署较为方便。只有监控功能,使用起来也较为安全。

若只需要监控功能,推荐使用KafkaOffsetMonito,若偏重Kafka集群管理,推荐使用Kafka Manager。

因为都是开源程序,稳定性欠缺。故需先了解清楚目前已存在哪些Bug,多测试一下,避免出现类似于Kafka Web Console的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
描述:所有的topic的消息速率(消息数/秒)
Mbean名:"kafka.server":name="AllTopicsMessagesInPerSec",type="BrokerTopicMetrics"
正常的值:

描述:所有的topic的流入数据速率(字节/秒)
Mbean名:"kafka.server":name="AllTopicsBytesInPerSec",type="BrokerTopicMetrics"
正常的值:

描述:producer或Fetch-consumer或Fetch-follower的请求速率(请求次数/秒)
Mbean名:"kafka.network":name="{Produce|Fetch-consumer|Fetch-follower}-RequestsPerSec",type="RequestMetrics"
正常的值:

描述:所有的topic的流出数据速率(字节/秒)
Mbean名: "kafka.server":name="AllTopicsBytesOutPerSec",type="BrokerTopicMetrics"
正常的值:

描述:刷日志的速率和耗时
Mbean名: "kafka.log":name="LogFlushRateAndTimeMs",type="LogFlushStats"
正常的值:

描述:正在做复制的partition的数量(|ISR| < |all replicas|)
Mbean名:"kafka.server":name="UnderReplicatedPartitions",type="ReplicaManager"
正常的值:0

描述:当前的broker是否为controller
Mbean名:"kafka.controller":name="ActiveControllerCount",type="KafkaController"
正常的值:在集群中只有一个broker的这个值为1

描述:选举leader的速率
Mbean名:"kafka.controller":name="LeaderElectionRateAndTimeMs",type="ControllerStats"
正常的值:如果有broker挂了,此值非0

描述:Unclean的leader选举速率
Mbean名:"kafka.controller":name="UncleanLeaderElectionsPerSec",type="ControllerStats"
正常的值:0

描述:该broker上的partition的数量
Mbean名: "kafka.server":name="PartitionCount",type="ReplicaManager"
正常的值:应在各个broker中平均分布

描述:Leader的replica的数量
Mbean名: "kafka.server":name="LeaderCount",type="ReplicaManager"
正常的值:应在各个broker中平均分布

描述:ISR的收缩(shrink)速率
Mbean名:"kafka.server":name="ISRShrinksPerSec",type="ReplicaManager"
正常的值:如果一个broker挂掉了,一些partition的ISR会收缩。当那个broker重新起来时,一旦它的replica完全跟上,ISR会扩大(expand)。除此之外,正常情况下,此值和下面的扩大速率都是0。

描述:ISR的扩大(expansion)速率
Mbean名: "kafka.server":name="ISRExpandsPerSec",type="ReplicaManager"
正常的值:参见ISR的收缩(shrink)速率

描述:follower落后leader replica的最大的消息数量
Mbean名:"kafka.server":name="([-.\w]+)-MaxLag",type="ReplicaFetcherManager"
正常的值:小于replica.lag.max.messages

描述:每个follower replica落后的消息速率
Mbean名:"kafka.server":name="([-.\w]+)-ConsumerLag",type="FetcherLagMetrics"
正常的值:小于replica.lag.max.messages

描述:等待producer purgatory的请求数
Mbean名:"kafka.server":name="PurgatorySize",type="ProducerRequestPurgatory"
正常的值:如果ack=-1,应为非0值

描述:等待fetch purgatory的请求数
Mbean名:"kafka.server":name="PurgatorySize",type="FetchRequestPurgatory"
正常的值:依赖于consumer的fetch.wait.max.ms的设置

描述:一个请求(producer,Fetch-Consumer,Fetch-Follower)耗费的所有时间
Mbean名:"kafka.network":name="{Produce|Fetch-Consumer|Fetch-Follower}-TotalTimeMs",type="RequestMetrics"
正常的值:包括了queue, local, remote和response send time

描述:请求(producer,Fetch-Consumer,Fetch-Follower)在请求队列中的等待时间
Mbean名:"kafka.network":name="{Produce|Fetch-Consumer|Fetch-Follower}-QueueTimeMs",type="RequestMetrics"
正常的值:

描述:请求(producer,Fetch-Consumer,Fetch-Follower)在leader处理请求花的时间
Mbean名:"kafka.network":name="{Produce|Fetch-Consumer|Fetch-Follower}-LocalTimeMs",type="RequestMetrics"
正常的值:

描述:请求(producer,Fetch-Consumer,Fetch-Follower)等待follower花费的时间
Mbean名:"kafka.network":name="{Produce|Fetch-Consumer|Fetch-Follower}-RemoteTimeMs",type="RequestMetrics"
正常的值:producer的ack=-1时,非0才正常

描述:发送响应花费的时间
Mbean名:"kafka.network":name="{Produce|Fetch-Consumer|Fetch-Follower}-ResponseSendTimeMs",type="RequestMetrics"
正常的值:

描述:consumer落后producer的消息数量
Mbean名:"kafka.consumer":name="([-.\w]+)-MaxLag",type="ConsumerFetcherManager"
正常的值:

建议对GC耗时和其他参数和诸如系统CPU,I/O时间等等进行监控。在client端,建议对"消息数量/字节数"的速率(全局的和对于每一个topic),请求的"速率/大小/耗时"进行监控。还有consumer端,所有partition的最大的落后情况和最小的fetch请求的速率。consumer为了能跟上,最大落后数量需要少于一个threshold并且最小fetch速率需要大于0.

kafka客户端汇总

pip install kafka-python
kafka官方文档

帮助文档

分布式消息中间件应用实践
http://www.ibm.com/developerworks/cn/opensource/os-cn-kafka-distributed/
Apache kafka 工作原理介绍
http://www.ibm.com/developerworks/cn/opensource/os-cn-kafka/index.html

kafka 快速入门手册
http://www.blogjava.net/paulwong/archive/2014/05/11/413506.html

kafka 集群安装与扩容
http://my.oschina.net/MaTech/blog/292090
http://my.oschina.net/MaTech/blog/292090

kafka入门:简介、使用场景、设计原理、主要配置及集群搭建(转)

kafka 性能测试
https://engineering.linkedin.com/kafka/benchmarking-apache-kafka-2-million-writes-second-three-cheap-machines

青云kafka集群介绍

https://docs.qingcloud.com/guide/queue.html#id7

kafka python+zabbix 监控脚本
http://club.oneapm.com/t/zabbix-kafka/854

实验环境介绍

实验环境规划

主机名 ip address 操作系统 职责
linux-node1 192.168.56.11 centos7 tomcat
1
2
3
4
5
6
[root@linux ~]# uname -r
3.10.0-229.el7.x86_64
[root@linux ~]# uname -m
x86_64
[root@linux ~]# cat /etc/redhat-release
CentOS Linux release 7.1.1503 (Core)

tomcat安装过程

先安装java环境

1
2
3
4
5
6
7
8
9
10
11
12
13
cd /usr/local/src/
tar -zxf jdk-8u65-linux-x64.tar.gz ##从官网下载好java包,上传到这个目录中。
mv jdk1.8.0_65/ /data/app/
ln -s /data/app/jdk1.8.0_65/ /data/app/java
vim /etc/profile
###setup java by biglittleant at 2015-08-28
export JAVA_HOME=/data/app/java
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=.$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar

source /etc/profile##生效一下java版本

安装tomcat

1
2
3
4
5
6
7
8
9
10
11
cd /usr/local/src/
tar -zxf apache-tomcat-8.0.32.tar.gz
mv apache-tomcat-8.0.32 /data/app/
ln -s /data/app/apache-tomcat-8.0.32/ /data/app/tomcat
useradd -d /data/app/tomcat -u 505 tomcat
passwd tomcat
chown -R tomcat.tomcat /data/app/apache-tomcat-8.0.32/
cd /data/app/tomcat/
chmod 744 -R bin/*.sh
su - tomcat
pwd

手动编写tomcat启停脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/bin/bash

TOMCAT_PATH=/data/app/tomcat

usage(){
echo "Usage: $0 [start|stop|status|restart]"
}

status_tomcat(){

ps aux | grep java | grep tomcat | grep -v 'grep'

}

start_tomcat(){
/data/app/tomcat/bin/startup.sh
}

stop_tomcat(){

TPID=$(ps aux | grep java | grep tomcat | grep -v 'grep' | awk '{print $2}')
kill -9 $TPID
sleep 5;

TSTAT=$(ps aux | grep java | grep tomcat | grep -v 'grep' | awk '{print $2}')
if [ -z $TSTAT ];then
echo "tomcat stop"
else
kill -9 $TSTAT
fi

cd $TOMCAT_PATH

rm temp/* -rf
rm work/* -rf

}

main(){
case $1 in

start)
start_tomcat;;
stop)
stop_tomcat;;
status)
status_tomcat;;
restart)
stop_tomcat && start_tomcat;;
*)
usage;
esac

}

main $1

配置server.xml

tomcat的server.xml 配置文件一共分为五大部分:

  • server块
  • service块
  • connector块
  • engine块
  • host块
    下图是我看到比较好的一张解释server.xml配置文件的图片
    tomcat-配置文件结构图

看懂配置文件以后我们来说明一下tomcat处理客户端请求的过程

  1. 客户端访问URL:http://www.biglittleant.cn:8080/java/index.jsp 服务器端收到请求以后,开始解析URL。
  2. connector块创建8080端口,并接收客户端的请求地址,交给engine块。
  3. engine会根据域名(www.biglittleant.cn)读取相应的host块。
  4. host块接到请求后,分析URI(/java/index.jsp)匹配相应的context块。然后跳转到context段docBase的目录,并读取index.jsp,返回给engine。
  5. engine返回给connector。
  6. connector把结果返回给客户端。

server.xml 配置文件详细解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?xml version='1.0' encoding='utf-8'?>

<Server port="8888" shutdown="shutdowntomcat">
###开启8888管理端口,设定shutdowntomcat可以关闭tomcat服务器。
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JasperListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />


<GlobalNamingResources>

<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>

<Service name="Catalina">
##定义一个service

<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
##开启一个8080端口,来接收客户端请求,使用HTTP/1.1协议,超时时间为20000,redirectPort 是SSL的端口,也可以不配置。
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
##同上。协议改变了。

<Engine name="Catalina" defaultHost="localhost">
#defaultHost 定义默认host主机,当所有host都匹配不了URL时,将转发给localhost虚拟主机。
<Realm className="org.apache.catalina.realm.LockOutRealm">

<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>

<Host name="localhost" appBase="webapps"
unpackWARs="false" autoDeploy="false">
##定义web应用程序的目录,关闭自动解压wAR包,关闭自动部署。生产上一定要关闭。否则会发生意想不到的问题。
<Context path="/java" docBase="/data/app/tomcat/webapps/java" debug="0" reloadable="false" crossContext="true"/>
##定义一个路径,当URI中包含/java时去,docbase路径下来查找相应的配置文件,reloadable=True时,会监控docbase路径下的文件是否发生改变,发生改变就自动重载配置,生产上这个也要禁止掉。

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t %r %s %b %{Referer}i %{User-Agent}i %D" resolveHosts="false" />
##定义本host的accesslog的文件名(localhost_access_log),后缀名(.txt)pattern 定义如何记录accesslog。

</Host>
</Engine>
</Service>
</Server>

配置web.xml

1
2
3
4
5
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
#关闭服务器的list目录的文件内容。

删除tomcat的默认文件

1
2
3
4
rm -f tomcat/webapps/*
rm -f tomcat/conf/tomcat-user.xml
去除其他用户对tomcat 起停脚本的执行权限
chmod 744 /bin/*.sh

调优JAVA,开启JVM监控

1
2
3
4
5
6
7
8
9
10
vim tomcat/bin/catalina.sh
JAVA_OPTS="-XX:PermSize=64M -XX:MaxPermSize=128m -Xms512m -Xmx512m -Duser.timezone=Asia/Shanghai"
## 配置服务器的jvm参数调优
export CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote"
export CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.port=10053"
export CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.authenticate=false"
export CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.ssl=false"
export CATALINA_OPTS="$CATALINA_OPTS -Djava.rmi.server.hostname=192.168.56.11"
##开启服务器的远程JVM端口
236 # ----- Execute The Requested Command -----------------------------------------

剩下的问题就是开启zabbix监控tomcat的JVM。

补充如何查看tomcat的jmx选项

JMX 有很多 Key 可以监控,具体的值,可以通过 jconsole 参看。如下图所示,如果要监控线程数,就可以写成 jmx[“java.lang:type=Threading”, “ThreadCount”]

Tomcat 日志配置

pattern属性值由字符串常量和pattern标识符加上前缀”%”组合而成。pattern标识符加上前缀”%”,用来代替当前请求/响应中的对应的变量值。目前支持如下的pattern:

  • %a - 远端IP地址
  • %A - 本地IP地址
  • %b - 发送的字节数,不包括HTTP头,如果为0,使用”-”
  • %B - 发送的字节数,不包括HTTP头
  • %h - 远端主机名(如果resolveHost=false,远端的IP地址)
  • %H - 请求协议
  • %l - 从identd返回的远端逻辑用户名(总是返回 ‘-‘)
  • %m - 请求的方法(GET,POST,等)
  • %p - 收到请求的本地端口号
  • %q - 查询字符串(如果存在,以 ‘?’开始)
  • %r - 请求的第一行,包含了请求的方法和URI
  • %s - 响应的状态码
  • %S - 用户的session ID
  • %t - 日志和时间,使用通常的Log格式
  • %u - 认证以后的远端用户(如果存在的话,否则为’-‘)
  • %U - 请求的URI路径
  • %v - 本地服务器的名称
  • %D - 处理请求的时间,以毫秒为单位
  • %T - 处理请求的时间,以秒为单位
    另外,Access Log中也支持cookie,请求header,响应headers,Session或者其他在ServletRequest中的对象的信息。格式遵循apache语法:
1
2
3
4
5
%{xxx}i 请求headers的信息
%{xxx}o 响应headers的信息
%{xxx}c 请求cookie的信息
%{xxx}r xxx是ServletRequest的一个属性
%{xxx}s xxx是HttpSession的一个属性

common模式的pattern(即默认pattern参数)的格式为’%h %l %u %t “%r” %s %b’。

combined模式的pattern可以增加Referer和User-Agent headers的参数形式,每个参数用双引号包起来,引号中的内容还是上面列举的参数。比如”%{User-Agent}i”使其为”%{User-Agent}i“,即请求的User-Agent(客户端,浏览器)。

关于Common Log Format参考:http://baike.baidu.com/view/2948003.htm

配置普通日志格式

1
2
3
4
<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs" prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t %r %s %b %{Referer}i %{User-Agent}i %D"
resolveHosts="false" />

日志访问实例

1
2
192.168.0.113 - - [11/Jul/2016:16:25:53 +0800] PUT /fontgenerate/ HTTP/1.1 200 37 - Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like G
ecko) Chrome/45.0.2454.101 Safari/537.36 1

将日志格式输出为json格式

1
2
3
4
5
<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs"
prefix="localhost_access_log." suffix=".json"
pattern="{&quot;client&quot;:&quot;%h&quot;, &quot;client user&quot;:&quot;%l&quot;, &quot;authenticated&quot;:&quot;%u&quot;, &quot;access time&quot;:&quot;%t&quot;, &quot;method&quot;:&quot;%r&quot;, &quot;status&quot;:&quot;%s&quot;, &quot;send bytes&quot;:&quot;%b&quot; , &quot;partner&quot;:&quot;%{Referer}i&quot;, &quot;Agent version&quot;:&quot;%{User-Agent}i&quot;, &quot;request_duration&quot;:&quot;%D&quot;}"
resolveHosts="false" />

访问日志结果:

1
2
3
4
5
6
7
8
9
10
11
12
{
"client": "192.168.0.13",
"client user": "-",
"authenticated": "-",
"access time": "[14/Sep/2016:11:03:33 +0800]",
"method": "GET /fontgenerate/?text=hello%E5%88%9D%E9%A1%B5&fontName=HYHLZTJ.ttf&type=Woff HTTP/1.1",
"status": "200",
"send bytes": "11572",
"partner": "http://192.168.0.226:8080/",
"Agent version": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.101 Safari/537.36",
"request_duration": "42"
}

sort

sort将文件的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。
英文翻译:sort lines of text files .

格式:

1
2
Usage: sort [OPTION]... [FILE]...
or: sort [OPTION]... --files0-from=F

参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Ordering options:

-b, --ignore-leading-blanks 忽略每行前面开始出的空格字符。
-d, --dictionary-order 排序时,处理英文字母、数字及空格字符外,忽略其他的字符。
-f, --ignore-case 排序时,将小写字母视为大写字母。
-g, --general-numeric-sort 按照常规数值排序
-i, --ignore-nonprinting 排序时,除了040至176之间的ASCII字符外,忽略其他的字符。
-M, --month-sort 将前面3个字母依照月份的缩写进行排序。
-h, --human-numeric-sort 使用人类可读的数字(例如: 2K 1G)
-n, --numeric-sort 依照数值的大小排序。
-R, --random-sort 根据随机hash 排序
--random-source=FILE 从指定文件中获得随机字节
-r, --reverse 以相反的顺序来排序。
--sort=WORD 按照WORD 指定的格式排序:
一般数字-g,高可读性-h,月份-M,数字-n,
随机-R,版本-V
-V, --version-sort 按照常规数值排序

Other options:

--batch-size=NMERGE 一次最多合并NMERGE 个输入;如果输入更多则使用临时文件
-c, --check, --check=diagnose-first 检查输入是否已排序,若已有序则不进行操作
-C, --check=quiet, --check=silent 类似-c,但不报告第一个无序行
--compress-program=PROG 使用指定程序压缩临时文件;使用该程序
的-d 参数解压缩文件
--debug 为用于排序的行添加注释,并将有可能有问题的
用法输出到标准错误输出
--files0-from=F 从指定文件读取以NUL 终止的名称,如果该文件被
指定为"-"则从标准输入读文件名
-k, --key=KEYDEF 在位置1 开始一个key,在位置2 终止(默认为行尾)
-m, --merge 合并已排序的文件,不再进行排序
-o, --output=FILE 将排序后的结果存入指定的文件。
-s, --stable 禁用last-resort 比较以稳定比较算法
-S, --buffer-size=SIZE 禁用last-resort 比较以稳定比较算法
-t, --field-separator=SEP 使用SEP作为排序时所用的分隔符。
-T, --temporary-directory=DIR 使用指定目录而非$TMPDIR 或/tmp 作为
临时目录,可用多个选项指定多个目录
--parallel=N 将同时运行的排序数改变为N
-u, --unique 配合-c,严格校验排序;不配合-c,则只输出一次排序结果
-z, --zero-terminated 以0 字节而非新行作为行尾标志
--help 显示帮助
--version 显示版本信息

常用参数:

1
2
3
4
5
6
-n 按照数字排序。
-r 倒叙。
-t “.”表示按点号分隔域(awk -F ,cut -d 取段 -f)。
-u 相同的行,只输出一行。
-k 指定列进行排序。
-b会忽略每一行前面的所有空白部分,从第一个可见字符开始比较。

示例

按照数字正序排序

1
2
3
4
5
6
7
8
[root@MySQL /]# sort -n file1.txt
10.0.0.7 f
10.0.0.7 n
10.0.0.8 c
10.0.0.8 k
10.0.0.8 z
10.0.0.9 a
10.0.0.9 o

按照数字排序并倒叙

1
2
3
4
5
6
7
8
[root@MySQL /]# sort -nr file1.txt
10.0.0.9 o
10.0.0.9 a
10.0.0.8 z
10.0.0.8 k
10.0.0.8 c
10.0.0.7 n
10.0.0.7 f

按照第二列开始倒叙排序

-t 指定分隔符,-k2 表示按照第二列开始倒叙排序

1
2
3
4
5
6
7
8
[root@MySQL /]# sort -t" " -k2  -r file1.txt
10.0.0.8 z
10.0.0.9 o
10.0.0.7 n
10.0.0.8 k
10.0.0.7 f
10.0.0.8 c
10.0.0.9 a

-t 指定分隔符,-k2 表示按照第二列开始排序

1
2
3
4
5
6
7
8
[root@MySQL /]# sort -t" " -k2  file1.txt
10.0.0.9 a
10.0.0.8 c
10.0.0.7 f
10.0.0.8 k
10.0.0.7 n
10.0.0.9 o
10.0.0.8 z

先按照第一行排序,在按第三列排序并忽略空格 。

1
2
3
4
5
6
7
#sort -k 1 -n -k 3 emp.data
Beth 4.00 0
Dan 3.75 0
kathy 4.00 10
Susie 4.25 18
Mark 5.00 20
Mary 5.50 22

-n 参数讲解

测试文件内容

1
2
3
4
5
# cat info.txt
aa,201
zz,502
bb,1
ee,42

第一步 按照第二列倒叙排序

1
2
3
4
5
[root@linux-node2 111:33:23]#sort -t ',' -k2r info.txt
zz,502
ee,42
aa,201
bb,1

通过结果查看: 是按照第二列的首字母进行排序。

1
2
3
4
5
[root@linux-node2 111:33:30]#sort -t ',' -k2nr info.txt
zz,502
aa,201
ee,42
bb,1

加上 -n 后将第二列当成了一个整体进行排序。

结论:

  • 如果不加-n 默认按照列的第一个首字母排序,如果首字母相同排序第二列。
  • 如果加了-n,sort 会将值当成一个整数,而不是按照首字母排序。