> For the complete documentation index, see [llms.txt](https://gitbook.wenzhihuai.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gitbook.wenzhihuai.com/ge-ren-wang-zhan/5.-xiao-ji-qun-bu-shu.md).

# 3.5 小集群部署.md

## 5.小集群部署.md

欢迎访问我的个人网站O(∩\_∩)O哈哈\~希望大佬们能给个star，个人网站网址：<http://www.wenzhihuai.com>，个人网站代码地址：<https://github.com/Zephery/newblog>。\
洋洋洒洒的买了两个服务器，用来学习分布式、集群之类的东西，整来整去，感觉分布式这种东西没人指导一下真的是太抽象了，先从网站的分布式部署一步一步学起来吧，虽然网站本身的访问量不大==。

## nginx负载均衡

一般情况下，当单实例无法支撑起用户的请求时，就需要就行扩容，部署的服务器可以分机房、分地域。而分地域会导致请求分配到太远的地区，比如：深圳的用户却访问到了北京的节点，然后还得从北京返回处理之后的数据，光是来回就至少得30ms。这部分可以通过智能DNS（就近访问）解决。而分机房，需要将请求合理的分配到不同的服务器，这部分就是我们所需要处理的。 通常，负载均衡分为硬件和软件两种，硬件层的比较牛逼,将4-7层负载均衡功能做到一个硬件里面,如F5,梭子鱼等。目前主流的软件负载均衡分为四层和七层，LVS属于四层负载均衡,工作在tcp/ip协议栈上,通过修改网络包的ip地址和端口来转发, 由于效率比七层高,一般放在架构的前端。七层的负载均衡有nginx, haproxy, apache等,虽然nginx自1.9.0版本后也开始支持四层的负载均衡，但是暂不讨论（我木有硬件条件）。下图来自[张开涛](http://jinnianshilongnian.iteye.com/)的《亿级流量网站架构核心技术》

![](https://github-images.wenzhihuai.com/images/20171018044732.png)

本站并没有那么多的服务器，目前只有两台，搭建不了那么大型的架构，就简陋的用两台服务器来模拟一下负载均衡的搭建。下图是本站的简单架构：

![](https://github-images.wenzhihuai.com/images/20171018051437.png)

其中服务器A（119.23.46.71）为深圳节点，服务器B（47.95.10.139）为北京节点，搭建Nginx之后流量是这么走的：user->A->B-A->user或者user->A->user，第一条中A将请求转发给B，然后B返回的是其运行结果的静态资源。因为这里仅仅是用来学习，所以请不要考虑因为地域导致延时的问题。。。。下面是过程。

### 1.1 Nginx的安装

可以选择tar.gz、yum、rpm安装等，这里，由于编译、nginx配置比较复杂，要是没有把握还是使用rpm来安装吧，比较简单。从<https://pkgs.org/download/nginx>可以找到最新的rpm包，然后rpm -ivh 文件，然后在命令行中输入nginx即可启动，可以使用netstat检查一下端口。

![](https://github-images.wenzhihuai.com/images/20171103062512.png)

启动后页面如下：

![](https://github-images.wenzhihuai.com/images/20171103063022.png)

记一下常用命令

```html
启动nginx，由于是采用rpm方式，所以环境变量什么的都配置好了。
[root@beijingali ~]# nginx          #启动nginx
[root@beijingali ~]# nginx -s reload         #重启nginx
[root@beijingali ~]# nginx -t           #校验nginx配置文件
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```

### 1.2 Nginx的配置

**1.2.1 负载均衡算法**

Nginx常用的算法有： （1）round-robin：轮询，nginx默认的算法，从词语上可以看出，轮流访问服务器，也可以通过weight来控制访问次数。 （2）ip\_hash：根据访客的ip，一个ip地址对应一个服务器。 （3）hash算法：hash算法常用的方式有根据uri、动态指定的consistent\_key两种。 使用hash算法的缺点是当添加服务器的时候，只有少部分的uri能够被重新分配到新的服务器。这里，本站使用的是hash uri的算法，将不同的uri分配到不同的服务器，但是由于是不同的服务器，tomcat中的session是不一致，解决办法是[tomcat session](http://blog.csdn.net/qh_java/article/details/45955923)的共享。额。。。可惜本站目前没有什么能够涉及到登陆什么session的问题。

```html
http{
    ...
    upstream backend {
        hash $uri;
        # 北京节点
        server 47.95.10.139:8080;
        # 深圳节点
        server 119.23.46.71:8080;
    }

    server {
        ...
        location / {
            root   html;
            index  index.html index.htm;
            proxy_pass http://backend;
            ...
        }
    ...
```

**1.2.2 日志格式**

之前有使用过ELK来跟踪日志，所以将日志格式化成了json的格式，这里贴一下吧

```html
    ...
    log_format main '{"@timestamp":"$time_iso8601",'
                    '"host":"$server_addr",'
                    '"clientip":"$remote_addr",'
                    '"size":$body_bytes_sent,'
                    '"responsetime":$request_time,'
                    '"upstreamtime":"$upstream_response_time",'
                    '"upstreamhost":"$upstream_addr",'
                    '"http_host":"$host",'
                    '"url":"$uri",'
                    '"xff":"$http_x_forwarded_for",'
                    '"referer":"$http_referer",'
                    '"agent":"$http_user_agent",'
                    '"status":"$status"}';
    access_log  logs/access.log  main;
    ...
```

**1.2.3 HTTP反向代理**

配置完上流服务器之后，需要配置Http的代理，将请求的端口转发到proxy\_pass设定的上流服务器，即当我们访问<http://wwww.wenzhihuai.com的时候，请求会被转发到backend中配置的服务器，此处为http://47.95.10.139:8080或者http://119.23.46.71:8080。但是，仔细注意之后，我们会发现，tomcat中的访问日志ip来源都是127.0.0.1，相当于本地访问自己的资源。由于后台中有处理ip的代码，对客户端的ip、访问uri等记录下来，所以需要设置nginx来获取用户的实际ip，参考[nginx> 配置]\(<http://blog.csdn.net/bao19901210/article/details/52537279)。参考文中的一句话：经过反向代理后，由于在客户端和web服务器之间增加了中间层，因此web服务器无法直接拿到客户端的ip，通过$remote\\_addr变量拿到的将是反向代理服务器的ip地址”。nginx是可以获得用户的真实ip的，也就是说nginx使用$remote\\_addr变量时获得的是用户的真实ip，如果我们想要在web端获得用户的真实ip，就必须在nginx这里作一个赋值操作，如下：>

```html
        location / {
            root   html;
            index  index.html index.htm;
            proxy_pass http://backend;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host;
            proxy_set_header REMOTE-HOST $remote_addr;
        }
```

**（1）proxy\_set\_header X-real-ip $remote\_addr;**\
其中这个X-real-ip是一个自定义的变量名，名字可以随意取，这样做完之后，用户的真实ip就被放在X-real-ip这个变量里了，然后，在web端可以这样获取： request.getAttribute("X-real-ip")

**（2）proxy\_set\_header X-Forwarded-For $proxy\_add\_x\_forwarded\_for;**

X-Forwarded-For：squid开发的，用于识别通过HTTP代理或负载平衡器原始IP一个连接到Web服务器的客户机地址的非rfc标准，这个不是默认有的，其经过代理转发之后，格式为client1, proxy1, proxy2，如果想通过这个变量来获取用户的ip，那么需要和$proxy\_add\_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”。

**1.2.4 HTTPS**

[HTTPS](https://baike.baidu.com/item/https/285356?fr=aladdin)（全称：Hyper Text Transfer Protocol over Secure Socket Layer），是以安全为目标的HTTP通道，简单讲是HTTP的安全版。即HTTP下加入SSL层，HTTPS的安全基础是SSL，因此加密的详细内容就需要SSL。一般情况下，能通过服务器的ssh来生成ssl证书，但是如果使用是自己的，一般浏览器（谷歌、360等）都会报证书不安全的错误，正常用户都不敢访问吧==，所以现在使用的是腾讯跟别的机构颁发的：

![](https://github-images.wenzhihuai.com/images/20171105024159.png)

首先需要下载证书，放在nginx.conf相同目录下，nginx上的配置也需要有所改变，在nginx.conf中设置listen 443 ssl;开启https。然后配置证书和私钥：

```html
        ssl_certificate 1_www.wenzhihuai.com_bundle.crt;    #主要文件路径
        ssl_certificate_key 2_www.wenzhihuai.com.key;
        ssl_session_timeout 5m;         # 超时时间
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #按照这个协议配置
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;#按照这个套件配置
        ssl_prefer_server_ciphers on;
```

至此，可以使用https来访问了。https带来的安全性（保证信息安全、识别钓鱼网站等）是http远远不能比拟的，目前大部分网站都是实现全站https，还能将http自动重定向为https，此处，需要在server中添加rewrite ^(.\*) https\://$server\_name$1 permanent;即可

**1.2.5 失败重试**

配置好了负载均衡之后，如果有一台服务器挂了怎么办？nginx中提供了可配置的服务器存活的识别，主要是通过max\_fails失败请求次数，fail\_timeout超时时间，weight为权重，下面的配置的意思是当服务器超时10秒，并失败了两次的时候，nginx将认为上游服务器不可用，将会摘掉上游服务器，fail\_timeout时间后会再次将该服务器加入到存活上游服务器列表进行重试

```html
upstream backend_server {
    server 10.23.46.71:8080 max_fails=2 fail_timeout=10s weight=1;
    server 47.95.10.139:8080 max_fails=2 fail_timeout=10s weight=1;
}
```

## session共享

分布式情况下难免会要解决session共享的问题，目前推荐的方法基本上都是使用redis，网上查找的方法目前流行的有下面四种，参考自[tomcat 集群中 session 共](http://blog.csdn.net/qh_java/article/details/45955923)： 1.使用 filter 方法存储。（推荐，因为它的服务器使用范围比较多，不仅限于tomcat ，而且实现的原理比较简单容易控制。） 2.使用 tomcat sessionmanager 方法存储。（直接配置即可） 3.使用 terracotta 服务器共享。（不知道，不了解） 4.使用spring-session。（spring的一个小项目，其原理也和第一种基本一致）

本站使用spring-session，毕竟是spring下的子项目，学习下还是挺好的。参考[Spring-Session官网](https://docs.spring.io/spring-session/docs/2.0.0.BUILD-SNAPSHOT/reference/html5/)。官方文档提供了spring-boot、spring等例子，可以参考参考。目前最新版本是2.0.0，不同版本使用方式不同，建议看官网的文档吧。

首先，添加相关依赖

```xml
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
            <version>1.3.1.RELEASE</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>${jedis.version}</version>
        </dependency>
```

新建一个session.xml，然后在spring的配置文件中添加该文件，然后在session.xml中添加：

```xml
    <!-- redis -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
    </bean>

    <bean id="jedisConnectionFactory"
          class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${host}" />
        <property name="port" value="${port}" />
        <property name="password" value="${password}" />
        <property name="timeout" value="${timeout}" />
        <property name="poolConfig" ref="jedisPoolConfig" />
        <property name="usePool" value="true" />
    </bean>

    <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory" />
    </bean>

    <!-- 将session放入redis -->
    <bean id="redisHttpSessionConfiguration"
          class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <property name="maxInactiveIntervalInSeconds" value="1800" />
    </bean>
```

然后我们需要保证servlet容器（tomcat）针对每一个请求都使用springSessionRepositoryFilter来拦截

```html
<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>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>
```

配置完成，使用RedisDesktopManager查看结果：

![](https://github-images.wenzhihuai.com/images/20171105045003.png)

### 测试：

访问<http://www.wenzhihuai.com\\>
tail -f localhost\_access\_log.2017-11-05.txt查看日志，然后清空一下当前记录

![](https://github-images.wenzhihuai.com/images/20171105050714.png)

访问技术杂谈页面，此时nginx将请求转发到119.23.46.71服务器，session为28424f91-5bc5-4bba-99ec-f725401d7318。

![](https://github-images.wenzhihuai.com/images/20171105050757.png)

点击生活笔记页面，转发到的服务器为47.95.10.139，session为28424f91-5bc5-4bba-99ec-f725401d7318，与上面相同。session已保持一致。

![](https://github-images.wenzhihuai.com/images/20171105050849.png)

**值得注意的是：同一个浏览器，在没有关闭的情况下，即使通过域名访问和ip访问得到的session是不同的。** 欢迎访问我的个人网站O(∩\_∩)O哈哈\~希望能给个star 个人网站网址：<http://www.wenzhihuai.com>\
个人网站代码地址：<https://github.com/Zephery/newblog>
