您的位置:首页 > 运维架构 > Tomcat

Nginx+Tomcat+其他存储 负载均衡配置和session共享资料整理及例子

2017-08-10 18:22 561 查看
摘要:今天这篇文章并非真正的原创文章,只是最近想了解一下负载均衡相关的知识,查阅了很多资料,也亲自在本机配置测试,之后想整理一下,网上资料虽多但是比较杂乱,大部分人转载却并没有亲自尝试,多次转载之后就不太正确了,导致很多人照着例子模仿学习的时候遇到很多问题,所以今天想把我实验过的内容整理出来供大家学习参考,也供自己复习。希望对他人和自己能有所帮助。

第一步:安装Nginx

我选用的是Ubuntu14.04的环境,使用

sudo apt-get install nginx


的方式进行安装。

安装成功后在浏览器输入ip地址,得到如下界面,说明安装已经成功



第二步: 安装配置Tomcat

这个应该比较简单,去tomcat下载一个tomcat版本,我需用的是tomcat7.0.61,因为产品环境使用的是这个版本,并且还有另一个原因,我是试了tomcat8.5之后失败才选择了这个版本,因为是先实验后写的这篇文章。

解压完成拷贝一份,如图所示:



我的其它环境就在这里不说了。

然后修改tomcat7.0.61-2/conf/server.xml文件,主要修改端口号,内容如下:

<Server port="8015" shutdown="SHUTDOWN">

<Connector port="8081" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />

<Connector port="8019" protocol="AJP/1.3" redirectPort="8443" />


使用过tomcat的都知道,如果在一台机器上启动tomcat的多个实例,就必须修改端口号,这里不再赘述。

结果:

tomcat1 10.227.6.62  8080
tomcat2 10.227.6.62  8081

第三步:准备测试war包

主要代码如下:

package com.darren.loadbalance.action;

import java.util.Random;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoadBalanceAction {

@RequestMapping(value = "/load.do")
public String load(ModelMap model, HttpServletRequest request){
String sessionId = request.getSession().getId();
model.put("sessionId", sessionId);
model.put("port", request.getLocalPort());
System.out.println("sessionId = " + sessionId);
System.out.println("port = " + request.getLocalPort());

return "main";
}
}

<!DOCTYPE html>
<html>
<body>
<h2>PORT = ${port}</h2>
<h2>SESSION = ${sessionId}</h2>
</body>
</html>


稍后我会把代码提交,给出GitHub的链接

打包后把war包分别丢进两个tomcat,然后分别启动,

然后在浏览器分别输入http://10.227.6.62:8080/darren-loadbalance/load.do,http://10.227.6.62:8080/darren-loadbalance/load.do得到如下所示的页面:





说明两台tomcat启动成功,我是通过端口来区分两台机器的,这里注意,以后要大量根据端口来区分的。

现在的Nginx和tomcat是各自独立的server,怎么连起来呢?

进行下一步

第四步:配置Nginx进行负载均衡

打开Nginx的配置文件,我的配置文件在/etc/nginx/conf.d/nginx.conf

这个是我自己放在这里的,因为我上边使用的是apt-get install 的方式安装的,我自己拷贝了一根default文件进行修改的,具体看配置

upstream  loadbalance.com {
server    127.0.0.1:8080;
server    127.0.0.1:8081;
}

server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

root /home/darren/program/nginx;
index index.html index.htm;

server_name localhost;

location ~ \.do$ {

proxy_pass http://loadbalance.com; proxy_redirect off;
proxy_set_header   Host    $host;
proxy_set_header   Remote_Addr    $remote_addr;
proxy_set_header   X-Real-IP    $remote_addr;
proxy_set_header   X-Forwarded-For    $proxy_add_x_forwarded_for;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /home/darren/program/nginx;
}
}


主要看第一部分的配置,我的两台tomcat配置在一起,location用来拦截请求,我是拦截*.do的请求,配置完成后启动Nginx。

这里介绍一下Nginx常用的一些命令:

nginx
//或
nginx -c /etc/nginx/nginx.conf
//启动

nginx -t
//测试nginx配置是否正确

nginx -s stop
//结束nginx进程

nginx -s reload
//重启nginx进程

如果提示没有权限,请在命令前加sudo。

好,启动之后在浏览器输入http://10.227.6.62/darren-loadbalance/load.do,查看页面:



然后刷新一下:



你会发现内容变了,变成第二台tomcat响应了,然后刷新多次,发现两台机器基本上是平均交替响应。ok,现在最基本最基本的负载均衡实现了。

但是很这里有很多问题,比如你会发现session每次都在变,这可不行,下面来解决这个问题

第五步:配置session共享

配置session共享是为了什么?其实目的很简单,为了在请求的时候我使用的是同一个session,这样我再访问别的机器的时候不会出现session不存在而让我重新登录的情况。那么解决这个问题就要很多方案:

1、伪共享

伪共享是为了解决上面描述的问题,但其实又不能很好的解决,Nginx本身就提供了一种解决方案,当然Nginx的第三方模块也提供的有,下面我来简单介绍一下这两种方案:

1) ip_hash

这种方案是Nginx自己提供的

ip_hash使用源地址哈希算法,将同一客户端的请求总是发往同一个后端服务器,除非该服务器不可用。

ip_hash语法:

upstream backend {
ip_hash;
server backend1.example.com;
server backend2.example.com;
server backend3.example.com down;
server backend4.example.com;
}
我本地的配置:
upstream loadbalance.com {
ip_hash;
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}

ip_hash简单易用,但有如下问题:

当后端服务器宕机后,session会丢失;
来自同一局域网的客户端会被转发到同一个后端服务器,可能导致负载失衡;
不适用于CDN网络,不适用于前段还有代理的情况。

既然有这么多的缺点,那有没有更好的方案呢?

答案当然是有的,下面我们来看看第三方模块sticky

2) sticky

使用sticky启用会话亲缘关系,这会导致来自同一客户端的请求被传递到一组服务器在同一台服务器。与ip_hash不同之处在于,它不是基于IP来判断客户端的,而是基于cookie来判断。因此可以避免上述ip_hash中来自同一局域网的客户端和前段代理导致负载失衡的情况。

下载地址:https://www.nginx.com/resources/wiki/modules/



GitHub源码地址:https://github.com/lusis/nginx-sticky-module

安装与配置在GitHub上的说明文档都已提供,这里我再贴出来方便查看:

Installation

You'll need to re-compile Nginx from source to include this module.
Modify your compile of Nginx by adding the following directive
(modified to suit your path of course):

./configure ... --add-module=/absolute/path/to/nginx-sticky-module
make
make install

Usage
upstream {
sticky;
server 127.0.0.1:9000;
server 127.0.0.1:9001;
server 127.0.0.1:9002;
}

sticky [name=route] [domain=.foo.bar] [path=/] [expires=1h] [hash=index|md5|sha1] [no_fallback];
- name: the name of the cookies used to track the persistant upstream srv
default: route

- domain: the domain in which the cookie will be valid
default: nothing. Let the browser handle this.

- path: the path in which the cookie will be valid
default: nothing. Let the browser handle this.

- expires: the validity duration of the cookie
default: nothing. It's a session cookie.
restriction: must be a duration greater than one second

- hash: the hash mechanism to encode upstream server. It cant' be used
with hmac.
md5|sha1: well known hash
index: it's not hashed, an in-memory index is used instead
it's quicker and the overhead is shorter
Warning: the matching against upstream servers list
is inconsistent. So, at reload, if upstreams servers
has changed, index values are not guaranted to
correspond to the same server as before!
USE IT WITH CAUTION and only if you need to!
default: md5

- hmac: the HMAC hash mechanism to encode upstream server
It's like the hash mechanism but it uses hmac_key
to secure the hashing. It can't be used with hash.
md5|sha1: well known hash
default: none. see hash.

-hmac_key: the key to use with hmac. It's mandatory when hmac is set
default: nothing.

-no_fallback: when this flag is set, nginx will return a 502 (Bad Gateway or
Proxy Error) if a request comes with a cookie and the
corresponding backend is unavailable.


注意:这里还有些问题,当你打开这个GitHub页面是,会发现有3~4年没有更新过,这个就存在一定的缺陷,对很多的Nginx版本并不支持,当然网上也有很多的方式,比如直接修改sticky的源文件,经过我尝试多次以后,发现了一个完美兼容的版本

首先,从Nginx第三方模块的那个网站下载sticky

其次,使用Nginx1.6.1版本,下载地址:http://nginx.org/download/

这就意味着什么呢,不能使用apt-get install nginx安装的方式,而是要自己通过如下命令进行安装Nginx:

在解压后的Nginx目录执行如下命令:

./configure --prefix=/home/darren/program/nginx/bin --with-http_gzip_static_module --with-http_flv_module --with-http_dav_module --with-http_stub_status_module --with-http_realip_module --add-module=/home/darren/program/nginx/source/nginx-sticky

然后:
make

最后:
make install

当然如果你之前没有使用过这些命令,你可能会遇到很多问题,比如软件不存在,命令不存在等等。
比如:

checking for OS
+ Linux 4.4.0-31-generic x86_64
checking for C compiler ... not found

./configure: error: C compiler cc is not found

这个错误是告诉我机器上没有安装gcc
当然还会有很多错误信息,但是基本都是缺少软件或命令,安装就行,大致缺少的软件或命令如下:

解决依赖包openssl安装,命令:

sudo apt-get install openssl libssl-dev解决依赖包pcre安装,命令:
sudo apt-get install libpcre3 libpcre3-dev

解决依赖包zlib安装,命令:
sudo apt-get install zlib1g-dev
我本地的配置:
upstream loadbalance.com {
sticky;
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}

注意:这里的配置可不是这么一点点,注意上下文,这里是改变的配置

sticky的工作原理:

客户端首次发起请求,请求头未带route的cookie。nginx接收请求,发现请求头没有route,则以轮询方式将请求分配给后端服务器。
后端服务器处理完请求,将响应头和内容返回给nginx。
nginx生成route的cookie,返回给客户端。route的值与后端服务器对应,可能是明文,也可能是md5、sha1等Hash值。
客户端接收请求,并创建route的cookie。
客户端再次发送请求时,带上route。
nginx接收到route,直接转给对应的后端服务器。



可以看到浏览器cookie下确实有。

现在打开浏览器随便刷新:



这个端口和session都不会变化。

Ok,现在我们回过头来思考一下,如果我把8080这台机器停掉,这个时候请求会发送到8081的机器上,我们来看一下这个时候的session有没有发生变化。



这个时候发现确实是8081的机器响应的请求,但是session变化了,那么为什么?

这个问题也不难吧,因为8080上的sessionId在8081上不认识吧,那么8081会创建一个新的session,sessionId当然要变化,这个时候如果8080宕机,用户是不是还要重新登录啊。

所以我说这个是伪共享,这个问题和ip_hash是不是是一样的啊,还是没有解决宕机时重新登录的问题。那么下面我们来介绍一下真正的session共享。

2、真session共享

我现在的架构模型是不是类似于下图所示:



tomcat如果要共享session,是不是可以使用缓存技术啊,现在大多数共享方案基本上也是如此,那么缓存技术有哪些呢?memcache,redis这些应该有吧。

1) 基于memcache的实现

这一种我还没有做实验,以后补充

2) 基于redis的实现

配置:

首先修改tomcat/conf/context.xml文件,添加如下内容:

<!--
Jedis save session
-->
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
<Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
host="localhost"
port="6379"
database="0"
maxInactiveInterval="60"/>如果你想使用redis的哨兵模式,那么请添加如下内容:
<!-- Sentinel 配置 -->
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
<Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
maxInactiveInterval="60"
sentinelMaster="mymaster"
sentinels="127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381,127.0.0.1:26382"
/>

我们就拿第一种配置为例来讲解,首先我们看到这里引用的类并不是tomcat的,也不是redis的对吧,因为看包名大致可以判断出来。没错,这个确实又是个第三方的,是一个个人开发的基于tomcat和redis的session共享模块。
这个模块的地址是:https://github.com/jcoleman/tomcat-redis-session-manager

打开这个GitHub地址可以发现,也是好久没有更新了,所以暂不支持tomcat8,这也是为什么我在文章开始的时候说我把tomcat8换成了tomcat7的原因。

我们需要把这个源码打包,当然也可以去网上下载,因为很多人已经打过包上传了,只需在百度里搜索一下就行,除此包外,我们还需要引入jedis的包以及jedia所依赖的包,总共有三个,下面是我所使用的:



这三个包需要放到tomcat lib目录下,测试前,还需要修改一些代码

package com.darren.loadbalance.action;

import java.util.Random;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoadBalanceAction {

@RequestMapping(value = "/load.do")
public String load(ModelMap model, HttpServletRequest request){
HttpSession session = request.getSession();
if(session.getAttribute("user") != null){
session.setAttribute("user", session.getAttribute("user"));
}else{
session.setAttribute("user", "Darren Zhang" + new Random().nextInt());
}
String sessionId = session.getId();
model.put("sessionId", sessionId);
model.put("port", request.getLocalPort());
System.out.println("sessionId = " + sessionId);
System.out.println("port = " + request.getLocalPort());

return "main";
}
}
<!DOCTYPE html>
<html>
<body>
<h2>Current User: ${user}</h2>
<h2>PORT = ${port}</h2>
<h2>SESSION = ${sessionId}</h2>
</body>
</html>

Nginx的配置:

upstream loadbalance.com {
# sticky;
# ip_hash;
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}

重启Nginx,然后访问http://10.227.6.62/darren-loadbalance/load.do



然后刷新一下:



你发现了什么,是不是只有端口改变了,其他的都没有变化,这说明什么,你的session是不是共享了,这个时候怎么判断session存不存在,是不是根据sessionId去redis里检查,如果有就认为是存在的,不需要再重新创建了吧。

如下看一看redis是不是存储了这个session:



看到了吧,我的redis确实存了当前session的信息吧。

3) tomcat原生的基于集群的配置

这一种我还没有做实验,以后补充

4) 使用spring+redis配置

这一种我还没有做实验,以后补充,

有兴趣的可以先参考:tomcat+nginx+redis实现均衡负载、session共享

常见错误整理:

98: Address already in use

解决方案请参考:nginx启动服务提示98: Address already in use错误的解决

参考:

Nginx反向代理实现会话(session)保持的两种方式

Nginx Sticky的使用及踩过的坑(nginx-sticky-module)

NGINX:sticky模块实现基于cookie的负载均衡

Nginx+Tomcat搭建高性能负载均衡集群

Nginx简介及使用Nginx实现负载均衡的原理【通俗易懂,言简意赅】

一分钟了解负载均衡的一切

tomcat+nginx+redis实现均衡负载、session共享

Tomcat7+Redis存储Session

关于 tomcat 集群中 session 共享的三种方法

nginx第三方模块---nginx-sticky-module的使用(基于cookie的会话保持)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: