Nginx+Tomcat集群实现反向代理负载均衡和Session共享
本文来源于https://blog.****.net/sunroyi666/article/details/79163088,如果有不当之处,请通知删除。
以前写过一篇Apache+Tomcat集群的文章,最近正好有空,把Nginx+Tomcat也写一下做为比较。
首先,用Nginx和Apache+Tomcat做集群的区别,我就直接从网上找了点资料。
主要区别在于,Apache是同步多进程模型,一个连接对应一个进程。Nginx是异步多进程模型,多个连接对应一个进程,这里的多个连接可以达到上万的级别,所以普遍认为,Nginx的性能更好。
Nginx:
轻量级,采用 C 进行编写,同样的 web 服务,会占用更少的内存及资源
抗并发,nginx 以 epoll and kqueue 作为开发模型,处理请求是异步非阻塞的,负载能力比 apache 高很多,而 apache 则是阻塞型的。在高并发下 nginx 能保持低资源低消耗高性能 ,而 apache 在 PHP 处理慢或者前端压力很大的情况下,很容易出现进程数飙升,从而拒绝服务的现象。
nginx 处理静态文件好,静态处理性能比 apache 高三倍以上
nginx 的设计高度模块化,编写模块相对简单
nginx 配置简洁,正则配置让很多事情变得简单,而且改完配置能使用 -t 测试配置有没有问题,apache 配置复杂 ,重启的时候发现配置出错了,会很崩溃
nginx 作为负载均衡服务器,支持 7 层负载均衡
nginx 本身就是一个反向代理服务器,而且可以作为非常优秀的邮件代理服务器
启动特别容易, 并且几乎可以做到 7*24 不间断运行,即使运行数个月也不需要重新启动,还能够不间断服务的情况下进行软件版本的升级
社区活跃,各种高性能模块出品迅速
apache 的 rewrite 比 nginx 强大,在 rewrite 频繁的情况下,用 apache
apache 发展到现在,模块超多,基本想到的都可以找到
apache 更为成熟,少 bug ,nginx 的 bug 相对较多
apache 超稳定
apache 对 PHP 支持比较简单,nginx 需要配合其他后端用
apache 在处理动态请求有优势,nginx 在这方面是鸡肋,一般动态请求要 apache 去做,nginx 适合静态和反向。
apache 仍然是目前的主流,拥有丰富的特性,成熟的技术和开发社区
总结:
两者最核心的区别在于 apache 是同步多进程模型,一个连接对应一个进程,而 nginx 是异步的,多个连接(万级别)可以对应一个进程
一般来说,需要性能的 web 服务,用 nginx 。如果不需要性能只求稳定,更考虑 apache ,后者的各种功能模块实现得比前者,例如 ssl 的模块就比前者好,可配置项多。epoll(freebsd 上是 kqueue ) 网络 IO 模型是 nginx 处理性能高的根本理由,但并不是所有的情况下都是 epoll 大获全胜的,如果本身提供静态服务的就只有寥寥几个文件,apache 的 select 模型或许比 epoll 更高性能。当然,这只是根据网络 IO 模型的原理作的一个假设,真正的应用还是需要实测了再说的。
更为通用的方案是,前端 nginx 抗并发,后端 apache 集群,配合起来会更好。
--------------------------------------------------------------------------------------------------
下面介绍如何配置Nginx+Tomcat集群并做到Session复制:
一. Tomcat配置(Tomcat8):
1.conf/server.xml:
主要注意的是端口号(port)的设置,<Engine>中jvmRoute的设置,<Cluster>的注释放开。
- <?xml version='1.0' encoding='utf-8'?>
- <Server port="8075" shutdown="SHUTDOWN">
- <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
- <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">
- <!-- 多个Tomcat的端口号不能相同,否则无法启动 -->
- <Connector port="8080" protocol="HTTP/1.1"
- connectionTimeout="20000" maxHttpHeaderSize="2000000"
- redirectPort="8444" />
- <Connector port="8072" protocol="AJP/1.3" redirectPort="8444" />
- <!-- 多个Tomcat的jvmRoute必须要设置且必须相同 -->
- <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
- <!-- Cluster原本是被注释掉的,现在要放开 -->
- <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
- <Realm className="org.apache.catalina.realm.LockOutRealm">
- <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
- resourceName="UserDatabase"/>
- </Realm>
- <Host name="localhost" appBase="webapps"
- unpackWARs="true" autoDeploy="true" reloadable="true">
- <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
- prefix="localhost_access_log" suffix=".txt"
- pattern="%h %l %u %t "%r" %s %b" />
- </Host>
- </Engine>
- </Service>
- </Server>
2.webapps/ROOT/WEB-INF/web.xml:
添加<distributable />
- <?xml version="1.0" encoding="ISO-8859-1"?>
- <web-app xmlns="http://java.sun.com/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
- http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
- version="3.0"
- metadata-complete="true">
- <display-name>Welcome to Tomcat</display-name>
- <description>
- Welcome to Tomcat
- </description>
- <distributable />
- </web-app>
- <%@ page contentType="text/html; charset=GBK" %>
- <%@ page import="java.util.*" %>
- <html><head><title>Cluster Test</title></head>
- <body>
- <%
- //HttpSession session = request.getSession(true);
- System.out.println(session.getId());
- out.println("<br> SESSION ID:" + session.getId()+"<br>");
- // 如果有新的请求,则添加session属性
- String name = request.getParameter("name");
- if (name != null && name.length() > 0) {
- String value = request.getParameter("value");
- session.setAttribute(name, value);
- }
- out.print("<b>Session List:</b>");
- Enumeration<String> names = session.getAttributeNames();
- while (names.hasMoreElements()) {
- String sname = names.nextElement();
- String value = session.getAttribute(sname).toString();
- out.println( sname + " = " + value+"<br>");
- System.out.println( sname + " = " + value);
- }
- %>
- <form action="testCluster.jsp" method="post">
- 名称:<input type=text size=20 name="name">
- <br>
- 值:<input type=text size=20 name="value">
- <br>
- <input type=submit value="提交">
- </form>
- <b>负载均衡测试:此为:Tomcat1上的文件,<font color=red>111111</font><b>
- </body>
- </html>
- <b>负载均衡测试:此为:Tomcat1上的文件,<font color=red>111111</font><b>
- <b>负载均衡测试:此为:Tomcat2上的文件,<font color=red>222222</font><b>
二. Nginx配置(nginx-1.12.2):
1.conf/nginx.conf:
- #user nobody;
- worker_processes 1; #工作进程的个数,一般与计算机的cpu核数一致
- #错误日志存放路径
- #error_log logs/error.log;
- #error_log logs/error.log notice;
- #error_log logs/error.log info;
- #指定pid存放文件
- #pid logs/nginx.pid;
- events {
- #使用网络IO模型linux建议epoll,FreeBSD建议采用kqueue,window下不指定。
- #use epoll;
- 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; #开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。
- #tcp_nopush on;
- #keepalive_timeout 0;
- keepalive_timeout 65; #长连接超时时间,单位是秒
- #gzip on; #启用Gizp压缩
- #服务器的集群
- upstream netitcast.com { #服务器集群名字
- #Nginx是如何实现负载均衡的,Nginx的upstream目前支持以下几种方式的分配
- #1、轮询(默认)
- #每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
- #2、weight
- #指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
- #2、ip_hash
- #每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
- #3、fair(第三方)
- #按后端服务器的响应时间来分配请求,响应时间短的优先分配。
- #4、url_hash(第三方)
- #按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。
- #ip_hash;
- server 192.168.8.23:8080 weight=1;#服务器配置 weight是权重的意思,权重越大,分配的概率越大。
- server 192.168.8.23:8079 weight=2;
- }
- #当前的Nginx的配置
- server {
- listen 8082; #监听80端口,可以改成其他端口
- server_name localhost; #当前服务的域名
- #charset koi8-r;
- #access_log logs/host.access.log main;
- location / {
- proxy_pass http://netitcast.com;
- proxy_redirect default;
- proxy_connect_timeout 3; #nginx跟后端服务器连接超时时间(代理连接超时)
- proxy_send_timeout 30; #后端服务器数据回传时间(代理发送超时)
- proxy_read_timeout 30; #连接成功后,后端服务器响应时间(代理接收超时)
- #root html;
- #index index.html index.htm;
- }
- #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;
- }
- # proxy the PHP scripts to Apache listening on 127.0.0.1:80
- #
- #location ~ \.php$ {
- # proxy_pass http://127.0.0.1;
- #}
- # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
- #
- #location ~ \.php$ {
- # root html;
- # fastcgi_pass 127.0.0.1:9000;
- # fastcgi_index index.php;
- # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
- # include fastcgi_params;
- #}
- # deny access to .htaccess files, if Apache's document root
- # concurs with nginx's one
- #
- #location ~ /\.ht {
- # deny all;
- #}
- }
- # another virtual host using mix of IP-, name-, and port-based configuration
- #
- #server {
- # listen 8000;
- # listen somename:8080;
- # server_name somename alias another.alias;
- # location / {
- # root html;
- # index index.html index.htm;
- # }
- #}
- # HTTPS server
- #
- #server {
- # listen 443 ssl;
- # server_name localhost;
- # ssl_certificate cert.pem;
- # ssl_certificate_key cert.key;
- # ssl_session_cache shared:SSL:1m;
- # ssl_session_timeout 5m;
- # ssl_ciphers HIGH:!aNULL:!MD5;
- # ssl_prefer_server_ciphers on;
- # location / {
- # root html;
- # index index.html index.htm;
- # }
- #}
- }
3.我在Nginx的启动过程中,遇到过一闪而过,启动失败的情况,这时可以看一下错误日志 logs/error.log
提示的错误是:2018/01/25 11:07:24 [emerg] 456476#460620: bind() to 0.0.0.0:8081 failed (10013: An attempt was made to access a socket in a way forbidden by its access permissions)
这是因为端口被占用了,把8081端口的进程关掉或者换一个其他的端口即可。
三. 测试
我Nginx用的端口是8082,所以输入http://localhost:8082/testCluster.jsp
1.这是第一次显示的画面,Tomcat1代表这是第一个Tomcat的页面
2.这是第二次显示的画面,Tomcat2表示这是第二个Tomcat的页面,说明负载均衡已成功
3.接下来测试Session复制。根据Tomcat变换前后,SESSION ID都是D07D8BB95B783F8CAA83D94175D24846.jvm1可以判断出,这是同一个Session。
可以输入名称和值,看看在服务器切换后,Session中是否可以保存。
4. 可以看到,2=222保存了,但是现在仍然在Tomcat2上,看不出效果,再试一次。
5.这次切换到Tomcat1了,而且Session List里面的值都带过来了。再试试Tomcat1能否传回Tomcat2。
6.回到Tomcat2以后,SessionList中的值仍然保持,至此Session复制完全成功了。