第10章、生产部署

来源:互联网 发布:什么是软件生存周期 编辑:程序博客网 时间:2024/05/16 11:58

将Lift应用程序部署到生产意味着比打包它更多,并确保您将运行模式设置production本章中的配方显示了如何为各种托管服务做这些。

您还可以在自己的服务器上安装和运行Tomcat或Jetty等容器“运行您的应用程序”中引入了容器这带来了需要了解如何安装,配置,启动,停止和管理每个容器,以及如何将其与负载平衡器或其他前端集成。这些是主题,您可以从这些来源中找到更多的内容

  • Lift维基 的部署部分
  • 托马斯·佩雷特,提升行动,第15章“部署和缩放”,曼宁出版社
  • Jason Brittain和Ian F. Darwin,Tomcat:The Definitive Guide,O'Reilly Media,Inc.
  • Tanuj KhareApache Tomcat 7 Essentials,Packt Publishing。

Lift系列包括与Lift相关的Tomcat配置选项页面

部署到CloudBees

问题

您拥有CloudBees PaaS托管环境的帐户,并且您希望在那里部署Lift应用程序。

截至2014年12月31日,CloudBees宣布正在关闭托管服务。

使用SBT package命令生成可以部署到CloudBees的WAR文件,然后使用CloudBees SDK来配置和部署应用程序。

从CloudBees“Grand Central”控制台中,在您的帐户下创建一个新的应用程序。接下来,我们假设您的帐户被调用myaccount,您的应用程序被调用myapp

为获得最佳性能,您需要确保Lift运行模式设置为“production”。从CloudBees SDK命令行执行此操作:

$ bees config:set -a myaccount / myapp run.mode =production

这将将您的CloudBees应用程序的运行模式设置为生产myaccount/myapp省略-a它将为您的整个CloudBees帐户设置它。

CloudBees将记住此设置,因此您只需要执行一次。

然后可以部署:

$ sbt package...[info] Packaging /Users/richard/myapp/target/scala-2.9.1/myapp.war......$ bees app:deploy -a myaccount/myapp ./target/scala-2.9.1/myapp.war$ sbt包...[info ]包装/Users/richard/myapp/target/scala-2.9.1/myapp.war ......$ 蜜蜂app:deploy -a myaccount / myapp ./target/scala-2.9.1/myapp.war

这将会将您的WAR文件发送到CloudBees并进行部署。在bees app:deploy命令完成后,您将看到应用程序输出的位置(URL)

如果更改配置设置,则需要重新启动应用程序才能使设置生效。部署应用程序将执行此操作,否则运行bees app:restart命令:

$ bees app:restart -a myaccount/myapp

讨论

如果要将应用程序部署到多个CloudBees实例,请注意,默认情况下,CloudBees将向每个实例轮询请求。如果您使用Lift的任何状态功能,则需要启用会话关联(粘性会话)

$ bees app:update -a myaccount/myapp stickySession=true

如果您使用的是Comet,它可以正常工作,但是CloudBees默认是启用 请求缓冲这允许CloudBees执行智能操作,例如,如果一台机器没有响应,则重新路由集群中的请求。请求缓冲的结果是长时间的彗星请求会更频繁地超时。要关闭此功能,请运行以下操作

$ bees app:update -a myaccount / myapp disableProxyBuffering=true

与运行模式设置一样,CloudBees将记住这些设置,因此您只需要设置一次。

最后,您可能希望增加JVM 永久性生成内存设置。默认情况下,应用程序为PermGen分配64 MB。要将其增加到128 MB,请运行bees app:update命令

$ bees app:update -a myaccount / myapp jvmPermSize=128

命令bees app:infobees config:list将您的应用程序报到的设置。

RDBMS配置

如果您在应用程序中使用SQL数据库,则需要配置src / main / webapp / WEB-INF / cloudbees-web.xml例如

<?xml version="1.0"?><cloudbees-web-app xmlns="http://www.cloudbees.com/xml/webapp/1"><appid>myaccount / myapp</appid><resource name="jdbc/mydb" auth="Container" type="javax.sql.DataSource">  <param name="username" value="dbuser" />  <param name="password" value="dbpassword" />  <param name="url" value="jdbc:cloudbees://mydb" />  <!-- For these connections settings, see:   http://commons.apache.org/dbcp/configuration.html  -->  <param name="maxActive" value="10" />  <param name="maxIdle" value="2" />  <param name="maxWait" value="15000" />  <param name="removeAbandoned" value="true" />  <param name="removeAbandonedTimeout" value="300" />  <param name="logAbandoned" value="true" />  <!-- Avoid idle timeouts -->  <param name="validationQuery" value="SELECT 1" />  <param name="testOnBorrow" value="true" /> </resource></cloudbees-web-app>

这是一个JNDI数据库配置,定义了一个名为CloudBees数据库的连接mydb如果JNDI名称将由Lift使用在Boot.scala中引用

DefaultConnectionIdentifier.jndiName = "jdbc/mydb"if (!DB.jndiJdbcConnAvailable_?) {  // set up alternative local database connection here}

因为JNDI设置仅在cloudbees-web.xml定义,所以只能在CloudBees环境中使用。这意味着您可以在本地开发不同的数据库,并在部署时使用您的CloudBees数据库。

主机IP和端口号

通常,您不需要知道部署的实例的公共主机名和端口号。对您的应用程序URL的请求将由CloudBees路由到特定实例。但是有些情况,特别是当你有多个实例时,你需要找到这些实例。例如,如果要从亚马逊的简单通知服务(SNS)接收邮件,则每个实例都需要在应用程序引导时为SNS提供一个直接的URL。

CloudBees提供了有关如何执行此操作的文档要获取公共主机名,您需要向http:// instance-data / latest / meta-data / public-hostname发出HTTP请求例如:

import io.Sourceval beesPublicHostname : Box[String] = tryo {  Source.fromURL("http://instance-data/latest/meta-data/public-hostname").    getLines().toStream.head}

这将Full在CloudBees环境中返回主机名,但在本地运行时将失败并返回Failure例如:

Failure(instance-data,Full(java.net.UnknownHostException: instance-data),Empty)

可以从应用程序部署的.genapps / ports文件夹中的文件的名称中找到端口号

val beesPort : Option[Int] = {  val portsDir = new File(System.getenv("PWD"), ".genapp/ports")  for {    files <- Option(portsDir.list)    port <- files.flatMap(asInt).headOption  } yield port}

java.io.File list方法返回目录中的文件名列表,但是null如果该目录不存在或者有任何IO错误,则会返回因此,我们将其包装Option成将null转换None

在本地运行,这将返回一个None,但在CloudBees上,您会看到一个Full[Int]端口号。

您可以将以下两个值放在一起:

import java.net.InetAddressval hostAndPort : String =  (beesPublicHostname openOr InetAddress.getLocalHost.getHostAddress) +  ":" + (beesPort getOrElse 8080).toString

在本地运行,hostAndPort可能会192.168.1.60:8080在CloudBees上运行,就像这样ec2-204-236-222-252.compute-1.amazonaws.com:8520

Java版本

目前,CloudBees提供的默认JVM是JDK 7,但您可以选择6,7和8.要更改默认Java虚拟机,请使用以下bees config:set命令:

$ bees config:set -a myaccount / myapp -Rjava_version =1.8

-a myaccount/myapp从命令中排除应用程序标识符将将JVM设置为帐户中所有应用程序的默认值。该 bees config:set命令将更新配置,但在应用程序更新或重新启动之前不会生效。

当通过以下命令部署或更新应用程序时,也可以更改JVM:

$ bees app:deploy -a myaccount / myapp sample.war -Rjava_version =1.6 $ bees app:update -a myaccount / myapp -Rjava_version =1.7

要确认应用程序当前运行的JVM,请使用bees config:list将显示Java版本的 命令:

$ bees config:list -a myaccount / myappRuntime Parameters:  java_version=1.6

集装箱版

CloudBees提供了几个容器:Tomcat 6.0.32(默认),Tomcat 7,JBoss 7.02,JBoss 7.1和GlassFish 3。

要更改容器,应用程序将需要重新部署,因为CloudBees为各种容器使用不同的文件配置。 因此我们使用bees app:deploy命令。以下示例更新到Tomcat 7:

$ bees app:deploy -t tomcat7 -a myaccount / myapp sample.war

JVM和容器命令可以单独运行bees app:deploy,如下所示:

$ bees app:deploy -t tomcat -a myaccount / myapp sample.war -Rjava_version =1.6

这将使用Tomcat 6.0.32和JDK 6 sample.war部署到应用myapp程序myaccount

要确定应用程序部署到哪个容器,请使用以下命令bees app:info

$ bees app:info -a myaccount/myappApplication     : myaccount/myappTitle           : myappCreated         : Wed Mar 20 11:02:40 EST 2013Status          : activeURL             : myapp.myaccount.cloudbees.netclusterSize     : 1container       : java_freecontainerType   : tomcatidleTimeout     : 21600maxMemory       : 256proxyBuffering  : falsesecurityMode    : PUBLICserverPool      : stax-global (Stax Global Pool)

ClickStart

ClickStart应用程序是快速获取应用程序的模板,并在CloudBees上自动构建和运行。Lift ClickStart在CloudBees上创建一个包含Lift 2.4应用程序的私有Git源存储库,提供MySQL数据库,创建基于Maven的Jenkins构建,并部署应用程序。所有您需要做的是为应用程序提供一个名称(不含空格)。

要访问为您创建的Git源存储库,您需要上传SSH公钥。您可以在CloudBees网站上的帐户设置的“我的密钥”部分中执行此操作。

为您创建的构建将在将更改推送到Git存储库时自动构建并将应用程序部署到CloudBees。

如果所有这些都与您想要使用的技术和服务相匹配,ClickStart是部署应用程序的好方法。或者,它为您提供了一个起点,您可以从中修改元素; 或者您可以分配CloudBees Lift模板并创建自己的模板

也可以看看

CloudBees SDK提供了配置和控制应用程序的命令行工具

CloudBees开发人员门户网站包含一个“资源”部分,提供CloudBees服务的详细信息。在其中,您将找到有关PermGen设置JVM选择servlet容器的详细信息

部署到亚马逊弹性豆串

问题

您希望在Amazon Web Services(AWS)弹性Beanstalk上运行Lift应用程序。

创建一个新的Tomcat 7 环境,使用SBT将Lift应用程序打包成WAR文件,然后将应用程序部署到您的环境中。

要创建新的环境,请访问AWS控制台,导航到Elastic Beanstalk,并选择“Apache Tomcat 7”作为您的环境。这将创建并启动默认的Beanstalk应用程序。这可能需要几分钟,但最终会报告“成功运行版本示例应用程序”。您将显示应用程序的URL(类似于http://default-environment-nsdmixm7ja.elasticbeanstalk.com),访问您所提供的URL将显示正在运行的默认Amazon应用程序。

通过运行以下命令来准备WAR文件:

$ sbt package

这将写入一个WAR文件到目标文件夹。要从AWS Beanstalk Web控制台部署此WAR文件(参见图10-1),请选择“弹性Beanstalk应用程序详细信息”下的“版本”选项卡,然后单击“上传新版本”按钮。您将获得一个对话框,您可以在其中给出版本标签,并使用“选择文件”按钮选择刚构建的WAR文件。您可以一步上传和部署,或先上传,然后在控制台中选择版本,然后点击“部署”按钮。

Beanstalk控制台将显示“环境更新...”,几分钟后,它将报告“成功运行”。您的Lift应用程序现在在Beanstalk上部署并运行。

最后一步是启用Lift的生产运行模式。从AWS Beanstalk Web控制台的环境中,按照“编辑配置”链接。将出现一个对话框,在“容器”选项卡下,添加-Drun.mode=production到“JVM命令行选项”,然后点击“应用更改”重新部署应用程序。


图10-1。AWS控制台,选择弹性Beanstalk服务

讨论

弹性Beanstalk提供了一个预构建的软件和基础设施堆栈,在这种情况下:Linux,Tomcat 7,64位“t1.micro”EC2实例,负载平衡和S3存储区。这是环境,它具有合理的默认设置。Beanstalk还提供了一种轻松部署Lift应用程序的方法。正如我们在这个配方中所看到的,你将应用程序(WAR文件)上传到Beanstalk并将其部署到环境中。

与许多云提供商一样,请记住,您希望避免本地文件存储。这样做的原因是允许在没有数据丢失的情况下终止或重新启动实例。使用Beanstalk应用程序,您确实有一个文件系统,您可以写入它,但如果图像重新启动,它将丢失。您可以获取持久的本地文件存储,例如使用Amazon弹性块存储但是你正在与平台的性质进行斗争

日志文件被写入本地文件系统。要访问它们,请从AWS控制台导航到您的环境,进入“日志”选项卡,然后点击“快照”按钮。这将需要一个日志的副本并将它们存储在S3桶中,并给你一个链接到文件内容。这是一个显示各种日志文件内容的单个文件,而catalina.out将显示您的Lift应用程序的任何输出。如果要尝试保留这些日志文件,您可以将环境配置为每小时将“日志”从“容器”选项卡的“编辑配置”下移动到S3。

Lift应用程序WAR文件存储在存储日志的同一S3桶中。从AWS控制台,您可以在S3页面下找到名称为“elastbeanstalk-us-east-1-5989673916964”的S3页面。您会注意到,通过为每个文件名添加前缀,AWS上传使您的WAR文件名唯一。如果您需要能够在S3中说明这些文件之间的区别,那么一个很好的方法是versionbuild.sbt文件触发此版本号包含在WAR 文件名中。

多个实例

Beanstalks 默认启用自动缩放也就是说,它启动了Lift应用程序的一个实例,但是如果负载增加到阈值以上,最多可能有四个实例正在运行。

如果您正在使用Lift的状态功能,则需要从环境配置的“负载平衡器”选项卡启用粘性会话。它是一个复选框,名为“启用会话粘性” - 很容易错过,但是如果您第一次看不到,该标签将滚动以显示更多选项。

使用数据库

没有什么不寻常的,你必须使用Lift和Beanstalk的数据库。但是,Beanstalk确实能够让您轻松使用Amazon的关系数据库服务(RDS)。在创建Beanstalk环境或稍后从配置选项中,您可以添加一个可以是Oracle,SQL Server或MySQL数据库的RDS实例。

MySQL选项将创建一个MySQL InnoDB数据库。数据库可以从Beanstalk访问,但不能从互联网上的其他地方访问。要更改它,请从AWS Web控制台修改RDS实例的安全组。例如,您可以允许从您的IP地址访问。

使用关联的RDS实例启动应用程序时,JVM系统属性包括数据库名称,主机,端口,用户和密码的设置。你可以将它们一起拉到Boot.scala中

Class.forName("com.mysql.jdbc.Driver")val connection = for {  host <- Box !! System.getProperty("RDS_HOSTNAME")  port <- Box !! System.getProperty("RDS_PORT")  db   <- Box !! System.getProperty("RDS_DB_NAME")  user <- Box !! System.getProperty("RDS_USERNAME")  pass <- Box !! System.getProperty("RDS_PASSWORD")} yield DriverManager.getConnection(    "jdbc:mysql://%s:%s/%s" format (host,port,db),    user, pass)

那会给你一个例子Box[Connection],如果Full你可以在一个SquerylRecord.initWithSquerylSession电话中使用(见第7章)。

或者,您可能希望通过向所有值提供默认值来保证连接:

Class.forName("com.mysql.jdbc.Driver")val connection = {  val host = System.getProperty("RDS_HOSTNAME", "localhost")  val port = System.getProperty("RDS_PORT", "3306")  val db = System.getProperty("RDS_DB_NAME", "db")  val user = System.getProperty("RDS_USERNAME", "sa")  val pass = System.getProperty("RDS_PASSWORD", "")  DriverManager.getConnection(    "jdbc:mysql://%s:%s/%s" format (host,port,db),    user, pass)}

也可以看看

亚马逊提供了截图的演示,展示了如何创建Beanstalk应用程序。

Elastic Beanstalk,van Vliet et al。(O'Reilly)介绍了Beanstalk基础架构的细节,如何使用Eclipse,实现持续集成,以及如何破解实例(例如,使用Nginx作为Beanstalk的前端)。

“使用AWS弹性Beanstalk配置数据库”的Amazon文档更详细地描述了RDS设置。

部署到Heroku

问题

您希望将Lift应用程序部署到Heroku云平台上的帐户中。

将Lift应用程序打包为WAR文件,并使用Heroku deploy插件来发送和运行应用程序。这将为您提供在Tomcat 7下运行的应用程序。任何人都可以使用此方法来部署应用程序,但Heroku仅为Enterprise Java客户提供支持。

该食谱分三个步骤完成:一次性设置; 部署WAR; 以及您的Lift应用程序的配置以进行生产性能。

如果还没有这样做,请下载并安装Heroku命令行工具(“Toolbelt”)并使用您的Heroku凭据登录并上传SSH密钥:

$ heroku loginEnter your Heroku credentials.Email: you@example.orgPassword (typing will be hidden):Found the following SSH public keys:1) github.pub2) id_rsa.pubWhich would you like to use with your Heroku account? 2Uploading SSH public key ~/.ssh/id_rsa.pub... doneAuthentication successful.

安装部署插件:

$ heroku plugins:install https://github.com/heroku/heroku-deployInstalling heroku-deploy... done

通过一次性设置完成,您可以在Heroku上创建一个应用程序。这里我们没有指定一个名字,所以我们将给出一个随机的名称“glacial-waters-6292”,我们将在整个食谱中使用:

$ heroku createCreating glacial-waters-6292... done, stack is cedarhttp://glacial-waters-6292.herokuapp.com/ |git@heroku.com:glacial-waters-6292.git

在部署之前,我们将Lift运行模式设置为生产。这是通过config:set命令完成的首先检查当前设置JAVA_OPTS,然后通过添加修改选项-Drun.mode=production

$ heroku config:get JAVA_OPTS --app glacial-waters-6292-Xmx384m -Xss512k -XX:+ UseCompressedOops$ heroku config:set JAVA_OPTS =“ -  Drun.mode = production -Xmx384m -Xss512k  -XX:+ UseCompressedOops“--app glacial-waters-6292

我们可以通过将应用程序打包为WAR文件,然后运行Heroku deploy:war命令来部署到Heroku 

$ sbt package....[info] Packaging target/scala-2.9.1/myapp-0.0.1.war .......$ heroku deploy:war --war target/scala-2.9.1/myapp-0.0.1.war  --app glacial-waters-6292Uploading target/scala-2.9.1/myapp-0.0.1.war............doneDeploying to glacial-waters-6292.........doneCreated release v6

您的Lift应用程序现在在Heroku上运行。

讨论

有关HerokuLift应用的几个重要评论。首先,请注意,不支持会话关联。如果部署到多个这就意味着DYNOS(Heroku的术语为实例),也没有协调过该请求到哪个服务器。因此,您将无法使用Lift的状态功能,并希望将其关闭(“运行无状态”描述如何执行此操作)。

其次,如果您使用的是LiftComet功能,则在Boot.scala中进行调整,以便在Heroku环境中更好地工作

LiftRules.cometRequestTimeout = Full(25)

此设置控制Lift在测试彗星连接之前等待的时间。由于Heroku在30秒后终止连接,所以我们正在将Lift的默认值替换为120秒,持续25秒。虽然Lift从此恢复,用户体验可能是在与页面交互时看到延迟。

要注意的第三个重要的一点是,每天都会重新启动该动力。另外,如果你只运行一个web dyno,它将在一个小时的闲置后空闲。您可以通过拖延应用程序日志来看到这种情况:

$ heroku logs -t --app glacial-waters-6292...2012-12-31T11:31:39+00:00 heroku[web.1]: Idling2012-12-31T11:31:41+00:00 heroku[web.1]: Stopping all processes with SIGTERM2012-12-31T11:31:43+00:00 heroku[web.1]: Process exited with status 1432012-12-31T11:31:43+00:00 heroku[web.1]: State changed from up to down

任何访问Lift应用程序的人都将导致Heroku联合您的应用程序。

请注意,应用程序已停止SIGTERM这是一个发送到一个进程的Unix信号,在这种情况下JVM要求它停止。不幸的是,Heroku上的Tomcat应用程序没有使用此信号来请求Lift关闭。这可能对您没有什么影响,但是如果您有外部资源要发布到关闭时执行的其他操作,则需要注册JVM的关闭钩子。

例如,如果您在Heroku上运行,则可以将其添加到Boot.scala中

Runtime.getRuntime().addShutdownHook(new Thread {  override def run() {    println("Shutdown hook being called")    // Do useful clean up here  }})

不要指望在关机时能够做很多事情。Heroku允许大约10秒钟后,发出JVM之后才能杀死JVM SIGTERM

可能更通用的方法是使用Lift的卸载钩进行清理(请参见“提升关闭时运行代码”),然后在Heroku发送信号终止时安排挂钩:

Runtime.getRuntime().addShutdownHook(new Thread {  override def run() {    LiftRules.unloadHooks.toList.foreach{ f => tryo { f() } }  }})

这种处理SIGTERM可能是一个惊喜,但是如果我们看看应用程序在Heroku上的运行方式,事情变得更加清晰。dyno是资源分配(512 MB内存),允许任意命令运行。正在运行的命令是启动“webapp runner”包的Java进程。你可以通过两种方式看到这一点。首先,如果你将shell绑定到你的dyno,你会看到一个WAR文件以及一个JAR文件:

$ heroku run bash --app glacial-waters-6292Running `bash` attached to terminal... up, run.8802~ $ lsProcfile  myapp-0.0.1.war  webapp-runner-7.0.29.3.jar

其次,通过查看执行的进程:

$ heroku ps --app glacial-waters-6292=== web: `${PRE_JAVA}java ${JAVA_OPTS} -jar webapp-runner-7.0.29.3.jar --port ${PORT} ${WEBAPP_RUNNER_OPTS} myapp-0.0.1.war`web.1: up 2013/01/01 22:37:35 (~ 31s ago

这里我们看到一个Java进程执行一个名为webapp-runner- 7.0.29.3.jar的JAR文件,它将 WAR文件作为参数传递。这与您可能会更加熟悉的Tomcat catalina.sh脚本不同,而是这个启动程序由于它没有注册处理程序来处理SIGTERM,所以如果我们需要在关机期间释放任何资源。

所有这一切意味着如果您要以不同的方式启动Lift应用程序,您可以。您需要包装一个适当的容器(例如Jetty或Tomcat),并main为Heroku 提供一种方法来调用。这有时被称为无容器部署

如果您不是Heroku Enterprise Java客户,并且您对deploy:war插件的不受支持的性质感到不舒服,那么现在您可以以支持的方式了解您需要执行的操作:提供main启动应用程序并侦听连接的方法。“See Also”部分提供了如何做到这一点的指针。

数据库访问在Heroku

Heroku不限制您可以从Lift应用程序连接到哪些数据库,但是通过将免费数据库附加到您创建的应用程序,他们尝试使其PostgreSQL服务变得更加容易。

您可以通过运行pg命令找出是否有数据库

$ heroku pg --app glacial-waters-6292=== HEROKU_POSTGRESQL_BLACK_URL (DATABASE_URL)Plan:        DevStatus:      availableConnections: 0PG Version:  9.1.6Created:     2012-12-31 10:02 UTCData Size:   5.9 MBTables:      0Rows:        0/10000 (In compliance)Fork/Follow: Unsupported

数据库的URL作为DATABASE_URL环境变量提供给Lift 应用程序。它将具有如下这样的值:

postgres:// gghetjutddgr:RNC_lINakkk899HHYEFUppwG@ec2-54-243-230-119.compute-1。 amazonaws.com:5432/d44nsahps11hda

此URL包含用户名,密码,主机和数据库名称,但需要被操纵以供JDBC使用。为此,您可以在Boot.scala中包含以下内容

 Box !! System.getenv("DATABASE_URL") match {  case Full(url) => initHerokuDb(url)  case _ => // configure local database perhaps}def initHerokuDb(dbInfo: String) {  Class.forName("org.postgresql.Driver")  // Extract credentials from Heroku database URL:  val dbUri = new URI(dbInfo)  val Array(user, pass) = dbUri.getUserInfo.split(":")  // Construct JDBC connection string from the URI:  def connection = DriverManager.getConnection(    "jdbc:postgresql://" + dbUri.getHost + ':' + dbUri.getPort +      dbUri.getPath, user, pass)  SquerylRecord.initWithSquerylSession(    Session.create(connection, new PostgreSqlAdapter))}

在这里,我们正在测试DATABASE_URL环境变量的存在,这表明我们在Heroku环境中。我们可以提取要使用的连接信息Session.create我们还需要完成“配置Squeryl和记录”addAround描述的通常配置

为了运行,build.sbt需要适当的依赖关系Record和PostgreSQL:

..."postgresql" % "postgresql" % "9.1-901.jdbc4","net.liftweb" %% "lift-record" % liftVersion,"net.liftweb" %% "lift-squeryl-record" % liftVersion,...

有了这一点,您的Lift应用程序可以使用Heroku数据库。您也可以从shell访问数据库,例如:

$ pg:psql --app glacial-waters-6292psql (9.1.4, server 9.1.6)SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)Type "help" for help.d44nsahps11hda=> \dNo relations found.d44nsahps11hda=> \q$

要通过Heroku环境之外的JDBC工具进行访问,您需要包含强制SSL的参数。例如:

jdbc:postgresql://ec2-54-243-230-119.compute-1.amazonaws.com:5432 / d44nsahps11hda?username = gghetjutddgr&password = RNC_lINakkk899HHYEFUppwG&ssl = true&sslfactory = org.postgresql.ssl.NonValidatingFactory

也可以看看

Heroku ScalaJava文章,Dynos和Dyno Manager可以了解更多的这个食谱描述的细节。

JVM关闭钩子在JDK文档中有描述

Heroku的无容器部署指南使用Maven打包应用程序。还有一个来自Matthew Henderson模板SBT项目,包括一个JettyLauncher类。

请求超时描述Heroku如何处理Comet长时间轮询。

跨多个服务器分发彗星

问题

您可以使用Lift的Comet支持,并希望跨多个服务器运行以增加冗余或处理增加的负载。

使用发布/订阅(pubsub)模型将每个服务器连接到主题,并将Comet消息传递到可以广播到所有应用程序一部分的服务器的主题。

您可以使用各种技术来完成此操作,例如数据库,消息系统和演员系统。对于这个配方,我们将使用RabbitMQ消息服务,但是在“另请参阅”部分中也有使用CouchDB和Amazon的简单通知服务的示例。

无论技术如何,原理如图10-2所示将一个Lift应用程序发起的彗星事件发送到服务进行重新分配。这项服务的责任(图中标有“主题”),以确保所有参与的升降机应用程序都收到该事件。


图10-2。源自一台服务器的彗星事件通过主题进行分发

第一步是下载并安装RabbitMQ然后启动服务器:

$ ./sbin/rabbitmq-server -detatched

这个命令会在开始时产生各种消息,但最终会说:“代理运行”。

我们将用于演示pubsub模式的Lift应用程序是Simply Lift中描述的实时聊天应用程序 第一个修改是将Lift模块与RabbitMQ进行通信。这是build.sbtlibraryDependencies中的一行除外

"net.liftmodules" %% "amqp_2.5" % "1.3"

AMQP代表高级消息队列协议,RabbitMQ会话协议。AMQP模块提供抽象的演员发送和接收消息,我们将实现这些演员,RemoteSend并且RemoteReceiver

package code.cometimport net.liftmodules.amqp._import com.rabbitmq.client._object Rabbit {  val factory = new ConnectionFactory {    import ConnectionFactory._    setHost("127.0.0.1")    setPort(DEFAULT_AMQP_PORT)  }  val exchange = "lift.chat"  val routing = ""  val durable = true  val autoAck = false  object RemoteSend extends AMQPSender[String](factory, exchange, routing) {    def configure(channel: Channel) =      channel.exchangeDeclare(exchange, "fanout", durable)  }  object RemoteReceiver extends AMQPDispatcher[String](factory) {    def configure(channel: Channel) = {      channel.exchangeDeclare(exchange, "fanout", durable)      val queueName = channel.queueDeclare().getQueue()      channel.queueBind(queueName, exchange, routing)      channel.basicConsume(queueName, autoAck,        new SerializedConsumer(channel, this) )    }  }}

此代码正在建立RemoteSend,并通过RabbitMQ RemoteReceiverString值进行序列化接下来的“讨论”部分将探讨该代码。

为了利用这一点并通过RabbitMQ路由Comet消息,我们需要进行两个更改。Boot.scala中,我们需要开始监听RabbitMQ的消息:

RemoteReceiver ! AMQPAddListener(ChatServer)

这是ChatServer作为AMQP消息的侦听器附加RemoteReceiver

最后的改变就是ChatServer它本身。常规行为ChatServerString从客户端接收消息并更新连接到Comet服务器的所有屏幕:

override def lowPriority = {  case s : String => msgs :+= s; updateListeners()}

通过RabbitMQ路由消息的更改是将任何String从客户端重定向到RabbitMQ,并处理来自RabbitMQ的任何AMQP消息,并更新所有客户端:

override def lowPriority = {  case AMQPMessage(s: String) => msgs :+= s; updateListeners()  case s: String => RemoteSend ! AMQPMessage(s)}

此更改意味着我们所有的Comet聊天消息都会发送到RabbitMQ,并将其分发到我们的Lift应用程序的所有实例,并且所有的实例都会收到消息作为AMQPMessage实例,并正常更新聊天客户端。

讨论

要在本地运行Lift应用程序的多个实例,您需要正常启动SBT,然后在另一个终端中重新启动,但需要使用不同的端口号:

$ sbt...> set port in container.Configuration := 9090[info] Reapplying settings...[info] Set current project to RabbitMQ Chat (in build file:rabbitmq_chat/)> container:start

然后,您可以访问http://127.0.0.1:8080的一个应用程序,另一个在http://127.0.0.1:9090

在示例代码中,您可以看到AMQPSender[T]AMQPDispatcher[T]为我们处理大部分工作,并提供一些配置。RemoteSend我们正在配置AMQPSender使用String消息和使用调用交换机的情况下lift.chat在RabbitMQ中,交换是我们发送消息的实体,交换机有责任传递消息。在这种情况下,交换是一个扇出(一种简单的主题),每个用户收到发送到交换机的任何消息的副本。这显然是我们希望将聊天消息发送到聊天应用程序的所有连接的Lift实例。

RemoteReceiver也被配置成接收String消息,虽然配置稍长。在这里,以及指示要使用的交换,我们为我们的Lift实例声明一个临时队列队列是RabbitMQ发送消息的地方,我们在这里说的是每个接收器都有自己的队列。扇出交换将确保发送到交换机的任何消息都被放入每个队列中。队列具有由RabbitMQ分配的随机名称,当我们断开连接时它被破坏。

最后一部分RemoteReceiver是指定消费方式。默认的行为RemoteSend是串行化对象,所以我们通过使用SerializedConsumerAMQP模块提供在接收方中镜像它

要查看RabbitMQ的行为,安装管理Web控制台很有用。从您安装RabbitMQ的目录:

$ ./sbin/rabbitmq-plugins enable rabbitmq_management

访问管理Web界面http://127.0.0.1:15672/并登录。默认用户名和密码为“guest”。

需要在开发过程中运行RabbitMQ(或其他类型的pubsub解决方案)可能不方便。在这种情况下,您可以简单地在Boot.scala初始化服务

if (Props.productionMode)  RemoteReceiver ! AMQPAddListener(ChatServer)

而在聊天服务器中,只发送给本地客户端:

override def lowPriority = {  case AMQPMessage(s: String) => msgs :+= s; updateListeners()  case s: String =>    if (Props.productionMode) RemoteSend ! AMQPMessage(s)    else { msgs :+= s; updateListeners() }  }

请注意,Props.productionModetrue对的运行模式ProductionStagingPilot

也可以看看

Lift聊天示例在Simply Lift中描述此配方中使用的源代码位于GitHub上

LiftAMQP模块的可以在GitHub上找到。

如果您想了解更多关于RabbitMQ的信息,请查看教程或Alvaro Videla和Jason JW Williams的“ RabbitMQ在行动:每个人的分布式消息传递”(Manning Publications Co.)。

Diego Medina已经使用CouchDB实现了一个分布式的Comet解决方案,并在博客文章中进行了描述

亚马逊的简单通知服务(SNS)是一个迷人的设施,因此也可以用于实现这种模式。您可以在GitHub上找到SNS的Lift模块

0 0
原创粉丝点击