<< Google App Engine for Java数据备份下载 | 首页 | Google云计算GAE开发的一个IT技术推荐应用 >>

Google App Engine性能调优 - 页面性能优化

GAE提供了简单实用的API和开发工具,结合已有的开发框架,Java开发人员可以很容易开发出自己的业务应用系统。

本次先介绍页面部分的性能优化技巧,只需要进行简单的设置和少量的编码,即可获得不错的性能提高。后续的文章

文中提到的技巧已经在本博客取得验证,从后来的统计数据中可以看到,首页的处理时间从平均400ms减少到了平均26ms,性能提高了15倍!

发表于:http://hoverblog.appspot.com/blog?key=aglob3ZlcmJsb2dyCwsSBEJsb2cYuRcM

指定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>

进行了上面的设置之后,你的应用可以得到较为明显的性能提升。

利用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;
    }
}

在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>

页面缓存的使用限制

WebCacheFilter会缓存整个页面的全部元素,如果页面中存在用户相关的代码,例如根据用户的身份不同而现实不同的内容的话,可能会出现不希望出现的后果。

假设你的页面中,判断如果是管理员的话,显示编辑链接:

jsp文件:

<s:if test="admin">
  <a href="edit-blog?key=<s:property value="key"/>">
    <s:text name="edit"/>
  </a>
</s:if>

如果管理员先访问了页面,则缓存中保存的页面中,就包含了“编辑”的链接。当另外一个普通用户访问同一个url时,从页面缓存中获得了前面管理员所看到的页面,因为,普通用户也看到了“编辑”的链接。

因此,在利用WebCacheFilter进行缓存的页面中,尽量避免太多的动态属性显示。数据的编辑、维护工作应该在专门的页面中进行。

标签 : , ,



发表评论 发送引用通报