高性能高并发服务的瓶颈及突破思路_第1页
高性能高并发服务的瓶颈及突破思路_第2页
高性能高并发服务的瓶颈及突破思路_第3页
高性能高并发服务的瓶颈及突破思路_第4页
高性能高并发服务的瓶颈及突破思路_第5页
已阅读5页,还剩1页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、高性能高并发服务的瓶颈及突破思路服务的瓶颈有哪些通常来说程序的定义是算法+数据结构+数据,算法简单的理解就是一种计算方式,数据结构顾名 思义是一种存储组织数据的结构,这两者体现了程序需要用到的计算机资源涉及到CPU资源、内存资 源,而数据部分除了内存资源,往往还可能涉及到硬盘资源,甚至是彼此之间传输数据时会消耗网络 (网卡)资源。当我们搞清楚程序运行起来时涉及哪些资源后,就可以更好地分析我们的服务中哪些可 能是临界资源。所谓临界资源就是多个进程(线程)并发访问某个资源时,该资源同只能服务某个或者 某些进程(线程)。服务的瓶颈主要就是在这些临界资源上,还有一些资源原本并不是临界资源,比如 内存在

2、一开始是够的,但是因为连接数或者线程数不断的增多,最终导致其成为临界资源,其他的 CPU、磁盘、网卡其实和内存一样,在访问量增大以后一样都可能会成为瓶颈。所以怎么做到高性能 高并发的服务,简单地说就是找到服务的瓶颈,在合理的范围内尽可能的消除瓶颈或者降低瓶颈带来 的影响,再通俗一点的说就是资源总量不够就加资源,确切的说是什么资源不够就加什么资源,同时 尽量降低单次访问的资源消耗,做到在资源总量一定的情况下有能力支撑更多的访问。如何提升服务的并发及性能数据拆分图1单数据实例改成数据库集群最典型的一个临界资源就是数据库,数据库在一个大访问量的 系统中往往是最薄弱的一环,因为数据库本身的服务能力是有

3、限的,以MySQL为例,可能MySQL可以 支持的并发连接数可能也就几千个,假设是3000个,如果一个服务对其数据库的并发访问如果超过 了 3000,有部分访问可能在建立连接的时候就失败了。在这种情况下,需要考虑的是如何将数据进 行分片,引入多个MySQL实例,增加资源,如图1所示。数据库这个临界资源通过数据拆分的方式, 由原来的一个MySQL实例变成了多个MySQL实例,这种情况下数据库资源的整体并发服务能力自然提 升了,同时由于服务压力被分散,整个数据库集群表现出来的性能也会比单个数据库实例高很多。存 储类的解决思路基本是类似的,都是将数据拆分,通过引入多个存储服务实例提升整体存储服务的能

4、 力,不管对于SQL类的还是NoSQL类的或文件存储系统等都可以采用这个思路。2、服务拆分应用服务应用服务图2服务拆分应用程序自身的服务需要根据业务情况进行合理的细化,让每个服务只负责某一类功能,这个思 想其实是和微服务思想类似。一句话就是尽量合理地将服务拆分,同时有一个非常重要的原则是让拆 分以后的同类服务尽量是无状态或弱关联,这样就可以很容易进行水平扩展,如果拆分以后的同类服 务的不同实例之间本身是有一些状态引起彼此非常强的依赖,比如彼此要共享一些信息这些信息又会 彼此影响,那这种拆分可能就未必非常的合理,需要结合业务重新进行审视。当然生产环节上下游拆 分以后不同的服务彼此之间的关联又是另

5、外一种情形,因为同一个生产环节上往往是走完一个服务环 节才能进入下一个服务环节,相当于有多个串行的服务,任何一个环节的服务都有可能瓶颈,所以需 要拆分以后针对相应的服务进行单独优化,这是拆分以后服务与服务之间的关系。假设各个同类服务 本身是无状态或者弱依赖的情况下,针对应用服务进行分析,不同的应用服务不太一样,但是通常都 会涉及到内存资源以及计算资源,以受内存资源限制为例,一个应用服务能承受的连接数是有限的(连 接数受限),另外如果涉及上传下载等大量数据传输的情况网络资源很快就会成为瓶颈(网卡打满), 这种情况下最简单的方式就是同样的应用服务实例部署多份,达到水平扩展,如图2所示。实际在真 正

6、拆分的时候需要考虑具体的业务特点,比如像京东主站这种类型的网站,在用户在访问的时候除了 加载基本信息以外,还有商品图片信息、价格信息、库存信息、购物车信息以及订单信息发票信息等, 以及下单完成以后对应的分拣配送等配套的物流服务,这些都是可以拆成单独的服务,拆分以后各个 服务各司其职也能做更好的优化。服务拆分这件事情,打个不是特别恰当的比方,就好比上学时都是 学习,但是分了很多的科目,高考的时候要看总分,有些同学会有偏科的现象,有些科成绩好有些科 成绩差一点,因为分很多科目所以很容易知道自己哪科是比较强的、哪科是比较弱的,为了保证总体 分数最优,一般在弱的科目上都需要多花点精力努力提高一下分数,

7、不然总体分数不会太高。服务拆 分也是同样的道理,拆分以后可以很容易知道哪个服务是整体服务的瓶颈,针对瓶颈服务再进行重点 优化比等就可以比较容易的提升整体服务的能力。3、适当增长服务链路,尽量缩短访问链路,降低单次访问的资源消耗在大型的网站服 务方案上在各种合理拆分以后,数据拆分以及服务拆分支持扩展只是其中的一部分工作,之后还要根据需 求看看是否需要引入缓存CDN之类的服务,我把这个叫做增长服务链路,原来直接打到数据库的请求, 现在可能变成了先打到缓存再打到数据库,对整个服务链路长度来说是变长的,增长服务链路的原则 主要是将越脆弱或者说越容易成为瓶颈的资源(比如数据库)放置在链路的越末端。在增长

8、完服务链路 之后,还要尽量的缩短访问链路,比如可以在CDN层面就返回的就尽量不要继续往下走了,如果可以 在缓存层面返回的就不要去访问数据库了,尽可能地让每次的访问链路变短,可以一步解决的事情就 一步解决,可以两步解决的事情就不要走第三步,本质上是降低每次访问的资源消耗,尤其是越到链 路的末端访问资源的消耗会越大。比如获取一些产品的图片信息可以在访问链路的最前端使用CDN, 将访问尽量挡住,如果CDN上没有命中,就继续往后端访问利用nginx等反向代理将访问打到相应的 图片服务器上,而图片服务器本身又可以针对性的做一些访问优化等。比如像价格等信息比较敏感, 如果有更改可能需要立即生效需要直接访问

9、最新的数据,但是如果让访问直接打到数据库中,数据库 往往直接就打挂了,所以可以考虑在数据库之前引入redis等缓存服务,将访问打到缓存上,价格服 务系统本身保证数据库和缓存的强一致,降低对数据库的访问压力。在极端情况下,数据量虽然不是 特别大,几十台缓存机器就可以抗住,但访问量可能会非常大,可以将所有的数据都放在缓存中,如 果缓存有异常甚至都不用去访问数据库直接返回访问失败即可。因为在访问量非常大的情况下,如果 缓存挂了,访问直接打到数据库上,可能瞬间就把数据库打趴下了,所以在特定场景下可以考虑将缓 存和数据库切开,服务只访问缓存,缓存失效重新从数据库中加载数据到缓存中再对外服务也是可以 的,

10、所以在实践中是可以灵活变通的。4、小结如何提升整体服务的性能及并发,一句话概括就是: 在合理范围内尽可能的拆分,拆分以后同类服务可以通过水平扩展达到整体的高性能高并发,同时将 越脆弱的资源放置在链路的越末端,访问的时候尽量将访问链接缩短,降低每次访问的资源消耗。如何提升单机服务的性能及并发前面说的这些情况可以解决大访问量情况下的高并发问题,但是高性能最终还是要依赖单台应用 的性能,如果单台应用性能在低访问量情况下性能已经成渣了,那部署再多机器也解决不了问题,所 以接下来聊一下单台服务本身如果支持高性能高并发。1、多线程/线程池方式listenfd 二 socket。;bind。;connfd

11、= accept。;while(nread 二 readfconnfd.) !二-1) (/do somethingclose(connfd);图3版本一以TCP server为例来展开说明,最简单的一个TCP server代码,版本一示例如图3所示。这种 方式纯粹是一个示例,因为这个server启动以后只能接受一条连接,也就是只能跟一个客户端互动, 且该连接断开以后,后续就连不上了,也就是这个server只能服务一次。这个当然是不行的,于是 就有了版本二如图4所示,版本二可以一次接受一条连接,并进行一些交互处理,当这条连接全部处 理完以后才能继续下一条连接。这个server相当于是串行的,没

12、有并发可言,所以在版本二的基础 上又演化出了版本三如图5所示。Hstenfd = socket。;I bind。; while connfd = accept。: dosometing(connfd); close(connfd);图4版本二Hstenfd = socket();bind。;while (1) (connfd = accept。;/start a thread dosomething(connfd);图5版本三这其实是我们经常会接触到的一种模型,这种模型的特点是每连接每线程,MySQL 5.5 以前用的就是这种模型,这种模型的特点是当有大量连接的时候会创建大量的线程,所以往往需

13、要限 制连接总数,如果不做限制可能会出现创建了大量的线程,很快就会将内存等资源耗干。listenfd = socket。;bind。;while (1) connfd = accept。;/threadpool.dosomething(connfd);图6版本四另一个是当出现了大量的线程的时候,操作系统会有大量的cpu资源花费在线程间 的上下文切换上,导致真正给业务提供服务的cpu资源比例反倒很小。同时,考虑到大多数时候即使 有很多连接也并不代表所有的连接在同一个时刻都是活跃的,所以版本三又演化出了版本四,如图6 所示,版本四的时候是很多的连接共享一个线程池,这些线程池里的线程数是固定的,这样

14、就可以做 到线程池里的一个线程同时服务多条连接了,MySQL 5.6之后采用的就是这种方式。在绝大多数的开 发中,线程池技术就已经足够了,但是线程池在充分榨干cpu计算资源或者说提供有效计算资源方面 并不是最完美的,以一核的计算资源为例,线程池里假设有x个线程,这x个线程会被操作系统依据 具体调度策略进行调度,但是线程上下文切换本身是会消耗一定的cpu资源的,假设这部分消耗代价 是w,而实际有效服务的能力是c,那么理论上来说w+c就是总的cpu实际提供的计算资源,同时假 设一核cpu理论上提供计算资源假设为t,这个是固定的。所以就会出现一种情况,当线程池中线程 数量较少的时候并发度较低,w虽然

15、小了,但是c也是比较小的,也就是w+c 一定的情况下希望尽量的多做一些事情)来说代价就会很大。线程池的方式是雇佣固定数量的服务员, 服务的时候一个服务员服务好几个客户,可以理解为一个服务员在客户A面前站1分钟,看看A客户 是否需要服务,如果不需要就到B客户那边站1分钟,看看B客户是否需要服务,以此类推。这种情 况会比之前每个客户一个服务员的情况节省一些成本,但是还是会出现一些成本上的浪费。还有一种 模式也就是epoll的方式,相当于服务员就在总台等着,客户有需要的时候就会在桌上的呼叫器上按 一下按钮表示自己需要服务,服务员每次看一下总台显示的信息,比如一共有100个客户,一次可能 有10个客户

16、呼叫,这个服务员就会过去为这10个客户服务(假设服务每个客户的时候不会出现停顿 且可以在较短的时间内处理完),等这个服务员为这10个客户服务员完以后再重新回到总台查看哪些 客户需要服务,依此类推。在这种情况下,可能只需要一个服务员,而餐厅剩余的空间可以全部给客 户使用。nginx服务器性能非常好,也能支撑非常多的连接,其网络模型使用的就是epoll的方式, 且在实现的时候采用了多个子进程的方式,相当于同时有多个epoll在工作,充分利用了 cpu多核的 特性,所以并发及性能都会比单个epoll的方式会有更大的提升。另外Redis缓存服务器大家应该也 非常熟悉,用的也是epoll的方式,性能也是

17、非常好,通过这些现成的经典开源项目,大家就可以直 观地理解基于事件驱动这一方式在实际生产环境中的性能是非常高的,性能提升以后并发效果一般都 会随之提升。但是这种方式在实现的时候是非常考验编程功底以及逻辑严谨性,换句话编程友好性是 非常差的。因为一个完整的上下文逻辑会被切成很多片段,比如“客户端发送一个命令-服务器端接 收命令进行操作-然后返回结果”这个过程,至少会包括一个可读事件、一个可写事件,可读事件简 单地理解就是指这条命令已经发送到服务器端的tcp缓存区了,服务器去读取命令(假设一次读取完, 如果一次读取的命令不完整,可能会触发多次读事件),服务器再根据命令进行操作获取到结果,同 时注册

18、一个可写事件到epoll上,等待下一次可写事件触发以后再将结果发送出去,想象一下当有很多客户端同时来访问时,服务器就会出现一种情况会儿在处理某个客户端的读事件,一会儿在处理另外的客户端的写事件,总之都是在做一个完整访问的上下文中的一个片段,其中任何一个片段 有等待或者卡顿都将引起整个程序的阻塞。当然这个问题在多线程编程时也是同样是存在的,只不过 有时候大家习惯将线程设置成多个,有些线程阻塞了,但可能其他线程并没有在同一时刻阻塞,所以 问题不是特别严重,更严谨的做法是在多线程编程时,将线程池的数量调整到最小进行测试,如果确 实有卡顿,可以确保程序在最快的时间内出现卡顿,从而快速确认逻辑上是否有不

19、足或者缺陷,确认 这种卡顿本身是否是正常现象。3、语言层提供协程支持多线程编程的方式明显是支持了高并发,但因为整个程序线程间上下文 调度可能造成cpu的利用率不是那么高,而基于事件驱动的编程方式效果非常好的,但对编程功底要 求非常高,而且在实现的时候需要花费的时间也是最多的。所以一种比较折中的方式是考虑采用提供 协程支持的语言比如golang这种的。简单说就是语言层面抽象出了一种更轻量级的线程,一般称为 协程,在golang里又叫goroutine,这些底层最终也是需要用操作系统的线程去跑,在golang的 runtime实现时底层用到的操作系统的线程数量相对会少一点,而上层程序里可以跑很多的

20、 goroutine,这些goroutine会在语言层面进行调度,看该由哪个线程来最终执行这个goroutine。 因为goroutine之间的切换代价是远小于操作系统线程之间的切换代价,而底层用到的操作系统数量 又较少,线程间的上下文切换代价本来也会大大降低。这类语言能比其他语言的多线程方式提供更好 的并发,因为它将操作系统的线程间切换的代价在语言层面尽可能挤压到最小,同时编程复杂度大大 降低,在这类语言中上下文逻辑可以保持连贯。因为降低了线程间上下文切换的代价,而goroutine 之间的切换成本相对来说是远远小于线程间切换成本,所以cpu的有效计算能力相对来说也不会太 低,相当于可以比较

21、容易的获得了一个高并发且性能还可以的服务。4、小结如何提升单机服务的性能及并发如果对性能或者高并发的要求没有达到非常苛刻的要求,选型的时候基于事件驱动的方式可以优 先级降低一点,选择普通的多线程编程即可(其实多数场景都可以满足了),如果想单机的并发程度 更好一点,可以考虑选择有协程支持的语言,如果还嫌不够,那就将逻辑理顺,考虑采用基于事件驱 动的模式,这个在C/C+里直接用select/epoll/kevent等就可以了,在java里可以考虑采用NIO 的方式,而从这点上来说像golang这种提供协程支持的语言一般是不支持在程序层面自己实现基于 事件驱动的编程方式的。四、总结其实并没有一刀切的万能法则,大体原则是根据实际情况具体问题具体分析,找到服务瓶颈,资 源不够加资源,尽可能降低每次访问的资源消耗,整体服务每个环节尽量做到可以水平扩展,同时尽 量提高单机的有效利用率,从而确保在扛住整个服务的同时尽量降低资源消耗成本。Q&AQ1:在

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论