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性能调优 - 页面性能优化》
【编辑推荐】