贝聊成立于2013年,是中国幼儿园家长工作平台,致力于通过互联网产品及定制化解决方案,帮助幼儿园解决展示、通知、沟通等家长工作中的痛点,促进家园关系和谐。贝聊是威创股份(A股幼教第一股)、清华启迪、网易联手投资的唯一品牌。在短短几年内,用户规模迅速达到千万级别,每年DAU均呈倍数级增长。
面对如此快速的发展,原有的技术架构很难支撑越来越复杂的业务场景,在系统可用性以及稳定性方面,都给贝聊技术团队带来了很大的压力。因此,如何针对当前需求,选择合适的技术架构,保证架构平滑演进,值得我们深入思考。
贝聊架构总体经历了三次重大历程,由几台服务器搭建的单体架构到目前的几百台分布式部署架构,在整个变化过程中,我们踩过了很多坑,遇到过很多重大技术挑战。
创业初期,我们的初始创业团队在进行架构选型时,主要基于以下几点进行考虑:
在创业初期,研发资源有限,研发人力有限,技术储备有限,需要选择一个易维护、简单的技术架构;
产品需要快速研发上线,并能够满足快速迭代要求,现实情况决定了一开始没有时间和精力来选择一个过于复杂的分布式架构系统,研发速度必须要快;
创业初期,业务复杂度比较低,业务量也比较小,如果选择过于复杂的架构,反而会增加研发难度以及运维难度;
遵从选择合适的技术而不是最好的技术原则,并权衡研发效率和产品目标,同时创业初期贝聊只有一个PHP研发人员,过于复杂的技术架构必然会带来比较高昂的学习成本。
正是基于以上几点考虑,最终选择了经典的LNMP技术架构,贝聊V1.0架构就这样诞生了,为了加快产品研发速度,尽快上线产品,首期通过外包公司实现了研发以及部署,后续由我们的PHP研发人员接手,并进行后续的迭代开发。
初期部署时,部署了三台ECS服务器,其中接入层nginx与系统部署在同一台机器,RDS数据库一台机器,Memcached缓存一台机器,V1.0架构具有以下特点:
单体架构,架构简单,清晰的分层结构;
可以快速研发,满足产品快速迭代要求;
没有复杂的技术,技术学习成本低,同时运维成本低,无需专业的运维,节省开支。
LNMP架构支撑贝聊业务发展了将近一年半左右的时间,简单、易维护的架构为贝聊的快速发展做出了很大的贡献,期间业务发展迅速,用户体量也越来越大,原有架构逐渐暴露出越来越多的问题。
我是在2015年初加入了贝聊,初始研发团队只有三人,有幸在这一时期
主导了贝聊技术架构重构,并经历了贝聊后续的几次架构演进路程,将原有PHP单体架构重构为JAVA分布式架构。
首先谈一谈我们做技术架构重构的契机,重构并非难在怎么做,而是难在何时开始做,所以我们做架构重构的契机主要基于以下几点:
原有LNMP架构经历了两个团队研发和维护,外包团队和公司PHP研发人员,由于业务变化比较快,原有的数据库设计逐渐暴露出来很多问题,很多表设计不合理, 很多字段定义不清,比较混乱;
2015年,由于业务发展,贝聊app需要拆分为两个客户端:贝聊家长端和贝聊老师端,通过不同的客户端来服务不同的用户群体,达到精准运营的目的,如果在原有架构上继续进行开发,则会导致新旧接口逻辑混在一起,并且早期的很多接口定义不是很规范,维护起来越来越麻烦、越来越吃力;
原有API接口系统是单体架构,里面包含了各种接口,混合了各组业务逻辑处理,所有功能都集中在API接口系统中,代码非常臃肿,业务非常繁杂,迭代开发的速度也逐渐减慢,各种性能问题经常爆出,BUG也比较多,并且部署困难,任何修改每次都需整体部署。由于业务逻辑混杂在一起,新入职研发人员需要很长时间才能够完全熟悉系统,很难快速通过以点及面的方式切入系统;
所有数据存储在一个RDS数据库中,只使用了一个主库,没有从库,同时很多系统共用一个数据库,一方面数据库没有做到物理上的隔离,另一方面很多表都放在了同一个数据库中,经常会碰到一个慢查询或者其他性能问题,导致整个RDS各项指标飙升,造成雪崩效应,所有系统连锁出现故障,最终都不能访问;
公共服务耦合比较严重,很多第三方服务都散落在各个系统里面,不便于统一维护,当需要修改公共服务参数或者做其他调整时,需要深入到每个系统里进行修改或者排查,非常麻烦,还非常容易出现遗漏,最终产生BUG,急需独立拆分出公共服务,将公共服务从业务系统中解耦出来,由专人进行独立维护、独立部署;
我们新的研发团队都拥有丰富的JAVA分布式架构设计经验,拥有高并发、高可用经验,因此将原有的单体架构重构为JAVA分布式架构也是顺势而为。
由于公司业务高速发展,如果停下来专门做技术架构重构是不可能的,我们选择了在维护现有系统的基础上,同时进行新的技术架构重构工作。重构期间,在原有PHP研发团队的大力支援下,我们的重构工作还算非常顺利,既保障了业务的快速迭代需求,又成功完成了新的技术架构重构,新的V2.0架构如下:
在V2.0架构时期,初步实现了分布式部署架构,根据不同的功能以及业务逻辑,完成系统级别的拆分,同时对第三方服务进行解耦,拆分出了独立的服务模块,针对DB,我们实现了系统级拆分以及物理独立部署,并实现了数据库主从分离,同时引入了MQ消息队列,并使用SLB实现了负载均衡和带宽流量入口统一。
V2.0时期的架构具有以下特点:
分布式部署架构,系统易扩展;
系统级拆分,拆分出业务功能逻辑独立的子系统,并独立拆分出DB;
初步实现了服务化,系统间调用使用Hessian实现RPC;
DB实现了物理隔离,避免以前单DB出故障,引发业务连锁故障,同时实现了数据库主从分离;
引入MQ消息队列实现消息和任务异步化,加快接口响应速度,提升用户体验,同时针对一些消息推送任务也实现异步化,避免早期的轮询MySQL机制,减少消息推送延时,提升消息推送速度;
使用SLB实现了Nginx负载均衡,在V1.0架构时期,我们的Nginx是单点部署,若一台Nginx服务器挂掉,则会影响很多业务系统,有单点故障风险,通过SLB实现多台Nginx负载均衡,达到高可用的目的,避免单点故障。
系统拆分和DB拆分
针对系统拆分以及DB拆分,我们通过两个阶段来完成该项工作。
第一阶段
首先在系统层面进行拆分,将原有的大系统拆分出多个业务逻辑独立的子系统,而DB暂时不进行拆分,多套系统还继续共用一个DB,只是根据业务逻辑划分各个系统所依赖的表,不同业务逻辑系统之间不能互相访问表,这样新系统只访问自己所归属的表,通过此种方案,可以保证原有系统业务不受影响,同时新拆分的业务系统研发工作也可以顺利进行,此阶段大概花费了我们几个月的时间,最终顺利完成系统层面的拆分。
第二阶段
在完成系统层面拆分之后,我们紧接着实施DB层面的拆分,将各个子系统所依赖的表独立拆分出来,分别放置到不同的RDS数据库,实现物理的隔离,同时实现了数据库主从分离。最终实现效果如下图:
初步服务化
本阶段,我们采用了比较简单易用的Hessian实现初期的RPC服务化。针对第三方公共服务,从原有系统中解耦出来,独立拆分出服务化组件,并做独立部署,供其余业务系统统一调用。而系统间调用也通过Hessian来实现RPC远程调用。
SLB负载均衡
在V1.0架构期间,我们的Nginx都是单点部署,一旦一台Nginx服务器出现故障,则会波及到大量业务系统,风险非常大,如下图:
在V2.0架构期间,我们引入了SLB实现负载均衡,SLB配置了多台Nginx,同时在业务系统层面也实现了负载均衡,避免了单点故障,达到高可用的目的。
进入2016年以来,贝聊业务高速发展,用户规模在短时间内增长数百万,同时各个业务线逐渐铺开,业务场景更加复杂,代码规模膨胀得也非常快,研发团队迅速达到了几十人规模,一个系统多人开发,研发人员层次不一,规范难以统一,同时业务逻辑耦合严重,每次上线都需要将整个大系统整体打包上线,风险非常大,并且新人入职之后学习成本非常高。因此我们引入了微服务架构,将业务逻辑拆分为独立的微服务组件,每个微服务都围绕着具体业务进行构建,由专人研发和维护,并由专人做性能优化和架构优化,各个微服务组件的研发与上线互不影响。
结合V2.0架构,在实施微服务架构时,基于多方面考虑,我们选择了Dubbo作为分布式微服务框架。
成熟的高性能分布式框架,目前很多公司都在使用,已经经受住各方面性能考验,比较稳定;
可以和Spring框架无缝集成,我们的架构正是基于Spring搭建,并且接入Dubbo时可以做到代码无侵入,接入也非常方便;
具备服务注册、发现、路由、负载均衡、服务降级、权重调节等能力;
代码开源,可以根据需求进行个性化定制,扩展功能,进行自研发;
在做微服务时,我们考虑了以下几个关键点:
以服务为中心,一切都是服务,每个服务都针对单一业务进行封装,保证功能完整性和职责单一性;
松耦合性,服务之间功能独立,能够独立部署,服务之间相互依赖;
高扩展性,分散资源,团队协同工作,可无限扩展,更高的代码重用率。
在实施微服务架构时,主要考虑从以下几个方面进行实施:
独立功能逻辑拆分为微服务,独立部署,独立维护;
系统功能全部通过调用微服务实现,系统不能直接访问DB;
小数据量高并发调用使用Dubbo长连接协议进行通讯,大数据量服务比如文件、图片、视频等使用Hessian协议进行通讯;
每个微服务都维护独立的DB。
微服务拆分案例
\1. 班级动态微服务
贝聊的班级动态是一个高频率使用功能,园长、老师、家长都可以在班级进行发布动态,通过点赞、回复进行互动。随着贝聊业务飞速发展,用户规模爆发,每天都产生数十万的班级动态量,同时日回复量和点赞量均达到了数百万级别。面对如此大规模的数据量,我们一方面要应对高并发的性能压力,另一方面又要应对数据压力,原有的班级动态功能散落在API接口系统以及后台管理系统中,相关的表也与原有系统共享一个DB,迫切需要我们拆分出独立的班级动态微服务组件,同时还需要做分库分表减少单数据库压力。因此我们专门抽调精干研发人力,拆分出了班级动态微服务组件。
旧班级动态调用方式如下
班级动态微服务组件调用方式如下:
拆分出班级动态微服务之后,我们解决了以下问题:
班级动态微服务对业务调用方透明,业务调用方只需调用接口即可,无需关注技术实现细节;
代码复用性,班级动态业务逻辑单独抽出来做成独立微服务组件,业务系统不再散落班级动态业务逻辑代码、无需再进行代码拷贝;
采用DRDS实施了分库分表,解决了单数据库数据量大、数据处理能力有限的瓶颈问题,在单数据库情况下,由于数据量比较大,高并发时期,经常遇到性能问题,接口响应速度非常慢,在实施分库分表之后,班级动态接口的整体性能提升了几倍,用户体验非常好,高并发时期也没有了性能问题。
\2. 用户通行证微服务
很多创业公司,在一开始发展时,为了追求速度,同时由于人力不足,都是将用户数据表与业务数据表暂时放在了一个DB里面,贝聊早期也是这样,这就造成了各个业务系统都是自己分别写DAO来获取用户数据,产生了大量重复的用户逻辑拷贝代码。随着业务发展的越来越快,越来越多的业务系统都需要访问用户数据,用户逻辑代码散落在各个业务系统,用户数据越来越难维护,复杂度越来越高,同时用户量越来越大,经常会遇到高并发性能问题,不容易做独立性能优化,因此拆分出独立的用户通行证微服务迫在眉睫。
旧用户数据获取方式
用户通行证微服务
拆分出用户通行证微服务之后,我们解决了以下问题:
代码复用性,原先几乎每个业务系统都散落有用户逻辑代码,到处都是拷贝代码,拆分出用户通行证微服务之后,业务系统只需调用用户通行证微服务接口即可;
用户数据一致性,以前由于获取以及修改用户数据代码散落在各个业务系统,经常会产生一些用户脏数据,并且很难查询在哪个系统修改了用户数据,同时由于不同的研发人员开发维护不同的业务系统,也给维护用户数据一致性带来了很大的挑战,拆分出用户通行证微服务之后,所有跟用户逻辑相关的功能,都由用户通行证微服务提供,保证了修改数据以及获取数据的接口一致性;
用户数据解耦,原有业务系统中经常会join用户表获取用户数据,难以拆分,拆分出微服务之后,用户数据库独立设计部署,方便进行扩容以及性能优化。
微服务治理
微服务架构开发、测试、部署复杂度远远大于单体架构,因此需要构建能够支撑微服务架构的交付和运维能力。
\1. 版本发布系统
微服务架构的应用开发、部署的复杂度都是远大于单体架构应用的,大量的微服务组件如果依然靠运维人员手工的配置管理显然是难于应付了,因此我们研发了自动化部署和发布的版本发布系统,我们的版本发布系统具有以下特性:
项目配置包括项目名称、管理员、项目成员、SVN/Git地址、帐号、服务启动的Shell、自定义脚本、不同环境的JVM配置、Web容器配置等等;
按照项目配置好之后,可以发起上线申请单,通过审批之后,一键即可部署;
支持灰度发布,可以灰度选择服务器进行版本发布,确保版本发布安全稳定;
可以实时收集部署过程产生的日志,可视化实时监控部署过程产生的问题;
针对发布异常,我们有发布异常处理机制,针对有多台服务器的情况,可以选择只要有失败就停止发布,即一台发布出错,后续其余服务器停止发布,也可以选择不管是否有失败都继续发布;
快速回滚,针对版本发布出现异常的情况,我们支持快速回滚,可以快速回滚到上一个稳定的版本。
通过版本发布系统,实现代码版本管理、一键部署上线、一键快速回滚、上线单申请、上线审核以及上线日志等。
\2. 开发测试发布部署
针对微服务复杂的架构,为了保证每个微服务交付的质量,我们部署了四个环境:
开发环境,供研发人员在开发、调试阶段使用;
测试环境,研发人员在开发环境完成所有功能开发、测试之后,部署给测试人员进行验收的环境;
预发布环境,在完成测试环境的功能验收之后,功能发布至生产环境前的一个预演环境,与生产环境共用相同的数据库、缓存、MQ消息队列等,用来在微服务上线生产环境前,确认是否还存在BUG等问题,不会影响生产环境的用户,最终用来确保上线生产环境成功;
生产环境,即线上环境,是面向用户的线上环境。
通过以上四个环境,确保微服务组件的研发、测试、发布的质量。
\3. 分布式配置中心以及分布式任务调度平台
随着微服务架构的实施,我们拆分出了很多的微服务以及子系统,各种配置信息都以明文形式配置在配置文件中,同时各种定时任务也散落在各个微服务以及子系统中,非常难管理。因此我们选择了合适的分布式配置中心以及分布式任务调度平台
Disconf分布式配置管理平台,实现了配置发布统一化,所有配置都存储在云端系统,用户统一在平台上进行发布、更新配置,更改配置时,无需重新打包或重启微服务,通过管理平台直接修改即可,同时我们进行了个性化定制研发,所有配置信息都实现了加密方式,避免账号、密码等敏感信息泄露;
Elastic-Job分布式任务调度平台,实现了定时任务注册中心、任务分片、任务运行服务器弹性扩容缩容、失效转移、任务停止恢复和禁用等特性,更方便的管理分布式架构环境中的定时任务。
\4. 全链路跟踪
微服务架构拆分了大量的子系统以及微服务组件,面对如此复杂大规模分布式集群,一次链路调用可能会发生在多个微服务组件之间,如何进行链路调用追踪,如何快速发现一次接口调用过程中哪些地方需要优化、哪个微服务接口导致了整体调用比较慢。针对上述问题,我们引入了美团点评的APM工具Cat实时监控系统,与Dubbo服务化框架进行整合,通过全局链路ID,实现链路追踪功能。
\5. 微服务授权
默认Dubbo没有实现服务授权功能,系统调用微服务、微服务之间调用均没有实现授权验证,都是直接访问微服务组件接口,因此我们针对Dubbo进行了个性化定制研发,研发了微服务授权认证中心,通过授权认证保证核心微服务接口的调用安全性。
\6. 微服务监控
拆分出大量的微服务组件,我们面对的是如何监控这么多的微服务运行状态,我们采用了Dubbo自带的简易监控中心,监控微服务组件的成功率、失败率、平均耗时、最大耗时、并发量等指标,通过这些指标发现微服务性能瓶颈,进而优化微服务性能。同时我们进行个性化定制扩展与研发,针对Dubbo接口调用,统计接口耗时排行、接口失败排行、接口访问异动排行等等,通过定制化研发的统计报表,更直观的监控Dubbo接口性能。
\7. 微服务管理
我们使用Dubbo的管理控制台,实现对微服务的路由规则配置、访问控制、权重调节、服务降级、服务禁用、容错等功能,可以非常方便的管理运行中的微服务组件。
经过一年多的微服务化历程,V3.0架构如下:
V3.0微服务架构具有以下特点:
完全实现了分布式部署架构,系统与微服务组件都非常容易扩展;
以服务为中心,全面构建了微服务组件;
系统、微服务组件、缓存、MQ消息队列、DB等均无单点风险,全部实现了HA高可用;
V3.0架构虽然实现了微服务架构,但该架构还存在以下可以继续演进的点:
Docker容器部署,Docker具备轻量级、快速部署、隔离应用、跨平台的优势,微服务非常适合与Docker结合进行快速部署,目前虽然我们实现了微服务架构,但还未做到快速弹性扩展,如果将微服务与Docker容器进行结合,可以实现快速弹性扩展,业务高峰期可以快速自动扩展服务器,业务低峰期可以自动回收服务器,接下来我们即将实施微服务组件的Docker容器化部署;
统一API网关,目前我们的核心API还只是一个统一的代理层,尚不具备网关的身份认证、防报文重放与防数据篡改、业务鉴权、流量与并发控制等等功能,实施API网关后,可以实现前后端分离,提供便捷的监控、报警、分析,还可以提供严格的权限管理以及流量限制,保障API的安全稳定。接下来我们将实施统一的API网关控制;
跨IDC机房部署,目前我们的系统还是单机房部署,单机房不具备冗余以及容灾机制,首先我们将逐渐实施同地多机房部署,先具备多机房部署能力,避免单机房故障,最后我们再实施异地跨IDC机房部署,达到异地冗余高可用以及用户就近访问的目的。
架构演进一直在路上,架构要围绕业务进行,不能脱离于业务,不同的业务时期需要不同的架构。
单体应用架构,更适合创业初期,公司需要快速试错以及验证市场反应,需要更快的研发速度,同时研发人员比较少,而单体应用架构比较简单,可以快速切入,对研发人员的技术栈要求不是特别高,可以快速上手快速研发,但在设计单体应用架构时最好可以提前规划好未来的扩展性,可以在业务层面先规划好,便于日后业务发展到一定规模,可以快速进行解耦,实施微服务化架构。
当企业发展到一定规模,业务线变的越来越多、越来越复杂,同时研发人员的数目也快速增长,单体应用架构就会慢慢暴露出来弊端,大量的研发人员在一个系统上进行开发,缺少并行研发能力,大量的业务代码耦合在一起,同时研发效率非常低。微服务架构可以更好的进行业务解耦,具备更好的扩展性以及独立性,可以提高研发团队间的并行化研发速度,提升效率、提高模块复用性,具备高可用、高并发特性。但微服务架构对服务治理的能力要求比较高,维护成本也会比单体应用高,需要强大的服务治理支持,对研发人员的技术能力要求也比较更高。
目前我们依然在架构演进的路上,经历了以上几次架构历程,虽然取得了一定的进步,但依然有很多挑战等待我们去迎战。规划技术架构需要综合考虑业务的规模、业务的时效性、研发团队的规模、研发的技术能力、基础环境配置等。架构来源于业务,架构演进的生命周期只有完美匹配好业务的生命周期,才能最终发挥出最好的效果。