首页 > 代码库 > 读书笔记--大规模web服务开发技术

读书笔记--大规模web服务开发技术

总评     
     这本书是日本一个叫hatena的大型网站的CTO写的,通过hatena网站从小到大的演进来反应一个web系统从小到大过程中的各种系统和技术架构变迁,比较接地气。
     书的内容不是很难,所以总的来说比较容易阅读,不需要特别累的啃,可想而知,不是非常深入的,更多的还是把作者的一些经验写出来,hatena这种量级的在国内应该是一个中型网站的水平,作者基本把这个量级web服务的运维的方方面面都讲了一遍,看完可以对这个这种量级网站有一个总体的了解,个人认为还是值得一读的。
 
逐章读书笔记:  
第一章 大规模web服务的开发定位
     先给出这个网站的一些具体数据,好有个接地气的感觉,作者写作这本书的时候(2009年),hatena网站的大略规模数据是:
  •      注册用户100w以上
  •      1500w uv每月
  •      几十亿pv每月
  •      繁忙时流量430mbps
  •      服务器500台以上
  •      员工数量50人左右
     作者的定义是,几百一千台左右服务器的web系统是一个大型web系统,上万台的服务系统是超大型web系统。
第二章 大规模数据处理入门
     介绍了hatena的数据规模,最大的表数据是三亿五千万条记录,其他表记录是千万级别。
     大规模数据处理的难点是:数据无法在内存中计算,但是相对内存来说,磁盘又是很慢的,速度差异是10^5到10^6倍。另外内存、磁盘和CPU连接的总线速度也有差异,书里给的一个数据是内存的总线速度能达到7.5G/秒,但是磁盘的总线速度只有58MB/秒,相差两个数量级。
     web服务的扩展性:web服务的两种扩展方式:纵向扩展(scale up)--通过购买昂贵的高速硬件提升单机性能;横向扩展(scale out)--通过组合大量廉价低性能的硬件来提升性能。对于web服务而言,明显是后者更合适。
     web服务扩展的两个要点--cpu负载扩展和IO负载扩展,cpu只对请求做计算,所有容易扩展,只需要横向加机器然后前端负载均衡分摊到各个机器即可,所以cpu负载容易扩展;而IO负载典型的是数据库,由于扩展数据库会存在数据分散、复制以及一致性等问题 ,所以IO负载很难扩展,不能通过简单的加机器来解决。
     介绍了linux的sar命令,可以方便查看服务器的CPU和IO的负载情况。
第三章 操作系统的缓存和分布式
     由于磁盘和内存速度差距非常大,所以操作系统本身提供一些缓存机制来尽量减少磁盘读取,提升性能。
     操作系统的页面缓存:这块讲的不是很清楚,需要找操作系统相关资料详细了解。
     降低IO负载的策略:
     1. 缓存:缓存对于降低IO负载的效果无疑是很明显的。作者提到一个有趣的细节:是增加内存实惠还是投入人员开发一个更高压缩率的压缩算法实惠?作者给的说法是目前(2010年)内存硬件16G一下都是     很便宜的,超过16G甚至32G之后成本会陡增,所以如果你的数据明显16G内能够吃的住,就没必要投入大量人力研发算法,反之就需要人才投入来研发算法。
     2.单台缓存不够了,扩展多台:不过这里的扩展多台和应用服务器的简单复制式的扩展又不一样,给IO用的换成的扩展不是那种“简单增加机器就行”,需要考虑合理的分布式策略。作者说了如下几种策略:
     a. 根据数据表分区:将数据库以表为单位分割到多台服务器上。比如hatena bookmark有1 2 3 4四张表,把1 2放在一个服务器上,3 4放在一个服务器上,一个千万记录级别的表一般10个G以内,如果服务器是8G或者16G内存,基本能完全读入页面缓存中,不需要走磁盘IO。
     b.对大表做分表:将一个大表分割成几个小表,比如把ID首字母a-c的放在服务器1上,d-f的放在服务器2上,这样实现表的分割。这种分表方式也是目前主流的分表方式,不过会有缺陷,就是如果需要改变分割粒度时,比如分的更细,就需要对数据作一些整理。一般情况,都要伴随短暂的停机才能实现平滑过渡。
     c.“岛”模式的切分:岛比较抽象,其实就是做一些业务划分,把不同业务类型的数据归到不同数据服务器上,比如人的访问和爬虫的访问(通过user agent来识别),以hatena的bookmark为例,人的访问集中在少数的热门书签上,这样缓存命中率很高,而爬虫是一直爬,所以会爬很冷门的书签,这样就会降低缓存命中率,如果两个混在一块,缓存命中率就下降了,所以要分开。而且,作者说,bookmark的爬虫访问比人访问还多。
     作者还讲到一个细节,就是服务器刚启动不要投入生产环境,因为操作系统还没换成,需要把数据库文件cat一遍,做预热。而且在做性能测试的时候,第一次的测试结果也要废弃,因为没有缓存,是不准的。
     本章的最后,作者指出,在负载均衡方面,操作系统是基础知识,需要深入了解操作系统的运行原理,比如操作系统的缓存、进程、县城、虚拟内存、文件系统等知识。
第四章 数据库的横向扩展策略
     这一章主要讲数据库的横向扩展策略,hatena主要使用LAMP(linux apache mysql perl)架构,所以主要数据库是mysql。
     作者提出分布式mysql的三大要点:
     1. 灵活使用操作系统缓存:
          a.考虑全部数据量,尽量让数据量小于物理内存,这样可以利用操作系统的页面缓存,如果内存不足并且增加内存成本不高,就增加内存
          b.考虑表结构的设计对数据大小的影响,这里作者举了个例子:hatena bookmark表有三亿个记录,如果增加一个大小为8字节的列,8*3亿就是3GB,所以大数据量的表,紧凑的表结构能明显降低数据量
     2. 正确设置索引
     mysql的索引数据结构主要是B+树,是B树的一种扩展,能将磁盘寻道次数最小化,具体数据结构资料另外找资料阅读
     另外作者讲了mysql索引相关的一些细节,比如mysql一次查询只能使用一个索引,所以如果是多条件查询就要设置复合索引,另外还讲了下mysql的explain命令
     3.以横向扩展为前提的设计
     讲述了hatena的数据库架构,分成master库和slave库,通过mysql的replication功能,master库数据polling到slave库,通过or框架将写的都路由到master库,而查询的路由到slave库,这样实现了读写分离。这样的结构好处是查询扩展很方便,只要增加slave服务器就可以,因为所有slave都是全亮复制的,但是master比较难扩展,需要采用分表的策略来实现master扩展,不过作者也说,web服务查询比写入高的多。所以查询更容易成为瓶颈。
     master的扩展需要用到partition、分表等策略。对于partition而言,就是把不同的表放在不同的服务器上,但是这样的话,就无法做join了(貌似join没法跨服务器?),这种情况就改成先查a表再用a的结果集查b表就可以了(利用where...in...的语句来实现)。
     partition虽然可以实现扩展,但是也是有代价的:1.运维变的复杂,不同数据库分布在不同服务器上,提升了运维成本。2.机器多了,故障率就会上升。3.增加新服务器都需要同时增加slave服务器,机器数会增加好几台。
第五章 大规模数据处理实践入门
     这一章没太多内容,主要提了一些大规模数据处理和文本分析入门知识,基本都是带过,给后面章节埋个伏笔,具体内容在后面章节详细展开。还讲到一个细节,这个细节估计很多有过海量数据网站经验同学也有感觉的,就是在海量数据的存储和查询的时候,很多是违反数据库三大范式的,比如尽量尽量避免做join,笛卡尔运算形成根本吃不消,所以需要做适当冗余,来降低关联查询之类的。
第六章 压缩编程
     作者抛出一个课题:对一个152M大小的保存整数的csv文件做压缩,变成二进制,把数据大小缩减到一半一下。
     作者阐述了利用可变字节码来压缩的思路和伪代码。具体的可以搜资料了解。
第七章 算法实用化
     作者阐述了算法复杂度的一些知识,O(n)负责读和O(logn)复杂度的差别,等等。不过作者也通过hatena bookmark的一个例子来说明,降低复杂度是一种理想的情况,但是实际中还是要结合实际,在满足实用的情况下,简单就是美,也就是我们平时常说的,不要过度设计。
     hatena diary的关键字加链接处理。作者举了这个例子来说明算法演进提升系统服务能力。关键字加链接就是把博客中一些特定词语加上一些类似wiki的解释链接或者相关链接,这种大家在看各大新闻网站的时候都能看到。一开始关键字数量不多,他们就用正则匹配的方式来做,就是把关键字组合成or的正则表达式(foo|bar|...)类似这样,然后去匹配文本,匹配到就加链接,由于正则表达式是基于状态机实现的,所以匹配性能很差,会逐个单词去匹配文本,是O(n^2)的复杂度,所以随着词库增加性能就吃不消了,于是作者提出把正则表达式改成trie树,改变匹配方式来提升性能,trie是一种单词查找树,通过把公共前缀集中到一起,来提升匹配性能,正则表达式中,搜索的时候是把每个单词去文本里匹配一次,而在trie树中,搜索是把文本放到trie树中去匹配,只需要顺序匹配一次即可。不过最后,作者采用了另一种更高效的算法,叫做aho-corasick算法,这个算法是trie树匹配的一种改进版本,具体内容大家可以搜索下。通过这个实例作者提出了他的思路:一开始使用简单快捷的方式先实现功能上线,等到达到相应复杂度之后再做优化,也就是我们说的不要过早优化过度优化,应该小步快跑。
     hatena bookmark文章分类处理。作者根据这个场景讨论如何用算法解决新问题。hatenabookmark提供自动分类功能,就是系统获取文本内容然后判断是哪个分类。主要用到贝叶斯过滤器,使用朴素贝叶斯算法从概率上判断文章属于哪个类别,这个的前提是事先已经通过机器学习告诉贝叶斯过滤器什么样的文章属于哪个类别,这块内容数据机器学习领域,跟现在流行的大数据比较沾边。
     本章后面附了一个专栏,写的是拼写错误改正功能的算法实现,这个功能就是搜索引擎中的“你是不是要找”功能,你输入了一个错误的词,搜索引擎会帮你纠错。hatenabookmark也有类似功能,主要思路是计算搜索查询和字典中词语的编辑距离(从错的改成对的次数),比如博客元-博客园,纠错的距离就是1,编辑距离可以用动态规划算法实现,如果字典中存在编辑距离短的就是认为可以推荐纠错的。当然搜索引擎的纠错比这个要复杂的多。
第八章 hatena关键字链接的实现
     这一章主要讲了上一章提到的关键字链接aho-corasick算法的具体实现,细节就不细表了。
第九章 挑战全文搜索技术
     这一章主要讲解了搜索引擎的倒排索引技术,基本原理大家应该都懂的,不细表。
第十章 创建全文搜索引擎
     这一章以一个实际的例子讲解了一个简单搜索引擎的创建,代码是用perl写的。对perl不熟...掠过...
第十一章 支持大规模数据处理的服务器/基础设施入门
     从十一章开始,作者主要讲一些基础设施相关的内容。
     作者把web服务和传统企业软件服务作了对比,企业软件流量低但是严谨性要求很高,web服务容易产生流量爆发,严谨性相对而言没那么高。
     作者还提到了云服务和自行构建基础设施的对比,这本书日本出版的时候是09年,从这里也可以看出日本在互联网领域的领先,国内应该还是最近两三年开始高呼云的。作者对云的看法是小公司很适合云服务,比如亚马逊的EC2等,因为云的特点是灵活和可扩展,但是大规模的公司就需要自己搭建服务,因为云服务在大数据量下怕会出现一些极限的不可控。
第十二章 保证可扩展性的必要思路
     本章作者列举了一个服务器的支撑能力,一台4核8G的服务器,每分钟处理几千个请求,那么每个月能处理100w级别的PV,就是说百万PV以内一台服务器可以撑得住。
     另外就是粗略的讲解了web服务不同层的扩展思路,应用层由于无状态,所以加机器扩展,另外就是根据不同用途扩展,面向爬虫的和面向真实用户的采用不同的策略等。观察负载主要看load,根据经验值,load不要超过CPU的核数都是ok的。比如四核CPU的,load不要超过4.
第十三章 保证冗余性和系统的稳定性
     作者提出,系统稳定性方面,最重视的就是单点稳定,即避免系统出现单点故障(指没有冗余,一旦故障就停止服务没法切换)
     应用程序服务器的冗余:通过负载均衡来分配流量,并且负载均衡器对服务器进行心跳检查,来判断机器是否正常服务,并且实现故障服务器的自动下线(不放流量)和自动恢复(重新放流量),这样来保证整个集群的正常服务。
     数据库服务器的冗余:这块的冗余大家熟知就是主备冗余,有master和slave,如果master挂了就立马切到slave。
     存储服务器的冗余:存储服务器主要是来保存图片等媒体文件,hatena的存储服务器采用的是第三方的分布式存储服务器叫做MogileFS,本身会有容灾功能。
     对于维护系统稳定性,作者提出两条大的对策:1.维持适当余量,不要榨干服务器,总是满负荷,这样容易出现雪崩,作者提出极限的70%,就是说正常服务的时候不要占用系统70%的资源,超过就要增加服务器。2.消灭不稳定性因素,比如消灭各种潜在的内存泄露,慢sql等因素。3.发生异常时的自动控制,比如有些程序会存在潜在的内存泄露,这种情况一般服务器刚启动没问题,但是慢慢内存耗尽,这种时候如果发现频繁的swap,就对web服务器自动重启(利用了monit这个工具来监视系统负载、内存等状况并适时执行apache重启甚至操作系统重启),这样虽然不能解决问题,但是能保证不宕机,还有就是定期(10s)检查数据库服务器上执行的sql查询并且杀掉慢查询,等等。
第十四章 提高效率
     这里特指提高硬件资源的利用率。
     作者在上一章提到不能让服务器超过70%的正常负载极限以及需要冗余,这必然导致一些资源的浪费,这一章作者就提出在适度冗余的前提下,尽可能的提高资源利用率。
     作者提出提高硬件利用率的思路:虚拟化技术。说白就是虚拟机,在一台物理机上虚拟出多太虚拟机,这样可以提高物理机的整体资源利用率,目前国内的大型网站也普遍采用这种方式,淘宝就是采用一虚三的方式来做虚拟机。hetena采用的虚拟技机技术是Xen。但是虚拟机会肯定会带来额外的开销,作者给出的数据是:cpu2%-3%,内存10%,IO降低5%,但总的来说虚拟机带来的利用率提升和运维上的方便很值得这些额外的性能付出。
     这一章作者还讲了hatena服务器硬件的一些东西,为了降低成本,很少采用价格昂贵的专门服务器,而是以自己组装廉价PC机为主,另外就是尝试采用SSD来提高服务器IO性能。
第十五章 web服务和网络
     这章主要讲了三个分界点:1.流量1Gbps,这个也是千兆网卡的极限,这个时候单个路由器支撑不住,要么买昂贵的专业路由器,要么搭建多个路由器。2.500台主机,当主机到500台后,子网的arp表会基本达到极限,再上去就容易丢包,所以一个子网的主机尽量控制在500台以内。3.全球化,如果有欧美用户访问hatena,那么请求讲跨越太平洋,这种延时就能难接受了,所以hatena的一些下载服务采用CDN的方式分布到全球,这样静态资源就可以通过CDN来下载了,hatena用的CDN服务是amazon cloudfront.
第十六章 当前构建web服务需要的实践技术
     这一章作者主要讲了一些大型web服务都会用到的中间件系统,包括异步队列系统、大数据存储系统、缓存系统、大数据计算集群等。
     作业系统:大型web服务的一个请求后面会处理很多事情,会跟很多分布式系统打交道,如果整个流程都是同步处理的,那么链路就会很长,处理耗时也很长,qps就会很低,而且链路长会导致出问题风险也增加,所以就会把一些可以异步处理的流程剥离出来交给队列系统异步处理,这样同步请求就不需要等待处理完成,提升主流程稳定性以及响应时间。作者举了个栗子,比如hatena的bookmark,用户收藏了一个url后,他们会异步的去拿url的概要、提取关键词、分类处理等。这些处理有的很耗时,卡在主流程会影响用户体验。最简单的作业系统就是调用一个单独的处理脚本,但是在大型系统里不合适,hatena用的队列系统主要是TheSchwartz和Gearman。
     存储系统:主要对比了RDBMS和NOSQL(key-value存储),应该说,这两个本身就决定了不同的用途,使用场景的差异显而易见。hatena用的RDBMS主要是MySQL,没说用哪个nosql,不过目前国内持久性nosql用的比较多的应该是Hbase,mongoDB也有些人再用。缓存系统一般用memcached.
     代理/缓存系统:主要讲了http的反向代理服务器搭建,主要讲了squidhe varnish这两个反向代理服务器搭建和对比,作者指出,合理搭建反向代理服务器对提升整体性能很有帮助,反向代理的缓存系统能邮箱的减轻后端应用服务器的压力,这两个是目前最流行的反向代理系统,相关资料也很多。
     计算集群:大规模web服务都需要做很多离线计算,最典型的入日志计算,比如算uv、pv等,都要对及时上百GB的日志文件做处理,所以需要搭建专门的计算集群。