中小企业web应用的架构演化

图片来自pixabay.com的hansbenn会员

本文从简单的单体应用架构说起,逐一介绍企业web应用在其演化进程之路上的各种典型架构,并对于各个架构阶段所面临的挑战进行讨论,最后讨论中小企业应用的终极架构目标:同城主备、异地双活。

1. 单体应用架构

在企业的创世阶段,应用开发是一个从0到1的阶段,选择一个简单架构,不仅可以快速搭建起业务,验证业务模式的正确性,还可以方便业务的快速频繁迭代。因此,很多创业企业都是从单体应用架构开始,这个应用架构的典型特点是:单应用+单数据库。

其架构示意图如下,

虽然是简单架构,麻雀虽小但五脏俱全,在这个阶段,选择合适的前后端技术栈和web框架非常重要,尽量为未来发展创造条件。

1.1 面临的挑战

业务的快速变化,应用迭代频繁,这是初期企业应用面临最大的挑战,这个时期,数据模型的设计甚至比技术栈的选择更加困难。很多时候,一旦业务发展起来了,再想对历史数据推倒重来,会是非常困难的一件事情。

为了能够面对业务的快速迭代,数据模型和应用尽量小范围、内聚、低耦合,在应用内尽量实现无状态的数据流转(比如:数据的事务操作),为未来的调整扩展留出空间、做好打算。

2. 高可用、高可靠、高性能的集群架构

当公司的业务发展起来后,对应用高可用、高可靠、高性能的要求随之而来,于是每一个应用服务部署多个实例,将其挂载在SLB下,通过SLB实现流量的均衡负载,这种集群部署方式其架构简单、技术成熟,在中小企业应用中非常流行。

为了保证数据的安全,一般会搭建起数据库的主从架构,主库用于读写,从库用于读。

2.1 架构示意图

一个集群架构的示意图如下,

图中显示了数据库的主从架构,更多的数据库架构设计可以选择,比如主主、一主多从、双主多从等。

2.2 面临的挑战

集群架构简单,部署起来也不复杂,刚开始很容易形成怎么快怎么做的问题,随着部署的应用数增多,机器环境的管理、应用的灰度部署和快速回滚、问题的定位等等,都会让部署问题显得特别突出。

这个时候,Jenkins等持续集成和持续交付工具的使用,配与一套行之有效的上线规范,将有利于上述问题的解决。

3. 微服务化架构

当公司的业务规模发展壮大到一定程度,应用数达到成百上千时,整个系统的复杂度将成倍增长,如何管控系统的复杂度是需要解决的问题。

为了解决这个问题,各种业界基础架构组件涌现登场,这是其发挥神通广大的时候,这些基础技术组件若按其实现目的有如下三种归类,

  1. 组件抽象(即各种中间件):缓存、消息、数据库访问、网关、隔离熔断、日志
  2. 系统的复杂度管理:配置、灰度发布、日志、监控、告警
  3. 架构解耦:RPC服务组件(RPC框架、服务注册和发现等)

从技术角度,各种中间件的使用相对比较独立,一般在企业应用架构早期便可引入;而对于配置、日志、监控、告警等组件,则会按需引入,这些组件的尽早使用将大大加速系统的运维效率。

而对于RPC服务组件,这是微服务化架构的核心组件,其主要解决服务和服务之间调用的问题。在引入RPC之前,应用和应用之间的调用通过HTTP + 域名 + 网关访问,这种方式的调用有三个主要弊端,

  • HTTP通过网关跳转,通信效率低
  • 网关成为流量瓶颈
  • 应用的上线和下线需要频繁通知网关

RPC服务组件解决了上述的弊端,服务和服务之间可以通过IP直连调用,大大提高了服务和服务之间的访问效率,各个服务的上下线可以通过注册和发现机制自动感知,这是微服务化架构所带来的最大的改变。为了对接内外服务调用,在流量层也需要添加一层内网网关,不仅可以实现服务的内外服务调用转换,还可以实现微服务集群的按应用、按环境隔离。

3.1 架构示意图

一个微服务架构的简要示意图如下,

注:上图勾勒了微服务架构所需的逻辑组件,图中并未画出内外服务的网络调用路径,但这也是搭建微服务架构特别需要关注的方面。

3.2 面临的挑战

为了解决系统的复杂度,引入大量中间件和RPC组件,引入的同时本身就加大了对系统的技术投入,用好、管理好这些基础组件需要较深的技术储备。

从另一个方面,各种基础组件的组合解决方案也层出不穷,花样繁多,这也考验着微服务架构的搭建好与坏。选择合适、成熟的解决方案,谨慎引入一个技术组件,对一个技术组件的考察,不仅考虑其开发使用、和整体架构的融合度,还要考虑其未来的升级,充分的考察和评审有助于搭建良好的微服务架构。

在数据库这一层,一旦某业务表的存储记录达到上亿级别,则需要考虑其分库分表的需求。

4. 多机房架构

当公司的业务规模持续发展,若经营盈利能力较好,业务模式上也有需求,就可以考虑多机房的投资建设。

从架构上有如下几种选择,

  • 同城主备:在同一个城市,搭建主副双机房,主机房正常接流量,副机房则冷备;容灾时主副流量切换。
  • 异地双活:在两个较远距离的城市,搭建双机房,两个机房都同时接流量;容灾时流量切换到可用机房。
  • 两地三中心:在两个较远距离的城市,搭建三机房,其中异地两个机房同时接流量,另外一个机房冷备;容灾时随时启用备用机房,流量切换到可用机房。金融行业为了保证数据和业务安全,有两地三中心的架构需求。

多机房的建设一方面满足业务的需求,实现用户就近访问,提高访问效率;另一个方面实现容灾建设,保障了数据不丢失、服务不中断的容灾目标。

对于大多数中小企业应用来说,同城主备、异地双活为终极架构目标。

4.1 架构示意图 - 同城主备

一个同城主备的示意图如下,

4.2 架构示意图 - 异地双活

异地实现双活,在应用层、数据层可以有两种实现方案,

  • 方案1:两地提供等同的应用服务;数据层根据机房位置,实现异地的数据库冷备。
  • 方案2:应用层根据业务归类,实现异地的应用服务冷备;数据库表根据业务应用归类,实现异地互为冷备。

两种架构模式的示意图如下,

  • 实现方案1

  • 实现方案2

4.3 面临的挑战

多机房架构的挑战主要来自网络架构方面,即使多机房之间拉取了双专线,网络的长距离传输导致的延时(大于100毫秒),对于实现容灾时的网络流量及时切换,会是挑战不小的工作。

因此,在多机房建设的前期,需要对如下方面进行充分的调研,

  1. 网络架构设计:机房双专线搭建、VPC网络搭建、运行环境搭建
  2. 容灾的自动化切换:监控告警、容灾预案、自动化切换(接入层、应用层、数据层)
  3. 面临数据时延性传输的挑战:数据库的DTS同步、应用对数据一致性的容错、避免跨机房的访问调用

5. 架构演化的简单对比

架构演化 企业发展阶段 基础设施投入 人员投入 技术架构
单体 初期 1-2人 简单
集群 中期 一般 2-5人 中等
微服务 中长期 5-10人 复杂
多机房 根据业务需求和经济能力 非常高 10+人 复杂

6. 小结

任何一个成长型企业,软件架构的演化都将经历一个从简单到复杂的进化过程,在不同的阶段有不同的功能职责要求,需要搭建起相应合适的应用架构,承载起企业的发展。

对于大多数中小企业应用来说,基本可以做到集群架构,但是在微服务架构上的搭建好坏,取决于团队的技术储备。受限于经济投入和应用场景,多机房的建设是需要仔细考量后再做定夺。

7. 参考资料

  1. 阿里云:《数据库异地多活解决方案》

Java数据库访问的分片技术架构实现

图片来自pixabay.com的spencerlikestorun会员

最近和朋友同事在一起聊天,问起我正在做的项目-数据访问中间件,很多人都有一丝疑惑,这不是重复造轮子的事情么?业界里ORM框架有mybatis/hiberate,分库分表有sharding-jdbc/mycat等等,你们为什么还要新做一个框架?新做的框架有什么不一样?

对于这些问题,我希望通过本文能够帮助解决这些疑惑。本文主要介绍了数据库访问的技术架构,然后讨论了分片在不同架构层级的技术实现方案,对标业界各个数据库访问中间件,相互进行比较,分析各种架构的实现差别和优缺点。

1. 什么是数据库分片

传统关系型数据库集中存储数据到单一节点,单表可以存储达数亿行的数据记录,通过主从备份作为灾备方案,保证数据的安全性,这基本可以覆盖大多数的应用场景。但是,随着互联网技术的发展,海量数据和高并发访问的应用场景日益增多,单表数据记录在突破一定阈值之后,其性能和可用性大幅下降。为了解决这个问题,将单一节点的数据拆分存储到多个数据库或表,即分库分表,使得关系型数据库能够存储的数据量阈值上限扩大1-2个数量级,从而满足业务需求。

数据库的分片拆分有两种方式,

  1. 按照业务划分的垂直拆分,将不同业务、不同模块的数据拆分为不同表。
  2. 按照容量平衡的水平拆分,将同一表的数据按照一定平衡策略,存储到不同数据库和表中。

前者的垂直拆分一般是一次性的,可以通过静态拆分实现(即停机拆分),而后者的水平拆分则需要技术框架的运行时刻支持。本文主要讨论水平拆分的技术架构实现,分析和对比相关框架和中间件。

讨论数据库分片的技术架构前,需要了解如下二个问题,

  1. 数据库访问的整个技术调用栈是怎样的?
  2. 在整个调用回路,哪里可以执行分片操作?如何实现?

2. Java数据库访问技术栈

先看看第一个问题,一个典型的Java应用,其数据库访问的技术调用栈如下图所示,

数据库访问技术调用栈

从上而下分别为,

  1. Application 应用程序
  2. ORM对象关系映射框架
  3. JDBC Client客户端
  4. JDBC Server服务端
  5. Database 物理数据库

这几个组件由上至下依次调用,相互之间进行输入输出。

下面一一介绍这几个技术组件的功能,为我们接下来讨论分片技术的实现和架构打下铺垫。

2.1 Application应用程序

在应用程序中有许多Java POJO对象,若希望将这些POJO对象存储到数据库中,首先要转换为ORM能够识别的、规范的Entity对象,也就是ORM框架中所定义的持久化对象(Persistent Entity),然后调用ORM提供的API对这些Entity对象进行增删改查操作。

2.2 ORM对象关系映射框架

ORM是Object Relation Mapping的简称,即对象关系映射。其定义了一个Java实体对象到数据库表的映射关系,使得我们对Java实体对象的各种增删改查操作可以进一步映射到对数据库表的SQL执行。业界中比较有名的Java ORM框架有Hibernate和MyBatis。

Java在JPA 2.2规范文档中针对ORM提供了语言上的实现规范。若ORM框架实现了JPA规范,则可称为JPA ORM,例如Hibernate、EclipseLink、OpenJPA、jOOQ(注1)。而有些ORM框架则实现了自定义的对象映射规范,比如MyBatis、Ctrip Dal等,在上图中被称之为Simple ORM。

ORM框架接受由上层应用程序所提供的Entity,并提供相应的增删改查API供应用程序调用,ORM将各种操作映射为Sql语句,并输出给下层的JDBC API执行。

2.3 JDBC Client

JDBC客户端主要实现了JDBC API规范文档中定义的接口,其主要包括如下几个接口组件,

  1. DataSource 数据库连接配置信息
  2. Connection 一个连接会话
  3. Statement 一个sql执行语句
  4. ResultSet 从数据库中获取的执行结果集

对于JDBC客户端来说,其接受ORM生成的sql语句,调用执行,然后返回结果集给ORM,然后ORM将结果集映射回规范化的Entity对象。

2.4 JDBC Server服务端

JDBC服务端一般指由物理数据库暴露给外部,实现了一定的通信协议,供外部组件通过网络远程调用访问后端的数据库。

业界中几个常见的数据库通信协议规范有,

协议 协议连接URL格式
MySql jdbc:mysql://host:port/datasource?serverTimezone=UTC&useSSL=false&useLocalSessionState=true
PostgreSQL jdbc:postgresql://host:port/datasource
SqlServer jdbc:sqlserver://host:port;DatabaseName=datasource
Oracle jdbc:oracle:thin:@host:port:datasource

各个物理数据库除了实现自己的通信协议,也可以采用已有流行的通信协议,比如分布式数据库的TiDB、AWS Aurora、OceanBase,以及数据访问中间件sharding-proxy、MyCat,都采用了兼容Mysql协议的接口服务。这使得用户和应用程序可以使用已有的Mysql客户端进行访问,方便用户和应用程序的接入。

作为JDBC服务端,其接受JDBC客户端远程调用时发送过来的Sql语句,执行后返回结果给JDBC客户端。

2.5 Database物理数据库

物理数据库本身的存储特性、容量性能、可扩展性,都对数据库技术分片技术的实现产生直接的影响。若物理数据库对分库分片提供原生支持,那么将大大简化分片技术的落地实现,比如阿里云的分布式关系型数据库产品DRDS。

3. 哪里分片、如何分片

在了解了数据库访问的技术调用栈之后,接下来的问题就是哪里分片、如何分片。

上文讲到分片是指将同一系列数据记录按照一定策略,存储到不同数据库和表中,使得数据的存储和读取达到最优解,这个策略就叫做分片策略。

常见的分片策略有,

  1. 简单的有按主键ID取模,平均分配记录到不同表中,平衡访问流量;
  2. 按地域分片,实现数据记录能够按最近IDC机房入库;
  3. 按时间分片,方便最新数据查询;
  4. 按数据关联度,比如和订单ID相关联的数据落入同一库,做到同库查询;

可以看到,分片策略依赖于分片字段的设计。

从分片技术实现的角度来说,分片字段的获取则是实现分片的第一步。在数据库访问的技术调用栈上,每层其实都可以获取到分片字段。下表描述了在各个技术调用栈层上,如何获取分片字段、如何实现分片策略,并列出了相应技术实现的分片框架、中间件或数据库,

数据库访问技术栈 如何获取分片字段 分片策略实现 技术实现
ORM 通过Entity解析 Sql生成执行时分片 MyBatis/Hibernate/Ctrip Dal
JDBC Client 通过Sql解析 对Sql改写 Sharding-JDBC
JDBC Server 通过Sql解析 对Sql改写 Sharding-Proxy, MyCat
物理数据库 通过存储数据字段 原生支持分片策略 阿里云的DRDS

上表中,分片字段的获取是区分不同分片架构实现的关键点之一,也影响着分片策略的技术实现。

4. Java数据库访问的分片技术架构实现

为了更加清晰地了解Java数据库访问的各个分片技术架构实现,下图以数据库访问技术调用栈层为基础,绘制了各个不同分片技术实现的架构,以便相互比较区分,

Java数据库访问的分片技术架构实现

图中绘制了五种架构实现,分别为,

  1. ORM:在对象映射框架中提供分片技术实现。其主要优势是ORM握有持久化对象(Persistent Entity),可以非常容易地获取到分片字段,同时ORM框架为开发者提供各种扩展手段实现分片技术,比如DAO的继承实现自定义API,ORM拦截器等。开发者可以针对ORM框架编写扩展代码,以灵活的方式实现所需分片逻辑。其缺点在于绑定了特定ORM框架,分片逻辑对应用不透明。
  2. JDBC Client:对于JDBC客户端来说,其JDBC API接受上层传输过来的Sql语句,所以可以通过Sql解析,获取分片字段信息,然后对Sql进行改写。这个方式优点在于对应用和ORM框架透明,支持不同ORM框架,甚至更换ORM框架对分片逻辑无影响。缺点主要在于Sql解析,其对Sql语句有要求,不支持复杂的Sql语句,而且由于绑定JDBC Client,只支持单一开发语言。
  3. 客户端(ORM+JDBC Client):这里的客户端是指同时提供ORM和JDBC Client的框架,携程的DAL就是这样一个数据库访问客户端。其主要优点是,由于同时涵盖了ORM和JDBC Client两大技术栈层,因而技术空间将大为丰富,除了能够提供代码生成、拥有丰富API接口的数据库访问DAO、对应用程序透明的分库分表功能之外,最大的特点便是提供动态灵活的Sql构建器,Sql构建器+Entity+分库分表的技术结合,大大方便了开发者的使用。缺点是单一语言,由于包括了JDBC Client技术栈,其支持的数据库将受到限制,比如携程的DAL只支持MySql和SqlServer两种数据库。
  4. 服务端(JDBC Protocol):这个可以认为是第二种JDBC Client模式的服务化版本,在JDBC Server前部署一个中间代理服务,在代理服务里做sql解析和sql改写。其拥有JDBC Client模式几乎所有优缺点,不一样之处在于应用开发不再受到语言限制,可以支持多种应用开发语言,数据库连接数将得到高效复用。与此同时,这种服务化架构将方便实现sql的集中治理和限流监控功能,这也是服务化之后带来的一大好处。但是多一层网络跳转将对访问性能有一定的影响,服务的高可用性也需要特别留心设计。
  5. 自定义客户端和服务端:这个可以认为是上述第三种客户端模式的服务化版本。其拥有客户端模式的优缺点,也有服务化之后的优缺点。

还有一种是物理数据库原生支持分片策略,比如阿里的DRDS,这个架构非常简单,分片策略对应用、ORM、JDBC都透明,都由数据库中配置和实现,本文就不列出讨论。

5. 技术架构实现对比

下表将各个分片技术方案的相关信息列出,作为对上述讨论的一个总结,

6. 是不是重复造轮子

区分是不是重复造轮子,一要判断架构是否有相似性,二要判断实现方式和提供的功能是否一样。即使是同一架构,同一实现功能,相互竞品之间也是有一定的性能特性区分。正所谓青出蓝而胜于蓝,长江后浪推前浪,在前人基础上,如何造出更好的轮子才是正道。

Hibernate、Sharding-JDBC、Ctrip DAL、MyCat四者分别以不同架构方式实现了数据库分片功能,通过上表可以知道,四者之间优缺点各有千秋,选择哪一种还是要取决于应用场景。

7. 分库分表的代价

我们在讨论分片的技术架构实现时,更需要了解的是分库分表所带来的成本代价。

分库分表操作不是一本万利的账,会带来新的风险和问题挑战,比如,

  1. 引入分库分表框架或中间件,其本身的技术掌握有一定成本,分库分表的配置管理和运行调试也是难点
  2. 数据记录插入容易,查询和Join变难
  3. 跨库的分布式事务,全局性约束失效(比如唯一主键)
  4. 自动化运维(灾备切换、数据库迁移等)实现
  5. 再扩容、更新分片算法

这些都是进行分库分表后面临的不小的成本代价。正确评估业务量和数据库能够承受的容量阈值,对症下药,是对分库分表的最佳建议。

以下是一些做分库分表前需要注意的考虑点,

  • 若数据库的容量阈值足够满足业务量的数据记录需求,能不做分库分表则不做,避免不必要的成本代价。若不确定是否满足,可以先不做,待后续运营一段时间后再评估。
  • 若业务量的数据记录需求超过数据库的容量阈值1-2个数量级,以审慎的态度考虑分库分片方案,并且对分库分表后的运营成本进行一定的考察。建议考虑下做定时数据备份清理工作,看能否把数据记录减少到数据库可支撑的范围之内,避免分库分表。只有在数据量超过容量阈值,数据库读写速度满足不了业务需求时,分库分片才是一个可选解决方案。
  • 若业务量的数据记录需求超过数据库的容量阈值3-N个数量级,这个时候,数据量也超过了分库分片所能承受的应用场景。有这么大数量级的业务数据,分布式关系数据库是一个方向,比如谷歌的Spanner,亚马逊云的Aurora,PingCAP的TiDB、阿里的OceanBase,其可以支撑的数据量都可达万亿行以上。

8. 参考资料

  1. 注1:jOOQ可以自动生成JPA实体代码,并通过JPA native query API来实现对实体的操作,一个使用方法如下所示,
EntityManager.createNativeQuery(org.jooq.Query.getSQL(), resultSetMapping)
  1. JPA 2.2规范文档
  2. JDBC 4.3 规范文档
  3. PingCAP的TiDB:https://www.pingcap.com/docs-cn/
  4. Sharding-Sphere:http://shardingsphere.io/
  5. Ctrip Dal开源项目:https://github.com/ctripcorp/dal
  6. 阿里云的分布式关系型数据库产品:DRDS

拍拍贷技术架构体系

拍拍贷公司成立于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/