大型网站分布式架构(八)—— Tomcat集群横向拓展 + Nginx负载均衡

概况

  • 搭建Tomcat集群,通过横向拓展解决单服务器上限瓶颈
  • Nginx处理静态资源,Tomcat处理接口请求,动静分离
  • Nginx实现负载均衡,均衡Tomcat服务的并发压力

搭建Tomcat集群

1.部署架构

  • 192.168.214.150:centos6-1 —— Tomcat1
  • 192.168.214.151:centos6-2 —— Tomcat2
  • 192.168.214.152:centos6-3 —— Tomcat3

其实两台就行了,博主习惯搭建奇数台

2.首先安装单机Tomcat

详见:大型网站分布式架构(二)—— Linux下Tomcat的安装和项目部署

博主在192.168.214.150节点上首先安装了单机Tomcat

3.分发Tomcat

首先需要配置3台服务器间的ssh免密登录:基于SSH的用户名密码验证和免密登录原理

[root@centos6-1 servers]# scp -r apache-tomcat-8.0.53 root@centos6-2:$PWD
[root@centos6-1 servers]# scp -r apache-tomcat-8.0.53 root@centos6-3:$PWD

查看是否分发成功
centos6-2

[root@centos6-2 ~]# cd /export/servers/
[root@centos6-2 servers]# ll
total 1
drwxr-xr-x.  9 root root 4096 Aug 27 06:40 apache-tomcat-8.0.53

centos6-3

[root@centos6-3 ~]# cd /export/servers/
[root@centos6-3 servers]# ll
total 1
drwxr-xr-x.  9 root root 4096 Aug 27 06:40 apache-tomcat-8.0.53

更新项目

1.修改IndexController

package com.zaomianbao.appdemo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;

@Controller
public class IndexController {

    @RequestMapping("/")
    public String index(HttpServletRequest request){
        try {
            System.out.println(new Date());
            InetAddress addr = InetAddress.getLocalHost();
            String ip=addr.getHostAddress().toString(); //获取本机ip
            String host=addr.getHostName().toString(); //获取本机计算机名称
            System.out.println(ip + " " + host);
            request.setAttribute("ip",ip);
            request.setAttribute("host",host);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return "index";
    }
}

2.修改index.html

使用的是thymeleaf模板语法

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>欢迎进入枣面包的面包坊</title>
    <script type="text/javascript" src="js/nothing.js"></script>
</head>
<body>
    <h1>欢迎进入枣面包的面包坊</h1>
    <img src="img/photo.jpg"><br/>
    <span th:text="${host}" ></span><br/>
    <span th:text="${ip}" ></span>
</body>
</html>

3.修改pom.xml

之前一直没有管过项目打包后的包名,现在设置一下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.zaomianbao</groupId>
	<artifactId>appdemo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<!--<packaging>jar</packaging>-->

	<name>appdemo</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!--<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<finalName>appdemo</finalName>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

4.部署项目到Tomcat集群

上传appdemo.war到Tomcat1

[root@centos6-1 apache-tomcat-8.0.53]# cd webapps/
[root@centos6-1 webapps]# rz
rz waiting to receive.
Starting zmodem transfer.  Press Ctrl+C to cancel.
Transferring appdemo.war...
  100%   17279 KB    17279 KB/sec    00:00:01       0 Errors

分发到centos6-2的Tomcat2和centos6-3的Tomcat3

[root@centos6-1 webapps]# scp -r appdemo.war  root@centos6-2:$PWD
appdemo.war                                                                                  100%   17MB  16.9MB/s   00:00    
[root@centos6-1 webapps]# scp -r appdemo.war  root@centos6-3:$PWD
appdemo.war                                                                                  100%   17MB  16.9MB/s   00:00    
[root@centos6-1 webapps]# 

启动Tomcat集群

分别启动centos6-1、centos6-2、centos6-3的Tomcat
启动Tomcat1

[root@centos6-1 bin]# ./startup.sh

启动Tomcat2

[root@centos6-2 bin]# ./startup.sh

启动Tomcat3

[root@centos6-3 bin]# ./startup.sh

浏览器分别访问三节点Tomcat

访问Tomcat1
这里写图片描述
访问Tomcat2
这里写图片描述
访问Tomcat3
这里写图片描述

可以看到3台服务器上的Tomcat都可以访问,同时在访问我们的项目的时候,都在页面显示出了Tomcat所在服务器本地的主机名和ip;这个就是同一个项目部署到集群的不同服务器中为用户提供服务;但是到目前为止,这三台服务器还是通过不同的访问地址才能访问得到,而我们需要得到的效果是访问同一地址,3台Tomcat都同时为用户提供服务,这里就需要我们的Nginx来做反向代理,实现Tomcat集群的负载均衡

配置Nginx反向代理

修改nginx.conf文件如下:

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    #动态服务器组
    upstream zaomianbao.com {
        server 192.168.214.150:8080;
        server 192.168.214.151:8080;
        server 192.168.214.152:8080;
    }
    server {
        listen       80;
        server_name  localhost;

        #配置静态资源交给nginx处理,这里先只配置js和jpg
        location ~ .*\.(js|jpg) {
            root /export/data/nginx; #静态文件目录
            expires 30d; #缓存天数
        }
        #配置除静态资源以外的交给tomcat处理
        location / {
            proxy_pass http://zaomianbao.com;
        }

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

}

重新加载配置文件

[root@centos6-1 ~]# cd /usr/local/nginx/sbin/
[root@centos6-1 sbin]# ./nginx -s reload

浏览器访问Nginx

连续访问三次出现以下结果:
第一次
这里写图片描述
第二次
这里写图片描述
第三次
这里写图片描述

发现3次访问同一个地址,但返回的内容却又差别,因为分别是3台不同的Tomcat处理动态的接口请求并返回html文档内容,而文档内容中动态渲染了Tomcat所在主机的主机名和ip,所以我们看到了不同的内容;而静态资源只在Nginx所在的centos6-1服务器上部署了一份。到这里就充分看到了Nginx集成Tomcat实现动静分离的好处以及Nginx对Tomcat横向拓展的负载均衡的技术支持。

容错机制

我们将Tomcat3宕掉

[root@centos6-3 bin]# ./shutdown.sh

再访问Nginx,发现不影响应用的使用,只是请求分发时不再分配到已经宕机的Tomcat3上了,这里就是Nginx的upstream所实现的容错机制,这样就可以实现web服务的7*24小时不间断服务。

总结

以上架构足以解决大中型网站的高并发访问。Nginx反向代理,Tomcat横向拓展集群,Nginx处理静态内容,Tomcat处理动态接口请求,Nginx负责动态请求的负载均衡分配,Nginx容错机制实现高可用。随着集群的搭建,带来了显而易见的好处就是网站的并发能力大大提升,但是集群的产生也带来了一些难题,例如会话的问题,后面就将讨论集群模式下Session的处理。