一个大型、成熟的分布式系统的背后,往往会涉及众多的支撑系统,也即所谓的分布式系统的基础设施,如第一章介绍过的分布式协作及配置管理系统Zookeeper,还有本章将要介绍的分布式缓存系统、持久化存储系统、分布式消息系统、搜索引擎,另外还有CDN系统、负载均衡系统、运维自动化系统等等。
本章一共分为四个小节,分别介绍了分布式缓存,持久化存储,分布式消息系统和垂直化搜索引擎。本章对这些分布式基础设施的介绍是一个整体性概述,更详细的内容将会在后续的读书笔记-大型网站系统与Java中间件实践中进行总结。
分布式缓存主要用于在高并发环境下减轻数据库的压力,提高系统的响应速度和并发吞吐。常用的分布式缓存方案有memcache、redis等。
Memcache既可以在单台机器上使用,也可以作为分布式缓存系统使用。Memcache使用key-value形式存储和访问数据,在内存中维护一张巨大的HashTable,使得对数据查询的时间复杂度降低到O(1)。内存的空间是有限的,当内存没有更多的空间来存储新的数据时,memcache就会使用LRU(Least Recently Used)算法将最近不常访问的数据淘汰掉。作为分布式缓存系统使用时,需要安装服务器端,具体的memcache分布式缓存系统搭建过程和使用方式网上资料很多。
使用缓存时需要注意的一些问题在另一遍文章中将会介绍-分布式缓存。
分布式缓存最典型的应用之一就是分布式session。
对于大型分布式网站来说,后端服务器是一个分布式集群,请求在不同服务器之间跳转。那么如何保持分布式环境下的session同步呢?传统网站一般通过将一部分数据存储在cookie中来规避分布式环境下session的操作,但是这样做得弊端很多,一方面cookie的安全性不高,另一方面cookie存储数据的大小是有限制的。随着移动互联网的发展,很多情况下还得兼顾移动端的session需求,使得采用cookie进行session同步的方式更显弊端,于是才产生了分布式session。
分布式session有两种方案,一种是持久化存储到DB中,可以保证宕机时会话不易丢失,但系统整体吞吐会受到影响。另一种方案是存储在分布式缓存服务器上。基于缓存的分布式session架构如下图所示:
持久化存储方案有传统的关系型数据库Mysql,也有非关系型的Nosql。
主从
在大型分布式系统中,持久化存储一般采用主从架构,以实现主从复制,读写分离。以Mysql为例,一台服务器作为主服务器,一台服务器作为从服务器。前端服务器通过主服务器Master来执行数据写入的操作,数据的更新通过Binary log同步到Slave集群,而对于数据读取的请求,则交由Slave来处理,这样Slave集群可以分担数据库读的压力。
主从复制架构也存在一个问题,即所谓的单点故障。当Master宕机时,系统将无法写入,而在某些特定场景下,也可能需要Master停机,以便进行系统维护升级。为了尽可能降低系统停止写入的时间,最佳的方式就是采用Dual-Master架构,即Master-Master架构。实际上就是两台Mysql互相将对方作为自己的Master,自己作为对方的Slave这样任何一台服务器上的数据变更都会通过Mysql的复制机制同步到另一台服务器。
当然Dual-Master架构并不是为了让两个Master能够同时提供写入服务,这样会导致很多问题,例如MasterA和MasterB几乎同时对一条数据进行了更新,对MasterA的更新比对MasterB的更新早,这样对MasterA上的更新会被同步到MasterB上,老版本的数据将会把版本更新的数据覆盖,并且不会抛出任何异常,从而导致数据不一致的现象发生。在通常情况下,仅仅开启一台Master的写入,另一台Master仅仅作为读库开放,只有当写库宕机或停机维护时才交换双方的角色。
分库分表
对于大型互联网应用来说,数据库单表的记录行数可能达到千万级别甚至是亿级,并且数据库面临这极高的并发访问,采用Master-Slave复制模式的Mysql架构只能够对数据的读进行扩展,而对数据库的写操作还是集中在Master上。
对于访问频繁且数据量巨大的单表来说,首先要做的是减少单表的记录条数,以便减少数据查询所需要的时间,提高数据库的吞吐,这就是所谓的分表。分表能够解决单表数据量过大带来的查询效率下降的问题,但无法给数据库的并发处理能力带来质的提升。此时可以对数据库进行拆分,从而提高数据库写入能力,这就是所谓的分库。分库和分表都可以采用通过一个关键字取模的方式,来对数据访问进行路由。
有时数据库可能即面临着高并发访问的压力,又需要面临海量数据的存储问题,这时需要对数据库既采用分库策略,又采用分表策略,以便同时扩展系统的并发处理能力,提升单表的查询性能,这就是所谓的分库分表。分库分表的策略比单纯的分库或分表的策略更为复杂,一种分库分表的策略如下:
举个栗子,假设有一张order表,拆分成2个库,每个库4张表,按照上面的路由策略,对于id=10001的记录,应该落在全局的第10001%8=1张表,第1/2=0个库,且是第0个库的第1%2=1张表。
数据分库分表后,虽然查询性能和并发处理能力提高,但也会带来一些问题,如原本跨表的事务上升为分布式事务;由于记录被切分到不同的库与不同的表当中,难以进行多表关联查询,并且不能不指定路由字段对数据进行查询等。
HBase是一种非关系型的持久化存储系统,是Apache Hadoop项目下的一个子项目,实现了高可靠性、高扩展性、实时读/写的列存储数据库。本质是一张稀疏的大表,用来存储粗粒度的结构化数据。
HBase运行在分布式文件系统HDFS之上,可以搭建大规模结构化存储集群。其数据以表的形式进行组织,每个表由行列组成,与传统关系型数据库不同的是,HBase每个列属于一个特定的列族,通过行和列确定一个存储单元,而每个存储单元又可以有多个版本,通过时间戳来标识,如下图所示:
HBase集群中通常包含两种角色,HMaster和HRegionServer。当表随着记录条数的增加而不断变大后,将会分裂成一个个region,每个region由(startKey,endKey)来标识,包含一个startKey到endKey的半闭合区间。一个HRegionServer可以管理多个Region,并由HMaster来负责HregionServer的调度及集群状态的监管。由于Region可分散并由不同的HRegionServer来管理,因此理论上再大的表都可以通过集群来处理。HBase的集群部署图如下:
与传统的关系型数据库相比,HBase有更好的伸缩能力,更适合于海量数据的存储和处理。而且由于多个Region Server的存在,使得HBase能够多个节点同时写入,显著提高了写入性能。但是HBase本身能够支持的查询维度有限,难以支持复杂查询,如group by、order by、join等,这些特点使得它的应用场景受到了限制。当然,通过第四小节将要介绍的搜索引擎在一定程度上可以解决这些问题。
在分布式系统中,消息作为应用间通信的一种方式,得到了广泛的应用。消息可以被保存在队列中,直到被接受者取出。由于消息发送者不需要同步等待消息接受者的响应,消息的异步接收降低了系统集成的耦合度,提升了分布式系统协作的效率,使得系统能够快响应用户,提供更高的吞吐。当系统处于峰值压力时,分布式消息队列还能够作为缓冲。
ActiveMQ是Apache所提供的一个开源的消息系统,完全采用Java来实现。JMS是一组Java应用程序接口,提供消息的创建、发送、接收、读取等一系列服务。JMS定义了一组公共应用程序接口和相应的语法,类似于Java数据库的统一访问接口JDBC,是一种与厂商无关的API,使得Java程序能够与不同厂商的消息组件很好地进行通信。
JMS支持两种消息发送和接收模型,分别是Point-to-Point和Pub/Sub。点对点模型基于队列(queue),而发布/订阅模型基于主题(toPic)。
对于点对点消息传输模型来说,多个消息的生产者和消息的消费者都可以注册到同一消息队列,当消息的生产者发动一条消息之后,只有其中一个消息消费者会接收到消息生产者所发送的消息,而不是所有消费者都能收到。
对于发布/订阅消息传输模型来说,消息的发布者需要将消息投递给topic,而消息的订阅者则需要在响应的topic进行注册,以便接收到相应topic的消息。与点对点传输模型不同的是,消息发布者的消息将被自动发送给所有订阅了该topic的消息订阅者。
垂直化搜索引擎在分布式系统中是一个非常重要的角色,它既能够满足用户对于全文检索、模糊匹配的需求,解决数据库like查询效率低下的问题,又能够解决分布式环境下,由于采用分库分表,或者使用NoSql数据库,导致无法进行多表关联或者复杂查询的问题。
垂直化搜索引擎主要针对企业内部的自有数据的检索。
Lucene是Apache旗下的一款高性能、可伸缩的开源的信息检索库。通过Lucene可以十分容易地为应用程序添加文本搜索功能,而不必深入地了解搜索引擎实现的技术细节以及 高深的算法,极大地降低了搜索引擎技术推广及使用的门槛。
搜索引擎的几个概念
倒排索引(inverted index)也称为反向索引,几乎所有的搜索引擎都会用到倒排索引。倒排索引提取文档的关键词,建立词与文档的映射关系,通过对倒排索引的检索,可以根据词快速获取到映射的文档。
分词又称为切词,就是将句子或者段落进行切割,从中提取出包含固定语义的词。相较于英文,中文分词较为复杂。中文以字为最小单位,多个字连在一起才能构成一个表达具体含义的词,而且词之间没有形式上的分隔符,因此对于支持中文的搜索引擎来说,需要一个合适的中文分词工具,以便建立倒排索引。中文分词器有一元分词器、二元分词器、词库分词器等多种。
停止词指使用频率高而无实际具体意义的词,如中文的助词“的”,“地”等。在进行索引的时候停止词应该被忽略。
排序,关键词搜索可能会命中很多文档,要将相关度更大的内容排在前面需要有适当的排序算法。至于如何判断相关度则有相应的规则,通常情况下命中标题的文档将比命中内容的文档有更高的相关性,命中多次的文档比命中一次的文档有更高的相关性。当然商业化的搜索引擎排序涉及广告、竞价排名等因素,规则更为复杂。
在Lucene中,文档是一系列域(Field)的集合,域是一系列域文档相关的内容,词(Term)是所有的基本单元。查询可能是Team的条件组合,称为TermQuery,也可能是短语查询(PhraseQuery)、前缀查询(PrefixQuery)、范围查询(包括TremRangeQuery、NumericRangeQuery)等。
Lucene索引的构建过程:通过指定的数据格式,将文档传递给分词器Analyzer进行分词,分词之后通过索引写入工具IndexWriter将索引写入到指定目录,如下图所示。
Lucene索引的查询过程:构建查询Query,通过IndexSearcher进行查询得到命中的TopDocs,然后通过scoreDocs()获取到对应的ScoreDoc,以此拿到文档编号。IndexSearcher通过文档编号使用IndexReade对指定目录下的索引内容进行读取,得到命中的文档后返回。
Solr是一个基于Lucene的功能强大的搜索引擎工具,它对Lucene进行了扩展,提供一系列功能强大的HTTP操作接口,支持通过Data Schema来定义字段、类型和设置文本分析,使得用户可以通过HTTP POST请求,向服务器提交Document生成索引,以及进行索引的更新和删除操作。Solr提供了一套表达式查询语言,能够方便实现字段匹配、模糊查询、分组统计等功能。同时Solr还提供了强大的可配置能力,以及功能完善的后台管理系统。
对这一章内容总结如下:
分布式系统的基础设施有很多,分布式缓存、持久化存储、消息系统和垂直化搜索引擎都是常用的基础组成部分。
分布式缓存主要介绍了memcache和redis,包括memcache使用的分布式策略和Hash算法的选择。
分布式存储解决方案介绍了关系型数据库Mysql,包括Mysql的分库分表。还介绍了文件存储系统HDFS中的HBase。
常用的分布式消息系统有ActiveMQ,Kafka,RabbitMQ等,本章主要通过ActiveMQ来介绍分布式消息系统的使用与集群架构。
垂直化搜索引擎主要介绍了垂直化搜索引擎的基本原理和开源检索工具Lucene,以及基于Lucene的Solr检索工具。