play框架使用起来(16)
1、缓存
缓存是用来避免频繁到服务器端获取数据而建立的一个存取更快的临时存储器。缓存的容量相对较小,但执行速度非常快,其主要作用为:
- 存储系统经常访问的数据。
- 存储耗时较长的计算结果。
合理地缓存数据,可以提高系统的性能。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前缀的方法是阻塞式的,会降低应用程序的性能。因此,在实际操作中需要酌情考虑,选择最佳方案。
Play只允许将少量的数据以字符串形式储存在HTTP Session中。读者可能会感到非常不适应,但这样的设计确实更优雅,因为Session本来就不应该是缓存数据的地方!
读者可能已经习惯于以下写法,将数据缓存在Session中:
httpServletRequest.getSession().put("userProducts", products);...// and then in subsequent requestsproducts = (List<Product>)httpServletRequest.getSession().get("userProducts");
但在Play中实现同样效果的方式却截然不同:
Cache.put(session.getId(), products);...// and then in subsequent requestsList<Product> products = Cache.get(session.getId(), List.class);
我们可以通过UUID获取缓存中关联用户的信息。
与Session对象不同,缓存中的内容是独立的,不会绑定任何特定的用户。
1.3 配置Memcached#
如果项目要启用Memcached,需要在application.conf中打开Memcached开关,并设置Memcached的守护进程地址:
memcached=enabledmemcached.host=127.0.0.1:11211
我们还可以指定多个守护进程地址,使之连接到同一个分布式缓存:
memcached=enabledmemcached.1.host=127.0.0.1:11211memcached.2.host=127.0.0.1:11212
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
请求头就会是原始主机产生的请求头了。通过以上这些配置,应用程序可以以更直接的方式在网上发布。
Play内置的服务器支持HTTPS协议,在产品化时同样也适用。内置服务器同时提供了对于证书的管理,包含了对原生的Java keyStore支持,以及对简单的证书和密钥文件支持。我们可以通过在application.conf配置文件中配置https.port来为Play应用打开HTTPS连接器,然后将证书放在conf目录下:
http.port=9000https.port=9443
Play支持了X509证书和keystore证书,其中X509证书必须按照如下方式命名:host.cert为证书,host.key为密钥。如果读者使用的是keystore证书,则默认的命名为certificate.jks。
使用X509证书的配置实例如下:
# X509 certificatescertificate.key.file=conf/host.keycertificate.file=conf/host.cert# 如果密钥文件是用密码保护certificate.password=secrettrustmanager.algorithm=JKS
使用keystore证书的配置实例如下:
keystore.algorithm=JKSkeystore.password=secretkeystore.file=conf/certificate.jks
以上例子均采用了默认值。我们还可以通过openssl命令生成自签名的证书。
openssl genrsa 1024 > host.keyopenssl req -new -x509 -nodes -sha1 -days 365 -key host.key > host.cert
对于使用keystore的用户,可以在application.conf文件中直接配置,生成自签名证书。配置信息如下:
# Keystore ssl.KeyManagerFactory.algorithm=SunX509trustmanager.algorithm=JKSkeystore.password=secretkeystore.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框架使用起来(16)相关推荐
- Netty网络框架学习笔记-16(心跳(heartbeat)服务源码分析)
Netty网络框架学习笔记-16(心跳(heartbeat)服务源码分析_2020.06.25) 前言: Netty 作为一个网络框架,提供了诸多功能,比如编码解码等,Netty 还提供了非常重要的一 ...
- Python爬虫之Scrapy框架系列(16)——深入剖析request和response类
目录: Request和Response类: 1. 深入剖析Request类: 利用request.meta传递参数 拓展一:FormRequest类 2. 深入剖析Response类: Reques ...
- 我的世界服务器宝石系统,[编程|娱乐]FarGem —— 全自定义宝石镶嵌框架[1.8-1.16]...
本帖最后由 chenmoand 于 2018-6-1 16:24 编辑 我说一下BUG 加入右击宝石后不小心右击到别的不应该上,,,,就会吞宝石........而且闪现宝石那个js那个不能玩, Cou ...
- layui框架实战案例(16):xm-select下拉多选插件实战记录(远程搜索、过滤、翻页、单选、提示文字)
始于 layui 的一个多选解决方案,前身 formSelects, 由于渲染速度慢, 代码冗余, 被放弃了.xm-select使用了新的开发方式, 利用preact进行渲染, 大幅度提高渲染速度, ...
- 高通WLAN框架学习(16)-- Optimized connectivity experience 优化连接体验
优化连接体验(OCE)是一种新的Wi-Fi联盟(WFA)认证,它是一组功能的集合,旨在优化扫描和连接时间,并减少无线局域网通道上的探测请求/响应流量. OCE采用快速FILS (initial lin ...
- java常用框架总结
今天想看看现在常用的框架有哪些,发现网上文章不多决定根据自己的理解写一篇文章,如有错误希望大家包涵: 1.java的5大框架.springboot都不说了,网上资料很多: 2.缓存工具:Ehcache ...
- Portlet MVC框架
Portlet MVC框架 16.1. 介绍 Spring不仅支持传统(基于Servlet)的Web开发,也支持JSR-168 Portlet开发. Portlet MVC框架尽可能多地采用Web M ...
- antvue 有赞布局_UI大全:前端UI框架集合(持续更新,当前32个)
2017-1209 ZanUI (Vue) 2017-1218 Onsen UI(Vue, React, Angular) 2017-1215 增加 Vuetify, Weex UI, Semanti ...
- win32下的OpenGL绘图环境框架
Win32下OpenGL入门 主要的步骤包括:添加opengl头文件,库文件,键盘鼠标响应,像素格式设置,opengl环境初始化,绘图变量设置,创建窗口,窗口大小改变时响应,绘制场景,源文件 1, 新 ...
- OpenGL绘制框架(Win32版)
#include <windows.h> // Windows的头文件 #include <gl\gl.h> // OpenGL32库的头文件#include <gl\g ...
最新文章
- Zabbix使用JMX监控tomcat
- 直接运行PowerShell脚本
- Simulink仿真 第五节 复用器和分路器
- springmvc教程--快速入门教程
- Redis list(列表)
- redis版本_全球首发|阿里云正式推出云数据库Redis6.0版本
- 5位院士谈科研瓶颈:必须“逼着自己在精神上愿意吃苦”
- 一男子安装lua开发环境傻逼操作,惊呆所有人
- PHP JSON文件解析并获取key、value,判断key是否存在
- h5端呼起摄像头扫描二维码并解析
- 一辆车,一年大概要花费多少钱,除了油费?
- 目瞪口呆,4款1M不到的实用软件,颠覆你的认知
- Android 抖音爱心动画,Android实现抖音心形函数
- 戴尔台式计算机usb驱动,dell服务器和电脑不支持usb2.0设备安装系统的解决方案方法...
- IMF Fintech负责人:金融科技监管体制设计的五原则
- 电商客服售前售后话术培训资料合集(共150份)
- 什么是执行计划? 怎么用?
- 求n个整数的平均值与中位数
- CS61A Lab 2
- 还没学会微服务?这份最全微服务总结送给你