拍拍贷技术架构体系

拍拍贷公司成立于2007年6月,其技术体系最早基于微软.Net平台搭建,到了2016年,开始慢慢转向了Java平台,微服务开发也选择了当前业界流行的Spring Boot/Spring Cloud体系。在搭建微服务平台的过程中,拍拍贷基础架构团队并未全盘接纳Spring Cloud技术组件,更多是取其精华,兼并选择业界生产部署验证过成熟的组件。到现在2018年,微服务平台的搭建已过去快2年,整个技术体系已渐渐成型。

本文对当前拍拍贷的逻辑架构体系和微服务架构设计进行简单介绍,以供参考。

1. 逻辑架构体系

拍拍贷的逻辑架构体系如下图所示,

从上到下,分别有,

  • UI用户界面:提供Web应用和前端页面、移动应用、H5页面,以便于用户各个渠道的访问
  • Edge接入层:这一层承接外部所有用户的访问流量,负载均衡,并进行HTTPS的卸载,校验用户请求的合法性。同时,三大网关(API、移动、H5)还承接内部应用服务域名的调用访问流量。
  • Service服务层:主要的业务逻辑应用服务层
  • Platform平台层:包括各大Java服务中间件,微服务开发框架和平台,支撑整个业务服务应用的开发和运营。
  • Data Storage数据存储:数据存储层,主要包括三种:关系型数据库的MySql/MS Sql Server(目前逐渐从MS Sql Server往MySql迁移),缓存Redis,大数据的HBase。

2. 微服务架构设计

拍拍贷的微服务架构基于Spring Cloud,但很多组件选择了业界在生产环境已验证过、比较成熟的技术方案,比如配置中心、调用链监控、监控告警等。

整个微服务架构设计如下,

主要组件模块的功能如下,

  • 网关Zuul 1.0:提供服务路由、负载均衡、访问安全控制、熔断限流
  • 注册中心Eureka:服务注册和发现
  • 配置中心Ctrip Apollo:应用环境配置
  • 微服务Spring Boot:应用服务开发
  • 发布系统:通过网关实现应用服务的上下线、灰度发布、蓝绿发布
  • 调用链监控Dianping CAT:应用服务的监控埋点
  • 数据收集器Kafka:进行ELK的日志分析,收集运营Metrics到KairosDB
  • 运营指标KairosDB:存储相关的运营指标,包括业务、系统和运维指标,比如用户的访问量、消息的消费吞吐量、zabbix的运维监控数据等。
  • 运营看板Grafana:对接ELK/KairosDB/Zabbix,展现数据看板
  • 告警Zalando Zmon:健康和熔断告警

该图可以和Spring Cloud的微服务架构图相比较,可以看到拍拍贷在搭建微服务架构所做的改变,

  1. 添加发布系统,对接注册中心和网关,实现微服务实例的无缝上下线,实现灰度发布要求,为微服务的持续交付打下基础
  2. 添加ELK日志分析,完善监控告警,通过Grafana提供运营看板,确保微服务运营的快速、简单和稳定。在Spring Cloud技术体系中,其运营和监控是短板,ELK和Grafana作为业界流行的日志分析和运营看板组件,具有高可扩展性。
  3. 内部应用服务调用并未选择直连模式,而是通过服务域名走网关,简单可靠,其适合当前拍拍贷的业务运营需求。

3. 参考资料

  1. 拍拍贷的关于我们 https://www.ppdai.com/help/aboutus/

Spring Cloud微服务架构

Spring Cloud是一个面向分布式系统构建的技术体系,为开发人员提供了构建分布式系统所需的核心和外围组件,其基于Spring Framework和Spring Boot技术框架,使得能够“开箱即用”,快速搭建如下微服务架构所需的功能,

  • 服务注册和发现
  • 服务路由
  • 负载均衡
  • 访问安全控制
  • 应用配置
  • 熔断限流
  • 调用监控
  • 健康检查

一个由Spring Cloud所搭建的微服务架构体系如下图所示,

主要组件模块的功能如下,

  • 网关Zuul:提供服务路由、负载均衡、访问安全控制、熔断限流
  • 注册中心Eureka:服务注册和发现
  • 配置中心Config:应用环境配置
  • 微服务Spring Boot:应用服务开发
  • 流量监控Turbine:流量监控、熔断限流监控
  • 监控看板Spring Cloud Dashboard:应用健康状态、心跳检查、熔断限流看板
  • 调用链监控Sleuth:通过日志跟踪调用链
  • 日志分析ELK:日志分析
  • 告警:健康和熔断告警

图中Spring Cloud组件都用相应的图标标记,注意的是,日志分析和告警两个模块组件,Spring Cloud尚未涉及相关组件,需要对接第三方所提供的组件模块。对于这两个组件,图中参考了拍拍贷基础架构设计,分别选用了ELK和Zalando Zmon,详细请见下篇拍拍贷的技术架构体系设计

参考资料

  1. Spring by Pivotal https://spring.io/

Quartz任务调度框架的四种应用方法

图片来自pixabay.com的maxmann-665103会员

Quartz是一个任务调度框架,其框架中定义了如下三个基本组件,

  • Job 执行任务
  • Trigger 触发器,任务触发条件(周期性、定时等)
  • Scheduler 调度器,接受触发器和执行任务对象,实现任务的定时、远程、分布式调度

Quartz作为一个优秀的任务调度框架,其支持从简单的单一应用定时任务,到复杂的远程任务和集群(分布式)任务调度,支持任务调度功能的高可用、高性能、高可扩展性部署。

本文将给出Quartz的代码使用样例,演示了从简单到复杂应用场景的使用方法,供快速应用时参考,

  1. 简单的定时任务
  2. Cron定时任务
  3. 远程任务调度
  4. 集群(分布式)任务调度

1. 简单的定时任务

一个简单的定时任务样例代码见这里,其分三个主要步骤,

  • 创建job/trigger/scheduler三个对象
  • 初始化任务调度器,通过scheduler.scheduleJob(job, trigger)绑定触发器和执行任务
  • 启动调度器scheduler,通过scheduler.start()实现任务的定时触发。

详细演示步骤见下文的完整代码样例。

下面是简略代码,

JobDetail job = JobBuilder.newJob(HelloJob.class)
        .withIdentity("MyJob", "MyGroup")
        .build();

// a trigger at an interval of 10 seconds with 10 repeated times
Trigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("MyTrigger", "MyGroup")
        .startAt(new Date())
        .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(10)
                .withRepeatCount(10))
        .build();

// scheduler job and start
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.scheduleJob(job, trigger);
scheduler.start();

//sleeping for scheduler to run job
Thread.sleep(60 * 1000L);

// shutdown the scheduler gracefully
scheduler.shutdown(true);

这里需要说明的是,

1) Job/Trigger的唯一标识

Job/Trigger有组group和名字name的联合唯一标识,在一个触发器中不允许有两个相同标识的Job/Trigger存在。若有的话,会抛出如下异常信息,

Exception in thread "main" org.quartz.ObjectAlreadyExistsException: Unable to store Job : 'MyGroup.MyJob', because one already exists with this identification.

2) Scheduler任务线程池

一旦执行scheduler.scheduleJob(job, trigger)之后,则会默认启动10个任务工作线程,整个scheduler的状态为,

Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.

这个时候调度器还未正式启动任务调度,需要执行scheduler.start()后才真正开始运行。

通过jvisualvm.exe工具可以查看scheduler.scheduleJob()之后的线程情况,

注意的是,若任务结束后,程序没有调用scheduler.shutdown()的话,则整个程序还会一直在运行中。

2. Cron定时任务

一个cron定时任务样例代码见这里,其步骤和上述代码样例差不多,不一样之处在于触发器的定义从SimpleTrigger变成了CronTrigger,

// a cron scheduler at every 10 seconds
CronTrigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("MyTrigger", "MyGroup")
        .startAt(DateBuilder.evenMinuteDate(new Date()))
        .endAt(new Date(System.currentTimeMillis() + 60 * 1000))
        .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
        .build();

详细演示步骤见下文的完整代码样例。

Cron表达式非常灵活,其可以覆盖广泛的定时任务定义,下面是一些常见的任务定时表达式样例,

Cron表达式 含义
0/20 * * * * ? 每隔20秒
15 0/2 * * * ? 每隔2分钟的第15秒,例如00:02:15,00:04:15,依次类推
0 0/2 8-17 * * ? 每天08-17点,每隔2分钟
0 0 10am 1,15 * ? 每月的第1天和第15天,上午10:00触发
0,30 * * ? * MON-FRI 周一到周五,每到0,30秒时触发,比如周一的00:00:00,00:00:30,依次类推。
更多周天代码,周一MON,周二 TUE,周三 WED,周四 THU,周五 FRI,周六SAT,周日 SUN
0,30 * * ? * SAT,SUN 周六和周日,每到0,30秒时触发

3. 远程任务调度

在很多情况,任务的调度和执行并不在同一台机器,这个时候可以使用远程任务调度功能。一个远程任务调度的代码样例见这里。其主要包括两个步骤,

  1. 启动调度服务器,准备接受调度任务,代码样例
  2. 启动任务调度客户端,发送任务到远程调度服务器,代码样例

若从代码上分析,其和上述的单机版本任务调度并无区别,远程任务调度的不一样之处在于启动配置文件。下面为两个简略启动脚本,

java -Dorg.quartz.properties=server.properties -classpath "./demo-quartz.jar" com.pphh.demo.remote.RemoteSchedulerServer
java -Dorg.quartz.properties=client.properties -classpath "./demo-quartz.jar" com.pphh.demo.remote.RemoteSchedulerClient

可以看到,启动配置直接影响着调度器的行为,配置调度器实现远程调度功能,包括客户端和服务端。

1) 客户端配置

在配置文件client.properties中主要有如下三个配置项,

org.quartz.scheduler.rmi.proxy: true
org.quartz.scheduler.rmi.registryHost: localhost
org.quartz.scheduler.rmi.registryPort: 1099

上述配置项定义了客户端的远程服务代理。

2) 服务器配置

在配置文件client.properties中主要有如下四个配置项,

org.quartz.scheduler.rmi.export: true
org.quartz.scheduler.rmi.registryHost: localhost
org.quartz.scheduler.rmi.registryPort: 1099
org.quartz.scheduler.rmi.createRegistry: true

上述配置项定义了服务器端的远程服务接受。

详细的远程任务调度演示步骤见下文的完整代码样例。

4. 集群(分布式)任务调度

集群任务调度也叫做分布式任务调度,主要为了满足任务调度的高可用、高性能、高可扩展性的需求。

一个基于Mysql数据库的集群任务调度代码样例见这里。其主要包括三个步骤,
- 初始化数据库表字段,准备存储任务调度信息,mysql初始化脚本
- 创建任务调度,保存到数据库中,代码样例
- 启动多个集群实例,实现任务的集群调度,代码样例

Quartz的集群调度依赖数据库实现任务的调度一致性,支持的数据库非常广泛。不同数据库的初始化脚本可以下载官方的发布包,解压后在文件目录./docs/dbTables中获取。

详细的集群任务调度演示步骤见下文的完整代码样例。

5. 完整代码样例和演示

演示代码仓库地址:https://gitee.com/pphh/simple-demo,可以通过如下git clone命令获取仓库代码,

git clone git@gitee.com:pphh/simple-demo.git

完整演示项目代码位于文件目录./demo-quartz-scheduler中。

整个项目的演示步骤请见README文件,请参考说明文件对项目进行编译构建,并运行演示程序。

6. 参考文档

  1. Quartz-Scheduler官方代码仓库:https://github.com/quartz-scheduler/quartz
  2. Quartz-Scheduler官方发布包:http://www.quartz-scheduler.org/downloads/