最近有不少小伙伴在后台留言,说 Java 的面试越来越难了,尤其是技术面,考察得越来越细,越来越底层。疫情期间虽说某些大厂高薪放低标准进行招聘,但很多小伙伴还是含泪转身。因为目前新老程序员更替严重,跳槽的元老还是有很多的,年轻人机会很大,特别是校招菜鸟。现在就给您们罗列一些高端架构学习素材! 希望您们早日实现大厂梦!薪资早日过20k。
-------------------------- 注意看文章中间段我会列出大厂高端面试的一些资料,会对您有一定的帮助。
作为一名程序员,技术面试是不可避免的一个环节,一般技术面试官都会通过自己的方式去考察程序员的技术功底与基础理论知识。如果你参加过一些面试,肯定会遇到一些这样的问题:
很多时候,大家面试遇到的题目会和自己准备的“题库”中的问题不太一样,即使做了复盘,下次面试还是不知道该从何处下手。如果你有这种感觉,那么说明你的技术还需要继续修炼,同时还缺乏一份系统全面的面试复盘笔记。
为了帮大家解决这一问题,我专门为大家从京东的一位老哥手上淘来了一份Java架构速成笔记,涵盖 23 个 Java 技术栈,图文并茂,肯定能对大家有所帮助!由于篇幅原因,在这只展示目录及内容截图,有需要的小伙伴麻烦转发后私信回复【444】即可来免费领取了
单体架构就是将所有的应用、数据库、文件都部署在一台机器上,俗称All-In-One。简单来讲其实就是我们熟知的SSH架构或SSM架构,把所有的业务模块都放在一个应用中开发,这里面又衍生出三层架构,即表示层、业务逻辑层和数据库访问层,虽然在软件设计中划分了经典的三层模型,但是对业务场景没有划分,一个典型的单体应用就是将所有的业务场景的表示层、业务逻辑层和数据访问层放在一个工程项目中,最终经过编译、打包,部署在一台服务器上。单体架构图如下:
1)部署简单: 由于是完整的结构体,可以直接部署在一个服务器上即可。
2)技术单一: 项目不需要复杂的技术栈,往往一套熟悉的技术栈就可以完成开发。
3)用人成本低: 单个程序员可以完成业务接口到数据库的整个流程。
1)系统启动慢: 一个进程包含了所有的业务逻辑,涉及到的启动模块过多,导致系统的启动、重启时间周期过长;
2)系统错误隔离性差、可用性差:任何一个模块的错误均可能造成整个系统的宕机;
3)可伸缩性差:系统的扩容只能只对这个应用进行扩容,不能做到对某个功能点进行扩容;
4)线上问题修复周期长:任何一个线上问题修复需要对整个应用系统进行全面升级。
随着公司业务的不断发展,由于单台服务器性能有限,逐渐无法满足业务需求,大量用户高并发的访问导致系统性能越来越差,数据存储空间开始出现不足,这时我们需要将应用和数据分离,分离后开始使用三台服务器:应用服务器、文件服务器、数据库服务器。如图:
应用和数据分离后,不同种类的服务器承担着不同的服务角色,网站的并发处理能力和数据存储空间都得到了很大的改善,支持网站业务进一步发展,但是随着用户量进一步增多,数据库压力依然越来越大,访问延迟不可避免,进而影响整个网站的性能,糟糕的用户体验使得系统需要进一步优化。
随着QPS持续提高,为了降低接口访问时间、提高服务性能和并发,我们注意到,网站访问有个著名的二八定律,即80%的业务集中访问在20%的数据上(热数据),其实部分数据有很多不需要每次都从数据库获取,比如经常被查询但对准确性要求并不是特别高的数据。如果我们将这一小部分热数据缓存在内存中,能够很好的减少数据库的访问压力,并大幅提升网站响应速度,因此网站就开始加入了缓存应用,常用的缓存组件有redis,ehcache等。
注意:能使用缓存的数据得满足如下要求:
1)对于数据实时性要求不高
对于一些经常访问但是很少改变的数据,查询明显多于修改,适用缓存就很有必要,比如一些网站配置项。
2)对于性能要求高
比如一些秒杀活动场景。
缓存在一定程度上解决了数据库访问量比较大的问题,但仍无法解决随着业务增多造成的服务器并发压力大的问题,因为单台服务器的计算能力有限,应用程序运行速度就有限,因此这时想要突破服务器性能的瓶颈,最直接的办法就是增加一台甚至多台应用服务器来分担原来服务器的访问压力和存储压力,多台应用服务器之间没有直接的交互,他们都是依赖数据库各自对外提供服务。注意,这里的多台应用服务器部署的都是同一套应用程序(即同一套项目源码或war包),这就是集群架构,结构如图:
1)用户的请求由谁来转发到到具体的应用服务器
2)有什么转发的算法
3)应用服务器如何返回用户的请求
4)用户如果每次访问到的服务器不一样,那么如何维护session的一致性
这时候,你会发现图中多了一个名为负载均衡器的代理服务器,通常我们使用Nginx来实现,它的作用主要就是用户不需要记住两个甚至多个应用服务器的IP或域名,只要记住一台代理服务器IP就可以了。由代理服务器来负责分发用户是访问哪个应用服务器,那么用户具体是访问服务器1、访问服务器2还是服务器N,由Nginx里面的权重设置来决定的,并且通过Nginx的配置,Session共享也可以附带实现,保证多台应用服务器的Session一致性。另外,这样做还有一个好处就是将代理服务器放在外网,应用服务器均放在内网,可以有效防止应用服务器被恶意攻击。
系统正常运行了一段时间后,虽然加有缓存,使绝大多数的数据库操作可以不通过数据库就能完成,但是仍然有一部分的数据库操作(缓存访问不命中或缓存过期)和全部的写操作需要访问数据库,当用户规模再一步增长后,数据库因为负载压力过大还是会成为系统性能的瓶颈,这时主流的数据库都提供的有主从热备份功能,通过配置两台数据库实现主从关系,可以将一台数据库服务器的数据更新同步到另一台服务器上。可以利用这一功能来实现数据库读写分离,从而改善数据库的负载压力,如图:
在图中我们可以发现主数据库服务器负责写操作,而从服务器负责读操作,这样数据库的压力就被分散在两台服务器上,而我们只需保证主从数据库的数据一致性即可,这里说明下Mysql的读写分离可以通过自身自带的从主复制实现,Oracle数据库可以通过阿里巴巴的mycat组件来实现。
1)负载:是指将请求负载到不同的服务器;
2)均衡:指的是平分服务器的请求次数。
3)热备份:指的是它可以在不停止数据库运行的情况下,自动的根据定时任务或者数量容量大小去备份数据库。
为了应对复杂的网络环境和不同地区用户的访问(比如你的服务器在美国或新加坡,国内用户直接访问可能会很卡),保障各个地区的用户都能流畅地访问系统,可以通过CDN和反向代理来加快用户访问的速度,同时减轻后端服务器的负载压力。CDN与反向代理的基本原理其实本质上都是缓存。CDN部署在网络提供商的机房,用户请求到来的时候从距离自己最近的网络提供商机房获取数据,而反向代理则部署在网站的中心机房中,请求带来的时候先去反向代理服务器中查看请求资源,如果有则直接返回。结构如图:
分布式架构的演变涉及分布式文件、分布式数据库、NoSql、搜索引擎以及业务模块的拆分,下面我们一个个看。
即使性能再强大的单台服务器也无法满足大型网站持续增长的业务需求,数据库经过读写分离后,数据库服务器从一台拆分成了两台,但是随着业务的飞速增长依然会达到性能瓶颈,这时我们需要使用分布式数据库,同时文件系统也一样,需要使用分布式文件系统。
分布式数据库是数据库拆分的最后的手段,只有在表单数据规模非常庞大的时候才使用,不到不得已时,我们更常用的手段是业务分库,将不同的业务数据部署在不同的物理服务器上。结构如图:
随着业务越来越复杂,系统对数据存储和检索的需求也跟着复杂化,这时一些NoSQL(Reids,HBase,mongodb)数据库技术和搜索引擎(Solr,Elasticsearch)的时候就显得很有必要。数据库做读库的时候,往往对模糊查询显得力不从心,即使做了读写分离,这个问题还未能解决。以我们电商网站为例,发布的商品存储在数据库中,用户最常使用的功能就是查找商品,尤其是根据商品的标题来查找对应的商品。对于这种需求,一般我们都是通过like功能来实现的,但是这种方式的代价非常大。此时我们可以使用搜索引擎的倒排索引来完成。
搜索引擎能够大大提高查询速度,但系统引入搜索引擎后也会带来以下的开销:
1)带来大量的维护工作,我们需要自己实现索引的构建过程,设计全量/增加的构建方式来应对非实时与实时的查询需求。
2)需要维护搜索引擎集群
3)搜索引擎并不能替代数据库,他解决了某些场景下的“读”的问题,是否引入搜索引擎,需要综合考虑整个系统的需求。
结构如下图:
NoSQL和搜索引擎对可伸缩的分布式特性具有更好的支持,应用服务器通过一个统一的数据访问模块访问各种数据,减轻应用程序管理诸多数据源的麻烦。
当访问量达到一定规模的时候我们可以通过分而治之的手段将整个系统的业务分成不同的产品线,例如我们将系统的首页,商铺,订单,买家,卖家,支付,订单等模块拆分成不同的产品线,每个产品线都独立成一个子项目,每个子项目都有自己的独立的数据库,子项目之间通过RPC框架(dubbo,webService,httpClient…)建立连接通信,也可以通过消息队列实现异步分发处理,从而构成一个完整的系统,结构如下图:
SOA是什么?SOA全英文是Service-Oriented Architecture,意为面向服务编程,也称为服务治理,是一种思想,一种方法论,一种分布式的服务架构,这里的服务可以理解为service层业务服务,它将共同的业务逻辑拆分成独立的应用进行部署,这些应用没有视图层,只对外提供RPC调用接口。
注意区分项目与服务概念:
项目表达的意思:包含业务逻辑层和视图层
服务表达的意思是:只包含业务逻辑层,没有视图层
SOA 系统原型的一个典型例子是 CORBA,它已经出现很长时间,其定义的概念与 SOA 相似。SOA 建立在 XML 等新技术的基础上,通过使用基于 XML 的语言来描述接口,服务已经转到更动态且更灵活的接口系统中,CORBA 中的 IDL 无法与之相比。如图描述了一个完整的 SOA 模型。
在 SOA 模型中,所有的功能都定义成了独立的服务。服务之间通过交互和协调完成业务的整体逻辑。所有的服务通过服务总线或流程管理器来连接。这种松散耦合的架构使得各服务在交互过程中无需考虑双方的内部实现细节,以及部署在什么平台上。一个独立的服务基本结构如图所示:
服务模型的表示层从逻辑层分离出来,中间增加了服务对外的接口层。通过服务接口的标准化描述,使得服务可以提供给在任何异构平台和任何用户接口使用。这允许并支持基于服务的系统成为松散耦合、面向构件和跨技术实现,服务请求者很可能根本不知道服务在哪里运行、是由哪种语言编写的,以及消息的传输路径,而是只需要提出服务请求,然后就会得到答案。举个例子:
通过上面的图我们可以看出,多个子系统直接相互交互,相互调用非常凌乱,这样我们就很不爽,所以我们就用到了我们的SOA架构,SOA又叫服务治理,SOA就是帮助我们把服务之间调用的乱七八糟的关系给治理起来,然后提供一个统一的标准,把我们的服务治理成下图所示,以前我们的服务是互相交互,现在是只对数据总线进行交互,这样系统就变得统一起来。
各个系统分别根据统一标准向数据总线进行注册,各子系统调用其他子系统时,我们并不关心如何找到其他子系统,我们只找数据总线,数据总线再根据统一标准找其他子系统,所以数据总线在这里充当一个指路人的作用。
1)系统集成:站在系统的角度,解决企业系统间的通信问 题,把原先散乱、无规划的系统间的网状结构,梳理成 规整、可治理的系统间星形结构,这一步往往需要引入 一些产品,比如 ESB、以及技术规范、服务管理规范; 这一步解决的核心问题是【有序】
2)系统的服务化:站在功能的角度,把业务逻辑抽象成 可复用、可组装的服务,通过服务的编排实现业务的 快速再生,目的:把原先固有的业务功能转变为通用 的业务服务,实现业务逻辑的快速复用;这一步解决 的核心问题是【复用】
3)业务的服务化:站在企业的角度,把企业职能抽象成 可复用、可组装的服务;把原先职能化的企业架构转变为服务化的企业架构,进一步提升企业的对外服务能力;“前面两步都是从技术层面来解决系统调用、系统功能复用的问题”。第三步,则是以业务驱动把一个业务单元封装成一项服务。这一步解决的核心问题是【高效】
1)降低用户成本,用户不需要关心各服务之间是什么语言的、不需要知道如果调用他们,只要通过统一标准找数据总线就可以了。
2)程序之间关系服务简单
3)识别哪些程序有问题(挂掉)
提示了系统的复杂程度,性能有相应影响。
随着业务拆分越来越小,存储系统越来越庞大,应用系统的整体复杂度呈指数级增加,部署维护越来越困难,由于所有应用要和所有数据库系统连接,最终导致数据库连接资源不足,拒绝服务。
1)当服务越来越多时,服务URL配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。
2)当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。
3)接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?
4)服务多了,沟通成本也开始上升,调某个服务失败该找谁?服务的参数都有什么约定?
5)一个服务有多个业务消费者,如何确保服务质量?
6)随着服务的不停升级,总有些意想不到的事发生,比如cache写错了导致内存溢出,故障不可避免,每次核心服务一挂,影响一大片,人心慌慌,如何7)控制故障的影响面?服务是否可以功能降级?或者资源劣化?
解决方案:公共的应用模块被提取出来,部署在分布式服务器上供应用服务器调用。也就是我们所说的分布式服务或者微服务。
微服务架构是一种架构概念,核心思想在于通过将业务功能和需求分解到各个不同的服务中进行管理,实现对业务整体解耦。围绕业务模式创建应用服务,应用服务可独立地进行开发、迭代、部署,使项目的架构更加清晰明确。目前主流的微服务架构框架就是SpringCloud。
以下是一个微服务案例的架构图:
1)每个服务足够内聚,足够小,代码容易理解、开发效率提高;
2)服务之间可以独立部署,微服务架构让持续部署成为可能;
3)每个服务可以各自进行负载均衡扩展和数据库扩展,而且,每个服务可以根据自己的需要部署到合适的硬件服务器上;
4)容易扩大开发团队,可以针对每个服务(service)组件开发团队;
5)提高容错性(fault isolation),一个服务的内存泄露并不会让整个系统瘫痪;
6)系统不会被长期限制在某个技术栈上。
1)开发人员要处理分布式系统的复杂性;
2)开发人员要设计服务之间的通信机制,对于需要多个后端服务的user case,要在没有分布式事务的情况下实现代码非常困难;
3)涉及多个服务直接的自动化测试也具备相当的挑战性;
4)服务管理的复杂性,在生产环境中要管理多个不同的服务的实例,这意味着开发团队需要全局统筹(PS:现在docker的出现适合解决这个问题);
1)单体架构很好区分,只要看所有应用程序、文件系统、数据库等是否都在一个服务器上
2)集群架构,其实就是将原先的单体架构中的应用程序分别部署在多台应用服务器上,不过他们还是公用一个数据库。
3)分布式架构,将原先的单体架构中的项目按照功能模块拆分成多个单独的子项目,分别部署在不同的应用服务器,并且每个子项目都有自己的独立的数据库。
4)SOA架构与微服务架构的区别:
首先SOA和微服务架构一个层面的东西,而对于ESB和微服务网关是一个层面的东西,一个谈到是架构风格和方法,一个谈的是实现工具或组件。
1.SOA(Service Oriented Architecture)“面向服务的架构”:他是一种设计方法,其中包含多个服务, 服务之间通过相互依赖最终提供一系列的功能。一个服务 通常以独立的形式存在与操作系统进程中。各个服务之间 通过网络调用。
2.微服务架构:其实和 SOA 架构类似,微服务是在 SOA 上做的升华,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。
微服务架构 = 80%的SOA服务架构思想 + 100%的组件化架构思想 + 80%的领域建模思想
JMM规定了所有的变量都存储在主内存(Main Memory)中。每个线程还有自己的工作内存(Working Memory),线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成。
1、并发编程三要素
原子性:即一个不可再被分割的颗粒。在Java中原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)
可见性:当多个线程访问同一个变量时,如果其中一个线程对其作了修改,其他线程能立即获取到最新的值。
2、 线程的五大状态
创建状态:当用 new 操作符创建一个线程的时候
就绪状态:调用 start 方法,处于就绪状态的线程并不一定马上就会执行 run 方法,还需要等待CPU的调度
运行状态:CPU 开始调度线程,并开始执行 run 方法
阻塞状态:线程的执行过程中由于一些原因进入阻塞状态比如:调用 sleep 方法、尝试去得到一个锁等等
死亡状态:run 方法执行完 或者 执行过程中遇到了一个异常
3、悲观锁与乐观锁
悲观锁:每次操作都会加锁,会造成线程阻塞。
乐观锁:每次操作不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止,不会造成线程阻塞。
4、线程之间的协作
线程间的协作有:wait/notify/notifyAll等
5、synchronized 关键字
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1)、修饰一个代码块:被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象
2)、修饰一个方法:被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象
3)、修改一个静态的方法:其作用的范围是整个静态方法,作用的对象是这个类的所有对象
4)、修改一个类:其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
6、CAS
CAS全称是Compare And Swap,即比较替换,是实现并发应用到的一种技术。操作包含三个操作数—内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。
CAS存在三大问题:ABA问题,循环时间长开销大,以及只能保证一个共享变量的原子操作。
7、线程池
如果我们使用线程的时候就去创建一个线程,虽然简单,但是存在很大的问题。如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。线程池通过复用可以大大减少线程频繁创建与销毁带来的性能上的损耗。
Java内存模型是围绕着并发编程中原子性、可见性、有序性这三个特征来建立的。
一个操作不能被打断,要么全部执行完毕,要么不执行。在这点上有点类似于事务操作,要么全部执行成功,要么回退到执行该操作之前的状态。
基本类型数据的访问大都是原子操作,long 和double类型的变量是64位,但是在32位JVM中,32位的JVM会将64位数据的读写操作分为2次32位的读写操作来进行,这就导致了long、double类型的变量在32位虚拟机中是非原子操作,数据有可能会被破坏,也就意味着多个线程在并发访问的时候是线程非安全的。
一个线程对共享变量做了修改之后 ,其他的线程立即能够看到(感知到)该变量的这种修改(变化)。
无论是普通变量还是volatile变量都是如此,区别在于:volatile的特殊规则保证了volatile变量值修改后的新值立刻同步到主内存,每次使用volatile变量前立即从主内存中刷新,因此volatile保证了多线程之间的操作变量的可见性,而普通变量则不能保证这一点。
除了volatile关键字能实现可见性之外,还有synchronized,Lock,final也是可以的。
使用synchronized关键字,在同步方法/同步块开始时(Monitor Enter),使用共享变量时会从主内存中刷新变量值到工作内存中(即从主内存中读取最新值到线程私有的工作内存中),在同步方法/同步块结束时(Monitor Exit),会将工作内存中的变量值同步到主内存中去(即将线程私有的工作内存中的值写入到主内存进行同步)。
使用Lock接口的最常用的实现ReentrantLock(重入锁)来实现可见性:当我们在方法的开始位置执行lock.lock()方法,这和synchronized开始位置(Monitor Enter)有相同的语义,即使用共享变量时会从主内存中刷新变量值到工作内存中(即从主内存中读取最新值到线程私有的工作内存中),在方法的最后finally块里执行lock.unlock()方法,和synchronized结束位置(Monitor Exit)有相同的语义,即会将工作内存中的变量值同步到主内存中去(即将线程私有的工作内存中的值写入到主内存进行同步)。
final关键字的可见性是指:被final修饰的变量,在构造函数数一旦初始化完成,并且在构造函数中并没有把“this”的引用传递出去(“this”引用逃逸是很危险的,其他的线程很可能通过该引用访问到只“初始化一半”的对象),那么其他线程就可以看到final变量的值。
对于一个线程的代码而言,我们总是以为代码的执行是从前往后的,依次执行的。这么说不能说完全不对,在单线程程序里,确实会这样执行;但是在多线程并发时,程序的执行就有可能出现乱序。用一句话可以总结为:在本线程内观察,操作都是有序的;如果在一个线程中观察另外一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行语义(WithIn Thread As-if-Serial Semantics)”,后半句是指“指令重排”现象和“工作内存和主内存同步延迟”现象。
一个最经典的例子就是银行汇款问题,一个银行账户存款100,这时一个人从该账户取10元,同时另一个人向该账户汇10元,那么余额应该还是100。那么此时可能发生这种情况,A线程负责取款,B线程负责汇款,A从主内存读到100,B从主内存读到100,A执行减10操作,并将数据刷新到主内存,这时主内存数据100-10=90,而B内存执行加10操作,并将数据刷新到主内存,最后主内存数据100+10=110,显然这是一个严重的问题,我们要保证A线程和B线程有序执行,先取款后汇款或者先汇款后取款,此为有序性。
Java提供了两个关键字volatile和synchronized来保证多线程之间操作的有序性,volatile关键字本身通过加入内存屏障来禁止指令的重排序,而synchronized关键字通过一个变量在同一时间只允许有一个线程对其进行加锁的规则来实现,
在单线程程序中,不会发生“指令重排”和“工作内存和主内存同步延迟”现象,只在多线程程序中出现。
源码层面无死角解析Netty
Mysql优化
FastDFS
OpenResty
1.maven
2.git
3.Jenkins
FindBugs
1.JDK源码解析
2.Spring源码解析
3.MyBatis源码解析
4.Dubbo源码解析
5.Spring MVC源码解
6.Netty源码解析
不论是技术经理还是架构师,没有绝对地说哪条路是对还是错,适合自己才是最重要。小公司的可能没有架构师这个概念,大公司的架构师职位又不是那么容易拿下。但不管怎样,不断去学习新的技术,提升自己的层次是很有必要的,无论你在哪一家公司,过硬的技术水平才能吃得开。我花了五年时间才悟到这些道理。
如果你还没有掌握这些主流技术,现在想要在最短的时间里吃透它,只需转发后私信回复【444】即可获取这套完整的体系资料。
作者:Java码农之路