泪目,不堪回首!

博主毕业​​4年​​了,最近秋招开始了,每次回想起自己的秋招,都感觉到当时自己特别的可惜(​​菜是原罪​​),自己当时简历上面的项目,只有一个 ​​农资电商平台​​,当时的秒杀系统还没有那么普及(简历人均​​秒杀系统​​)。

第一次微众面试

当年自己的八股文背的其实还可以,但是自己的项目就只是一个单机系统,​​分布式​​?​​微服务​​?什么玩意?,还记得当时​​微众面试​​,是​​二面​​,在一个酒店房间,面试官笑嘻嘻的看着我,说让我先画一下我项目里面的​​农资电商平台​​, 我脑子嗡嗡叫,啥?咋画, 就一个​​安卓系统​​,一个​​前端页面​​,和一个​​后台系统​​?

大概长这样子

面试官:你给我画一下秒杀系统的架构图!_缓存image.png我擦,这也太​​简单​​了吧, 我是不是该画复杂一点? 或者说,我这个能叫​​架构​​吗?就这样,犹豫之间,毛线都没有画出来... 我记得当时好像画了个这样子的玩意。。毫无意外的,嗝屁了~

这玩意有点四不像,不说了,丢脸~

面试官:你给我画一下秒杀系统的架构图!_数据_02image.png

第二次微众面试

第二次​​微众面试​​,毕业有快一年了,抱着试一下的心态,找了个​​师姐​​内推, 那时候我在干啥呢,在搞爬虫。公司离微众比较近,就在​​金蝶​​那边,下班了溜过去,跟面试官吧啦了一会​​八股文​​,好家伙,没一会就掏出了一张纸:

来画一下你们现在这个爬虫系统的架构图!

当时系统的​​部署架构​​长这样吧, 比上面的看起来还简单一点。面试官:你给我画一下秒杀系统的架构图!_数据_03image.png但是,我就是画不出手啊!!!心里想着太简单了啊!!这玩意能叫​​架构​​吗?

摊牌了, 我不会画!

现在想起来,真的太​​憋屈​​了,年轻啊!那如果现在来回头看的话,能怎么画呢?

单体系统的部署架构图

面试官:你给我画一下秒杀系统的架构图!_数据_04image.png

爬虫系统的分层架构图

面试官:你给我画一下秒杀系统的架构图!_缓存_05image.png

爬虫系统的业务架构

面试官:你给我画一下秒杀系统的架构图!_缓存_06image.png

架构图

从上面的各个方向描述架构来看,其实即使是​​单体系统​​ 也能够画出不一般的架构图!(为啥当时我就不会呢!)最近在看架构相关的内容(​​华仔的课​​),在4+1 视图里面,从多方面描述了我们的系统,可以参考下面的描述,面试官:你给我画一下秒杀系统的架构图!_缓存_07image.png

你的秒杀系统,架构是怎么样的?

单体系统

不管你们简历吹的多牛逼,我猜你们的服务,大部分都是长这个样子的,​​猜对的话点个关注​​, 只有​​浏览器​​是分布式的。面试官:你给我画一下秒杀系统的架构图!_缓存_08image.png

那我该如何去描述我的单体系统呢?

架构设计的三大原则:


  • 简单原则
  • 合适原则
  • 演进原则

每一条原则都符合我们大学做的秒杀系统啊!!

​简单原则​​:一个系统就可以满足我们秒杀服务的所有动作,没有太多的中间件依赖​​合适原则​​:在我们的实践项目中,单体系统是最适合不过的了。(主要是没钱啊!拆分服务,引入中间件,部署集群,都得钱啊!)​​演进原则​​:这个比较好理解,没有什么系统架构是一出生就定下来的,是随着时间,业务需求,不断演变出来的。

总结:

我们架构的优势: ​​成本低​​,​​系统复杂度低​​,​​维护成本低​​,​​快速定位问题​​劣势:​​稳定性差​​,​​并发量低​​,​​扩展性弱​​等

在梳理架构时,每个方案都有他的优势和缺点,所以需要了解你目前方案的优缺点。才能更好的向面试官展示你的系统!

服务拆分

好家伙,参加了个科创比赛,​​资金​​到位了,能买​​更多机器​​了,那不得将服务优化一下,拆分个微服务系统出来!面试官:你给我画一下秒杀系统的架构图!_缓存_09image.png

在这个服务拆分的架构中,我们做了哪些动作?


  • 静态资源隔离(CDN加速
  • 代理服务器(Nginx
  • 服务拆分,应用独立部署
  • 服务rpc通信 (rpc框架 & 注册中心

1、前后端分离

在单体系统中,我们的静态资源(​​Html,JS,CSS 和 IMG​​)可能都是通过我们服务端进行返回,存在的问题是:


  • 前端代码维护
    成本比较高(全栈开发成本也高)
  • 前端代码发布,需要整个系统进行发布
  • 服务器带宽
    ,请求资源
    占用等

那么通过前后端分离所带来的好处就很明显了:


  • 代码独立维护(低耦合
    ),发布成本低(高效率
    )
  • 前后端通过接口交互动态数据
  • CDN资源
    访问加速,减少后端服务压力(高性能

2、反向代理

​反向代理​​的作用比较明显, 由于我们服务拆分成多个,那么我们和​​前端​​进行交互时,需要提供一个通用的​​入口​​。而这个入口,就是我们的​​反向代理服务器​​(​​Nginx​​)。例如:服务域名:​​https://www.jiuling.com​​ ,根据restful规范,我们可以通过 ​​https://www.jiuling.com/user/1.0/login​​ 将请求转发到 ​​用户服务​​的登录接口中。

3.进程间通信

随着服务的拆分,在部分功能的实现上,就会涉及到​​服务间相互调用​​的情况,例如:面试官:你给我画一下秒杀系统的架构图!_缓存_10image.png在常见的实现方案上,我们会采用 ​​注册中心​​ 和 ​​RPC框架​​,来实现这一能力。而我们比较常用的实现方案就是 ​​zookeeper & dubbo​​。面试官:你给我画一下秒杀系统的架构图!_数据_11image.png

为什么要使用 RPC 框架?

当我们提到使用 ​​RPC框架​​ 的时候,是否有去思考过,​​为什么要使用 RPC框架?​​ 每个服务提供 ​​RESTful​​ 接口,不是也能够完成服务间通信吗?这里就需要进行对比 ​​RPC​​ 和 ​​RESTful​​ 的区别了:


  • 数据报文小&传输效率快
    :RPC
    简化了传输协议中一些必要的头部信息,从而加快了传输效率。
  • 开发成本低
    :例如 Dubbo
    框架,封装好了服务间调用的逻辑(如:反射
    ,建连
    和超时控制
    等),只需要开发相应的接口
    和数据模型
    即可。
  • 服务治理
    : 在分布式场景下,我们的服务提供者不止一台,那么就涉及到 服务健康
    ,负载均衡
    和服务流控
    等情况需要处理,而这部分能力在rpc & 注册中心
    的架构下,都已经满足了。

说完优点后,再来分析一下,​​RPC的缺点​​:


  • 耦合性强
    :相较于 RESTful
    而言,RPC
    框架在跨语言的场景下实现比较困难。并且版本依赖
    比较强。服务脱离了当前内网环境
    后,无法正常提供服务,迁移成本高。
  • 内网调用
    :RPC
    更适合内网传输,在公网环境下,显得没那么安全。

分布式微服务

在上一个版本的​​服务拆分​​中, 我们根据不同的​​业务边界​​,​​功能职责​​,划分出了多个​​子系统​​,而针对不同的系统,他所承受的​​负载压力​​是不一样的,例如:​​订单服务​​的每个请求处理​​耗时较长​​(其他服务压力不大),为了挺升我们的下单量,我们可以只扩容​​订单服务​​即可,这就是我们在服务拆分所带来的​​收益​​,性能使用率提升!面试官:你给我画一下秒杀系统的架构图!_静态资源_12image.png从上面的图我们可以看到,有些服务出现了不同的重影,每一个​​方块​​,可以理解为一台​​机器​​,在这个架构中, 为了保证我们的下单成功率,以及下单量,我们主要将服务器集中在了​​订单服务​​。除此之前,再来看看我们的​​中间件​​集群部署:


  • mysql 主从架构
    :读写分离,减轻主库压力,确保数据能正常写入,保障订单数据落库.
  • zookeeper 主从架构
    :保障注册中心可用,避免导致全链路雪崩。
  • redis 哨兵集群
    :避免redis
    宕机导致大流量直接打到数据库中。

小结

到这里为止,一般我们自己开发的系统,也就基本完成了整个​​秒杀系统​​的演进了。可能大伙一直有个疑问,为什么少了我们最熟悉的​​MQ​​呢?在整个调用链路中,我都是以​​同步调用​​的方式去讲述这一个秒杀系统的架构,因为这个已经满足我们当前的流量诉求了,在​​架构设计的原则​​里面,提到的,​​合适原则​​,和​​演进原则​​。在当前满足流量需求的情况下,我们需要先思考引入消息中间件,带来的问题是什么?解决的问题又是什么?在权衡利弊后,才是我们决策是否要使用这个方案的时候。

高性能

在上述架构演进的过程中,我们通过​​服务拆分​​,​​垂直扩容​​,​​分布式部署​​等方式,提升了我们架构的​​性能​​和​​稳定性​​,对于我们自研阶段的​​架构演进​​已经是足够满足我们的流量诉求了,但如果我们想继续优化我们的系统,提升服务性能,可以从以下几个方面进行优化:


  • 资源预热
  • 缓存预热
  • 异步调用

1、资源预热

在上面的​​服务拆分​​​阶段, 我们就提到了​​资源动静分离​​​, 这里的静态资源包括:​​html,js,css,img​​​ 等。我们活动阶段,可以通过后台管理系统,将商品服务中的活动的静态资源​​预热​​​到CDN,​​加速资源​​的访问。

资源预热: 通过预先将资源加载到CDN
回源:CDN找不到资源后,会触发源站(商品服务)调用,进行查询对应资源,如果源站存在该资源,则会返回到CDN中进行缓存。
OSS: 实际存储静态资源的服务(可参考阿里云OSS)

面试官:你给我画一下秒杀系统的架构图!_缓存_13image.png上面有反复提到,引入一个技术的时候,需要同时考虑它所带来的​​利和弊​​,那么 ​​CDN的风险​​是什么呢?


  • 成本
    : 比较直接,就是得多花钱!
  • 带宽
    :在大流量的访问下, CDN 是否能支撑那么多的带宽,每个服务器能支撑的流量是有限的,需要考虑CDN是否能支撑业务的访问量。
  • CDN命中率
    : 在CDN命中率低的情况下,比如活动图片
    ,每一个小时都会发生改变,那么每次图片的替换,都会触发回源操作
    ,这时候的资源访问效率反而有所下降。

2、缓存预热

与上面的​​静态资源加速​​相对比,​​动态数据​​则需要通过​​缓存​​进行性能上的优化,老生常谈,为什么​​redis​​ 那么快?


  • 单线程(redis的性能瓶颈并不在这,所以这个不算优势)
  • 多路I/O复用模型
  • 数据结构简单
  • 基于内存操作

面试官:你给我画一下秒杀系统的架构图!_缓存_14image.png引入 ​​redis​​ 带来的风险主要有:


  • reids
    宕机:单机部署的情况下,会导致大量的服务调用超时,最终引起服务雪崩。可通过Sentinel集群
    优化。
  • 缓存击穿
    :大流量下,缓存MISS
    和缓存过期
    等情况,会导致请求穿透到数据库,如果数据库扛不住压力,会造成服务雪崩。可以通过 布隆过滤器
    进行优化。
  • 数据一致性
    :缓存数据与DB
    的数据一致性问题,需要通过更新策略进行保障。

3、异步调用

通过异步的方式,将减库存成功的用户,通过消息的方式,发送给订单服务,进行后续的下单操作。可以在短时间内,将所有的商品销售出去。整体的流程如下图所示:

​MQ​​异步调用为什么能过提升我们服务的​​吞吐量​​呢?

主要原因在于,通过异步调用的方式,我们将消息投递过去了,就完成了这一次的请求处理,那么性能的瓶颈,由订单服务,转移到了秒杀服务这里。通过减少调用依赖,从而提升了整体服务的吞吐量。

面试官:你给我画一下秒杀系统的架构图!_缓存_15image.png​​MQ​​ 带来的常见问题:


  • 数据一致性
  • 重复消费
    :由于生产者重复投递消息,或者消费缓慢导致重复推送消息。需要通过加锁,消费幂等来保证消费正常。
  • 消息堆积
    :生产能力远大于消费能力情况下,会导致消息堆积。
  • MQ可用性
    :MQ宕机的情况下,需要支持同步调用切换。

这里不做详细介绍,后面会专门写一篇​​MQ相关的文章​​。

高可用

能看到这里真不容易,感谢大家的支持。关于可用性这里,之前有写过一篇 # 《高可用实战》-B站蹦了,关我A站什么事?感兴趣可以看一下。

高可用主要可以从:


  • 动态扩容
    :根据服务压力,针对不同服务进行动态扩容。
  • 限流熔断
    :可参考我之前的文章:# 《高可用实战》-B站蹦了,关我A站什么事?
  • 异地多活
    : 通过多机房部署,避免物理攻击

同城双活

部署在同一个城市不同区的机房,用专用网络连接。两个机房距离一般就是​​几十千米​​,网络传输速度几乎和同一个机房相同,​​降低了系统复杂度、成本​​。面试官:你给我画一下秒杀系统的架构图!_数据_16image.png这个模式无法解决极端的灾难情况,例如某个城市的​​地震、水灾​​,此方式是用来解决一些​​常规故障​​的,例如机房的​​火灾、停电、空调故障​​。

异地多活

在上述模式中,没办法解决城市级别的服务容灾,比如​​水灾​​​,​​地震​​​等情。而通过异地多活的部署方案,则可以解决这种问题。但是每个方案都是存在利和弊的,那么异地多活的弊端主要体现在​​网络传输​​​和​​数据一致性​​的问题上!

跨城异地主要问题就是网络传输延迟,例如北京到广州,正常情况下的RTT(Round-Trip Time 往返时延)是50毫秒,
当遇到网络波动等情况,会升到500毫秒甚至1秒,而且会有丢包问题。

物理距离必然导致数据不一致,这就得从“数据”特性来解决,
如果是强一致性要求的数据(如存款余额),就无法做异地多活。

点关注,不迷路