整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

nginx+nginx-upsync-module实现动态负载及自定义验证

、说明

nginx一般直接在配置文件里配置upstream即可实现负载均衡,但有些特定的环境下此种方式就显得有些局限性。比如后端服务器无法依据端口占用检查存活的时候;后台动态调整节点的时候;调整节点后不想修改配置文件重启nginx的时候等等。

此文的思路是将配置文件从nginx本地迁移到其他第三方服务上如etcd、consul上,然后时候拉取配置到本地。理论上说任何第三方配置中心都可以实现该功能,但需要对应的nginx模块。本文采用nginx-upsync-module,主要支持consul、etcd,本文以consul为例。

迁移配置文件还无法满足需求,还需要解决服务检测机制。这里不再以端口占用为准,而是实际访问某一个接口,查看是否返回数据,并以此为存活依据,最后通过调用consul的rest接口管理配置。

二、安装

2.1安装nginx

nginx安装参照前文《Linux下Nginx1.8安装》

需要注意的是,在安装nginx的时候需要安装nginx-upsync-module模块。

2.2安装nginx-upsync-module

打开https://github.com/weibocom/nginx-upsync-module,如果遇到github打不开,可以参照如下链接解决:https://www.php.cn/faq/445082.html

下载完成后,解压到linux目录备用。

2.3安装consul

consul的安装比较简单,这里不再赘述,可参照如下链接:https://blog.csdn.net/junaozun/article/details/90699384

三、搭建&测试

3.1搭建

本文的基础目录为:

1、nginx源码目录:/opt/server/software/nginx-1.19.1

2、nginx安装目录:/home/nginx/nginx

3、nginx-upsync-module目录:/opt/server/software/nginx-upsync-module/

确定consul正常运行后,配置nginx:

consul.conf

#user  nobody;
worker_processes  1;


#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;


#pid        logs/nginx.pid;




events {
    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;
    #tcp_nopush     on;


    #keepalive_timeout  0;
    keepalive_timeout  65;


    #gzip  on;


    upstream testconsul {
        #这个配置无用了,但删除后启动会报错,如果consul里没有配置则会调用此地址,故此设置应该设置为一个默认的可用的地址,一旦从consul拉取到数据这个配置就无用了
        server 127.0.0.1:11111;
        #### 连接consul server,获取动态upstreams,配置负载均衡信息,间隔0.5s获取配置信息,upsync_timeout配置从consul拉取上游服务器配置的超时时间;upsync_interval配置从consul拉取上游服务器配置的间隔时间;upsync_type指定使用consul配置服务器;strong_dependency配置nginx在启动时是否强制依赖配置服务器,如果配置为on,则拉取配置失败时nginx启动同样失败
        upsync 192.168.1.97:8500/v1/kv/upstreams/testconsul/  upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off;
        ### 动态获取consul server相关负载均衡配置信息持久化在硬盘,这样即使consul服务器出问题了,本地还有一个备份。
        upsync_dump_path /home/nginx/nginx/conf/servers/testconsul.conf;
        }


        server {
                listen       80 ;
                server_name  testconsul;
                charset utf8;
                location /{
                        proxy_set_header Host $host;
                        proxy_set_header X-Real-IP $remote_addr;
                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                        proxy_pass http://testconsul/HelloWord;


               }        


                access_log /home/nginx/nginx/logs/access-upsync.log;
                location /stub-status {
                        stub_status on;
                }       
                location = /upstream_show {
                    upstream_show;
                }   


          }     






    # 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.2测试

URL目录

1、负载状态:http://nginxhost:port/stub-status

2、当前负载节点列表:http://nginxhost:port/upstream_show

3、测试URL:http://nginxhost:port/HelloWord

4、consul增加节点(也可直接在UI上添加):http://consulhost:port/v1/kv/upstreams/consultest/192.168.1.22:8080

四、自定义检测机制

核心功能是根据配置检测服务是否可用,然后根据结果更新到consul上。代码比较简单,主要代码如下:

pom.xml

<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.cmgplex.aotucheck4consul</groupId>
  <artifactId>aotucheck4consul</artifactId>
  <version>1.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>


  <name>aotucheck4consul</name>
  <url>http://www.cmgplex.com</url>


  <parent>
    <groupId>com.cmgplex.hr.parent</groupId>
    <artifactId>hr-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <relativePath></relativePath>
  </parent>
  <properties>
    <snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory>
    <asciidoctor.input.directory>${project.basedir}/docs/asciidoc</asciidoctor.input.directory>
    <generated.asciidoc.directory>${project.build.directory}/asciidoc</generated.asciidoc.directory>
    <asciidoctor.html.output.directory>${project.build.directory}/asciidoc/html</asciidoctor.html.output.directory>
    <asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdf</asciidoctor.pdf.output.directory>
  </properties>


  <dependencies>
    <!--base dependency for all start -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>net.logstash.log4j</groupId>
      <artifactId>jsonevent-layout</artifactId>
      <version>1.0</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
    </dependency>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
    </dependency>
    <!--base dependency for all end -->


    <!--jetty begin -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>
    <!--jetty end -->


    <!--consul begin -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-consul-all</artifactId>
    </dependency>
    <!--consul end -->


    <!--stream rabbit begin -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>
    <!--stream rabbit end -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
    <!--feign begin -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!--feign end -->


    <!--actuator begin -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--actuator end -->




    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.46</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>javax.mail</groupId>
      <artifactId>mail</artifactId>
      <version>1.4.7</version>
    </dependency>
    <dependency>
      <groupId>com.sun.mail</groupId>
      <artifactId>javax.mail</artifactId>
    </dependency>
    <!-- consul api begin -->
    <dependency>
      <groupId>com.ecwid.consul</groupId>
      <artifactId>consul-api</artifactId>
      <version>1.4.5</version>
    </dependency>
    <!-- consul api end -->
  </dependencies>
  <build>
    <finalName>aotucheck4consul</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
        </includes>
      </resource>
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.*</include>
        </includes>
      </resource>
      <resource>
        <directory>src/main/webapp</directory>
        <targetPath>META-INF/resources</targetPath>
        <includes>
          <include>**/*.*</include>
        </includes>
      </resource>
    </resources>
  </build>
  <!-- 这里是为了方便未配置maven的情况下,找到依赖 -->
  <repositories>
    <repository>
      <id>lumi-snapshots</id>
      <name>lumi-snapshots</name>
      <url>http://repo.lumiai.top/repository/maven-snapshots/</url>
      <layout>default</layout>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>
</project>

application.properties

spring.main.allow-bean-definition-overriding=true


#the following is for cloud


spring.datasource.hikari.idle-timeout = 600000
spring.datasource.hikari.connection-timeout = 30000
spring.datasource.hikari.max-lifetime = 1800000
spring.datasource.hikari.maximum-pool-size = 20
spring.datasource.type = com.zaxxer.hikari.HikariDataSource


#这个是关于mq的配置
spring.rabbitmq.addresses=amqp://192.168.1.2:5672
spring.rabbitmq.password=guest
spring.rabbitmq.username=guest
spring.cloud.stream.default-binder=rabbit
#定时检查,每隔1分钟
job.cleanConsulDeadService.cron=1 * * * * ? 
#consul配置key默认前缀
consul.default.key.prefix=upstreams/
#服务检查配置
#----------------api服务
#要检查服务的url,不包含地址
server.check.apps.api.url=/api/b
#要检查服务的host,只能是IP,包含端口,即使是80也不能少,不然nginx那边读取不到配置会报错而导致配置不可用
server.check.apps.api.hosts=http://192.168.1.1:80,http://192.168.1.2:81
#要检查服务的method
server.check.apps.api.method=get
#暂不支持json参数
server.check.apps.api.param=get
#链接超时时间,单位毫秒
server.check.apps.api.timeout=3000
#断定服务正常的response返回code
server.check.apps.api.successcode=200,302
#----------------baidu服务
#要检查服务的url,不包含地址
server.check.apps.consultest.url=/s
#要检查服务的host,只能是IP,包含端口,即使是80也不能少,不然nginx那边读取不到配置会报错而导致配置不可用
server.check.apps.consultest.hosts=http://182.61.200.6:80
#要检查服务的method
server.check.apps.consultest.method=get
#暂不支持json参数
server.check.apps.consultest.param=wd=javahttp&tn=98012088_5_dg&ch=11
#链接超时时间,单位毫秒
server.check.apps.consultest.timeout=3000
#断定服务正常的response返回code
server.check.apps.consultest.successcode=200,302




#********************************************
#本地服务监听端口
server.port = 17000
#cpu的核数
server.undertow.io-threads=4
#预估的最佳线程数
server.undertow.worker-threads=400
spring.application.name=aotucheck4consul
spring.profiles.active=dev
#********************************************
#consul的配置
spring.cloud.consul.enabled=true
spring.cloud.consul.host=192.168.1.2
spring.cloud.consul.port=8500


#********************************************
#注册中心的配置
spring.cloud.consul.discovery.enabled=false
spring.cloud.consul.discovery.prefer-ip-address=true
spring.cloud.consul.discovery.health-check-path=/actuator/health
spring.cloud.consul.discovery.health-check-interval=10s
spring.cloud.consul.discovery.health-check-timeout=1s
spring.cloud.consul.discovery.instance-id=${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}




#是否把自己向注册中心注册,可以做纯consumer不注册自己
spring.cloud.consul.discovery.register=true


#********************************************
#配置服务的配置
spring.cloud.consul.config.enabled=true
spring.cloud.consul.config.prefix=config
spring.cloud.consul.config.profile-separator=,
spring.cloud.consul.config.default-context=application
spring.cloud.consul.config.watch.enabled=true
spring.cloud.consul.config.watch.delay=1000
spring.cloud.consul.config.watch.wait-time=3
spring.cloud.consul.config.format=properties
spring.cloud.consul.config.data-key=configuration
#禁用熔断器首次调用时强制1秒超时
hystrix.command.default.execution.timeout.enabled=false

CheckServiceScheduler.java

@Component
@EnableScheduling
public class CheckServiceScheduler {
  private static final Logger LOGGER = LoggerFactory.getLogger(CheckServiceScheduler.class);
  @Autowired
  private ICheckService checkService;


  
  @Scheduled(cron = "${job.cleanConsulDeadService.cron}")
  public void checkServer() {
    LOGGER.info("-----------------------------start checkServer Scheduler-----------------------------");
    this.checkService.check();
    LOGGER.info("-----------------------------end checkServer Scheduler-----------------------------");
  }
}




CheckServiceImpl.java
@Service
public class CheckServiceImpl implements ICheckService {
  private static final Logger LOGGER = LoggerFactory.getLogger(CheckServiceImpl.class);
  @Resource
  private ServerPropertiesConfig serverPropertiesConfig;
  @Resource
  private IConsulService consulService;
  @Value("${consul.default.key.prefix}")
  private String consulKeyPrefix;


  /*
   * (非 Javadoc) <p>Title: check</p> <p>Description: </p>
   * 
   * @return
   * 
   * @see com.cmgplex.aotucheck4consul.jobsystem.service.ICheckService#check()
   */
  @Override
  public void check() {
    LOGGER.debug("serverPropertiesConfig={}", this.serverPropertiesConfig);
    Map<String, ServerInfoVo> apps = this.serverPropertiesConfig.getApps();
    for (Entry<String, ServerInfoVo> app : apps.entrySet()) {
      String appName = app.getKey();
      LOGGER.info("appName={}", appName);
      ServerInfoVo config = app.getValue();
      LOGGER.info("config={}", config);
      List<Integer> expectCodeList = ListUtils.str2list(config.getSuccesscode(), ",");
      List<String> hostsList = ListUtils.str2list4String(config.getHosts(), ",");
      String method = config.getMethod();
      HttpMethod httpMethod = HttpMethod.valueOf(method.toUpperCase());
      String param = config.getParam();
      Integer timeout = config.getTimeout();
      hostsList.parallelStream().forEach(host -> {
        try {
          LOGGER.info("host={}", host);
          String httpurl = host + config.getUrl();
          LOGGER.info("httpurl={}", httpurl);
          boolean isSuccess = HttpTools.compareCode(expectCodeList, httpMethod, httpurl, param, timeout);
          LOGGER.info("isSuccess={}", isSuccess);
          String key = this.consulKeyPrefix + appName + "/"
              + host.replace("http://", "").replace("https://", "");
          LOGGER.info("key={}", key);
          // 如果服务可用
          if (isSuccess) {
            // 检查consul上配置是否正常
            if (!this.consulService.isKeyExist(key)) {
              // 如果不正常,则更新正常
              this.consulService.addKeyValue(key, new NginxServerConfig(2, 1, 10));
              LOGGER.info("server success,consul faild,now update consul,key={}", key);
            }
          } else {
            // 如果服务不可用
            // 检查consul上配置是否正常
            if (this.consulService.isKeyExist(key)) {
              // 如果正常,则更新为不正常
              this.consulService.deleteKey(key);
              LOGGER.info("server faild,consul success,now update consul,key={}", key);
            }
          }
        } catch (Exception e) {
          LOGGER.error(e.getMessage(), e);
        }
      });
    }
  }


}

ConsulServiceImpl.java

ntroduction

部署是指将Web应用程序(第三方WAR或您自己的自定义Web应用程序)安装到Tomcat服务器的过程。

Web应用程序部署可以在Tomcat服务器中以多种方式完成。

  • 静态;在Tomcat启动之前设置Web应用程序
  • 动态;通过直接操作已部署的Web应用程序(依赖于auto-deployment功能)或使用Tomcat Manager Web应用程序远程访问

Tomcat Manager是一个Web应用程序,可以通过HTML GUI交互使用,或以编程方式(通过基于URL的API)来部署和管理Web应用程序。

有许多方法可以执行依赖Manager Web应用程序的部署。 Apache Tomcat为Apache Ant构建工具提供任务。Apache Tomcat Maven Plugin project提供与Apache Maven的集成。还有一个名为Client Deployer的工具,可以从命令行使用它,并提供其他功能,例如编译和验证Web应用程序以及将Web应用程序打包到Web应用程序资源(WAR)文件中。

Installation

静态部署Web应用程序不需要安装,因为Tomcat提供了开箱即用的功能。使用Tomcat Manager的部署功能也不需要任何安装,尽管需要进行一些配置,详细说明在下一节。但是,如果您希望使用Tomcat客户端部署程序(TCD),则需要安装。

TCD未与Tomcat核心发行版一起打包,因此必须从“下载”区域单独下载。下载通常是标记的apache-tomcat-9.0.x-deployer.


TCD需要先安装Apache Ant 1.6.2+和Java。您需要设置指向Ant安装根目录的ANT_HOME环境值,以及指向Java安装根目录的JAVA_HOME值。此外,您应该确保Ant的ant命令,以及Java javac编译器命令可以在shell中运行。

  1. 下载TCD发行版
  2. 不需要将TCD包提取到任何现有的Tomcat安装中,它可以被提取到任何位置。
  3. Read Using the Tomcat Client Deployer

A word on Contexts

在谈论Web应用程序的部署时,Context这个概念需要被理解。 Context在Tomcat中被称为Web应用程序。

为了在Tomcat中配置一个Context ,一个Context Descriptor(Context描述)是必须的。Context Descriptor只是一个XML文件,它包含上下文的Tomcat相关配置,例如命名资源或会话管理器配置。在早期版本的Tomcat中,Context Descriptor配置的内容通常存储在Tomcat的主配置文件中server.xml。但现在不鼓励这种做法(尽管目前仍然有效)。


Context Descriptor不仅帮助Tomcat知道如何配置上下文,而且其他工具(如Tomcat Manager和TCD)通常使用这些上下文描述符来正确执行其角色。

Context Descriptor的位置是:

  1. $CATALINA_BASE/conf/[enginename]/[hostname]/[webappname].xml
  2. $CATALINA_BASE/webapps/[webappname]/META-INF/context.xml

(1)中的文件名为[webappname] .xml,但(2)中的文件名为context.xml。如果没有为Context提供Context Descriptor,Tomcat将使用默认值配置Context。

在Tomcat启动时部署

如果您对使用Tomcat Manager或TCD不感兴趣,那么您需要将Web应用程序静态部署到Tomcat,然后启动Tomcat,此类部署的位置称为appBase,它对于每个主机是指定的。你要么复制一个所谓的exploded web application到此位置,或压缩的Web应用程序资源.WAR文件。

还有当Host的deployOnStartup属性为“true”时,在Tomcat启动时应用才会部署到appBase指定的位置。

在这种情况下,Tomcat启动时将发生以下部署顺序:

  1. 将首先部署任何上下文描述符。
  2. 然后将部署未被任何上下文描述符引用的exploded web application。如果他们在appBase中有一个关联的.WAR文件,并且它比exploded web application更新,则会删除exploded web application,并从.WAR重新部署webapp。
  3. 将部署.WAR文件

在正在运行的Tomcat服务器上部署

可以将Web应用程序部署到正在运行的Tomcat服务器。

如果 Host的 autoDeploy属性为“true”,主机将尝试根据需要动态部署和更新Web应用程序,例如,如果将新的.WAR放入appBase。为此,主机需要启用后台处理,这是默认配置。

autoDeploy设置为“true”,运行Tomcat允许:

  • 部署.WAR文件复制到主机中appBase.
  • 部署已复制到主机中的exploded web application.
  • 重新部署已在提供新.WAR时从.WAR部署的Web应用程序。在这种情况下,将删除exploded web application,并再次展开.WAR。请注意,如果配置主机以便.WAR不会使用展开,unpackWARs属性设置为“false”,在这种情况下,Web应用程序将简单地重新部署为压缩存档。
  • 如果更新了/WEB-INF/web.xml文件(或定义为WatchedResource的任何其他资源),则重新加载Web应用程序。
  • 如果更新了部署Web应用程序的Context Descriptor文件,则重新部署Web应用程序。
  • 如果更新Web应用程序使用的全局或每主机上下文描述符文件,则重新部署从属Web应用程序。
  • 如果将上下文描述符文件(具有与先前部署的Web应用程序的上下文路径对应的文件名)添加到Web应用程序,则重新部署Web应用程序$CATALINA_BASE/conf/[enginename]/[hostname]/ directory.
  • 如果删除了其文档库(docBase),则取消部署Web应用程序。请注意,在Windows上,这假定启用了反锁定功能(请参阅上下文配置),否则无法删除正在运行的Web应用程序的资源。

请注意,也可以在加载程序中配置Web应用程序重新加载,在这种情况下,将跟踪已加载的类以进行更改。

使用客户端部署程序包进行部署

最后,可以使用Tomcat Client Deployer实现Web应用程序的部署。这是一个包,可用于验证,编译,压缩到.WAR,以及将Web应用程序部署到生产或开发Tomcat服务器。应该注意,此功能使用Tomcat Manager,因此目标Tomcat服务器应该运行。

假设用户熟悉Apache Ant以使用TCD。 Apache Ant是一个脚本化的构建工具。 TCD预先打包了要使用的构建脚本。只需要对Apache Ant有一定的了解(本页前面列出的安装,熟悉使用操作系统命令shell和配置环境变量)。

TCD包括Ant任务,部署前用于JSP编译的Jasper页面编译器,以及验证Web应用程序上下文描述符的任务。验证器任务(类org.apache.catalina.ant.ValidatorTask)仅允许一个参数:解压的Web应用程序的基本路径。

TCD使用解压的Web应用程序作为输入(请参阅下面使用的属性列表)。以部署程序编程部署的Web应用程序可以包括上下文描述符/META-INF/context.xml.

TCD包含一个即用型Ant脚本,具有以下目标:

  • compile(默认):编译并验证Web应用程序。这可以单独使用,也不需要运行Tomcat服务器。已编译的应用程序仅在关联的应用程序上运行Tomcat X.Y.Z服务器版本,并不保证在另一个Tomcat版本上工作,因为Jasper生成的代码取决于其运行时组件。还应该注意的是,该目标还将自动编译位于其中的任何Java源文件/WEB-INF/classesWeb应用程序的文件夹。
  • deploy:将Web应用程序(已编译或未编译)部署到Tomcat服务器。
  • undeploy:取消部署Web应用程序
  • start:启动Web应用程序
  • reload:重新加载Web应用程序
  • stop:停止Web应用程序

为了配置部署,请创建一个名为的文件deployer.properties在TCD安装目录root中。在此文件中,每行添加以下name = value对:

此外,您需要确保已为目标Tomcat Manager(TCD使用)设置了用户,否则TCD将不会使用Tomcat Manager进行身份验证,部署将失败。要执行此操作,请参阅Tomcat Manager页面。

  • build:默认情况下,使用的构建文件夹将是${build}/webapp/${path} (${build},默认情况下,指向${basedir}/build)。执行结束后compile目标,Web应用程序.WAR将位于${build}/webapp/${path}.war.
  • webapp:包含将被编译和验证的解压的Web应用程序的目录。默认情况下,该文件夹是myapp.
  • path:默认情况下,部署Web应用程序的上下文路径/myapp.
  • url:正在运行的Tomcat服务器的Tomcat Manager Web应用程序的绝对URL,将用于部署和取消部署Web应用程序。默认情况下,部署者将尝试访问在localhost上运行的Tomcat实例http://localhost:8080/manager/text.
  • username:Tomcat Manager用户名(用户应该具有manager-script的角色)
  • password:Tomcat Manager密码。

较早之前,部署 Java web 服务只是单纯使用 Tomcat 做 Web 服务器,前后端代码融合在一个工程之中。Tomcat 启动后对外提供一个端口接收和相应 http请求。随着 Nginx 得越来越流行,同时加上其优秀的反向代理和负载均衡功能,我们在线上的 Java web 通常会结合二者,即使用 Nginx + Tomcat 的方式来部署 Java web 服务。最近两年,随着微服务化和前后端工程分离思想的流行,使用 Spring Boot 和 Vue 框架进行 Java web 开发的人的人越来越多。由于前后端分离后需要解决请求跨域的问题,往往会使用 Nginx 做一层反向代理,这样可以减少一些代码风险。所以,目前主流的 Java web开发模式是:

  • 基于 Vue 等优秀的前端框架完成页面开发;
  • 使用 Spring Boot 等 java web 框完成后端服务开发;
  • 前端工程打包后是一堆静态文件,可以直接由 Nginx 进行代理访问;后端服务启动后会占用端口等待请求,Nginx 将使用反向代理功能将前端发起的 http 请求转到对应的后台服务去处理。如果在多台机器上部署了相同的服务,还可以使用 Nginx 中的负载均衡功能,将请求均匀分发到上游的服务,实现系统的高可用性。

1. 部署前端

前端框架如 Vue 打包出来往往是静态的文件 index.html 加上一个 static 目录。static 目录下有 fonts、css、js、img等静态资源目录。前端的访问是从 index.html 开始的。假设服务器上打包出的前端代码放到/root/test-web目录下,对应部署前端的配置如下:

...

http{
    server {
       # 监听8080端口
       listen 8080;     
       # 指定域名,不指定也可以
       server_name www.xxx.com;
       
       # 浏览器交互调参,打开gzip压缩、缓存等等
       gzip on;
       ...
       
       location / {
           root  /root/test-web;
           # 也可以简单使用 index index.html
           try_files $uri $uri/ /index.html;
       }
       
       # vue 页面中向后台 java 服务发送请求
       ...
    }
}
...

2. 部署java后台服务

Nginx 部署 Java Web 服务时,主要用到是 Nginx 的代理转发功能,对于不同类型的接口而言,可能会有不同的转发逻辑。如果是使用 spring cloud 这样的微服务框架,每个服务可能会部署多个或者分开部署在不同机器,在 Nginx 同样只需要使用 proxy_pass 指令将 http 请求转发到对应的上游服务上即可,同时负载均衡模块也在 java web 后台服务中用到的比较多。最后是在java web 的开发中,也常常会涉及到 websocket 协议,因此 Nginx 在部署 java web 服务时也会用到 websocket 代理转发。所以 Nginx 在部署 Java Web 服务时的基本配置大概如下:

...

http{

    server {
       # 监听8080端口
       listen 8080;     
       # 指定域名,不指定也可以
       server_name www.xxx.com;
       
       # 参数调优
       client_max_body_size 20m; 
       client_body_buffer_size 128k
       ...
       
       # 如果使用多个后台服务,可以配置负载均衡
       ...
       
       # 访前端的 vue 页面
       location / {
           ...
       }
       # vue 页面中向后台 java 服务发送请求
       location /xxxx { 
            proxy_pass http://xxxx:xx/xxx;
       }
       
       # 配置多种方向代理,不同类型接口有不同的转发方式
       ...
       
       # 如果有,则配置websocket代理
       location /xxxy {
           proxy_http_version 1.1;
           proxy_set_header Upgrade $http_upgrade;
           proxy_set_header Connection "upgrade";
           proxy_pass http://xxxxx:xx/yyy;
       }
    }   
}
...

如果涉及的服务较多, Nginx 的配置往往会拆成多个文件进行编写,这样就用到了前面提到的 include 指令。

3. 小结

总结 Nginx 部署 java 服务,主要是使用 proxy_pass 指令进行端口转发,因为都是 http 请求的转发,所以配置会比较简单。但是如果服务较多时, Nginx 需要编写多个 server 指令块或者多个 location 块去匹配不同的 URL并转发到对应的上游服务。往往大型网站使用的服务较多时,会使用 include 指令对 Nginx 的配置进行拆分,不同的配置处理不同服务的转发,这样会更简洁明了,方便网站运维人员管理和修改 Nginx 配置。