转载自https://www.cnblogs.com/wetest/p/6806506.html 和https://www.cnblogs.com/dudu0614/p/8821811.html
什么是分布式系统
分布式这一概念,一直都是后端工程师绕不过去的一个坎,今天,我们就一起来看看到底什么是分布式系统,又有哪些分布式技术世我们需要学习的。
根据百度百科的介绍,分布式系统(distributed system)是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。因此,网络和分布式系统之间的区别更多的在于高层软件(特别是操作系统),而不是硬件。
从分布式系统的诞生说起
我们常常会听说,某个互联网应用的服务器端系统多么牛逼,比如QQ、微信、淘宝。那么,一个互联网应用的服务器端系统,到底牛逼在什么地方?为什么海量的用户访问,会让一个服务器端系统变得更复杂?本文就是想从最基本的地方开始,探寻服务器端系统技术的基础概念。
承载量是分布式系统存在的原因
当一个互联网业务获得大众欢迎的时候,最显著碰到的技术问题,就是服务器非常繁忙。当每天有1000万个用户访问你的网站时,无论你使用什么样的服务器硬件,都不可能只用一台机器就承载的了。因此,在互联网程序员解决服务器端问题的时候,必须要考虑如何使用多台服务器,为同一种互联网应用提供服务,这就是所谓“分布式系统”的来源。
然而,大量用户访问同一个互联网业务,所造成的问题并不简单。从表面上看,要能满足很多用户来自互联网的请求,最基本的需求就是所谓性能需求:用户反应网页打开很慢,或者网游中的动作很卡等等。而这些对于“服务速度”的要求,实际上包含的部分却是以下几个:高吞吐、高并发、低延迟和负载均衡。
高吞吐,意味着你的系统,可以同时承载大量的用户使用。这里关注的整个系统能同时服务的用户数。这个吞吐量肯定是不可能用单台服务器解决的,因此需要多台服务器协作,才能达到所需要的吞吐量。而在多台服务器的协作中,如何才能有效的利用这些服务器,不致于其中某一部分服务器成为瓶颈,从而影响整个系统的处理能力,这就是一个分布式系统,在架构上需要仔细权衡的问题。
高并发是高吞吐的一个延伸需求。当我们在承载海量用户的时候,我们当然希望每个服务器都能尽其所能的工作,而不要出现无谓的消耗和等待的情况。然而,软件系统并不是简单的设计,就能对同时处理多个任务,做到“尽量多”的处理。很多时候,我们的程序会因为要选择处理哪个任务,而导致额外的消耗。这也是分布式系统解决的问题。
低延迟对于人数稀少的服务来说不算什么问题。然而,如果我们需要在大量用户访问的时候,也能很快的返回计算结果,这就要困难的多。因为除了大量用户访问可能造成请求在排队外,还有可能因为排队的长度太长,导致内存耗尽、带宽占满等空间性的问题。如果因为排队失败而采取重试的策略,则整个延迟会变的更高。所以分布式系统会采用很多请求分拣和分发的做法,尽快的让更多的服务器来出来用户的请求。但是,由于一个数量庞大的分布式系统,必然需要把用户的请求经过多次的分发,整个延迟可能会因为这些分发和转交的操作,变得更高,所以分布式系统除了分发请求外,还要尽量想办法减少分发的层次数,以便让请求能尽快的得到处理。
由于互联网业务的用户来自全世界,因此在物理空间上可能来自各种不同延迟的网络和线路,在时间上也可能来自不同的时区,所以要有效的应对这种用户来源的复杂性,就需要把多个服务器部署在不同的空间来提供服务。同时,我们也需要让同时发生的请求,有效的让多个不同服务器承载。所谓的负载均衡,就是分布式系统与生俱来需要完成的功课。
由于分布式系统,几乎是解决互联网业务承载量问题,的最基本方法,所以作为一个服务器端程序员,掌握分布式系统技术就变得异常重要了。然而,分布式系统的问题,并非是学会用几个框架和使用几个库,就能轻易解决的,因为当一个程序在一个电脑上运行,变成了又无数个电脑上同时协同运行,在开发、运维上都会带来很大的差别。
分布式系统提高承载量的基本手段
分层模型(路由、代理)
使用多态服务器来协同完成计算任务,最简单的思路就是,让每个服务器都能完成全部的请求,然后把请求随机的发给任何一个服务器处理。最早期的互联网应用中,DNS轮询就是这样的做法:当用户输入一个域名试图访问某个网站,这个域名会被解释成多个IP地址中的一个,随后这个网站的访问请求,就被发往对应IP的服务器了,这样多个服务器(多个IP地址)就能一起解决处理大量的用户请求。
然而,单纯的请求随机转发,并不能解决一切问题。比如我们很多互联网业务,都是需要用户登录的。在登录某一个服务器后,用户会发起多个请求,如果我们把这些请求随机的转发到不同的服务器上,那么用户登录的状态就会丢失,造成一些请求处理失败。简单的依靠一层服务转发是不够的,所以我们会增加一批服务器,这些服务器会根据用户的Cookie,或者用户的登录凭据,来再次转发给后面具体处理业务的服务器。
除了登录的需求外,我们还发现,很多数据是需要数据库来处理的,而我们的这些数据往往都只能集中到一个数据库中,否则在查询的时候就会丢失其他服务器上存放的数据结果。所以往往我们还会把数据库单独出来成为一批专用的服务器。
至此,我们就会发现,一个典型的三层结构出现了:接入、逻辑、存储。然而,这种三层结果,并不就能包医百病。例如,当我们需要让用户在线互动(网游就是典型) ,那么分割在不同逻辑服务器上的在线状态数据,是无法知道对方的,这样我们就需要专门做一个类似互动服务器的专门系统,让用户登录的时候,也同时记录一份数据到它那里,表明某个用户登录在某个服务器上,而所有的互动操作,要先经过这个互动服务器,才能正确的把消息转发到目标用户的服务器上。
又例如,当我们在使用网上论坛(BBS)系统的时候,我们发的文章,不可能只写入一个数据库里,因为太多人的阅读请求会拖死这个数据库。我们常常会按论坛板块来写入不同的数据库,又或者是同时写入多个数据库。这样把文章数据分别存放到不同的服务器上,才能应对大量的操作请求。然而,用户在读取文章的时候,就需要有一个专门的程序,去查找具体文章在哪一个服务器上,这时候我们就要架设一个专门的代理层,把所有的文章请求先转交给它,由它按照我们预设的存储计划,去找对应的数据库获取数据。
根据上面的例子来看,分布式系统虽然具有三层典型的结构,但是实际上往往不止有三层,而是根据业务需求,会设计成多个层次的。为了把请求转交给正确的进程处理,我们而设计很多专门用于转发请求的进程和服务器。这些进程我们常常以Proxy或者Router来命名,一个多层结构常常会具备各种各样的Proxy进程。这些代理进程,很多时候都是通过TCP来连接前后两端。然而,TCP虽然简单,但是却会有故障后不容易恢复的问题。而且TCP的网络编程,也是有点复杂的。——所以,人们设计出更好进程间通讯机制:消息队列。
尽管通过各种Proxy或者Router进程能组建出强大的分布式系统,但是其管理的复杂性也是非常高的。所以人们在分层模式的基础上,想出了更多的方法,来让这种分层模式的程序变得更简单高效的方法。
并发模型(多线程、异步)
当我们在编写服务器端程序是,我们会明确的知道,大部分的程序,都是会处理同时到达的多个请求的。因此我们不能好像HelloWorld那么简单的,从一个简单的输入计算出输出来。因为我们会同时获得很多个输入,需要返回很多个输出。在这些处理的过程中,往往我们还会碰到需要“等待”或“阻塞”的情况,比如我们的程序要等待数据库处理结果,等待向另外一个进程请求结果等等……如果我们把请求一个挨着一个的处理,那么这些空闲的等待时间将白白浪费,造成用户的响应延时增加,以及整体系统的吞吐量极度下降。
所以在如何同时处理多个请求的问题上,业界有2个典型的方案。一种是多线程,一种是异步。在早期的系统中,多线程或多进程是最常用的技术。这种技术的代码编写起来比较简单,因为每个线程中的代码都肯定是按先后顺序执行的。但是由于同时运行着多个线程,所以你无法保障多个线程之间的代码的先后顺序。这对于需要处理同一个数据的逻辑来说,是一个非常严重的问题,最简单的例子就是显示某个新闻的阅读量。两个++操作同时运行,有可能结果只加了1,而不是2。所以多线程下,我们常常要加很多数据的锁,而这些锁又反过来可能导致线程的死锁。
因此异步回调模型在随后比多线程更加流行,除了多线程的死锁问题外,异步还能解决多线程下,线程反复切换导致不必要的开销的问题:每个线程都需要一个独立的栈空间,在多线程并行运行的时候,这些栈的数据可能需要来回的拷贝,这额外消耗了CPU。同时由于每个线程都需要占用栈空间,所以在大量线程存在的时候,内存的消耗也是巨大的。而异步回调模型则能很好的解决这些问题,不过异步回调更像是“手工版”的并行处理,需要开发者自己去实现如何“并行”的问题。
异步回调基于非阻塞的I/O操作(网