在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:
请设计一个支持大并发/大数据的软件架构,说说设计思路。
最近有小伙伴在面试阿里,又遇到了相关的面试题。小伙伴懵了,因为没有遇到过,所以支支吾吾的说了几句,面试官不满意,面试挂了。
所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。
当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V166版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请到文末公号【技术自由圈】获取
优秀的架构和产品都是一步一步迭代出来的,用户量的不断增大,业务的扩展进行不断地迭代升级,最终演化成优秀的架构。
早期项目,由于团队规模有限,技术经验不足,往往使用一个简单的单体架构、单节点DB。
随着流量增加和业务演变,需要不断修正系统架构中的问题点,基于后面的几个原则,一点一点的进行系统演进
演进原则:高并发/大数据系统的演进是循序渐进的,以在高并发、大数据场景下不断优化用户体验为目标。
定义:对一个类而言,应该仅有一个引起它变化的原因。
说明:一个类应该是相关性很高的封装,类只实现一个功能。
很多的时候,我们代码中有大量的上帝类,所谓上帝类,
把不应该是一个类的功能也往自己身上揽,大包大揽,导致内聚性就会很差,
内聚性差将导致代码很难被复用,不能复用,只能复制(Repeat Yourself),其结果就是一团乱麻。
定义:软件中的对象应该对于扩展是开放的,对于修改是关闭的。面对新需求,对程序的改动应该是通过增加代码实现的,而不是修改现有代码来实现。
说明:当软件需要变化时,我们尽可能的通过扩展的方式来实现变化,
比如通过继承,通过装饰者模式来增加新的功能,而不是通过修改已有的接口来实现,一旦修改接口,上层调用的地方均需要修改。
开闭原则是面向对象设计的核心所在,遵循开闭原则的最好手段就是抽象。
防止变异(Protected Variations)
问题:如何设计对象,子系统和系统,使其内部的变化或不稳定性不会对其他元素产生不良影响?
解决方案:分离变与不变, 识别变化或不稳定的地方,抽象出稳定的接口。
开发人员应该仅对程序中频繁出现变化的那些部分做出抽象,如果对于应用程序中每个部分都做刻意的抽象并不是个好主意。
拒绝不成熟的抽象和抽象本身一样重要。
定义:
一个对象应该对其他对象有最少的了解。也被称为最少知识原则。
该原则首先强调的是在类的结构设计上,应该尽可能低的设计成员的访问权限。
其根本思想是强调了类的松耦合,类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会波及有关系的类。
也就是说,一个类应该对自己需要耦合或调用的类知道的最少,类与类之间的关系越密切,耦合度越大,那么类的变化对其耦合的类的影响也会越大,这也是我们面向对象设计的核心原则:低耦合,高内聚。
什么是直接的朋友?
每个对象都必然与其他对象有耦合关系,两个对象的耦合就成为朋友关系,这种关系的类型很多,例如组合、聚合、依赖、关联等。
其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
纵向扩展总是有 上限的, 大数据、大并发系统的核心思路之一就是: 横向扩展。
横向扩展(Scale-out)的核心思路:采用分布式策略将系统的负载分散到多台服务器上,每台服务器处理一部分并发和流量。
横向扩展(Scale-out)可以充分利用现有硬件资源,提高系统的整体性能,使系统更能应对大规模并发请求。
缓存,广泛应用于系统设计的各个方面。
缓存,从操作系统到浏览器,从数据库到消息队列,从应用软件到操作系统,从操作系统到CPU,无处不在。
缓存,几乎所有复杂的服务和组件都在使用。
缓存,是空间换时间的思想。
同步调用,意味着调用方在调用一个方法后会阻塞等待该方法的逻辑执行完成。
异步调用 与 同步相反。
异步调用 , 主要是三大步骤:
调用方发出请求后不需要等待,快速返回
被调方可以慢慢执行,
调用方通过回调函数、事件通知等方式获取执行的结果。
异步调用 方式在大规模高并发系统中被广泛采用。
以 12306 网站为例。
当用户订票时,先将请求丢到消息队列中,然后立即告诉用户正在处理,然后立即返回。
系统进行复杂的订票操作,如查询余票、下单、更改余票状态等,这些操作可能需要耗费较长时间
当订票操作完成后,系统再通知用户订票成功或失败。
这种异步处理方式使得系统能够更好地应对高并发,减少了资源占用,提高了系统的性能和可扩展性。
高并发/大数据的架构设计,一般是分层进行,可以从下面的5大层来建设和分析:
主要流量入口
负责具体业务和视图展示;
网站首页、用户中心、商品中心、购物车、红包业务、活动中心等,
根据业务领域每个子域单独一个服务,分而治之。
服务层为应用层提供服务支持; 比如:订单服务、用户管理服务、红包服务、商品服务等
这个是我们重点要关注的架构设计,架构设计不合理,就很难抗住高并发,主要包括各种架构和模块的设计。
数据库和NoSQL,文件存储等。
关系数据库、nosql数据库等,提供数据存储查询服务。
这个是最基础的依赖,主要是一些服务的部署。
基础设施层一般包含了服务器、中间件、部署方式等等。
动静分离,分而治之
请参见尼恩之前的文章
10Wqps网关接入层,LVS+Keepalived(DR模式)如何搭建?
CDN相当于加上一层缓存, 加载离用户最近的idc机房, 由cdn的运营商提供, 比如电信等
秒杀的静态页面通过到CDN上预热(CDN是内容分发网络,可以简单理解成互联网上的巨大的缓存,用于存放静态页面、图片、视频等,可以显著提高访问速度),
用CDN扛流量,这样大量的商品详情页的访问请求就不用访问自己的网站(源站)。这样既可以提高访问速度,也没有给网站增加压力,同时也减少了网站带宽压力。
对一个复杂的业务,需要分割成不同的模块单元,分而治之
一个大的问题域,需要分解为很多小的问题域,分而治之
就是是微服务划分、微服务架构
微服务架构解决大单体架构的的很多问题,比如扩展性、弹性伸缩能力、小规模团队的敏捷开发等等。
如何进行微服务架构,如何划分微服务:
如何进行业务解耦,如何划分微服务?
在实操过程中, 建议使用DDD的建模方法进行建模。
具体请参见尼恩的系列文章
《字节面试:微服务一定要DDD,为什么?TDD和DDD 有何关系?》
《美团面试:微服务如何拆分?原则是什么?》
横向扩展设计,包括:应用集群、服务集群
应对高并发系统,单节点模式都不可能搞定,因此都需要搭建应用集群、服务集群,
常见的微服务Provider的自动伸缩策略有以下两种:
1)通过Kubernetes HPA组件实现自动伸缩。
2)通过微服务Provider自动伸缩伺服组件实现自动伸缩。
具体请参见尼恩的卷3:
缓存,广泛应用于系统设计的各个方面。
缓存,从操作系统到浏览器,从数据库到消息队列,从应用软件到操作系统,从操作系统到CPU,无处不在。
缓存,几乎所有复杂的服务和组件都在使用。
缓存,是空间换时间的思想。
缓存的可以提升系统性能,保护后端存储不被大流量打垮。
缓存的设计,需要分多个思路并行。
缓存的设计方案很多, 很复杂,也是面试的热点和难点
具体方案,请参见尼恩的卷3
进程内的异步, 是一个很有深度的问题, 尼恩把这个问题归纳为全链路异步
全链路异步,能大大的优化系统的并发量,单机解决高并发问题
从应用层的线程池,再到 IO层的Epoll 事件驱动(Nginx),都需要最大程度的异步。
全链路异步模式改造 具体的内容,请参考尼恩的深度文章:
全链路异步,让你的性能优化10倍+
另外,对于特殊的高并发场景,可以使用 Go+java的混合架构, 进一步压榨 CPU的性能
借助 Go 语言协程,去提高并发能力。
go + Java 混合微服务 架构, 请参见尼恩视频。
消息队列也是一种异步化操作,是依赖外部的中间件如消息队列,进行的异步。
针对流量突峰,仅仅有缓存来抗量可能还不够,还需要使用消息队列来异步削峰。
使用消息队列后,可以将同步处理的请求改为 通过消费 MQ 消息来异步消费,这样可以大大减少系统处理的压力,增加系统的并发量。
常用的消息队列比如 kafka、Rocketmq。
预热实际应用的场景有很多,比如在电商的大促到来前,我们可以把一些热点的商品提前加载到缓存中,防止大流量冲击DB。
预热原则一般有JVM预热、缓存预热、DB预热等,
通过预热的方式让系统先“热”起来,为高并发流量的到来做好准备。
数据存储量大的时候,就需要通过分库分表来存储。
分库分表模式虽然能显著提升数据库的容量,但会增加系统复杂性,因此在设计分库分表方案的时候需要结合具体业务场景,更全面地考虑。
高并发系统,大多数都是读多写少,因此读写分离可以帮助主库抗量。
一般我们都是一主多从的架构,可以抗量,也可以保证数据不丢。
针对业务场景而言,如果数据有冷热之分的话,可以将历史冷数据与当前热数据分开存储,
这样可以减轻当前热数据的存储量,可以提高性能。
当数据库中的数据多到一定规模时,数据库就不适用于复杂的查询了,往往只能满足普通查询的场景。
对于统计报表场景,在数据量大时不一定能跑出结果,而且在跑复杂查询时会导致其他查询变慢,对于全文检索、可变数据结构等场景,数据库天生不适用。
以上内容,请参见尼恩的卷3
基础设施层架构 包含:
具体,请参见尼恩的文章
网易面试:亿级用户,如何做微服务底层架构?
除了前面的高并发常规武器, 还有高并发的核武器::单元化+异地多活设计
一个服务对外的使用方可能有 A 业务、B 业务,那么如何保证 AB 业务不会相互影响,那么就是单元化(Set化)设计。
所谓单元,是指一个能完成所有业务操作的自包含集合,在这个集合中包含了所有业务所需的所有服务,以及单元的数据分片。
单元化架构就是把单元作为系统部署的基本单位,在全站所有idc机房中部署数个单元.
每个idc机房里的单元数目不定,任意一个单元都部署了系统所需的所有的服务。
任意一个单元的数据是首先拥有分片数据,但是为了切流的方便, 最终需要拥有全量数据。
传统意义上的 SOA 化(服务化)架构,服务是分层的,每层的节点数量不尽相同,上层调用下层时,随机选择节点。
单元化架构下,服务仍然是分层的,
不同的是每一层中的任意一个节点都属于且仅属于某一个单元,上层调用下层时,仅会选择本单元内的节点。
而要做到单元化,必须要满足以下要求:
业务必须是可分片的,如 淘宝按照用户分片, 饿了么按照地理位置分片
单元内的业务是自包含的,调用尽量封闭
单元化署就是把业务系统分为多个可扩展的逻辑分区,每个 SET 的逻辑分区都可以独立部署并提供服务,SET 也可以理解为 ”逻辑机房“ ,主要目的就是为了进行独立部署并且做到业务上的逻辑隔离。
关于 单元化SET 的具体例子:
微信红包用户发一个红包时,微信红包系统生成一个ID作为这个红包的唯一标识。
接下来这个红包的所有发红包、抢红包、拆红包、查询红包详情等操作,都根据这个ID关联。
红包系统根据这个红包ID,按一定的规则(如按ID尾号取模等),垂直上下切分。
切分后,一个垂直链条上的逻辑Server服务器、DB统称为一个SET。
单元化SET 部署之后,系统将所有红包请求这个巨大的洪流分散为多股小流,互不影响,分而治之。
现在稍微有点体量的公司都在做单元化,单元化的好处
1. 多AZ(可用区) 容灾、异地多活。
2. 服务器体量太大,单一IDC没有足够的机器。
3. 提升用户请求访问速度。
基础设施层一般包含了服务器、IDC、部署方式等等。
异地多活方案复杂, 尼恩之前积累过一系列的问题,可供参考:
《B站刚崩,唯品会又崩:亿级用户网站的架构硬伤与解决方案》
《100Wqps异地多活,得物是怎么架构的?》
《大家都崩,美团不崩:其高可用架构,巧夺天工!》
《美团面试:ES+Redis+MySQL高可用,如何实现?》
以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。
最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。
在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,里边有大量的大厂真题、面试难题、架构难题。很多小伙伴刷完后, 吊打面试官, 大厂横着走。
在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。
另外,如果没有面试机会,可以找尼恩来改简历、做帮扶。
遇到职业难题,找老架构取经, 可以省去太多的折腾,省去太多的弯路。
尼恩指导了大量的小伙伴上岸,前段时间,刚指导一个40岁+被裁小伙伴,拿到了一个年薪100W的offer。
狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由” 。
……完整版尼恩技术圣经PDF集群,请找尼恩领取
《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》PDF,请到下面公号【技术自由圈】取↓↓↓