分布式集群---nginx代理+负载均衡+Session一致性问题

前言

今天主要是从我的角度来理解分布式项目(不是分布式也能用到),nginx代理模块,负载均衡和session一致性问题。(纯属个人理解,如有问题请指出)

nginx

nginx是什么

nginx是什么,我相信既然看我博客了,那就是有点技术水平了,我也就不过多解释了。(哈哈哈)

它是一个高性能的HTTP和反向代理web服务器,当然还有其他的比如F5(性能方面稍微比nginx好点,但是人家要收钱啊!!!!),况且

nginx

网易,阿里都在用这个。

正向代理

简单的讲就是我们平时说的翻墙。

主要就是找到一个可以访问国外网站的代理服务器,我们将请求发送给代理服务器,代理服务器去访问国外的网站,然后将访问到的数据传递给我们!

它最大的特点是客户端非常明确要访问的服务器地址,服务器只清楚请求来自哪个代理服务器,而不清楚来自哪个具体的客户端;

nginx

反向代理

是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。

我们在租房子的过程中,除了有些房源需要通过中介以外,还有一些是可以直接通过房东来租的。用户直接找到房东租房的这种情况就是我们不使用代理直接访问国内的网站的情况。

还有一种情况,就是我们以为我们接触的是房东,其实有时候也有可能并非房主本人,有可能是他的亲戚、朋友,甚至是二房东。但是我们并不知道和我们沟通的并不是真正的房东。这种帮助真正的房主租房的二房东其实就是反向代理服务器。这个过程就是反向代理。

nginx

废话不多说,我们直接走代码

首先我在我的nginx中设置集群

nginx

1
2
3
4
upstream webProject{
server localhost:8090;
server localhost:8091;
}

部署了两个服务的地址端口分别是8090和8091

1
2
3
4
5
6
location / {
# root html;
# index index.html index.htm;

proxy_pass http://webProject;
}

然后在location指向了webProject。

同时,我把服务部署在8090和8091上。

nginx

nginx

nginx

nginx

可以看出,我分别在8090和8091两个服务器上部署了项目,然后我对8080端口做了一层监听,当我输入8080/login的时候,就能转到登陆8090或者8091页面,而这个就是反向代理。

nginx

nginx

这里有个坑

nginx

这里必须为

1
request.getLocalPort();

才能获取nginx反向代理的port

不能为

1
request.getServerPort();

否则就是它输入的端口号。

因为nginx 监听的端口,servlet容器监听的端口,用户访问的是nginx的,然后nginx访问容器的端口

负载均衡

为什么要做负载均衡

现在我公司做的项目都是单体项目(不支持高并发),然后部署在一个tomcat上面,并发量并不高,如果说用一个压力测试软件往同一个端口发送1000次请求(tomcat优化再怎么好估计也就700并发量),那估计tomcat就崩了,后台应该不停的报timeout的错误。

那么我们就要引入一个集群的概念,我把项目部署在两个tomcat上,然后中间通过nginx进行反向代理和负载均衡

nginx

负载均衡策略

轮询

轮询就比较简单了,因为nginx默认的负载均衡就是轮询。就是多个集群服务器或者端口轮流访问。

weight权重

权重也就在集群后面加入各种的比重。

1
2
3
4
5
upstream webProject{
# ip_hash;
server localhost:8090 weight=10;
server localhost:8091 weight=1;
}

上面就是权重,最常用的写法,在各自的server写上权重,然后代理的端口就会说10:1的出现率。

ip_hash

ip_hash的配置就更加简单了,如下代码。

1
2
3
4
5
upstream webProject{
ip_hash;
server localhost:8090 ;
server localhost:8091 ;
}
ip_hash会固定一个ip的请求,只要请求的ip的hash值没有变化,那么他就会一直访问同一个服务器。

session一致性

session不一致问题

场景:如果我登陆某个网站,登陆的时候处理请求的是服务器A,接着我要购物加入购物车,而加入购物车的请求处理是服务器B,而服务器B上没有我的session,所以就会自动退出要求重新登陆。所以这就降低了用户体验,同时也体现了在分布式架构中的session不一样问题。为了解决这个问题,我提供了以下三种解决方案。

如图:我用8080/login登陆后,点击登录后信息,显示的是登录成功,但是如果我再刷新一下页面,会发现登录失败,端口好也不一样了。

nginx
nginx

基于ip_hash负载均衡

1:修改nginx.conf文件

1
2
3
4
5
6

upstream webProject{
ip_hash;
server localhost:8090 ;
server localhost:8091 ;
}

其实这个想法就是负载均衡中,固定ip的想法,我一直保留请求同一个服务器,那么他就不可能出现session不一致问题。

其中ip_hash的底层主要有两个因素。

1:获取请求ip,然后进行hash算法得出一个固定值,只要请求ip不变,那么就不回换服务器(需满足条件2)。

2:upstream webProject中的服务器数量需要保持不变,如果改变请求将会被重定向到其他服务器上(需满足条件1)。

操作过程:nginx会获取请求ip进行hash算法,得到一个长整型的数值,然后会去计算upstream webProject中服务器的个数,并进行编号(我这里就是0,1),接着对刚刚得到的hash值进行取余操作并进行定位。

优势:

1:配置简单,无侵入性,不需要改太多代码

2:安全性高

3:只要hash上均匀的,那么多台服务器的负载时均衡的

劣势:

1:会存在单点的服务器负载高风险

2:重启服务器会造成session丢失

服务器session复制

其实就是upstream webProject中多个服务器的同步操作,让多个服务器同时记住该session。操作如下:

1:修改tomcat中server.xml中的

1
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>

2:修改项目web.xml中的标签

1
<distributable/>

原理:利用了路由器的网络ip数据传输三种方式中的组播模式(单播,组播,广播),因为我们的tomcat开通了cluster标签,那么它会把我们的服务器放到一个组内,进行同步操作。

nginx

优势:

1:侵入性小,无需修改代码。

2:能适应各种负载均衡策略

3:服务器宕机啊什么的,不会造成session丢失

劣势:

1:同步操作会有延时

2:占用资源

3:降低cpu性能

session统一redis缓存

思想:把所有集群服务器中的session,放到一个公共的redis缓存中,然后所有服务器都去这个地方取session。

1:增加redis client 和spring session依赖

1
2
3
4
5
6
7
8
9
10
11
<!-- Spring-Session+Redis实现session共享依赖 start -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>

2:修改web.xml,增加filter

1
2
3
4
5
6
7
8
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

3:配置redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

<!--Spring-Session+Redis实现session共享 redis配置-->
<bean id="redisHttpSessionConfiguration"
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="600" />
</bean>

<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="100" />
<property name="maxIdle" value="10" />
</bean>

<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
destroy-method="destroy">
<property name="hostName" value="localhost" />
<property name="port" value="6379" />
<property name="password" value="" />
<property name="timeout" value="3000" />
<property name="usePool" value="true" />
<property name="poolConfig" ref="jedisPoolConfig" />
</bean>

当我们每次请求后,所有的session都会存储在redis中了。

nginx

优点:

1:能适应各种负载均衡策略

2:无论服务器怎么样都不会造成session的丢失

3:安全性高

4:适合集群大的时候使用

5:水平扩展性高

缺点:

1:代码修改比较多,需要配置很多东西

2:增加一次网络开销

3:消耗cpu性能

总结

总体来讲还是比较简单,我们在真正部署分布式项目的时候,需要定位正在好的session一致性策略

我觉得,如果说大型分布式项目,我推荐redis做公共缓存,如果是中型的分布式架构下,推荐ip_hash,如果是小型的分布式架构,推荐session复制。

源码的git地址为:

1
git@github.com:MessiCY1994/spring-redis-session.git

其中nginx的话,就自己配吧,哈哈哈哈哈。

-------------本文结束感谢您的阅读-------------