前言
疫情三年,全国各地的健康码成为了每个人的重要生活组成部分。虽然过去一年,但是回想起来任然历历在目。
今天我就通过当时基于小程序的健康码架构,来给大家讲一下如何基于java,springboot等技术来快速搭建一个后台业务系统
本次“腾讯防疫健康码”小程序项目本身因为时间特别仓促,从1月28号启动,到2月1号开始部署其他地市,业务迭代非常快速,已经达到了和阿里的健康码直面竞争的态势,全国各个省市抢地盘。从健康上报,到抢口罩,到健康码,经历的几次主业务方向的调整,外加各种ISV的开发团队加入进来,面临更加严峻的问题就是微应急->健康码,用户量大增,并且加入了各省市互通的业务需求。
从最开始简单的“微应用” 式的后台开发模式,一直没有进行架构上的调整与设计,导致中后期开始出现在研发技术,以及合作研发方面的各种问题,所以本次项目在架构上并不是很理想,于是我想是否能够根据以往经验,梳理总结一套能够更大众的框架。
声明:因为我们团队在docker方面,以及云原生,小程序云开发方面没有丰富的积累,所以本次项目依然使用的java的springboot框架进行服务开发,部署在腾讯云的虚拟机上。
先说一句:好的架构不是设计出来的,而是演进出来的
一、健康码架构简述
本次开发服务全部都是基于腾讯云开发,其中使用waf,clb,cdn,redis,mysql,es,cos等中间件已经作为成熟的解决方案。在这里就不再赘述。
下图为 部署架构:
1.1、本框架设计优点:
1、小程序服务链路与管理后台服务,口罩服务通过使用clb进行链路分离,根据服务属性区分开,思路清晰
2、使用rio智能网关,其中api网关有效的进行了限流,并能统计接口以及接口安全防护,保护了后台的服务,API网关用来注册接口,实现了可扩展与第三方接入等,但是api网关是在很后期加入了。
3、数据库和redis使用了分库设计,根据不同业务进行了分库,减缓部分压力。
1.2、本项目暴露的问题:
通过以上图的架构,我们也会发现:
1、没有加服务发现的能力,导致后期服务的扩展性很差, 同事带来以下几个问题
2、因为没有服务发现,那么应用层是按照模块的一个个单体服务,没有进行分层设计,出现的问题就是不同的服务可能同样的代码在多个服务上出现,明明是加一个接口的问题就可以解决的问题。
2.1 比如调用API网关是需要配置paasid和token的,如果没有加一个代理proxy层,那么所有调用网关的服务一旦修改token就要全部修改服务重启
2.2 比如同一张表,多个服务同时操作,就会出现字段乱修改,而互相没有告知对方,就会出现各种服务上的莫名其妙的bug
3、没有面向服务的开发模式,一些能力参差不齐的开发商,随意操作公共部分能力,尤其是处于性能瓶颈的数据库,第三方外部接口等。
4、应用或者服务过多,就会带来在管理应用,以及配置发布变更带来了巨大的挑战。
二、健康码登录以及基本请求流程
下图主要是描述用户登录小程序以及登录后请求到我们的业务服务模块。
其中流程中已经省略准入网关一层,因为目前我们的网关主要是用来限流,并没有校验登录态或者说拦截登录的能力。接下来主要讲整个登录以及如何做第第一笔业务。
-
account服务,主要是用来通过微信小程序的jscode ,去微信服务器换取用户的openi,以及sessionkey,同时会生成用户的唯一会话ID,以及用户我们系统的useid,进行关联,接下来通过session_key信息等来解析用户的电话号码,我们微应急通过是否有电话来确认用户登录完成。
-
业务请求,当header存在登录态的时候,请求业务接口api,http的header会存有一个sessionid,业务层通过拦截器的功能,获取sessionid,通过协商好的get sessionid 获取临时登录信息,存在表示在有效期,不存在直接返回用户未登录的的状态码。
通过以上表述,我们可以看到,用户登录态校验也是在每个业务应用进行校验,也就是说每次请求都必须经过一次redis的get处理。我们通过一个公共的第三方jar包的形式加入到各个应用中。
这就带来两个问题:
1、每次请求都会get一次redis,这回对redis带来一定的压力,虽然目前这个瓶颈还没遇到
2、通过业务层的拦截器方式,一旦common包修改,就要对所有应用进行重新编译处理。
通过分析,我们可以发现,在这种“微应用模式”下的架构,尤其是在有各种ISV,第三方开发服务商,个人能力不是很强的情况下,加入进来的时候,并不是一种优秀的开发模式,尤其中间还没有进行及时的架构调整,因此通过我个人的过往项目开发经历,来简述一下架构经验。
三、腾讯短信后台架构
我在16年-18年参与的短信后台架构,针对当时已有老的短信架构进行深度的重构,基于taf的c++框架进行开发,充分利用了taf的综合解决方案,服务发现以及dcache等,还有使用公司的cmq消息队列对服务充分的解耦等技术。请看下图:
这个是重构后的一个目前使用的新架构,绿色箭头所指服务为下发短信的主服务调用链条。逻辑层与接入层和内部之间的服务均为taf调用。按照功能与逻辑进行了解耦和拆分。
2.1、产品接入层:
提供了tcp协议,http/https的标准协议,以及基于taf的jce协议。并提供了c++版本,java,php等多语言服务 频率限制服务:用来控制每个业务的每秒的发送速率。默认100.
2.2、逻辑层
这个层是主要对短信处理的业务逻辑层。
1、短信下行服务:短信下行收到接入层的短信后,进行编解码,长短信拆分,脏词过滤,短信签名,分配端口等等逻辑处理。
2、流量控制服务:而流量控制服务主要是用来针对某条短信是用来选择一个最优的通道号,如果端口挂了也会通过它控制。接下来会着重讲述这块服务是如何进行选择最优的一个接入号的
3、短信数据服务:主要功能有,对短信的存储,匹配,采集数据,统计等一系列工作。为流量控制提供数据支持依据。而数据服务的数据源就是从网关处取得
4、异常服务 :主要用来在下发错误短信后,如没有下发成功后,通知到业务方 上行服务:有一些业务需要用户发送一些业务代码,我们这里需要进行转发工作。
5、分发路由服务:因为短信是按照通道号进行下发,运营商网关负责连接到对应的特定的运营商。因此该服务就是负责对应通道号的短信派发到指定的网关接入层。
2.3、运营商接入层
1、接入服务,负责维持和所有供应商的网络连接服务。 调度模块:就是用来管理多个网关服务的之间的故障处理,负载均衡,容错等一系列操作。
2、预处理服务:通过cmq和接入层解耦,将所有短信都丢在预处理层进行记录,预处理保存所有短信记录,使用TDSQL进行分库分表
3、管理层:提供的是一个可视化的管理信息系统,比如查询短信下发后记录的客服查询系统。给营销短信提供操作的营销平台。等等
以上新架构,在未来各种多样的业务需求下,通过服务扩展等方式来加以轻松的支持
2.4 、TAF的框架介绍
目前taf在内网已经非常成熟了,而且各个部门都有在使用,集成开发,运维,部署等一体的解决方案,也同时支持了docker等能力
目前外网开源的版本叫tars,基本的功能都已经具备了。
四、电子签章架构
电子签章项目是我18年-19年的在数字广东的一个子项目,历时五个月,作为技术负责人,带领几个外包和实习生等人员开发,也是做了对整个业务分析,技术架构等进行了详细分解和架构。该项目主要是基于golang开发,并且我们当时搭建了一个完整的基于golang的框架。
曾经也简单的总结过,文章连接:http://km.woa.com/group/38350/articles/show/369458 ,项目过程以及内容不再过多描述
服务应用结构:
我在这里主要是简述和应用层内容:
1、使用了基于etcd的服务发现以及统一配置管理,做到服务统一管理以及配置发布。
2、接入层api网关,可以理解为nginx以及api路由,用来实现和外部接口暴露的安全接入。
3、使用数据库代理层,保证数据库层压力以及使用事物等
4、第三方服务,比如我们会用到小信科技与国脉的接口,封装在同一层,保证了只修改协议适配层即可,业务逻辑层不用大动干戈。
五、“数据ai中台”项目架构
当前,我正在负责研发 基于多种大数据套件 平台的机器学习训练平台以及人工智能方面的平台性质的项目,比如腾讯的TBDS,华为的fusioninsght,以及CDH等平台,几乎都要深度的使用他们的开放的api接口能力。该项目团队最多时候20多人,我作为技术负责人,负责整个项目技术方面的工作。
项目也是考虑了方方面面的设计,因为是属于tob项目,会根据部署以及不同环境要求,都会有个性化的系统配置等,因此使用了通知数据库代理配置,第三方接口适配层。
因此我们有了如下的功能架构:
我这边进行了清晰的对架构分层设计,业务服务模块必须调用组件层的api接口来完成业务逻辑。保证了可以适配不通的大数据平台,依然能够使用。
-
1、API-GATEWAY
作为ng的下一层,挂着所有服务的入口,同时所有的请求的登录态都在改层完成校验完成,可以适配不同套件的用户接入,当一个请求进入的时候,会根据session换取user_id,放到头部,传递到下一层业务逻辑。同时也完成基本的流量控制,鉴权等能力
-
2、微服务层-业务逻辑
微服务层会暴露所有的api接口在 gateway上,包括模型训练,模型部署,模型编排,应用管理,系统参数配置,作业管理,发布服务等所有接口操作
-
微服务层-公共组件与代理
服务对数据库的操作以及对大数据的暴露的API接口的调用,全部都是通过用代理层
六、优化我们的“健康码”项目架构
通过以上架构的总结,其实我想通过以往的架构设计经验,来如何优化我们本次”防疫健康码“架构,以及在一些技术点上的优化,下面我来主要介绍我的想法。
6.1、必须要引入网关能力
在我最近三年经历的的服务总结,我们发现,目前所有项目几乎都是微服务形式,因此必须要有网关,以及服务发现的能力,所以,我们简单总结:
服务网关 = 路由转发 + 过滤器+...
引入网关的基本简图如下:
-
1、服务网关、open-service和service启动时注册到注册中心上去;
-
2、用户请求时直接请求网关,网关做智能路由转发(包括服务发现,负载均衡)到open-service,这其中包含权限校验、监控、限流等操作
-
3、open-service聚合内部service响应,返回给网关,网关再返回给用户
其中
-
智能路由:接收外部一切请求,并转发到后端的对外服务open-service上去;
-
注意:我们只转发外部请求,服务之间的请求不走网关,这就表示全链路追踪、内部服务API监控、内部服务之间调用的容错、智能路由不能在网关完成;当然,也可以将所有的服务调用都走网关,那么几乎所有的功能都可以集成到网关中,但是这样的话,网关的压力会很大,不堪重负。
-
-
权限校验:只校验用户向open-service服务的请求,不校验服务内部的请求。服务内部的请求有必要校验吗?
-
API监控:只监控经过网关的请求,以及网关本身的一些性能指标(例如,gc等);
-
限流:与监控配合,进行限流操作;
-
API日志统一收集:类似于一个aspect切面,记录接口的进入和出去时的相关日志
-
综上:在做任何大型项目,使用网关+注册中心是必要的。
6.2 、能用缓存必须要用
在本次项目中,因为在对ISV的开发同学的技术能力不甚了解,因此在review代码中,出现了大量的,几乎都是直接连接mysql数据库进行业务逻辑操作,甚至出现了多表联合查询的情况发生。让人痛心不已,果然在将服务上线后,尤其以省级项目的时候,政府大力宣传,出现了服务各种不可用,数据库最终成为最大瓶颈。
因此,在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节。所以,就需要使用机器内存或者redis做一个缓冲操作,让请求先访问到redis,而不是直接访问Mysql等数据库。这样可以大大缓解数据库的压力。
按缓存类型可以分为:
1、本地缓存:不需要序列化,速度快,缓存的数量与大小受限于本机内存,很多语言提供一些框架来支持内存缓存,例如 guava cache,spring默认集成的 Ehcache。
2、分布式缓存:需要序列化,速度相较于本地缓存较慢,但是理论上缓存的数量与容量无限制(因为分布式缓存机器可以不断扩展),常见的分布式缓存包括 Redis 和 Memcache,本次使用了腾讯云的集群redis。
但是在使用缓存也要注意很多方面,比如缓存击穿,缓存以及mysql的一致性问题,避免方案有很多,比如双删,根据场景设置过期时间等等手段,在这里不一一详述。
6.3、索引必须优化
本次项目,我们也发现大量的sql语句没有命中索引:一方面java的很多同学喜欢使用多表联合查询,一方面没有那种在高并发大流量的情况下优化索引的意识,这两点在类toc项目中导致非常致命的性能问题。
每次评审,我都会要开发同学对sql进行explain的优化。
因此,我对mysql的索引原理以及优化,进行过一次讲座:
ppt链接:http://km.woa.com/group/27940/attachments/attachment_view/161143
6.4、解耦一定要用队列
伴随着业务的复杂,我们往往会遇到这个场景,一个数据操作后,需要触发下游若干个子操作。本次我们的项目使用rabbitmq,我在短信项目使用了公司内部的cmq的队列服务,效果是非常好的。
我们可以把消息队列(MQ)比作是一个存放消息的容器,Producer 负责生产消息,将消息发送到MQ,Consumer取出消息供自己使用。
我在这里是强烈推荐腾讯云的ckafka,稳定高效,金融级别的队列服务,如下图
腾讯云上也有同样的服务能力。
6.5 、如何优雅的拆分服务
在拆分我们微服务的的时候,我个人认为并不是完全的微服务化,将服务拆的很细,所以,经过我各种总结:
总结一下我的思路与原则:
1、分离服务中占用系统资源(cpu,内存,IO等)较多的功能,独立成服务器,屏蔽性能瓶颈,比如实现实现数据库代理层因为涉及到磁盘+网络的io,实现接口代理层,因为涉及到网络,
2、在同一服务器架构下的不同模块,应尽可能的复用某些服务器(进程服务级别的复用)。比如读取配置,统一出口。
3、以多线程并发的编程方式适应多核处理器。
4、宁可在服务器之间多复制数据,也要保持清晰的数据流向。
5、主要按照场景划分进程,若需按功能划分,必须保持整个逻辑足够的简单,并满足以上1,2点。
6.6、“防疫健康码“理想架构
经过以上总结与整理,我想本次的后台架构应该都是这个样子。
以上,是基于sprigcloud技术栈实现的技术架构图,包含了服务发现节点,统一配置节点,以及认证等基础设施。在服务集群模块,往往是比实际更加复杂。
七、总结
说了以上那么多,总结两点就是
1、如何能够搭建好一个好的后台项目架构
2、如何能够写出优雅的代码
在架构层次,一般分为:业务架构、技术架构、应用架构、部署架构,不同的架构关注面不一样。
业务架构:业务提供的能力,业务能做什么;
技术架构:技术领域解决问题,如存储、计算等;
应用架构:应用之间的关系;
部署架构:应用实际是怎样部署的,不同机房等。
所谓的软件架构,是对业务进行理解并设计,不同业务使用不同的软件架构,要画出一个概貌图出来表示这个系统有哪些关键部分,通过这些关键部分组成的骨架可以看到整个系统的扭转流程。
健壮的代码方面就需要多多看一些优秀的开源代码,多多总结实践。
综上述:架够是一种全局,代表了整体的方向,如果大方向出错了,即使再多的努力也是没有用的。