架构分析与设计到实现的过程展示

一、需求

2017年底开始,有一个项目计划,建设云数据中心,然后基于数据开拓增值服务。

背景:略。

要建议云数据中心,第一个就是要先把源端的数据同步回来。源端就是第三方,向我们开放数据服务,我们可以根据我们的用户,把我们用户相关的信息从源端同步到云端。这种场景就好像是:开办信用卡的代理只能通过它的客户(信用卡申请人)授权,才能查看客户的信贷信息。

源端的主要约束,除了上述的外,还有:

1)  只能在每个月的16号到月底可以同步。

2)  每次只能同步一种数据(据源端方面的人所说,一种数据其实就是对应他们的一张表)。数据有700多种,也就是有700多个数据接口,分三类,A类有20多种数据,B类有10多种数据,C类有700多种数据。

3)  只接受我们的10个并发请求。

4)  都是日志类的数据,数据接口的每次调用必须设置时间标识。时间的跨度越大,返回的数据量就越大。

我们的主要约束(要求):

1)  数据保存在公有云,数据库是MySQL。

2)  由于某种原因,我们必须采用定时任务方式同步数据。定时方式,我们又称为任务策略。

3)  不提供操作界面。

下面,就数据同步子系统的建设进行分析与设计。

二、分析与设计

1、  流程分析与设计:

1)初步分析与结果:

因为我们必须根据我们手上的用户信息,才能把源端的数据同步回来,因此,我们必须有个初始化步骤,把我们用户关键信息还有源端的数据种类导入来。接着,就是启动任务将数据同步回来,再将同步回来的数据根据数据种类不同而分开保存(也就是说,每个源端的数据种类,在云端都有一个对应的数据表)。流程是这样的:

架构分析与设计到实现的过程展示_第1张图片

每次不能同步太多的数据回来,因此,我们决定按数据类型、数据种类、时间标识分批将数据同步回来,另外,将原来一个流程拆分为初始化与数据同步(任务)两个流程。

初始化流程:


数据同步(任务)流程:


架构分析与设计到实现的过程展示_第2张图片

于是,得出粗略的第一个业务建模版本:

“签约用户”、“待同步数据种类列表”与“源数据获取”有关联,为了解耦,设计了一个新的业务对象“签约用户待同步数据”,也就是说,“签约用户待同步数据”是由“签约用户”和“待同步数据种类列表”组成。

“同步任务”是由“源数据获取”、“同步日志”、“任务策略”组成。

第一版业务建模:

架构分析与设计到实现的过程展示_第3张图片

设计第一版本的接口与交互:

数据初始化流程,在系统初始化时发生,也就是签约用户数据与源数据种类的数据导入,计划手工完成,因此不设计接口;并且,即使不是手工导入,也不考虑接口设计,只关注关键的部分,也就是通过数据同步任务将数据取回来,于是设计了数据同步任务相关接口与交互,如下图:

架构分析与设计到实现的过程展示_第4张图片

2)迭代一

根据上述的接口设计,再优化业务模型:

增加了“调用上下文”业务对象,使任务与数据保存解耦。如下图所示:

架构分析与设计到实现的过程展示_第5张图片

因为已经打算将“数据保存”从“任务”同解耦出来,不过,在流程设计中还没有体现出来,当然接口设计中也没有体现出来,于是,重新设计数据保存(任务)的流程与接口。

任务流程:

架构分析与设计到实现的过程展示_第6张图片

数据保存流程:

架构分析与设计到实现的过程展示_第7张图片

任务与数据保存接口设计与交互:

可以看到,在上下文中执行任务并保存数据。

架构分析与设计到实现的过程展示_第8张图片

将关键数据持久化,设计关键的数据ER模型如下:

架构分析与设计到实现的过程展示_第9张图片

2、  功能设计:

系统不要求提供界面,我们主要实现三大功能:任务调度服务器,任务执行,数据同步(保存)。

细分。略。

3、  构架划分:

通过上述的分析,我们已经将业务流程与模型、业务接口都已经设计出来了。接着,我们可以考虑子系统(模块)的划分了。一般情况下,在业务分析过程中,对于一个熟悉开发技术的架构师来说,会一边脑补构架如何实现,会对业务进行切分(也就是分出子系统)。出于解耦目的,对于数据同步的任务调度器,我们当然希望是独立的与业务无关,它的作用就是发送一个通知启动任务;任务被触发后,调用接口获取数据;获取数据后,将数据保存到数据表。因此基本可以确定,系统分为三部分。逻辑架构如下图所示:

架构分析与设计到实现的过程展示_第10张图片

在数据获取成功后,就通过MQ把数据传到数据保存服务将数据持久化。

4、  再迭代

因为通过MQ解耦了,接口与交互的设计必须改进:

架构分析与设计到实现的过程展示_第11张图片

在数据同步方面,收到任务中发过来的消息(数据)后,保存在数据库表中:

架构分析与设计到实现的过程展示_第12张图片

5、  技术选型

关键技术:

任务调度服务器:选择开源的实现XXL-JOB

开发技术:JAVA

消息中间件:RabbitMQ

数据存储服务:DRDS

四、接口定义:

关键接口定义。通过上一节的分析与设计,已产出了任务与数据保存接口,接着就是根据设计进行接口定义。

要定义的接口有:

触发任务、任务执行、记录任务日志、选择任务策略、用户待同步信息、同步数据、执行源数据获取请求、记录同步日志、记录最终失败日志、保存已获取到的数据、表数据保存、记录数据保存日志、消息发送、消息接收。

其中,触发任务与选择任务策略,已由XXL-JOB提供并已实现,那么,只需要定义其它的接口即可。

1)  任务执行

publicinterface ISyncTask {

/**任务执行接口:
* 向源端发起数据获取调用{@linkjob.excutor.api.sync.IDataSyncService#sync()},记录数据获取调用的日志{@linkjob.excutor.api.logs.IDataSyncLogService#log4DataSync(Object)},同时还要保存获得的数据通过MQ传到数据保存子系统的数据库中{@linkjob.excutor.api.persist.IDataSaveService#saveSyncData(java.io.Serializable)} 或者{@job.excutor.api.persist.IDataSaveService#saveSyncData(java.util.List)}。
* 初始化同步与增量同步,具有共通性,
* 因此,可将初始化同步与增量同步定义为同一个方法。
*
* * @paramt java.io.Serializable, 是任务调度服务器传过来的参数(建议带上任务每次调用的UUID,这样可以实现任务结果的异步通知。)。 *@return java.io.Serializable, 自定义的数据结构体,数据体包括两部分:
* 1)任务的执行结果2)其它。 */ AgetData4Save(T t); }

 

2)  记录任务日志

public interfaceITaskExeLogService {

/**记录任务执行前日志。
* * @paramk 任务每次执行的UUID,自动生成。 * @parammsg Object,日志信息 *@return Boolean 任务执行的结果,成功还是失败 */ Boolean preExeLog(K k, Object msg); /**记录任务执行后日志。
* 主要是记录任务执行后的结果。 * * @paramk 任务每次执行的UUID,用于定位任务,写回结果。 * @parammsg Object,日志信息 *@return Boolean 任务执行的结果,成功还是失败 */ Boolean afterExeLog(K k, Object msg); }

 

3)  用户待同步信息

publicinterface IUserParamsService{

/**获取用户待同步信息。用于构造入参调用源数据服务接口。
* 用户待同步信息是保存在数据库中。 *@returnList, P自定义数据体,必须包括两方面信息:用户信息,那种源数据类型 */ List

getUserParams(); }


 

4)  同步数据

public interface IDataSyncService {

/**数据同步接口:
* 数据同步由源端数据获取{@linkjob.excutor.api.fetch.IDataFetchService#getDataFromOutside(java.io.Serializable)}、 * 同步日志{@linkjob.excutor.api.logs.IDataSyncLogService#log4DataSync(Object)}、数据保存{@linkjob.excutor.api.persist.IDataSaveService#saveSyncData(java.io.Serializable)} 或者 {@linkjob.excutor.api.persist.IDataSaveService#saveSyncData(java.util.List)}, * 三大部分组成。
* @paramparam Serializable 从任务上下文环境中传过来的参数,用于发送Remote请求获得数据。 *@return boolean */ boolean sync(java.io.Serializable params); }

 

5)  执行源数据获取请求

public interface IDataFetchService {

/**从源端获取数据的接口:
* 向源端发起数据获取调用,记录数据获取调用的日志{@linkjob.excutor.api.logs.IDataSyncLogService#log4DataSync(Object)}。
* 待同步的信息从数据库中获取,接着根据这些信息通过远程调用获取局端数据。 *
* * @paramt java.io.Serializable, 数据获取时的参数:
* 主要是从任务调用服务器带来的参数。建议带上任务每次调用的UUID,这样可以实现任务结果的异步通知。
* *@return java.io.Serializable, 自定义的数据结构体,数据体包括:
* 从源端取回来的数据。 */
AgetDataFromOutside(java.io.Serializable t); }

 

6)  记录同步日志

 

publicinterface IDataSyncLogService {

/**记录数据同步请求的日志。

 * @paramdataFetchParams Object, 发送数据获取请求的参数结构体,包括签约用户待同步信息。

 *@return Boolean, 数据同步是成功还是失败

 */

Boolean log4DataSync(Object dataFetchParams);

}

7)  记录最终失败日志

publicinterface IFinalFailureLogService {

/**数据同步失败(包括数据获取与数据保存)最终失败,记录关键信息,
* 用于追查与人工介入处理。
* * @paramfailureDetailsInfo 可用于追查与人工介入处理的信息,如:
* 签约用户、尖端数据种类、同步的时间节点,任务UUID等。 */ void logSyncFailureLog(ObjectfailureDetailsInfo); }


8)  保存已获取到的数据

public interfaceIDataSaveService {

/**将从源端取得的目标数据保存到本地数据库中。
* @paramt java.io.Serializable 数据表的entity *@return Boolean 数据保存操作的结果,成功还是失败。 */ BooleansaveSyncData(T t); /**将从源端取得的目标数据并批量保存到本地数据库中。
* @paramlist List,数据表的entity的列表 *@return Boolean 数据保存操作的结果,成功还是失败。 */ BooleansaveListSyncData(List list); }

 

9)  表数据保存

publicinterface ITableSaveService{

 

/**将从源端取得的目标数据保存到本地数据库中。
* @paramt java.io.Serializable 数据表的entity *@return Boolean 数据保存操作的结果,成功还是失败。 */ boolean save(); /**将从源端取得的目标数据并批量保存到本地数据库中。
* @paramlist List,数据表的entity的列表 *@return Boolean 数据保存操作的结果,成功还是失败。 */ BooleansaveList(List list); }

 

10)             记录数据保存日志

publicinterface ITableSaveLogService{

 

/**

 * @parammsg

 *

 */

void saveLog(Object msg);

}

 

五、任务执行接口实现

由于任务是由记录任务日志、选择任务策略、用户待同步信息、同步数据组成;

而同步数据是由执行源数据获取请求、记录同步日志、记录最终失败日志组成;

保存已获取到的数据是由表数据保存、记录数据保存日志、记录最终失败日志组成。

1、  “同步数据”接口实现

publicclass DataSyncService implements IDataSyncService {

                                                                                 

private IDataFetchService djDataFetchService;

private IDataSyncLogServicedjDataSyncLogService;

private IFinalFailureLogServicedjFinalFailureLogService;

private IDjDataSaveService djDataSaveService;

 

/* (non-Javadoc)

 * @see job.excutor.api.sync.IDataSyncService#sync()

 */

@Override

public boolean sync(Serializable params) {

           // TODO Auto-generated method stub

           Listlist=this.getRequestParamsList(params);

           for(Serializable param:list){

                    Serializableresult=djDataFetchService.getDataFromOutside(param);

                    booleansuccessFlag=djDataSyncLogService.log4DataSync(result);

                    if(!successFlag){//如果最终还是失败,记录日志,用于人工介入处理

                             djFinalFailureLogService.logSyncFailureLog(this.toFinalFailureLog(param,result));

                             return false;

                    }

                   
                    successFlag=djDataSaveService.saveSyncData(toTableData(result));

                    if(!successFlag){//如果数据保存失败,记录日志,用于人工介入处理

                             djFinalFailureLogService.logSyncFailureLog(this.toFinalFailureLog(param,result));

                             return false;

                    }

           }

          
           return true;

}

/**数据获取最终失败,记录失败信息,使可用于人工介入处理:纠正错误,根据参数重新再发送请求。

 * @paramparam Serializable 数据获取请求时所携带的参数

 * @paramresult Serializable 数据获取返回的(失败)结果

 *@return 失败日志数据结构体

 */

private Object toFinalFailureLog(Serializableparam, Serializable result){

           //todo ...

           return null;

}

/**将获取的数据待久化到数据表。
* * @paramresult Serializable 数据获取返回的(成功)结果 * @returnList 表entity列表 */ private ListtoTableData(Serializable result){ // todo ... return null; } /**根据任务执行传入的参数,构造签约待同步数据列表,用于进行下一步的数据获取请求。 * @paramparams Serializable 任务执行传入的参数 *@return List */ private ListgetRequestParamsList(Serializable params){ // todo ... return null; } }

 

2、  “任务执行”接口实现

publicclass DjSyncTask implements ISyncTask {

private IDataSyncService djDataSyncService;

private ITaskExeLogService djTaskExeLogService;

 

/* (non-Javadoc)

 * @seejob.excutor.api.task.ISyncTask#getData4Save(java.io.Serializable)

 */

@Override

public  AgetData4Save(Serializable t) {

           // TODO Auto-generated method stub

           Object preMsg=null; // toDo ...

           djTaskExeLogService.preExeLog(getTaskUUID(t),preMsg);

          

           booleansuccessFalg=djDataSyncService.sync(t);

          

           djTaskExeLogService.afterExeLog(getTaskUUID(t),createMsg(successFalg));

          

           // todo ...

           // 构造结果消息数据体,并返回

          

           return null;

}

private Object getTaskUUID(Serializable t){

           //todo ...

           return null;

}

private Object createMsg(boolean success){

          

           return null;

}

 
}

 

其它的接口实现,略。

根据接口设计与业务建模,可以灵活地实现接口。如:数据同步接口可以看作是数据保存接口的代理,可以应用代理设计模块。数据保存,我只设计一个接口方法,而调用这个接口时,要根据源数据种类分别保存到700多张表中去,这里可以应用工厂模式。任务执行,在这个接口被调用的前后,我都会记录日志,那么这里可以应用模板模式。

六、接口调用,实现业务

接口调用,实现业务的执行是上下文业务对象

public class JobHandler {

        

         private ISyncTask djSyncTask;

 

 

         publicvoid execute(String param) throws Exception {

                   //TODO Auto-generated method stub

                   SerializabletaskResult=djSyncTask.getData4Save(param);

                  

                   //todo...

 

         }

 

}
 

注意,在业务建模中,上下文对象还调用了“数据同步”接口,不过由于数据获取与数据保存是在两个进程中,通过MQ通讯,因此,“数据同步”这个接口,并不是在JobHandler中被调用,而是在另一个上下文的实现类中实现(略)。




你可能感兴趣的:(架构)