我们现在看到的大型网站或架构,都是从小型网站、简单架构一步步发展而来的。当然,有些是基于现有的分布式架构构建的,这也取决于业务发展。在架构迭代演进的过程中,会遇到很多问题,就像升级打怪一样,等级越高,遇到的怪越强。之前有个同学问我,什么是建筑。这是我的答案。比如我们要盖房子,在盖房子之前必须要有建筑图纸。本图描述了建筑物的外形、内部结构、材料、设备等信息。项目实施时,将以此图为基础进行建设。软件架构也是如此,相当于一个软件系统的设计图。该图描述了各个组件之间的连接方式,并详细描述了组件之间的通信机制。在实现阶段,程序员将这些抽象图提炼成实际的组件,比如具体的接口定义、类定义等。然后我们会从纯技术的角度模拟一个简单的案例,看看架构迭代带来的问题。而解决方案,通过这样的迭代,让大家更清晰的理解架构。在整个过程中,重点关注架构变化带来的数据量和访问量的变化。不要特别关注业务功能。从电子商务网站开始。为了更好地理解,我们以电子商务网站为例。作为交易型网站,必须具备用户(用户注册、用户管理)、产品(产品展示、产品管理)、交易(下单、支付)等功能。如果我们只需要支持这些基本功能,那么我们的初始架构应该是这样的。这里需要注意的是,功能模块是通过JVM内部的方法调用进行交互的,应用程序和数据库是通过JDBC访问的。单机负载告警,数据库与应用分离随着网站的开通,访问量不断增加,那么此时服务器的负载势必会不断增加,必须采取一些措施来应对它。这里不管是换机还是各种软件层面的优化,都应该从架构的结构上做一些调整。我们可以把数据库和应用从一台机器改成两台机器:网站从一台机器改成两台机器。这个变化对我们影响很小。在单机的情况下,我们的应用使用JDBC连接数据库。现在数据库已经和应用程序分离了,我们只需要在配置文件中把数据库的地址从本机改成数据库服务器的ip地址即可。对开发、测试和部署没有影响。经过调整,我们可以缓解目前的系统压力。但是,随着时间的推移,访问量的不断增加,我们的系统仍然需要进行修改。为什么这么分裂?从计算机本身的角度来看,从一个请求的访问到处理到最后返回,性能瓶颈只会是:CPU、文件IO、网络IO、内存等因素。而计算机中的这些纬度存在性能瓶颈。如果某项资源消耗过多,通常会导致系统响应缓慢。因此,增加一台机器使得数据库的IO和CPU资源独占一台机器,从而提高性能。这里插个小题外话,简单说一下各种资源消耗的原因。CPU/IO/Memory:主要是上下文切换,因为每个CPU核一次只能执行一个线程,CPU调度有抢占式、轮询式等几种方式。以抢占式为例,每个线程都会分配一定的执行时间,当执行时间到了,线程中有IO阻塞,或者有高优先级的线程要执行。CPU会切换去执行其他线程。在切换的过程中,需要存储当前线程的执行状态,恢复待执行线程的状态。这个过程就是上下文切换。比如IO、锁等待等场景也会触发上下文切换。当上下文切换过多时,内核会占用更多的CPU。文件IO,比如频繁的日志写入,以及磁盘本身的处理速度慢都会导致IO性能问题。网络IO,带宽不够内存,包括内存溢出,内存泄漏,内存不足。其实无论是应用层调优与否,还是硬件升级。其实无非就是对这些因素的调整。应用服务器复杂告警,如何让应用服务器迁移到集群如果此时应用服务器压力变大,我们可以根据应用的检测结果,有针对性地优化性能压力大的地方.这里考虑通过横向扩展进行优化,将单机从一台变为两台集群应用服务器。这两个应用服务器之间没有直接交互,都依赖数据库对外提供服务。有两个问题。最终用户访问两个应用服务器的选择。对于这个问题,是可以使用DNS来解决,还是可以使用负载均衡设备来解决session问题?水平和垂直扩展对于大规模的分布式架构,我们一直追求一种简单优雅的方式来应对流量和数据量的增长。而这种方法通常意味着不需要更改软件程序,只需升级硬件或增加机器即可解决。这就是分布式架构下的伸缩设计。缩放分为垂直缩放和水平缩放两种。技术难度相对较低,运行和改造成本相对较低。但缺点是机器性能存在瓶颈,同时升级高性能小型机或大型机的成本非常高。这也是阿里去IOE增加CPU核心数的原因之一:增加CPU后,系统的服务能力可以大大提升,比如响应速度,同时可以处理的线程数。同一时间。但是CPU的引入也会带来一些显着的问题1.锁竞争加剧;多个线程同时运行访问一个共享数据,那么就涉及到锁竞争,当锁竞争激烈的时候,会有很多线程在等待锁,所以即使增加CPU也不能让线程处理得更快。当然这里还有调优的方法,可以用来减少锁竞争*2。支持并发请求的线程数是固定的,所以立即增加CPU并不能提高系统的服务能力*3。对于单线程任务,多核CPU作用不大**增加内存:增加内存可以直接提高系统的响应速度。当然也有可能达不到效果,就是如果JVM堆内存是固定的。横向扩展:通过增加机器来支持流量和数据量的增长,就变成了横向扩展。横向扩展理论上没有瓶颈,但缺点是技术要求比较高,同时给运维带来了更大的挑战。垂直缩放和水平缩放都有各自的优势。我们在实际使用过程中将两者结合起来。一方面要考虑硬件升级的成本,另一方面要考虑软件改造的成本。引入负载均衡设备服务路由,基于负载均衡设备实现,引入负载均衡器后,会带来会话相关的问题负载均衡算法RoundRobin方法将请求依次分发到后台服务器,以及均衡对待每台服务器,不考虑服务器的实际连接数和当前系统负载缺点:当集群中服务器的硬件配置不同,性能差异较大时,随机方式无法处理不同的。通过系统的随机功能,根据后台服务器列表的大小值,随机选择其中一台进行访问。随着调用次数的增加,它的实际效果越来越接近后台将流量平均分配给各个服务器,也就是轮询方式的效果。优点:使用简单,不需要额外的配置和算法。缺点:随机数的特点是只有在数据量足够大的情况下才能保证均衡,所以如果限制请求量,可能无法满足负载均衡的要求。源地址散列法根据服务消费者请求的客户端IP地址,通过散列函数计算出一个散列值,对散列值与服务器列表的大小进行取模运算,结果为服务器待访问地址的序号。源地址哈希方法用于负载均衡。同一个IP客户端,如果服务器列表不变,会映射到同一个后台服务器进行访问。不同WeightedRoundRobin方式的后台服务器可能机器配置不同,当前系统负载不同,因此抗压能力也不同。为高配置、低负载的机器分配更高的权重,使其能够处理更多的请求;为低配置、高负载的机器分配较低的权重,以降低其系统负载。加权循环非常有效。这个问题已经很好的处理了,request是按照权重顺序分配到后端的。最小连接数的方法。前面的方法都是通过请求次数的合理分配来最大化服务器的利用率,但实际上,请求次数的平衡并不代表负载的平衡。因此引入最小连接数法。它根据后端服务器当前的连接状态,动态选择一个连接积压最少的服务器来处理当前请求,尽可能提高后端服务器的利用率,合理分配负载给各个服务器。会话问题当我们打开一个网页时,基本上需要浏览器和网络服务器进行多次交互。我们都知道Http协议本身是无状态的。这也是http协议设计的初衷。客户端只需要简单地请求服务器下载某个文件,客户端和服务器都不需要记录彼此过去的行为,每个请求都是独立的,就像顾客和自动售货机的关系。其实我们很多场景都是需要Stateful特性的,所以很巧妙的引入了session+cookie的机制,为每次请求记住session。在会话开始时,为当前会话分配一个唯一的会话ID(sessionid),然后通过cookie将这个ID告诉浏览器。以后每次请求时,浏览器都会带上这个sessionID来告诉web服务器这个请求是属于哪个session的。在web服务器上,每个session都有一个独立的storage来保存不同session的信息。如果遇到cookie被禁用的情况,一般的做法是将这个sessionID放在URL的参数中。而我们的应用服务器从一台变成两台之后,就会遇到session的问题。分布式环境下的session共享session共享在现在的互联网背景下已经不是什么新鲜话题了,如何解决session共享其实是一个问题。许多非常成熟的解决方案服务器实现会话复制或会话共享。这种共享会话与服务器密切相关。我们增加了Web服务器之间的session数据同步,通过同步来保证不同Web服务器之间的session数据。持续的。一般应用容器支持SessionReplication的方式存在一个问题:同步Session数据会导致网络带宽开销。只要会话数据发生变化,就需要将数据同步到所有其他机器上。机器越多,同步带来的网络带宽开销就越大。每个Web服务器都必须保存所有会话数据。如果整个集群的session数据很多(很多人同时访问网站),每台机器保存session数据的内容就会很严重。这种方案是依靠应用容器完成Session的复制来解决Session的问题,应用本身并不关心这个事情。该方案不适用于集群机器数量较多的场景。使用成熟的session复制技术,如12306使用的gemfire,例如RedisSession等常用内存数据库,数据不保存到本机,而是保存在一个集中存储的地方,session的修改也发生在集中存储的地方.Web服务器使用Session从集中存储中读取。这样可以保证不同的web服务器读取到的Session数据是一样的。Session的具体存储方式可能是数据库出了问题:读写Session数据引入了网络操作。与本地数据读取相比,问题在于存在延迟和不稳定,但我们的通信基本发生在网络中,问题不大。如果集中存储Session的机器或者集群出现问题,就会影响到我们的应用。与SessionReplication相比,当Web服务器数量比较多,Session数量比较多的时候,这种集中存储方案的优势非常明显。很容易想到在客户端使用cookie来维护session,但是客户端存在风险,数据不安全,可存储的数据量比较小,所以在客户端维护session需要加密会话中的信息。我们的Session数据放在Cookie中,然后在Web服务器上从Cookie中生成对应的Session数据。就好像我们每次都自带碗筷,这样我们就可以随意选择去哪家餐厅了。与以往的中心化存储方案相比,不依赖于外部存储系统,从外部系统获取和写入会话数据不存在网络延迟或不稳定。有一个问题:安全。Session数据本来就是服务器端的数据,而这种方案是让这些服务器端的数据去往外网和客户端,所以存在安全问题。我们可以对写入的Cookie的Session数据进行加密,但是为了安全起见,物理上不可访问是安全的。数据库压力越来越大,还是读写分离吧。随着业务的不断增长,数据量和访问量也在不断增加。对于大型网站,有很多业务读多写少,这种情况会直接反馈到数据库。所以针对这种情况,我们可以考虑采用读写分离的方式来优化数据库的压力。这种结构性变化会带来两个问题。如何同步数据?我们希望通过读库来分担主库的读压力,那么首先需要解决的就是如何复制到读库。数据库系统一般都会提供数据复制的功能,我们可以直接使用数据库系统本身的机制。不同的数据库系统有不同的支持。比如Mysql支持Master+slave的结构来提供数据复制机制。如何将应用程序路由到数据源。对于应用来说,增加读库对结构变化有一定的影响,就是我们的应用。需要根据不同的情况选择不同的数据库源。搜索引擎实际上是一个图书馆阅读搜索引擎。其实可以理解为图书馆——阅读。我们的产品存储在数据库中,网站需要为用户提供实时检索的功能,尤其是产品。搜索此片。对于这样的读请求,如果全部使用walk-read库,实际上会存在性能瓶颈。使用搜索引擎不仅可以大大提高检索速度。也可以减轻读取数据库的压力。搜索引擎最重要的工作就是根据被搜索的数据建立索引,随着被搜索数据的变化,索引也需要随之变化。搜索集群的使用方法和读库的方法是一样的,但是建立索引的过程基本上需要自己来实现。搜索引擎建立索引的方式可以从两个纬度来规划,一个是基于全量/增量的划分。一种是基于实时/非实时划分。full方法用于首次创建索引,可能是新建的,也可能是重建的。增量法是在全量的基础上不断更新索引。实时与非实时检索在索引更新时间上,实时最好,非实时主要考虑对数据源的保护。总的来说,搜索引擎技术解决了网站搜索时在某些场景下的阅读问题。提供更好的查询效率。数据读取加速的利器——缓存和分布式存储在大型网站中,基本上是在解决存储和计算的问题,所以存储是一个非常重要的支撑系统。在建站初期,我们都是从关系型数据库入手,很多时候为了方便,我们会把一些业务逻辑放在数据库中,比如触发器、存储过程等。虽然前期解决问题很方便,但是在以后的开发过程中会带来很多麻烦,关系型数据库无法完全满足存储需求。分布式文件系统不适合一些图片和大文本,所以我们会使用分布式文件系统来实现文件存储。分布式文件系统有很多产品,比如淘宝的TFS,谷歌的GFS。还有开源的HDFSNoSQLNoSQL,我们可以理解为NotOnlySQL或者NoSQL。两个意思都是表达在大型网站中,关系型数据库可以解决大部分问题,但是对于不同的内容特征、访问特征、交易特征等的存储要求是不同的。NoSQL定位于文件系统和SQL关系数据库之间的一类。数据缓存是为了更好的服务。大型网站内部会使用一些数据缓存。主要用于分担数据库的读取压力。缓存系统一般用于保存和查询键值对。应用系统一般会将热点数据放入缓存中,缓存的填充也应该由应用系统来完成。如果数据不存在,则单独从数据库中取出后放入缓存中。随着时间的推移,当缓存容量不足需要清除数据时,最近未访问的数据将被清除。另一种方式是在数据库中的数据发生变化后,主动将数据放入缓存系统。这样做的好处是当数据发生变化时可以及时更新缓存的数据,不会造成读取失败。页面缓存除了数据缓存,我们还可以缓存页面。数据缓存可以加快应用程序响应请求但页面最终显示给用户时的数据读取次数。对于一些动态生成的页面或者访问量特别高的页面,我们会或者内容做一些缓存。为了弥补关系型数据库的不足,引入分布式存储,目前应用最广泛的还是关系型数据库,但是在某些场景下,关系型数据库并不是很适合。所以我们会引入分布式存储系统,比如redis、mongoDB、cassandra、HBase等,根据不同的场景和数据结构类型,选择合适的分布式存储系统可以极大的提升性能??。分布式系统通过集群提供大容量、高并发访问的支持,数据冗余融资。读写分离后,数据库遇到了瓶颈。通过读写分离,在某些场景下用分布式存储系统替代关系型数据库,可以减轻主库的压力,解决数据存储的问题。发展,我们的主库也会遇到瓶颈。推演到现在,我们网站的每一个模块:交易、商品、用户数据都还是保存在一个数据库中。虽然加入了缓存和读写分离的方法,但是数据库的压力还是越来越大,所以我们可以对数据进行纵横拆分,解决数据库压力问题就是将数据库中不同的业务数据拆分成不同的数据库,那么按照我们推导的例子,用户、交易、商品的数据是分离的,不同业务的数据从原来的数据库拆分到多个数据库,那么就需要考虑如何处理原来的站-单独的跨业务交易。使用分布式事务来解决去掉事务或者不追求强大的事务支持的问题。数据垂直拆分后,解决了所有业务数据放在一个数据库的压力问题,也可以根据不同业务的特点进行更多的优化。垂直拆分后,遇到了瓶颈。数据的水平拆分对应于数据的垂直拆分和水平拆分。数据水平拆分就是将同一张表的数据拆分到两个数据库中,数据水平拆分的原因是某个业务数据表的数据量或者更新量已经达到了单个数据库的瓶颈。这时可以将表拆分到两个或多个数据库中。数据水平拆分与读写分离的区别在于,读写分离解决的是读取压力大的问题,对于数据量大或者更新量大的情况不适用。数据水平拆分和数据垂直拆分的区别在于,垂直拆分是将不同的表拆分到不同的数据库中,而水平拆分是将同一张表拆分到不同的数据库中。我们可以进一步将用户表拆分成两个数据库。他们有结构完全相同的用户表,每个图书馆的用户表只覆盖了一部分用户。两个数据库的users之和相当于没有拆分之前,user表的水平拆分会影响到sql路由问题。需要根据条件确定当前请求发送到的数据库中主键的处理。不能使用自增id,需要全局id,因为同业务的数据被屏蔽了。拆分到不同的数据库,所以有些查询需要跨两个数据库获取。如果数据量太大,需要分页,会比较难处理。分布式存储,数据垂直拆分和水平拆分都是解决数据问题的方法。接下来,我们将看看应用程序的变化。随着业务的发展,应用的功能会越来越多,应用也会越来越大,应用越大,我们就需要思考如何让应用不增长,这就需要把应用拆解,从一个应用改过来到两个甚至多个。第一种方式是根据业务特点拆分应用。在我们的例子中,主要的业务功能分为三个部分,用户、产品和交易。我们可以把原来的应用拆分成两个应用,分别关注交易和商品。对于交易和商品,都会有设计用来使用用户的地方。我们让这两个系统自己完成涉及用户的工作,类似用户注册、登陆、登录等基本的用户工作可以暂时交给两个系统之一来完成。我们也可以按照用户注册、用户登录、用户信息维护将其拆分成三个系统。系统中会有一些类似的代码,比如用户相关的代码。如何保证这些代码的一致性,如何为其他模块提供复用,也是需要解决的问题。而且,这样拆分出来的新系统之间也没有直接的相互调用服务。让我们看一下面向服务的方法。我们把应用分为三层,web系统在最上面,用来完成不同的任务。对于业务功能,中间有一些服务中心,不同的服务中心提供不同的业务服务;在底部,业务数据库与之前相比有几个重要的变化。首先,业务功能之间的访问不仅是单机内部的方法调用,还引入了远程服务调用。第二,共享代码不再分散在不同的应用中,这些实现都放在各个服务中心。最后,与数据库的连接也发生了一些变化。我们把数据库的交互工作放在服务中心,让前端web应用更关注与浏览器的交互工作,而不是过多关注业务逻辑。将链接数据库的任务交给相应的业务服务中心,可以减少数据库的连接次数。服务中心不仅集中管理了一些可以共享的代码,也让这些代码更容易维护。服务方式会带来很多好处。首先,从结构上看,系统架构比??原来的架构更清晰、更立体。从稳定性的角度来看,一些分散在多个应用系统中的代码已经成为一个服务,由专门的团队统一维护。一方面,它可以提高代码的质量。相对于业务系统,频率会少很多,这样也会提高整个架构的稳定性。最后,下层资源由服务层统一管理,结构更加清晰。对于团队开发效率的提升服务有比较大的途径,对研发也会有很大的影响。之前的开发模式是几个大的团队负责几个大的应用。随着服务的实施,我们的应用数量会迅速增加,系统内部的依赖也会变得错综复杂。同时,团队也进行了拆分,每个小团队专注于一个特定的服务或应用方面,迭代效率会更高。版权声明:除特别声明外,本博客所有文章均采用CCBY-NC-SA4.0许可协议。转载请注明来自Mic带你学建筑!如果本文对您有帮助,请给个关注和点赞。您的坚持是我不断创作的动力。欢迎关注同名微信公众号获取更多技术干货!
