Google App Engine性能调优 - 页面性能优化
GAE提供了简单实用的API和开发工具,结合已有的开发框架,Java开发人员可以很容易开发出自己的业务应用系统。 本次先介绍页面部分的性能优化技巧,只需要进行简单的设置和少量的编码,即可获得不错的性能提高。后续的文章 文中提到的技巧已经在本博客取得验证,从后来的统计数据中可以看到,首页的处理时间从平均400ms减少到了平均26ms,性能提高了15倍! 发表于:http://hoverblog.appspot.com/blog?key=aglob3ZlcmJsb2dyCwsSBEJsb2cYuRcM 在一般的httpd + tomcat的架构中,客户端对图片、css文件以及js文件等静态资源文件,会根据文件的lsat modified属性,尽量只会请求一次,只有当文件进行了更新之后,才会重新请求新的文件。 但是在GAE里面,如果你不进行静态文件的设置,默认情况下,是无法享受上面所提的好处的。下面来看看设置的文件/WEB-INF/appengine-web.xml: 进行了上面的设置之后,你的应用可以得到较为明显的性能提升。 GAE提供了Memcache服务,可以将经常使用到数据暂时存储在Memcache中,可以大大减少请求的处理时间,提高页面响应速度。下面提供几个代码例子,利用Servlet Filter技术,可以对经常访问的页面进行缓存操作。 因需要在多处地方访问Cache,因此这里使用了Singleton模式,可以在不同的Action中访问同一个Cache实例。 WebCacheFilter.java 在初始化的时候,调用CacheSingleton.init()方法,初始化Memecache的调用接口。 WebCacheFilter只处理HTTP GET请求,对后台数据的修改、删除、新增等操作,应该使用HTTP POST方式来提交数据。 下面将此Filter所用到的其他辅助类列在下面: FilterServletOutputStream.java GenericResponseWrapper.java PageInfo.java 在web.xml中,配置WebCacheFilter,对经常访问的页面进行缓存。下面是我的博客的配置: WebCacheFilter会缓存整个页面的全部元素,如果页面中存在用户相关的代码,例如根据用户的身份不同而现实不同的内容的话,可能会出现不希望出现的后果。 假设你的页面中,判断如果是管理员的话,显示编辑链接: 如果管理员先访问了页面,则缓存中保存的页面中,就包含了“编辑”的链接。当另外一个普通用户访问同一个url时,从页面缓存中获得了前面管理员所看到的页面,因为,普通用户也看到了“编辑”的链接。 因此,在利用WebCacheFilter进行缓存的页面中,尽量避免太多的动态属性显示。数据的编辑、维护工作应该在专门的页面中进行。指定GAE的静态文件配置
<?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>
利用Memcache服务进行页面缓存
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();
}
}
}
WebCacheFilter
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() {
}
}
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);
}
}
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();
}
}
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;
}
}
在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>
页面缓存的使用限制
jsp文件:
<s:if test="admin">
<a href="edit-blog?key=<s:property value="key"/>">
<s:text name="edit"/>
</a>
</s:if>