赵玉伟的博客

高并发网站架构概略

一个高并发网站首先面临负载均衡、缓存、业务拆分服务化、异步处理、分库分表这几个问题。

负载均衡

首要问题就是服务端的压力,一般用qps来衡量服务端的抗压能力,上万级别的qps的抗压能力已经很不错了。服务端一般通过分流来解决压力问题。
对于前端应用服务器, 可以通过负载均衡、动静分离、应用服务器堆机器一起抗压。
负载均衡有高大上的F5, 但是很贵, 一般公司承受不起这笔开销,阿里的很多服务是通过LVS+Tengine进行负载均衡,tengine是在nginx的基础上做了很多优化的一款web服务器。 应用服务器挂载在tengine的下层。负载均衡的主要功能有两个,一是要能够抗住海量的并发量,二是负责将请求基本均衡的转发到下层的服务。

动静分离是将静态文件,比如js、css、图片、视频、或者用于渲染页面的模板从应用服务器中分离, 访问这些静态资源的请求,直接通过apache、nginx这样的web服务器获取。像阿里、腾讯、七牛这样有势力的互联网公司, 由于访问的人遍布在全国各地,所以会搭建一层cdn的服务器用于缓存静态资源,以减少一访问的网络环节。

应用服务器里面跑的是我们实现的业务, 一般跑在tomcat或者jboss这类应用服务器中,因为需要做业务的计算, 能支撑多少qps主要受内存、cpu的影响, 单机的qps一般是几百上千的量。

一个简图如下:

层与层之间就像一道道闸门一样,每一层拦截处理掉一部分请求,让请求数量层层递减, 当请求真正落到应用里的时候,那么就是我们需要通过计算才能够处理完成的;
而层与层之间可以通过水平增加机器来一起扛量。像有些大应用,可能需要用上千台机器才能把压力抗住。

缓存

缓存数据

一个网站需要多台服务器组合完成时,性能瓶颈一般出现在网络IO上,比如:从数据库中读写一条数据、进行一次rpc调用、从消息中间件中消费一条消息等,这种走网络的访问代价是比较高的(由于职责单一化,所以应用进行本地IO的情况现在是比较少见的)。这种情况下, 可以将需要走IO才能获取的数据缓存起来, 即减少了数据的获取链路,同时又有效的对下层服务减少了压力。 缓存可以理解成一个内存数据库, 直接访问内存速度是最快的,再配合每种缓存系统的存储结构和算法, 可以支撑几万或者几十万的qps是没有问题的,当然,缓存也是要搭建集群的。 另外、对于一些热点的数据,比如秒杀, 可以落到应用本地的内存,来减少缓存的压力, 可以理解成二级缓存。如下图:

维持session

在上面一节中,提到过应用服务器可以水平扩展,水平扩展的前提是每个server是对等的,也就是server之间能够做到等价替换, 那么像用户登录的这种状态就不能让server维持,因为用户登录之后,接下来的每个请求,可以落到任何一台机器上。 这种case也可以通过缓存解决: 将用户登录的session放在cache中集中管理。

具体实现

实现上常用的有memcache + 一致性哈希; 淘宝的tair; redis。每种实现有自己的特点,可以根据业务场景进行选择与研修。

三个问题

1、缓存数据的准确度?

缓存的数据应该主要用来存放不变的数据的, 如果基于缓存数据做二次计算,之后又把计算的结果再次落缓存,那么这种场景不适合用缓存解决,比如:
把数据A放到缓存,然后将A重新计算再次赋给A(如:A= A + B)。

2、缓存的数据能否丢失?

这个问题看场景,最好是不要丢失, 比如,某台机器挂掉了,那么原先落到这台机器上的数据会导致不能命中。如果不丢数据, 需要做缓存的主从备份, 淘宝的tair有这种机制, 备份就意味着需要占用机器资源。

3、缓存数据的一致性怎么解决?

比如数据库的数据改动之后,如何让缓存数据实时的感知? 这个问题有两种场景,
1、同一个服务的缓存。 首先要更新数据, 然后再清空缓存。 一定不能先删缓存的数据,因为如果并发高的话,其他的线程马上就把数据刷到缓存中,这样就会导致最新的数据无法落到缓存中。

2、跨服务的缓存,也就是A服务需要用到B服务的数据,而B服务更新数据后,A服务如何实时的感知? 这个问题我没有碰到过具体场景。但是我认为跨服务实时更新数据比如涉及到接口的调用,而架构设计中,如果B服务再调用A服务接口的话,这种相互依赖的调用是要禁止的。 一个折中的办法是对缓存数据设置一个超时时间。

业务拆分

当一个业务达到一定复杂化之后,必然要做业务拆分。像淘宝这种网站, 是以服务为单位进行组合的。 服务化其实是一种分治的思想, 就是把一个大的问题拆分成数个小的问题;好处有很多, 可以有效的降低业务间的耦合度;
还可以对一些不重要的业务发生异常时做降级处理;
对于一些实时性要求不是很高的调用,通过消息中间件进行异步处理, 保证主流程的用户体验等等。

当然,业务拆分的粒度是否合适,要考验业务专家的水平, 需要即懂得业务,又懂得技术的人来完成。

服务化之后,技术层面面临的问题就是通信框架的选择, 通信框架要完成服务对外的感知、序列化、机器的灾备、升降级等任务; 我了解的有淘宝使用的HSF框架, B2B使用的dubbo框架。
另外, 一个问题需要我们注意,一个请求从第一个服务接管请求,一直到最终返回结果, 中间可能需要调用N个服务配合来完成。 单次的请求链路不应该太深,因为大部分请求都是同步返回结果的,同步就意味着实际响应时间是N个服务响应时间的总和 + 网络通信时间。也就是如果在写自己服务的时候, 也要去了解所依赖的服务, 要把握全局。另外,我们的服务一旦对外提供服务,就需要对当前的服务能力做一个压力的评估,也就是当前能够支撑多少的qps, 依赖于该服务的上层服务要评估超时时间, 如果 压力+超时时间 的组合评估有问题, 当服务达到峰值时,会造成请求排队,很可能引起“雪崩”,导致服务彻底挂掉,不得不重启服务,临时加机器等等。关于“雪崩”的话题我打算单独开一篇聊一聊。

异步处理

当一个请求需要经过多个服务完成时, 响应时间是一个很重要的性能指标。比如一个对外的接口需要几百个毫秒甚至几秒钟才能完成,那么这种体验不是很好的,除了自己系统进行性能测评与优化外,可以通过异步处理来提高用户的体验。如下图:

分库分表

如果没有对数据分离,那么这个系统是谈不上服务化的,不能做多个服务访问同一个库的这种设计。最好是库A只对服务A提供数据,库B只对服务B提供数据。

请求的压力首先会落到应用服务器中, 而应用如果需要访问DB的话,压力又会落到DB中, 那么DB应该如何设计? 方案有多种, 但是基本上都是在分库分表的基础上,衍生出不同的处理方案。

  • 分库是为了达到压力用单机扛不住,用多台机器抗压的目的。
  • 分表是为了解决某张表的数据量太大、单次查询性能低下,甚至处理不了(比如把内存撑爆)的问题。 通过将一张表打散成多张表, 将数据分布到多张表实现。

分库分表我认为是最复杂的一个环节,因为涉及到数据的分布式存储。同一类别的数据分散在多个存储节点上,查询时路由怎么解决? 数据不丢失的话,如何冗余存储? 多机存储涉及到事务怎么解决? 读写分离以及master – slave,数据的实时性怎么保证? 如果master – master 相互备份又要如何解决? 总之会面临很多很复杂的问题, 每一个问题都很专业, 想做好需要内功。阿里的TDDL、单元化部署异地多活确实做得很好。

零零碎碎的说了这些,这算是最粗粒度的一个概括, 每一个知识点要做好,都需要下大功夫深入的学习。 而且真正在架设一个网站时, 面临的问题会比这些要多得多,需要我们把自己角色所承担的责任做好, 相互配合与支持才可能做的好。