转载 徐汉彬:亿级Web系统搭建——单机到分布式集群

 

摘要:随着数据暴增,单服务器开始疲于应对海量用户的访问。自本期《问底》,徐汉彬将带你们开启异地跨集群分布式系统打造,本次关注的重点则是架构从单机到分布式集群的转变。
Web系统的缓存机制的创建和优化

刚刚咱们讲完了Web系统的外部网络环境,如今咱们开始关注咱们Web系统自身的性能问题。咱们的Web站点随着访问量的上升,会遇到不少的挑战,解决这些问题不只仅是扩容机器这么简单,创建和使用合适的缓存机制才是根本。

最开始,咱们的Web系统架构多是这样的,每一个环节,均可能只有1台机器。html

 

咱们从最根本的数据存储开始看哈。

1、 MySQL数据库内部缓存使用

MySQL的缓存机制,就从先从MySQL内部开始,下面的内容将以最多见的InnoDB存储引擎为主。

1. 创建恰当的索引

最简单的是创建索引,索引在表数据比较大的时候,起到快速检索数据的做用,可是成本也是有的。首先,占用了必定的磁盘空间,其中组合索引最突出,使用须要谨慎,它产生的索引甚至会比源数据更大。其次,创建索引以后的数据insert/update/delete等操做,由于须要更新原来的索引,耗时会增长。固然,实际上咱们的系统从整体来讲,是以select查询操做居多,所以,索引的使用仍然对系统性能有大幅提高的做用。

2. 数据库链接线程池缓存

若是,每个数据库操做请求都须要建立和销毁链接的话,对数据库来讲,无疑也是一种巨大的开销。为了减小这类型的开销,能够在MySQL中配置thread_cache_size来表示保留多少线程用于复用。线程不够的时候,再建立,空闲过多的时候,则销毁。前端

其实,还有更为激进一点的作法,使用pconnect(数据库长链接),线程一旦建立在很长时间内都保持着。可是,在访问量比较大,机器比较多的状况下,这种用法极可能会致使“数据库链接数耗尽”,由于创建链接并不回收,最终达到数据库的max_connections(最大链接数)。所以,长链接的用法一般须要在CGI和MySQL之间实现一个“链接池”服务,控制CGI机器“盲目”建立链接数。redis

创建数据库链接池服务,有不少实现的方式,PHP的话,我推荐使用swoole(PHP的一个网络通信拓展)来实现。sql

3. Innodb缓存设置(innodb_buffer_pool_size)

innodb_buffer_pool_size这是个用来保存索引和数据的内存缓存区,若是机器是MySQL独占的机器,通常推荐为机器物理内存的80%。在取表数据的场景中,它能够减小磁盘IO。通常来讲,这个值设置越大,cache命中率会越高。

4. 分库/分表/分区。

MySQL数据库表通常承受数据量在百万级别,再往上增加,各项性能将会出现大幅度降低,所以,当咱们预见数据量会超过这个量级的时候,建议进行分库/分表/分区等操做。最好的作法,是服务在搭建之初就设计为分库分表的存储模式,从根本上杜绝中后期的风险。不过,会牺牲一些便利性,例如列表式的查询,同时,也增长了维护的复杂度。不过,到了数据量千万级别或者以上的时候,咱们会发现,它们都是值得的。

2、 MySQL数据库多台服务搭建

1台MySQL机器,其实是高风险的单点,由于若是它挂了,咱们Web服务就不可用了。并且,随着Web系统访问量继续增长,终于有一天,咱们发现1台MySQL服务器没法支撑下去,咱们开始须要使用更多的MySQL机器。当引入多台MySQL机器的时候,不少新的问题又将产生。

1. 创建MySQL主从,从库做为备份

这种作法纯粹为了解决“单点故障”的问题,在主库出故障的时候,切换到从库。不过,这种作法实际上有点浪费资源,由于从库实际上被闲着了。数据库

2. MySQL读写分离,主库写,从库读。

两台数据库作读写分离,主库负责写入类的操做,从库负责读的操做。而且,若是主库发生故障,仍然不影响读的操做,同时也能够将所有读写都临时切换到从库中(须要注意流量,可能会由于流量过大,把从库也拖垮)。后端

3. 主主互备。

两台MySQL之间互为彼此的从库,同时又是主库。这种方案,既作到了访问量的压力分流,同时也解决了“单点故障”问题。任何一台故障,都还有另一套可供使用的服务。缓存

 

不过,这种方案,只能用在两台机器的场景。若是业务拓展仍是很快的话,能够选择将业务分离,创建多个主主互备。

3、 MySQL数据库机器之间的数据同步

每当咱们解决一个问题,新的问题必然诞生在旧的解决方案上。当咱们有多台MySQL,在业务高峰期,极可能出现两个库之间的数据有延迟的场景。而且,网络和机器负载等,也会影响数据同步的延迟。咱们曾经遇到过,在日访问量接近1亿的特殊场景下,出现,从库数据须要数日才能同步追上主库的数据。这种场景下,从库基本失去效用了。

因而,解决同步问题,就是咱们下一步须要关注的点。

1. MySQL自带多线程同步

MySQL5.6开始支持主库和从库数据同步,走多线程。可是,限制也是比较明显的,只能以库为单位。MySQL数据同步是经过binlog日志,主库写入到binlog日志的操做,是具备顺序的,尤为当SQL操做中含有对于表结构的修改等操做,对于后续的SQL语句操做是有影响的。所以,从库同步数据,必须走单进程。

2. 本身实现解析binlog,多线程写入。

以数据库的表为单位,解析binlog多张表同时作数据同步。这样作的话,的确可以加快数据同步的效率,可是,若是表和表之间存在结构关系或者数据依赖的话,则一样存在写入顺序的问题。这种方式,可用于一些比较稳定而且相对独立的数据表。服务器

 

国内一线互联网公司,大部分都是经过这种方式,来加快数据同步效率。还有更为激进的作法,是直接解析binlog,忽略以表为单位,直接写入。可是这种作法,实现复杂,使用范围就更受到限制,只能用于一些场景特殊的数据库中(没有表结构变动,表和表之间没有数据依赖等特殊表)。

4、 在Web服务器和数据库之间创建缓存

实际上,解决大访问量的问题,不能仅仅着眼于数据库层面。根据“二八定律”,80%的请求只关注在20%的热点数据上。所以,咱们应该创建Web服务器和数据库之间的缓存机制。这种机制,能够用磁盘做为缓存,也能够用内存缓存的方式。经过它们,将大部分的热点数据查询,阻挡在数据库以前。swoole

 

1. 页面静态化

用户访问网站的某个页面,页面上的大部份内容在很长一段时间内,可能都是没有变化的。例如一篇新闻报道,一旦发布几乎是不会修改内容的。这样的话,经过CGI生成的静态html页面缓存到Web服务器的磁盘本地。除了第一次,是经过动态CGI查询数据库获取以外,以后都直接将本地磁盘文件返回给用户。网络

在Web系统规模比较小的时候,这种作法看似完美。可是,一旦Web系统规模变大,例如当我有100台的Web服务器的时候。那样这些磁盘文件,将会有100份,这个是资源浪费,也很差维护。这个时候有人会想,能够集中一台服务器存起来,呵呵,不如看看下面一种缓存方式吧,它就是这样作的。

2. 单台内存缓存

经过页面静态化的例子中,咱们能够知道将“缓存”搭建在Web机器本机是很差维护的,会带来更多问题(实际上,经过PHP的apc拓展,可经过Key/value操做Web服务器的本机内存)。所以,咱们选择搭建的内存缓存服务,也必须是一个独立的服务。

内存缓存的选择,主要有redis/memcache。从性能上说,二者差异不大,从功能丰富程度上说,Redis更胜一筹。

3. 内存缓存集群

当咱们搭建单台内存缓存完毕,咱们又会面临单点故障的问题,所以,咱们必须将它变成一个集群。简单的作法,是给他增长一个slave做为备份机器。可是,若是请求量真的不少,咱们发现cache命中率不高,须要更多的机器内存呢?所以,咱们更建议将它配置成一个集群。例如,相似redis cluster。

Redis cluster集群内的Redis互为多组主从,同时每一个节点均可以接受请求,在拓展集群的时候比较方便。客户端能够向任意一个节点发送请求,若是是它的“负责”的内容,则直接返回内容。不然,查找实际负责Redis节点,而后将地址告知客户端,客户端从新请求。

对于使用缓存服务的客户端来讲,这一切是透明的。

内存缓存服务在切换的时候,是有必定风险的。从A集群切换到B集群的过程当中,必须保证B集群提早作好“预热”(B集群的内存中的热点数据,应该尽可能与A集群相同,不然,切换的一瞬间大量请求内容,在B集群的内存缓存中查找不到,流量直接冲击后端的数据库服务,极可能致使数据库宕机)。

 

4. 减小数据库“写”

上面的机制,都实现减小数据库的“读”的操做,可是,写的操做也是一个大的压力。写的操做,虽然没法减小,可是能够经过合并请求,来起到减轻压力的效果。这个时候,咱们就须要在内存缓存集群和数据库集群之间,创建一个修改同步机制。

先将修改请求生效在cache中,让外界查询显示正常,而后将这些sql修改放入到一个队列中存储起来,队列满或者每隔一段时间,合并为一个请求到数据库中更新数据库。

 

除了上述经过改变系统架构的方式提高写的性能外,MySQL自己也能够经过配置参数innodb_flush_log_at_trx_commit来调整写入磁盘的策略。若是机器成本容许,从硬件层面解决问题,能够选择老一点的RAID(Redundant Arrays of independent Disks,磁盘列阵)或者比较新的SSD(Solid State Drives,固态硬盘)。

 

5. NoSQL存储

无论数据库的读仍是写,当流量再进一步上涨,终会达到“人力有穷时”的场景。继续加机器的成本比较高,而且不必定能够真正解决问题的时候。这个时候,部分核心数据,就能够考虑使用NoSQL的数据库。NoSQL存储,大部分都是采用key-value的方式,这里比较推荐使用上面介绍过Redis,Redis自己是一个内存cache,同时也能够当作一个存储来使用,让它直接将数据落地到磁盘。

这样的话,咱们就将数据库中某些被频繁读写的数据,分离出来,放在咱们新搭建的Redis存储集群中,又进一步减轻原来MySQL数据库的压力,同时由于Redis自己是个内存级别的Cache,读写的性能都会大幅度提高。

国内一线互联网公司,架构上采用的解决方案不少是相似于上述方案,不过,使用的cache服务却不必定是Redis,他们会有更丰富的其余选择,甚至根据自身业务特色开发出本身的NoSQL服务。

 

6. 空节点查询问题

当咱们搭建完前面所说的所有服务,认为Web系统已经很强的时候。咱们仍是那句话,新的问题仍是会来的。空节点查询,是指那些数据库中根本不存在的数据请求。例如,我请求查询一个不存在人员信息,系统会从各级缓存逐级查找,最后查到到数据库自己,而后才得出查找不到的结论,返回给前端。由于各级cache对它无效,这个请求是很是消耗系统资源的,而若是大量的空节点查询,是能够冲击到系统服务的。

 

在我曾经的工做经历中,曾深受其害。所以,为了维护Web系统的稳定性,设计适当的空节点过滤机制,很是有必要。

咱们当时采用的方式,就是设计一张简单的记录映射表。将存在的记录存储起来,放入到一台内存cache中,这样的话,若是还有空节点查询,则在缓存这一层就被阻挡了。

相关文章
相关标签/搜索