play框架使用起来(16)
2016-11-04 17:11
253 查看
1、缓存
缓存是用来避免频繁到服务器端获取数据而建立的一个存取更快的临时存储器。缓存的容量相对较小,但执行速度非常快,其主要作用为:
存储系统经常访问的数据。
存储耗时较长的计算结果。
合理地缓存数据,可以提高系统的性能。Play内置了缓存库,并为分布式环境提供了Memcached缓存数据的支持。
补充:
Memcached是一套开源的分布式内存对象缓存系统,它通过在内存中缓存数据和对象来减少读取数据库的次数,从而大幅度降低数据库负载。
如果项目中没有配置Memcached,Play将使用JVM堆中的独立缓存进行数据存储。但是将数据缓存在不同服务器的JVM堆中破坏了Play的share nothing原则:我们不能将应用程序运行在多个服务器上的同时,还期望数据保持一致,这样做只会导致每个程序实例都拥有各自不同的数据副本。
当我们在使用缓存时,必须明确其自身特性:缓存存在于内存中(不进行持久化),只是用于存放暂时性的数据,时间一到就会过期。因此缓存并不是一个安全的存储器,不能保证数据可以永久存在。如果发现数据在缓存中已过期,需要重新获取数据,并再次放入缓存:
play.cache.Cache类提供了一系列访问缓存的API,包含了完整的设置、替换和获取数据的方法:
操作缓存的API中有很多方法是以safe作为前缀的,如safeDelete,safeSet等。带safe前缀的方法是阻塞的,而标准方法是非阻塞的,这意味当我们执行以下程序时:
delete方法会立即返回结果,并没有等待缓存对象是否被真正地物理删除。因此,如果程序执行期间发生了错误(例如IO错误),缓存对象可能仍然存在,并没有被删除。
如果操作需要确保缓存对象被删除,可以使用safeDelete方法:
该方法是阻塞式的,并返回一个布尔值标识对象是否被成功删除。确保缓存对象被删除的标准写法如下:
注意:
带safe前缀的方法是阻塞式的,会降低应用程序的性能。因此,在实际操作中需要酌情考虑,选择最佳方案。
1.2 避免使用Session作为缓存#
Play只允许将少量的数据以字符串形式储存在HTTP Session中。读者可能会感到非常不适应,但这样的设计确实更优雅,因为Session本来就不应该是缓存数据的地方!
读者可能已经习惯于以下写法,将数据缓存在Session中:
但在Play中实现同样效果的方式却截然不同:
我们可以通过UUID获取缓存中关联用户的信息。
注意:
与Session对象不同,缓存中的内容是独立的,不会绑定任何特定的用户。
如果项目要启用Memcached,需要在application.conf中打开Memcached开关,并设置Memcached的守护进程地址:
我们还可以指定多个守护进程地址,使之连接到同一个分布式缓存:
2、产品化
首先需要给应用指定框架ID。例如,我们使用play id命令将框架ID设置为production,之后如果需要将Play框架设置为PROD模式只需在application.conf文件里进行如下配置:
在该模式下启动应用,Play会预编译所有的Java文件和模版文件。如果在这一步出现了错误,应用是不能被成功启动的,此时对源文件的修改也不再会被热编译与加载。
如果应用还在使用开发数据库(例如内存数据库db=mem或者文件数据库db=fs,),显然不能满足产品化的需求,我们必须在产品化的时候选择更加健壮的数据库引擎。下例给出通用的JDBC连接方式,并以Mysql为例:
如果应用中使用了Hibernate提供的Schema自动更新特性,我们必须在产品化时将其关闭。在产品服务器中,使用Hibernate来自动更新数据库与数据并不是可行的方式,因为这可能会导致诸如数据覆盖、丢失或是没有足够权限操纵数据表等问题的出现。
如果用户确保应用对产品环境的数据库有完整的操纵权限,并且只是初次部署(即只做数据初始化工作),也是可以使用这个特性的。针对这种情况,需要在application.conf文件中进行如下配置:
注意:
请确保只在初次发布时使用该方式,并在之后更新部署时关闭该配置,否则会造成数据覆盖、丢失等错误发生。如果没有特殊的需求,笔者和Play作者都建议在产品化时将该配置取消。
Play的密钥具有安全特性,比如在Session签名中就会使用到,因此在Play应用中请务必保证该密钥的私有性。我们可以在application.conf配置文件中通过如下配置设定密钥:
在Play中可以通过play secret命令生成随机密钥。读者在使用密钥时需要注意,如果应用需要被部署到分布式的环境,我们必须要确保所有的应用实例都具有相同的密钥。
如果我们需要将应用部署在Play自带的服务器,只需在application.conf文件中配置如下信息即可:
但是有时候我们需要将多个应用部署在同个服务器上,或是需要为多个应用实例提供负载均衡来提高伸缩性和容错性,这时我们可以使用第三方的HTTP服务器。
注意:
直接使用Play内置的服务器将会比使用其他HTTP服务器具有更好的性能。
配置lighttpd
以下这个例子将会展示如何配置lighttpd作为HTTP服务器。尽管Apache也可以做到这些,但是如果读者只需要其中的虚拟主机或负载均衡功能,lighttpd会是更轻便、更易于配置的选择。/etc/lighttpd/lighttpd.conf文件的具体配置如下:
配置Apache
以下这个例子将展示如何配置Apache httpd server作为Play应用的HTTP服务器。
Apache服务器的配置文件路径为\conf\httpd.conf。Apache配置文件中使用#进行注释,取消以下这条配置信息前的#注释(如果存在)。
然后在文件末尾添加如下信息:
利用Apache服务器全透明部署应用
如果需要更新Web应用,我们通常会关闭服务器,更新应用,最后重新启动服务器。但是这个期间造成的中断服务显然是对用户很不友好的表现。理想的情况是更新Web应用而不中断原有服务,即透明化部署应用的过程。实现该功能的原理是运行同个Play应用的两个实例,并利用HTTP服务器负载均衡。当其中某个应用实例无法提供服务时,HTTP服务器会将所有的请求切换至仍然能提供服务的另一个实例。
下面将演示如何做到这一点。为了方便演示,我们会启动同个Play应用两次,只是将其中的一个端口设置为9999,另一个端口设置为9998。
在实际中,应用很有可能处于两个不同的服务器。复制一份相同的Play应用并编辑application.conf文件,改变其中的端口配置。然后使用play start命令分别运行这两个Web应用:
接着配置Apache的负载均衡器:
在该配置中需要注意的地方是balancer://mycluster,其声明了一个负载均衡器。跟在第二个Play应用后的参数+H表明第二个Play应用处于待用状态,不过这并不影响其参与负载均衡。其他的配置细节已超出本书的范围,不再赘述。当我们需要更新应用时,首先停止第一个应用的服务:
负载均衡器会将所有的请求转向到mysuperwebapp2。这时便可以对应用mysuperwebapp1进行更新,当我们更新完成之后再次启动mysuperwebapp1:
此时我们又可以安全地更新mysuperwebapp2了(停止—更新—启动)。
Apache也提供了简单的方式来监视集群的状态,即在浏览器中转向/balancer-manager来监视当前集群。因为Play是完全无状态的,所以我们无需管理两个集群Session共享的问题。事实上,我们也可以同时运行两个以上的Play应用集群。
当Play应用与HTTP服务器运行在不同的机器上时,请求地址会被视为是来自HTTP服务器的地址。在默认情况下,当Play应用和服务器代理运行在同一台物理服务器上时,Play应用会将请求地址视为来自127.0.0.1。
代理服务器可以添加特殊的请求头来告诉被代理的Web应用当前这个请求是来自哪里的。大多数Web服务器都会添加X-Forwarded-For来完成这样的事情,其值通常是运行着Web应用的原始主机IP。如果我们在XForwardedSupport中打开转发支持,Play会将request.remoteAddress修改为运行Play的物理服务器IP而不是默认的代理服务器IP,不过为了使它正常工作我们还必须为其列出所有的代理服务器。
主机的请求头仍是不透明的,我们还需要对服务器进行一些配置。以Apache 2.x为例子,只需要在配置文件中添加如下信息:
请求头就会是原始主机产生的请求头了。通过以上这些配置,应用程序可以以更直接的方式在网上发布。
2.7 配置HTTPS#
Play内置的服务器支持HTTPS协议,在产品化时同样也适用。内置服务器同时提供了对于证书的管理,包含了对原生的Java keyStore支持,以及对简单的证书和密钥文件支持。我们可以通过在application.conf配置文件中配置https.port来为Play应用打开HTTPS连接器,然后将证书放在conf目录下:
Play支持了X509证书和keystore证书,其中X509证书必须按照如下方式命名:host.cert为证书,host.key为密钥。如果读者使用的是keystore证书,则默认的命名为certificate.jks。
使用X509证书的配置实例如下:
使用keystore证书的配置实例如下:
以上例子均采用了默认值。我们还可以通过openssl命令生成自签名的证书。
对于使用keystore的用户,可以在application.conf文件中直接配置,生成自签名证书。配置信息如下:
Python在大多数Unix系的系统中都被默认安装了。虽然Play在Windows版本中也包含了嵌入式的Python,但也不排除有无法支持Python运行环境的服务器的存在。针对这个问题,Play附带了ant配置文件build.xml,提供有限功能的部署方式。
在应用根目录,使用ant start命令运行服务器:
如果需要停止服务器,则可以使用ant stop命令:
警告:
当我们使用Play命令时,输出会被重定向到System.out。但是使用ant时,标准输出是无法访问的。所以使用这种方式部署时,我们必须为其提供Log4j配置文件。
我们也可以在环境变量中指定Play框架的路径,或者将路径直接写入应用的build.xml配置文件里。
缓存
缓存是用来避免频繁到服务器端获取数据而建立的一个存取更快的临时存储器。缓存的容量相对较小,但执行速度非常快,其主要作用为:
存储系统经常访问的数据。
存储耗时较长的计算结果。
合理地缓存数据,可以提高系统的性能。Play内置了缓存库,并为分布式环境提供了Memcached缓存数据的支持。
补充:
Memcached是一套开源的分布式内存对象缓存系统,它通过在内存中缓存数据和对象来减少读取数据库的次数,从而大幅度降低数据库负载。
如果项目中没有配置Memcached,Play将使用JVM堆中的独立缓存进行数据存储。但是将数据缓存在不同服务器的JVM堆中破坏了Play的share nothing原则:我们不能将应用程序运行在多个服务器上的同时,还期望数据保持一致,这样做只会导致每个程序实例都拥有各自不同的数据副本。
当我们在使用缓存时,必须明确其自身特性:缓存存在于内存中(不进行持久化),只是用于存放暂时性的数据,时间一到就会过期。因此缓存并不是一个安全的存储器,不能保证数据可以永久存在。如果发现数据在缓存中已过期,需要重新获取数据,并再次放入缓存:
public static void allProducts() { List<Product> products = Cache.get("products", List.class); if(products == null) { products = Product.findAll(); Cache.set("products", products, "30mn"); } render(products); }
1.1 缓存 API#
play.cache.Cache类提供了一系列访问缓存的API,包含了完整的设置、替换和获取数据的方法:public static void showProduct(String id) { Product product = Cache.get(id, Product.class); if(product == null) { product = Product.findById(id); Cache.set("product_"+id, product, "30mn"); } render(product); } public static void addProduct(String name, int price) { Product product = new Product(name, price); product.save(); showProduct(id); } public static void editProduct(String id, String name, int price) { Product product = Product.findById(id); product.name = name; product.price = price; Cache.set("product_"+id, product, "30mn"); showProduct(id); } public static void deleteProduct(String id) { Product product = Product.findById(id); product.delete(); Cache.delete("product_"+id); allProducts(); }
操作缓存的API中有很多方法是以safe作为前缀的,如safeDelete,safeSet等。带safe前缀的方法是阻塞的,而标准方法是非阻塞的,这意味当我们执行以下程序时:
Cache.delete("product_"+id);
delete方法会立即返回结果,并没有等待缓存对象是否被真正地物理删除。因此,如果程序执行期间发生了错误(例如IO错误),缓存对象可能仍然存在,并没有被删除。
如果操作需要确保缓存对象被删除,可以使用safeDelete方法:
Cache.safeDelete("product_"+id);
该方法是阻塞式的,并返回一个布尔值标识对象是否被成功删除。确保缓存对象被删除的标准写法如下:
if(!Cache.safeDelete("product_" + id)) { throw new Exception("Oops, the product has not been removed from the cache"); } ...
注意:
带safe前缀的方法是阻塞式的,会降低应用程序的性能。因此,在实际操作中需要酌情考虑,选择最佳方案。
1.2 避免使用Session作为缓存#
Play只允许将少量的数据以字符串形式储存在HTTP Session中。读者可能会感到非常不适应,但这样的设计确实更优雅,因为Session本来就不应该是缓存数据的地方!
读者可能已经习惯于以下写法,将数据缓存在Session中:
httpServletRequest.getSession().put("userProducts", products); ... // and then in subsequent requests products = (List<Product>)httpServletRequest.getSession().get("userProducts");
但在Play中实现同样效果的方式却截然不同:
Cache.put(session.getId(), products); ... // and then in subsequent requests List<Product> products = Cache.get(session.getId(), List.class);
我们可以通过UUID获取缓存中关联用户的信息。
注意:
与Session对象不同,缓存中的内容是独立的,不会绑定任何特定的用户。
1.3 配置Memcached#
如果项目要启用Memcached,需要在application.conf中打开Memcached开关,并设置Memcached的守护进程地址:memcached=enabled memcached.host=127.0.0.1:11211
我们还可以指定多个守护进程地址,使之连接到同一个分布式缓存:
memcached=enabled memcached.1.host=127.0.0.1:11211 memcached.2.host=127.0.0.1:11212
2、产品化
2.1 定义框架 ID#
首先需要给应用指定框架ID。例如,我们使用play id命令将框架ID设置为production,之后如果需要将Play框架设置为PROD模式只需在application.conf文件里进行如下配置:
%production.application.mode=prod
在该模式下启动应用,Play会预编译所有的Java文件和模版文件。如果在这一步出现了错误,应用是不能被成功启动的,此时对源文件的修改也不再会被热编译与加载。
2.2 设置数据库#
如果应用还在使用开发数据库(例如内存数据库db=mem或者文件数据库db=fs,),显然不能满足产品化的需求,我们必须在产品化的时候选择更加健壮的数据库引擎。下例给出通用的JDBC连接方式,并以Mysql为例:%production.db.url=jdbc:mysql://localhost/prod %production.db.driver=com.mysql.jdbc.Driver %production.db.user=root %production.db.pass=1515312
2.3 禁用JPA Schema自动更新#
如果应用中使用了Hibernate提供的Schema自动更新特性,我们必须在产品化时将其关闭。在产品服务器中,使用Hibernate来自动更新数据库与数据并不是可行的方式,因为这可能会导致诸如数据覆盖、丢失或是没有足够权限操纵数据表等问题的出现。如果用户确保应用对产品环境的数据库有完整的操纵权限,并且只是初次部署(即只做数据初始化工作),也是可以使用这个特性的。针对这种情况,需要在application.conf文件中进行如下配置:
%production.jpa.ddl=create
注意:
请确保只在初次发布时使用该方式,并在之后更新部署时关闭该配置,否则会造成数据覆盖、丢失等错误发生。如果没有特殊的需求,笔者和Play作者都建议在产品化时将该配置取消。
2.4 配置密钥#
Play的密钥具有安全特性,比如在Session签名中就会使用到,因此在Play应用中请务必保证该密钥的私有性。我们可以在application.conf配置文件中通过如下配置设定密钥:%production.application.secret=c12d1c59af499d20f4955d07255ed8ea333
在Play中可以通过play secret命令生成随机密钥。读者在使用密钥时需要注意,如果应用需要被部署到分布式的环境,我们必须要确保所有的应用实例都具有相同的密钥。
2.5 配置前端HTTP服务器#
如果我们需要将应用部署在Play自带的服务器,只需在application.conf文件中配置如下信息即可:%production.http.port=80
但是有时候我们需要将多个应用部署在同个服务器上,或是需要为多个应用实例提供负载均衡来提高伸缩性和容错性,这时我们可以使用第三方的HTTP服务器。
注意:
直接使用Play内置的服务器将会比使用其他HTTP服务器具有更好的性能。
配置lighttpd
以下这个例子将会展示如何配置lighttpd作为HTTP服务器。尽管Apache也可以做到这些,但是如果读者只需要其中的虚拟主机或负载均衡功能,lighttpd会是更轻便、更易于配置的选择。/etc/lighttpd/lighttpd.conf文件的具体配置如下:
server.modules = ( "mod_access", "mod_proxy", "mod_accesslog" ) … $HTTP["host"] =~ "www.myapp.com" { proxy.balance = "round-robin" proxy.server = ( "/" => ( ( "host" => "127.0.0.1", "port" => 9000 ) ) ) } $HTTP["host"] =~ "www.loadbalancedapp.com" { proxy.balance = "round-robin" proxy.server = ( "/" => ( ( "host" => "127.0.0.1", "port" => 9000 ), ( "host" => "127.0.0.1", "port" => 9001 ) ) ) }
配置Apache
以下这个例子将展示如何配置Apache httpd server作为Play应用的HTTP服务器。
Apache服务器的配置文件路径为\conf\httpd.conf。Apache配置文件中使用#进行注释,取消以下这条配置信息前的#注释(如果存在)。
LoadModule proxy_module modules/mod_proxy.so
然后在文件末尾添加如下信息:
<VirtualHost *:80> ProxyPreserveHost On ServerName www.loadbalancedapp.com ProxyPass / http://127.0.0.1:9000/ ProxyPassReverse / http://127.0.0.1:9000/ </VirtualHost>
利用Apache服务器全透明部署应用
如果需要更新Web应用,我们通常会关闭服务器,更新应用,最后重新启动服务器。但是这个期间造成的中断服务显然是对用户很不友好的表现。理想的情况是更新Web应用而不中断原有服务,即透明化部署应用的过程。实现该功能的原理是运行同个Play应用的两个实例,并利用HTTP服务器负载均衡。当其中某个应用实例无法提供服务时,HTTP服务器会将所有的请求切换至仍然能提供服务的另一个实例。
下面将演示如何做到这一点。为了方便演示,我们会启动同个Play应用两次,只是将其中的一个端口设置为9999,另一个端口设置为9998。
在实际中,应用很有可能处于两个不同的服务器。复制一份相同的Play应用并编辑application.conf文件,改变其中的端口配置。然后使用play start命令分别运行这两个Web应用:
play start mysuperwebapp
接着配置Apache的负载均衡器:
<VirtualHost mysuperwebapp.com:80> ServerName mysuperwebapp.com <Location /balancer-manager> SetHandler balancer-manager Order Deny,Allow Deny from all Allow from .mysuperwebapp.com </Location> <Proxy balancer://mycluster> BalancerMember http://localhost:9999 BalancerMember http://localhost:9998 status=+H </Proxy> <Proxy *> Order Allow,Deny Allow From All </Proxy> ProxyPreserveHost On ProxyPass /balancer-manager ! ProxyPass / balancer://mycluster/ ProxyPassReverse / http://localhost:9999/ ProxyPassReverse / http://localhost:9998/ </VirtualHost>
在该配置中需要注意的地方是balancer://mycluster,其声明了一个负载均衡器。跟在第二个Play应用后的参数+H表明第二个Play应用处于待用状态,不过这并不影响其参与负载均衡。其他的配置细节已超出本书的范围,不再赘述。当我们需要更新应用时,首先停止第一个应用的服务:
play stop mysuperwebapp1
负载均衡器会将所有的请求转向到mysuperwebapp2。这时便可以对应用mysuperwebapp1进行更新,当我们更新完成之后再次启动mysuperwebapp1:
play start mysuperwebapp1
此时我们又可以安全地更新mysuperwebapp2了(停止—更新—启动)。
Apache也提供了简单的方式来监视集群的状态,即在浏览器中转向/balancer-manager来监视当前集群。因为Play是完全无状态的,所以我们无需管理两个集群Session共享的问题。事实上,我们也可以同时运行两个以上的Play应用集群。
2.6 高级代理设置#
当Play应用与HTTP服务器运行在不同的机器上时,请求地址会被视为是来自HTTP服务器的地址。在默认情况下,当Play应用和服务器代理运行在同一台物理服务器上时,Play应用会将请求地址视为来自127.0.0.1。代理服务器可以添加特殊的请求头来告诉被代理的Web应用当前这个请求是来自哪里的。大多数Web服务器都会添加X-Forwarded-For来完成这样的事情,其值通常是运行着Web应用的原始主机IP。如果我们在XForwardedSupport中打开转发支持,Play会将request.remoteAddress修改为运行Play的物理服务器IP而不是默认的代理服务器IP,不过为了使它正常工作我们还必须为其列出所有的代理服务器。
主机的请求头仍是不透明的,我们还需要对服务器进行一些配置。以Apache 2.x为例子,只需要在配置文件中添加如下信息:
ProxyPreserveHost on
请求头就会是原始主机产生的请求头了。通过以上这些配置,应用程序可以以更直接的方式在网上发布。
2.7 配置HTTPS#
Play内置的服务器支持HTTPS协议,在产品化时同样也适用。内置服务器同时提供了对于证书的管理,包含了对原生的Java keyStore支持,以及对简单的证书和密钥文件支持。我们可以通过在application.conf配置文件中配置https.port来为Play应用打开HTTPS连接器,然后将证书放在conf目录下:
http.port=9000 https.port=9443
Play支持了X509证书和keystore证书,其中X509证书必须按照如下方式命名:host.cert为证书,host.key为密钥。如果读者使用的是keystore证书,则默认的命名为certificate.jks。
使用X509证书的配置实例如下:
# X509 certificates certificate.key.file=conf/host.key certificate.file=conf/host.cert # 如果密钥文件是用密码保护 certificate.password=secret trustmanager.algorithm=JKS
使用keystore证书的配置实例如下:
keystore.algorithm=JKS keystore.password=secret keystore.file=conf/certificate.jks
以上例子均采用了默认值。我们还可以通过openssl命令生成自签名的证书。
openssl genrsa 1024 > host.key openssl req -new -x509 -nodes -sha1 -days 365 -key host.key > host.cert
对于使用keystore的用户,可以在application.conf文件中直接配置,生成自签名证书。配置信息如下:
# Keystore ssl.KeyManagerFactory.algorithm=SunX509 trustmanager.algorithm=JKS keystore.password=secret keystore.file=certificate.jks
2.8 非Python环境下的部署#
Python在大多数Unix系的系统中都被默认安装了。虽然Play在Windows版本中也包含了嵌入式的Python,但也不排除有无法支持Python运行环境的服务器的存在。针对这个问题,Play附带了ant配置文件build.xml,提供有限功能的部署方式。在应用根目录,使用ant start命令运行服务器:
ant start -Dplay.path=/path/to/playdirectory
如果需要停止服务器,则可以使用ant stop命令:
ant stop -Dplay.path=/path/to/playdirectory
警告:
当我们使用Play命令时,输出会被重定向到System.out。但是使用ant时,标准输出是无法访问的。所以使用这种方式部署时,我们必须为其提供Log4j配置文件。
我们也可以在环境变量中指定Play框架的路径,或者将路径直接写入应用的build.xml配置文件里。
缓存
相关文章推荐
- play框架使用起来(6)
- play框架使用起来(8)
- play框架使用起来(7)
- play框架使用起来(18)
- play框架使用起来(12)
- play框架使用起来(10)
- play框架使用起来(14)-高级指南
- play框架使用起来(17)
- play框架使用起来(5)
- play框架使用起来(11)
- play框架使用起来(13)
- play框架使用起来(15)
- Play框架的验证码使用
- Yii框架官方指南系列16——使用表单:概览
- play框架用起来(3)
- 16-集合框架-05-常用对象API(集合框架-迭代器使用)
- 基于MVC4+EasyUI的Web开发框架经验总结(16)--使用云打印控件C-Lodop打印页面或套打报关运单信息
- Web自动化框架LazyUI使用手册(2)--先跑起来再说(第一个测试用例-百度搜索)
- play框架用起来(1)
- play框架2.5.6教程——使用play控制台