nginx
nginx 是俄罗斯人写的轻量级 http 服务器, Nginx 以事件驱动的方式编写, 有非常好的性能, 同时也是一个非常高效的反向代理, 负载均衡.
nginx 稳定性高, 模块库丰富, 配置灵活, 系统资源的消耗低. 响应静态页面的速度非常快
nginx 做什么
处理静态文件
反向代理, 负载均衡和容错
大并发
易配置, 易拓展
nginx 处理过程
nginx 是异步非阻塞的方式处理请求. 采用 epoll 事件循环, 多个独立 worker 处理请求, 并不是并发, 避免加锁和上下文切换带来的性能问题.
深入浅出 nodejs 对事件循环讲的很详细
具体请求的处理
解析配置文件, 得到需要监听的端口与 ip 地址, 然后在 Nginx 的 master 进程里面, 先初始化好这个监控的 socket
然后再 fork 出多个子进程出来, 然后子进程会竞争 accept 新的连接
与客户端三次握手得到这个建立好的连接的 socket, 然后创建 Nginx 对连接的封装
Nginx 或客户端来主动关掉连接
nginx 的配置
分为几个模块:
main: Nginx 在运行时与具体业务功能 (比如 http 服务或者 email 服务代理) 无关的一些参数, 比如工作进程数, 运行的身份等.
http: 与提供 http 服务相关的一些配置参数. 例如: 是否使用 keepalive 啊, 是否使用 gzip 进行压缩等.
server: http 服务上支持若干虚拟主机. 每个虚拟主机一个对应的 server 配置项, 配置项里面包含该虚拟主机相关的配置.
location: http 服务中, 某些特定的 URL 对应的一系列配置项.
默认情况下, 这个配置文件通常命名为 nginx.conf 并且会放置在{$108
- }local/nginx/conf,{$110
}nginx, 或者{$111
}local/etc/nginx
示例配置
- user nobody;
- worker_processes 1;
- error_log logs/error.log info;
- events {
- worker_connections 1024;
- }
- http {
- include mime.types;
- default_type application/octet-stream;
- sendfile on;
- keepalive_timeout 65;
- gzip on;
- server {
- listen 80;
- server_name localhost;
- access_log /var/log/nginx/host.access.log main;
- location / {
- index index.html;
- root /usr/local/openresty/nginx/html;
- }
- }
- }
nginx 的使用
开启 nginx
- nginx -c /usr/local/nginx/conf/nginx.conf
- nginx -s signal
signal 可以为下列命令之一:
stop - 直接关闭 nginx
quit - 会在处理完当前正在的请求后退出, 也叫优雅关闭
reload - 重新加载配置文件, 相当于重启 // 滚动升级
reopen - 重新打开日志文件
nginx with lua -- openresty
OpenResty® 是一个基于 Nginx 与 Lua 的高性能 web 平台, 其内部集成了大量精良的 Lua 库, 第三方模块以及大多数的依赖项. 用于方便地搭建能够处理超高并发, 扩展性极高的动态 Web 应用, Web 服务和动态网关.
通过融合 lua, 可以将 Nginx 有效地变成一个强大的 web 服务器!
环境搭建
直接搭建 openresty, nginx + lua 套餐
brew tap homebrew/nginx
- brew install homebrew/nginx/openresty
如果一切顺利, OpenResty 应该已经安装好了.
为了方便, 这边直接用 docker 装 OpenResty:
新建一个 Dockerfile, 写入:
- #Dockerfile
- FROM openresty/openresty:trusty
RUN apt-get update && apt-get install -y vim
可以直接装 openresty 的, 但是容器的 bash 里面没有 vim, 在里面改代码很麻烦, 所以就自己构建了一个 image.
然后构建 image
docker build -t openresty .
docker container run -itd -p 80:80 --name test openresty
构建一个名叫 test 的容器.
ok, 现在已经跑起来了, 访问 http://localhost, 已经出现了 openresty 的欢迎页
运行
docker ps -a
可以看到:
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- 06c9a63607eb openresty "/usr/local/openrest..." 4 hours ago Up 25 minutes 0.0.0.0:80->80/tcp test
已经起了服务.
我们进入容器看一下:
docker exec -it test bash
里面就是 nginx 的目录.
我们的配置在
- vi usr/local/openresty/nginx/conf/nginx.conf
- #user nobody;
- worker_processes 1;
- #error_log logs/error.log;
- #error_log logs/error.log notice;
- #error_log logs/error.log info;
- #pid logs/nginx.pid;
- events {
- worker_connections 1024;
- }
- 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 0;
- keepalive_timeout 65;
- #gzip on;
- include /etc/nginx/conf.d/*.conf;
- }
其中引入了 / etc/nginx/conf.d/*.conf; 我们的 server 配置就在这里.
现在该这个 etc/nginx/conf.d/default.conf 就可以愉快的进行 nginx 配置了.
- location / {
- root /usr/local/openresty/nginx/html;
- index index.html index.htm;
- }
我们访问的主页目录就在 root 指定下的 index.html,root 是指定根目录, index 指定默认访问文件.
访问控制
通过 lua 来控制是否进入处理逻辑. 其中 ngx.var.remote_addr,ngx.HTTP_FORBIDDEN 都是 nginx 的内置变量
- access_by_lua_block {
- local black_ips = {["127.0.0.1"]=true}
- local ip = ngx.var.remote_addr
- if true == black_ips[ip] then
- ngx.exit(ngx.HTTP_FORBIDDEN)
- end
- };
- // 业务逻辑处理...
nginx 反向代理和负载均衡
nginx 反向代理和负载均衡配置比较简单.
反向代理
- # 1. 用户访问 http://ip:port, 则反向代理到 https://github.com
- location / {
- proxy_pass https://github.com;
- 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;
- }
proxy_pass: proxy_pass 后面跟着一个 URL, 用来将请求反向代理到 URL 参数指定的服务器上. 例如我们上面例子中的 proxy_pass https://github.com, 则将匹配的请求反向代理到 https://github.xn--com,-355f43owes8fcvbpte41e7q8brdvc5hk37fo3r/ https://github.com/
proxy_set_header 反向代理不会转发原始请求中的 Host 头部, 如果需要转发, 就需要加上这句: proxy_set_header Host $host;
负载均衡
nginx 通过 upstream 字段配置负载均衡.
- upstream test.net{
- ip_hash;
- server 192.168.10.13:80;
- server 192.168.10.14:80 down;
- server 192.168.10.15:8009 max_fails=3 fail_timeout=20s;
- server 192.168.10.16:8080;
- }
- server {
- location / {
- proxy_pass http://test.net;
- }
- }
upstream 是 Nginx 的 HTTP Upstream 模块, 这个模块通过一个简单的调度算法来实现客户端 IP 到后端服务器的负载均衡.
Nginx 的负载均衡模块目前支持 6 种调度算法:
轮询(默认)
- ip_hash
- fair
- url_hash
- least_conn
- hash
例子用的是 ip_hash, 是每个请求按访问 IP 的 hash 结果分配.
采用轮询时可以指定每个 server 的权重:
- upstream webservers {
- server 192.168.18.201 weight=1 max_fails=2 fail_timeout=2;
- server 192.168.18.202 weight=1 max_fails=2 fail_timeout=2;
- }
利用 max_fails,fail_timeout 参数, 控制异常情况.
max_fails: 允许请求失败的次数, 默认为 1 . 当超过最大次数时, 返回 proxy_next_upstream 模块定义的错误.
fail_timeout: 在经历了 max_fails 次失败后, 暂停服务的时间. max_fails 可以和 fail_timeout 一起使用.
还有:
down: 表示当前的 server 暂时不参与负载均衡.
backup: 预留的备份机器. 当其他所有的非 backup 机器出现故障或者忙的时候, 才会请求 backup 机器, 因此这台机器的压力最轻.
backup 一般用做降级处理.
日志 log
nginx 的日志路径是写在 nginx.conf 里的.
看一下 usr/local/openresty/nginx/conf/nginx.conf, 把里面注释的这几行放出来.
- error_log logs/error.log;
- 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;
error_log 是错误日志, access_log 是访问日志, log_format 是根据格式 format 日志消息.
日志文件在: usr{$131
- }openresty/nginx/logs/access.log
可以看到访问的 log.error.log 是错误信息.
172.17.0.1 - - [24/Apr/2018:09:46:24 +0000] "GET /res HTTP/1.1" 200 68 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36" "-"
172.17.0.1 - - [24/Apr/2018:09:46:34 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36" "-"
如果看不到, 先删除已存在的 access.log 和 error.log 再重启 nginx 应该就可以了.
退出容器 docker logs test 也可以看到 nginx 的 log.
如何结合 lua
lua 提供了好多个指令, 比如:
content_by_lua_file 直接加 lua file 的路径, 接受请求, 并处理响应.
lua_package_path 设置用 lua 代码写的扩展库路径, lua 文件里 require 要用到.
set_by_lua_file $var <path-to-lua-script-file> [$arg1 $arg2 ...]; 设置一个 Nginx 变量, 变量值从 lua 脚本里运算由 return 返回, 可以实现复杂的赋值逻辑; 此处是阻塞的, Lua 代码要做到非常快.
rewrite_by_lua_file lua 文件的重定向操作
access_by_lua_file lua 文件的访问控制
header_filter_by_lua_file 设置 header 和 cookie
init_by_lua_file ginx Master 进程加载配置时执行; 通常用于初始化全局配置 / 预加载 Lua 模块
...
lua 常用的方法和常量:
- ngx.arg[index] #ngx 指令参数, 当这个变量在 set_by_lua 或者 set_by_lua_file 内使用的时候是只读的, 指的是在配置指令输入的参数
- ngx.var.varname #读写 NGINX 变量的值, 最好在 lua 脚本里缓存变量值, 避免在当前请求的生命周期内内存的泄漏
- ngx.config.ngx_lua_version #当前 ngx_lua 模块版本号
- ngx.config.nginx_version #nginx 版本
- ngx.worker.pid #当前 worker 进程的 PID
- ...
- print() #与 ngx.print()方法有区别, print() 相当于 ngx.log()
- ngx.ctx #这是一个 lua 的 table, 用于保存 ngx 上下文的变量, 在整个请求的生命周期内都有效, 详细参考官方
- ngx.location.capture() #发出一个子请求
- ngx.location.capture_multi() #发出多个子请求
- ngx.status #读或者写当前请求的相应状态. 必须在输出相应头之前被调用
- ngx.header.HEADER #访问或设置 http header 头信息
- ngx.req.set_uri() #设置当前请求的 URI
- ngx.set_uri_args(args) #根据 args 参数重新定义当前请求的 URI 参数
- ngx.req.get_uri_args() #返回一个 lua table, 包含当前请求的全部的 URL 参数
- ngx.req.get_post_args() #返回一个 LUA TABLE, 包括所有当前请求的 POST 参数
- ngx.req.get_headers() #返回一个包含当前请求头信息的 lua table
- ngx.req.set_header() #设置当前请求头 header 某字段值. 当前请求的子请求不会受到影响
- ngx.req.read_body() #在不阻塞 ngnix 其他事件的情况下同步读取客户端的 body 信息
- ngx.time() #返回当前时间戳
- ngx.re.match(subject,regex,options,ctx) #ngx 正则表达式匹配
- ...
开始 openresty
在 etc/nginx/conf.d/default.conf 的 location 下面加一句
- location /test {
- default_type text/html;
- content_by_lua_block {
- ngx.say("HelloWorld")
- }
- }
重启 nginx nginx -s reload
访问 http://localhost/test 可以看到我们写的 helloworld.
再比如
- location = /sum {
- # 只允许内部调用
- internal;
- # 这里做了一个求和运算只是一个例子, 可以在这里完成一些数据库,
- # 缓存服务器的操作, 达到基础模块和业务逻辑分离目的
- content_by_lua_block {
- local args = ngx.req.get_uri_args()
- ngx.say(tonumber(args.a) + tonumber(args.b))
- }
- }
- location = /app/test {
- content_by_lua_block {
- local res = ngx.location.capture(
- "/sum", {args={a=3, b=8}}
- )
- ngx.say("status:", res.status, "response:", res.body)
- }
- }
请求内部接口, 返回结果.
比如:
lua 可以帮我们做跳转. 重写 url 等等.
- location = /stream {
- rewrite_by_lua_block {
- return ngx.redirect('http://open.toutiao.com');
- }
- }
比如
- location /print_param {
- default_type text/html;
- content_by_lua_block {
- local arg = ngx.req.get_uri_args()
- for k,v in pairs(arg) do
ngx.say("[GET ] key:", k, "v:", v)
- end
- ngx.req.read_body() #解析 body 参数之前一定要先读取 body
- local arg = ngx.req.get_post_args()
- for k,v in pairs(arg) do
ngx.say("[POST] key:", k, "v:", v)
- end
- }
- }
访问 http://localhost/print_param?a=1&b=2&c=3
可以看到[GET ] key:b v:2 [GET ] key:a v:1 [GET ] key:c v:3
- location /res {
- default_type text/html;
- local table = {
- "hello,",
- {"world:", true, "or", false,
- {":", nil}}
- }
- ngx.print(table)
- }
ngx.print 吐出碎片化字符串. hello, world: true or false:
- location /res {
- content_by_lua_block {
- ngx.header['Content-Type'] = 'text/json; charset=UTF-8'
- local str = '{"a":1,"b":"ss","c":{"c1":1,"c2":2},"d":[10,11],"1":100}'
- ngx.say(str)
- }
- }
返回 json
nginx
读到这里大致就应该会搞几个小 demo 了, 好多 api 没有介绍, 看一下文档就会用了. 大概后续会补充一些 nginx 安全, 负载均衡算法, 等等一些高级应用.
来源: https://www.cnblogs.com/dh-dh/p/8931654.html