GAE即Google App Engine,还不了解什么是Google App Engine的可以阅读一下它的介绍。
GAE提供了简单实用的API和开发工具,结合已有的开发框架,Java开发人员可以很容易开发出自己的业务应用系统。本次先介绍页面部分的性能优化技巧,只需要进行简单的设置和少量的编码,即可获得不错的性能提高。文中提到的技巧已经在本博客取得验证,从后来的统计数据中可以看到,首页的处理时间从平均400ms减少到了平均26ms,性能提高了15倍!
App Engine性能优化:指定GAE的静态文件配置
在一般的httpd + tomcat的架构中,客户端对图片、css文件以及js文件等静态资源文件,会根据文件的lsat modified属性,尽量只会请求一次,只有当文件进行了更新之后,才会重新请求新的文件。
但是在GAE里面,如果你不进行静态文件的设置,默认情况下,是无法享受上面所提的好处的。下面来看看设置的文件/WEB-INF/appengine-web.xml:
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>yourappid</application>
<version>1</version>
<static-files>
<include path="/**.jpg"/>
<include path="/**.png"/>
<include path="/**.gif"/>
<include path="/**.ico"/>
<include path="/**.css"/>
<include path="/**.js"/>
</static-files>
</appengine-web-app>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
进行了上面的设置之后,你的应用可以得到较为明显的性能提升。
App Engine性能优化:利用Memcache服务进行页面缓存
GAE提供了Memcache服务,可以将经常使用到数据暂时存储在Memcache中,可以大大减少请求的处理时间,提高页面响应速度。下面提供几个代码例子,利用Servlet Filter技术,可以对经常访问的页面进行缓存操作。
CacheSingleton.java
package hover.blog.servlet;
import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheFactory;
import javax.cache.CacheManager;
import javax.servlet.ServletException;
import java.util.Map;
/**
* @author Hover
* @version 1.0
*/
public class CacheSingleton {
private static final CacheSingleton instance = new CacheSingleton();
private Cache cache;
private CacheSingleton() {
}
public static CacheSingleton getInstance() {
return instance;
}
public void init(Map props) throws ServletException {
try {
CacheFactory factory = CacheManager.getInstance().getCacheFactory();
cache = factory.createCache(props);
} catch (CacheException e) {
throw new ServletException("cache error: " + e.getMessage(), e);
}
}
public Cache getCache() {
return cache;
}
public void clear() {
if (cache != null) {
cache.clear();
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
因需要在多处地方访问Cache,因此这里使用了Singleton模式,可以在不同的Action中访问同一个Cache实例。
WebCacheFilter
WebCacheFilter.java
package hover.blog.servlet;
import javax.cache.Cache;
import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
/**
* @author Hover
* @version 1.0
*/
@SuppressWarnings("unchecked")
public class WebCacheFilter implements Filter {
public static final String PAGE_PREFIX = "/page";
public void init(FilterConfig config) throws ServletException {
CacheSingleton.getInstance().init(Collections.emptyMap());
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
Cache cache = CacheSingleton.getInstance().getCache();
if ("post".equalsIgnoreCase(request.getMethod()) || cache == null) {
chain.doFilter(servletRequest, servletResponse);
} else {
String requestPath = request.getRequestURI();
String queryString = request.getQueryString();
if (queryString != null && queryString.length() > 0) {
requestPath += "?" + queryString;
}
String cachePath = PAGE_PREFIX + requestPath;
PageInfo page = null;
try {
page = (PageInfo) cache.get(cachePath);
}
catch (Exception e) {
// type mis-match
}
if (page == null) { // on cache content
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
GenericResponseWrapper wrapper = new GenericResponseWrapper(response, byteArrayOutputStream);
chain.doFilter(request, wrapper);
wrapper.flush();
page = new PageInfo(wrapper.getStatus(), wrapper.getContentType(), wrapper.getHeaders(),
wrapper.getCookies(), byteArrayOutputStream.toByteArray());
if (page.getStatus() == HttpServletResponse.SC_OK) {
cache.put(cachePath, page);
}
}
response.setStatus(page.getStatus());
String contentType = page.getContentType();
if (contentType != null && contentType.length() > 0) {
response.setContentType(contentType);
}
for (Cookie cookie : (List) page.getCookies()) {
response.addCookie(cookie);
}
for (String[] header : (List) page.getResponseHeaders()) {
response.setHeader(header[0], header[1]);
}
response.setContentLength(page.getBody().length);
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(page.getBody());
out.flush();
}
}
public void destroy() {
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
在初始化的时候,调用CacheSingleton.init()方法,初始化Memecache的调用接口。
WebCacheFilter只处理HTTP GET请求,对后台数据的修改、删除、新增等操作,应该使用HTTP POST方式来提交数据。
下面将此Filter所用到的其他辅助类列在下面:
FilterServletOutputStream.java
package hover.blog.servlet;
import javax.servlet.ServletOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* @author Hover
* @version 1.0
*/
public class FilterServletOutputStream extends ServletOutputStream {
private OutputStream stream;
public FilterServletOutputStream(final OutputStream stream) {
this.stream = stream;
}
/**
* Writes to the stream.
*/
public void write(final int b) throws IOException {
stream.write(b);
}
/**
* Writes to the stream.
*/
public void write(final byte[] b) throws IOException {
stream.write(b);
}
/**
* Writes to the stream.
*/
public void write(final byte[] b, final int off, final int len) throws IOException {
stream.write(b, off, len);
}
}
GenericResponseWrapper.java
package hover.blog.servlet;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
/**
* @author Hover
* @version 1.0
*/
@SuppressWarnings("unchecked")
public class GenericResponseWrapper extends HttpServletResponseWrapper implements Serializable {
private static final Logger LOG = Logger.getLogger(GenericResponseWrapper.class.getName());
private int statusCode = SC_OK;
private int contentLength;
private String contentType;
private final List headers = new ArrayList();
private final List cookies = new ArrayList();
private ServletOutputStream outstr;
private PrintWriter writer;
/**
* Creates a GenericResponseWrapper
*/
public GenericResponseWrapper(final HttpServletResponse response, final OutputStream outstr) {
super(response);
this.outstr = new FilterServletOutputStream(outstr);
}
/**
* Gets the outputstream.
*/
public ServletOutputStream getOutputStream() {
return outstr;
}
/**
* Sets the status code for this response.
*/
public void setStatus(final int code) {
statusCode = code;
super.setStatus(code);
}
/**
* Send the error. If the response is not ok, most of the logic is bypassed and the error is sent raw
* Also, the content is not cached.
*
* @param i the status code
* @param string the error message
* @throws IOException
*/
public void sendError(int i, String string) throws IOException {
statusCode = i;
super.sendError(i, string);
}
/**
* Send the error. If the response is not ok, most of the logic is bypassed and the error is sent raw
* Also, the content is not cached.
*
* @param i the status code
* @throws IOException
*/
public void sendError(int i) throws IOException {
statusCode = i;
super.sendError(i);
}
/**
* Send the redirect. If the response is not ok, most of the logic is bypassed and the error is sent raw.
* Also, the content is not cached.
*
* @param string the URL to redirect to
* @throws IOException
*/
public void sendRedirect(String string) throws IOException {
statusCode = HttpServletResponse.SC_MOVED_TEMPORARILY;
super.sendRedirect(string);
}
/**
* Sets the status code for this response.
*/
public void setStatus(final int code, final String msg) {
statusCode = code;
LOG.warning("Discarding message because this method is deprecated.");
super.setStatus(code);
}
/**
* Returns the status code for this response.
*/
public int getStatus() {
return statusCode;
}
/**
* Sets the content length.
*/
public void setContentLength(final int length) {
this.contentLength = length;
super.setContentLength(length);
}
/**
* Gets the content length.
*/
public int getContentLength() {
return contentLength;
}
/**
* Sets the content type.
*/
public void setContentType(final String type) {
this.contentType = type;
super.setContentType(type);
}
/**
* Gets the content type.
*/
public String getContentType() {
return contentType;
}
/**
* Gets the print writer.
*/
public PrintWriter getWriter() throws IOException {
if (writer == null) {
writer = new PrintWriter(new OutputStreamWriter(outstr, getCharacterEncoding()), true);
}
return writer;
}
/**
* Adds a header.
*/
public void addHeader(final String name, final String value) {
final String[] header = new String[]{name, value};
headers.add(header);
super.addHeader(name, value);
}
/**
* @see #addHeader
*/
public void setHeader(final String name, final String value) {
addHeader(name, value);
}
/**
* Gets the headers.
*/
public List getHeaders() {
return headers;
}
/**
* Adds a cookie.
*/
public void addCookie(final Cookie cookie) {
cookies.add(cookie);
super.addCookie(cookie);
}
/**
* Gets all the cookies.
*/
public List getCookies() {
return cookies;
}
/**
* Flushes buffer and commits response to client.
*/
public void flushBuffer() throws IOException {
flush();
super.flushBuffer();
}
/**
* Resets the response.
*/
public void reset() {
super.reset();
cookies.clear();
headers.clear();
statusCode = SC_OK;
contentType = null;
contentLength = 0;
}
/**
* Resets the buffers.
*/
public void resetBuffer() {
super.resetBuffer();
}
/**
* Flushes all the streams for this response.
*/
public void flush() throws IOException {
if (writer != null) {
writer.flush();
}
outstr.flush();
}
}
PageInfo.java
package hover.blog.servlet;
import java.io.Serializable;
import java.util.List;
/**
* @author Hover
* @version 1.0
*/
public class PageInfo implements Serializable {
private int status;
private String contentType;
private List responseHeaders;
private List cookies;
private byte[] body;
public PageInfo() {
}
public PageInfo(int status, String contentType, List responseHeaders, List cookies, byte[] body) {
this.status = status;
this.contentType = contentType;
this.responseHeaders = responseHeaders;
this.cookies = cookies;
this.body = body;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public List getResponseHeaders() {
return responseHeaders;
}
public void setResponseHeaders(List responseHeaders) {
this.responseHeaders = responseHeaders;
}
public List getCookies() {
return cookies;
}
public void setCookies(List cookies) {
this.cookies = cookies;
}
public byte[] getBody() {
return body;
}
public void setBody(byte[] body) {
this.body = body;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
- 250.
- 251.
- 252.
- 253.
- 254.
- 255.
- 256.
- 257.
- 258.
- 259.
- 260.
- 261.
- 262.
- 263.
- 264.
- 265.
- 266.
- 267.
- 268.
- 269.
- 270.
- 271.
- 272.
- 273.
- 274.
- 275.
- 276.
- 277.
- 278.
- 279.
- 280.
- 281.
- 282.
- 283.
- 284.
- 285.
- 286.
- 287.
- 288.
- 289.
- 290.
- 291.
- 292.
- 293.
- 294.
- 295.
- 296.
- 297.
- 298.
- 299.
- 300.
- 301.
- 302.
- 303.
- 304.
- 305.
- 306.
- 307.
- 308.
- 309.
- 310.
- 311.
- 312.
- 313.
- 314.
- 315.
- 316.
- 317.
- 318.
- 319.
- 320.
- 321.
- 322.
- 323.
- 324.
- 325.
- 326.
- 327.
- 328.
- 329.
- 330.
- 331.
App Engine性能优化:在web.xml中配置WebCacheFilter
在web.xml中,配置WebCacheFilter,对经常访问的页面进行缓存。下面是我的博客的配置:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<filter>
<filter-name>webCache</filter-name>
<filter-class>hover.blog.servlet.WebCacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>webCache</filter-name>
<url-pattern>/main</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>webCache</filter-name>
<url-pattern>/blog</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>webCache</filter-name>
<url-pattern>/category</url-pattern>
</filter-mapping>
</web-app>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
App Engine性能优化:页面缓存的使用限制
WebCacheFilter会缓存整个页面的全部元素,如果页面中存在用户相关的代码,例如根据用户的身份不同而现实不同的内容的话,可能会出现不希望出现的后果。
假设你的页面中,判断如果是管理员的话,显示编辑链接:
jsp文件:
<s:if test="admin">
<a href="edit-blog?key=<s:property value="key"/>">
<s:text name="edit"/>
</a>
</s:if>
- 1.
- 2.
- 3.
- 4.
- 5.
如果管理员先访问了页面,则缓存中保存的页面中,就包含了“编辑”的链接。当另外一个普通用户访问同一个url时,从页面缓存中获得了前面管理员所看到的页面,因为,普通用户也看到了“编辑”的链接。
因此,在利用WebCacheFilter进行缓存的页面中,尽量避免太多的动态属性显示。数据的编辑、维护工作应该在专门的页面中进行。
本文来自JavaEye博客:《Google App Engine性能调优 - 页面性能优化》
【编辑推荐】