关于大型网站技术演进

起因:

最近项目要做动静分离,即将动态web项目,发布到两台服务器,静态页面发布到另外三台服务器,动态web服务器和静态资源服务器,分别做了集群。

过程:

由于测试环境系统没有装好,在这之前,进行了一系列的资料查找,包括websphere的静默安装,动静分离的相关知识,其中看到了一个作者,写的关于大型网站技术演进,感觉特别好,基于此,我将我觉得对我有用的部分,做了一下总结。

一个大型网站,是技术和业务的结合,一个满足某些用户需求的网站只要技术和业务二者有一方难度很大,必然会让企业投入更多的、更优秀的人力成本实现它,那么这样的网站就是所谓的大型网站了。

为什么要用多台服务器部署?

  1. 保证网站的可用性,多台服务器部署应用,那么其中一些服务器挂掉了,只要网站还有服务器能正常运转,那么网站对外仍然可以正常提供服务。
  2. 提高网站的并发量,服务器越多那么网站能够服务的用户,单位时间内能承载的请求数也就越大。                                                             


 多台服务器部署存在问题:因为大多数网站在用户使用时候都是要保持用户的状态,具体点就是网站要记住请求是归属到那一个客户端,而这个状态在网站开发里就是通过会话session来体现的。分开部署的web应用服务要解决的一个首要问题就是要保持不同物理部署服务器之间的session同步问题,从而达到当用户第一次请求访问到服务器A,第二个请求访问到服务器B,网站任然知道这两个请求是同一个人,解决方案很直接:服务器A和服务器B上的session信息要时刻保持同步,那么如何保证两台服务器之间session信息的同步呢?

项目里客户端的请求到达web服务器之前,会先到F5,F5是一个用来做负载均衡的硬件设备,它的作用是将用户请求均匀的分发到后台的服务器集群,F5是硬件的负载均衡解决方案,如果我们没那么多钱买这样的设备,也有软件的负载均衡解决方案,这个方案就是大名鼎鼎的LVS了,这些负载均衡设备除了可以分发请求外它们还有个能力,这个能力是根据http协议的特点设计的,一个http请求从客户端到达最终的存储服务器之前可能会经过很多不同的设备,如果我们把一个请求比作高速公路上的一辆汽车,这些设备也可以叫做这些节点就是高速路上的收费站,这些收费站都能根据自己的需求改变http报文的内容,所以负载均衡设备可以记住每个sessionid值对应的后台服务器,当一个带有sessionid值的请求通过负载均衡设备时候,负载均衡设备会根据该sessionid值直接找到指定的web服务器,这种做法有个专有名词就是session粘滞,这种做法也比那种session信息在不同服务器之间拷贝复制要高效,不过该做法还是比存cookie的效率低下,而且对于网站的稳定性也有一定影响即如果某台服务器挂掉了,那么连接到该服务器的用户的会话都会失效。

早期的淘宝在这个问题解决更加巧妙,他们将session的信息直接存储到浏览器的cookie里,每次请求cookie信息都会随着http一起传递到web服务器,这样就避免了web服务器之间session信息同步的问题,这种方案会让很多人诟病,诟病的原因是cookie的不安全性是总所周知的,如果有人恶意截取cookie信息那么网站不就不安全了吗?这个答案还真不好说,但是我觉得我们仅仅是跟踪用户的状态,把session存在cookie里其实也没什么大不了的。

时下使用的比较多的方案就是使用独立的缓存服务器,也就是将session的数据存储在一台独立的服务器上,如果觉得存在一台服务器不安全,那么可以使用memcached这样的分布式缓存服务器进行存储,这样既可以满足了网站稳定性问题也提升了网站的并发能力。

解决session的问题的本质也就是解决session的存储问题,其本质也就是解决网站的存储问题,一个初建的网站在早期的运营期需要解决的问题基本都是由存储导致的。

排除一些不可控的因素,网站在高并发下挂掉的原因90%都是因为数据库不堪重负所致,而应用的瓶颈往往只有在解决了存储瓶颈后才会暴露,那么我们要升级网站能力的第一步工作就是提升数据库的承载能力,对于读远大于写的网站我们采取的方式就是将数据库从读写这个角度拆分,具体操作就是将数据库读写分离,如下图所示:

 


我们这时要设计两个数据库,一个数据库主要负责写操作我们称之为主库,一个数据库专门负责读操作我们称之为副库,副库的数据都是从主库导入的,数据库的读写分离可以有效的保证关键数据的安全性,但是有个缺点就是当用户浏览数据时候,读的数据都会有点延时,这种延时比起全站不可用那肯定是可以接受的。不过针对12306的场景,仅仅读写分离还是远远不够的,特别是负责读操作的副库,在高访问下也是很容易达到性能的瓶颈的,那么我们就得使用新的解决方案:使用分布式缓存,不过缓存的缺点就是不能有效的实时更新,因此我们使用缓存前首先要对读操作的数据进行分类,对于那些经常不发生变化的数据可以事先存放到缓存里,缓存的访问效率很高,这样会让读更加高效,同时也减轻了数据库的访问压力。至于用于写操作的主库,因为大部分网站读写的比例是严重失衡,所以让主库达到瓶颈还是比较难的,不过主库也有一个读的压力就是主库和副库的数据同步问题,不过同步时候数据都是批量操作,而不是像请求那样进行少量数据读取操作,读取操作特别多,因此想达到瓶颈还是有一定的难度的。听人说,美国牛逼的facebook对数据的任何操作都是事先合并为批量操作,从而达到减轻数据库压力的目的。

  上面的方案我们可以保证在高并发下网站的稳定性,但是针对于读,如果数据量太大了,就算网站不挂掉了,用户能很快的在海量数据里检索到所需要的信息又成为了网站的一个瓶颈,如果用户需要很长时间才能获得自己想要的数据,很多用户会失去耐心从而放弃对网站的使用,那么这个问题又该如何解决了?

  解决方案就是我们经常使用的百度,谷歌哪里得来,对于海量数据的读我们可以采用搜索技术,我们可以将数据库的数据导出到文件里,对文件建立索引,使用倒排索引技术来检索信息,我们看到了百度,谷歌有整个互联网的信息我们任然能很快的检索到数据,搜索技术是解决快速读取数据的一个有效方案,不过这个读取还是和数据库的读取有所区别的,如果用户查询的数据是通过数据库的主键字段,或者是通过很明确的建立了索引的字段来检索,那么数据库的查询效率是很高的,但是使用网站的人跟喜欢使用一些模糊查询来查找自己的信息,那么这个操作在数据库里就是个like操作,like操作在数据库里效率是很低的,这个时候使用搜索技术的优势就非常明显了,搜索技术非常适合于模糊查询操作


memcached实现的原理,当网站使用缓存集群时候,缓存数据是通过一定的算法将缓存数据尽量均匀分不到不同服务器上,如果用户A的缓存在服务器A上,那么服务器B上是没有该用户的缓存数据,早期的memcache数据分布式的算法是根据缓存数据的key即键值计算出一个hash值,这个hash值再除以缓存服务器的个数,得到的余数会对应某一台服务器,例如1对应服务器A,2对应服务器B,那么余数是1的key值缓存就会存储在服务器A上,这样的算法会导致某一台服务器挂掉,那么网站损失的缓存数据的占比就会比较高,为了解决这个问题,memcached引入了一致性hash算法。


 将数据库进行读写分离是网站解决存储瓶颈的第一步,为什么说是第一步呢?因为读写分离从业务角度而言它是一种粗粒度的数据拆分,因此它所包含的业务复杂度比较低,容易操作和被掌控,从技术而言,实现手段也相对简单,因此读写分离是一种低成本解决存储瓶颈的一种手段,这种方案是一种改良方案而不是革命性的的方案,不管是从难度,还是影响范围或者是经济成本角度考虑都是很容易让相关方接受的。

      那么我们仅仅将数据库做读写分离为何能产生好的效率了?回答这个问题我们首先要了解下硬盘的机制,硬盘的物理机制就有一个大圆盘飞速旋转,然后有个磁头不断扫描这个大圆盘,这样的物理机制就会导致硬盘数据的顺序操作比随机操作效率更高,这点对于硬盘的读和写还算公平,但是写操作在高并发情况下会有点复杂,写操作有个特性就是我们要保证写操作的准确性,但是高并发下可能会出现多个用户同时修改某一条数据,为了保证数据能被准确的修改,那么我们通常要把并行的操作转变为串行操作,这个时候就会出现一个锁机制,锁机制的实现是很复杂的,它会消耗很多系统性能,如果写操作掺杂了读操作情况就更复杂,效率会更加低效,相对于写操作读操作就单纯多了,如果我们的数据只有读操作,那么读的性能也就是硬盘顺序读能力和随机读能力的体现,即使掺杂了并发也不会对其有很大的影响,因此如果把读操作和写操作分离,效率自然会得到很大提升。

      既然读写分离可以提升存储系统的效率,那么为什么我们又要引入缓存系统和搜索技术了?缓存将数据存在内存中,内存效率是硬盘的几万倍,这样的好处不言而喻,而选择搜索技术的背后的原理就不同了,数据库存储的数据称之为结构化数据,结构化数据的限制很多,当结构化数据遇到了千变万化的随机访问时候,其效率会变得异常低效,但是如果一个网站不能提供灵活、高效的随机访问能力,那么这个网站就会变得单板没有活力,例如我们在淘宝里查找我们想要的商品,但是时常我们并不清楚自己到底想买啥,如果是在实体店里店员会引导我们的消费,但是网站又如何引导我们的消费,那么我们必须要赋予网站通过人们简单意向随机找到各种不同的商品,这个对于数据库就是一个like操作的,但是数据里数据量达到了一定规模以后like的低效是无法让人忍受的,这时候搜索技术在随机访问的能力正好可以弥补数据库这块的不足。

     业务再接着的增长下去,数据量也会随之越来越大了,这样发展下去总有一天主库也会产生瓶颈了,那么接下来我们又该如何解决主库的瓶颈了?方法很简单就是我们要拆分主库的数据了,那么我该以什么维度拆分数据了?一个数据库里有很多张表,不同的表都针对不同的业务,网站的不同业务所带来的数据量也不是不同的,这个时候系统的短板就是那些数据量最大的表,所以我们要把那些会让数据库产生瓶颈的表拆出来,例如电商系统里商品表和交易表往往数据量非常大,那么我们可以把这两种表建立在单独的两个数据库里,这样就拆分了数据库的压力,这种做法叫做数据垂直拆分,不过垂直拆分会给原有的数据库查询,特别是有事务的相关操作产生影响,这些问题我们必须要进行改造,关于这个问题,我将在下篇里进行讨论。

     当我们的系统做完了读写分离,数据垂直拆分后,我们的网站还在迅猛发展,最终一定又会达到新的数据库瓶颈,当然这些瓶颈首先还是出现在那些数据量大的表里,这些表数据的处理已经超出了单台服务器的能力,这个时候我们就得对这个单库单表的数据进行更进一步的拆分,也就是将一张表分布到两台不同的数据库里,这个做法就是叫做数据的水平拆分了

    一个解决大型网站数据瓶颈的一个脉络了,具体如下:

     单库数据库-->数据库读写分离-->缓存技术-->搜索技术-->数据的垂直拆分-->数据的水平拆分

做系统开发一定要有个清晰的认识: 能用简单的方案解决问题,就一定要毫不犹豫的舍弃复杂的方案,当系统需要使用高难度技术的时候,我们一定要让自己感受到这是迫不得已的

我们公司的数据库建模很简单,怎么个简单法了,数据库的表之间都没有外键,数据库不准写触发器,可以写写存储过程,但是存储过程决不能用于处理生产业务逻辑,而只能是一些辅助工作,例如导入导出写数据啊,就算是数据库做到了读写分离,数据之间同步也最好是用java程序做,也不要使用存储过程,除非迫不得已。其实作为存储数据的数据库,它和我们开发出的程序的本质是一样的那就是:存储和计算,那么当数据库作为一个业务系统的存储介质时候,那么它的存储对业务系统的重要性要远远大于它所能承担的计算功能,当数据库作为互联网系统的存储介质时候,如果这个互联网系统成长迅速,那么这个时候我们对数据库存储的要求就会越来越高,最后估计我们都想把数据库的计算特性给阉割掉,当然数据库基本的增删改查我们是不能舍弃的,因为它们是数据库和外界沟通的入口,我们如果接触过具有海量数据的数据库,我们会发现让数据库运行的单个sql语句都会变得异常简洁和简单,因为这个时候我们知道数据库已经在存储这块承担了太多的负担,那么我们能帮助数据库的手段只能是尽量降低它运算的压力


水平拆分主键设计的第一个原则:被水平拆分的表的主键设计最好使用一个字段表示

  如果我们的主键只是表达记录唯一性的话,那么水平拆分时候相对要简单的多,例如在Oracle数据库里有一个sequence机制,这其实就是一个自增数的算法,自增机制几乎所有关系数据库都有,也是我们平时最喜欢使用的主键字段设计方案,如果我们要拆分的表,使用了自增字段,同时这个自增字段只是用来表达记录唯一性,那么水平拆分时候处理起来就简单多了,我这里给出两个经典方案,方案如下:

  方案一:自增列都有设定步长的特性,假如我们打算把一张表只拆分为两个物理表,那么我们可以在其中一张表里把主键的自增列的步长设计为2,起始值为1,那么它的自增规律就是1,3,5,7依次类推,另外一张物理表的步长我们也可以设置为2,如果起始值为2,那么自增规律就是2,4,6,8以此类推,这样两张表的主键就绝对不会重复了,而且我们也不用另外做两张物理表相应的逻辑关联了。这种方案还有个潜在的好处,那就是步长的大小和水平数据拆分的粒度关联,也是我们为水平拆分的扩容留有余量,例如我们把步长设计为9,那么理论上水平拆分的物理表可以扩容到9个。

  方案二:拆分出的物理表我们允许它最多存储多少数据,我们其实事先通过一定业务技术规则大致估算出来,假如我们估算一张表我们最多让它存储2亿条,那么我们可以这么设定自增列的规律,第一张物理表自增列从1开始,步长就设为1,第二种物理表的自增列则从2亿开始,步长也设为1,自增列都做最大值的限制,其他的依次类推。


而缓存技术和搜索技术在数据库的任意阶段里都可以根据实际的业务需求随时切入其中帮助数据库减轻不必要的压力。例如,当网站的后台数据库还是单库的时候,数据库渐渐出现了瓶颈问题,而这个瓶颈又没有达到需要采取大张旗鼓做读写分离方案的程度,那么我这个时候可以考虑引入缓存机制。不过要合理的使用缓存我们首先要明确缓存本身的特点,这些特点如下所示:

  特点一:缓存主要是适用于读操作,并且缓存的读操作的效率要远远高于从数据库以及硬盘读取数据的效率。

  特点二:缓存的数据是存储在内存当中,因此当系统重启,宕机等等异常场景下,缓存数据就会不可逆的丢失,且无法恢复,因此缓存不能作为可靠存储设备,这就导致一个问题,缓存里的数据必须首先从数据库里同步到内存中,而使用缓存的目的就是为了解决数据库的读操作效率低下的问题,数据库的数据同步到缓存的操作会因为数据库的效率低下而在性能上大打折扣,所以缓存适合的场景是那些固定不变的数据以及业务对实时性变化要求不高的数据。

  根据缓存的上述两个特点,我们可以把数据库里和上述描述类似操作的相关数据迁移到缓存里,那样我们就从数据库上剥离了那些对数据库价值不高的操作,让数据库专心做有价值的操作,这样也是减轻数据库压力的一种手段。

  不过这个手段局限性很强,局限性主要是一台计算机了用于存储缓存的内存的大小都是远远要低于硬盘,并且内存的价格要远贵于硬盘,如果我们将大规模的数据从硬盘往内存迁移,从资源成本和利用率角度考虑性价比还是很低的,因此缓存往往都是用于转存那些不会经常变化的数据字典,以及经常会被读,而修改较少的数据,但是这些数据的规模也是有一定限度的,因此当单库数据库出现了瓶颈时候马上就着手进行读写分离方案的设计性价比还是很高的。


启迪一:数据库的读写分离不是简单的把主库数据导入到读库里就能解决问题,读数据库和写数据的分离的目的是为了让读和写操作不能相互影响效率。

  启迪二:解决读的瓶颈问题的本质是减少数据的检索范围,数据检索的范围越小,读的效率也就越高;

  启迪三:数据库的垂直拆分和水平拆分首先不应该从技术角度进行,而是通过业务角度进行,如果数据库进行业务角度的水平拆分,那么拆分的维度往往是要根据该表的某个字段进行的,这个字段选择要有一定原则,这个原则主要是该字段的维度的粒度不能过细,该字段的维度范围不能经常的动态发生变化,最后就是该维度不能让数据分布严重失衡。

  回到现实的开发里,对于一个数据库做拆表,分表的工作其实是一件很让人恼火的工作,这主要是有以下原因所造成的,具体如下所述:

  原因一:一个数据库其实容纳多少张表是有一定限制的,就算没有超过这个限制,如果原库本来有30张表,我们拆分后变成了60张,接着是120张,那么数据库本身管理这么多表也会消耗很多性能,因此公司的DBA往往会控制那些过多分表的行为。

  原因二:每次拆表后,都会牵涉到历史数据的迁移问题,这个迁移风险很大,迁移方案如果设计的不完善可能会导致数据丢失或者损坏,如果关键数据发生了丢失和损坏,结果可能非常致命。因此在设计数据库分表分库方案时候我们要尽量让受影响的数据范围变得最小。

  原因三:每次拆表和分表都会让系统的相关方绷紧神经,方案执行后,会有很长时间的监控和观察期,所以拆数据库时常是一件令人讨厌的事情。

  原因四:为了保证新方案执行后确保系统没有问题,我们常常会让新旧系统并行运行一段时间,这样可以保证如果新方案出现问题,问题的影响面最低,但是这种做法也有一个恶果就是会导致数据迁移方案要进行动态调整,从而增加迁移数据的风险

  因此当公司不得不做这件事情时候,公司都会很自然去考虑第三种解决方案,第三种解决方案是指尽量不改变原数据库的功能,而是另起炉灶,使用新技术来解决我们的问题,例如前文所说的搜索技术解决数据库like的低效问题就是其中方案之一,该方案只要我们将数据库的表按一定时间导入到文件系统,然后对文件建立倒排索引,让like查询效率更好,这样就不用改变原数据库的功能,又能减轻数据库的压力。

  现在常用的第三种解决方案就是使用NoSql数据库,NoSql数据库大多都是针对文件进行的,因此我们可以和使用搜索引擎那样把数据导入到文件里就行了,NoSql基本都采用Key/Value这种简单的数据结构,这种数据结构和关系数据库比起来更加的灵活,对原始数据的约束最少,所以在NoSql数据库里建表我们可以很灵活的把列和行的特性交叉起来用


痛点一:数据库的连接数不够用了。换句话说就是在同一个时间内,要求和数据库建立连接的请求超出了数据库所允许的最大连接数,如果我们对超出的连接数没有进行有效的控制让它们直接落到了数据库上,那么就有可能会让数据库不堪重负,那么我们就得要分散这些连接,或者让请求排队。

  痛点二:对于数据库表的操作无非两种一种是写操作,一种是读操作,在现实场景下很难出现读写都成问题的事情,往往是其中一种表的操作出现了瓶颈问题所引起的,由于读和写都是操作同一个介质,这就导致如果我们不对介质进行拆分去单独解决读的问题或者写的问题会让问题变的复杂化,最后很难从根本上解决问题。

  痛点三:实时计算和海量数据的矛盾。本系列讲存储瓶颈问题其实有一个范畴的,那就是本系列讲到的手段都是在使用关系数据库来完成实时计算的业务场景,而现实中,数据库里表的数据都会随着时间推移而不断增长,当表的数据超出了一定规模后,受制于计算机硬盘、内存以及CPU本身的能力,我们很难完成对这些数据的实时处理,因此我们就必须要采取新的手段解决这些问题。

痛点四:当数据库所在服务器的硬件有很大提升时候,我们可以优先考虑是否可以通过提升硬件性能的手段来提升数据库的性能。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值