Category Archives: Server

Nginx porxy的一些是使用总结

一、前言

Nginx的Proxy功能很强大,可以做负载均衡,可以做反向代理,可以做页面缓存等等等功能。今天就来详细说一下在我使用的一些经验之谈。也许里面就有所需要的。

Nginx的porxy默认就是自带的,无须任何第三方的模块就可以直接使用,至于nginx的配置以及安装,直接google查一下,相关的文章非常之多。

二、反向代理

1. proxy_pass

指定nginx需要代理谁。语法: proxy_pass URL。作用域在location。首先我们可以来尝试一下,将设你已经有了一个网站 http://a.com,但是我需要http://b.com访问相同的a.com。这个时候你可以这样,

server {
   listen 80;
   server_name b.com;
   index index.html;
   location / {
     proxy_pass http://a.com;
   }
}

现在你访问的b.com就是nginx把a.com反向代理回来的结果。这个时候,你需要问,我程序在a.com需要获得用户的IP地址这些,全是b.com的地址呀。那这个时候就需要下面一个命令了。

2. proxy_set_header

发送给原服务器的时候增加或者修改请求头的信息。 语法: proxy_set_header key value。作用域在location。

proxy_set_header        Host            $host;
proxy_set_header        X-Real-IP       $remote_addr;
proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;

把这3行加入到刚刚前面的配置下面,这时候a.com就能获得真实的用户IP地址了。

三、负载均衡

网站发展初期可能只有一台服务器,但是随着你的业务增长,你的服务器越来越多。这时候,你需要服务器能智能的分配用户到不同的服务器上。现有方式的有两种,第一种通过DNS,第二种就是我们接下来要说的nginx负载均衡了。

1. upstream
Nginx的upstream通proxy一样,默认就自带了。通过upstream他将会智能的分配后台的服务器。首先我们看一下配置

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;
}

server {
 listen 80;
 server_name a.com;
 index index.html;
 location / {
  proxy_pass http://backend; #这里填的就是upstream。必须要有http头
  proxy_set_header        Host            $host;
  proxy_set_header        X-Real-IP       $remote_addr;
  proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
 }
}

现在你访问a.com,nginx将会自动向你分配一台服务器为你服务。可能上面这些配置你看得一头雾水,下面将会一一进行详解。
如何添加负载均衡呢? 通过upstream创建, 语法 upstream NAME。

1.1 server
语法: server address [parameters]
将指定的服务器绑定在负载均衡上,可以使用网址、ip地址或者unix://地址。如果没有特别指定端口号的话,默认就为80。就如上面backup1.example.com:8080 和 backend1.example.com。 一个是指向8080端口,一个是指向80端口。

parameters参数详解:
1.1.1 weight=number
服务器权重,默认为1。值越大被访问的几率也就越大。

1.1.2 max_fails=number
允许最大请求失败数,默认为1。当超过最大次数时,将会在proxy_next_upstream设定的错误。

1.1.3 fail_timeout=time
在经历了max_fails次失败后,暂停服务的时间。max_fails可以和fail_timeout一起使用。

1.1.4 backup
预留的备份机器。当其他所有的非backup机器出现故障或者忙的时候,才会请求backup机器,因此这台机器的压力最轻。

1.1.5 down
表示当前的server暂时不参与负载均衡。只作用于ip_hash轮询。

另外一些非常用的参数:
1.1.6 max_conns=number
设定单台服务器的最大连接数(从1.5.9版本生效)。默认为0, 0表示无连接数限制。

1.1.7 resolve
监控域名地址的变化,将会自动更改解析地址,不需要重启服务器。(从1.5.12版本生效)。特别要说明的是,如果要设定resolve,必须要设定在http作用域范围内。

http {
    resolver 10.0.0.1;
    upstream u {
        zone ...;
        ...
        server example.com resolve;
    }
}

1.1.8 route=string
设定服务器的路由名称

1.1.9 slow_start=time
设定一个恢复正常的服务器从0到设定的weight值所需的时间。主要是针对 服务器挂了重启,访问过慢等等情况。默认为0,禁用。

1.2 负载均衡的分配方式
nginx有多种负载均衡的方式。在默认情况下是使用轮询的方式,即通过weight值来轮询访问。第二种为ip_hash,每个请求按访问IP的hash结果分配,这样来自同一个IP的访客固定访问一个后端服务器,有效解决了动态网页存在的session共享问题。

其余还有health_check, keepalive等等。详细可以预约官方文档: http://nginx.org/en/docs/http/ngx_http_upstream_module.html#health_check

四、页面缓存

说完负载均衡,现在来说说页面缓存。Nginx的页面缓存功能与专业的(squid)web缓存器来比一点也不差。当然配置起来也有点小麻烦。Nginx的proxy默认请下是没有任何配置的所以,你首先要指定缓存规则,缓存的路径等等信息。
1. proxy_cache_path PATH [levels=levels] keys_zone=name:size [inactive=time] [max_size=size] [loader_files=number] [loader_sleep=time] [loader_threshold=time];

PATH缓存的目录,levels参数指定缓存的子目录数。levels指定目录结构,可以使用任意的1位或2位数字作为目录结构,如 X, X:X,或X:X:X 例如: “2”, “2:2”, “1:1:2“,但是最多只能是三级目录。 所有活动的key和元数据存储在共享的内存池中,这个区域用keys_zone参数指定。name指的是共享池的名称,size指的是共享池的大小,可以使用k,m,g。如果在inactive参数指定的时间内缓存的数据没有被请求则被删除,默认inactive为10分钟。一个名为cache manager的进程控制磁盘的缓存大小,它被用来删除不活动的缓存和控制缓存大小,这些都在max_size参数中定义,当目前缓存的值超出max_size指定的值之后,超过其大小后最少使用数据(LRU替换算法)将被删除。

2. proxy_cache_key
设定了缓存路径,接下来就需要缓存key,避免数据重复。默认为$scheme$proxy_host$request_uri 。 你可以添加nginx下的各种参数来组合不同的key值

3. proxy_cache
设置一个缓存区域的名称,一个相同的区域可以在不同的地方使用。
在0.7.48后,缓存遵循后端的”Expires”, “Cache-Control: no-cache”, “Cache-Control: max-age=XXX”头部字段,0.7.66版本以后,”Cache-Control:“private”和”no-store”头同样被遵循。nginx在缓存过程中不会处理”Vary”头,为了确保一些私有数据不被所有的用户看到,后端必须设置 “no-cache”或者”max-age=0”头,或者proxy_cache_key包含用户指定的数据如$cookie_xxx,使用cookie的值作为proxy_cache_key的一部分可以防止缓存私有数据,所以可以在不同的location中分别指定proxy_cache_key的值以便分开私有数据和公有数据。
缓存指令依赖代理缓冲区(buffers),如果proxy_buffers设置为off,缓存不会生效。

注意: 如上面所说,nginx是根据expires,max-age等信息来进行缓存的,如果你需要强制缓存的话,你可以使用proxy_ignore_headers指令,强制进行缓存。

4. proxy_cache_valid
设定缓存的时间。例如对应200 10m, 对应404 1h 写法如下:

proxy_cache_valid 200 10m;
proxy_cache_valid 404 1h;
proxy_cache_valid any 1d;

5. proxy_cache_use_stale , proxy_next_upstream
设定下次更新缓存的触发条件。

6. $upstream_cache_status
在配置底部增加一条set_header X-Cache $upstream_cache_status 这时候你访问页面的时候,就可以了解当前页面是否被nginx缓存成功。一共有多种状态:MISS, EXPIRED, HIT

需要了解更多信息,可以仔细查看官方的proxy文档,文档是介绍指令的方式来说明的,因此有些你需要的东西,可能要花一定的时间全部看完,才知道。就如怎么强制进行缓存。我是看了四五遍之后才发现,可以如此干。

Nginx还有很多功能还没发掘,需要在实际使用中好好研究。会有更多收获。

利用Redis开发预约系统

这次来讲讲redis的一些高级使用机制,redis就不在这里过于介绍了,不了解的可以google,可以wiki,也可以去redis官网看看。上面都有它的历史和使用说明。

Redis主要提供了集中类型:字符串,hash,list,有序/无序集合。每个类型都有不同的使用场景,例如字符串可以保存一些固定key的数据。比如用户基础数据等等。Redis是一个单进程单线程的内存数据库,对于并发来说,基本上不会出现重复的问题,同时它所处理的速度也是极快的。因此在门票预约或者发红包的时候,利用Redis这一特性,可以很好的解决并发所带来的问题,同时也提高了运行效率,处理速度。

对于门票预约来说,一般都会事先生成好所有的座位,这里的座位我们称之为ticket。Redis中List是提供了push和pop功能。第一步需要先把所有生成的ticket放入list中,用户在点击预约时,用lpop命令弹出一个。如果想退订或者失败回滚处理用rpush命令插入。另外,门票每个人只能预约一张,你可以创建一个hash表,用userid做为这个hash表的key,在每次获取订单的时候,你可以去这个hash表上查询,此userid时候已经存在,若不存在,可以将预约成功者的userid以及ticket、状态、时间。存储到这个hash表中。

在这套预约系统中,有一个问题,就是我创建了这个预约事件,那么我想调整人数时候就遇到难题了。增加人数还好说,通过差值,在list中rpush那些数量的ticket。而需要减少的时候,可就没办法了。除非你把这个预约事件全部取消并删除。

在并发测试的时候,没有遇到同时会分配到两个相同的ticket。而且redis的并发量,超出了我们的想象,若是只使用Mysql这类关系数据库,对服务器的压力可能会有所增加,同时,mysql因为行锁原因,可能会比Redis低一些。

通过我上面那么多的废话,你设计这套只需要4个:
1. ticketqueue:2 ticket队列表 后面的数字代表预约事件id。 list类型
2. orderedlist:2 成功预约列表。hash表类型
3. userlist 用户数据表,存储用户的联系方式。 hash表
4. eventlist 预约表。 这个存储每个预约的信息。 hash表

除了你用Redis之外,你还可以用mysql。这个Mysql只是用来存储最终的结果,就是当整个预约流程成功跑完获得ticket之后,将状态,预约信息,用户信息存在数据库中,便于后台的检索。

在Nginx上编程

Nginx是一款高效的http server,它的第三方扩展性也是非常非常之多。这次有个需求,需要通过链接转换成真正的图片地址,将资源rewrite出去。如果资源不存在将随机一张图片返回。

在开始写的过程中,本来想考虑使用LUA模块,但是这么简单的一个功能,觉得利用rewrite功能就可以了,但是他需要随机,nginx自身是不提供随机数的,所以在官方第三方中找到了一个HttpSetMiscModule模块。

原来是通过rewrite功能,将链接根据需求分割获得相应的参数,然后使用set命令,设定nginx下的环境变量。具体看下面的例子。

    location ^~ /live/screenshots/ {
        valid_referers none blocked *.xx.cn; #设定有效来源
        if ($invalid_referer) {
            return 403;
        }
        expires 1m;
        if ($uri ~ /live/screenshots/(\d+)/(\d+)\.(icon|html|htm|jpeg|jpg|gif|bmp|png|flv|swf|zip|rar|doc|docx|xls|xlsx)) { #针对链接进行分析,取出需要的参数
            set $img_path $document_root/live/screenshots/$1/1.$3; #设定nginx环境变量
            set $roomid $1;
            set $gameid $2;
            set $img_type $3;
        }
        if (-e $img_path) { #判断此文件是否存在
            rewrite ^ /live/screenshots/$roomid/1.$img_type break; #存在就重写链接 
        }
        set_random $rand 1 10; #生成随机数
        set $rand_img /live/capture/$gameid/$rand.jpg;
        set $rand_img_path $document_root$rand_img;
        if (-e $rand_img_path) { #判断随机图片
            rewrite ^ $rand_img break;
        }
        error_page 404 =200 http://img.xx.cn/live/NonePic/none.jpg; #如果没有图片,返回404图片,状态仍然保持200
    }

通过上面的例子和注释,应该大致了解了吧。通过rewrite的if提供的regex以-e -f -d函数功能实现了强大的功能。在利用set功能,将nginx的环境进行配置。这个与写一个php或者其他脚本来比较,速度非常快。