java web项目性能优化之五花八门

来源:互联网 发布:社交网络电影图解 编辑:程序博客网 时间:2024/05/24 15:36

       最近是做了半年的项目到了最后测试准备上线了,流程走通后开始做一些性能测试,在此期间做了很多性能优化的工作,在此做下笔记,分享一下。交流一下,希望同道中人有新的东西欢迎补充。在此就不做太多的具体操作,主要还是从思路上出发。

      性能优化主要从几个方面着手。

      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;@Controllerpublic 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<Double> totalFeeFuture = pool.submit(new Callable<Double>() {            @Override            public Double call() throws Exception {                return dealOrder(userName);// 返回总金额 2秒            }        });        Future<String> tokenFuture = pool.submit(new Callable<String>() {            @Override            public String call() throws Exception {                return getToken(userName, password);// 2秒            }        });        Future<String> picUrlFuture = pool.submit(new Callable<String>() {            @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;    }

   这样的结果大约就是    2秒左右;future模式,可以先返回一个Future给调用者,主线程可以立即得到返回,往下运行,等需要得到结果时调用future.get()方法获取结果,此方法会阻塞,当然可以设置一个超时时间, 防止程序死在这里,提醒一下,向这种异步处理应该在依赖的返回结果的情况下,有两个原则:
   a.有回调的也就是传Callable参数的应该越早越省时间。
   b.消耗时间越长的调用越先执行。
  如果你参与的项目有幸用的是java8,java8中有CompletableFuture增强Future,自带forkJorkPool线程池。也可以自已指定线程池。上代码,上面主方法可改为:
  @RequestMapping("pay")    @ResponseBody    public Boolean payGood(String userName, String password, double money)  {                try {            CompletableFuture<Double> totalFeeFuture = CompletableFuture.supplyAsync(() -> dealOrder(userName));            CompletableFuture<String> tokenFuture = CompletableFuture.supplyAsync(() -> getToken(userName, password));            CompletableFuture<String> 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;    }

 简洁吧? lambda表达式威力是不是很大?来来,我们继续,
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;    }

这里await()方法会阻塞.
第二种,java8并行流:
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;    }

好了就写这么多吧。下次再写吧。孩子想爸爸了,回家

a



原创粉丝点击