京东资深架构师:高性能高并发服务的瓶颈与突破思路与思考。本次分享主要包括三个部分:服务的瓶颈是什么如何提高整体服务的性能和并发如何提高单机服务的性能并发服务的瓶颈是什么简单的理解就是计算方法。顾名思义,数据结构是一种存储和组织数据的结构。这两个反映了程序需要使用的计算机资源,涉及CPU资源和内存资源。除了内存资源,数据部分还可能涉及到硬盘资源,甚至相互之间传输数据时会消耗网络(网卡)。资源。当我们弄清楚程序运行时涉及到哪些资源时,我们可以更好地分析我们的服务中哪些资源可能是关键的。所谓临界资源是指当多个进程(线程)并发访问某个资源时,该资源只能同时为一个或部分进程(线程)服务。服务的瓶颈主要在这些关键资源上,有些资源本来就不是关键资源。例如,内存一开始是充足的,但由于连接数或线程数不断增加,最终成为临界资源。其实其他的CPU、磁盘、网卡都和内存一样,可能访问次数增加后就变得一样了。瓶颈。那么如何实现高性能高并发的服务,简单来说就是找到服务的瓶颈,并在合理的范围内尽可能的消除瓶颈或者降低瓶颈的影响。简单点说就是资源总量不足的时候加资源,资源不足的时候加资源。同时尽可能降低单次访问的资源消耗,以便在资源总量一定的情况下能够支持更多的访问。如何提高整体服务性能和并发数据拆分?最典型的关键资源是数据库。数据库往往是访问量很大的系统中最薄弱的环节,因为数据库本身的服务能力是有限的。以MySQL为例,MySQL能够支持的并发连接数可能有几千,假设是3000。如果一个服务对其数据库的并发访问超过3000,那么在建立连接时可能会出现部分访问失败的情况。.在这种情况下,需要考虑的是如何对数据进行分片,引入多个MySQL实例,增加资源,如图1所示。图1:将单个数据实例改成数据库集群。通过数据拆分,将数据库的关键资源从一个MySQL实例变为多个MySQL实例。在这种情况下,数据库资源的整体并发服务能力自然得到提升。同时,由于服务压力被分散,整个数据库集群的性能将远高于单个数据库实例。存储类的解决方案基本类似。他们通过引入多个存储服务实例来拆分数据并提高整体存储服务能力。SQL、NoSQL或文件存储系统都可以采用这种想法。.服务拆分应用自身的服务需要根据业务情况进行合理细化,让每个服务只负责某一类功能。这个思路类似于微服务的思路。一句话,就是尽可能合理的拆分服务。同时,一个很重要的原则就是拆分后类似的服务尽可能做到无状态或者弱关联,这样可以方便的进行水平扩展。如果拆分后的同一个服务的不同实例之间存在一些相互依赖性很强的状态,比如相互共享一些信息,这些信息会相互影响,那么这种拆分可能不是很合理。重新审视业务。当然,生产环节的上下游拆分后,不同服务之间的关系又是另外一种情况,因为同一个生产环节往往完成一个服务环节后才进入下一个服务环节。相当于有多个串行服务,服务的任何一个环节都可能出现瓶颈,需要针对相应的服务分别进行拆分和优化。这是拆分后的服务之间的关系。假设每个同类服务都是无状态或弱依赖的,分析应用服务。不同的应用服务不尽相同,但通常会涉及到内存资源和计算资源。以内存资源的限制为例,一个应用服务所能承受的连接数是有限的(连接数有限)。另外,如果涉及到上传、下载等大量数据传输,网络资源很快就会成为瓶颈(网卡满了。)。在这种情况下,最简单的方式就是部署同一个应用服务实例的多个副本,实现横向扩展,如图2所示。图2:服务拆分其实拆分时需要考虑具体的业务特点。例如,在京东主站这样的网站上,用户在访问时不仅加载了基本信息,还加载了商品图片信息、价格信息、库存信息、购物车信息、订单信息、发票信息等。以及配套的下单后的分拣、配送等物流服务,可以拆分成单独的服务。拆分之后,各个服务各司其职,可以更好的优化。对于服务拆分这件事,并不是一个特别恰当的类比。这就像在学校学习,但科目很多。高考的时候要看总分。有些学生会有部分科目。好在有些科目稍差。因为科目多,所以很容易知道哪个科目强哪个科目弱。为了保证最好的总分,一般需要在薄弱的科目上多花精力来提高分数,否则总分会更高。分数不会太高。服务拆分也是如此。拆分之后,很容易知道哪个服务是整体服务的瓶颈。重点优化瓶颈服务,相对容易提升整体服务能力。增加服务环节在大型网站服务方案中,经过各种合理拆分后,数据拆分和服务拆分支持扩展只是一部分工作,接下来需要看是否需要根据需求引入缓存CDN等服务。我把这个叫做增长服务链接。原来直接打数据库的请求,现在可能是先打缓存再打数据库,整个服务链路的长度更长。增长服务链路的原则是将更脆弱或更容易成为瓶颈的资源(如数据库)放在链路的末端。增加服务链路后,应尽可能缩短接入链路。比如能在CDN层面返回,就尽量不要继续往下。如果可以在缓存层面返回,就不要去访问数据库,并且让每个访问环节尽可能的短。能一步解决的事情一步解决,两步解决的事情不要走第三步。本质上就是减少每次访问的资源消耗,尤其是链路末端的访问资源消耗更大。比如获取一些商品的图片信息,可以在访问链接的前端使用CDN,尽可能的阻断访问。如果CDN上没有***,继续访问后端,使用Nginx等反向代理将访问发送到对应的图片服务器,图片服务器本身可以做一些针对性的访问优化。例如,价格等信息是比较敏感的。如果有变化,可能会立即生效,需要直接访问最新的数据。但是如果访问是直接访问数据库,往往会导致数据库直接挂掉。因此,可以考虑在数据库之前引入Redis等缓存服务,将访问权转移到缓存中。价格服务系统本身保证了数据库和缓存的强一致性,减轻了数据库访问的压力。极端情况下,虽然数据量不是特别大,几十台缓存机也能抗住,但是访问量可能会非常大,可以把所有数据都放在缓存里。如果缓存中有异常,甚至不需要访问它。数据库可以直接返回访问失败。因为在访问量非常大的情况下,如果缓存宕机,访问直接打到数据库上,数据库可能瞬间就挂掉了。因此,在特定场景下,可以考虑将缓存与数据库分离。该服务仅访问缓存。也可以在缓存失效时,将数据库中的数据重新加载到缓存中,再对外服务。因此,它在实践中是灵活的。总结一下如何提高整体服务的性能和并发,可以用一句话来概括:在合理范围内尽可能拆分。拆分后类似的服务可以横向扩展,实现整体的高性能和高并发。同时,将比较脆弱的资源放在链路的末端,接入时尽可能缩短接入链路,减少每次接入的资源消耗。如何提高单机服务的性能和并发上面提到的几种情况可以解决大访问量情况下的高并发问题,但是高性能最终还是取决于单体应用的性能。如果说单体应用在低流量的情况下性能已经成为渣,那么部署更多的机器也解决不了问题,那我们就来说说单体服务本身是如何支撑高性能和高并发的。多线程/线程池方法以TCP服务器为例进行说明。最简单的TCP服务器代码,版本1示例如图3所示。图3:版本1的这种方法纯属示例,因为这个服务器启动后只能接受一个连接,即只能与一个客户端交互,并且断开连接后,以后无法再连接,即Thisservercanonlyserveonce。这当然是不可能的,于是就有了版本2,如图4所示,版本2可以一次接受一个连接并进行一些交互处理,只有在这个连接处理完之后才能继续下一个连接。图4:版本2的服务器相当于串行,完全没有并发,所以版本3在版本2的基础上演进了,如图5。图5:版本3这其实是一个模型,我们经常接触到。这种模型的特点是每个线程每个连接。这个模型在MySQL5.5之前使用。这种模型的特点是当有大量的连接时,会创建大量的线程。因此,经常需要限制连接总数。如果没有限制,可能会创建大量的线程,内存等资源很快就会耗尽。另一个是当线程数量较多时,操作系统会花费大量的CPU资源在线程间的上下文切换上,导致只有很少一部分CPU资源真正为业务提供服务。同时,考虑到即使大多数时候连接很多,也不代表所有连接同时处于活动状态,所以版本3演变成版本4,如图6所示。图6:版本4在版本4中,许多连接共享一个线程池。这些线程池中的线程数是固定的,这样线程池中的一个线程可以同时为多个连接服务。MySQL5.6以后是这样使用的。在绝大多数的开发中,线程池技术已经足够了,但是线程池并不是最擅长完全耗尽CPU计算资源或者提供有效计算资源的。以单核计算资源为例,假设线程池中有x个线程,这x个线程会被操作系统按照特定的调度策略进行调度,但是线程上下文切换本身会消耗一定的CPU资源。假设这部分的消耗成本为w,实际有效服务能力为c,那么理论上w+c就是总CPU实际提供的计算资源。同时,假设一个核心CPU理论上提供计算资源为t,这是稳定的。所以就会出现一种情况:当线程池的线程数少的时候,并发度就低。w虽然小,但c也比较小,即w+c
