架构案例学习
2022-06-27 16:58:18 0 举报
架构实战案例学习
作者其他创作
大纲/内容
技术架构
概念
系统的物理模型
模型图
内容
接入系统,它负责接收用户的请求,然后把用户的请求分发到某个 Web 服务器进行处理,接入系统主要包括 DNS 域名解析、负载均衡、Web 服务器这些组件。
Web 服务器会把请求交给应用系统进行处理。一般来说,我们是基于某个开发框架来开发应用的,比如 Java 应用一般是基于 Spring MVC 框架。
还有基础平台,它由好几个部分组成:首先是各个语言的运行时,比如说 JVM;然后是容器或虚拟机;下面还有操作系统;最底下就是硬件和网络。
数据库、缓存、消息队列,以及 RPC 通讯框架等等。这里,我统称它们为核心组件
还有大量的运维系统,它们提供监控、安全、资源调度等功能
技术架构的挑战
技术架构的职责,首先是负责系统所有组件的技术选型,然后确保这些组件可以正常运行。
硬件的问题
硬件的处理能力有限
Scale Up:通过升级硬件来提升处理能力
垂直扩展是最简单的方式
但它有物理上的瓶颈或成本的问题
Scale Out:通过增加机器数量来提升处理能力
理论上可以应对所有服务器处理能力不足的情况,并实现系统处理能力和硬件成本保持一个线性增长的关系
如何有效地管理大量的机器,我们需要通过很复杂的技术架构设计来保障。
硬件不是 100% 的可靠
我们在做技术架构设计时,就要充分考虑各种硬件故障的可能性,做好应对方案
软件的问题
有利点
首先是弥补了硬件的缺陷。比如 Redis 集群,通过数据分片,解决了单台服务器内存和带宽的瓶颈问题,实现服务器处理能力的水平扩展
封装让我们可以更高效地访问系统资源。比如说,数据库是对文件系统的加强,使数据的存取更高效
问题
CAP 理论:C 代表系统内部的数据一致性,A 代码系统的可用性,P 代表节点之间的网络是否允许出问题,我们在这三者里面只能选择两个
技术架构的目标
要选择和组合各种软硬件,再结合我们开发的应用代码,来解决系统非功能性需求
系统的高可用
可用性的衡量标准是,系统正常工作的时间除以总体时间
导致不可用原因
软硬件本身有故障,比如机器断电,网络不通
及时解决当前节点的故障问题
做故障转移,让备份系统快速顶上
高并发引起的系统处理能力的不足,软硬件系统经常在处理能力不足时,直接瘫痪掉
提升处理能力,比如采取水平扩展、缓存等措施
流量控制在系统能处理的水平,比如采取限流、降级等措施
系统的高性能
高性能,并不是指系统的绝对性能要多高,而是系统要提供合理的性能
如何保证合理性
常规的流量进来,但系统内部处理比较复杂,我们就需要运用技术手段进行优化
高并发的流量进来,系统仍旧需要在合理的时间内提供响应
系统的可伸缩和低成本
保证系统在业务高峰时,要能快速地增加资源来提升系统处理能力;反之,当业务低谷时,可以快速地减少系统资源,保证系统的低成本
我们在做技术架构设计时,不能不顾一切地要求达到所有目标,而是要根据业务特点,选择最关键的目标予以实现。
高可用架构
概念
系统的故障点
资源不可用,包括网络和服务器出故障,网络出故障表明节点连接不上,服务器出故障表明该节点本身不能正常工作
资源不足,常规的流量进来,节点能正常工作,但在高并发的情况下,节点无法正常工作,对外表现为响应超时。
节点的功能有问题,这个主要体现在我们开发的代码上,比如它的内部业务逻辑有问题,或者是接口不兼容导致客户端调用出了问题;另外有些不够成熟的中间件,有时也会有功能性问题
系统故障解决思路
避免发生:我们可以通过 UPS(Uninterruptible Power System,不间断电源)来避免服务器断电,可以通过事先增加机器来解决硬件资源不足的问题。
故障转移:比如说,我们可以通过冗余部署,当一个节点发生故障时,用其它正常的节点来代替问题节点。
降低影响:比如说流量太大,我们可以通过限流,来保证部分用户可以正常使用,或者通过业务降级的手段,关闭一些次要功能,保证核心功能仍旧可用。
快速恢复:我们要尽快找到问题的原因,然后修复故障节点,使系统恢复到正常状态。
ps:处理线上事故的首要原则是先尽快恢复业务,而不是先定位系统的问题,再通过解决问题来恢复系统。
高可用架构原则
正面保障
冗余无单点
我们要保证系统的各个节点在部署时是冗余的,没有单点
水平扩展
很多时候,系统的不可用都是因为流量引起的:在高并发的情况下,系统往往会整体瘫痪,完全不可用。
减少损失
柔性事务
在很多业务场景中,系统的可用性比数据的实时一致性更重要
系统可降级
限流:让部分用户流量进入系统处理,其它流量直接抛弃
降级:系统抛弃部分不重要的功能,比如不发送短信通知,以此确保核心功能不受影响。
熔断:我们不去调用出问题的服务,让系统绕开故障点
功能禁用:针对具体的功能,我们设置好功能开关,让代码根据开关设置,灵活决定是否执行这部分逻辑。比如商品搜索,在系统繁忙时,我们可以选择不进行复杂的深度搜索。
做好监控
系统可监控
通过监控,我们可以实时地了解系统的当前状态
高可用手段
客户端 -> 接入层
首先要解决网络的可用性问题:可以拉多条线路,比如在企业私有的 IDC 机房和公有云之间,同时拉移动和电信的线路
可以选择 Nginx、HAProxy、LVS 等负载均衡软件,它们都能很好地支持双节点 +Keepalived 部署
通过冗余和自动切换避免了单点的故障
接入层 -> Web 应用
Web 应用通常是无状态的,我们可以部署多个实例,很方便地通过水平扩展的方式,提升系统的处理能力
接入层的负载均衡设备,可以通过各种算法进行多个 Web 实例的路由,并且对它们进行健康检测,如果某个实例有问题,请求可以转发到另一个实例进行处理,从而实现故障的自动转移。
还可以在接入层做限流,比如,在 Nginx 中设置每秒多少个并发的限制,超过这个并发数,Nginx 就直接返回错误
同时支持了 Web 节点的水平扩展、自动故障转移以及系统的可降级(限流)
Web 应用 -> 内部服务
服务通常也是无状态的,我们也可以通过部署多个实例进行水平扩展
可以使用传统的代理服务器方式,进行请求分发
很多的微服务框架本身就支持服务的直接路由,比如在 Spring Cloud 中,我们就可以通过 Eureka 进行服务的自动注册和路由
我们可以为不同服务配置不同的线程池,实现资源隔离,避免因为一个服务响应慢,而占用所有的线程资源;如果某个服务调用失败,我们可以对它进行熔断操作,避免无谓的超时等待,影响调用方的整体性能。
针对具体的功能,我们还可以做一些功能开关。开关实际上是一个标志变量,它的值可以是 on/off, 我们在代码中可以根据它的值,来确定某一段逻辑是否要执行。开关的值可以在数据库或配置系统里定义,这样我们就能够通过外部的开关值,控制应用内部的行为,
同时支持了服务节点的水平扩展、自动故障转移以及系统的可降级(熔断和业务开关)。
访问基础资源
关系数据库
主从部署,一方面通过读写分离,提升数据库读的性能,减轻主库压力
另一方面,数据库有成熟的 MHA 方案,支持主库故障时,能够自动实现主从切换,应用可以通过 VIP 访问数据库
物理的水平分库方式,对数据进行分片,这样就有多个主库支持写入
缓存
数据读写比很高的情况下,我们可以利用缓存优化数据库的访问性能,包括进程内部缓存和分布式缓存,缓存是应对高并发的有效武器。
通过多节点支持处理能力的水平扩展,通过数据的多副本来支持故障转移
消息系统
通过多节点部署来支持处理能力的水平扩展,也能通过数据的多分区,实现故障的自动切换
案例一:实现O2O平台24小时在线
背景
这是一个小程序点餐平台,用户在小程序上点餐并支付完成后;订单会先落到订单库,然后进一步推送到门店的收银系统;收银系统接单后,推送给后厨系统进行生产;同时返回小程序取餐码,用户可以凭取餐码去门店取餐或收取外卖。
预计在高峰时,前端小程序请求将会达到每秒 10 万 QPS,并且预计首日的订单数量会超过 500 万。在这种高并发的情况下,我们为了保证用户的体验,系统整体的可用性要达到 99.99%。
具体过程
小程序前端通过 Nginx 网关,访问小程序服务端
小程序服务端会调用一系列的基础服务,完成相应的请求处理,包括门店服务、会员服务、商品服务、订单服务、支付服务等,每个服务都有自己独立的数据库和 Redis 缓存;
订单服务接收到新订单后,先在本地数据库落地订单,然后通过 MQ 同步订单给 OMS 履单中心
门店的收银系统通过 HTTP 远程访问云端的 OMS 履单中心,拉取新订单,并返回取餐码给 OMS,OMS 再调用小程序订单服务同步取餐码
小程序前端刷新页面,访问服务端获得取餐码,然后用户可以根据取餐码到门店取餐或等待外卖
架构图
高可用改造措施
总体改造架构图
前端接入改造
小程序端的CDN优化
原因:用户点餐前,需要先浏览商品和菜单,这个用户请求的频率很高,数据流量大,会对服务端造成很大的压力
措施:通过 CDN 供应商,在全国各地构建了多个 CDN 中心,储存静态的商品数据,特别是图片,这样小程序前端可以就近访问 CDN,流量无需通过小程序服务端,缓解了服务端的压力。
Nginx 负载均衡
原因:一台 Nginx 一般可以支持数万的并发,本来这里无需这么多台 Nginx,这是因为云服务商对单个 LB 的接入有网络带宽的限制,所以我们要通过提升 Nginx 的数量,来保证接入有足够的带宽。
措施:独立搭建了数十台的 Nginx 集群,集群除了负载均衡,还提供限流支持,如果 QPS 总数超过了 10 万,前端的访问请求将会被丢弃掉
Nginx 在这里还有一个好处,就是可以实时提供每个接口的访问频率和网络带宽占用情况,能够起到很好的接入层监控功能
收银端的通信线路备份
门店的收银系统会通过前置代理服务器,来访问云端的 OMS 系统,这个代理服务器部署在商户自己的 IDC 机房,原来只通过电信线路和云端机房打通。在这次改造中,我们增加了移动线路,这样当电信主线路出问题时,系统就可以快速地切换到移动线路
应用和服务的水平扩展
小程序服务端的部署,我们把实例数从十几台提升到了 100 台,水平扩展它的处理能力
基础服务全部迁移到了容器环境,这样在提升资源利用率的同时,也更好地支持了基础服务的弹性扩容
小程序服务端依赖了 7 个基础服务,每个基础服务也做了相应的水平扩展
订单水平分库
背景:下单高峰期,订单主库的写访问频率很高,一个订单会对应 6~7 次的写操作,包括了创建新订单和订单状态变更;订单的读操作,我们之前通过一主多从部署和读写分离,已经得到了支持,负责写入的主库只有一个实例
措施
通过订单的水平分库,扩充了订单主库的实例数,改造后,我们有 4 个主库来负责订单数据写入
通过订单 ID 取模进行分库,基于进程内的 Sharding-JDBC 技术,实现了数据库的自动路由。
数据库的配置,也从原来的 8 核 16G 提升到了 16 核 32G,这样我们通过硬件的垂直扩展,进一步提升了数据库的处理能力。
异步化处理
通过消息系统对它们进行了解耦。 一方面,前台下单要求比较快,后台 OMS 的订单处理能力比较弱(OMS 库没有进行水平分库),通过消息的异步化处理,我们实现了对订单流量的削峰;另一方面,如果 OMS 有问题,以异步的方式进行数据同步,也不会影响前台用户下单。
在用户支付完成或者后台生成取餐码后,我们会以微信消息的方式通知用户,这个在代码中,也是通过异步方式实现的,如果微信消息发送不成功,用户还是可以在小程序上看到相关信息,不影响用户取餐。
缓存的使用
当收银系统向 OMS 拉取新订单时,OMS 不是到数据库里查询新订单,而是把新订单先保存在 Redis 队列里,OMS 通过直接查询 Redis,把新订单列表返回给收银系统
商品服务中,菜单和商品数据也是放在了 Redis 中,每天凌晨,我们通过定时任务,模仿前端小程序,遍历访问每个商品数据,实现对缓存的预刷新,进一步保证缓存数据的一致性,也避免了缓存数据的同时失效,导致缓存雪崩。
主动通知,避免轮询
背景:原来的架构中,前台小程序是通过轮询服务端的方式,来获取取餐码;同样,商户的收银系统也是通过轮询 OMS 系统拉取新订单,这样的收银系统有上万个,每隔 10s 就会拉取一次。这种盲目轮询的方式,不但效率低,而且会对服务端造成很大的压力
落地了消息推送中心,收银系统通过 Socket 方式,和推送中心保持长连接。当 OMS 系统接收到前台的新订单后,会发送消息到消息推送中心;然后,收银系统就可以实时地获取新订单的消息,再访问 OMS 系统拉取新订单。为了避免因消息推送中心出问题(比如消息中心挂掉了),导致收银系统拿不到新订单,收银系统还保持对 OMS 系统的轮询,但频率降低到了 1 分钟一次。
小程序前端会通过 Web Socket 方式,和消息推送中心保持长连接。当 OMS 系统在接收到收银系统的取餐码后,会发送消息到消息推送中心。这样,小程序前端可以及时地获取取餐码信息。
一体化监控
Zabbix 做系统监控、CAT(Central Application Tracking) 做应用监控、拉订单曲线做业务监控
所有的节点都在一个页面里显示,包括 Web 应用、Redis、MQ 和数据库,页面也会体现节点之间的上下游关系。我们通过采集节点的状态数据,实时监测每个节点的健康程度,并且用红黄绿三种颜色,表示每个节点的健康状况
小结
如何检验
按照 10 万 QPS 和 99.99% 的可用指标要求,通过大量的压测来确定的。
首先,我们对每个节点进行接口压测,做各种性能优化,确定好需要的机器数量
我们利用 JMeter,模拟小程序前端发起混合场景的调用,以此检验系统的抗压能力,以及在压力下,系统的可用性是否达到了预定的要求
我们在生产环境中根据压测环境,按照服务器 1:1 的数量进行部署,保证性能不打折,最终这个小程序下单平台总的机器规模,也达到了数百台的量级
改造措施和原则对应关系
正面保障的角度来看,我们首先在各个环节都避免了单点,包括远程通信线路,这样能保证任意一个节点出了问题,都有其他实例可以顶上去
我们通过节点的垂直扩展和水平扩展,大幅度提升了系统的处理能力,包括应用、服务和数据库的扩展
有效地利用了 Redis 缓存,对高频的订单和菜单数据的读取进行了优化。
在柔性处理方面,我们通过异步处理,来优化系统的性能和避免大流量的直接冲击,包括使用消息系统解耦前台下单系统和后台 OMS 系统,以及通过及时的消息推送,避免前端盲目轮询服务端。
在系统接入层,通过 Nginx 进行限流,为系统的可用性进行兜底,这样在流量超过预估时,能够有效地避免后端系统被冲垮
强有力的监控手段,可以实时全面地了解系统运行状况,随时为异常情况做好准备。
案例二:如何第一时间知道系统哪里有问题
监控分类
监控层次图
用户体验监控:指的是从前端用户的访问速度出发,来监测系统的可用性,包括页面能否打开、关键接口的响应时间等等,用户体验监控一般结合前端的埋点来实现。
业务监控:它是从业务结果的角度来看,比如说订单数、交易金额等等;对于业务监控,我们一般是从数据库里定时拉取业务数据,然后以曲线的方式展示业务指标随着时间的变化过程。除了当前的曲线,一般还有同比和环比曲线。同比是和前一天的数据进行比较,环比是和一周前的数据进行比较
应用监控:指的是对自己开发的代码进行监控,比如接口在一段时间内的调用次数、响应时间、出错次数等等;一个外部请求的处理过程包含了很多环节,比如说网关、应用、服务、缓存和数据库,我们可以通过调用链监控把这些环节串起来,当系统有问题时,我们可以一步步地排查。有很多 APM 工具可以实现调用链监控,如 CAT、SkyWalking 等等。
中间件监控:指的是对标准中间件进行监控,它是第三方开发的代码,比如数据库、缓存、Tomcat 等等,这些组件对应的是系统的 PaaS 层。这些中间件往往带有配套的监控系统,比如,RabbitMQ 就有自带的监控后台。
基础平台监控:指的是对系统底层资源进行监控,如操作系统、硬件设备等等,这个层次的监控对应的是系统的 IaaS 层。Zabbix 就是典型的基础设施监控工具,它可以监控 CPU、内存和磁盘的使用情况。
监控痛点
发现问题慢:业务监控的曲线一般 1 分钟更新一次,有时候因为正常的业务抖动,Monitor 还需要把这种情况排除掉。因此,他会倾向于多观察几分钟,这样就导致问题的确认有很大的滞后性。
定位问题慢:系统节点多,大量的人需要介入排查,而且由于节点依赖复杂,需要反复沟通才能把信息串起来,因此很多时候,这种排查方式是串行或者说无序的。一方面,无关的人会卷入进来,造成人员的浪费;另一方面排查效率低,定位问题的时间长。
解决问题慢:当定位到问题,对系统进行调整后,验证问题是否已经得到解决,也不是一件很直观的事情,需要各个研发到相应的监控系统里去进行观察,通过滞后的业务曲线观察业务是否恢复
总结:我们面对事故,就像是热锅上的蚂蚁,众说纷纭,谁也不能肯定问题出在哪里。结果呢,我们病急乱投医,胡乱干预系统,不但没能解决问题,而且往往引发了二次事故。
解决思路
两点要求
系统能够自动地判断每个节点是否正常,并直观地给出结果,不需要经过专业人员的分析。
系统能够自动把各个节点的监控信息有机地串起来,从整体的角度对系统进行监控,不需要很多人反复地进行沟通。
解决思路类比
交通监控
我们经常可以在市内的高架上看到交通拥堵示意图。在下面的这张交通信息图上,你可以看到,每条道路都通过上下左右不同的方位,有机地关联在一起,形成一个整体的交通网络;同时,在交通图上,通过红黄绿三种状态,实时地反映了每条道路的拥堵情况。这样,司机就可以非常直观地了解道路是否畅通,从而提前避开拥堵路段。
实时,直观, 整体
系统监控
首先,系统中的每个节点对应交通图的一条道路
节点的健康状况对应道路的拥堵情况,节点同样也有红黄绿三种不同的颜色,来展示该节点是否正常
节点之间的调用关系对应道路的方位关系。
解决方案
方案架构图
每个被监控的节点,均有对应的 Agent 负责采集健康数据,不同的节点类型,数据采集的方式也不一样:Web 节点通过 HTTP 接口调用,Redis 通过 Jredis,MQ 也通过对应的 API 接口,DB 则采用 JDBC
Agent 每隔 3s 采集节点数据,然后上报数据给 Monitor Service;Monitor Service 负责确定节点当前的状态并保存到数据库,这样就完成了节点健康状态的检测;
前端 Dashboard 每隔 3s,拉取所有节点的状态,以红黄绿三种颜色在同一页面展示,同时还会显示具体的出错信息。
如何判断节点监控状态
以 DB 为例简单说明一下。Agent 每隔 3 秒会去尝试连接数据库,并进行简单的表读写操作,如果连接和读写都能够成功,那就说明该 DB 当前的运行是正常的,相应的,在 Dashboard 里面,这个 DB 节点会显示为绿色。
Web 应用的健康规则会相对复杂一些,我们会结合 Web 应用接口的功能和性能来做综合判断
案例三:打造一体化监控系统
节点信息采集
方法
架构图
Agent 负责采集节点的健康数据,每隔 3s,主动访问一次
Agent 会根据这些数据,结合相应的规则,来判断节点的健康状态
健康状态有三种,分别是错误、警告和正常,这三种状态也对应了 Dashboard 中节点的红黄绿三种颜色。
Agent 在获取了这 4 类节点的健康状态后,会调用 Monitor Service 进行数据上报,如果节点有问题,上报内容还包括具体的错误消息。
注意事项
Agent 本身是一个独立的应用,它不需要和节点部署在一起,如果节点数量少,我们部署一个 Agent 实例就可以;如果节点的数量比较多,我们可以部署多个 Agent 实例,比如给每类节点部署一个实例。总的要求就是,让 Agent 能够在 3s 内,完成所有节点的健康信息收集就可以了。
节点的连接信息,事先是配置在数据库里的,比如数据库节点的 IP 端口、账号和密码等等,当 Agent 启动的时候,它会通过 Monitor Service 获取节点配置信息,Agent 在运行过程中也会定期刷新这个配置。
节点分类
redis
Agent 通过 Jredis API,尝试连接 Redis 实例并进行简单的读写。如果这个操作没有问题,就表明 Redis 当前的健康状态是正常的,否则就是错误的。
MQ
Agent 是通过 MQ API,来检测 MQ 节点是否有活跃的消费者在连接,同时检测队列积压的消息数量。如果没有活跃的消费者,或者未消费的消息超过了预设的值,就表明当前的 MQ 节点的健康状态是错误的,否则它就是正常的。
数据库
Agent 是通过 JDBC 去连接数据库,并对表进行简单的读写。如果操作成功,表明数据库的健康状态是正常的,否则就是错误的。
web应用
Agent 采集的方式则稍微复杂一些,它会同时采集应用的功能和性能数据,具体包括最近 3s 的接口调用次数、接口平均响应时间、接口出错次数、节点的健康状态和错误消息。
Web 节点会预先提供一个 HTTP 接口,Agent 通过调用这个接口,返回当前 Web 实例最近 3s 的健康状态。
最主要的就是 status 字段,它表明了 Web 节点最近 3s 是否健康,如果是“error”或者“warning”,返回的结果还会包含 error_info 字段,它负责给出具体的错误信息
节点接入
对于 Redis、MQ、DB 这三类节点,接入监控系统只需要提供配置信息就可以了,无需额外的开发
Web 应用接入监控
改造点
针对每次接口调用,应用程序需要在接口代码中记录本次调用的耗时以及出错状况;
应用程序需要汇总最近 3 秒的接口调用情况,根据规则,给出节点的健康状态;
应用程序提供一个对外的 HTTP 接口,供 Agent 来获取上一步给出的健康状态。
为了方便 Web 应用的接入,监控系统开发团队提供了 SDK,它内置了接口调用信息的统计和健康计算规则。应用程序借助 SDK,就可以给 Agent 提供最终的健康结果,也就是说 SDK 帮助应用完成了最复杂的第二步工作。
SDK 的内部,实际上是一个 HashMap 结构,它的 key 就是 Web 应用的各个接口名字,它的 value 是一个简单的对象,包含这个接口最近 3s 总的调用数量、总的出错次数和总的耗时等。
健康规则
如果最近 3s,接口调用没有发生错误,节点的健康结果就是正常;如果出错次数在 1 到 5 之间,健康结果就是警告;如果大于 5,健康结果就是错误
如果最近 3s,接口响应时间超过正常值的 10 倍,健康结果就是错误;如果在 5 倍到 10 倍之间,健康结果就是警告,否则结果就是正常
接口调用响应时间的正常值是怎么来的呢?这个值不是预先设置的,我们知道,如果预先设置的话,这个数字很难确定。这里的正常值其实是 SDK 自动计算的,SDK 会记录应用从启动开始到目前为止,接口的总耗时和总调用次数,然后得出平均的响应时间,作为接口调用的正常耗时
值得注意的是,SDK 会针对每个接口进行分别计算,最后取最差接口的结果。比如说,应用有 10 个接口,如果其中 8 个接口是正常状态,1 个接口是警告状态,1 个接口是错误状态,那么该应用的健康结果就是错误状态。
前端展示
我们把页面的展示内容分为三个层次,分组、应用和节点。一个页面代表一个系统,它包含多个分组,一个分组包含多个应用,一个应用包含多个节点(节点代表了一个具体的实例,有独立 IP)。
一个系统页面监控
前端展示界面
红色圈里的是各个分组,蓝色圈里是各个应用。我们可以很清晰地看到,“应用层”分组里的会员应用,会调用“依赖服务”分组里的四个服务。
布局是在后台定义的,保存在数据库里。我们为每个系统预先设定好布局,类似 HTML 里的 Table 布局语法,行用 TR 表示,列用 TD 表示。我们根据页面显示要求,提前确定好分组和应用会占用多少行,多少列
前端通过 Monitor Service 的接口获取页面的布局信息,然后根据布局信息进行动态展示,如果系统的部署有变化,我们在管理后台调整布局就可以了,非常灵活。
我们通过类似 Table 方式的布局,前端通过一套代码,就可以满足所有系统的节点展示需求,并且能够比较好地体现应用之间的上下游依赖关系,当系统有问题时,我们就可以很直观地判断出问题的根源在哪里。
针对所有系统的大盘监控
前端页面展示
首先,前端页面读取所有节点的健康状态,按照节点分类展示有问题的节点,并标识出相应的颜色;
然后,节点的具体出错信息也可以在大盘中展示;
最后,我们根据每个系统内部节点的健康状况,按照一定的规则,算出各个系统的总体健康状态,在页面展示系统的健康状态。
库表设计
表结构关系图
系统信息表,用来定义监控体系里有哪些系统,其中 Layout(布局)定义了该系统前端的布局方式。
节点信息表,用来定义节点的配置信息,其中节点类型可选的值有 Web 应用、Redis、MQ、DB 等等,节点类型决定了节点健康信息的获取方式。其他字段用于 Agent 如何去连接节点,还有邮箱和手机用于节点出错时,相应的人可以接收报警信息。
节点监控状态表,用来记录每个节点的最新健康状态,用于 Dashboard 显示。
总结
从监控的层次来看,这个监控系统可以分为大盘级别监控 -> 系统级别监控 -> 节点级别监控,你甚至还可以快速关联到每个节点的专门监控系统,比如 Zabbix 的硬件监控、CAT 的应用监控、ELK 的日志监控等等,实现最粗粒度到最细粒度监控的一体化。
相比较各个专门的监控系统,我们这里不求对各类节点的监控做得多深入,而是大致上能反映节点的健康状况即可。我们更强调的是要把系统的所有节点串起来,直观地反映它们的健康状况,避免监控系统的碎片化和专业化
这个监控系统就相当于是一个全身体检,不同于对某个器官的深入检查,它是把系统的各个部位都做了初步检查,并且给出了一个很容易阅读的结果报告。这个系统实现起来很简单,但非常实用,我们相当于用 20% 的成本,实现了 80% 的监控效果。
高性能和可伸缩架构
概念
系统性能的挑战
系统的 TPS 很低,只要流量一大,系统就挂,加机器也没用;
机器的资源利用率很低,造成资源严重浪费。
常用的性能数据
总结图
内存的数据读取是 SSD 磁盘的 10 倍,SSD 磁盘又是普通磁盘的 10 倍,一个远程调用的网络耗时是机房内部调用的 1000 倍,一个分布式缓存访问相对于数据库访问,性能也有数十倍的提升。
高性能策略
背景
对于一个实际的业务系统来说,情况就会复杂很多。一个外部请求进来,需要经过内部很多的软硬件节点处理,用户请求的处理时间就等于所有节点的处理时间相加。只要某个节点性能有问题(比如数据库),或者某项资源不足(比如网络带宽),系统整体的 TPS 就上不去。这也是在实践中,很多系统 TPS 只有个位数的原因。
策略
加快单个请求处理
优化处理路径上每个节点的处理速度。比如说,我们可以在代码中使用更好的算法和数据结构,来降低算法的时间和空间复杂度;可以通过索引,来优化数据库查询;也可以在高读写比的场景下,通过缓存来代替数据库访问等等
并行处理单个请求。我们把一个请求分解为多个子请求,内部使用多个节点同时处理子请求,然后对结果进行合并。
同时处理多个请求
当有多个外部请求进来时,系统同时使用多个节点来处理请求,每个节点分别来处理一个请求,从而提升系统单位时间内处理请求的数量。
在同一个节点内部,我们还可以利用多进程、多线程技术,同时处理多个请求。
请求处理异步化
系统处理请求不一定要实时同步,请求流量的高峰期时间往往很短,所以有些时候,我们可以延长系统的处理时间,只要在一个相对合理的时间内,系统能够处理完请求就可以了,这是一种异步化的处理方式。
我们在处理核心业务时,把相对不核心的逻辑做异步化处理,也是这个思路。比如说下单时,系统实时进行扣库存、生成订单等操作,而非核心的下单送积分、下单成功发消息等操作,我们就可以做异步处理,这样就能够提升下单接口的性能。
总结
首先,我们要加快单个请求的处理。单节点性能提升是系统整体处理能力提升的基础,这也是我们作为技术人员的基本功。但这里的问题是,节点的性能提升是有瓶颈的,我们不能超越前面说的基础操作的性能。至于把请求分解为多个小请求进行并行处理,这个在很多情况下并不可行,
对多个请求进行同时处理是应对海量请求的强有力手段,如果我们能够水平扩展每一个处理节点,这样在理论上,系统处理请求的能力无限的。而这里的问题是,对于无状态的计算节点,我们很容易扩展,比如说 Web 应用和服务;但对于有状态的存储节点,比如说数据库,要想水平扩展它的处理能力,我们往往要对系统做很大的改造。
异步化处理,在某些场景下是很好的提升系统性能的方式,我们不用增加机器,系统就能够完成请求的处理。但问题是,同步调用变成异步的方式,往往会导致处理结果不能实时返回,有时候会影响到用户体验,而且对程序的改造也会比较大。
可伸缩策略
背景
我们经常说,业务是可运营的,而实际上,系统也是可运营的。我们可以动态地调整系统软硬件部署,在业务高峰期增加软硬件节点,在业务低谷期减少软硬件节点,这就是系统的可伸缩能力。
策略
节点级别的可伸缩
对于无状态的节点,我们直接增减节点就可以了。比如说订单服务,白天我们需要 10 台机器来提供服务,到了半夜,由于单量减少,我们就可以停掉部分机器。如果做得好,我们还可以实现弹性伸缩,让系统根据硬件的负载情况,来确定机器的数量。比如说,当服务器的 CPU 或内存使用率在 10% 以下了,系统就自动减少服务实例的数量。
而对于有状态的服务,我们需要能够支持状态数据的重新分布。比如进行水平分库的时候,要从 4 个库增加到 8 个库,我们需要把原先 4 个库的数据,按照新的分库规则,重新分布到 8 个库中。如果这个调整对应用的影响小,那系统的可伸缩性就高。
存在的问题:系统是一个整体,如果只是节点级别的伸缩,我们可能要对多个节点分别进行操作,而且不同节点的资源配置会相互影响,这样对各个节点的调整就非常复杂,影响了系统的可伸缩能力。如果能实现系统端到端的伸缩,同时对多个节点进行伸缩处理,那系统的可伸缩能力就更高了。
系统级别的可伸缩
把多个处理节点打包在一起,形成一个处理单元。
通过单元化处理,我们把相关的节点绑定在一起,同进同退,更容易实现系统的可伸缩。
演示图
总结
如果我们把单元扩大到系统的所有节点,这就是一个虚拟机房的概念。我们可以在一个物理机房部署多个虚拟机房,也可以在不同的物理机房部署多个虚拟机房,这样,部署系统就像部署一个应用一样,系统的可伸缩性自然就更好。
子主题
高性能和可伸缩架构原则
可水平拆分和无状态
这意味着节点支持多实例部署,我们可以通过水平扩展,线性地提升节点的处理能力,保证良好的伸缩性以及低成本。
短事务和柔性事务
短事务意味着资源锁定的时间短,系统能够更好地支持并发处理
柔性事务意味着系统只需要保证状态的最终一致,这样我们就有更多的灵活手段来支持系统的高性能,比如说通过异步消息等等。
数据可缓存
缓存是系统性能优化的利器,如果数据能够缓存,我们就可以在内存里拿到数据,而不是通过磁盘 IO,这样可以大大减少数据库的压力,相对于数据库的成本,缓存的成本显然也更低。
计算可并行
如果计算可并行,我们就可以通过增加机器节点,加快单次请求的速度,提高性能。
可异步处理
异步处理给系统的处理增加了弹性空间,我们可以利用更多的处理时间,来降低系统对资源的实时需求,在保证系统处理能力的同时,降低系统的成本。
虚拟化和容器化
虚拟化和容器化是指对基础资源进行了抽象,这意味着我们不需要再依赖具体的硬件,对节点的移植和扩容也就更加方便。
虚拟化和容器化对系统的资源切分得更细,也就说明对资源的利用率更高,系统的成本也就更低
高性能架构案例
背景
在 2014 年的时候,1 号店作为网上超市类电商,经常在线上举行各种大促活动。比如进口牛奶促销活动,每次促销的牛奶有几十万盒,促销价格非常优惠,一般这样的促销活动会在某个整点的时间进行开卖(如上午 10 点)
这本质上是一种秒杀活动,但商品数量非常大,一瞬间会有大量的用户流量涌入,流量可以高达平时的几十倍。而且和少量商品的秒杀不同,这些都是有效流量,最终会生成订单。
而在正常情况下,系统因为资源有限,只能处理 10% 的流量,无法处理剩下的 90% 流量,瞬间高并发的流量涌入,很大程度上会引起后台系统超时报错,导致用户下单不成功。
最终的结果就是,系统往往由于过载,整体处理能力下降,甚至瘫痪,导致所有用户都无法购买。
为什么加机器不行
我们对峰值流量的预估以及要加多少机器都是拍脑袋的,和实际出入往往很大,一旦估计少了,系统同样会面临过载的风险;
为了短暂的几分钟促销,我们需要增加大量的机器,事先要做很多的运维准备工作,不但浪费资源,而且效率很低;
最为关键的是,有些处理节点,系统不是通过加机器就能扩展处理能力的,比如商品库存数据库,下单时,我们需要扣库存,而为了防止库存更新冲突,我们需要锁定库存记录,导致系统的并发处理能力有限,这个问题单靠加机器是解决不了的。
解决方案
总结该活动特点
短期 只有1-2分钟
短期无法处理大量订单,可以延长处理订单时长
用户还是愿意为买到等待一会
得出结论:可以利用异步处理的思路来应对秒杀活动
方案架构图
关键点
用户在商品详情页提交订单后,这个订单会作为预订单进入排队区,同时排队系统会返回用户一个排队编号,这个编号用于跟踪后续的订单处理进度;
用户被引导到一个等待页,这个页面会根据排队号,定时地查询排队系统,排队系统会返回预订单在队列中的位置信息,包括它前面还有多少未处理的预订单,以及后台系统大概还要多久会处理这个预订单,这样用户就不会焦虑;
在排队系统的处理区,有很多消费者,它们依次从排队区的队列里获取预订单,然后调用后台下单系统生成实际的订单;
随着预订单变成正式的订单,队列里的预订单会逐渐变少,如果当前的预订单已经从队列里被移除了,用户的等待页就会检测到这个情况,页面自动跳转到订单完成页,这就和常规的购物流程一样了,用户进行最后的支付,最终完成整个前台下单过程。
这种方式比较适合瞬时有高并发流量的场景,单高峰会持续一段较长的时间,而用户对订单处理又有比较高的时间要求,那就不适合采用这种异步削峰的方式。
内部设计
针对队列的技术选型,排队系统使用的是 Redis,而不是 MQ。因为相对于 MQ 来说,Redis 更轻量级,性能更好,它内置了队列数据结构,除了和 MQ 一样支持消息的先进先出以外,我们还可以获取队列的长度,以及通过排队号获取消息在队列中的位置,这样我们就可以给前端反馈预订单的处理进度。
关于队列的调度问题,也就是消费者优先从哪个队列里拿预订单,排队系统会结合下单时间和队列的长度来确定,以保证用户合理的时间体验。
关于队列长度,为了保证用户能够买到商品,我们并不是把所有前台的下单请求都会放到队列里,而是根据参与活动的秒杀商品数量,按照 1:1 的比例,设置队列初始长度,这样就保证了进入队列的请求最终都能生成订单。
这里我们为每个秒杀商品提供一个单独的队列,这样就可以分散数据在 Redis 中的存取,多个队列可以提供更好的性能。
其他优化点
对于秒杀活动来说,参与活动的商品种类是有限的,但这些商品库存的扣减非常频繁,因此我们建立了活动库存的概念,把少量参与促销的商品种类单独放在一个库里,避免和大量常规的商品放在一起,这样也大幅度地提高了库存数据库的读写性能。
可伸缩架构案例
背景
每日的订单量接近 100 万
订单库已有上亿条记录,订单表有上百个字段,这些数据存储在一个 Oracle 数据库里
已经实现了订单的服务化改造,只有订单服务才能访问这个订单数据库
单一数据库的存储容量和访问性能都已经不能满足业务需求了,订单数据库已成为系统的瓶颈
解决思路
垂直分库
垂直分库就是数据库里的表太多,我们把它们分散到多个数据库,一般是根据业务进行划分,把关系密切的表放在同一个数据库里
水平分库
某些表太大,单个数据库存储不下,或者数据库的读写性能有压力。通过水平分库,我们把一张表拆成多张表,每张表存放部分记录,分别保存在不同的数据库里
整体改造图
分库策略
分库的维度
原则
尽量避免应用代码和 SQL 性能受到影响。具体地说,就是现有的 SQL 在分库后,它的访问尽量落在单个数据库里
方法
最好的做法是,先收集所有 SQL,挑选出 WHERE 语句中最常出现的过滤字段,分别统计这常用字段的使用情况
每个 SQL 访问的频率是不一样的,所以,我们还要分析每个 SQL 的实际访问量。
数据怎么分
基本方法
根据 ID 范围进行分库,比如把用户 ID 为 1 ~ 999 的记录分到第一个库,1000 ~ 1999 的分到第二个库,以此类推
根据 ID 取模进行分库,比如把用户 ID mod 10,余数为 0 的记录放到第一个库,余数为 1 的放到第二个库,以此类推
两种方法优劣
为了运维方便,选择 ID 取模进行分库的做法比较多。同时为了数据迁移方便,一般分库的数量是按照倍数增加的,比如说,一开始是 4 个库,二次分裂为 8 个,再分成 16 个。这样对于某个库的数据,在分裂的时候,一半数据会移到新库,剩余的可以不用动。
分几个库
和单库能处理的记录数有关。一般来说,MySQL 单库超过了 5000 万条记录,Oracle 单库超过了 1 亿条记录,DB 的压力就很大
如果分库的数量太少,我们达不到分散存储和减轻 DB 性能压力的目的;如果分库的数量太多,好处是单库访问性能好,但对于跨多个库的访问,应用程序需要同时访问多个库,如果我们并发地访问所有数据库,就意味着要消耗更多的线程资源;如果是串行的访问模式,执行的时间会大大地增加
分库数量还直接影响了硬件的投入
分库带来的问题
分库路由
对于单库访问,比如查询条件已经指定了用户 ID,那么该 SQL 只需访问特定库即可。此时应该由 DAL 层自动路由到特定库,当库二次分裂时,我们也只需要修改取模因子就可以了,应用代码不会受到影响。
对于简单的多库查询,DAL 层负责汇总各个分库返回的记录,此时它仍对上层应用透明。
对于带聚合运算的多库查询,比如说带 groupby、orderby、min、max、avg 等关键字,建议可以让 DAL 层汇总单个库返回的结果,然后由上层应用做进一步的处理。这样做有两方面的原因,一方面是因为让 DAL 层支持所有可能的聚合场景,实现逻辑会很复杂;另一方面,从 1 号店的实践来看,这样的聚合场景并不多,在上层应用做针对性处理,会更加灵活。
分页处理
在分库情况下,对于每个数据库,我们要取更多的记录,并且汇总后,还要在应用里做二次排序,越是靠后的分页,系统要耗费更多的内存和执行时间
解决方案
可以限定用户只能看到前面 n 页
如果是后台批处理任务要求分批获取数据,我们可以加大分页的大小
分库设计时,一般还有配套的大数据平台负责汇总所有分库的记录,所以有些分页查询,我们可以考虑走大数据平台。
分库字段映射
分库字段只有一个,比如这里,我们用的是用户 ID,如果给定用户 ID,这个查询会落到具体的某个库。但我们知道,在订单服务里,根据订单 ID 查询的场景也很多见,不过由于订单 ID 不是分库字段,如果不对它做特殊处理,系统会盲目查询所有分库,
解决方案
为订单 ID 和用户 ID 创建映射,保存在 Lookup 表里
在实际使用时,我们可以通过分布式缓存,来优化 Lookup 表的查询性能。
总体架构图
架构图
注释
上层应用通过订单服务访问数据库;
分库代理实现了分库相关的功能,包括聚合运算、订单 ID 到用户 ID 的映射,做到分库逻辑对订单服务透明
Lookup 表用于订单 ID 和用户 ID 的映射,保证订单服务按订单 ID 访问时,可以直接落到单个库,Cache 是 Lookup 表数据的缓存;
DDAL 提供库的路由,可以根据用户 ID 定位到某个库,对于多库访问,DDAL 支持可选的多线程并发访问模式,并支持简单的记录汇总;
Lookup 表初始化数据来自于现有的分库数据,当新增订单记录时,由分库代理异步写入
如何安全落地
方案
首先,实现 Oracle 和 MySQL 两套库并行,所有数据读写指向 Oracle 库,我们通过数据同步程序,把数据从 Oracle 拆分到多个 MySQL 库,比如说 3 分钟增量同步一次
其次,我们选择几个对数据实时性要求不高的访问场景(比如访问历史订单),把订单服务转向访问 MySQL 数据库,以检验整套方案的可行性。
最后,经过大量测试,如果性能和功能都没有问题,我们再一次性把所有实时读写访问转向 MySQL,废弃 Oracle。
总结
第一阶段,把部分非实时的功能切换到 MySQL,这个阶段主要是为了验证技术,它包括了分库代理、DDAL、Lookup 表等基础设施的改造
第二阶段,主要是验证业务功能,我们把所有订单场景全面接入 MySQL
项目总结
花了很大精力做性能测试。为了模拟真实的线上场景,我们通过 TCPCopy,把线上实际的查询流量引到测试环境,先后经过 13 轮的性能测试,最终 6 个 MySQL 库相对一个 Oracle
订单 ID 的生成规则,使其包括用户 ID 后三位,这样新订单 ID 本身就包含了库定位所需信息,无需走 Lookup 映射机制。
扩充数据库步骤
增加 6 个 MySQL 实例,把现有 6 个库的数据同步到新的库,比如说,0 号库同步到 6 号库,1 号库同步到 7 号库等等;
在配置文件里把分库的取模从 6 变成 12
通过数据库脚本,每个库删掉一半数据,比如对于 0 号库,删掉用户 ID%12=6 的记录,对于 6 号库,删掉用户 ID%12=0 的记录
一个综合案例:互联网平台技术架构演变
一个实际的电商系统很复杂,在案例介绍中,为了简化,我用比较有代表性的交易系统和账户系统来代表整体的电商系统,并具体分析这两个系统在电商平台发展过程中,它们都碰到了什么瓶颈,以及我们在技术架构上是如何解决的。
单体系统
架构图
特点
所有的代码都打包在一个应用里,部署的时候会有多个实例,我们通过负载均衡,把用户请求分发到具体的实例中
所有的数据表还在一个数据库里。
存在问题
单体应用的所有代码都放在一起,代码编译需要很长时间,应用启动也需要很长时间,并且代码相互依赖,开发效率低,并行开发困难。
SOA架构
架构图
特点
针对单体应用体量过大带来的问题,我们对系统进行拆分,把整体系统分为多个子系统
底层数据库还没有拆分,两个系统还是访问同一个数据库。
系统整体就变成了 SOA 架构,这样,我们减少了代码冲突,系统的开发也变得更加高效,部署的时候,我们也更容易针对各个系统的处理能力进行水平扩展。
存在问题
内部服务通过中心化的负载均衡进行访问,中心化的负载均衡增加了服务的调用时间。
在电商场景下,内部的服务很多,服务调用的频率很高,每秒可能有上百万次,导致了负载均衡的连接能力不够
而且负载均衡是单点,如果它出了问题,很容易引发系统整体的可用性问题(即使负载均衡是多实例,当系统流量很大时,也会因为某台负载有问题,导致其他节点压力增大而引起雪崩效应)。
服务调用去中心化
架构图
特点
针对内部服务路由中心化的问题,我们去掉了内部的负载均衡,加入了服务注册中心,比如 ZooKeeper。
当服务实例启动或退出时,它们会自动在注册中心进行注册或销毁,服务的客户端和注册中心保持长连接,可以实时地获取可用的服务列表
然后在客户端,根据相应的算法选择服务实例,直接调用服务。每次调用无需经过注册中心,如果注册中心有问题,也只是新的服务实例无法注册,或者是已有的服务实例无法注销,这对客户端调用服务的影响是非常有限的。
通过注册中心和更体系化的微服务框架,我们还可以实现完善的服务治理,包括服务隔离、熔断、降级,这些都是对原来简单的负载均衡方式的加强,能够进一步提升服务的可用性。
存在问题
单个数据库(比如 Oracle)的性能和储存容量已经无法满足需求了。这个时候,我们就需要对数据库进行改造,提升它的处理能力。
垂直分库
架构图
特点
对于单个数据库性能和容量瓶颈,解决的办法就是,我们对数据库进行垂直拆分,按照业务拆分为交易数据库和账户数据库,这样就可以满足它们各自的容量和性能需求,同时也避免了不同业务数据表之间的相互耦合
存在问题
不过,新的问题又来了,垂直分库后,每个数据库都是单实例。随着业务的发展,和原来系统只有单个数据库类似,现在交易系统也只有一个数据库,它的性能和容量还是有问题,并且数据库单实例也带来了可用性的问题,如果数据库挂了,相应的系统也就不可用。
水平分库及高可用部署
架构图
特点
针对单个数据库的可用性问题,我们可以采用 MHA 高可用(Master High Availability)方式部署。比如数据库部署一主多从,通过 MHA 机制,我们可以实时检测主库的可用性,如果主库有问题,系统会自动 Failover(故障转移)到最新的从库。另一方面,我们还可以利用多个从库支持读写分离,减轻主库的访问压力。
针对单个数据库的性能和容量问题,首先我们可以引入缓存,在高读写比的场景下,让应用先访问缓存,大大减轻对底层数据库的压力。然后,我们可以对数据库按照某个维度(比如用户维度),进行水平拆分,把数据记录分布到多个实例中,最终分散主库的写压力以及数据存储的瓶颈
我们还可以提供多套水平分库。比如说,针对交易数据,我们可以同时按照用户维度和商户维度进行水平分库,用户维度的库用于前台用户下单的场景,商户维度的库用于后台商家履单的场景。这里,只有用户维度的分库会支持写,我们通过数据同步机制,把用户维度分库的更新同步到商户维度的分库里。
存在问题
当系统体量发展到了一定程度,我们又碰到了新的问题:单个机房的服务器不够用,无法在同一个机房找到更多的机器部署交易系统和账户系统。
多机房部署
架构图
特点
对于单机房服务器不够的问题,我们可以在新的机房部署交易系统和账户系统,为了落地方便,所有服务还是注册到旧机房的注册中心,数据还是存放在旧机房的交易数据库和账户数据库。 这样,我们通过在新机房部署应用,对应用节点进行水平扩展,从而解决了单机房机器不足的问题。
存在问题
产生了跨机房访问的问题:首先,我们只有一个服务注册中心,服务实例一部分部署在老机房,一部分部署在新机房,对于服务调用者来说,它会同时访问新旧机房的服务实例;其次,数据库部署在老机房,新机房的应用会访问旧机房的数据库。
根据机房物理距离的不同,跨机房访问的网络延时在数十毫秒到数百毫秒之间,是机房内部通信耗时的上千倍,这会对应用的性能产生很大影响,而且跨机房的网络可用性也经常是一个问题。
服务调用本地化
架构图
特点
在新机房也单独部署了服务注册中心,让每个机房的服务注册到同机房的注册中心。这样,客户端的服务调用会路由到同机房的服务端,实现了服务调用的本地化,大大降低了跨机房通信带来的延时和不可用性问题。
存在问题
随着业务越来越复杂,新的问题又来了:交易系统会依赖很多周边服务。比如下单后,我们需要给用户送积分,交易系统会同步调用积分服务。但是同步调用积分服务,一方面会影响下单的性能,另一方面如果积分服务不可用,会导致核心的下单功能失败。
依赖分级管理
架构图
特点
对于外部服务依赖的可用性问题,我们的解决办法是,针对这些外部依赖进行分级管理,根据依赖功能的重要性不同,把它们分为强依赖和弱依赖。
对于强依赖,我们实时同步调用,比如在用户下单时调用库存服务,由于库存非常重要,必须实时扣减,如果调用库存服务失败,下单也失败。
对于大量的弱依赖,我们以异步消息的方式进行信息同步,比如对于积分服务,可以通过柔性事务来保证数据的最终一致性,这样大大提升了核心系统的性能和可用性。
存在问题
新机房的交易系统和账户系统都在访问老机房的数据库,有跨机房数据库访问的性能问题,以及老机房整体故障带来的可用性问题。比如说,机房断电,通信光纤有问题或者发生自然灾害,导致老机房整体不可用,这就会导致所有系统都不可用。
多机房独立部署
架构图
特点
针对机房整体不可用的问题,解决方案是,我们在多个机房做对等的部署,这样每个机房的系统可以形成内部闭环,包括服务、注册中心和数据库,机房之间不产生直接的相互依赖,从而实现了机房级别的水平部署。
存在问题
如果系统的单元化做得完善,我们还可以进一步支持虚拟机房的概念,一个物理机房可以部署多个虚拟机房,每个虚拟机房包含了一个完整的系统。通过多机房独立部署,我们极大地提升了系统的可用性、处理能力和可伸缩性,可以应对系统面临的各种异常情况。
总结篇
架构原则汇总
汇总图
可回滚/可禁用
可回滚原则确保了系统可以向后兼容,当系统升级出现问题的时候,我们可以回滚到旧版本,保证系统始终可用。
如果数据库的新旧表结构差异很大,除了回滚代码,我们还要回滚数据库,这样操作起来往往需要很长时间,系统的可回滚性就比较差。
可禁用原则要求我们提供功能是否可用的配置,在系统出现故障时,我们能够快速下线相应的功能。比如说,新的商品推荐算法有问题,我们可以通过程序开关禁用这个功能。
使用成熟的技术
一方面,要从系统的稳定性出发,尽量选择成熟的技术,避免因为新技术的坑而导致系统可用性出现问题;
另一方面,选择成熟的技术也意味着选择了团队熟悉的技术,这样学习成本低,落地快。
使用同质化硬件
我们选择同样的 CPU 和内存配置,以及同样的操作系统版本,这样我们更容易通过统一的自动化脚本,对节点进行配置,对系统做水平扩展时也会更加容易。
架构落地过程
架构师职责
构师的职责就是负责设计架构,并跟踪架构的实施过程,解决过程中出现的疑难问题,确保架构顺利落地
架构师工作阶段
架构师要和产品经理或者业务人员沟通,了解业务;和开发人员沟通,了解系统。
设计方案
架构师针对业务需求,分解相应功能到现有的各个系统,把系统的各个部分串起来,这个第一版的方案至少要能够在表面上解决当前的问题,这样就形成一个草根的方案。
架构师要进一步深入思考业务的本质,对现有的草根方案进行升华,比如说,通过抽象,让方案更加通用,可以解决多个类似的或潜在的业务需求,这样,草根的方案就变成了一个高大上的方案,这里很考验架构师的透过问题看本质和抽象总结的能力
基于现有的各项约束,比如时间、资金和人员技术能力等因素,架构师要对方案进行简化,把高大上的方案变成一个接地气的方案,以最小的代价实现最大的价值,这里很考验架构师的平衡取舍能力
进行宣讲,架构师需要说服相关的人员接受方案,并且在后续的方案执行中,负责跟踪架构的落地,如果过程中有疑难问题,架构师还要协助解决。
架构师知识结构
知识结构图
具体技术
第一部分是开发相关的基本知识,比如数据结构和算法、具体的开发语言、常用的设计模式以及开发框架等等,这样你就具备了基本的开发能力
第二部分是各种中间件知识,常用的中间件包括数据库、缓存、消息系统、微服务框架等等,对于这些核心中间件,我们不但要了解具体的用法,还要深入理解它们的适用场景。这样你就能写出高效健壮的代码,能够独立承担一个子系统的开发。
你还要学习分布式系统相关的知识,包括底层网络和分布式通信技术,这样你就可以了解系统是怎么连接在一起的。除此之外,你还要了解一些周边的系统,比如大数据平台、运维监控系统、接入系统等等,这样,你就可以了解系统端到端的运行过程,从技术架构上保证系统的稳定可用。
架构师成长路径
java为例的学习内容
初级开发阶段
你需要深入学习数据结构和算法,并且一定要深入掌握单体应用的分层架构,因为这是架构设计的基础。
对 JDK 的一些核心类,你不能仅仅停留在使用层面,而是要深入研读源代码,了解它的内部设计。这样你就知道如何开发一个高效的程序,如何进行各种代码级的调优
高级开发阶段
你需要非常了解设计模式,每个设计模式都可以看做是一个小型的架构设计,这里面有很好的设计原则和抽象思维,你在做系统设计时可以借鉴它们。
你需要非常了解核心的中间件,包括 DB、微服务框架、缓存和消息系统,要清楚地了解它们的适用场景(比如消息系统的削峰、解耦和异步),知道如何对它们进行调优,以及了解它们都有哪些常见的坑等等,核心中间件是我们做技术选型的基础。
你要深入掌握数据库设计和服务接口设计,了解它们的最佳设计实践,它们承载了系统核心的业务数据和业务逻辑。
你需要进一步研读源码,源码是活的教材,它包含了大量实用的设计原则和技巧。这里我建议你选择一些开源的开发框架和 RPC 通信框架,去深入了解它们内部的实现原理,比如 Spring 和 Netty。
架构师阶段
你需要深入了解网络通信,比如说网络分层和 HTTP/TCP 协议,还有各种常见的 RPC 通讯框架,了解它们的特性和适用场景,这样你在设计分布式系统时,就能够进行合理的技术选型。
了解底层系统,包括 JVM、操作系统和硬件原理,再往上延伸到系统的接入部分,了解常见的负载均衡特性和用法
需要熟练掌握各种设计工具和方法论,比如领域驱动设计和 UML,了解常用的架构设计原则
大师阶段
还要对运维和监控有深入的认知
了解业界的架构实践,跟踪技术的发展趋势,如果出来一项新技术,你可以比较准确地对它进行定位,把它纳入到自己的能力体系当中
架构师境界
第一层看山不是山
刚接手项目的时候,你对业务还不太了解,经常会被业务方冒出的术语弄得一愣一愣的,如果把现有问题比作山,那就是横看成岭侧成峰,你根本摸不透,此时看山不是山。
第二层看山是山
经过业务梳理和深入了解系统以后,你能够设计出一个简单的方案,把各个系统串起来,能解决当前的问题,对当前的这个“山”能够看清楚全貌,此时就做到了看山是山。
第三层看山不是山
通过进一步抽象,你能够发现问题的本质,明白了原来这个问题是共性的,后续还会有很多类似的问题。然后你就对设计进行总结和升华,得到一个通用的方案,它不光能解决当前的问题,还可以解决潜在的问题。此时,你看到的已经是问题的本质,看山不是山
第四层看山是山
最后回到问题本身,你能够去除过度的抽象,给出的设计简洁明了,增之一分嫌肥,减之一分嫌瘦,既能解决当前的问题,又保留了一定的扩展能力,此时问题还是那个问题,山还是那个山
架构本质
通过合理的内部编排,保证系统高度有序,能够不断扩展,满足业务和技术的变化
首先,架构的出发点是业务和技术在不断复杂化,引起系统混乱,需要通过架构来保证有序
其次,架构实现从无序到有序,是通过合理的内部编排实现的,基本的手段,就是“分”与“合”,先把系统打散,然后将它们重新组合,形成更合理的关系。
分类
业务架构:概念
业务架构就是讲清楚核心业务的处理过程,定义各个业务模块的相互关系,它从概念层面帮助我们理解系统面临哪些问题以及如何处理
应用架构:逻辑
应用架构就是讲清楚系统内部是怎么组织的,有哪些应用,相互间是怎么调用的,它从逻辑层面帮助我们理解系统内部是如何分工与协作的
技术架构:物理
术架构就是讲清楚系统由哪些硬件、操作系统和中间件组成,它们是如何和我们开发的应用一起配合,应对各种异常情况,保持系统的稳定可用。
好的架构要求
业务复杂性
满足业务的可扩展、可复用
技术复杂性
满足系统的高可用、高性能和可伸缩,并尽量采用低成本的方式落地。
好的架构师要求
TA 必定是一个出色的程序员,写的一手好代码
需要有思维的高度,具备抽象思维能力
思维的深度,能够透过问题看本质:一个跨网络调用,知道数据是如何通过各种介质(比如网卡端口)到达目标位置
具备良好的沟通能力(感性)
良好的平衡取舍能力(理性)
业务架构
概念
产品和业务架构师区别
产品经理的职责就是:告诉用户,系统长什么样子;告诉开发,他要实现什么功能
对业务架构师来说,TA 的工作,就是把业务流程和节点打散,按照业务域的维度来划分系统模块,并定义这些模块之间的关系,最终形成一个高度结构化的模块体系。
业务架构目标
业务的可扩展
业务的主题是变化和创新,系统的主题是稳定和可靠
业务的可复用
按照业务域来划分业务,把业务流程中的节点拆分到各个业务域,按照业务域构造系统模块
模块要求
模块的职责定位要非常清晰
模块的数据模型和接口设计要保证通用
实现模块的高复用,还需要做好业务的层次划分
可扩展架构
概念
何为系统?即 模块 + 关系
对模块的要求:
1、定位明确,概念完整;
2、自成体系,粒度适中
1、定位明确,概念完整;
2、自成体系,粒度适中
对关系的要求:
1、同时简化依赖的方向和减少依赖的数量;
2、我们要尽量把网状结构转化为层次结构,模块结构层次化是简化模块依赖关系的有力手段。
1、同时简化依赖的方向和减少依赖的数量;
2、我们要尽量把网状结构转化为层次结构,模块结构层次化是简化模块依赖关系的有力手段。
如何打造可扩展架构
该系统本质是:通过构建合理的模块体系,有效地控制系统复杂度,最小化业务变化引起的系统调整。
实现方法:通过拆分,实现模块划分;通过整合,优化模块依赖关系
拆分:一般做业务架构时,我们先考虑垂直拆分,从大方向上,把不同业务给区分清楚,然后再针对具体业务,按照业务处理流程进行水平拆分
整合:
1、通用化整合:通用化指的是通过抽象设计,让一个模块具备通用的能力,能够替代多个类似功能的模块。
2、平台化整合:平台化是把定位相同的模块组织在一起,以组团的方式对外提供服务。对于外部系统来说,我们可以把这些模块看成是一个整体,一起对业务场景提供全面的支撑。
1、通用化整合:通用化指的是通过抽象设计,让一个模块具备通用的能力,能够替代多个类似功能的模块。
2、平台化整合:平台化是把定位相同的模块组织在一起,以组团的方式对外提供服务。对于外部系统来说,我们可以把这些模块看成是一个整体,一起对业务场景提供全面的支撑。
案例一:电商平台的架构演变
单体架构 2000年
特点
单体应用内部一般采用分层结构,从上到下,一般分为表示层、业务层、数据访问层、DB 层。
单体架构在水平方向上,通过层次化的划分,降低了业务的深度复杂性;
不过在垂直方向上,单体应用缺乏清晰的边界,上下层模块之间是多对多的网状依赖关系
不过在垂直方向上,单体应用缺乏清晰的边界,上下层模块之间是多对多的网状依赖关系
在单体架构中,模块结构是否合理,很大程度上依赖于开发者的个人水平。
架构图
优点
快速落地
项目初期,适应快速验证市场
弊端
业务系统的体量变大时,弊端就会暴露
1、多团队并行开发的话,需要额外的资源来协调不同团队之间的并行开发和上线
2、代码合并和编译非常复杂
分布式 2006年
特点
分布式架构,简单来说就是系统由多个独立的应用组成,它们互相协作,成为一个整体。
在分布式架构中,API 接口属于应用的一部分,它和表示层共享底层的业务逻辑
把一个大系统的业务复杂度,分割成多个小业务的复杂度,从而降低了整体的复杂度。
分布式架构适用于业务相关性低、耦合少的业务系统
架构图
优点
通过拆分后,各个应用之间的耦合度低,就可以很好地支持多团队的并行开发。
弊端
作为应用的开发者,除了要满足自身业务的需求之外,同时还需要考虑外部业务的需求,这两部分经常会打架。
每个应用都是从头到尾,自搭一套完整的体系,导致业务之间重复造轮子,造成资源浪费。
SOA 2009年
传统SOA
目的: 它解决的是企业内部大量异构系统集成的问题
特点
让各个系统通过提供标准的服务,来满足外部调用需求。
外部系统通过这个服务访问系统内部,解决不同系统相互集成的问题
架构图
新 SOA
目的
它解决的是系统重复建设的问题
特点
它通过服务化思想,提供更好的业务封装性,并通过标准技术,能更友好地对外输出业务能力;
SOA 服务不依附于某个具体应用,它可以独立地部署和扩展,这样避免了直接影响现有的系统;
服务通过封装通用的业务逻辑,可以供所有应用共享,解决了重复造轮子的问题。
架构图
弊端
然而 SOA 服务化的思想很好,但在系统实现上比较重,落地比较困难。
微服务 2014年
特点
相对于SOA架构,思想是相似的,但是架构更轻,但两者不同的地方在于,微服务是去中心化的,不需要 SOA 架构中 ESB 的集中管理方式。这就需要不同的通讯方式
每个微服务,都是负责端到端的业务
包括前端的 UI 展现部分和后端业务逻辑。由这个小团队负责应用的整个生命周期管理。
从一定程度上说,微服务叫做微应用,微产品,是拆分得更细的分布式架构
微服务强调围绕业务,进行清晰的业务和数据边界划分,并通过良好定义的接口输出业务能力
微服务强调所谓的哑管道,通过 HTTP 等简单的技术手段就可以访问
微服务强调智能终端,所有的业务逻辑包含在微服务内部,不需要额外的中间层提供业务规则处理
架构图
微服务 = 小应用 + 小服务
实际而言侧重小服务
很难把一个大系统,按照端到端业务的方式,拆分为一个个应用
需要把现有的职能团队打散后重组,这种人员组织的调整实际上也很难落地。
实践方法:弱化微服务的小应用定位,然后扩大化微服务小服务的定位
封装底层基础业务的是共享微服务
封装流程的是聚合微服务
封装具体业务场景的服务端是应用微服务
封装基础中间件(如 Redis 缓存、消息推送)的是系统微服务
中台 2018年
产生背景
概念
前台指的是面向 C 端的应用,比如像微信、淘宝这样的应用,后台指的是企业内部系统,比如 ERP、CRM、仓库管理系统等等
冲突
前台和后台的特性是不一样的。前台对外,我们知道,消费者的需求快速多变,所以前台需要能快速响应,做到低成本试错;而后台对内,企业内部的业务流程不能经常变,所以后台需要稳定,不能随意调整
解决方案
前台和后台是企业 IT 系统的一体两面,它们需要紧密协作,共同服务于企业的业务战略,如何实现前后台的平滑对接,这是一个巨大的挑战,中台架构因此而生
中台适用场景
示意图
要求
1、这和公司业务线的数量有关,业务线越多,意味着重复建设的成本会更大,当我们开始上第 3 条业务线时,就应该要考虑转到“山”字形了
2、和各个业务线的相似度有关,相似度越高,意味着业务线之间有更多类似的逻辑
优势
从业务角度来看,中台收敛了业务场景,统一了业务规则
从系统角度看,中台相当于操作系统,对外提供标准接口,屏蔽了底层系统的复杂性
从数据角度看,中台收敛了数据,比如使用同一套订单数据模型,让所有渠道的订单使用相同的订单模型
总结:通过实现基础业务的平台化,实现了企业级业务能力的快速复用
中台落地模式
互联网企业
架构图
特点
松散的微服务 -> 共享服务体系 -> 中台,这是微服务架构向中台架构的演进过程。
基础业务能力由通用基础业务平台来实现;另外,通用聚合服务对基础业务进行组合,进一步提升了业务能力的易用性;而通用中间件平台,通过技术手段保证了业务中台的稳定性,三者一起实现了企业整体业务能力的复用
优点
微服务升级为了商品中心、订单中心,每个中心更强调体系化,包括更好的业务通用能力,更好的系统运营能力(如监控、稳定性、性能的强化),更好的业务运营能力(比如商品中心自带配套的商品管理后台)。
传统企业
架构图
特点
渠道 & 应用层,这是整个系统的对外部分,包括了各个应用的前端,如 App、小程序、公众号等等
应用平台是各个具体应用的母体,它包含了各个应用的服务端,做流程编排和信息的聚合。中间网关实现前后端隔离,具体负责外部访问的安全验证和监控,以及内外部请求的路由和消息格式转换。
业务中台是中台架构的核心,它包括一系列的通用基础服务
后台包括两部分,第一部分是适配插件,用于连接商户内部系统和中台基础服务;第二部分是企业内部系统,这个是企业的 IT 基础设施,业务最终会在这里落地。
优点
中台代表了企业核心的业务能力,它自成体系,能够为 C 端的互联网场景提供通用的能力,并通过各种插件和后台打通
一号店app端架构演变
v1.0 单体架构
架构图
移动团队将web端jar包物理组装,基于jar包业务逻辑向app前端提供接口
优点
简单方便,只需要个移动团队就可以
可快速落地
局限
移动端对jar包紧密依赖,但web端并没有职责去协同移动端接口情况,导致app端常出bug
移动团队需要理解web端所有团队的业务逻辑,进行二次开发,对移动端开发能力要求较高
移动团队依赖web团队,并行开发困难
v2.0 分布式
架构
web端业务团队直接对接app前端,移动端和web共享底层业务逻辑,有助于快速复制pc端功能
优点
通过 V2.0 架构的升级,业务线团队的生产力就被完全释放了,App 的功能也就快速丰富起来了。
局限
app和web相互影响,两者用户体验性不一样,共同开发会影响系统的功能
重复开发:每个模块的移动端接口都需要一些系统级功能:安全验证,日志记录,性能监控
由于是直连的方式,只要后端系统出问题,直接影响app可用性,使得app整体脆弱
v3.0 SOA
特点
架构图
对v2.0服务端进行拆分,物理独立;同时将app端共性系统级功能进行集中,个性化业务进行分散处理
通过移动网关访问服务端接口,主要处理系统级功能
优点
app端和pc端彻底独立
核心业务功能复用
方便强化了系统级功能
可复用架构
概念
复用分类
架构图
技术复用:代码复用和技术组件复用
代码复用有自己打包的类库,第三方提供的 SDK,还有各种算法封装等
技术组件复用有我们自己封装的,更多的是大量开源的中间件,比如 Redis、MQ、Dubbo 等
代码级复用和技术组件复用都属于工具层面,它们的好处是在很多地方都可以用,但和业务场景隔得有点远,不直接对应业务功能,因此复用的价值相对比较低。
业务复用:业务实体复用、业务流程复用和产品复用
业务实体复用针对细分的业务领域,比如订单、商品、用户等领域
业务流程的复用针对的是业务场景,它可以把多个业务实体串起来,完成一个端到端的任务。如下单流程服务
最高层次的复用是对整个系统的复用,比如说一个 SaaS 系统(Software-as-a-Service),它在内部做了各种通用化设计,允许我们通过各种参数配置,得到我们想要的功能;
对比总结
从技术复用到业务复用,越往上,复用程度越高,复用产生的价值也越大,但实现起来也越复杂,它能复用的场景就越有限
如何实现?首要划分基础服务边界
完整性原则
要尽可能在服务内部封装完整的业务规则,对外提供完整的业务语义,最大程度地简化服务的使用。不能仅仅是DAO
在划分服务边界时,要保证服务数据完整、功能全面,这样才能支撑一个完整的业务领域。
一致性原则
服务的数据和职责要一致,谁拥有信息,谁就负责提供相应的功能
正交原则
基础服务处于调用链的底层,服务之间不会有任何的调用关系,也就是说基础服务相互之间是正交的。
服务之间可以有数据的依赖关系,但没有接口的调用关系。
案例一:如何设计一个基础服务
可能遇到问题
对服务的边界没有进行很好的划分,结果在落地的过程中,大家反复争论具体功能的归属
对业务的了解不够深入,我们要么设计不足,导致同一个服务有很多版本;要么服务过度设计,实现了一堆永远用不上的功能
核心
服务边界的划分和功能的抽象设计是核心。服务边界确定了这个服务应该“做什么”,抽象设计确定了这个服务应该“怎么做”
举例:订单业务架构
业务场景图
订单服务边界划分
主要服务
基本信息管理
订单优惠管理
订单生命周期管理
订单服务的状态要做到通用,能够支持各种可能的状态定义和状态转换过程
不包含服务
订单服务不主动调用其他服务
订单服务不负责和第三方系统的集成
同步功能时特定定制的,不具备通用性,对接功能不在订单服务内部实现,由额外的同步程序实现
同步程序主动调用订单服务,在后在和第三方对接
订单服务不提供优惠计算或成本分摊逻辑
不负责具体的优惠计算,只提供优惠结果的存储和查询
该服务不提供履单详情,不负责详细物流信息的存储
订单服务内部设计
内部数据模型:订单状态通用化
数据模型图
存在问题
不同的行业甚至不同的企业,他们对于订单状态管理都是不一样的,订单服务作为一个共享服务,它必须要满足不同项目的订单状态管理
解决方案
开放订单状态定义
理论上可以满足各种状态的定义,满足各种状态之间的变化
订单状态是完全由外部负责管理的,上层应用的负担会很重,不但要负责定义有哪些状态,而且还要维护状态的转换规则,一不小心,订单可能从状态 A 非法地变成状态 B,导致业务出问题。
订单状态是订单业务规则的核心体现,这样的订单服务是没有灵魂的,也失去了大部分业务复用的价值
应用和服务共同管理
订单的基本状态,我们称之为“主状态”,它们由订单服务负责定义
子状态有哪些具体的取值,不同的项目是不一样的,这个就开放给各个应用来定义。
订单服务抓大放小,通过主状态管理把控住了订单的核心业务规则,同时把子状态开放给应用进行管理,为具体的业务场景提供了灵活性。
外部接口
服务接口调用
在服务接口命名时,一定要规范和统一,接口名字要能够望文生义,方便调用者快速找到所需要的接口
如何设计查询接口
粗粒度接口,只返回订单最基本的 7-8 个字段
中粒度接口,返回订单比较常用的十几个字段
细粒度接口,返回订单的详细信息
异步消息通知
胖消息包含了尽可能多的字段,但传输效率低
瘦消息只包含最基本的字段,传输效率高
案例二:如何对现有系统做微服务改造
微服务化改造面临的挑战
面临的一般是巨大的单体应用,底层的数据表集中放在一个数据库里,这些表加起来可能有几百张
应用和数据表紧密耦合在一起,代码模块和表是多对多的依赖关系
表都在一个数据库里,开发人员往往会随意对表做关联
改造不能影响业务的稳定性
一号店“库存”改造案例
背景
所有商品相关的表都存在产品库里面,数量超过了上百张;(表多)
商品是电商业务的核心,几乎所有的前后台系统都需要访问这个产品库;(压力大)
对这些表的访问是怎么方便怎么来,有些 SQL 语句会对大量的表做 Join 关联。(使用乱)
商品是电商业务的核心,几乎所有的前后台系统都需要访问这个产品库;(压力大)
对这些表的访问是怎么方便怎么来,有些 SQL 语句会对大量的表做 Join 关联。(使用乱)
存在问题
从应用方面来说,各个系统功能重复建设;另外,如果修改了库存表的某个字段,这些系统同时会受影响,正所谓牵一发而动全身。
从数据库方面来说,数据库的可用性是比较差的,如果某个系统有慢查询,它就很可能拖垮整个产品数据库;另外,数据库的连接数也经常不够用。
目标
首先是对这个大数据库按照业务维度进行垂直拆分,比如分成产品数据库、库存数据库、价格数据库等等
然后基于这些拆分后的库,构建微服务,以接口的方式来支持数据库表的访问
最后将各个业务系统统一接入微服务,最终完成整个商品体系的微服务化改造。
改造步骤
改造流程图
准备阶段
圈表
圈表就是用来确定库存微服务具体包含哪些表,也就是确定服务的数据模型;库存微服务就负责这些表的访问,当然,库存微服务也不会访问其它的表
一方面要满足所有的库存访问需求,这些表之间关系紧密,和其它的表关联不大;另一方面,这些表的数量不能太多,一般不超过十几张。
改造系统从圈表开始的原因
表是现成的,它比业务功能要直观很多,所以从表入手比较高效;从下往上确定服务边界
如果从表入手,构造的服务和表是对应的;避免表字段的拆分带来额外的复杂性
为了避免一下子引入太多变化,我们先不对库存的表结构进行调整
收集SQL
我们会收集所有业务系统访问这些表的 SQL 语句,包括它的业务场景说明、访问频率
可以根据业务场景和访问频率设计查询方法,设计缓存等
SQL拆分
各个业务团队先进行拆分,保证最后提供给服务开发团队的 SQL,只包含访问库存的相关表
实施阶段
微服务开发
接口设计、代码开发、功能测试
对业务方提供的 SQL 进行梳理,然后对接口做一定的通用化设计,避免为每个 SQL 定制一个单独的接口,以此保证服务的复用能力
服务接入
存服务经过功能和性能验证以后,会由各个业务开发团队逐步接入,替换原来的 SQL 语句
独立数据库
所有的 SQL 语句都被替换后,业务系统已经不会直接访问这些库存的表;我们就可以把库存相关的表,从原来的产品库中迁移出来,部署成为一个物理上独立的数据库
通过库存表独立成库,我们可以从物理层面,切断业务团队对这些表的依赖,同时,也可以大幅度降低产品库的压力
改造前后架构图对比
老架构
新架构
微服务化后,平时库存服务会部署 50 个实例,大促时会部署更多,我们很容易通过加机器的方式,实现库存服务的水平扩展
总结
除了做好库存服务本身的设计开发工作,相关团队之间的配合也是非常重要的,需要团队之间沟通和确认
从项目推进的角度来看,这种核心服务的改造,很多时候都是技术一把手工程
基于现有系统进行改造和全新的服务设计是有所不同的,我们不能追求理想化和一步到位,而是要考虑到系统的平滑过渡,先实现微服务的顺利落地,后续再考虑各种优化
一个高复用和扩展平台架构图
案例三:中台如何练成
升级中台因素
业务上有什么重大变化,导致当前系统的弊端已经很明显,不能适应业务发展了
如何在业务、系统、资源三者之间做好平衡,对系统进行分步式的改造
订单系统改造案例
背景
公司作为供应商,为大型餐饮连锁企业打造 O2O 交易平台,包括三方聚合外卖、自有小程序、App 点餐,这些线上用户的订单最终会落到门店的收银系统,由门店进行履单。
改造经历
聚合外卖订单架构
架构图
特征
外卖同步接口负责和第三方外卖平台对接,它主要是针对不同的外卖平台做接口适配,POS 接口负责和门店的收银系统对接,系统对接。这两个模块都是使用同一个外卖订单数据库
从数据模型上看,系统的订单模型也是完全按照外卖订单的需求设计的,订单状态管理也相对比较简单,因为这些订单都是用户在第三方外卖平台已经完成支付的
从系统架构上看,外卖系统从外卖平台接单,然后把订单推送给后面的收银系统,只需要一个应用、一个数据库、两套接口就可以支持,使用单体架构就能很好地满足外卖的接单需求
小程序下单架构
架构图
特征
不同于三方外卖订单,小程序下单平台是一个完整的业务,它包括小程序用户注册、商品和菜单浏览、商品加购物车、在线支付等等
方案是一个比较务实的选择,通过复用外卖订单的履单通路,我们也实现了小程序订单的闭环处理。
优缺点
我们节省了重新搭建系统的成本,也快速落地了小程序交易这条新业务线。
我们把小程序的订单硬生生地套在了外卖订单的模型里,这样限制了小程序订单能力的扩展
小程序订单处理链路过长,一共包含了 8 个处理环节,系统整体的性能和可用性都存在很大问题,存在消息队列堵塞
使用了消息队列在两个库之间同步订单数据,这降低了系统整体的稳定性。实践中,也发生过多起消息队列故障导致的线上事故
统一订单服务架构
架构图
特征
原来外卖和小程序各自有一个订单库,现在合并为了一个订单库,由这个订单服务统一对外提供订单数据的访问和状态管理
原来外卖系统的两个模块“外卖同步接口”和“POS 接口”,升级为了两个独立的应用。外卖同步接口变成外卖同步服务,对接外卖平台;POS 接口变成 POS 服务,对接门店的收银系统。它们都是通过统一订单服务存取订单数据。
经过升级,新的架构具备了明显的层次结构,自上而下分为三层;这里就不仅仅是一个外卖订单和小程序订单的处理平台,而是升级成了一个完整的全渠道交易平台
订单处理的链路大大缩短
统一订单服务实现了统一的订单属性定义、统一的订单状态管理,以及订单数据的集中存储,这对后续的 BI 分析和数据中台建设非常有帮助
中台架构
架构图
形成方法
按照上述订单服务逻辑,可以形成商品服务,促销服务,库存服务
通过构建这样一系列的共享服务,我们就实现了各个渠道业务规则和业务数据的统一管理,最终我们落地了一个强大的业务中台,可以很方便地扩展各个业务,实现企业整体业务能力的复用。
特征
前端有 3 个业务场景,分别是小程序点单、App 商城下单、外卖平台下单,每个业务场景都有相应的服务端负责对接
在各个服务端下面,还有一些辅助的应用,如购物车、秒杀、拼团等等。同时这里还有一个订单控制服务(Order Control Service,OCS),负责订单逻辑的编排以及前后台之间的状态同步,可以把它看作是基础服务之上的聚合服务。
再底下就是核心的业务中台,它由 9 大服务中心组成,这些中心和商户内部系统进行对接。其中,商品中心和库存中心对接 ERP 系统,会员中心对接 CRM 系统,订单中心对接 POS 收银系统,这里的对接分别由对应的适配插件负责
中台由各个通用的基础服务构成,它是相对标准的;而插件是定制的,具体和每个企业的后台系统有关
0 条评论
下一页