(1)項目說明
該項目是筆者參與的某省建設銀行省分行數據倉庫項目,本節案例主要說明該項目的月末程序在多進程併發上的實現。下面是該案例的簡要說明,以及該案例在硬件、數據庫配置、數據庫建庫腳本、程序上調優的簡要介紹。
月末程序主要功能是計算各個客戶的存款、貸款、消費、貢獻度,然後按照指標把客戶分成一系列的等級(如金卡、白金、銀卡客戶等),最後把優質客戶分配給各個客戶經理進行個性化營銷。
該項目月末模塊計算量很大,但需要月末某天10小時內計算完成,所以需要利用軟硬件上的各種性能。
在硬件上,使用多CPU,高內存,磁盤陣列採用0+1方式,將表空間分佈到不同的磁盤陣列上。在數據庫管理配置上,使用非日誌方式,同時把數據緩衝區配置相當大(總內存1/3至1/2)。在建表腳本上,將大表建立在多個表空間上發揮磁盤陣列的併發功能,同時數據塊的配置應儘量大,使用大數據塊能讓磁盤陣列一次能順序讀出大量的數據頁。建表腳本使用頁鎖而不使用行級鎖。對於大數據量的處理在程序上處理優化也顯得尤爲重要,下面是程序優化的說明。
該行客戶信息表和存款表都有幾千萬級的數據量,所以單進程計算效率上是不允許的,只能用多進程併發。程序設計時取了最大客戶號,按照定製進程數,將最大客戶號/進程數劃分客戶號段分配給各進程,然後每個進程按照客戶號範圍併發將大表數據導入到十幾個小表內,導入完後對小表建索引,然後利用十幾個進程進行併發計算。
由於月末處理程序劃分爲20多個子交易順序完成,程序使用一個控制表來完成整個流程控制處理,支持每一步報錯的重做功能。進程主控每次取控制表步驟號,調用每一步子主控函數,子主控函數fork十幾個進程,然後進行功能函數處理,子主控函數等待各子進程退出,完成該步子交易處理後進程主控修改控制表步驟號並繼續完成下一步。
在以前OLTP的項目中,因爲是查找少量數據,where子句上建索引可大幅提高查詢性能,但這條規律並不適用OLAP項目。在併發分表時,由於各表都對客戶號建了索引,分表時系統默認走索引,如把客戶信息表分成17個表需要一個小時,但項目後期一次偶然機會,我使用數據庫僞指令(SELECT --+FULL),進行全表掃描,結果客戶信息表分表只用了20分鐘。可見,實踐出真知,思考出智慧,有時嘗試一下新思路有時會有新的收穫。
在數據庫使用方法上,大表一般重新由小表併發生成,然後建索引;鎖方式設置爲髒讀,表數據更新後進行更新表的統計信息;處理完成後把原客戶信息表、貢獻度表等重命名,併發重新生成新的客戶信息表、貢獻度等表,生成新表後併發裝載數據,然後再重建索引。
應用要求fork進程數必須完成規定個數,一個報錯要殺掉其他進程,主進程要等待該步所有子進程功能完成才能進行下一步程序。下面的demo代碼爲該月末程序簡化的多進程調度部分。
(2)下面是多進程併發處理的demo程序
mufork.c源代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#define ProNU 3 /** 進程數 ****/
int proc_id[ProNU] ; /**存放子進程的id號**/
int process()
{
sleep(30) ; /*便於查看進程數*/
printf("process success pid=%d\n",getpid()) ;
return 0 ;
}
/**************************************************************************
* Function Name: cif_wait_proc( ) *
* Description: 等待所有子進程退出 *
* Return: 0 : 成功 *
* -1 : 失敗 *
**************************************************************************/
int cif_wait_proc(int *count, int proc_id[])
{
int status, i, j, pid ;
/* 父進程等待所有子進程完成 */
i = *count;
if ( *count < ProNU-1 ) /**子進程數未達規定數殺死所有子進程**/
{
fprintf( stderr, "fork 失敗") ;
for( j = 0; j< *count; j++ )
{
kill(proc_id[j],9) ;
sleep( 1 ) ;
printf("fork 失敗 ") ;
return -1 ;
}
}
for( j = 0; j< *count; j++ )
{
printf("proc[%d]=%ld\n",j, proc_id[j]) ;
}
/***子進程達到預計數等待所有子進程退出***/
while ( i ) {
pid = wait( &status );
if ( pid < 0 ){
continue;
}
for( j = 0; j< *count; j++ ) {
/* 如果找到相應的子進程記錄, 進行出錯處理 */
if ( pid == proc_id[j] ) {
i--;
if ( WIFSIGNALED( status ) ) {
fprintf( stderr, "子進程[%d]被信號[%d]終止", pid,WTERMSIG( status ) );
for( j = 0; j< *count; j++ )
{
kill(proc_id[j],9) ;
sleep( 1 ) ;
}
return -1;
}
else if ( WIFSTOPPED( status ) ) {
fprintf( stderr, "子進程[%d]終止,終止信號[%d]",pid,WSTOPSIG( status ) );
for( j = 0; j< *count; j++ )
{
kill(proc_id[j],9) ;
sleep( 1 ) ;
}
return -1;
}
}
}
}
return 0 ;
}
int main()
{
int ret , paranu , dbsnu , minnu1 , maxnu1 ;
memset( proc_id, 0x00, sizeof(proc_id) ) ;
/*根據定製結果產生規定的進程數*/
for( paranu = 0 ; paranu < ProNU ; paranu++ ){
if( (proc_id[paranu] = fork()) == 0 ){
ret = process();
if( ret == -1 ){
fprintf( stderr, "子進程出錯,序號[%d]!\n", paranu) ;
kill( getpid() , 9 );
}
exit( 0 ) ;
}
else if( proc_id[paranu] < 0 ){
fprintf( stderr, "Fork子進程出錯,序號[%d]!\n", paranu) ;
break ;
}
}
ret = cif_wait_proc(¶nu ,proc_id ) ;
if ( ret == -1 )
{
fprintf(stderr,"子進程出錯,序號[%d]!\n",paranu);
return -1 ;
}
return 0;
}
編譯 gcc mufork.c –o mufork。
./mufork,可以用ps –ef|grep mufork查看進程數。
proc[0]=6923
proc[1]=6924
proc[2]=6925
process success pid=6923
process success pid=6924
process success pid=6925
摘錄自《深入淺出Linux工具與編程》