将数据从源端经过抽取(extract)、转换(transform)、加载(load)至目的端的过程。
什么是紫燕ETL项目?紫燕ETL项目是指将各个外卖平台的订单经过拉取、订阅等方式汇聚到中间库,然后经过一定的转换之后通过消息中间件技术推送到门店终端POS系统,进而实现方便、快捷的外卖接单业务;同时提供通过门店POS终端管理外卖平台的门店开、歇业及菜品上、下架功能;
这里从业务执行顺序作为切入点阐述整个系统的技术架构设计;
数据库(MongoDB) Model => MongoTemplate(DAL)=> Service(BLL)=> Controller(Restful Web API)=> AngularJS
MongoDB:非关系型数据库(NO SQL),它具有快速、高拓展性、Failover机制、Json格式存储等优势;
MongoTemplate:用于操作MongoDB的API
Service:业务处理层,对MongoDB的增删改查、四大外卖平台接口调用与业务处理均在这一层,该层又分为ApiService、InnerService、FacadeService,他们之间的调用关系如下图所示:
Controller:这一层是直接与UI展示层交互,主要用到Spring mvc @Controller 、 @RequestMapping、@ResponseBody、@RequestBody 这4个注解,@Controller用于完成Bean创建和自动依赖注入的功能;@RequestMapping是用来处理请求地址映射的注解,用于指定请求的实际地址、请求方式(GET/POST等)、http请求Header、ContentType、Charset等;@ResponseBody将内容或对象作为HTTP响应正文返回;@RequestBody将Http请求正文转换为合适的HttpMessageConverter对象(值得一提的是这里我们用的HttpMessageConverter工具类是Gson的GsonHttpMessageConverter,并对GsonHttpMessageConverter的Gson对象属性做了增强,详见增强类GsonHttpMessageConverterEx);引入REST风格Web Service来构架更加优质的Web架构,更加正确的使用Web标准(HTTP、URI等)。
Interceptor(拦截器):通过HandlerInterceptorAdapter抽象类实现,系统通过AccessController类覆写preHandle、afterCompletion等方法实现白名单等请求过滤与拦截功能;具体可以通过查看任何一个Rest服务调用堆栈(如图所示);
值得讨论:结合紫燕外卖这块的业务场景,白名单过滤真的有效吗?
AngularJS:优秀的Web前端框架,这里不再赘述。
异常处理设计方面总体包含2个维度:区分平台&已知异常和未知异常
考虑到外卖订单的时效性和业务的重要性,系统异常处理显得非常重要;同时考虑到各个外卖平台业务与接口的差异性,需要我们能够很直观的辨别出具体是哪个外卖平台发生的异常,进而路由到各自平台开发对接人迅速处理异常;故平台引入了5种自定义异常类:BaiDuException(百度模块异常)、JdHomeException(京东到家模块异常)、MeiTuanException(美团模块异常)、ElemeException(饿了么模块异常)、ScheduleException(框架异常),其他系统级异常统一用基类Exception捕获;
异常抛出(不捕获任何内部调用异常):设计原则ETL不会向任何上下游系统抛出不友好的代码异常信息,系统所有已知和未知异常会在ApiService和InnerService(请结合Service层图示)层向上层调用者throws 异常,已知异常(比如 throws BaiduException),自开发框架异常(throws ScheduleException)、其他工具类和系统级框架异常(IOException、NoSuchAlgorithmException等)
异常捕获:所有的异常捕获统一放在各自平台的FaceadeService层捕获,并记录异常;异常代码:-997表示已知的平台接口调用异常,-999表示未知的自开发模块代码异常,-998其他系统或三方工具类调用异常;
异常日志:考虑到异常信息最终要推送到EKP系统,并以微信消息的方式推送到开发负责人手机上,开发者在第一时间能够看到这条异常,进而快速响应问题,故异常信息不考虑选择用文件的方式记录而直接存储到MongoDB数据库中,然后借助JMS把日志消息推送到EKP系统;
代码目录及配置:
${mq.topic.destination.waimai.order}
应用场景:
1、ETL在接收到外卖平台推送过来的外卖订单后,经过一定的转换后需要将订单信息推送至门店POS系统,商家进行接单操作;
2、当外卖平台的订单状态发生改变时,外卖平台会把订单的状态推送到ETL,由ETL转发给门店POS系统;
3、系统异常日志推送至微信企业号;
工具类:JmsTemplate,封装了常用的Java Message Service API,大大减少了收、发消息这块的精力投入;
消息发送模式:
Queue(队列模式):点对点推送模式;
Topic(Subscribe/Publish):订阅发布模式;
Topic和queue的最大区别在于topic是以广播的形式,通知所有在线监听的客户端有新的消息,没有监听的客户端将收不到消息;而queue则是以点对点的形式通知多个处于监听状态的客户端中的一个,结合紫燕ETL应用场景这里选择的是Topic模式;
消息生产者:用于产生消息,并将消息发布至mq服务器(Broker),通过调用JmsTemplate对象的send方法发送消息至MQ服务端(参考TopicMessageProducer);
Broker(中间人):消息协调器,俗称MQ的服务端;
消息消费者(Producer):负责向Broker订阅消息,消费端可以是跨平台的,参考TopicMessageConsumer;
消费者过滤(Consumer):生产者的消息虽然是以广播的形式发出去的,但其实并不希望任何消费者都可以收到的,例如外卖平台订单信息,生产者只希望紫燕的门店的pos系统才能够订阅,非pos端是不希望能够订阅到的;同时A门店的外卖订单是不希望被B门店收到的,topic模式是不可以满足我们的需求呢?答案是肯定的,首先需要我们在定义ConnectionFactory的时候添加属性clientIDPrefix,凡是有该属性的
MQ服务器监控(心跳模式): org.apache.activemq.transport.TransportListener
问题:使用JmsTemplate发送消息时,当前的线程也会被wait直到重新连接ActiveMQ服务器成功,这种情况可能会导致线程被耗尽的危险
整个项目是一个Maven工程项目,使得系统不同构件之间的依赖管理更加方便、快捷,同时能够有效规避不同构件版本冲突、依赖臃肿等问题,通过一各pom.xml文件全部搞定;使用Jetty作为内嵌Servlet 容器启动项目;
IDE:IDEA version 14.0.2
构建工具:Maven
Web 容器:Jetty version 9.3.4.RC1
运行方式:2种运行方式,一种是直接再IDEA环境下调试运行,另一种是以shell脚本方式运行(运行./bin/ctl.sh);
author:杨大山 qq技术交流群:242573682