孫 立 哲
(公安部第三研究所 上海 201204)
前期在異步接口性能測試方面做了一些初步性探索工作,并以HTTP異步接口為例設計一種能夠覆蓋異步接口內部完整業務流程的性能測試方案[1]。該性能測試方案的核心思想是在待測異步接口中增加將請求數據和異步響應數據分別寫入不同數據庫表的處理邏輯,并在壓測結束后通過對數據庫表中請求數據和響應數據作比對統計分析,以評估異步接口完整業務下的性能情況。該方案覆蓋了異步接口內部完整業務處理流程,能對整體性能表現作出較為全面的評測。但尚未對方案中所涉及的關鍵技術以及這些技術實現是否會對待測異步接口原有性能表現產生影響等作較為深入的設計優化研究與實踐驗證。這些關鍵技術主要表現在兩方面,一是異步接口中增加的數據入庫模塊;二是對庫中數據作快速統計分析的模塊。本文重點針對這些技術及其影響做進一步的技術探索與實踐驗證。
采用Hibernate框架對象持久化技術[2]、EJB組件封裝與依賴注入[3]、線程池與多線程并發技術[4-5]來實現將數據寫入數據庫。
首先,以Hibernate框架對象持久化技術實現對Java對象數據與數據庫表字段映射,以及與數據庫之間的連接與數據訪問操作執行。Hibernate是一個開源的對象關系映射框架,它實現了對數據庫連接輕量級的對象封裝,能提供高性能的對象關系型持久化存儲和查詢服務[6]。
其次,采用EJB組件封裝技術將對數據庫的增刪改查等操作封裝成接口,通過調用EJB組件接口觸發對數據庫的操作。采用EJB依賴注入技術將已封裝好的接口注入異步接口內,異步接口內以直接調用EJB接口的方式完成對數據庫表數據的寫入。EJB是服務端組件模型,設計目標與核心應用是部署分布式應用程序。EJB容器(如JBoss)提供了對象池和緩存機制,沒有事務機制的無狀態Session Bean比普通JavaBeans具有更強的性能。
采用線程池,根據實際需求創建一定數量的線程數,把向數據庫寫數據的操作放入線程池以多線程的方式去執行,降低數據庫操作對異步接口自身性能的影響。在高并發場景下,可通過調整線程數來調整性能表現。線程池及各線程在異步接口服務程序部署啟動時創建。異步接口將寫數據的任務傳給線程池時,線程池將任務分配給一個線程來執行。任務執行結束后,該線程返回線程池中成為空閑狀態,等待執行下一個任務。線程池多線程處理機制具有很好的性能優勢。
與用于記錄數據的各數據庫表對應,創建實體類。采用JPA對象持久化技術建立實體類與數據庫表的映射關系。采用EntityManager接口實現對Java對象到數據庫表的寫入操作。EntityManager是JPA中用于對數據庫表數據進行增刪改查的接口,連接內存中的Java對象和數據庫的數據存儲[7]。采用EntityManager的persist方法和flush方法將實體類對象持久化寫入數據庫表。用EJB本地接口封裝對數據庫表的操作。以EJB依賴注入的方式將EJB接口實例注入異步接口,異步接口內調用EJB接口實例的各方法來完成對數據庫表的操作。采用Java開發工具包中Executors類的newFixedThreadPool方法創建線程池。將寫入數據庫的操作單獨封裝為一個數據庫寫入方法。在異步接口實現類中新建內部類并實現Runnable接口,在覆寫的run方法中調用外部類的數據庫寫入方法,以新建內部類對象的方式新建線程并放入線程池。以一級異步接口為例,寫數據入對應數據庫表的類圖如圖1所示。

圖1 一級異步接口寫數據入數據庫表類圖
以一級異步接口為例,具體實現:
1) 定義實體類,實現持久化。與請求表和響應表相對應,創建兩個實現序列化接口的實體類,記為請求實體類和響應實體類。兩個實體類中定義各屬性,分別對應表中各字段,并在各字段前添加相應注釋,如唯一自增注釋。創建兩個普通類,記為請求類和響應類,分別對應請求實體類和響應實體類,普通類中屬性分別對應實體類中除唯一自增屬性之外的其他各屬性。
2) 定義EJB接口,用于封裝將實體類數據持久化寫入數據庫的操作。創建兩個EJB接口,記為請求接口和響應接口,接口中聲明數據寫入方法,方法入參類型分別為請求類類型和響應類類型。創建兩個EJB無狀態Session Bean類,記為EJB請求類和EJB響應類,分別實現請求接口和響應接口,類前加無狀態注釋,加PersistenceContext依賴注入EntityManager實例,PersistenceContext依賴中的“持久化單元名”與配置文件persistent.xml中定義的持久化單元persistence-unit的name屬性值一致。在ds.xml文件中配置MySQL數據源,然后在persistent.xml文件中引入該數據源。ds.xml文件與異步接口部署在相同目錄下。實現接口中聲明的數據寫入方法,方法中將入參類對象作數據轉換轉為對應的實體類對象,調用EntityManager實例的persist方法和flush方法將實體類對象數據寫入數據庫表。
3) 在異步接口中加依賴注入EJB接口實例,并以線程池多線程方式調用EJB接口實例的數據庫寫入方法。在異步接口中依賴方式注入EJB實例時,異步接口需設為EJB組件接口。在異步接口中加EJB注釋引入EJB請求類實例和EJB響應類實例。創建.properties屬性配置文件,在配置文件中設定線程池線程數。創建線程池工具類,類中讀取配置文件獲取線程數,并在類加載時自動創建靜態線程池及各線程。在異步接口實現類中,將調用EJB接口實例方法進行數據庫寫入的操作單獨封裝為一個數據庫寫入方法,方法內調用EJB請求類實例對象的數據寫入方法和EJB響應類實例對象的數據寫入方法。在異步接口實現類中新建內部類并實現Runnable接口,在覆寫的run方法中調用外部類的數據庫寫入方法。在異步接口內部作異步響應返回前,獲取線程池工具類中預創建的靜態線程池,以新建內部類對象的方式新建線程并放入線程池中執行。
基于以上設計與實現,以HTTP異步接口為例,對異步接口分將數據寫入數據庫和不寫數據庫兩種場景作性能對比驗證測試。測試環境整體架構如圖2所示。

圖2 性能測試環境整體架構
異步接口請求數據與響應數據均采用序列化的JSON(JavaScript Object Notation)字符串格式。請求數據串中包含業務請求時間、業務流水號、其他特定業務數據鍵值對。異步響應數據串中包含異步響應時間、業務流水號、業務處理結果、其他特定業務處理結果數據鍵值對。響應串中業務流水號與對應的請求串中業務流水號一致。請求數據表中每一條請求串都能在響應數據表中找到唯一響應數據記錄,說明所有請求均被成功響應。
驗證測試時,請求數據業務請求時間取壓測客戶端當前系統時間并精確到毫秒級。采用隨機生成通用唯一標識作為業務流水號,確保每個請求具有唯一性業務流水號。響應數據業務響應時間取服務端當前系統時間。服務端系統時間與壓測客戶端系統時間同步。異步接口內每個響應數據的業務流水號取其對應請求數據的業務流水號。請求數據與響應數據中其他特定業務數據采用固定數據。
壓測過程中按上述格式及方式準備請求數據串請求異步接口。
驗證環境為:
1) 數據庫所在服務器硬件配置為8核處理器、16 GB內存、300 GB硬盤。
2) 異步接口部署所在服務器硬件配置為8核處理器、16 GB內存、300 GB硬盤。
3) 異步回調接口部署所在服務器硬件配置為4核處理器、8 GB內存、300 GB硬盤。
4) JMeter所在壓測客戶機硬件配置為8核處理器、16 GB內存、200 GB硬盤。
5) 整套環境通過一個千兆以太網交換機部署在同一局域網內。
驗證結果如下:
在并發數相同以及客戶端和服務端處理器、內存等系統資源未出現資源耗盡等性能瓶頸情況下,客戶端壓測并發數和服務端線程池線程數配置均為70,不寫數據庫時異步接口每秒處理請求數2 824.8次,寫數據庫時異步接口每秒處理請求數2 473.9次。對比結果顯示,寫數據庫時異步接口性能有略微下降。但相比而言,增加寫數據庫處理邏輯對異步接口原有性能產生的影響不大。
前期設計實現的數據統計分析程序可以對請求數據與響應數據作出正確的統計分析。但因采用的是單線程處理,且數據庫表未創建索引,在數據量較大的情況下,統計分析執行過程耗時會比較長。為了提高統計分析執行速率,采用數據庫表索引、線程池和多線程并發技術作進一步的設計優化。索引的合理創建可提高數據庫表數據的查詢檢索速度[8],線程池多線程并發技術可實現對數據分組作并行查詢比對,從而提高統計分析執行速率。
數據統計分析過程中,對請求表作數據查詢時的檢索條件是唯一屬性字段。唯一屬性字段本身是一種特殊的主鍵索引[9],因此數據檢索速度會比較快。對響應表作數據查詢時的檢索條件為業務流水號,業務流水號默認不是主鍵,也沒有對應的索引,因此,對響應表的數據查詢速度會比較慢。故對響應表增加業務流水號字段索引,以提高表數據檢索速度。
在統計分析過程中,數據比對是從請求表中根據唯一屬性自增字段依序提取請求數據,然后根據請求數據中業務流水號與響應表中數據作查詢并比對。請求表中不同的請求數據之間沒有關聯關系,請求表中數據與響應表中數據預期存在一一對應關系。在同一時間可以并行地在不同的線程中分別對不同的數據作查詢比對。因此,可引入線程池多線程來實現并行處理,從而減少統計分析時間。將請求數據按照唯一屬性自增字段作分組,對不同分組分別同時在不同線程進行獨立統計分析,并記錄分組內統計分析結果。在所有線程全部執行結束后,對各分組內統計分析結果再作匯總統計。
創建.properties屬性配置文件,在文件中設定數據分組長度,分組長度表示分組內的數據個數。統計分析程序讀取配置文件,獲取分組長度,將所有請求數據按分組長度作分組。若請求數據總數是分組長度的整數倍,則請求數據總數除以分組長度所得整數即為分組個數。若請求數據總數非分組長度的整數倍,則請求數據總數除以分組長度所得整數再加一即為分組個數。如果請求數據總數小于等于分組長度,則以單線程作統計分析,如果請求數據總數大于分組長度,則以多線程分組作統計分析。以分組個數作為線程數,創建線程池。一個線程對應一個分組,單個分組內的數據統計分析在一個線程中執行,不同分組在不同線程中并行執行。對每個分組中各請求數據根據唯一屬性自增字段值按順序逐一提取并與響應表中數據作查詢比對,分別統計各分組內的請求成功數、請求失敗數、平均響應時間、最小響應耗時、最大響應耗時,并將每個分組的統計結果分別記錄在不同的文件內,一個分組對應一個文件。所有線程執行結束后,對各分組統計分析結果作匯總集成統計,得出總請求成功數、總請求失敗數、總平均響應時間、總最小響應耗時、總最大響應耗時。多線程統計分析處理邏輯流程如圖3所示,分組統計子流程如圖4所示。

圖3 多線程統計分析處理邏輯流程

圖4 分組統計子流程
線程池中線程數與分組個數相同,分組個數取決于配置文件中分組長度設定值。因此,在總請求數一定的情況下,配置文件中設定的分組長度越小,線程數越多,分組長度越大,線程數越少。線程數過少,統計分析執行耗時越接近單線程處理耗時,耗時會比較久。線程數過多,統計分析程序執行時會過多地占用系統資源,系統資源耗盡時會影響統計分析程序的執行速率。合理設定數據分組長度,才能使統計分析程序達到較優的性能。
基于以上設計優化與實現,對數據統計分析程序作優化前后的性能對比驗證測試。
統計分析過程中所用數據為異步接口性能壓測時分別寫入請求數據表和響應數據表中的數據。
驗證環境如下:
1) 統計分析程序所在客戶機硬件配置為8核處理器、16 GB內存、200 GB硬盤。
2) 數據庫服務所在服務器硬件配置為4核處理器、8 GB內存、60 GB硬盤。
3) 客戶機與服務器在同一局域網內。
驗證結果如下:
1) 總請求數為479 400時,優化前的單線程統計分析耗時為385.77 s,優化后的多線程統計分析耗時為183.53 s。
2) 總請求數為741 812時,優化前的單線程統計分析耗時為829.16 s,優化后的多線程統計分析耗時為317.97 s。
3) 總請求數為1 531 640時,優化前的單線程統計分析耗時為1 726.97 s,優化后的多線程統計分析耗時為603.48 s。
對比結果顯示,優化后的基于多線程并發技術的統計分析程序執行速率提升顯著。
本文在前期進行的關于異步接口性能測試方案設計基礎上,針對方案中所涉及的一些關鍵技術點,作了進一步的設計優化研究與實踐驗證。主要表現在兩方面,一方面是采用Hibernate框架JPA對象持久化技術、EJB組件封裝與依賴注入、線程池與多線程并發技術,對異步接口數據寫入數據庫模塊作了優化設計與實現以及驗證測試,另一方面是采用數據庫表索引、線程池與多線程并發處理等技術對數據統計分析程序作了優化設計與實現以及對比驗證測試。驗證結果顯示,本文做優化設計并實現的異步接口數據寫入數據庫的模塊性能表現良好,對異步接口原有性能影響較小,優化后的數據統計分析程序相比優化前在執行速率上提升了1~2倍,性能提升較明顯。