(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工具与编程》