Nginx 使用 GeoIP 模块区分用户地区

检查 GeoIP 是否安装

首先需要确认当前安装的 Nginx 是否安装了 GeoIP 模块

$ nginx -V
nginx version: nginx/1.12.2
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC)
built with OpenSSL 1.0.2k-fips  26 Jan 2017
TLS SNI support enabled
configure arguments: --user=nginx --group=nginx --with-http_geoip_module --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-mail --with-mail_ssl_module --with-file-aio

如果版本信息中包含 –with-http_geoip_module,则说明已经支持该模块,如果不支持请往下看

安装 GeoIP

首先安装依赖

$ yum -y install zlib zlib-devel

安装 GeoIP

$ wget http://geolite.maxmind.com/download/geoip/api/c/GeoIP.tar.gz
$ tar -zxvf GeoIP.tar.gz
$ cd GeoIP-1.4.8
$ ./configure
$ make
$ make install

使用ldconfig将库索引到系统中

$ echo '/usr/local/lib' > /etc/ld.so.conf.d/geoip.conf
$ ldconfig

检查库是否加载成功

$ ldconfig -v | grep GeoIP
libGeoIPUpdate.so.0 -> libGeoIPUpdate.so.0.0.0
libGeoIP.so.1 -> libGeoIP.so.1.4.8
libGeoIPUpdate.so.0 -> libGeoIPUpdate.so.0.0.0
libGeoIP.so.1 -> libGeoIP.so.1.5.0

将 GeoIP 模块编译到 Nginx 中

根据你当前 Nginx 的安装参数带上 –with-http_geoip_module 重新编译

$ ./configure --user=nginx --group=nginx 
    --with-http_geoip_module 
    --with-http_ssl_module 
    --with-http_realip_module 
    --with-http_addition_module 
    --with-http_sub_module 
    --with-http_dav_module 
    --with-http_flv_module 
    --with-http_mp4_module 
    --with-http_gunzip_module 
    --with-http_gzip_static_module 
    --with-http_random_index_module 
    --with-http_secure_link_module 
    --with-http_stub_status_module 
    --with-mail 
    --with-mail_ssl_module 
    --with-file-aio
$ make && make install

或者重新安装

$ wget https://nginx.org/download/nginx-1.12.2.tar.gz
$ tar zxvf nginx-1.12.2.tar.gz
$ cd nginx-1.12.2
$ ./configure --user=nginx --group=nginx 
    --with-http_geoip_module 
    --with-http_ssl_module 
    --with-http_realip_module 
    --with-http_addition_module 
    --with-http_sub_module 
    --with-http_dav_module 
    --with-http_flv_module 
    --with-http_mp4_module 
    --with-http_gunzip_module 
    --with-http_gzip_static_module 
    --with-http_random_index_module 
    --with-http_secure_link_module 
    --with-http_stub_status_module 
    --with-mail 
    --with-mail_ssl_module 
    --with-file-aio
$ make && make install

使用 GeoIP

首先查看本地是否已有 GeoIP 数据库

$ cd /usr/local/share/GeoIP
$ ll
-rw-r--r--. 1 root root  1183408 Mar 31 06:00 GeoIP.dat
-rw-r--r--. 1 root root 20539238 Mar 27 05:05 GeoLiteCity.dat

如果没有这两个库,则手动下载

wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
gzip GeoLiteCity.dat.gz
wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
gzip GeoIP.dat.gz

将库地址配置到 nginx.conf 中这个位置

http{
    ...
    geoip_country /usr/local/share/GeoIP/GeoIP.dat;
    geoip_city /usr/local/share/GeoIP/GeoLiteCity.dat;
    server {
        location / {
            root /www;
            if( $geo_country_code = CN ){
                root /www/zh;
            }
        }
    }
}

其他参数

  • $geoip_country_code; – 两个字母的国家代码,如:”RU”, “US”。
  • $geoip_country_code3; – 三个字母的国家代码,如:”RUS”, “USA”。
  • $geoip_country_name; – 国家的完整名称,如:”Russian Federation”, “United States”。
  • $geoip_region – 地区的名称(类似于省,地区,州,行政区,联邦土地等),如:”30”。 30代码就是广州的意思
  • $geoip_city – 城市名称,如”Guangzhou”, “ShangHai”(如果可用)。
  • $geoip_postal_code – 邮政编码。
  • $geoip_city_continent_code。
  • $geoip_latitude – 所在维度。
  • $geoip_longitude – 所在经度。

nginx如何有效控制资源请求有效期及资源位置

nginx作为目前最流行的服务器端软件,成功有其必然的原因。苏南大叔的博客,服务器端容器也使用了nginx技术。在本文中,您将看到:“nginx的conf配置文件,如何有效控制资源请求”的相关文章。这些静态资源主要包括:img、js、css等常见类型,涉及的内容都是nginx的conf文件的高级用法。

禁用静态资源缓存

如果禁用了静态资源缓存的话,客户每次访问网站的时候,静态资源都是要从服务器端进行请求的,而不是从本地缓存中读取。

  • 可能有人会说了,谁会这么干啊。其实,确实有这样的需求的。比如:苏南大叔以前写过一个网速对比程序。要求不能从本地缓存读取到的数据,必须每次都从服务器请求数据。
  • 当然,也有人会说:可以修改网页代码中静态资源的地址,在其地址后面,增加个随机时间戳。这样,就可以有效避免读取本地缓存了。苏南大叔以前也这么干过,不过觉得还是不稳妥。修改代码比较麻烦。那么利用nginx的conf文件配置,就可以从服务器端进行设定了。

核心配置代码如下:

location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$ {
  add_header Cache-Control no-store;
}
location ~ .*.(js|css)?$ {
  add_header Cache-Control no-store;
}

控制静态资源的缓存时长

不过一般来说,对于静态资源的缓存控制,一般是类似下面所示的。下面的单位d字样就是day天。

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

expires指令用于控制HTTP应答中的“Expires”和“Cache-Control”Header头部信息,起到控制页面缓存的作用。

语法:expires [time|epoch|max|off]
默认值:off

  • expires <time>:可以使用正数或负数。“Expires”头标的值将通过当前系统时间加上设定time值来设定。
    time值还控制”Cache-Control”的值:负数表示no-cache,正数或零表示max-age=time
  • expires -1:指定“Expires”的值为当前服务器时间-1s,即永远过期。
  • expires epoch:指定“Expires”的值为 1 January,1970,00:00:01 GMT
  • expires max: 指定“Expires”的值为31 December2037 23:59:59GMT,”Cache-Control”的值为10年。
  • expires off:不修改“Expires”和”Cache-Control”的值

转移服务器端静态资源位置

假如,苏南大叔要请求同一个资源/demo/demo.png,如果nginx用的是不同的配置,则:

  • 如果设置的是:root /code/nginx/demo/,那么实际上的资源位置应该是/code/nginx/demo/demo/demo.png。
  • 如果设置的是:alias /code/nginx/demo/,那么实际上的字样位置应该被改写到/code/nginx/demo/demo.png。

下面的配置代码中,苏南大叔再一次演示如何使用root/alias进行nginx请求改写。

location ^~ /root/ {
    root  /code/nginx/root/ ;
}
location ^~ /alias/ {
    alias  /code/nginx/alias/ ;
}

未分类

未分类

拦截请求跳转到新的url

下面演示的是:拦截/baidu/下面的请求,然后永久性的permanent跳转到百度的搜索页面。当然,这个过程中,地址栏里面的url是发生变化的。其实这个小功能还是不错的,善加利用一下,就可以利用这种拦截特殊url的方式,做很多特殊的跳转动作。想象力由你来发挥了。

location ^~ /baidu/ {
    rewrite ^/baidu/(.*)$  https://www.baidu.com/s?wd=$1 permanent;
}

未分类

未分类

总结

nginx设置,是网站运维的一个必备技能点。如果能够掌握nginx的配置文件的写法,应该可以做到事半功倍的效果,这里仅仅总结了几点简单常见的nginx设置场景。

Nginx+Tomcat+memcached高可用会话保持

一、概述

之前文章已经描述了企业高可用负载相关的架构及实现,其中常用的nginx或haproxy,LVS结合keepalived做前端高可用调度器;但之前没有提到会话高可用保持;
本文通过 Tomcat Session Replication Cluster(tomcat自带)和tomcat结合memcat及第三方组件实现Tomcat Memcache Session Server高可用会话缓存服务;
实现的效果:
同一客户端访问业务网站,经过调度器负载调度到达后端,不管选择的是那个后端,session ID都不变,都保存在两台或多台的memcached缓存中(负载冗余);以保持持会话;

架构图:

未分类

说明:客户端请求时nginx通过负载调度算法将请求调度至某一后端服务器;tomcat 把会话通过组播的方式复制到集群各节点;所有节点共享会话;

未分类

说明:客户端请求时nginx通过负载调度算法将请求调度至某一后端服务器;并把session存储到两台memcached中;客户端刷新(不换浏览器)时,请求换成另一个后端服务器响应时session ID保持不变;

测试环境:

nginx: CentOS7 epel 安装nginx WAN:172.16.3.152 LAN:192.168.10.254
tomcat A: CentOS7 node1.san.com epel 安装 tomcat 7 openjdk-1.8.0 memcached(现实环境中单独服务器)
tomcat B: CentOS7 nodde2.san.com epel 安装 tomcat 7 openjdk-1.8.0 memcached 现实环境中单独服务器)
测试客户端ubuntu 16.04

cat /etc/hosts
172.16.3.152       www.san.com

二、安装配置集群

nginx安装

[root@nginx ~]# yum install epel-release -y
[root@nginx ~]# yum install nginx -y

nginx配置

在/etc/nginx/nginx.conf http段添加如下行

    upstream tcsrvs {
        server 192.168.10.11:8080;
        server 192.168.10.12:8080;
        }

cat /etc/nginx/conf.d/san.com.conf

[root@nginx ~]# cat /etc/nginx/conf.d/san.com.conf 
server {
            listen 80;
            server_name www.san.com;
            location / {
                proxy_pass http://tcsrvs;
                }
        }

Tomcat配置:
两台均需要安装

#yum install epel-release -y
#yum install java-1.8.0 java-1.8.0-openjdk-devel tomcat tomcat-webapps tomcat-admin-webapps tomcat-docs-webapp  -y

说明:也可以通过oracle官方下载jdk 下载tomcat 解压到指定目录并添加环境变量;一般企业推荐此种方式;为了快捷,我们用epel仓库中的稳定版本;

添加测试页

yum 安装的tomcat工作目录在/var/lib/tomcat/webapps 分别在node1与node2上,此目录下创建测试项目,title 分别叫Tomcat A与Tomcat B 颜色分别为green与red;以示区别;生产环境node1 与node2内容一致;这里为了测试区别node1与node2内容;

#mkdir -pv /var/lib/tomcat/webapps/test/{WEB-INF,META-INF,classes,lib}
#cat /var/lib/tomcat/webapps/test/index.jsp
<%@ page language="java" %>
 <html>
      <head><title>Tomcat A</title></head>
          <body>
             <h1><font color="red">TomcatA.san.com</font></h1>
           <table align="centre" border="1">
            <tr>
                <td>Session ID</td>
            <% session.setAttribute("san.com","san.com"); %>
            <td><%= session.getId() %></td>
            </tr>
            <tr>
            <td>Created on</td>
           <td><%= session.getCreationTime() %></td>
           </tr>
      </table>
    </body>
</html>

配置管理页密码

tomcat与管理程序安装好后配置访问密码
修改注释/etc/tomcat/tomcat-users.xml文件

<role rolename="admin-gui"/>
<role rolename="manager-gui"/>
<user username="tomcat" password="tomcat" roles="manager-gui,admin-gui"/>

备份默认/etc/tomcat/server.xml文件

cd /etc/tomcat
cp server.xml server.xml_def

测试页访问

http://www.san.com/test 如图:出现Tomcat A

未分类

Ctrl+F5强制刷新 又出现Tomcat B

未分类

引发问题:如果是两台内容一样的配置,客户端访问刷新一下就换到另一个后端处理;类似通过session保留信息的服务(购买物车)如何保留?换句话说,如何保持会话不中断,无论请求被分配到那一个后端?

解决方案

1)会话sticky(粘性):分为source_ip 基于源ip和cookie

source_ip在不同的调度器上有不同的实现方式:
lvs:sh算法;
nginx:ip_hash或hash $request_uri consistent(一致性哈希算法)
haproxy: source

cookie:
nginx:hash 或 hash $cookie_name consistent;
haproxy:cookie

2)会话集群(session cluster):delta session manager

3)session server: redis(store),memcached(cache)

以下基于tomcat自带会话集群与memcached实现会话保持 功能;

三、Tomcat Session Replication Cluster配置

Tomcat Session Replication Cluster中文又叫 tomcat 会话复制集群,即会话通过组播方式复制到每一个后端tomcat节点;
可参考自带帮助文档:http://www.san.com/docs/cluster-howto.html
两台node1 node2节点/etc/hosts中添加如下:

#cat /etc/hosts
192.168.10.11 node1.san.com node1
192.168.10.12 node2.san.com node2

两台tomcat 节点sever.xml的Host字段中添加如下内容:

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
                 channelSendOptions="8">

          <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>

          <Channel className="org.apache.catalina.tribes.group.GroupChannel">
            <Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="228.10.0.4"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="auto"           <!--   如果没有/etc/hosts解析则需要本机ip   -->
                      port="4000"
                      autoBind="100"
                      selectorTimeout="5000"
                      maxThreads="6"/>

            <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
              <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
            </Sender>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
          </Channel>

          <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
                 filter=""/>
          <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>

          <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
                    tempDir="/tmp/war-temp/"
                    deployDir="/tmp/war-deploy/"
                    watchDir="/tmp/war-listen/"
                    watchEnabled="false"/>

          <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
          <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
        </Cluster>

复制 /etc/tomcat/web.xml /var/lib/tomcat/webapps/test/WEB-INF/ 下并在web.xml的”“字段下添加 ““;
重启tomcat 并再次访问http://www.san.com/test 如图:

未分类

Ctrl + F5强制刷新如图:

未分类

可以可出会话得到保持,只要是从同一个客户端中请求,刷新或关闭重新打开(基于同一个浏览器) 只要会话没有过期,会话(session id) 无论来自那个后端,均是一样;

缺点:

tomcat自带支持会话集群(能过多播方式发送各节点);但有一个缺点;后端tomcat节点过多时效率低下,不适用大规模;

四、Tomcat Memcache Session Server高可用配置

原理说明:
客户端请求到达前端nginx调度器并分配到后端某tomcat节点时,tomcat会优先使用本机内存保存session,当一个请求结束后,tomcat会通过第三方组件(kryo,javolution,xstream,flexjson)把session序列化并发送到memcached节点上存放作备份,第二次请求时,如果本地有session就直接返回,第二次请求结束,把session修改后的信息更新到后端的memcached服务器,以这样的方式来保持本地的session与memcached上的session同步。当这个tomcat节点宕机时,那么用户的下一次请求就会被前端的负载均衡器路由到另一个tomcat节点上,而这个节点上并没有这个用户的session信息,这个节点就从memcached服务器上去读取session,并把session保存到本地的内存,当请求结束,session又被修改,再送回到memcached进行存放备份
当后端配置了多台memcached时,tomcat在更新session信息时会同时向多个memcached节点更新session,当一个memcached节点故障时,tomcat可以从选择一个正常工作的memcached节点读取session信息来发送给用户的浏览器,让其重置session信息,这样,memcached也达到了高可用的目的;
以下操作均在两台node上操作

还原默认配置文件

#cd /etc/tomcat/
#cp server.xml server.xml_cluster
#cp server.xml_def server.xml
#systemctl stop tomcat

安装memcached服务

#yum install memcached -y
#systemctl start memcached

memcache配置(默认即可,生产环境时需要加大内存与并发连接数)

# cat /etc/sysconfig/memcached 
PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS=""

两台/etc/tomcat/server.xml Host段中添加如下内容:

<Context path="/test" docBase="test" reloadable="true">
        <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
                memcachedNodes="m1:192.168.10.11:11211,m2:192.168.10.12:11211"
                failoverNodes="m1"
                requestUriIgnorePattern=".*.(ico|png|gif|jpg|css|js)$"
                transcoderFactoryClass="de.javakaffee.web.msm.serializer.javolution.JavolutionTranscoderFactory"/>
        </Context>

说明:

添加两个冗余备份memcached节点分别叫m1,m2 failoverNodes=”m1″ 表示m1作为备份;当m2失败时连接;即使用m2;

安装对应版本组件

下载以下JAR包到tomcat库目录;
cd /usr/share/tomcat/lib 
wget http://www.java2s.com/Code/JarDownload/javolution/javolution-5.5.1.jar.zip  
#需要解压 unzip javolution-5.5.1.jar.zip
wget http://repo1.maven.org/maven2/net/spy/spymemcached/2.12.1/spymemcached-2.12.1.jar
wget http://repo1.maven.org/maven2/de/javakaffee/msm/msm-javolution-serializer/2.1.1/msm-javolution-serializer-2.1.1.jar
wget http://repo1.maven.org/maven2/de/javakaffee/msm/memcached-session-manager-tc7/2.1.1/memcached-session-manager-tc7-2.1.1.jar
wget http://repo1.maven.org/maven2/de/javakaffee/msm/memcached-session-manager/2.1.1/memcached-session-manager-2.1.1.jar

注意:epel安装的tomcat 和openjdk版本如下:
openjdk: “1.8.0_161”
tomcat : “7.0.76”
以上第三方插件须和对应的版本是兼容的;如发现tomcat启动有问题;无法访问或如下类似错误

#tail -fn 100 /var/log/tomcat/catalina.xxxx.log
三月 23, 2018 4:12:52 下午 org.apache.catalina.core.StandardContext startInternal
严重: The session manager failed to start
org.apache.catalina.LifecycleException: Failed to start component [de.javakaffee.web.msm.MemcachedBackupSessionManager[/test]]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:162)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5643)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)

则表示第三方组件与tomcat不兼容!请重新下载版本;

测试:
浏览器访问http://www.san.com/test 如图:

未分类

Ctrl+F5强刷新 如图:

未分类

从测试上可以看出目前已经通过memcache存储session等缓存信息;并同步到两台memcache上;当前只使用m2节点;

总结

通过nginx快速实现负载tomcat应用;引用session不可保持问题;通过自带的Tomcat Session Replication Cluster和结合memcached及第三方组件实现高可用会话缓存服务来保持会话;前者不适合大规模应用;

修改系统及nginx程序最大访问数,解决高并发问题

修改最大访问数。先更改内核参数

查看:

ulimit -a

未分类

查看此行

open files (-n) 140000

进入系统配置文件进行修改:

[root@bogon ~]#vi /etc/systemd/system.conf
#DefaultLimitNOFILE=
DefaultLimitNOFILE=140000
#DefaultLimitAS=
#DefaultLimitNPROC=
DefaultLimitNPROC=140000

重启生效

但是查看程序发现Max open files依然还是没变

过滤出程序端口号: ps aux |grep nginx
查看linmits信息: cat /proc13636/limits // Max processes ;Max open files` 主要查看这两个数据变化

未分类

ps:上面的图时已经修改过的。默认是1024
所以要再进行一次设置才行

vim /usr/lib/systemd/system/nginx.service //需要在此设置nginx访问数大小

未分类

在[Unit]里面添加 LimitNOFILE=10000000 //数字随意但是不能超过系统值

systemctl restart nginx //重启 ,会报警让你开启 systemctl daemon-reload,那你就开启喽~~~

systemctl daemon-reload //敲这个命令

ps aux |grep nginx // 再次过滤uuid

cat /proc/14524/limits //查看数值发生改变

问题解决。

使用nginx后如何在web应用中获取用户ip及原理解释

问题背景

在实际应用中,我们可能需要获取用户的ip地址,比如做异地登陆的判断,或者统计ip访问次数等,通常情况下我们使用request.getRemoteAddr()就可以获取到客户端ip,但是当我们使用了nginx作为反向代理后,使用request.getRemoteAddr()获取到的就一直是nginx服务器的ip的地址,那这时应该怎么办?

part1:解决方案

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

part2:原理介绍

这里我们将nginx里的相关变量解释一下,通常我们会看到有这样一些配置

server {
        listen       88;
        server_name  localhost;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
        location /{
            root   html;
            index  index.html index.htm;
                            proxy_pass                  http://backend; 
           proxy_redirect              off;
           proxy_set_header            Host $host;
           proxy_set_header            X-real-ip $remote_addr;
           proxy_set_header            X-Forwarded-For $proxy_add_x_forwarded_for;
                     # proxy_set_header            X-Forwarded-For $http_x_forwarded_for;
        }

我们来一条条的看

1. proxy_set_header X-real-ip $remote_addr;
这句话之前已经解释过,有了这句就可以在web服务器端获得用户的真实ip
但是,实际上要获得用户的真实ip,不是只有这一个方法,下面我们继续看。

2. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
我们先看看这里有个X-Forwarded-For变量,这是一个squid开发的,用于识别通过HTTP代理或负载平衡器原始IP一个连接到Web服务器的客户机地址的非rfc标准,如果有做X-Forwarded-For设置的话,每次经过proxy转发都会有记录,格式就是client1, proxy1, proxy2,以逗号隔开各个地址,由于他是非rfc标准,所以默认是没有的,需要强制添加,在默认情况下经过proxy转发的请求,在后端看来远程地址都是proxy端的ip 。也就是说在默认情况下我们使用request.getAttribute(“X-Forwarded-For”)获取不到用户的ip,如果我们想要通过这个变量获得用户的ip,我们需要自己在nginx添加如下配置:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
意思是增加一个$proxy_add_x_forwarded_for到X-Forwarded-For里去,注意是增加,而不是覆盖,当然由于默认的X-Forwarded-For值是空的,所以我们总感觉X-Forwarded-For的值就等于$proxy_add_x_forwarded_for的值,实际上当你搭建两台nginx在不同的ip上,并且都使用了这段配置,那你会发现在web服务器端通过request.getAttribute(“X-Forwarded-For”)获得的将会是客户端ip和第一台nginx的ip。

那么$proxy_add_x_forwarded_for又是什么?
$proxy_add_x_forwarded_for变量包含客户端请求头中的”X-Forwarded-For”,与$remote_addr两部分,他们之间用逗号分开。
举个例子,有一个web应用,在它之前通过了两个nginx转发,即用户访问该web通过两台nginx。
在第一台nginx中,使用
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
现在的$proxy_add_x_forwarded_for变量的”X-Forwarded-For”部分是空的,所以只有$remote_addr,而$remote_addr的值是用户的ip,于是赋值以后,X-Forwarded-For变量的值就是用户的真实的ip地址了。

到了第二台nginx,使用
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
现在的$proxy_add_x_forwarded_for变量,X-Forwarded-For部分包含的是用户的真实ip,$remote_addr部分的值是上一台nginx的ip地址,于是通过这个赋值以后现在的X-Forwarded-For的值就变成了“用户的真实ip,第一台nginx的ip”,这样就清楚了吧。

最后我们看到还有一个$http_x_forwarded_for变量,这个变量就是X-Forwarded-For,由于之前我们说了,默认的这个X-Forwarded-For是为空的,所以当我们直接使用proxy_set_header X-Forwarded-For $http_x_forwarded_for时会发现,web服务器端使用request.getAttribute(“X-Forwarded-For”)获得的值是null。如果想要通过request.getAttribute(“X-Forwarded-For”)获得用户ip,就必须先使用proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;这样就可以获得用户真实ip。

ps:变量名太长,自己感觉看着好晕,打字打的我眼睛都花了,希望解释清楚了,如果有疑问可以和我交流,共同学习。

使用Kibana分析Nginx访问日志

介绍

使用Kibana可以很方便的分析Nginx访问日志,可以分析出如:网站的访问趋势、页面访问次数、访问者地区分布、访问者所使用的浏览器设备占比等,可以发挥你的想象任意指定kibana的图表。

效果图:

未分类

未分类

未分类

未分类

具体用法

以创建浏览器统计饼图为例:

1、进入Visualize栏目,选择Pie图

未分类

2、下一步选择Index

未分类

3、配置数据

配置如下图,最后点击左上角的保存按钮,一张浏览器统计饼图就做好了。

未分类

最后可以把做好的图标添加到仪表盘Dashboard来方便查看。

附上一些配置

未分类

未分类

未分类

未分类

未分类

未分类

Nginx访问频率控制

HTTP服务器的吞吐率(单位时间吞吐量)通常有一个上限,尤其是普通配置的机器,在带宽够的情况下,用压测工具经常能把服务器压出翔,为了线上环境稳定性,防止恶意攻击影响到其他用户,可选择对客户端访问频率进行合理限制。

限制原理

限制原理并不难,可一句话概括为:“根据客户端特征,限制其访问频率”,客户端特征主要指IP、UserAgent等。使用IP比UserAgent更可靠,因为IP无法造假,UserAgent可随意伪造。

虽然IP无法造假,但恶意人员可以利用代理,因此仅依靠限制IP访问频率并不能应对大量代理的情况,另外在限制IP访问频率时也要考虑多用户共享网络出口的情况,比如校园网、企业局域网网络之类。

实践

由于存在盲区,不知道Nginx中有访问控制模块,想着自己在应用代码中使用Redis实现基于IP的访问频率控制,在准备写代码之前发现Nginx有limit_req模块可限制基于IP的访问频率,因此选择Nginx,这肯定比自己实现更省事,性能也更优秀。

limit_req_zone

Syntax:  limit_req_zone key zone=name:size rate=rate;
Default: —
Context: http
  • key,表示作为限制的请求特征,可以包含文本与变量,IP场景使用$binary_remote_addr
  • name,zone的名称,limit_req会用到
  • size,zone的大小,1M大小在64位系统可存储8000个state(ip、count…),每次添加新state时,可能删除至多两个前60秒未使用的- state,若添加新state时zone大小不够,则删除较旧的state,释放空间后依旧不够返回503
  • rate,访问速率,支持秒或者分钟为单位,但nginx内部使用毫秒追踪请求数,如果限制是10r/1s,实际上是1r/100ms

limit_req

Syntax:  limit_req zone=name [burst=number] [nodelay];
Default: —
Context: http, server, location
  • name,limit_req_zone中配置的名称
  • burst,可理解为缓冲卡槽,如果设置则所有请求都经由缓冲卡槽转发给upstream,通常可并发接收的请求数为number + 1,但当number为0时会拒绝所有请求
  • nodelay,缓冲卡槽中请求转发给upstream的时机,不设置时,会按照zone的速率逐个转发,当设置为nodelay时,请求到达缓冲卡槽后会立即转发给upstream,但卡槽中的占位依旧按照频率释放

配置

理解limit_req_zone与limit_req之后,感叹这真是个好设计,也知道它背后的形象的名称:漏桶算法。

了解配置方式后开始实际操作,在Nginx配置中的http内添加:

limit_req_zone $binary_remote_addr zone=one:2m rate=10r/s;

在需要限制的server内添加:

limit_req zone=one burst=10 nodelay;

按照官方文档,2M大小在64位系统中大约可存储16000个状态数据,针对自己的个人网站足够,10r/s即1r/100ms,配合burst=10应该也OK,重启Nginx,然后使用压测工具检验一下。

rate、burst、nodelay的不同特点:

排除其他因素,rate的大小针对同一客户端的平均吞吐率起到决定性作用,而burst与nodelay可根据业务需求选择,burst越大可接收的并发请求越多,但rate跟不上可能导致大量客户端请求超时,nodelay在rate较小时可以提升业务在瞬时的吞吐率表现

白名单

之所以会限制IP访问频率,主要是为了阻止外部调用者的恶意行为,但经过上述配置后,对系统内部调用者同样会有所限制,因此我们希望将内部调用者列入白名单内,使其不受访问频率限制。

这主要借助Nginx中的geo与map功能,通过geo将IP映射成值,然后再通过map将值映射成变量或常量,恰好limit_req_zone中如果key为”表示不对其进行频率限制,所以只需要将白名单用户的key设置为”。

修改配置文件中http的内容:

geo $limit {
    default 1;
    127.0.0.1 0;     # 本机地址
    172.31.0.0/16 0; # 内网地址
}

map $limit $limit_key {
    0 "";
    1 $binary_remote_addr;
}

limit_req_zone $limit_key zone=one:2m rate=10r/s;

总结

至此,根据IP限制访问频率配置完成,Nginx中与limit_req类似的还有limit_conn,可用来在连接层面进行限制,同时针对limit_req还有两个配置项limit_req_status与limit_req_log_level,前者用来设置达到限制时返回何种状态码,后者制定达到限制时的日志采用何种级别,会导致达到限制的信息出现在不同的日志文件中。

从打算自己实现,到使用Nginx实现,感觉自己的对服务器的理解还需要提升,应该从合理性角度就可以推断出Nginx包含该类功能,而不是在搜索的过程中发现Nginx包含该功能。

nginx和apache添加brotli算法压缩网站

什么是brotli?

brotli是Google开发的最新压缩算法,有效减少网站传输数据
具体内容请查看WIKI
https://en.wikipedia.org/wiki/Brotli

安装依赖文件[仅限centos]

yum groupinstall 'Development Tools' -y
yum install cmake -y

编译安装brotli库

wget https://github.com/google/brotli/archive/v1.0.3.tar.gz
tar -zxvf v1.0.3.tar.gz
cd brotli-1.0.3
./configure-cmake
make 
make test 
make install

apache/nginx添加编译参数

 "--enable-brotli"  
"--with-brotli=/usr/local/lib"    #apache官方模块,依赖brotli库 

--add-module=../ngx_brotli-master   #添加ngx_brotli模块编译

ngx_brotli模块下载地址
https://github.com/google/ngx_brotli
https://github.com/eustas/ngx_brotli

apache/nginx修改配置文件

http://httpd.apache.org/docs/2.4/mod/mod_brotli.html
apache修改文件 /etc/httpd/conf/extra/httpd-deflate.conf

<IfModule brotli_module> 
 SetOutputFilter BROTLI_COMPRESS;DEFLATE
 SetEnvIfNoCase Request_URI .(?:gif|jpe?g|png)$ no-gzip no-brotli dont-vary
 BrotliCompressionQuality  6
 BrotliCompressionWindow 18
 AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/css text/xml
 AddOutputFilterByType BROTLI_COMPRESS application/x-javascript application/javascript
 AddOutputFilterByType BROTLI_COMPRESS application/rss+xml
 AddOutputFilterByType BROTLI_COMPRESS application/xml
 AddOutputFilterByType BROTLI_COMPRESS application/json
</IfModule>
#nginx配置文件  
    brotli on;
    brotli_types text/html text/plain text/javascript text/css text/xml text/x-component application/javascript application/x-javascript application/xml application/json application/xhtml+xml application/rss+xml application/atom+xml application/x-font-ttf application/vnd.ms-fontobject image/svg+xml image/x-icon font/opentype;
    brotli_static off;
    brotli_comp_level 6;
    brotli_buffers 8 16k;
    brotli_window 512k;
    brotli_min_length 512;

出错解决办法

nginx: error while loading shared libraries: libbrotlienc.so.1: cannot open shared object file: No such file or directory
可行的解决方案之一,是把对应的库文件做软链接:

# 64 位系统  
 ln -s /usr/local/lib/libbrotlienc.so.1 /lib64 
 ln -s /usr/local/lib/libbrotlicommon.so.1  /lib64

# 32 位系统  
ln -s /usr/local/lib/libbrotlienc.so.1 /lib 
ln -s /usr/local/lib/libbrotlicommon.so.1  /lib

重载nginx,若无报错,即问题解决
nginx -s reload

基于Nginx+lua的蓝绿发布系统

一、什么是蓝绿发布

蓝绿部署是不停老版本,部署新版本然后进行测试,确认OK,将流量切到新版本,然后老版本同时也升级到新版本。

1、特点

蓝绿部署无需停机,并且风险较小。

2、蓝绿发布的注意事项

当你切换到蓝色环境时,需要妥当处理未完成的业务和新的业务。如果你的数据库后端无法处理,会是一个比较麻烦的问题;

可能会出现需要同时处理“微服务架构应用”和“传统架构应用”的情况,如果在蓝绿部署中协调不好这两者,还是有可能会导致服务停止。
需要提前考虑数据库与应用部署同步迁移 /回滚的问题。
蓝绿部署需要有基础设施支持。
在非隔离基础架构( VM 、 Docker 等)上执行蓝绿部署,蓝色环境和绿色环境有被摧毁的风险。

二、为什么需要蓝绿发布系统

1、新项目和新需求非常多

2、新需求的上线过程是,先上线一台服务器然后观察会不会出问题,如果没有问题则全部上线。

3、分流是关键,但是动态分流是痛点。

三、老分流方案

未分类

方案存在的问题点:

1、nginx.conf配置文件里各种if、set和rewrite,并且容易配置出错。
2、修改完配置文件后,重启或者reload后才能生效。
3、不能实现太复杂的逻辑。
4、不能实现一些特殊分流方式。

四、新分流方案

未分类

功能说明:

  • 采用Redis存放分流策略
  • 分流策略包括按时间来分流,比如每分钟分流多少笔订单,还有按权重分流,比如新老系统之间的比例是1:9
  • 采用OpenResty+lua,整体性能优秀。

单台压测结果:

未分类

配置Lua转发Nginx请求复制

通过配置Nginx来将请求进行复制,转发到其他应用,以下是自己实际搭建的步骤以及自己的理解,方便以后使用

1、环境搭建

实际搭建环境如下:Linux CenterOS 6.5 ,Nginx1.9.0,headers-more-nginx-module-0.31,LuaJIT-2.1.0-beta2,lua-nginx-module-0.10.2,ngx_devel_kit-0.2.19。

以上是搭建成功的各个对应版本,如果版本不对应可能会导致nginx编译失败,github下载后的插件尽量重命名一下,方便使用。

按照参考链接进行编译Nginx。

2、Nginx+Lua文件配置

a、编写一个 copy request 的 lua 脚本copy_req.lua

local res1, res2, action
action = ngx.var.request_method
if action == "POST" then
        arry = {method = ngx.HTTP_POST, body = ngx.req.read_body()}
else
        arry = {method = ngx.HTTP_GET}
end

if ngx.var.svr == "on" then
        res1, res2 = ngx.location.capture_multi {
                { "/product" .. ngx.var.request_uri , arry},
                { "/test" .. ngx.var.request_uri , arry},
        }
else
        res1, res2 = ngx.location.capture_multi {
                { "/product" .. ngx.var.request_uri , arry},
        }
end

if res1.status == ngx.HTTP_OK then
        local header_list = {"Content-Length", "Content-Type", "Content-Encoding", "Accept-Ranges"}
        for _, i in ipairs(header_list) do
                if res1.header[i] then
                        ngx.header[i] = res1.header[i]
                end
        end
        ngx.say(res1.body)  #此处代表只返回生产环境的返回结果
else
        ngx.status = ngx.HTTP_NOT_FOUND
end

此处文件地址引用是可以写觉得地址,相对地址是相对于nginx目录的。
b、配置对应的Nginx配置文件,此处本文地址是conf/vhost/fenliu.conf,在nginx.conf下端加入include vhost/*.conf;

fenliu.conf文件配置如下:

upstream product {
        server  127.0.0.1:80;
}
upstream test {
        server  192.168.1.1:88;
}
server {
        listen      8000;
        #lua_code_cache off;

        location ~* ^/product {
                log_subrequest on;
                rewrite ^/product(.*)$ $1 break;
                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 http://product;
                access_log logs/product-upstream.log;
        }

        location ~* ^/test {
                log_subrequest on;
                rewrite ^/test(.*)$ $1 break;
                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 http://test;
                access_log logs/test-upstream.log;
        }

        location ~* ^/(.*)$ {
                client_body_buffer_size 2m;
                set $svr    "on";              #开启或关闭copy功能
                content_by_lua_file conf/vhost/copy_req.lua;
        }
}

此文件很重要,这里备注的是本人自己的理解,^/product,^/test主要就是对这两个路径访问的url进行转发,一个转发到生产,一个到测试,多了一个rewrite是为了重写请求地址,下面会讲到,
^/(.*)$才是重点,是将所有非product,test请求进行请求复制转发。

以上面配置为例,实际使用的流程如下:

1、请求地址:http://ip:8000/hello/req.do
2、nginx不匹配product和test会走最后一个,通过Lua配置会变成两个请求/product/hello/req.do和/test/hello/req.do
3、这时会被nginx的product和test拦截到,进行转发到生产和测试环境,此时地址是不对的,所以使用rewrite进行url重写,
rewrite ^/product(.*)$ $1 break; 匹配/product/hello/req.do会变成/product(/hello/req.do),$1代表/hello/req.do,重写后的地址就会变成我们想要的地址,转发后就变成http://product/hello/req.do。