服务框架能提高性能吗

(六)B2C商城项目实战

有了路线解析图有没有免费资料?有没有志同道合的小伙伴共同进步

以上技术方向我们有自己的高清思维方向导图以及阿里架构师讲解的架构视頻分享(包括高可用,高并发spring源码,mybatis源码JVM,大数据Netty等多个技术知识的架构视频资料和各种电子书籍阅读)视频资料获取方式帮忙关紸私信回复“架构”领取!

精讲架构视频资料获取方式关注我私信回复“架构”即可领取

以及一些一线互联网公司的面试题解析含答案

}

本次分享主要包括三个部分:

  • 如哬提升整体服务的性能及并发

  • 如何提升单机服务的性能及并发

通常来说程序的定义是算法+数据结构+数据算法简单的理解就是一种计算方式,数据结构顾名思义是一种存储组织数据的结构

这两者体现了程序需要用到的计算机资源,涉及到 CPU 资源、内存资源而数据部分除了內存资源,往往还可能涉及到硬盘资源甚至是彼此之间传输数据时会消耗网络(网卡)资源。

当我们搞清楚程序运行起来时涉及哪些资源后就可以更好地分析我们的服务中哪些可能是临界资源。

所谓临界资源就是多个进程(线程)并发访问某个资源时该资源同时只能服务某个戓者某些进程(线程)。

服务的瓶颈主要就是在这些临界资源上还有一些资源原本并不是临界资源。

比如内存在一开始是够的但是因为连接数或者线程数不断地增多,最终导致其成为临界资源其他的 CPU、磁盘、网卡其实和内存一样,在访问量增大以后一样都可能会成为瓶颈

所以怎么做到高性能高并发的服务,简单地说就是找到服务的瓶颈在合理的范围内尽可能的消除瓶颈或者降低瓶颈带来的影响。

再通俗一点的说就是资源总量不够就加资源什么资源不够就加什么资源,同时尽量降低单次访问的资源消耗做到在资源总量一定的情况下囿能力支撑更多的访问。

如何提升整体服务的性能及并发

最典型的一个临界资源就是数据库数据库在一个大访问量的系统中往往是最薄弱的一环,因为数据库本身的服务能力是有限的

以 MySQL 为例,MySQL 可以支持的并发连接数可能也就几千个假设是 3000 个,一个服务对其数据库的并發访问如果超过了 3000 个有部分访问可能在建立连接的时候就失败了。

在这种情况下需要考虑的是如何将数据进行分片,引入多个 MySQL 实例增加资源,如图 1 所示

图 1:单数据实例改成数据库集群

数据库这个临界资源通过数据拆分的方式,由原来的一个 MySQL 实例变成了多个 MySQL 实例

这種情况下数据库资源的整体并发服务能力自然提升了,同时由于服务压力被分散整个数据库集群表现出来的性能也会比单个数据库实例高很多。

存储类的解决思路基本是类似的都是将数据拆分,通过引入多个存储服务实例提升整体存储服务的能力不管对于 SQL 类的还是 NoSQL 类嘚或文件存储系统等都可以采用这个思路。

应用程序自身的服务需要根据业务情况进行合理的细化让每个服务只负责某一类功能,这个思想和微服务思想类似

一句话就是尽量合理地将服务拆分,同时有一个非常重要的原则是让拆分以后的同类服务尽量是无状态或弱关联这样就可以很容易进行水平扩展。

如果拆分以后的同类服务的不同实例之间本身是有一些状态引起彼此非常强的依赖比如彼此要共享┅些信息这些信息又会彼此影响,那这种拆分可能就未必非常的合理需要结合业务重新进行审视。

当然生产环节上下游拆分以后不同的垺务彼此之间的关联又是另外一种情形因为同一个生产环节上往往是走完一个服务环节才能进入下一个服务环节。

相当于有多个串行的垺务任何一个环节的服务都有可能出现瓶颈,所以需要拆分以后针对相应的服务进行单独优化这是拆分以后服务与服务之间的关系。

假设各个同类服务本身是无状态或者弱依赖的情况下针对应用服务进行分析,不同的应用服务不太一样但是通常都会涉及到内存资源鉯及计算资源。

以受内存资源限制为例一个应用服务能承受的连接数是有限的(连接数受限),另外如果涉及上传下载等大量数据传输的情況网络资源很快就会成为瓶颈(网卡打满)。

这种情况下最简单的方式就是同样的应用服务实例部署多份达到水平扩展,如图 2 所示

实际茬真正拆分的时候需要考虑具体的业务特点,比如像京东主站这种类型的网站用户在访问的时候除了加载基本信息以外,还有商品图片信息、价格信息、库存信息、购物车信息以及订单信息、发票信息等

以及下单完成以后对应的分拣配送等配套的物流服务,这些都可以拆成单独的服务拆分以后各个服务各司其职也能做更好的优化。

服务拆分这件事情打个不是特别恰当的比方,就好比上学时都是学习但是分了很多的科目,高考的时候要看总分有些同学会有偏科的现象,有些科成绩好有些科成绩差一点

因为分很多科目所以很容易知道自己哪科是比较强的、哪科是比较弱的,为了保证总体分数最优一般在弱的科目上都需要多花点精力努力提高一下分数,不然总体汾数不会太高

服务拆分也是同样的道理,拆分以后可以很容易知道哪个服务是整体服务的瓶颈针对瓶颈服务再进行重点优化就可以比較容易的提升整体服务的能力。

在大型的网站服务方案上在各种合理拆分以后,数据拆分以及服务拆分支持扩展只是其中的一部分工作之后还要根据需求看看是否需要引入缓存 CDN 之类的服务。

我把这个叫做增长服务链路原来直接打到数据库的请求,现在可能变成了先打箌缓存再打到数据库对整个服务链路长度来说是变长的。

增长服务链路的原则主要是将越脆弱或者说越容易成为瓶颈的资源(比如数据库)放置在链路的越末端

在增长完服务链路之后,还要尽量的缩短访问链路比如可以在 CDN 层面就返回的就尽量不要继续往下走了。

如果可以茬缓存层面返回的就不要去访问数据库了尽可能地让每次的访问链路变短。

可以一步解决的事情就一步解决可以两步解决的事情就不偠走第三步,本质上是降低每次访问的资源消耗尤其是越到链路的末端访问资源的消耗会越大。

比如获取一些产品的图片信息可以在访問链路的最前端使用 CDN将访问尽量挡住。

如果 CDN 上没有命中就继续往后端访问,利用 Nginx 等反向代理将访问打到相应的图片服务器上而图片垺务器本身又可以针对性的做一些访问优化等。

比如像价格等信息比较敏感如果有更改可能需要立即生效,需要直接访问最新的数据泹是如果让访问直接打到数据库中,数据库往往直接就打挂了

所以可以考虑在数据库之前引入 Redis 等缓存服务,将访问打到缓存上价格服務系统本身保证数据库和缓存的强一致,降低对数据库的访问压力

在极端情况下,数据量虽然不是特别大几十台缓存机器就可以抗住,但访问量可能会非常大可以将所有的数据都放在缓存中,如果缓存有异常甚至都不用去访问数据库直接返回访问失败即可

因为在访問量非常大的情况下,如果缓存挂了访问直接打到数据库上,可能瞬间就把数据库打趴下了

所以在特定场景下可以考虑将缓存和数据庫切开,服务只访问缓存缓存失效重新从数据库中加载数据到缓存中再对外服务也是可以的,所以在实践中是可以灵活变通的

如何提升整体服务的性能及并发,一句话概括就是:在合理范围内尽可能的拆分拆分以后同类服务可以通过水平扩展达到整体的高性能高并发。

同时将越脆弱的资源放置在链路的越末端访问的时候尽量将访问链接缩短,降低每次访问的资源消耗

如何提升单机服务的性能及并發

前面说的这些情况可以解决大访问量情况下的高并发问题,但是高性能最终还是要依赖单台应用的性能

如果单台应用性能在低访问量凊况下性能已经成渣了,那部署再多机器也解决不了问题所以接下来聊一下单台服务本身如果支持高性能高并发。

以 TCP server 为例来展开说明朂简单的一个 TCP server 代码,版本一示例如图 3 所示

这种方式纯粹是一个示例,因为这个 server 启动以后只能接受一条连接也就是只能跟一个客户端互動,且该连接断开以后后续就连不上了,也就是这个 server 只能服务一次

这个当然是不行的,于是就有了版本二如图 4 所示,版本二可以一佽接受一条连接并进行一些交互处理,当这条连接全部处理完以后才能继续下一条连接

这个 server 相当于是串行的,没有并发可言所以在蝂本二的基础上又演化出了版本三,如图 5 所示

这其实是我们经常会接触到的一种模型,这种模型的特点是每连接每线程MySQL 5.5 以前用的就是這种模型,这种模型的特点是当有大量连接的时候会创建大量的线程

所以往往需要限制连接总数,如果不做限制可能会出现创建了大量嘚线程很快就会将内存等资源耗干。

另一个是当出现了大量的线程的时候操作系统会有大量的 CPU 资源花费在线程间的上下文切换上,导致真正给业务提供服务的 CPU 资源比例反倒很小

同时,考虑到大多数时候即使有很多连接也并不代表所有的连接在同一个时刻都是活跃的所以版本三又演化出了版本四,如图 6 所示

版本四的时候是很多的连接共享一个线程池,这些线程池里的线程数是固定的这样就可以做箌线程池里的一个线程同时服务多条连接了,MySQL 5.6 之后采用的就是这种方式

在绝大多数的开发中,线程池技术就已经足够了但是线程池在充分榨干 CPU 计算资源或者说提供有效计算资源方面并不是最完美的。

以一核的计算资源为例线程池里假设有 x 个线程,这 x 个线程会被操作系統依据具体调度策略进行调度但是线程上下文切换本身是会消耗一定的 CPU 资源的。

假设这部分消耗代价是 w而实际有效服务的能力是 c,那麼理论上来说 w+c 就是总的 CPU 实际提供的计算资源同时假设一核 CPU 理论上提供计算资源假设为 t,这个是固定的

所以就会出现一种情况:当线程池中线程数量较少的时候并发度较低,w 虽然小了但是 c 也是比较小的,也就是 w+c < t甚至是远远小于 t,如果线程数很多又会出现上下文切换玳价太大,即 w 变大了

虽然 c 也随之提升了一些,但因为 t 是固定的所以 c 的上限值一定是小于 t-w 的,而且随着 w 越大c 的上限值反倒降低了,因此使用线程池的时候线程数的设置需要根据实际情况进行调整。

多线程(线程池)的方式可以较为方便地进行并发编程但是多线程的方式對 CPU 的有效利用率并不是最高的,真正能够充分利用 CPU 的编程方式是尽量让 CPU 一直在工作同时又尽量避免线程的上下文切换等开销。

基于事件驅动的模式(也称 I/O 多路复用)在充分利用 CPU 有效计算能力这件事件上是非常出色的

这种模式的特点是将要监听的 socket fd 注册在 epoll 上,等这个描述符可读倳件或者可写事件就绪了那么就会触发相应的读操作或者写操作。

可以简单地理解为需要 CPU 干活的时候就会告知 CPU 需要做什么事情实际使鼡时示例,如图 7 所示

这个事情拿一个经典的例子来说明:假如在餐厅就餐,餐厅里有很多顾客(访问)每连接每线程的方式相当于每个客戶一个服务员(线程相当于一个服务员)。

服务的过程中一个服务员一直为一个客户服务那就会出现这个服务员除了真正提供服务以外有很夶一段时间可能是空闲的,且随着客户数越多服务员数量也会越多可餐厅的容量是有限的。

因为要同时容纳相同数量的服务员和顾客所以餐厅服务顾客的数量将变成理论容量的 50%。

那这件事件对于老板(老板相当于开发人员希望可以充分利用 CPU 的计算能力,也就是在 CPU 计算能仂<成本>一定的情况下希望尽量的多做一些事情)来说代价就会很大

线程池的方式是雇佣固定数量的服务员,服务的时候一个服务员服务好幾个客户可以理解为一个服务员在客户 A 面前站 1 分钟,看看 A 客户是否需要服务

如果不需要就到 B 客户那边站 1 分钟,看看 B 客户是否需要服务以此类推。这种情况会比之前每个客户一个服务员的情况节省一些成本但是还是会出现一些成本上的浪费。

还有一种模式也就是 epoll 的方式相当于服务员就在总台等着,客户有需要的时候就会在桌上的呼叫器上按一下按钮表示自己需要服务服务员每次看一下总台显示的信息。

比如一共有 100 个客户一次可能有 10 个客户呼叫,这个服务员就会过去为这 10 个客户服务(假设服务每个客户的时候不会出现停顿且可以在較短的时间内处理完)

等这个服务员为这 10 个客户服务员完以后再重新回到总台查看哪些客户需要服务,依此类推在这种情况下,可能只需要一个服务员而餐厅剩余的空间可以全部给客户使用。

Nginx 服务器性能非常好也能支撑非常多的连接,其网络模型使用的就是 epoll 的方式苴在实现的时候采用了多个子进程的方式。

相当于同时有多个 epoll 在工作充分利用了 CPU 多核的特性,所以并发及性能都会比单个 epoll 的方式会有更夶的提升

另外 Redis 缓存服务器大家应该也非常熟悉,用的也是 epoll 的方式性能也是非常好。

通过这些现成的经典开源项目大家就可以直观地悝解基于事件驱动这一方式在实际生产环境中的性能是非常高的,性能提升以后并发效果一般都会随之提升

但是这种方式在实现的时候昰非常考验编程功底以及逻辑严谨性,换句话编程友好性是非常差的

因为一个完整的上下文逻辑会被切成很多片段,比如“客户端发送┅个命令-服务器端接收命令进行操作-然后返回结果”这个过程

这个过程至少会包括一个可读事件、一个可写事件。可读事件简单地理解就是指这条命令已经发送到服务器端的 tcp 缓存区了,服务器去读取命令(假设一次读取完如果一次读取的命令不完整,可能会触发多次读倳件)

服务器再根据命令进行操作获取到结果,同时注册一个可写事件到 epoll 上等待下一次可写事件触发以后再将结果发送出去。

想象一下當有很多客户端同时来访问时服务器就会出现一种情况——一会儿在处理某个客户端的读事件,一会儿在处理另外的客户端的写事件

總之都是在做一个完整访问的上下文中的一个片段,其中任何一个片段有等待或者卡顿都将引起整个程序的阻塞

当然这个问题在多线程編程时也是同样是存在的,只不过有时候大家习惯将线程设置成多个有些线程阻塞了,但可能其他线程并没有在同一时刻阻塞

所以问題不是特别严重,更严谨的做法是在多线程编程时将线程池的数量调整到最小进行测试。

如果确实有卡顿可以确保程序在最快的时间內出现卡顿,从而快速确认逻辑上是否有不足或者缺陷确认这种卡顿本身是否是正常现象。

多线程编程的方式明显是支持了高并发但洇为整个程序线程间上下文调度可能造成 CPU 的利用率不是那么高,而基于事件驱动的编程方式效果是非常好的

但对编程功底要求非常高,洏且在实现的时候需要花费的时间也是最多的所以一种比较折中的方式是考虑采用提供协程支持的语言比如 golang 这种的。

简单说就是语言层媔抽象出了一种更轻量级的线程一般称为协程,在 golang 里又叫 goroutine

这些底层最终也是需要用操作系统的线程去跑,在 golang 的 runtime 实现时底层用到的操作系统的线程数量相对会少一点

而上层程序里可以跑很多的 goroutine,这些 goroutine 会在语言层面进行调度看该由哪个线程来最终执行这个 goroutine。

因为 goroutine 之间的切换代价是远小于操作系统线程之间的切换代价而底层用到的操作系统数量又较少,线程间的上下文切换代价也会大大降低

这类语言能比其他语言的多线程方式提供更好的并发,因为它将操作系统的线程间切换的代价在语言层面尽可能挤压到最小同时编程复杂度大大降低,在这类语言中上下文逻辑可以保持连贯

因为降低了线程间上下文切换的代价,而 goroutine 之间的切换成本相对来说是远远小于线程间切换荿本

所以 CPU 的有效计算能力相对来说也不会太低,可以比较容易的获得了一个高并发且性能还可以的服务

如何提升单机服务的性能及并發,如果对性能或者高并发的要求没有达到非常苛刻的要求选型的时候基于事件驱动的方式可以优先级降低一点,选择普通的多线程编程即可(其实多数场景都可以满足了)

如果想单机的并发程度更好一点,可以考虑选择有协程支持的语言如果还嫌不够,那就将逻辑悝顺考虑采用基于事件驱动的模式,这个在 C/C++ 里直接用 select/epoll/kevent 等就可以了

在 Java 里可以考虑采用 NIO 的方式,而从这点上来说像 golang 这种提供协程支持的语訁一般是不支持在程序层面自己实现基于事件驱动的编程方式的

其实并没有一刀切的万能法则,大体原则是根据实际情况具体问题具体汾析找到服务瓶颈,资源不够加资源尽可能降低每次访问的资源消耗,整体服务每个环节尽量做到可以水平扩展

同时尽量提高单机嘚有效利用率,从而确保在扛住整个服务的同时尽量降低资源消耗成本


}

本文内容来源于任伟【沪江技術沙龙】-漫谈微服务架构实践上的主题演讲IT大咖说为沪江技术沙龙独家视频知识分享平台。

Bilibili作为一个大型弹幕视频网站在竞争日益激烮的互联网行业中,开始重视技术生态的演进探索寻求适合企业本身的一个微服务架构。本次分享主要讲述了B站高性能微服务架构的演進

大家好,我是来自bilibili的任伟今天的分享分为三个部分内容:

  • 高性能微服务架构在B站的落地。

B站从成立至今已经有将近八年的时间了泹是从前两年我们才开始重视整个的技术生态的演进。在整个B站的代码体系里面我们曾经也把B站的老代码称之为全家桶。因为它是一套玳码涵盖了几乎所有bilibili里面的业务体系。我们现在的引进方向以科研为主整个B站光是网站这一块,就有很多的分支而整个的分支对应嘚域名也很多。

B站以前代码的体系从安全体系上来讲我们进行了一系列的拆分。整个的代码仓库主要分为三个部分一是主站的业务逻輯,还有一个是分发管理的逻辑以及配置文件。配置文件整体的发布是一套非常繁杂的流程它用脚本的方式把整个配置文件慢慢的生荿,而这些跟本身主张的代码逻辑是隔离开来的

我是一个工作很多年的PHP研发。在接触B站之前我一直认为PHP的业务结构开发速度会非常快。但是了解了B站的代码就会发现其实用PHP语言体系来做的事情非常多。就目前而言整个B站的运维体系的工具都是由PHP来完成的。因为我们昰一个视频类的网站最重要的就是视频资源的管理,而这个调度其实一开始也是由PHP来完成的

下面图片是一个我们的业务集群,主要分彡大块一块是面向移动端的服务集群,一个是面向PC端的服务集群还有一个就是面向弹幕的。

整个B站曾经的体系是非常庞杂的这么大嘚一个系统面临着很多问题。

就代码来说维护的难度非常大。对于研发而言如果我们只是关注某一块的业务逻辑,就好像管中窥豹洏且最重要的是它文档缺失。虽说一个好的编码习惯就是一个好的文档但在业务量或整个体系比较庞大的情况下,文档和代码还是有本質区别的

B站是基于各种网站慢慢成长起来的一个企业,所以当时在做这块的时候没有特别重视文档一直有比较大的缺失,导致代码维護非常麻烦

整个的基础架构是基于织梦CMS,是一个比较流行的开源的内容管理系统绝大多数业务逻辑我们做了一些深度的定制,导致一般研发很难搞定前面底层里的一些逻辑

业务机会聚合在一起,不易被扩展和拆分B站在发展到前两年的时候,让运维独立去搭一套整个B站的扩展体系并不是那么容易B站的运行环境基本上只能通过创始人来扩展我们的负载。

运维复杂因为配置也是相当复杂。后来已经不尣许在运维再增加业务上的一些重写逻辑只有让代码这边自己去处理。所以重构优化我们已经提上了日程。

我们公司成立的基础是一個天才型选手以前在那套系统加入了一些黑科技的东西,但同时就限制了公司团队的发展

基于这样的一些重点问题,我们在去年开始思考怎么来解决B站目前面临的这些问题因为B站发展速度非常快,业务的发展导致团队也会不停的增长我们需要考虑各方面的因素。我們需要有一部分的业务要参与进来然后梳理出来,再进行一系列架构方面的重组

高性能微服务如何在B站落地

通过整个的服务体系我们鈳以看到,基本上以命名规则可以看到service里面的一些内部服务对于终端和PC端,我们都是以show和interface作为作为项目向外透露接口他们的区别就在於show是一个单纯的业务,它有紧急预案和service但是interface会做一些数据的聚合。服务间的依赖标准主要是RPC

介绍完大体框架之后,我们先看一下为什麼当时B站会选择go语言作为技术站我们选择go主要是因为它的执行和开发效率非常的高效。相比其他语言优势还是挺明显的。比如我们主站的首页的动态图每五秒钟需要获取各个分区里面的最新稿件,订单访问量是非常大的利用go服务可以明显地感觉到移动端的访问量占整个B站的访问量已经达到了60%以上,但是他那边基本上所有的服务接口都不走CDN直接打到元,他们那边量也是非常大但是也没有出过什么錯。

B站go语言成长非常迅速因为它的背景是google,生态也比较丰富支持kafka、canel、hbase这些比较流行的风格式管理框架。鉴于此我们就选择了go语言作為我们整个公司的在技术上的统一。而且相对而言它的调用效率要比http比较高,就是我们不走apI接口接收内部的RPC

为什么说B站微服务在整个經营效率上会这么高呢,除了它本身语言体系上没有其他语言那么臃肿之外我们还做了一些努力。比如在整个的对外服务的这一层上基本上没有任何的请求可以直接打到DB,全部是缓存我们都是通过多层缓存机制来保障的。

我觉得微服务最重要的一点就是服务隔离在實际项目中我们也遇到很多问题。因为公共资源导致某一个服务和资源挂钩,会拖垮相应的服务所以说服务隔离非常重要。

选择go的另外一个重要原因就是它本身跟docker的结合有天然的优势因为go语言的运行环境非常的精良,它不需要依赖于任何的其他的环境所以我们动态嘚管理相对于其他项目来讲的,是整个公司里面最干净的docker我们的团队也会做服务巡查。某一个服务如果出现问题都能第一时间来反馈到峩们的平台里

数据总线中间件,叫Databus它是一个面向redis协议背靠kafka的消息中间件,它是基于内地市场放上的行为主要目的就是用来deal。

我们主張直接更新缓存并把消息推送到数据总线,然后由数据总线来更新数据库

我们这边本身也有一些稿件的时候,比如说用户提交的一些視频在我们这边的话会有一个基于canal的go服务,这个服务的主要作用就是在于监听数据库日志来解析出数据库里面的更新和参数方程,来哽新缓存

我们自己魔改了twproxy,这是一个开源的想法我们自己做了一些二次的开发。因为以前bilitw是单进程我们这个是一个多进程的魔改负載均衡的组件。

配置中心disconf也是我们自己研发基本上我们以自己造文字为主了。也做了一套自己的小文件存储系统BFS这套系统跟当前比较鋶行的一些云存储还是很像的,它的吞吐量足够大扩展性也足够好。

B站发展到现在微服务还只是一个刚起步的阶段,我们也在微服务這条路上慢慢探索适合我们的一个微服务架构我认为适合企业本身的微服务就是最好的。

我今天要分享的就这么多谢谢!

}

我要回帖

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信