最近是做了半年的项目到了最后测试准备上线了,流程走通后开始做一些性能测试,在此期间做了很多性能优化的工作,在此做下笔记,分享一下。交流一下,希望同道中人有新的东西欢迎补充。在此就不做太多的具体操作,主要还是从思路上出发。
性能优化主要从几个方面着手。
1.从架构设计的角度
现在的web项目不再像七八年前以前的项目单个的动态web工程就能满足性能的要求了,现如今项目只要是抱着一个美好前景的话,一般都会假设自己的项目未来是PB级数据量,亿级用户量,几万并发的赚暴的独角兽项目,无论是电商,新浪,搜狐等门户网站都会有大量数据,大量的用户,现在热门的物联网虽然用户量没有大规模,然而一堆一堆的传感器从不觉得累地同时访问你,产生大量的数据。
为此,要想你的系统在满足要求的情况下扛住压力实现高可用,靠提高硬件已经性价比上不能接收。如下是一张简单流行的分布式架构图,不全面,只用来说明一下性能方面相关:
上图web服务器一般以集群的形式,用lvs,Nginx等开源工具做反向代理和API层的负载均衡。业务层service可以用Dubbo等RPC框架实现分布式调用,达到多节点同时处理计算,现在又有一种新的趋势,以springboot框架做微服务进行服务间以restful接口调用,两种形式各有千秋,前者较后都就目前来说更流行一些,在此只关注对性能相关的话题。
另外,使用redis,memcache等开源工具做缓存对性能也有较大的提高,当然也会有一些管理难的代价,管理不好经常出现数据不一致。
2.从数据库的角度
关系形数据库在数据量达到一定规模查询效果较差,像一些操作纪录等数据可以用elasticsearch,redis,mongodb等nosql非关系形数据库来存储,查询性能比关系形数据库好很多,但是比如金钱,订单,用户信息等“贵重”信息只能用关系形数据库来存储。关系形数据库性能提高常的方法一般包括建立索引,视图等。有些数据库如mysql官方还提供代理工具实现水平拆分,垂直拆分等,Mysql proxy代理工具可以实现数据库的读写分离,都能一定程序提高关系数据库的性能。
3.从代码的角度
进入一家新公司后,一般架构都已经定了,为了性能动架构的机会不是很多,除非决定整个项目重构,难道在性能方面就没有办法了吗?答案是否定的,java真的是一门神奇的语言,可能简洁度上不如php语言,性能不如c++,api没有scala丰富,也没有golang那么高效,然而java是最中庸的,综合实力最强。在此为java点个赞,好了,还是上代码吧。
比如有个需求,第一步要调用北京总公司的中控服务器拿token等验证信息,平均耗时要1秒,调本系统查询订单处理要2秒,调百度上传图片要2秒,调阿里支付要3秒,有一个方法里面全部完成这些操作,普通的写法如下:
package com.web.service.back.impl;
import java.util.concurrent.TimeUnit;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
@Controller
public class PayOrder {
@RequestMapping("pay")
@ResponseBody
public Boolean payGood(String userName, String password, double money) throws InterruptedException {
String token = getToken(userName, password);// 1秒
String url = upLoadPic();// 2秒
double totalFee = dealOrder(userName);// 返回总金额 2秒
payMoney(totalFee);//3秒
return true;
}
private String upLoadPic() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
return "url";
}
private void payMoney(double totalFee) throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
}
private double dealOrder(String userName) throws InterruptedException {
double sum = 0;
for (int i = 0; i < 20; i++) {
TimeUnit.MILLISECONDS.sleep(100);// 模拟处理单个订单消耗00毫秒,20个订单为2秒
sum += i * 50.00;
}
return sum;
}
private String getToken(String userName, String password) throws InterruptedException {
TimeUnit.SECONDS.sleep(1);// 模拟调用时间为2秒
return "123456";
}
}
这样的结果有点吓人,总的消耗时间为 1+2+2+3 =8秒,这在生产环境是不能被忍受的。上面代码是串行运行,我们可以做以下处理
@RequestMapping("pay")
@ResponseBody
public Boolean payGood(String userName, String password, double money) throws InterruptedException {
ExecutorService pool = Executors.newCachedThreadPool();
Future totalFeeFuture = pool.submit(new Callable() {
@Override
public Double call() throws Exception {
return dealOrder(userName);// 返回总金额 2秒
}
});
Future tokenFuture = pool.submit(new Callable() {
@Override
public String call() throws Exception {
return getToken(userName, password);// 2秒
}
});
Future picUrlFuture = pool.submit(new Callable() {
@Override
public String call() throws Exception {
return upLoadPic();// 2秒
}
});
pool.submit(new Runnable() {
@Override
public void run() {
try {
payMoney(totalFeeFuture.get(2, TimeUnit.SECONDS));// 设置超时设置
} catch (InterruptedException | ExecutionException | TimeoutException e) {
e.printStackTrace();
} // 3秒
}
});
try {
String url = picUrlFuture.get(2, TimeUnit.SECONDS);
tokenFuture.get(2, TimeUnit.SECONDS);
} catch (ExecutionException | TimeoutException e) {
e.printStackTrace();
}
return true;
}
@RequestMapping("pay")
@ResponseBody
public Boolean payGood(String userName, String password, double money) {
try {
CompletableFuture totalFeeFuture = CompletableFuture.supplyAsync(() -> dealOrder(userName));
CompletableFuture tokenFuture = CompletableFuture.supplyAsync(() -> getToken(userName, password));
CompletableFuture picUrlFuture = CompletableFuture.supplyAsync(() -> upLoadPic());
CompletableFuture.runAsync(() -> payMoney(totalFeeFuture.get(2, TimeUnit.SECONDS)));
String token = tokenFuture.get();
String url = picUrlFuture.get();
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}
private double dealOrder(String userName) throws InterruptedException {
double sum = 0;
for (int i = 0; i < 20; i++) {
TimeUnit.MILLISECONDS.sleep(100);// 模拟处理单个订单消耗00毫秒,20个订单为2秒
sum += i * 50.00;
}
return sum;
}
处理订单是在一个串行20次循环中处理,感觉也糟糕透了。下面提供两程优化。第一种countDownLatch
double sum = 0;
private double dealOrder(String userName) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(20);
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.execute(new Runnable() {
AtomicInteger i = new AtomicInteger(0);
@Override
public void run() {
sum += i.doubleValue()*50.00;
i.incrementAndGet();
latch.countDown();
}
});
latch.await();
return sum;
}
private double dealOrder(String userName) throws InterruptedException {
double sum = IntStream.rangeClosed(0, 20).parallel().asDoubleStream().map((i) -> i*50.00).reduce(0, Double::sum);
return sum;
}