项目修炼之路(3)3百亿交易额的处理

62 篇文章 0 订阅
36 篇文章 1 订阅


        现在公司项目中,都有几个核心业务,处理的数据量相较于其他是巨量的,而在我们公司,当下就与一个问题,在现在百亿交易额的处理中,有一个核心业务,就是每日发息(已符合最新法规),以前的发息任务,要跑将近3,4个小时,而我要为下一个交易量的增长,可能是千亿的交易额处理做技术准备,我们都知道,从数据来看,交易量的增长和数据的增长,有时候不是简单的线性关系,数据极有可能是交易量增长倍数的3-5倍,这就是一个挑战,今天和大家分享,这个挑战,我是如何度过的。

1,方向

        大的方向,是我们要首先确定的,在问题处理上分三个层次处理

  • 业务处理
  • 数据处理
  • 结构处理

       简单介绍下:

(1)业务处理

        在业务上,首先确定一个原则:分析实时性业务的必要性,尽量做到非实时,为处理留时间

        对应到业务上就是,发息:不是实时发息,虽然有发息时间,但是统一到流量不大的深夜执行,第二天用户收到利息;投资:采取排队机制,将投资结果非实时反馈给用户,为校验留时间;匹配:先做借贷匹配,做占用,然后线上实时匹配(就是做债权缓存池)


        (2)数据处理

        在数据上,也有一个原则就是500万,热表操作数据控制在500万内

       在冷热数据处理上,用触发器在某个时刻,定时移动冷热数据(根据业务),保证热库中的数据是流畅可用的。


        (3)结构处理

        还是有个原则:按维度多线程处理,solr作为查询第一数据来源,修改时与数据库同步,查不到再走数据库。

          

2,技术

        今天只介绍数据批量提交与多线程结合的例子:

        线程类:


package com.xvshu.service.logic.dayinter;

import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Types;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.Callable;

/**
 * Created by Administrator on 2015/11/17.
 */
public<span style="color:#ff0000;background-color: rgb(255, 255, 153);"> class InsertBatchThread implements Callable<Integer> </span>{
    private final static Logger LOGGER = LoggerFactory.getLogger(InsertBatchThread.class);
    private final static String WST_TITLE = "WST_";
    private Integer vdate;
    private Integer uid;
    private Integer count;
    private Integer SUCCESS =-1;
    private Integer UNSUCCESS ;
    private List<Integer> intErrorUsers=new ArrayList<Integer>();

    private PushTaskHandler pushTaskHandler;

    public PushTaskHandler getPushTaskHandler() {
        if (null == pushTaskHandler) {
            synchronized (this) {
                if (null == pushTaskHandler) {
                    pushTaskHandler = SpringContextUtils.getBean("pushTaskHandler");
                }
            }
        }
        return pushTaskHandler;
    }

    private IWstTaskService wstTaskService;

    public IWstTaskService getWstTaskService() {
        if (null == wstTaskService) {
            synchronized (this) {
                if (null == wstTaskService) {
                    wstTaskService = SpringContextUtils.getBean("wstTaskService");
                }
            }
        }
        return wstTaskService;
    }

    private IOperateLogService operateLogService;
    public IOperateLogService getOperateLogService() {
        if (null == operateLogService) {
            synchronized (this) {
                if (null == operateLogService) {
                    operateLogService = SpringContextUtils.getBean("operateLogService");
                }
            }
        }
        return operateLogService;
    }

    private ProCommFundsInfoMapper proCommFundsInfoMapper;
    public ProCommFundsInfoMapper getProCommFundsInfoMapper() {
        if (null == proCommFundsInfoMapper) {
            synchronized (this) {
                if (null == proCommFundsInfoMapper) {
                    proCommFundsInfoMapper = SpringContextUtils.getBean("proCommFundsInfoMapper");
                }
            }
        }
        return proCommFundsInfoMapper;
    }


    private DataSource dataSource;
    public DataSource getDataSource() {
        if (null == dataSource) {
            synchronized (this) {
                if (null == dataSource) {
                    dataSource = SpringContextUtils.getBean("dataSource");
                }
            }
        }
        return dataSource;
    }

    /**
     * 无参构造函数
     */
    public InsertBatchThread(){

    }

    /**
     * 构造函数
     */
    public InsertBatchThread(Integer vdate, Integer uid, Integer count){
        this.vdate=vdate;
        this.uid=uid;
        this.count=count;
        this.UNSUCCESS=count;
        this.dataSource = getDataSource();
   
    }

    /**
     * 多线程规定好的方法,如果是继承Thread类则必须实现这个方法
     */
    public Integer call() {

		//初始化时间,计算整个函数耗时用
        long time = 0;
        long timestart = System.currentTimeMillis();

		//count能为-1否则无法查到数据
        if(count.equals(-1)){
            LOGGER.error("=call=>--count:[" + count + "]----count uis -1 ---------time:" + DateUtil.getCurrentTime());
            return null;
        }

        Integer pageSize =Constant.WMPS_INTERWRAP_BATCHUSERSIZE;
        Connection conn = null;
		
        //记录日志
        LOGGER.info("=call=>--count:[" + count + "]-------------begain time:" + DateUtil.getCurrentTime());
        int result = -1;
        try {

            conn= getDataSource().getConnection();
            conn.setAutoCommit(false);
            try {
                //操作
                List<Integer> listUsers = new ArrayList<Integer>();
                if(uid == null){
                    listUsers = getProCommFundsInfoMapper().selectUserForInsertBatch(vdate,count * pageSize, pageSize);
                }else{
                    listUsers.add(uid);
                }
                LOGGER.info("=call=>--["+count+"].[get -- listUsers] size:"+listUsers.size()+"-------------end time:" + DateUtil.getCurrentTime());

                LOGGER.info("=call=>["+count+"] beginuid:"+listUsers.get(0)+" enduid:"+listUsers.get(listUsers.size() - 1));

                time=System.currentTimeMillis();
                List<ProCommFundsInfo> listOnePage = getProCommFundsInfoMapper().selectForInsertBatchByUser(vdate, listUsers.get(0), listUsers.get(listUsers.size() - 1));
                LOGGER.info("=call=>--["+count+"].[get -- listOnePage] size:"+listOnePage.size()+"-------------spen time:" + (System.currentTimeMillis()-time));

                time=System.currentTimeMillis();
                List<ProCommFundsInfo> listOnePageNew = getProCommFundsInfoMapper().selectForInsertBatchByUserNew(vdate, listUsers.get(0), listUsers.get(listUsers.size() - 1));
                

                //给procommanlist赋id值与初始状态
                for(int i=0;i<listOnePage.size();i++){
                    if( listOnePage.get(i) == null ){
                        listOnePage.remove(i);
                        i--;
                    }
                    if(i>=0){
                        listOnePage.get(i).setId((int)SequenceHelp.get(SequenceHelp.D_PRO_COMM_FUNDS_INFO));
                        listOnePage.get(i).setStatus(BigDecimal.ONE);
                    }
                }

              
                //------------------------开始操作数据库:--------begin--------------------------

                 //批量插入proCommFundsInfo
                time=System.currentTimeMillis();
                proCommFundsInfoInsertBatch(listOnePage,conn);
                LOGGER.info("=call=>--["+count+"].[set -- proCommFundsInfoInsertBatch]size:"+listOnePage.size()+"-------------spen time:" + (System.currentTimeMillis()-time));

               
                //------------------------开始操作数据库:--------end------------------------
                //提交事务
                conn.commit();

                //TODO:单独处理发站内信的问题
                try {
                    //发息成功,发站内信
                    if(listOnePageDayInner!=null && !listOnePageDayInner.isEmpty()){
                        time=System.currentTimeMillis();
                        List<NoticetaskVoForBatch> listNotice = new ArrayList<NoticetaskVoForBatch>();
                        for (WmpsDayInter oneDayInter : listOnePageDayInner) {
                            NoticetaskVoForBatch oneNotice = new NoticetaskVoForBatch();
                            oneNotice.setNoticeNumber(Constant.NOTICE_YLB_HX);
                            oneNotice.setTitleParams(new Object[]{"恭喜您!您已成功收到口口口口的付息!"});
                            oneNotice.setUuid(createUUID(oneDayInter));
                            listNotice.add(oneNotice);
                        }
                        getLogicNoticetaskService().batchSendNotice(listNotice);
                        LOGGER.info("=call=>sendNotice spen time :"+(System.currentTimeMillis()-time));
                    }

                }catch(Exception e){
                    LOGGER.error("=call=>sendNotice error",e);
                    String wstDayInterNoticeUuid = "WST_DAY_INTER_NOTICE_"+ UUID.randomUUID().toString();
                    wstTaskService.objectsToWstTask(listOnePageDayInner,wstDayInterNoticeUuid, WstTaskTypeEnum.WST_INSERT_DAYINTER_NOTICE.getValue(),conn);
                    //发任务引擎
                    getPushTaskHandler().handlePushTask(JSON.toJSONString(wstDayInterNoticeUuid), WST_SENDNOTICE_INSERT_DAYINTER_TASK_TYPE,UUID.randomUUID().toString() , null, null);
                }
            }catch (Exception e){
                conn.rollback();
                result=UNSUCCESS;
                e.printStackTrace();
                LOGGER.error("=call=>error--["+count+"]-------------spen time:" +  (System.currentTimeMillis()-timestart),e);
            }

        }catch (Exception e){
            e.printStackTrace();
            LOGGER.error("=call=>error--["+count+"]-------------spen time:" + (System.currentTimeMillis()-timestart),e);
        }finally {
            try {
                if (conn != null) {
                    conn.setAutoCommit(true);
                    conn.close();
                }
            }catch (Exception e){
                LOGGER.error("=call=>error close conn",e);
            }
        }
        LOGGER.info("<=call=>--[" + count + "]-------------spen time:" + (System.currentTimeMillis() - timestart));

        return  result;
    }

    /**
     * 批量插入procomm
     * @param listOnePage
     * @param conn
     * @return
     * @throws Exception
     */
    public int[] proCommFundsInfoInsertBatch(List<ProCommFundsInfo> listOnePage ,Connection conn) throws Exception{
        <span style="color:#cc0000;background-color: rgb(255, 255, 153);">int[] results = new int[0];
        PreparedStatement ps = conn.prepareStatement("INSERT ignore  INTO d_口口口口口口info" +
                "(uid,income,rid) values" +
                "(?,?,?)");
        try {
            for (int i =0 ;i< listOnePage.size();i++) {
                ProCommFundsInfo oneObj = listOnePage.get(i);
                if (oneObj.getUid() == null) {
                    ps.setNull(1, Types.INTEGER);
                } else {
                    ps.setInt(1, oneObj.getUid());
                }

                ps.setBigDecimal(2, oneObj.getIncome());

                if (oneObj.getRid() == null) {
                    ps.setNull(3, Types.INTEGER);
                } else {
                    ps.setInt(3, oneObj.getRid());
                }
                ps.addBatch();
            }
            int[] result = ps.executeBatch();
            results = ArrayUtils.addAll(results,result);

        }finally {
            ps.close();
        }
        return results;</span>
    }

  






}

前台控制类

private Boolean insertDayInter(Integer vdate, Integer uid ) {

		if(isPushTask){
			LOGGER.info("=insertDayInter=> is PushTaskEngine call This Method ");
		}
		//记录日志
		OperateLog operateLog = operateLogService.selectLogForCheckInter(vdate);
		//判断是否插入过日志
		if(operateLog !=null){

			LOGGER.info("=insertDayInter=> operateLog is not null ");
			//标记成功,直接返回
			if(operateLog.getStatus()!=null && operateLog.getStatus().equals(Constant.OPERATELOG_STATUS_SUCEESS)){
				return true;
			}else if (operateLog.getStatus()!=null && operateLog.getStatus().equals(Constant.OPERATELOG_STATUS_PROCESSING)){
				//时间间隔小于1小时则直接返回
				Long time =  DateUtil.getNowDate().getTime() - operateLog.getModified().getTime();
				if(time<3600000){
					return true;
				}
			}
			operateLog.setStatus(Constant.OPERATELOG_STATUS_PROCESSING);
			operateLog.setModified(new Date());
			operateLogService.updateByPrimaryKeySelective(operateLog);
		}else{
			LOGGER.info("=insertDayInter=> operateLog is  null ");
			operateLog = new OperateLog();
			operateLog.setId((Long) SequenceHelp.get(SequenceHelp.OPERATE_LOG));

			if(uid==null){
				operateLog.setLogType(Constant.OPERATELOG_LOGTYPE_INTERWRAP);
			}else{
				operateLog.setLogType(Constant.OPERATELOG_LOGTYPE_TEST_INTERWRAP);
				operateLog.setRemark("uid:"+uid);
			}

			operateLog.setStatus(Constant.OPERATELOG_STATUS_UNTREATED);
			operateLog.setPri(Constant.OPERATELOG_PRI_ONE);
			operateLog.setAutoRedo(Constant.OPERATELOG_AUTOREDO_TRUE);
			operateLog.setCreated(new Date(vdate*1000L));
			operateLog.setModified(new Date());

			operateLogService.insertForDayInter(operateLog);
		}

		if (vdate == null || vdate.intValue() <= 0 ) {
			LOGGER.error("=insertDayInter=>error,vdate=" + vdate + ",uid=" + uid);
		} else {
			int totalNum = 0;
			if(uid ==null){
				totalNum = proCommFundsInfoService.countForInsertBatchByUser(vdate);
			}else{
				totalNum=1;
			}

			if(totalNum==0){
				LOGGER.error("=insertDayInter=>error,vdate=" + vdate + ",uid=" + uid+" totalNum is 0 ");
				return true;
			}

			int startRow = 0;
			List<FutureTask<Integer>> listFutures = new ArrayList<FutureTask<Integer>>();

			//线程发息,每个线程处理n个用户
			getTaskExecutor();
			try {


				LOGGER.info("=insertDayInter=> insert proCommFundsInfo begin");
				<span style="color:#ff0000;background-color: rgb(255, 255, 153);">for (int i = 0; i <= Math.floor(totalNum / Constant.口口_口口_口口); i++) {
					FutureTask<Integer> futureTask = new FutureTask<Integer>(new InsertBatchThread(vdate, uid, i));
					taskExecutor.execute(futureTask);
					listFutures.add(futureTask);
				}</span>

				LOGGER.info("fixedThreadPool is ok");
				if(listFutures !=null && !listFutures.isEmpty()){
					for(FutureTask<Integer> ontFuture : listFutures){
						Integer threadReturn  = ontFuture.get();
						if(threadReturn ==null ){
							LOGGER.error("FutureTask is null "+listFutures.indexOf(ontFuture));
							listException.add(listFutures.indexOf(ontFuture));
						}else {
							if (!threadReturn.equals(-1)) {
								listException.add(threadReturn);
							}
						}
					}
				}
				result =true;

				operateLog.setStatus(Constant.OPERATELOG_STATUS_SUCEESS);


				operateLog.setLogData("SUCEESS");
				operateLog.setModified(new Date());
				operateLogService.updateByPrimaryKey(operateLog);
				LOGGER.info("=insertDayInter=> insert proCommFundsInfo end");
			}catch (Exception e){
				operateLog.setStatus(Constant.OPERATELOG_STATUS_FAILURE);

				result = false;
				if(listException != null && !listException.isEmpty() ){
					operateLog.setLogData(com.el.wst.soa.common.utils.StringUtils.ListToString(listException));
				}else{
					operateLog.setLogData("SUCEESS");
				}
				operateLog.setModified(new Date());
				operateLogService.updateByPrimaryKey(operateLog);

				LOGGER.error("<=insertDayInter=>error",e);
				e.printStackTrace();
			}

		}
		LOGGER.info("<=insertDayInter=>");
		return result;
	}

spring线程池:


 <!-- 线程池 -->
    <bean id="taskExecutor"
          class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="20" />
        <property name="maxPoolSize" value="50" />
        <property name="queueCapacity" value="500" />
        <property name="keepAliveSeconds" value="300" />
        <property name="rejectedExecutionHandler">
            <bean class="java.util.concurrent.ThreadPoolExecutor$DiscardOldestPolicy" />
        </property>
    </bean>


3,结果

综上所做,可以讲百亿交易量的处理,控制在20分钟内完成(测试10万用户,5个产品,5分钟完成),测试环境:双核四线程,普通pc

总结

      经过这一路的走来,验证了一个道理,万事有利必有弊,这样我们虽然可以提升效率,但是代码可读性,扩展性上,有了瓶颈,如果谁有更好的方案,欢迎交流,交流中,我们一起来探讨更大,更复杂数据的处理。


声明:以下内容为技术分享,涉及公司真实业务部分已经掩盖,代码不可直接执行,请大家注意。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值