一个简单 java 项目的优化过程(未完...)

523 查看

第一次写 java 项目,用的 netty5.0,也没啥经验,前期开发比较紧所以以实现功能为主,下面记录自己的一些性能优化笔记。
以某接口为例,该接口是 feed 流,里面包含的信息有:

30条 feed 信息
每条 feed 下的最近的5条评论,和该评论总数
每条 feed 属主的用户信息
每条 feed 属主和浏览者的好友关系
每条 feed 属主和浏览者的地理位置距离

先不做压测了,直接在 chrome 里打开看响应耗时,
请求时间可以忽略不计,等待时间和文档下载时间都太长了

后端缓存优化

将上面所有的信息都在 redis 缓存后的耗时

服务器优化

后端服务我直接用 netty 做服务和 api,没有设置压缩导致 api 文本很大
调试一个类似大小的之前项目的 api 做了压缩处理的,发现文档大小变化很大,如下图 124k 被压缩到了9.4k
返回的头信息

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 25 Aug 2015 04:14:54 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Content-Encoding: gzip
Vary: Accept-Encoding

而我目前却还没做压缩处理,
直接请求 netty 返回的头信息

HTTP/1.1 200 OK
content-length: 126792
connection: keep-alive

走前端 nginx 转发到后端 netty 返回的头信息

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 25 Aug 2015 04:21:39 GMT
Content-Length: 126792
Connection: keep-alive

添加transfer-encoding头信息

response.headers().set(TRANSFER_ENCODING,HttpHeaderValues.CHUNKED);

虽然不能缩短请求时间,但是能在接受到第一个 chunked 包就可以开始解析 api 文档了。理论上可以缩短了客户端加载的时间(不对请拍砖)解释:http://blog.haohtml.com/archives/4777

我尝试着在 netty 里添加了压缩

response.headers().set(CONTENT_ENCODING, HttpHeaderValues.GZIP);

但是不好使,找了半天也没找到资料,最后只能把压缩转 nginx 里做的处理。
nginx 里的 gzip 配置如下:

  gzip on;
  gzip_min_length  1k;
  gzip_buffers     4 4k;
  gzip_http_version 1.0;
  gzip_comp_level 2;
  gzip_types       text/plain application/x-javascript text/css application/xml text/javascript;
  gzip_vary on;

在不声明文档类型的时候,通过 nginx 反向代理之后的 http 返回头信息如下:

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 25 Aug 2015 07:49:39 GMT
Transfer-Encoding: chunked
Connection: keep-alive

在 netty 里添加了文件类型声明之后

response.headers().set(CONTENT_TYPE,HttpHeaderValues.TEXT_PLAIN);

再看 nginx 返回的信息则有压缩了

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 25 Aug 2015 07:55:57 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Content-Encoding: gzip


发现文本有了很大的压缩,下载时间大大减少了(在我做笔记的时候api 的文档大小因为数据变化而减少了3k)

数据库连接的优化

今天在来公司的路上想到,我们大 PHP 在使用数据库的时候基本上是使用了就不用关(在析构函数里释放数据库连接),在同一个请求里面使用的数据库连接是同一个,在请求完毕的时候就释放该数据库连接。

做了如下测试,模拟一个 api 需要做30次数据库查询

for ($i=1; $i < 30; $i++) { 
    $sql = "select 1 from user limit where id=".$i;
    $res = $db->query($sql);
    echo "查询了".$i."次\n";
}
zhoumengkang$ mysql -uroot -pzmkzmk -e "show global status"|grep "Connections"
Warning: Using a password on the command line interface can be insecure.
Connections    30198
zhoumengkang$ php 2.php
查询了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次
数据库连接关闭了
zhoumengkang$ mysql -uroot -pzmkzmk -e "show global status"|grep "Connections"
Warning: Using a password on the command line interface can be insecure.
Connections    30200

加上上面终端请求查询数据库,共增加了两个连接,也就是说上面的30次请求就连接了一次数据库。

如果是 java 在不使用数据库连接池的情况下,必须在使用完数据库之后关闭连接。类似的方法测试发现 java 会连接30次数据库。所以使用数据库连接池势在必行,不然就是费代码了。

弄了半天,使用的是apache.commons.dbcp,做了下压测对比,还是在一个 api 里做30次数据库插入操作。
在不使用连接池的情况下

zhoumengkang$ ab -c100 -n1000 "http://localhost:8081/?method=test"
Requests per second:    17.24 [#/sec] (mean)
Time per request:       5800.506 [ms] (mean)

连接数直接增加了3万。耗时58秒。使用了连接池后,

Requests per second:    158.87 [#/sec] (mean)
Time per request:       629.462 [ms] (mean)

设置的最小连接数20,耗时6秒。相差还是很大的。

未完待续...