非web服务器(不是web服务器)「不是web服务器的功能」

  作者简介

  本文作者伸开涛,京东团体技能研发的资深Java工程师,着名技能博客《开涛的博客》作者。

  你曾经由于摆设/上线而痛楚吗?你曾经由于要去运维那改设置而烦恼吗?在我打仗过的一些摆设/上线方式中,曾碰到过以下一些题目:

  1、程序代码和依靠都是人工上传到服务器,不是通过工具举行摆设和发布;

  2、目次布局没有规范,jar启动时通过-classpath恣意指定;

  3、fatjar,把程序代码、设置文件和依靠jar都打包到一个jar中,改设置文件太费劲;

  4、不管黑白web应用还是web应用都摆设到web容器环境,如Tomcat;

  5、web应用还必要先在服务器上安装好环境(如装Tomcat)才华摆设,想升级版本大概换个容器太难了;

  6、线上参数修改还必要找运维,痛楚。

  尚有如没有主动摆设平台,回滚到上一个版本那可真是天方夜谈;增量包而非全量包,无法自由在在的回滚;前端代码直接覆盖而非版本化,难快速回滚,出题目要整理CDN,痛楚;ngx_lua项目时不按照项目标方式摆设,在服务器上随意修改代码,导致某些服务器忘记修改大概版本不同等,排查问题太痛楚。

  尚有很多摆设中不好的方式,但是本文只关注闭环Java应用带来的长处。起首先容下应该怎样摆设应用,然后先容下什么是闭环Java应用,它的长处和怎样搭建。

  应该怎样摆设应用

  

  项目

  项目中应该包罗了全部要实行的代码、启停脚本,比如非web应用

  

  web应用

  

  打包应用后,会按照相应的目次布局构建。假如项目利用maven,可以利用maven-assembly-plugin举行按照相应的目次布局构件。

  即项目、打包的应用要按照同一的风格来实行。

  主动摆设体系

  主动摆设体系负责打包应用(比如实行mvn相应的下令即可)、抽包(从指定目次抽取要摆设的代码,如target/nonweb-example-package目次)、摆设代码(发布代码,将代码同步到宿主呆板)、启停应用(设置指定的启停脚本并调用)。

  主动摆设除了这些功能外,应该尚有如发布汗青管理(回滚)、分组管理(如差别机房差别的设置文件)、设置管理(如要修改启动/克制脚本、修改设置文件[差别机房差别的设置]、参数管理[如jvm参数等])等。

  宿主呆板

  即代码摆设到的呆板,它应该只安装最小化环境,如只必要装JDK即可,像Tomcat是不必要安装的,由应用决定利用哪个容器。

  通过增长主动摆设体系可以更好的举行项目标同一发布、管理和回滚。

  闭环Java应用

  闭环Java应用指Java代码、容器、设置文件、启停脚本等都在同一处维护,修改设置文件、修改环境参数、更改容器范例等都不必要到宿主呆板上举行更改。宿主呆板只提供根本运行环境,如仅摆设JDK环境即可,不必要摆设如Tomcat容器,必要什么容器,都是在Java应用中指定。

  如许的长处是设置文件修改、JVM参数修改、容器的选择都可以在Java应用中设置,形成闭环。

  闭环Java应用的目标重要是让Java应用能自启动,如许程序的控制权就在我们手里,而不是运维手里。而我们更懂我们的程序。

  随着微服务概念的盛行,springboot也受到各人的热捧。springboot能资助我们快速构建基于spring的应用;其能方便创建自启动应用、可以嵌入各种容器(如Tomcat、Jetty)、提供了一些starterpom用于简化设置文件、主动化设置(只必要引入相干的pom,就主动得到了某些功能)等。

  在先容springboot之前,我们看下在从前是怎么构建闭环Java应用。

  从零构建非web应用

  项目布局

  

  本示例演示了构建一个非web应用RPC服务生产者(如Dubbo服务),还可以构建如Worker范例的应用,他们本身不必要web容器,作为平凡的java应用启动即可。

  maven依靠(pom.xml)

  必要本身添加如spring-core、spring-context等相干依靠,此处就不展示了。

  打包设置(pom.xml)

  nonweb-examplepom.xml

  plugin

  groupIdorg.apache.maven.plugins/groupId

  artifactIdmaven-assembly-plugin/artifactId

  version2.6/version

  configuration

  deorsrc/assembly/assembly.xml/deor

  finalName${project.build.finalName}/finalName

  /configuration

  executions

  execution

  phasepackage/phase

  goals

  goaldirectory/goal

  /goals

  /execution

  /executions

  /plugin

  利用maven-assembly-plugin举行打包;打包设置如下:

  idpackage/id

  formats

  formatdir/format

  /formats

  includeBaseDirectoryfalse/includeBaseDirectory

  fileSets

  !--可实行文件--

  fileSet

  directorysrc/bin/directory

  outputDirectorybin/outputDirectory

  includes

  include*.bat/include

  /includes

  lineEndingdos/lineEnding

  /fileSet

  fileSet

  directorysrc/bin/directory

  outputDirectorybin/outputDirectory

  includes

  include*.sh/include

  /includes

  lineEndingunix/lineEnding

  fileMode0755/fileMode

  /fileSet

  !--classes--

  fileSet

  directory${project.build.directory}/classes/directory

  outputDirectoryclasses/outputDirectory

  /fileSet

  /fileSets

  !--依靠jar包--

  dependencySets

  dependencySet

  outputDirectorylib/outputDirectory

  excludes

  excludecom.jd:nonweb-example/exclude

  /excludes

  /dependencySet

  /dependencySets

  重要有三组设置:

  formats:打包格式,此处利用的是dir,还可以是zip、rar等;

  fileSet:拷贝文件,本示例重要有bin文件、classes文件必要拷贝;

  dependencySets:依靠jar,拷贝到lib目次;

  实行mvnpackage后形成了将得到如下布局:

  

  将该目次通过主动摆设抽包并摆设到宿主呆板即可。然后主动摆设体系实行bin下的启停脚本实行即可。

  启动类

  publicclassBootstrap{

  publicstaticvoidmain(String[]args)throwsException{

  ClassPathXmlApplicationContextctx=newClassPathXmlApplicationContext("classpath:spring-config.xml");

  ctx.registerShutdownHook();

  Thread.currentThread().join();

  }

  }

  本示例没有利用JavaConfig方式构建,直接加载spring设置文件启动Java应用。

  启动脚本

  #!/bin/sh

  echo-------------------------------------------

  echostartserver

  echo-------------------------------------------

  #设置项目代码路径

  exportCODE_HOME="/export/App/nonweb-example-startup-package"

  #日记路径

  exportLOG_PATH="/export/Logs/nonweb.example.jd.local"

  mkdir-p$LOG_PATH

  #设置依靠路径

  exportCLASSPATH="$CODE_HOME/classes:$CODE_HOME/lib/*"

  #java可实行文件位置

  export_EXECJAVA="$JAVA_HOME/bin/java"

  #JVM启动参数

  exportJAVA_OPTS="-server-Xms128m-Xmx256m-Xss256k-XX:MaxDirectMemorySize=128m"

  #启动类

  exportMAIN_CLASS=com.jd.nonweb.example.startup.Bootstrap

  $_EXECJAVA$JAVA_OPTS-classpath$CLASSPATH$MAIN_CLASS

  tail-f$LOG_PATH/stdout.log

  设置项目代码路径、日记路径、依靠路径、java实行文件路径、JVM启动参数、启动类。

  克制脚本

  #日记路径

  exportLOG_PATH="/export/Logs/nonweb.example.jd.local"

  mkdir-p$LOG_PATH

  #启动类

  exportMAIN_CLASS=com.jd.nonweb.example.startup.Bootstrap

  echo-------------------------------------------

  echostopserver

  #全部相干进程

  PIDs=`jps-l|grep$MAIN_CLASS|awk'{print$1}'`

  #克制进程

  if[-n"$PIDs"];then

  forPIDin$PIDs;do

  kill$PID

  echo"kill$PID"

  done

  fi

  #等待50秒

  foriin110;do

  PIDs=`jps-l|grep$MAIN_CLASS|awk'{print$1}'`

  if[!-n"$PIDs"];then

  echo"stopserversuccess"

  echo-------------------------------------------

  break

  fi

  echo"sleep5s"

  sleep5

  done

  #假如等待50秒还没有克制完,直接杀掉

  PIDs=`jps-l|grep$MAIN_CLASS|awk'{print$1}'`

  if[-n"$PIDs"];then

  forPIDin$PIDs;do

  kill-9$PID

  echo"kill-9$PID"

  done

  fi

  tail-fn200$LOG_PATH/stdout.log

  到此一个闭环非web应用就构建完了,启停脚本、启动类、项目代码都是同一在一处维护,并利用maven-assembly-plugin将这些打包在一起,通过主动摆设发布并实行,到达了闭环的目标。

  从零构建web应用

  项目布局

  

  maven依靠(pom.xml)

  必要本身添加如spring-core、spring-context、spring-web、spring-webmvc、velocity等相干依靠,此处就不展示了。

  打包设置(pom.xml)

  web-examplepom.xml

  plugin

  groupIdorg.apache.maven.plugins/groupId

  artifactIdmaven-assembly-plugin/artifactId

  version2.6/version

  configuration

  deorsrc/assembly/assembly.xml/deor

  finalName${project.build.finalName}/finalName

  /configuration

  executions

  execution

  phasepackage/phase

  goals

  goaldirectory/goal

  /goals

  /execution

  /executions

  /plugin

  利用maven-assembly-plugin举行打包;打包设置如下:

  idpackage/id

  formats

  formatdir/format

  /formats

  includeBaseDirectoryfalse/includeBaseDirectory

  fileSets

  fileSet

  directorysrc/bin/directory

  outputDirectorybin/outputDirectory

  includes

  include*.sh/include

  /includes

  lineEndingunix/lineEnding

  fileMode0755/fileMode

  /fileSet

  !--WEB-INF--

  fileSet

  directorysrc/main/webapp/directory

  outputDirectory/outputDirectory

  /fileSet

  !--classes--

  fileSet

  directory${project.build.directory}/classes/directory

  outputDirectoryWEB-INF/classes/outputDirectory

  /fileSet

  /fileSets

  !--依靠jar包--

  dependencySets

  dependencySet

  outputDirectoryWEB-INF/lib/outputDirectory

  excludes

  excludecom.jd:web-example/exclude

  /excludes

  /dependencySet

  /dependencySets

  重要有三组设置:

  formats:打包格式,此处利用的是dir,还可以是zip、rar等;

  fileSet:拷贝文件,本示例重要有bin文件、classes文件、webapp文件必要拷贝;

  dependencySets:依靠jar,拷贝到WEB-INFlib目次;

  实行mvnpackage后形成了将得到如下布局:

  

  打包的目次布局和平凡web布局完全一样;将该目次通过主动摆设抽包并发布到宿主呆板即可。然后主动摆设体系实行bin下的启停脚本实行即可。

  启动类

  publicclassTomcatBootstrap{

  privatestaticfinalLoggerLOG=LoggerFactory.getLogger(TomcatBootstrap.class);

  publicstaticvoidmain(String[]args)throwsException{

  //提拔性能(https://wiki.apache.org/tomcat/HowTo/FasterStartUp)

  System.setProperty("tomcat.util.scan.StandardJarScanFilter.jarsToSkip","*.jar");

  //System.setProperty("securerandom.source","file:/dev/./urandom");

  intport=Integer.parseInt(System.getProperty("server.port","8080"));

  StringcontextPath=System.getProperty("server.contextPath","");

  StringdocBase=System.getProperty("server.docBase",getDefaultDocBase());

  LOG.info("serverport:{},contextpath:{},docbase:{}",port,contextPath,docBase);

  Tomcattomcat=createTomcat(port,contextPath,docBase);

  tomcat.start();

  Runtime.getRuntime().addShutdownHook(newThread(){

  @Override

  publicvoidrun(){

  try{

非web服务器(不是web服务器) 非web服务器(不是web服务器)「不是web服务器的功能」 行业资讯

  tomcat.stop();

  }catch(LifecycleExceptione){

  LOG.error("stoptomcaterror.",e);

  }

  }

  });

  tomcat.getServer().await();

  }

  privatestaticStringgetDefaultDocBase(){

  FileclasspathDir=newFile(Thread.currentThread().getContextClassLoader().getResource(".").getFile());

  FileprojectDir=classpathDir.getParentFile().getParentFile();

  returnnewFile(projectDir,"src/main/webapp").getPath();

  }

  privatestaticTomcatcreateTomcat(intport,StringcontextPath,StringdocBase)throwsException{

  Stringtmpdir=System.getProperty("java.io.tmpdir");

  Tomcattomcat=newTomcat();

  tomcat.setBaseDir(tmpdir);

  tomcat.getHost().setAppBase(tmpdir);

  tomcat.getHost().setAutoDeploy(false);

  tomcat.getHost().setDeployOnStartup(false);

  tomcat.getEngine().setBackgroundProcessorDelay(-1);

  tomcat.setConnector(newNioConnector());

  tomcat.getConnector().setPort(port);

  tomcat.getService().addConnector(tomcat.getConnector());

  Contextcontext=tomcat.addWebapp(contextPath,docBase);

  StandardServerserver=(StandardServer)tomcat.getServer();

  //APRlibraryloader.Documentationat/docs/apr.html

非web服务器(不是web服务器) 非web服务器(不是web服务器)「不是web服务器的功能」 行业资讯

  server.addLifecycleListener(newAprLifecycleListener());

  //Preventmemoryleaksduetouseofparticularjava/javaxAPIs

  server.addLifecycleListener(newJreMemoryLeakPreventionListener());

  returntomcat;

  }

  //在这里调解参数优化

  privatestaticConnectornewNioConnector(){

  Connectorconnector=newConnector("org.apache.coyote.http11.Http11NioProtocol");

  Http11NioProtocolprotocol=(Http11NioProtocol)connector.getProtocolHandler();

  returnconnector;

  }

  }

  通过嵌入Tomcat容器启动,这种方式简直定是必要先写Tomcat的启动代码,长处也很显着:以后Tomcat的控制权在我们手中,可以随时举行切换大概优化,不必要改线上的设置文件。

  启动脚本

  #!/bin/sh

  echo-------------------------------------------

  echostartserver

  echo-------------------------------------------

  #设置项目代码路径

  exportCODE_HOME="/export/App/web-example-web-package"

  #日记路径

  exportLOG_PATH="/export/Logs/web.example.jd.local"

  mkdir-p$LOG_PATH

  #设置依靠路径

  exportCLASSPATH="$CODE_HOME/WEB-INF/classes:$CODE_HOME/WEB-INF/lib/*"

  #java可实行文件位置

  export_EXECJAVA="$JAVA_HOME/bin/java"

  #JVM启动参数

  exportJAVA_OPTS="-server-Xms128m-Xmx256m-Xss256k-XX:MaxDirectMemorySize=128m"

  #服务端端口、上下文、项目根设置

  exportSERVER_INFO="-Dserver.port=8090-Dserver.contextPath=-Dserver.docBase=$CODE_HOME"

  #启动类

  exportMAIN_CLASS=com.jd.web.example.startup.TomcatBootstrap

  $_EXECJAVA$JAVA_OPTS-classpath$CLASSPATH$SERVER_INFO$MAIN_CLASS

  tail-f$LOG_PATH/stdout.log

  设置项目代码路径、日记路径、依靠路径、java实行文件路径、JVM启动参数、启动类;相称于非web应用,多了web服务器端口、上下文、项目根路径设置。

  克制脚本

  和非web的雷同就不再重复了。

  到此一个闭环web应用就构建完了,启停脚本、启动类、项目代码都是同一在一处维护,并利用maven-assembly-plugin将这些打包在一起,通过主动摆设发布并实行。到达了闭环的目标。

  SpringBoot构建非web/web应用

  项目布局

  

  maven依靠(pom.xml)

  spring-boot-example/pom.xml继承spring-boot-starter-parent

  parent

  groupIdorg.springframework.boot/groupId

  artifactIdspring-boot-starter-parent/artifactId

  version1.4.1.BUILD-SNAPSHOT/version

  /parent

  spring-boot-starter-parent中是一些通用设置,如JDK编码、依靠管理(它又继承了spring-boot-dependencies,这里边界说了全部依靠);

  依靠

  dependency

  groupIdorg.springframework.boot/groupId

  artifactIdspring-boot-starter/artifactId

  /dependency

  dependency

  groupIdorg.springframework.boot/groupId

  artifactIdspring-boot-starter-web/artifactId

  /dependency

  dependency

  groupIdorg.springframework.boot/groupId

  artifactIdspring-boot-starter-velocity/artifactId

  /dependency

  dependency

  groupIdorg.springframework.boot/groupId

  artifactIdspring-boot-starter-log4j2/artifactId

  /dependency

  spring-boot-starter是最小化的springboot环境(spring-core、spring-context等);spring-boot-starter-web是springmvc环境,并利用Tomcat作为web容器;spring-boot-starter-velocity将主动将模板引擎设置为velocity。此处可以看到starter的长处了,必要什么功能只必要引入一个starter,相干的依靠主动添加,而且会主动设置利用该特性。

  打包设置(pom.xml)

  spring-boot-example-webpom.xml添加如下maven插件:

  plugin

  groupIdorg.springframework.boot/groupId

  artifactIdspring-boot-maven-plugin/artifactId

  /plugin

  实行mvnpackage时将得到如下fatjar:

  

  启动类

  packagecom.jd.springboot.example.web.startup;

  importorg.springframework.boot.SpringApplication;

  importorg.springframework.boot.autoconfigure.SpringBootApplication;

  importorg.springframework.context.annotation.ImportResource;

  @SpringBootApplication(scanBasePackages="com.jd.springboot.example")

  @ImportResource("classpath:spring-config.xml")

  publicclassBootstrap{

  publicstaticvoidmain(String[]args){

  SpringApplication.run(Bootstrap.class,args);

  }

  }

  @SpringBootApplication指定了要扫描的包、可以利用@ImportResource引入xml设置文件。然后可以直接作为平凡java应用启动即可,此时主动利用tomcat作为web容器启动。

  运行jar-jarspring-boot-example-1.0-SNAPSHOT.jar即可启动(META-INFMANIFEST.MF指定了Main-Class)。

  个人不太喜好fatjar的方式。可以利用maven-assembly-plugin共同来打包Java应用。项目布局如下所示:

  

  项目布局和之前的区别是多了assembly和bin。

  打包设置(pom.xml)

  spring-boot-example-webpom.xml将如下maven插件

  plugin

  groupIdorg.springframework.boot/groupId

  artifactIdspring-boot-maven-plugin/artifactId

  /plugin

  更改为assembly插件

  plugin

  groupIdorg.apache.maven.plugins/groupId

  artifactIdmaven-assembly-plugin/artifactId

  version2.6/version

  configuration

  deorsrc/assembly/assembly.xml/deor

  finalName${project.build.finalName}/finalName

  /configuration

  executions

  execution

  phasepackage/phase

  goals

  goaldirectory/goal

  /goals

  /execution

  /executions

  /plugin

  assembly.xml和“从零构建非web应用”的雷同,就不贴设置了。

  实行mvnpackage时将得到如下打包:

  

  启停脚本也是雷同的,在此也不贴设置了。到此基于springboot的非fatjar方式的自启动Java应用就构建好了。

  总结

  从零构建非web应用/web应用必要我们查找相干依靠并设置,还必要举行一些设置(Spring设置、容器设置),假如构建一个新的项目还是相对较慢的,但是在公司内各人应该都有本身的“starterpom”,因此实际构建也不会很慢。而假如没有一些项目标积聚,利用springboot可以非常轻易而且快速的就能搭建出想要的项目。利用springboot后:轻易添加依靠、启动类不消本身创建、享受到主动设置的长处等;而自带的spring-boot-maven-plugin会天生fatjar,不外可以共同maven-assembly-plugin来实现之前的方式的。

  别的因笔者地点公司利用Docker容器,一个宿主呆板只摆设一个JVM示例,示例中的启停脚本不消思量单机多JVM实例题目。

  创建闭环Java应用,可以更轻易的举行如JVM参数调优、修改容器设置文件、非web应用不必要摆设到Tomcat容器中;这是笔者想举行闭环Java应用的重要目标。

  

  长按二维码,关注“公众号”

客户评论

我要评论