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

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

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

      1.从架构设计的角度

           现在的web项目不再像七八年前以前的项目单个的动态web工程就能满足性能的要求了,现如今项目只要是抱着一个美好前景的话,一般都会假设自己的项目未来是PB级数据量,亿级用户量,几万并发的赚暴的独角兽项目,无论是电商,新浪,搜狐等门户网站都会有大量数据,大量的用户,现在热门的物联网虽然用户量没有大规模,然而一堆一堆的传感器从不觉得累地同时访问你,产生大量的数据。

          为此,要想你的系统在满足要求的情况下扛住压力实现高可用,靠提高硬件已经性价比上不能接收。如下是一张简单流行的分布式架构图,不全面,只用来说明一下性能方面相关:

      java web项目性能优化之五花八门_第1张图片

     上图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;

    }

   这样的结果大约就是    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 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;

    }

 简洁吧? 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



你可能感兴趣的:(java web项目性能优化之五花八门)