1. 程式人生 > >java web專案效能優化之五花八門

java web專案效能優化之五花八門

       最近是做了半年的專案到了最後測試準備上線了,流程走通後開始做一些效能測試,在此期間做了很多效能優化的工作,在此做下筆記,分享一下。交流一下,希望同道中人有新的東西歡迎補充。在此就不做太多的具體操作,主要還是從思路上出發。

      效能優化主要從幾個方面著手。

      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<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


相關推薦

java web專案效能優化五花八門

       最近是做了半年的專案到了最後測試準備上線了,流程走通後開始做一些效能測試,在此期間做了很多效能優化的工作,在此做下筆記,分享一下。交流一下,希望同道中人有新的東西歡迎補充。在此就不做太多的具體操作,主要還是從思路上出發。       效能優化主要從幾個方面著手

web前端效能優化CDN

什麼是CDN CDN (Content Delivery Network) 可直譯成內容分發網路。CDN的本質仍然李詠快取技術快取, 解決的是__如何將資料快速可靠從源站傳遞到使用者的問題__。使用者獲取資料時,不需要直接從源站獲取,通過CDN對於資料的分發,使用者可以從一個較優的伺服器獲取資料,從而達到快

android專案效能優化啟動時間

一般來說,判定一個android專案效能優劣,我們有以下幾個指標: 啟動時間 apk大小 UI渲染 穩定性 記憶體佔用 電量消耗 接下來,讓我們就這幾個指標展開來詳述各自究竟應該怎樣去優化。 啟動時間 一般來說,應用啟動時間分為三種 首次啟動

關於 Java Web 專案效能提升的一些思路

使用 Nginx 作為前端接入         用 Nginx 進行動靜分離。這個不用多講,新浪、網易、淘寶、騰訊等巨頭的使用已經說明了一切。保持最簡單的架構         遵守 KISS 原則(Keep it simple and stupid)。儘量不要考慮專案外的重

java Web專案效能的提高

前端高效能、反向代理、資料庫高效能、負載均衡等等 一.前端的高效能優化 主要是指減少請求數、減少傳輸的資料以及提高使用者體驗。 在這個部分,圖片的優化顯得至關重要。許多網站的美化,都是靠絢麗的圖片達到的,圖片恰恰是佔用頻寬的元凶。每個 img 標籤,瀏覽器都會試圖發起一個

菜鳥要做架構師——java效能優化for迴圈

完成同樣的功能,用不同的程式碼來實現,效能上可能會有比較大的差別,所以對於一些效能敏感的模組來說,對程式碼進行一定的優化還是很有必要的。今天就來說一下java程式碼優化的事情,今天主要聊一下對於for(while等同理)迴圈的優化。 作為三大結構之一的迴圈,在我們編寫程式碼的時候會經常用到。

Java效能優化JVM記憶體模型

JVM記憶體模型 首先介紹下Java程式具體執行的過程: Java原始碼檔案(.java字尾)會被Java編譯器編譯為位元組碼檔案(.class字尾); 由JVM中的類載入器載入各個類的位元組碼檔案,載入完畢之後,交由JVM執行引擎執行 在整個程式執行過程中,JVM會用==一段空間==來儲存程式執

部署Java Web 專案注意事項

上篇 Docker - 部署Java Web 專案記錄了使用docker如何搭建一個擁有java執行環境的docker映象。 這篇部落格記錄之前構建映象過程中遇到的一些問題。 這裡,在docker容器中,部署專案思路: 1.先git拉取最新程式碼 2.因

Java效能優化作業系統層面優化

目前常用的作業系統分為:windows,Unix(Linux),我們會分別介紹在不同系統上的調優。 一,概念 效能監控:一種以非侵入方式收集或檢視應用執行效能資料的活動,通常是指在生產,質量評估, 開發環境中實施的帶有預防或主動性的活動。 效能分析:一種以侵入方式收集執行效能資料的活

架構優化高效能:web前端效能優化,靜態資源快取,檔案壓縮

web前端效能優化 內容主要來自阿里架構一書。自己總結以及進行實踐 一.瀏覽器訪問優化 1.減少http請求 合併css,合併JS,合併圖片:圖片也可以進行合併,多張圖片合併成一張, 現在的瀏覽器會自動的複用tcp連結,不會剛用完就關閉 2.設定使用瀏覽器快取 靜態資源(如何設定?可

Java Web專案檔案更新部署的優化方案

現狀 專案已部署在生產環境的WebSphere伺服器叢集下,但公司並沒有在生產環境下整合專案自動構建工具Jenkins。 但是,由於專案穩定性的需要,又不能進行全量部署或增量部署。 注:如果隨意進行全量部署,可能會將線上已經穩定的程式碼替換掉,變成測試環境中未

Linux(Centos)安裝tomcat並且部署Java Web專案

  b.因為tomcat的安裝依賴於Java jdk,所以我們需要在判斷linux系統下面是否安裝jdk     b.1 使用(Xshell)連線到Linux系統下面     b.2 輸入命令:java -version,如果顯示jdk版本號,則證明已經安

Web效能優化CSS效能優化

非常感謝原文作者的分享,個人覺得非常有用.所以將原文進行翻譯,如果有錯誤,麻煩回覆指出 附上原文地址 什麼是高效的CSS?不同的選擇器對效能的影響如何?是花括號裡的屬性重要還是選擇器重要?. 我們在做優化網站的效能時,CSS的優化往往是

需求分析--我的第一個java-web專案路(一)

需求分析 (-:引用的定義:-) 所謂”需求分析“,是指對要解決的問題進行詳細的分析,弄清楚問題的要求,包括需要輸入什麼資料,要得到什麼結果,最後應輸出什麼. 需求分析是一個專案的開始,主要是弄明白這個專案需要做什麼,用來做什麼。簡單來說就

JAVA效能優化 String

1.String 3個基本特點,不變性、針對常量池的優化及類的final定義 2.String物件的4種建立方式: ①當時用任何方式來建立一個字串物件X時,Java執行時(執行中JVM)會拿著這個X在String池中查詢是否存在內容相同的字串物件,如

小型Java Web專案DAO操作標準、DAO介面真實實現類、DAO代理實現類、DAO工廠

DAO模式的好處是資料訪問和業務邏輯分離,便於資料維護,業務邏輯不需要了解訪問細節。 DAO的組成: 1.DatabaseConnection:負責開啟和關閉資料庫; 2.VO:包含屬性以及其getter和setter方法; 3.DAO:定義操作的介面,

WEB效能優化GZIP壓縮

GZIP壓縮是一個經常被用到的WEB效能優化的技巧,它主要是對頁面程式碼,CSS,Javascript,PHP等檔案進行壓縮,而且在壓縮的前後,檔案的大小會有明顯的改變,從而達到網站訪問加速的目的。 接下來我們就介紹一下什麼是GZIP壓縮,以及GZIP壓縮是個什麼概念。 GZIP網頁壓縮,是一種WEB伺

小型Java Web專案DatabaseConnection

做JavaWeb專案必然離不開資料庫,這裡使用的是MySQL資料庫進行開發。具體連結類如下: package dbc; import java.sql.Connection; import ja

Web效能優化動態合併JS/CSS檔案並快取客戶端

在Web開發過程中,會產生很多的js/css檔案,傳統的引用外部檔案的方式會產生多次的http請求,從而加重伺服器負擔且網頁載入緩慢,如何在一次請求中將多個檔案一次加載出來?接下來給大家介紹在ASP.NET中動態合併載入多個js或css檔案。 原理:減少請求伺服器的次數達

web效能優化事件節流

問題:滑鼠滾輪滾動一下,scroll事件觸發了12次document.addEventListener('scroll',function(){ console.log('hello') })事件節流背後的思想是指:某些程式碼不可以在沒有間斷的情況下連續重複執行,第一次呼叫