迁移到Cassandra——why,what&how

作者: yangzhe1991 分类: 我是搞技术的 发布时间: 2014-05-01 19:06 ė 62条评论

俺们公司infra组员工纷纷跑路之后,自己山寨的轮子基本都停止维护,作为一个全套山寨了GFS、MapReduce、BigTable的公司,野心是有的,可惜终究敌不过现实。不过当初公司成立的时候hadoop还没成型甚至好像还没出,不造轮子也不行。有时候就是这么尴尬,恰巧处在一个必须用却没有现成东西可用的年代,再加上码农都有造轮子的梦想,于是就造了,于是也一发不可收拾。现在的创业公司真是比当年幸福多了,机器有云计算VPS,架构有各种开源的东西还算靠谱,开发成本低,都用开源的从各个公司招人也方便上手。

言归正传吧。因为公司自己山寨的BigTable停止维护,而这玩意又确实隔三差五挂一回,保险起见自然是要找开源的成熟方案。正常情况下最接近我们公司造的轮子的自然是hbase,但我们公司不用hadoop、hdfs,如果上hbase的话还得搭hdfs(ZooKeeper倒是有了),相当于多维护了一套分布式文件系统。而且,除了hdfs的节点之外其他机器是只提供服务不提供存储的,初期使用新数据库肯定不会有大量的机器,而集群的机器越少那几台不能提供数据容量的机器就越显得浪费(公司的大部分机器都是固定x块硬盘,定制硬盘数量倒不是不行但毕竟显得麻烦)。所以总体上并不倾向于用hbase。

于是就把目光投向了Cassandra。

Cassandra基于Amazon的Dynamo,去除中心化的gossip协议来做分布式。P2P的结构去中心化无任何单点问题,Gossip协议的原理就是每个周期内每个机器随机选一个其他机器来互相把彼此的消息同步,拿到新的扔掉旧的。同步的内容可以理解为各种元数据的信息,所以元数据并不是实时同步到所有节点上也就是说并不是强一致性而是最终一致性。最简单的例子说,1秒前建了一个表,1秒后还有很多机器不知道这个表被创建因此往这个表插入数据还很有可能报错。不过像增减节点、增减数据库和表这样的操作并不是频繁发生,所以在这方面最终一致性影响应该不会很大。每个节点维护完整的元数据这样每个节点都可以接受客户端的任何请求,他知道请求的数据应该放在哪个或哪几个服务器,所以就可以作为代理。保存元数据的同时也保存一部分的数据,所以Cassandra里只有一个类型的服务器——node。这样就可以保证所有服务器的磁盘空间完全相同时磁盘的利用率最大化,也减少了多种类型服务器导致的维护成本。

和Dynamo一样,Cassandra采用一致性哈希来判断一个数据该放在哪个服务器,但Cassandra引入了“virtual node”策略——即一个物理节点在环上可以代表多个节点(默认为256)——而从概率上讲节点越多数据分布和对应导致的负载都会越容易更均衡。当然,靠概率的分布肯定是没有BigTable/HBase的region server自动负载均衡的效果好了,而且相邻的key数据也不在一起意味着无法扫表。

也和Dynamo一样,每个数据存几份是可配置的。而Cassandra实现了一套跨机房的解决方案,可以指定每个机房存几份之类的。

还和Dynamo一样,每次读或写时要求操作成功的节点数也是可配置的。所谓成功的定义是:读出来算成功,写进去而且即使这个机器挂了也不会丢数据(即不只是把数据放内存)为成功。如果配置读成功数为R才返回给客户端、写成功W份告知客户端,而数据存了N份,理论上W+R>N可实现强一致性,否则是最终一致性。写数据的时候被客户端连接的服务器会同时把写操作告知N个服务器,如果有服务器写操作超时(比如挂了或者load太高),这个数据会暂时放在其他服务器保留一定时间(Hinted Handoff‎),如果该节点恢复则把数据导过去,如果一段时间后还没恢复那么不会再替他暂存,这个数据会只有N-1份。需要手动的repair操作来检查每个数据是否在N份中都有。因为Hinted Handoff‎会专门为一个节点保存所有他需要在重启之后写入的数据,因此可以直接批量导入加快节点重启之后的恢复速度。但要注意Hinted Handoff并不是算写入成功,因此如果设W=N那么如果有一个节点挂了就会返回超时无法写入成功,读取也是。所以如果想要强一致性也想要可用性那最佳方案只能是N=3,W=R=2。豆瓣自己搞了个Bean DB也是Dynamo的模型,我记得某个地方说他们是W=1,R=3,不知道他们是怎么处理某个节点挂的情况的。‎

除了Dynamo的模型之外,又引入了BigTable的数据模型——所谓的wide column——和KV模型比除了Row Key之外还有个Column Key,相当于map<key,value>中的value也是个map,而且是有序的SortedMap。因为Cassandra的RowKey无序因此Column Key的查询是唯一可以高效返回有序结果或按大于小于等条件查询的。

Cassandra的存储方式和BigTable也类似,写入数据时记log保证持久化然后再memTable中更新,内存中的memTable满了就把数据按key有序持久化成SSTable,SSTable多了就合并。读数据时先用bloom filter减小SSTable的搜索范围,然后在“可能有数据”的SSTable中找数据,有就返回。具体SSTable的合并方式可以是传统的把大小差不多的若干小文件合并成一个大文件,也可以是level-db引入的多级大小的方式。

Cassandra不知道在哪个版本开始引入了一种类似SQL的查询语言——CQL,这东西的引入把这个noSQL数据库尽可能的向关系型数据库用户套近乎,在CQL中虽然没把keyspace改叫database但把column family改叫table,而且把column key旋转九十度变成table中的一个行,这样一个row key多个column key就变成了多个primary key。当然本质上他还是noSQL。

因为Hadoop的普及度,HBase在很多公司都是顺理成章的东西。但对我们公司、没有了infra专业支持之后的我们组来说,单独上一个复杂的系统风险实在有点大而且属于平白无故多找活干(旧系统挂了又不是我们的问题但自己找个新系统换完挂了就是我们的事情了……),所以一定要简单。当然我们的旧数据库也是支持扫表操作而Cassandra不支持,所以迁移的时候一些表要重新设计(好在没发现必须扫表才能搞的逻辑)。而我们也在初期先迁移一些单纯的key-value系统。

而在调研先拿哪个服务当小白鼠迁移到Cassandra时,理了下现有系统的架构。我们词典的核心服务当然是查词、查例句(因为翻译是专门一批人做因此不包括翻译)等其实都是只读的服务,数据定期生成之后就不会在线进行写操作全是只读。当然有时候也会遇到特别急迫的修改数据的需求——比如发现某个词释义不对或者某个从网页抓取的网络释义和例句遇到了敏感词——就再搞一套服务在内存里存着临时编辑的内容,每次查询过一遍这个表然后在本地查只读数据。如果一个机器放不下所有数据了就按hash分成2份重新做数据做索引,再放不下就改成三份,以此类推到N份。如果性能撑不住或者怕服务器挂掉就放M套各N份的服务器作为数据服务,每个web服务器根据一定策略从每套数据服务轮询地查询。实际上这就是个简单的分布式系统,一定程度上当初设计成这样的架构是因为当初没有靠谱的分布式数据库可用只能自己造轮子(所以晚创建的公司真的很幸福),当然还有一些特殊的需求的原因,比如例句其实是全文索引的查询而且要有ranking而不像英汉词典释义完全就是KV结构。所以其实对于单纯的只读KV查询,这种硬盘/机器分片访问只读文件+内存放修改内容的模式完全可以都放到分布式数据库,而且因为只在做数据和遇到敏感词要删东西的时候才会有写操作也对一致性完全没要求,最终一致性足矣。所以为了保证可用性,这类数据只存两份,R和W都设为1即可比之前的系统更好的满足需求——每个服务N套集群改成了一个大集群,也因为可以随时修改数据,更节约机器资源,也让代码的逻辑和结构更简单。

当然这都是对Cassandra的初步使用和调研,还没遇到什么坑(当然但愿别遇到……),在查资料的时候也深感业界用的比HBase少导致可用的资料少尤其是新资料少(搜到很多地方还是在说新建keyspace要重启集群而这都是好几年前0.xx版的事情了),而maillist也明显不如hbase活跃。Apache官方的文档非常少,实际的文档都是基于Cassandra提供商业数据库服务的公司DataStex在维护以至于都当做官方文档了。所以就先这么用着吧,应该也没有基于他再开发的需求(毕竟不是专门做基础架构的都还有好多其他事情要搞),只要每年停止服务的总时间比之前的数据库低就算没白忙活……

本文出自 杨肉的演讲台,转载时请注明出处及相应链接。

本文永久链接: https://yangzhe1991.org/blog/2014/05/cassandra/

2条评论

  1. 1957 2014 年 6 月 19 日 02:58 回复
    Google Chrome 34.0.1847.134 Google Chrome 34.0.1847.134 Google Chrome OS x64 Google Chrome OS x64

    orz

  2. Pingback: Cassandra轻量级事务(compare-and-set, CAS)的实现 | Philo's philosophy

发表评论

邮箱地址不会被公开。 必填项已用*标注

Ɣ回顶部