欢迎各位兄弟 发布技术文章
这里的技术是共享的
对于一个操作系统来讲,IO动作到底该如何执行?为了我们系统的安全,进程无法直接操作IO设备的,必须要通过系统调用,请求内核来协助完成IO的动作,内核会在内核空间当中为每个IO设备准备一个buffer并维护一个buffer(缓冲区),当我们的进程发起请求以后,它请求的是内核的buffer,而内核在它的地址空间当中去跟IO设备进行交互,在内核与IO设备交互当中,由于IO设备可能会很慢,所以内核的空间buffer必须要等待IO设备将它的所有请求的数据复制到内核空间(buffer)当中才可以,当数据从内核buffer中,或者说从IO设备中读取完成之后,而接着我们的内核buffer开始将数据复制给进程,复制给请求者(进程),
如下图,当一个进程请求某个资源的时候,它期望从我们的硬盘上去读一个文件,我们当前这个进程就需要通过系统调用,向内核发起申请,由内核去获取这个文件,在内核在将这个文件获取并响应给这个进程之前,进程要处于等待状态,,,,,,,,,,我们内核必须要将buffer里的数据充完后,进程才算等待结束,,,,,,,,,,,,,于是我们内核开始从硬盘上将我们的用户请求的页面数据从磁盘读到内核缓冲区里面,当内核缓冲区填满之后,进程是无法直接访问内核空间的,所以内核又需要将数据从内核缓冲区复制到进程的自己的内存空间,而后进程才能响应给客户端,,,,,,,,,这个过程有几个等待状态,首先,用户向我们的服务器发起请求,等会儿我们的服务器响应之前它是一定处于等待状态的,当然对于用户来讲,可以做别的事,但是浏览器必须要在等待,,,,,,,,,,,,,,进程向内核发起系统调用,等这个用户空间准备好之前,也要处于等待状态,,,,,,,,,,,,内核接收请求以后,需要向IO设备发起请求,由IO设备将数据复制到内核空间里面来,所以内核也要处于等待状态,,,,,,,,,这是两段等待状态
所以这就是我们的IO操作,中间需要完成的等待,对于输入(请求的进程)而言,等待需要包括数据输入到buffer(数据从IO读到buffer空间),并且数据还要从buffer拷贝到进程的自己的内存里面去,,,所以等待分为两段,而根据等待模式的不同IO动作可以分为5种模式,
如下图,根据等待模式的不同IO动作可以分为5种模式,
1)blocking I/O: 阻塞式IO
2)nonblocking I/O: 非阻塞式IO
3)I/O multiplexing (select and poll): IO复用,为什么要进行IO复用?如果一个进程需要处理两个IO的时候,它就必须要进行复用,不然的话,它无法正常完成此类处理,,,,,,,,,,,,比如我们的一个进程同时处理多个请求时,必须得复用,,,,,,,,,,,一个进程同时处理两个连接时,必须得复用,,,,,要处理多个文件描述符时,也必须得复用,,,哪怕一个进程处理一个请求,有时也得复用,因为第一,它可能会允许用户输入一些交互式数据的(接收用户输入的数据,本地键盘上的输入的数据),,第二,它还得处理网络连接,网络IO,,,,,,,,,,,只要一个进程需要处理多个IO的时候,必须得复用
4)signal driven I/O (SIGIO): 信号驱动的IO 信号通知的IO
5)asynchronous I/O (aio): 异步IO
四种IO的组合
一个阻塞式的IO,可以同步方式执行,也可以异步方式执行,
而一个非阻塞IO,可以同步方式执行,也可以异步方式执行,
同步阻塞IO,它们就是以读写方式,系统调用来,完成交互 引起请求进程阻塞(在我完成之前,你不能干别的事),直到I/O完成
异步阻塞IO,叫做IO复用,,多路复用
同步非阻塞IO,使用读写方式,但是属于O_NUNBLOCK这种类型来完成???????????
异步非阻塞IO,就是AIO,异步IO,异步非阻塞IO 不导致请求进程阻塞
只要是同步IO,就意味着它必须通过read/write这种模型来完成IO操作的,非阻塞的话,它包含了两种类型,多路复用和异步IO,
阻塞的概念,如下图,左端为客户端,右端为服务端,其中一方向另外一方发起了IO请求,(这个IO的完成需要经过两段,发生IO请求一定是通过系统调用实现的),所以内核中的第一段要完成从磁盘中复制数据到内核的buffer当中,第二段是将内核的buffer中的数据复制到调用者自己的进程地址空间里面去,等第二段完成了,才能响应给进程了,,,,,,,,,,在此过程中,我们的进程一直处于等待状态(被阻塞了), 在阻塞期间,进程不能干别的事了,
非阻塞的概念,工作模型差不多,只不过在非阻塞下,在内核数据没准备好的时候,我们的进程一直处于询问状态,,,,无论如何,从IO设备上复制到内核空间当中这一段时间,我们内核中的数据没有准备好,不能告诉用户进程数据已经准备好了,可以复制了,,,,非阻塞指的是压根不知道,buffer中的数据已经准备好了,进程不停地询问系统,buffer中的数据有没有准备,系统也不停的回复,buffer中的数据有没有准备好,当回复buffer中的数据准备好了之后,进程就不询问系统了,,,,此时等buffer中的数据复制到用户空间时,用户进程就可使用数据返回给客户了, 由下图,第一段是非阻塞的(异步IO询问阶段,是否从IO复制到buffer,,,,是异步的,不知道buffer数据准备好了没有,也不知道啥时候准备好,,,,就像别人跟你说,寄了一封信,如果邮递员不负责往你家里送的话,跟你说过一段时间去取,于是只能每天去询问一下,是否信到了),第二段仍然是阻塞的(同步IO等待数据从buffer复制到用户进程空间,,,某一天,邮递员跟你说,信到了,要等分拣给你,于是你等一会儿,这个等待是需要的,等邮递员找到你的信,然后你才可以拿到信走了,)
之所以称为异步,那是因为我们并不知道对方什么时候准备好,我们什么时候能拿到,而同步的时候,意味着我一直盯着你,你啥时候好了,跟我说一声,
同步:是闲等,性能好
异步:是忙等(一会儿问一遍,一会儿再问一遍,不知啥时候好,一旦准备好了,才告诉我),性能差,,一般没有模型使用这种异步的方式,(异步非阻塞的方式)???????
但是异步非阻塞也可以用别的机制来增强其功能的,
如下图图一,,,,,,IO复用模型,异步阻塞,我们通过系统调用从内核向磁盘请求数据,在等待数据完成的时候,我们要处于阻塞状态,等一会儿数据准备好了,告诉我们数据OK了,而后,我们再次发起系统调用,(像刚才同步的时候,整个过程全是等待的)(而这里IO复用模型,就是整个过程都是异步的,如下图,两段是分开执行的)(阻塞模式下,这里我们只发起一个系统调用,等待数据完成了就行了)(IO复用模型下,要发起两次系统调用,第一次系统调用,就是数据从磁盘到内核缓冲区,当内核缓冲区OK了,自己再重新发起一个系统调用,再阻塞,又阻塞了一次,我们把整个过程分成了两段,每一段是独立执行的,,,而且每一段都是阻塞的,,,,如下图图二,这就称为IO复用),,,当别人通知给我数据准备完成了,我是不是可以等一会儿再去复制数据,所以中间并不是数据一准备完成就立即复制,而是等待进程发起请求,我这边需要数据了,内核那边才复制数据给进程空间,,,,所以这两段并不是一气呵成的,所以称为异步的,,,,整体来讲称为异步的,而每一段我们都要等待对方完成,所以我们又称为阻塞的,所以这种称为异步阻塞模型,称为IO复用,,,,,,,,,,,,,
图一
图二
如下图,为什么能IO复用呢?当数据准备好了的过程中,不用等待了,进程还可以请求别的IO,而且是异步的方式请求,等第二个请求完成了,我再把第一次请求的IO的从内核复制到用户空间,,,,,,因为一个事件的完成,有可能需要多个IO同时完成才行,
信号通知的IO 应该也叫事件驱动吧
信号通知的IO,如下图,与IO复用模型相关的,请求仍然是各自独立的,当发起磁盘IO请求以后,第一段不再阻塞了,,,,当内核空间的数据准备好之后,通知给进程,(假如是非阻塞,是一遍一遍的去问,意味着忙等,意味着要浪费大量的系统资源来查看文件描述符是不是准备成功的,,,,这里我们不用忙等了,进行通知了,如果有别的用户来需要连接的话,你该咋处理就咋处理,,,,当此前用户请求的页面的数据已经在内核空间准备好了,已经在内核缓冲区了,这时就需要复制到进程地址空间里面去了,此时就向进程发起通告,告诉进程,数据准备完成,,,,,数据准备完成怎么办?)(在同步IO模型下,数据一旦准备完成了,由内核自己直接把数据放到进程的地址空间里面去了,,,,)(而异步指的是,数据在内核中准备完成后,它不是内核直接复制给进程,而是,进程自己再发一次系统调用,让进程主导这个复制数据的,,,,这两段是分开进行,所以称为异步)(对于事件通知来讲,它是异步的,而且第一段还是非阻塞的,只不过这种非阻塞,不需要用户进程每一次来轮询向内核那边问一遍是否已经把数据从磁盘复制到内核缓冲区了,而是内核缓冲区的数据一旦准备好,就通知给用户进程了,,,,通知后,当用户进程需要的时候,就向内核发起系统调用,把数据从内核缓存复制到用户进程空间,,数据复制完了,用户进程就可以使用了,,第二段依然是阻塞的,,,用户进程发起系统数据复制了(从内核到用户进程),当复制(从内核到用户进程)完之前,肯定是无法使用这个数据的.)所以它只是解决了让IO复用的第一段不用阻塞了,让异步模型的第一段不用再轮询询问了,,,,,,这就是事件驱动,
如下图,,,因此在信号完成之前, 在信号完成后,,由内核向用户进程发了通知,,因此称为事件驱动IO,,,,,,,,为啥叫事件驱动?是因为准备好这个数据的事已经OK了,即事件来驱动.....通知几次?通知一次,对方没看到,再通知一次,对方仍然没有看到............这叫通知的水平触发, ,,只通知一次,不管对方有没有看到,这叫边缘触发.,,边缘触发性能好,,,,,,,,,这就是事件驱动的IO
多路复用的性能好,还是事件驱动的性能好,IO复用机制简单,但是性能要差点,因为我们的性能要阻塞的,两段都得阻塞,,两段阻塞过程中间,是异步的,但是两个阻塞期间是什么事也没有做,下图是多路复用
,,,,,,,如下图,而对于事件驱动来讲,当内核的buffer区没到从磁盘上复制完成之前,即内核中没有通知给进程之前,进程只要在之前发起了请求,那么就可以干任何事,在发起请求之后,留给内核一个联系方式,告诉内核,当buffer区已经从磁盘上复制过来后,打电话给进程,,,,,,即函数的回调机制,下图是事件驱动
进程第一次发起请求,并给内核留下个联系方式,等待对方buffer被磁盘复制完成之后,通过回调进行通知,,,,,信号驱动从根本上来讲,也是一种IO复用,叫事件驱动IO, event-driven IO (事件驱动的意思,就是信号驱动??????)
如下图,,,,,,,,异步IO,非阻塞,又异步,,,,,,,,,复用指的是异步阻塞的,要么是两段都阻塞(只不过是各自阻塞),要么是第二段阻塞,,,,,,,,,,异步IO指的是两端都不阻塞,而且还是异步的
异步IO,两段都不阻塞了,当我们内核那边数据准备好了,数据已经复制到内核缓冲区了,也由内核缓冲区送到进程的地址空间了,,,,但是 进程却没有在相应的地方等着,进程自己怎么知道数据已经复制完成了?(营业员把饭端到客户前,客户自然可以看到,但是这个道理,进程不懂),,,一个进程放到cpu上执行之前,所有的操作跟进程是没有关系的,它属于睡眠状态的,它是趴在那儿睡着等着,必须向进程通知,向进程发起了一个信号,比如说唤醒一个进程的信号,(把饭端到顾客的面前,敲下桌子,饭好了),,,,这种信号机制与事件驱动不太一样,因为事件驱动是数据从磁盘复制到内核缓冲区,进行通知,这个事件,是在内核中完成的事件,所以我们称为事件驱动(此时是中场通知),,,,,而我们这里异步IO的通知是整个过程都已经数据已复制到进程的内存空间,所以当我们的数据完全准备好之后,要通知给进程......(此时是终场通知)
各种IO模型比较,如下图
左一: 同步阻塞,发起请求,一直处于阻塞,等啊等,,等到完成就结束了
右一: 异步IO,发起请求,留个联系方式,啥也不干,等到最后,,数据全部准备好了,发通知给我,让我回来取数据
左二: 同步非阻塞,发起请求之后,该干嘛干嘛,但是不知道数据准备完成与否,于是check,check,check,一旦发现数据完全到内核空间了,在数据由内核空间到进程的地址空间的这一段,仍然是阻塞的
左三: IO多路复用,过程是两段式的,第一段阻塞,第二段阻塞,只不过两个阻塞之间,进程可以自行决定什么时候发起系统调用.
右二:事件通知,跟IO复用一样,只不过第一段,留下联系方式,该干嘛干嘛,中间可以处理别的事,只不过当第一段结束,即数据复制到内核缓冲区时,通知给进程,第二段仍然是阻塞的
web工作模型倒底如何工作的,见下图
当一个用户请求进来之后,请求是不能断开的,一直处于工作状态,(一断开,数据就不能回去了),所以用户请求在网络上一定是阻塞的,我们找了一个进程来给用户请求响应,这个进程怎么响应给这个用户?
见下图,如果这个进程工作在同步阻塞模型下,意味着 它要向内核发起系统调用,内核负责将数据从磁盘上读到内核的缓冲区里面来,所以它发起系统调用,意味着监控着一个内核缓冲区了(内核内存),所以数据从磁盘到内核缓冲区,从内核缓冲区到进程的内存空间,是两段时间,进程都要等待,进程不能响应别的请求了,这就叫同步阻塞
见下图,如果这个进程工作在异步阻塞模型下,意味着 第一段复制过程,不停的在询问,?????????第二段复制过程,又要阻塞?????????????
见下图,我们能不能让异步阻塞的模型下,让它同时响应两个请求的?
第一段,发起调用,首先自己一定是发起系统调用,之后,第一段是非阻塞的,进程可以干别的事,同时进程还可以继续check,check,check,检查,检查,检查,可以同时再接入一个用户请求,如果第二个请求的是别的内容,就会发起第二个io,还得再检查,检查,检查,检查通过了,又要阻塞,,,,假如两个检查都通过了,进程两个io都会阻塞?当有一个io完成后,要立即处理的,问题是进程已经阻塞在第一个io上了,第二个io怎么办?内核等待你,等过了半天才通知进程,你的io完成了吗?,,,,,,,,,或者是第一个io完成了??????也阻塞了,最后,数据从内存复制到进程空间了,第二个io检查,检查,检查,io完成了,再复制,也行,,,,,,,,,,所以第一个io完成,于是阻塞了,阻塞的过程中不能干别的事了,当第一个io阻塞完成了,数据已经复制完成了,然后再检查第二个,又进入第二个,然后检查,检查,完成了,把数据从内核缓冲区复制到进程空间,此时发觉这个进程太忙了,,如果有太多的请求,比如5个请求的话,这个进程要忙死了,这种模式,性能太差了,这种模式,的性能,就算是响应一个请求,性能都不如同步阻塞的性能好,所以这种模型几乎没有人用
io复用,见下图,
两段都阻塞,第一段阻塞好了之后,通知一下,第二段又阻塞, 就两个阻塞之间的很小的功夫,这个很小的功夫到底多长,可以自己决定,因为第二段怎么调用,进程自己看着办,,,,,中间空闲时间本来就不多,我用一个进程响应多个请求,不行,但是可以实现多个进程响应多个请求,
因为我们的主进程监听在套接字上,随时接受用户请求进来,接收完以后,我们就分配一个新的进程来处理这一个请求,这一个请求只需要完成这一个IO就行了,这样子,就会很简单了,,,,,,所以在第二种模型下??????,由于我们采用了IO复用这种机制,所以每一个进程仍然是只用一个进程响应一个用户请求,,,,我们仍然可以使用一个进程响应多个用户请求,但是性能会非常非常非常差,因为它跟异步差不多, 两段都要阻塞,阻塞跟忙等相比,还不如忙等灵活,,,,,,,,,
apache在io复用模型下是怎么工作的?它有一个主进程,有N个子进程,所以它也可以实现响应多个请求,只不过借助于多个子进程来响应多个用户请求,而且利用了IO复用模型,
而在IO复用模型下,我们有一个常用的函数,完成IO复用的,select()函数,见下图,任何一个进程要想它的内核给它准备了这个缓冲区是否准备完成了,由于第一段是阻塞,所以它要随时监控着缓冲区是不是准备完成了,而这个缓冲区可以理解成为了等待完成这里需要打开的文件,因为我们需要去read这样一个内存缓冲区,read这个缓冲区,这个缓冲区准备好了,就会返回给我们一个准备好的状态,所以这中间需要打开一个文件描述符,,,如果这一个进程打开了多个文件呢,意味着需要同时打开多个文件描述符,,,(事实上真正的场景当中,每一个资源都是一个独立的进程来完成,所以不用考虑太复杂),,,,,,,,,,,,,我们的父进程需要生成好多子进程,每一个子进程响应一个请求,每一个子进程都要打开一个文件,子进程都要属于父进程的,而一般来讲,我们的父进程自身对于select而言最多只支持,它是基于这种模式来完成多路复用的,因为这正是多路复用的功能,,,,要想实现多路复用这种方式,就必须使用select或poll这种机制,select 默认只支持打开1024个文件,所以最多只有1024个子进程进行响应了,所以最多只接受1024个并发连接进来,,,,poll跟select一样,但是poll没有文件上限的限制,,,,,尽管如此,由于poll的工作机制跟select一样,它每一段都阻塞,第二段再复制,还得阻塞,只要阻塞,意味着我们的服务器上有大量资源处于等待状态,就算它没有上限,但是它的性能依然是差,跟select差不多,(select为什么要限定1024,因为超过1024所以性能就太太太差了),,,,,,,,,,这就是IO复用的机制
见下图,事件驱动机制,假设一个进程,用户请求来了,我们进程这边向内核发起请求,并告诉内核,我们进程留个电话号码,这是哪个请求的,我们进程此时可以干别的事了,,如果第二个用户请求进来,这个进程又接进一个请求,如果第二个请求访问文件,向内核又发起一个新的请求,又留了一个联系方式,...第三个用户请求,第四个用户请求,都可以这么做,,,,,,,当内核准备好了第一个请求的数据,它只需要向第一个联系方式发起一个回调机制,告诉你,这个完成了,接下来进程可以响应用户请求了,,,,(当然这个进程自己内部得知道,它留的哪个联系方式,准备好的文件属于哪个请求的,要建立这种关联关系),,,,,,,,,,,,基于事件驱动的这种IO复用模型,可以完成一个线程(一个进程)响应多个请求了,,,,,一个线程响应多个请求,当我们负责响应每个请求的时候,不需要线程切换,,,,,,,,,,,,(一个进程,多个线程)(每个线程都是一个独立的执行实体,每一个线程响应一个请求,当某一个请求准备好了,我们要切换到请求对应的线程去工作,),,,,,,,,,,,,现在的问题是我们一个线程响应多个请求了,第一个请求的数据还没准备好了的时候,处理第二个请求了,处理第二个请求的过程当中,第一个请求的数据准备好了,这次回来响应第一个请求的时候,不需要线程切换的,为什么不需要切换呢?是因为它使用了事件驱动的机制,,,,,,,,,,,事件驱动这种模型,select,poll都是实现不了的,在linux上epoll,在sorias上/dev/poll,在frebsd上叫kqueue,这三种机制,都实现了事件驱动的IO复用,,,,,在事件驱动的IO复用下,它是一个线程(强调一个线程,怕被理解成进程内部可能有多个线程的,实际上不是这样子的),一个彻底的线程,响应了多个请求,在自己内部,它维持了有多个连接,而且每一个连接请求的资源,都是可以独立去请求的,只不过某一个资源准备的过程中(还是准备好之后????????)需要阻塞一下(第二段阻塞),将数据复制回来,(第一段不阻塞,只要告诉电话号码就行了,但是告诉我哪一段准备好以后,第二段仍然是需要阻塞的,,,第二段很快,因为它是从内核缓冲区内存空间复制到线程的内存空间,这是从内存到内存的),,,,,,,,,,如果我们的内存干脆把缓冲区共享给这个进程,不用复制了,直接拿过来用,速度会更快,这种机制叫内存间的映射(内存间的共享映射机制),并不是mmap这种机制,mmap内存映射是另外一种机制,,,,,,,,,,
我们为了访问一个文件,必须要把这个文件的数据流读到内存里面来,先读到内存缓冲区,再给进程缓冲区, 如果两者之间共享内存,速度会很快,
什么叫内存映射mmap: 第一段,文件从硬盘到内存到内核的缓冲区里面,这个过程是要复制过去的,这整个的数据要流动一次才行,内存映射指的是这数据是不用复制了,直接把这个文件结构里面的所有数据映射进内存,(复制是拿一份放过去,映射指的是不拿了,只是在它俩之间建立了关联关系),当访问的时候,直接去文件中取,这就叫内存映射,,,这种方法更快,我会立即告诉你好了,(只要连接关联关系一建立,数据不用复制一遍,数据准备好了,来取吧),,我们工作后主要的服务器是web,nginx对这些机制都支持
异步IO,如下图,进程需要复制数据,向内核发起发起系统请求了,发起系统请求后直接留了一个电话,说把数据完全复制到进程空间后,再给我打电话,所以进程发起系统请求后,该干嘛干嘛去,,此时数据从硬盘到内核缓冲区,再从内核缓冲区到进程缓冲区,都准备好了,再告诉进程,OK了,唤醒进程,进程就可以使用准备好的数据响应客户端了,,,,,,,,异步IO实现起来比较复杂,nginx支持磁盘的异步IO,这种方式对我们进程占用的资源的会更少,(若是事件驱动模型,第二段是需要等待的,而在异步IO下,是不阻塞的),,,,但是异步IO是非常复杂的,nginx支持磁盘的异步IO,但是对网络的异步IO是不支持的,网络IO是一直存在的,(IO事实上是分两个的,一个是网络IO,另一个是磁盘IO),,nginx的最大好处在于
nginx的最大好处在于,如下图,支持文件系统的AIO,mmap内存映射,event-driven事件驱动
http://nginx.org/ 官方站点
nginx不像传统的服务器,并不依赖于一个线程响应一个请求,它使用了事件驱动异步的架构,比起传统意义上使用select模型的apache来讲,nginx所能够接受的连接数大得多,它能够处理的并发连接数相当大,基于这种机制,它解决了c10k的问题
有人做过测试,单机nginx在纯静态的内容的前提下,并发连接数可达三两万,三五万,30k了,而 apache在prefork模型下,1024个,多于1024个拒绝响应了,apache在worker模型下,,,,,,
httpd:
MPM: 有三种模型
prefork: 一个进程响应一个请求,最多 1024个
worker: 一个线程响应一个请求,多进程,一个进程生成多个线程,仍然是线程处理机制,仍然受限于select,性能上比prefork并没有什么改进,只不过是利用了线程机制而已,它的进程切换数少了一些而己,并发连接数目并没有提升,
event:基于事件驱动,也借助于epoll方式,比prefork,worker要好一些,但是由于httpd有了很多很多的模块,它不像nginx只有1M,,,所以它比nginx要重量级得多,中间处理机制要复杂得多,之所有有这么多机制,它要提供丰富的特性的,,,所以它的性能上仍然比nginx差,,,当然nginx与apache不能比的地方在于nginx的功能太单一了,太简单了,众多apache所支持的特性和功能,nginx都不支持,,,,,,,,,所以到今天为止,httpd仍然占据很高的市场份额,但nginx在不断的蚕食它,
现在很多应用,大家很少把nginx拿来当web服务器,因为它的特性不够丰富,而基于一个线程响应多个请求,固然很好,但有一个问题,如果一个线程崩溃了,n个请求都崩溃了,,,(假如一个进程响应一个请求,一个进程崩溃了,不影响其它用户,.所以apache的preform模型下要稳定的多得多)
因此,很多人拿nginx做反向代理,,跟mysql proxy一样,请求mysql proxy,但mysql proxy不是mysql服务器,但是它却可以去响应mysql用户的请求,你可以连进mysql proxy,mysql proxy 可以工作在 3306端口,它不只过把用户的请求给它透明的(前面的用户并不知道)转发到后端的mysql服务器上,如下图
web的代理也是一个道理,nginx就是一个web代理服务器,它不像mysql proxy,nginx只能代理web应用,只能解析httpd协议,这种代理称为反向代理,还有一种代理叫正向代理,
如下图,通常把nginx做成反向代理,,,,apache做成web server,如果提供动态内容的话,比如提供lamp平台,
在后端安装php模块,再往后端提供mysql(可以做主从,主从要读写分离,读写分离可以在前面加个mysql proxy),由mysql proxy读写分离至后端的mysql上,,静态的web页面应该在哪里? 第一种方式:静态,动态内容都放到apache上,这两个apache的内容都一样(文件同步,inotify+async,共享存储,nas或san都可以,使用san时要使用集群文件系统,),,,,,如果网页内容不允许被修改,也不允许用户上传附件,但是用户可以注册,发贴,发贴的内容都保存在mysql里面了,,,,,,因为网页内容不允许修改,直接放两份,用不着共享存储了,,,,,,apache对静态内容的响应其实不如nginx,apache既处理动态又处理静态,有点吃力,此时我们可以静态的放在nginx前端上,如果我们的访问量并发量很大的话,nginx既是web服务器,又是反向代理,很累,所以可以专门放第二个nginx服务器,所有静态内容都从专门的第二个nginx上读,所有动态内容都从这两个apache上面读,,,第一个nginx本来就是web的反向代理,一定能够理解用户的请求的,于是在第一个nginx上做一些简单的配置,如果请求的是php页面,转到两个 apache 上面,如果请求的是jpg,gif,jpeg,png等图片或css样式表,都转到第二个nginx上,,,用户请求的时候,并不关心是从哪个服务器(apache还是nginx)来的,最终通过浏览器合并,都是同一个页面而已,只不过(两个apache和nginx)的页面最好要保持一致,页面文件都一样,,如果我们需要用户上传附件的话,也可以了,用户上传的附件都是静态的内容,我们一般不允许用户上传脚本,(上传脚本会威胁到网站的安全),只允许用户上传压缩文件或图片文件(事实上也是可能产生漏洞的洞,所以让用户上传是不安全的做法,但是作为论坛来讲,通常是需要用户上传的)等,我们专门找个服务器,让用户上传,这台服务器可以工作在web协议下让用户去获取,工作在ftp协议下让用户去上传,,,,上传的时候看起来在一个页面上上传,事实上上传的内容可能放到另外一台服务器上去了,就是因为它能够解析http协议,,,,放在lvs director上,是不能理解哪些是动态请求,哪些是静态请求的,最多只是知道是对web服务的请求,所以放在director上,高级功能的设定上就不具备了,,,,,,,,,正是因为web的反向代理,它能够精确理解用户所正在执行的协议,所以它能够做出向用户请求内容的地址的重写,资源的重定向等操作,还可以实现url重写的(URL rewrite),,(明明请求的是一个站点上的,可以重定向到另个一个站点上) 这就是反向代理的好处,,,,,,,,,事实上apache也支持反向代理,讲到tomcat集群的时候是用到apache的反向代理功能的,,,,,,,,,,,,,无论哪一种web服务器,对静态内容的响应都速度很快,对于动态内容来讲,是要执行脚本的,在你的当前主机上,可能在cpu上要转n圈,才能生成一个结果,生成一个html文档,才能返回出去,,,,所以动态内容都非常慢,
如下图比如一个4G内存,两颗cpu的一个服务器,一般来讲,响应一个动态内容(平均的逻辑),一秒钟响应1000个左右就不错了,而对于静态内容的请求,而对于静态内容的请求,比如使用一个nginx,响应一个不太大的图片(假假设5K),一秒钟响应5000-10000个并发应该是没有问题的,这个主要取决于带宽,而不是取决于服务器的工作能力,也取决于磁盘IO的速度,,,,,,动态内容执行的速度非常慢,如果用户请求的内容大部分是动态的,我们把图片等许多都使用了静态的方式来响应了,我们的网站仍然比较慢,,,,就算是动态内容,比如浏览淘宝的一个店铺的页面,我们去看某一个商品,这商品是用户刚添加的,必定是添加在数据库里面,这页面是动态生成的,生成以后,如果不删这个商品,而且没怎么修改,这个页面第一个用户访问,第二个用户访问,第三个用户访问..............它们生成的结果是一样的,所以如果生成的内容没有做任何修改,我们没有必要一次一次的重复生成,我们可以将生成的结果缓存下来,以后只要不改变,我就从缓存中取,速度会快很多,生成以后,缓存下来,直接响应,就是静态内容了,,,,,,,,,,静态内容比动态内容要快得多,所以我们可以加缓存服务器,它能够缓存web对象,根据web服务器的数量,看加几个,,假设web服务器很多,web请求量非常大,于是我们加了两台缓存服务器,假设缓存中没有,由缓存代为向对应的应用程序服务器发请求,如下图,每台缓存服务器指向两个apache服务器,本来每一个apache服务器上它要处理的连接数多达2千个,因为服务器太忙,动态服务每秒钟并发8000个,四个apache,就算负载均衡,每个apache上也得2000个,加了缓存服务器以后,只要缓存策略设计的足够好,而且后端的这些动态内容变化的频率(店铺就算修改,它的修改量也不会大)不大,所以我们能够缓存下来,再次被请求的时候,它的命中率应该是非常高的,只要缓存空间足够大,可以有效的降低数倍(只要策略设计的足够好,降低10倍都没有问题的)的后端用户请求,,,,本来每个apache上响应2000个,我们只需要处理200个就可以完成所有的问题了..............由于缓存本身都是静态的, 所以它的响应速度非常快,所以web服务器的性能大大提升了,,,,,我们这里的web服务器有4台,我们的web服务器有时可以多达几十台,我们这里有2个缓存服务器,我们的缓存服务器也可以多增加一些,,,,,,,,,,,,,当用户请求的内容在缓存服务器中找不到的话,请求就要向后端服务器转发,如果用户的请求量太大太大,并发请求量达到了5万,每个请求需要2秒来完成,一天能完成多少请求?5万*86400/2,,20亿个请求,(这只是理论计算,带宽有没有那么大,很难说,)假设服务器端接受的请求量很大了,后端的mysql实在是难以为继,因为我们的读取量很大,虽然前端使用缓存服务器了,但是有些内容需要更新,有些内容缓存命中不了的时候,仍然需要到后端web服务器中去,后台的几个web服务器需要不停的到mysql服务器上读数据
读写的比例大概是5比1,如下图,mysql服务器实在抗不住,mysql本身对读写的请求量在没有使用连接线程池的前提下,一个mysql服务器使用innodb引擎(或myisam引擎)就算硬件足够强,它能连接进来2000个就顶天了,事实上达到1000个,性能就急剧下降了,,,数据连接与前端的web连接是两码事,因为数据连接是要发送大量数据的,一个select语句可能就要发一堆数据,(select * from 表,表里面有十万行数据,这需要太多的数据发送,),,,所以mysql的并发能力,与前端的web服务器,尤其是静态页面的并发能力,不可同日而语的,,,,因为大量的请求是读请求,我们可以使用一主多从的模型,(读很多,写能抗得住,读抗不住了,我们加上几个mysql从服务器)(虽然是加了几个从服务器,但是每一次的查询,直接从数据库返回,性能很差啊)
如下图,mysql服务器要执行一次查询,一个查询没问题,两个查询并行呢?三个查询并行呢?五十个查询并行呢?五百个查询并行呢?每一个select语句,它要查询数据,要有磁盘io,要有cpu占据大量的时间周期,查询量非常大,磁盘io非常慢,有时,一个大的mysql的查询需要几秒钟,(小的mysql查询可能0.0几秒就能完成了),,并发量大的情况下,磁盘io一并行的话,各自都受影响,,,所以如果每一次查询都要从磁盘io直接返回,性能会很差,
加缓存,应该会很好吧,mysql自身有缓存,但是第一次查的时候,在第一个mysql从服务器上,第二次查的时候,在第二个mysql从服务器上,仍然效果不佳,而且mysql自身的缓存能力,在缓存管理上,第一, mysql既要负责查询,又要负责缓存,mysql的运行是受到影响的,,,,而且缓存本身需要不停的分配内存,回收内存,一个组织忙n件事,实在忙不过来, 所以在几个mysql从服务器上又增加缓存memcached服务器,,,, 我们已经读写分离了,,,memcached没有的时候,请求到mysql从服务器上取,,,mysql从服务器非常多的时候,要加一个负载均衡器,,,我们的应用程序到memcached中看,没有数据,此时请求从mysql代理转向到负载均衡器,负载均衡器查询mysql数据库,查了之后,我们的应用程序指挥着,应用程序自己将查询的数据缓存在memcached当中,然后返回去向客户端响应,,,,第二个服务器发起查询语句,(每一个程序在查询之前,不会直接交给负载均衡器的,先去查询memecached,,memcached不是工作中mysql proxy后端的,而是工作在应用程序的后端的)(memcached可以理解成与web服务器是同一个交换机上的主机),,,,,,,,当每一个php需要查询数据的时候,不会先去向mysql proxy提交,而是提交给memcached,memcached如果没有,php就去请求mysql proxy,mysql proxy发现是读,交给负载均衡器,负载均衡器挑一个mysql服务器,结果逐层返回,返回以后,先缓存到memcached,再响应.
如下图,php到php4.0以后,php的代码要先编译成opcode来执行的,如果用户每一次请求都要编译执行,速度会很慢,opcode也是可以缓存的,靠xcache缓存的,当然xcache不需要一台服务器了,只要在php安装的时候,安装成php模块,,,,,最前端,第一个反向代理nginx,后面系统很大,但是都从第一个反向代理nginx进来,怎么办?高可用,同理,mysql proxy,高可用,,,memcached不用高可用,无非是缓存,缓存坏就坏了,没有太大影响,(memcached无法高可用,因为memcached不支持节点通信的,你可以弄两个节点,但是如果一个坏了,缓存重新定向到第二个memcached,重新生成,,,但是缓存数据是没办法共享的,)
如下图,,,,,,apached在我们这里只处理动态内容,不处理静态内容,非要apache吗?干脆把php做成服务器就可以,把fast-cgi自己做成服务器就可以了,而且fast-cgi这种方式它在实现动态进程的生成和撤销上,可以实现自我管理的,,,,所以可以改一改上面的结构图,,,,前端,仍然是反向代理,请求过来,如果是静态内容,可以让其定向到内部的一个专门提供静态内容的服务器上来,,如果是动态内容的请求,可以交给 varnish(或者是缓存服务器,varnish就是缓存服务器,缓存服务器是用来缓存动态生成结果的,动态生成的结果就是静态内容),这两个varnish负责将用户的请求转发到多个动态服务器上去,,,这两个varnish将请求转发到多个服务器上的时候,是越平均越好,(可以使用lvs负载均衡,或者是通过交换机直接往上连也行,只不过无法实现很好的负载均衡效果罢了)(但是varnish自己也可以实现负载均衡能力的,只不过他的负载均衡算法没有lvs那么好)(haproxy也可以实现很好的负载均衡效果,haproxy也是工作在七层的,将web请求负载均衡至多个服务器上来),,,,,,我们下图不用haproxy,使用lvs示意吧,lvs要能够监控每个fast-cgi服务器(应用程序服务器)的健康状况,有了lvs的负载均衡器,使得每个fast-cgi服务器(应用程序服务器)负载平均了,
如下图我们假设的应用程序服务器(fast-cgi)每次最多只能响应500个用户请求, 一下子来了5000个动态请求,假如由于我们设计不善,我们的varnish的缓存命中率很低,所以这么多动态请求过来,就算负载均衡了,五个应用程序服务器,平均下来1000个,我们最多只能响应500个,到600个时,性能很差,到1000时,就直接拒绝服务了,怎么办?我们要考虑我们系统的承载能力,就算拒绝一部分用户,也不能让所有用户都不能提供服务,(我们可以做连接数限定,每个应用程序服务器上最多分多少个连接,haproxy可以支持让一个应用程序服务器上最多支持多少个连接,)(或者在varnish前面?????,或者在nginx代理的前面??????加一个队列管理器,如果是异步执行,或异步运作的话,可以加一个队列管理器,这个队列管理器将所有用户请求都接进来,由于是异步的,所以它不需要将用户所有的请求都转交给后面的real server(应用程序服务器),,,,,来了5000个,自己接进来5000个,但是只是给每个主机发500个,当应用程序服务器有了空闲,再发一些请求到应用程序服务器),,,,这个是异步消息同步的平台???,我们称为队列管理器 (rabbitmq,zeromq),队列管理器可以理解成一个连接池,所有的连接进来了,我们不是马上给你提供服务,而是先在队列管理器里面缓存下来,按照应用程序服器的能力一个个的响应(比如一下子使每个响应100个),但不能让全部5000个连接全部涌进来,,,,在异步应用场景当中,队列管理器很常用,
如下图,后面的mysql服务器是同样的道理,加memcached缓存,加读写分离,,,,最好处理的就是静态内容,在前面,一个nginx服务器不够,可以负载均衡多加几个,,,可以都是静态的,它也会有问题,比如淘宝,有无数张图片,每天用户都不停的添加图片,一天几十G,上百G,一年以后,多少T了,2年,5年以后呢?图片数据库太大了,用户请求一个图片,到图片服务器上找半天,
如下图假如文件系统有很大,能存下10T的数据,从10T的数据中找图片返回给用户,要很长时间,,,,我们的图片量虽然非常大,但是真正热门的店家就那几家,永远都是二八法则,20%的店处于活跃状态,只有20%的图片经常会被访问,所以我们的数据会有局部性,我们使用什么方式来提高我们的性能?缓存.缓存的效果是程序有局部性??????数据有局部性??????所以才能提供缓存的,所以我们在图片服务器前端加缓存,我们再加两个varnish缓存服务器,缓存服务器装上了很大的内存,使用固态硬盘,或者使用fewio这种硬盘(或者就放上500G的固态硬盘),提供64G的内存,然后将那些经常被访问的数据直接缓存在varnish上,如果varnish能够达到50%的命中率(varnish是在内存中缓存数据的,也可以在磁盘上缓存数据,看你的数据量有多大,你打算缓存多少数据,如果你的64G内存里面,如果数据量不是很大,能够60G就能够缓存了,我们完全可以在内存中缓存,否则你可以在500G的固态磁盘中缓存)无论是固态硬盘或内存,速度远远比nginx服务器的文件系统要快得多,,,,大大提高了用户缓存的命中率,实现了用户快速对某个资源的访问,,,,,,,,,,但是当我们的数据的用户量像淘宝一样多的时候,平时没有问题,一抢购,就全挂了,所以一个系统绝对是抗不住的,,,,只要我们的静态服务器缓存命中率足够高,动态服务器缓存命中率足够高,那么所有的内容都是显示的静态内容,
如下图,只要是静态,我们就可以使用地理位置法则,在全国各地机房里面,华北区,华东区,华南区,各自建一个专门的缓存服务器,把我们系统的所有数据,尤其是图片数据,各自都给它缓存到各自建一堆的varnish集群(用户的家门口),,,用户使用www点淘宝点com,去访问站点的时候,使用智能dns解析,来自于华北区的,直接解析到华北区的缓存varnish上,90%的内容从varnish本地返回,,,,抢购肯定是动态内容,所以抢购这种方式,只要打开的过程(返回图片的过程,返回页面本身的过程)是静态的,所以使得打开不再面临太大的压力,
如下图而要想抢购的话,大家把所有的请求都涌向了同一个服务器了,此时抢购平台要设计得足够,mysql 服务器集群要做得足够大,分区要做得足够好,据说淘宝上在抢购那天,mysql 每秒钟所处理的事务量tps( Transaction Per Second 每秒事务处理量 ) 达到5万多个,,,,我们一个mysql服务为了查询读操作,数据量就算不大,一个mysql服务器能接收500个并发连接,都会抗不住的,,,,,,5万个写操作啥概念?人家一个商品是有限的,你抢回后,别人就抢不到了,所以数据还得随时向前端更新的,,,此时纯粹利用mysql可能解决不了问题了,,,,所以我们需要将数据完全在前端的内存服务器中完成,在内存中执行事务,而且都是小事务,每一次抢购,抢完以后,一个事务就结束了,快速执行,快速反馈,,商品的更新,只要一个事务完成,立马让它更新,3个抢下剩2个,2个抢下剩1个,,,,这个时候,我们需要借助于能够工作在内存中支持事务的服务器,而且对于整个约束法则要求不是特别严格的场景,像NoSQL,就派上用场了,,刚刚我们提供到memcached,(memcached本身不是NoSQL,但是memcached可以提供这个功能,memcached是提供缓存的,不能保存数据,)像NoSQL中有些能够保存数据,像redis,mongodb,等等,尤其像redis,作为程序计算器的情况下,(比如像微博,新浪微博,每天太多人同时发微博,微博有多少人转载了,多少人评论了,它底下是有计数的,这些计数,关联的数字,每个人评论一次,它要更新一次,数据库压力太大了,所以这些数据都不是在mysql数据库中更新的,而是在redis中更新的,redis是一个NoSQL,直接在内存中工作,但是它是可以将数据同步到硬盘上去,进行持久性存储,并且还可以将数据转存到mysql 数据库里面去),,所以我们既有持久存储法则,又有快速响应手段,,,,,所以众多计数器的更新,都使用NoSQL来实现,,,,,,对事务的支持,尤其是事务写操作比较多的时候,可以基于mongodb来实现,但是好像有人说mongodb不是特别的好,不是特别的稳定,
如下图,我们的静态图片有10T,我们要想对图片进行分析,分析图片去年增加了多少,前年增加了多少,,,对10T数据进行分析,都图片一张张都在内存中打开分析?要多长时间?有那么大内存?
还有我们的web服务用户访问都是要记录日志的,这么多服务器,访问量那么大,日志放在哪里?让每个服务器自己记录也不好(不会有助于我们此后的分析),,,,,,,所以我们可以有日志服务器,日志服务器得有多快的性能才能把来自于那么多的日志信息全都记录下来,,我们可以将日志放在mysql 数据库当中, 把mysql放在一个非常非常有着更高IO能力的硬盘上,比如few IO,它支持的事务量非常大,存下来之后,再给它导入到另外一个分布式文件系统上去(或者直接通过其它机制导入到另外一个分布式文件系统上去,有的机制可以实现,比如facebook,facebook有个日志收集器,我们让每个服务器自己独立管理日志也可以,或者独立10个日志服务器,然后在每个日志服务器上,布署一个小程序,这个程序每天把这些日志都读出来,送给另外一个服务器,而另外一个服务器是一个分布式文件系统服务器,把这些日志都存储在分布式文件系统上,过一段时间,在这个分布式文件系统上,可以运行并行处理程序,完成日志分析)(分析一下,在过去一天的抢购当中,有来自于华南的多少用户,华北的多少用户,哪个店铺最受欢迎,哪个产品交易量最大,,,这些都可以分析,,,,而这些日志,一天产生的日志量,有可能达到数百个G,甚至上T的),,,,,,,,,,所以此时你想使用grep awk 去分析的话,分析个半年,估计也分析不出来,所以hadoop 就上场了,hadoop可以实现对这些数据做批处理的,但是hadoop的分析是异步的, 而且hadoop两段支持的执行过程也都是各自分开执行的??????所以它是个批处理平台,速度可能比较慢,它依赖于整个系统中的短板,假如这个系统需要10个资源,而最慢的那个资源将决定了整个hadoop的运行速度,所以hadoop在实现实时分析上可能性能不够好,有些地方是需要实时分析,,,,比如说立马需要一个大显示屏,给前端显示,现在有多少个用户正在在线,有多少笔交易完成了,哪个店铺正在购抢时机,,,,假如我们需要提供实时分析平台的话,实现实时数据分析,hadoop没有这种能力,hadoop原生是没有这种能力的,,,,hadoop是谷歌发表出来论文以后,开源界利用谷歌的论文才建立出来的一个开源平台,,,,,谷歌内部就有实时分析平台了,开源界也提供了实时分析解决方案,,,,,,我们的数据量越来越大,如果一个机器可以实现machine learning(机器学习)
如果一个机器可以实现machine learning(机器学习),他使用很好的机器学习算法,从人类所生的知识库里面,分析人类的各种行为特征,产生自己能够作智能决策,作智能思维的话,天网会降临,机器可以基于人的神经网络,人手疼一下,会作出反应,靠神经末稍来实现传导信息的,现在所构建的大型的神经网络,像hadoop集群,能够使用几万个节点,能实现海量数据处理,能实现学习了知识之后,能够进化,只要算法设计的足够好,利用神经网络这种方式,像人掌握知识一样,可以进化的,,,
有强大的处理能力,有很好的机器学习算法,能够实现机器的智能决策
我们需要实时写操作的时候,而且操作量非常大,就需要内存数据库或者NoSQL,,,,,需要持久存储,需要后续分析,关系型数据库比较好,,结构化数据可以放在数据库当中,对于非结构化数据(比如日志放在mysql中,虽然可行,但是并没有人这么做,因为日志是半结构化或非结构化数据,这些数据都要放在文件系统上,将来我们要在文件系统上对数据进行分析的话,那它就不像mysql一样了,mysql可以检索出有效的数据集再分析,要放在文件系统上进行数据分析,你必须要进行全量数据提取进行分析的,这个时候,需要用到并行处理平台才能完成相关工作)随着我们网络规模增大,我们的主机数量是越来越多了,某一个主机哪天坏了,需要人工过去维修,你把某一个服务直接建立在物理服务器上,物理服务器坏的可能性能大,,,,,我们把一个机房里面所有的主机做成一个高可用集群,做成一个云平台,在云平台上开一堆的虚拟机,各种虚拟机来完成各种任务,哪一个坏了,直接把它kill掉,(进程嘛 ,就是虚拟机),然后再启用一个新虚拟机,,,或者是坏了,也没关系,支持实时迁移,所有的内容,物理机不是坏了吗?这些资源实时迁移到其它平台上去了,一大堆的虚拟机要用到共享存储,这些共享存储怎么解决?弄一大堆存储,找一个监控存储的解决方案,任何时候开一个虚拟机,自动的会找一个空间,给你开一个虚拟磁盘,然后实现虚拟机进程的启动,,,,,,,,但是hadoop不能放在虚拟机上,因为hadoop需要大量的IO,而虚拟机的IO能力很差,,,所以有些关键性的内容,对IO量,对磁盘IO量非常大的话,就不要放在虚拟机上,,,,,,,,,像前端这些返回一个页面文件,读一个页面文件,程序进行处理的,我们完全可以放在虚拟机上(一般文件的读取量毕竟要小得多得多),,比起这10T的IO量,是天壤之别,,,,,,,,,,,,,所以云 hadoop 结合起来使用,并结合mysql数据库,,,,,
如下图,mysql一大堆的读,一大堆的写,也不爽,可以把mysql放在云里面,让mysql直接提供云服务,云有三种类型,ias,pas,sas,,,我们完全可以把mysql做成sas的,直接向外提供软件服务,,,用户所有看到的都是mysql接口,数据我给你存,至于怎么存的,你不用管,,,,, 我们完全可以把整个系统模块化,后端数据库模块化,hadoop模块化,云平台模块化,web服务模块化,,,,让每一个模块需要一个人,一个团队来运作,我就是架构师,,,,,我们所涉及到的概念是非常少的,真正做架构师,经验,无论是网络,自动化运维的编程(shell python) 以及整个每一个服务本身的性能(本身的特性,适用场景,优点和缺点,要做到通盘掌握,,,才能做到架构师的层次) 到了架构师的层次,dba能力要有,自动化运维的编程能力要有,服务运维能力要有,前端运维的实际经验要有,,,,,,,,,马哥说也没什么复杂的地方,一两年以后,只要有机会接触大型的系统,你会发现,它们的结构无非是在下图所表示的系统上慢慢拓展起来的,只不过在不同的场景下,由于管理员可能熟悉的技术不一样,他们所采取的组件不同,还有根据我们的业务需求,业务运作的方式不一样,所使用起来对应的项目的产品不同而已,
如下图的网络,就依赖cdn了,把数据缓存在用户的家门口,依赖于智能dns,解析用户的请求,直达家门口那台服务器,而这样的服务器本身,它依赖的数据来自于我们后端的一个非常大的系统所生成的静态结果缓存下来的,而且这些cnd服务器彼此之间(比如来自华北区的用户,访问华北区服务器的时候,缓存上没有,而这些服务器本身还可以到其它的cdn缓存服务器上去取数据,所有的缓存服务器上都没有需要的数据,再到原始的的系统服务器上去取数据),,,所以能够实现内容路由的,我一个节点发现数据没有的话,它知道去哪儿找,到兄弟服务器,父缓存服务器,或者直接到原始服务器去找,这就叫cdn网络,cdn依赖的一个重要的前提就是智能dns,另外一个就是缓存服务器,缓存策略,缓存路由的功能,
下图的架构涉及到的一些知识,比如varnish是什么?haproxy怎么用?NoSQL有哪些(各种NoSQL的特性),hadooop怎么去构建,以及云平台如何去实施,,,我们还要回归到一台服务器上讲这些功能的性能优化