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>
进行了上面的设置之后,你的应用可以得到较为明显的性能提升。
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();
- }
- }
- }
因需要在多处地方访问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() {
- }
- }
在初始化的时候,调用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;
- }
- }
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>
App Engine性能优化:页面缓存的使用限制
WebCacheFilter会缓存整个页面的全部元素,如果页面中存在用户相关的代码,例如根据用户的身份不同而现实不同的内容的话,可能会出现不希望出现的后果。
假设你的页面中,判断如果是管理员的话,显示编辑链接:
jsp文件:
- <s:if test="admin">
- <a href="edit-blog?key=<s:property value="key"/>">
- <s:text name="edit"/>
- </a>
- </s:if>
如果管理员先访问了页面,则缓存中保存的页面中,就包含了“编辑”的链接。当另外一个普通用户访问同一个url时,从页面缓存中获得了前面管理员所看到的页面,因为,普通用户也看到了“编辑”的链接。
因此,在利用WebCacheFilter进行缓存的页面中,尽量避免太多的动态属性显示。数据的编辑、维护工作应该在专门的页面中进行。
本文来自JavaEye博客:《Google App Engine性能调优 - 页面性能优化》
【编辑推荐】