[转载] 360分布式存储系统Bada的设计和应用

原文: http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=208931479&idx=1&sn=1dc6ea4fa28a3fb527a6204a9a5c23b1&key=c76941211a49ab5849fe180925fd9816350457f931e54a80feca07c081bffea5828ae0bbb2b1f7be41501db7dea48977&ascene=0&uin=Mjk1ODMyNTYyMg%3D%3D&devicetype=iMac+MacBookPro9%2C2+OSX+OSX+10.10.3+build(14D136)&version=11020012&pass_ticket=XBh3IrGvu4uh9iuU9u54bHyVlV1hde36lZbrQOc%2F4ICtKWbJji6PTss8d%2FMelkMW前端

此文根据【QCON高可用架构群】分享内容,由群内【编辑组】志愿整理,转发请注明来自“高可用架构(ArchNotes)”微信公众号。git

陈宗志:奇虎360基础架构组 高级存储研发工程师,目前负责360分布式存储系统Bada的设计和实现,同时负责360虚拟化相关技术的研究。github


 

本次分享主题

主要向你们介绍一下360自主研发的分布式存储系统Nosql-Bada,做为设计者我一直以为设计过程就是在作一些折衷,因此大部分的内容是咱们开发实现Bada过程当中的一些经验和坑, 也有不少的权衡, 但愿和你们一块儿分享, 有不对的地方欢迎指出。redis

 

虽然项目目前还未开源, 可是咱们的一些组件, 用于异步同步数据的Mario库等, 均已经开源,后续Bada也会开源。这是360官方的Github帐号https://github.com/Qihoo360sql


主要应用场景

咱们的定位是海量数据的持久化存储, 为线上的热门应用服务。不过咱们目前没有接入跟钱相关的业务, 由于咱们的系统毕竟是最终一致性的系统。数据库

 

咱们倾向使用Bada的用户数据value的大小在10k之内, 那么咱们的延迟可以作到1ms左右。咱们为了读取性能有必定的优点, 通常要求机器都挂载SSD盘。若是用于存储冷数据, 咱们会建议用户存数据到公司的其余存储产品, 好比hbase,cassandra等等。后端

 

目前公司内部云盘, 移动搜索, LBS, Onebox, 导航影视, 白名单等多个业务均在使用。微信

 

云盘的场景是:全部上传, 下载文件的时候经过Bada查询文件所在的集群。这个业务数据量大概600亿, 其中某一个机房的访问量15亿。网络

 

 

LBS这个业务是将全部的POI的信息存储在Bada中, 业务须要在5个机房进行数据同步。天天的请求量大概7亿。数据结构

 

 

总体架构

Bada SDK 是咱们提供给用户SDK, 360 QConf 配置管理服务 你们以前也了解过, 咱们是QConf的重度用户。用户经过SDK从QConf中得到存活的Bada节点, 而后进行访问。

 

Data Server是咱们的服务节点,其设计是学习自Amazon Dynamo(不过好像Dynamo 自己也被不少人喷), 每个节点都是对等结构, 每个节点存储了全部的元信息。为何这么作?

 

目前主流的设计通常是两种:

  • BigTable 为表明的, 有MetaServer, DataServer的设计, MetaServer存储元数据信息, DataServer存储实际的数据。包括 BigTable, HBase, 百度的Mola等等。

  • Dynamo 为表明的, 对等结构设计. 每个节点都是同样的结构, 每个节点都保存了数据的元信息以及数据. 包括 Cassandra, Riak 等等。

     

Bada 的选择

其实我以为两个结构都是合适的。为了部署, 扩展等方便,咱们不但愿部署的时候须要分开部署Meta节点, Data节点。计算机行业, 加一层能够解决大部分问题, 所以咱们以为对等网络的设计更有挑战性。我的观点, 在数据量更大的状况下, Meta 节点极有可能成为瓶颈。固然Dynamo的结构确定也有自身的缺点, 好比如何保证元数据的一致性等问题。

 

Data Server主要模块

  • Network Proxy: 用于接收客户端的请求, 咱们的协议是定制的protobuf 协议, Network Proxy模块负责解析协议, 而后请求转发到对应的节点

  • Meta Info: 用于存储公共的元信息, 元信息包括每个分片存储在哪一个节点

  • DB Engine: 咱们底下的引擎是基于LevelDB的定制化开发, 包括支持cas, 过时时间, 多数据结构等等

     

数据分布策略

能够看到咱们目前使用的是有主从的副本策略, 图中的Primary 是主节点, Secondary 是从节点。为何这么作?

 

首先为何不使用ec编码(erasure code 纠删码), 由于ec编码主要用于保存偏冷数据, ec编码遇到的问题是若是某一个副本挂掉之后, 想要恢复副本的过程必须与其余多个节点进行通讯来恢复数据, 会照成大量的网络开销. 所以这里3副本更合适。

 

常见的分布式系统的多副本策略主要分红两类:

  • 以Cassandra, Dynamo 为主的, 没有主从结构的设计, 读写的时候知足quorum W + R > N, 所以写入的时候写入2个副本成功才能返回。读的时候须要读副本而后返回最新的。这里的最新能够是时间戳或者逻辑时间。

  • 以MongoDB, Bada为主的, 有主从结构的设计, 那么读写的时候, 客户端访问的都是主副本, 经过binlog/oplog 来将数据同步给从副本。

     

两种设计都只能知足最终一致性。那么咱们再从CAP理论上看, 那么都是在哪些维度作了权衡?

  • 从性能上来看,有主从的设计很明显性能会因为无主从的, 由于有主从的设计只须要访问一个副本就能够返回, 而无主从的至少两个副本返回才能够。

  • 从一致性来看,有主从的设计若是挂掉一个节点, 若是这个节点是主, 那么就会形成因为数据同步的不及时, 这段时间写入的数据丢。若是挂掉的是从节点, 那么则对数据没有任何的影响。只要这个节点在接下来的时间内可以起来便可。无主从的设计若是挂掉一个节点, 理论上对结果是无影响的, 由于返回的时候会比较最新的结果。有主从的结构因为写入都在一个节点, 所以不存在冲突。而无主从的结构因为写入的是任意的两个副本, 会存在对同一个key的修改在不一样的副本, 致使客户端读取的时候是两个不一致的版本, 这个时候就须要去解决冲突, 常见的方案就涉及到vector clock, 时间戳等等。不过, 整体来看无主从的设计一致性应该优于有主从的设计。

  • 从分区容错来看, 两边都必须有一半以上的节点存活才可以对外提供服务, 由于有主从的设计中必须得到超过一半节点的投票才能成为主节点。而无主从的结构, 常见在W = 2, R = 2的状况下, 必须2个副本以上才能对外提供服务。

  • 从可靠性来看,有主从的设计由于只访问一个副本, 性能优于无主从的设计。并且无主从的设计中, 由于对单条数据必须有两次读取, 所以对系统的访问压力也会比无主从的来的多。固然有主从的设计容易形成主落在同一个机器上, 形成负载不均的状况, 可是这里只要将主平均到全部的机器, 就能够解决这个问题。可是有主从的设计在切换主从的时候, 必然有一段时间没法对外提供服务, 而无主从的设计则不存在这样的问题。整体来讲, 笔者认为从可靠性的角度来讲, 有主从的设计应该比无主历来的可靠。

     

咱们使用的是有主从结构的设计, 缘由:

  • Bada主要的应用场景对性能的要求比较高, 大部分的请求须要在1ms左右的时间返回, 所以有主从的设计, 性能更知足需求

  • 线上服务的可靠性是咱们另一个考虑的因素

  • 具体的分析过程能够看 http://baotiao.github.io/2015/03/Bada-design-replicaset/

     

数据分片策略,咱们叫两次映射.

  • key -> PartitionId(hash)

  • PartitionId -> Node(MetaData)

好比上面这张图中咱们能够看出, 咱们将全部数据分红10个Partition, 而后每个机器存有主节点和从节点. 咱们会尽量的保证每个机器上面的主节点是同样多的, 这样可以作到每个节点的负载都是均衡的。

 

请求流程

  • 当请求的数据Primary正好是当前这个节点

  • 当请求的数据Primary 不是当前节点

 

多机房架构

360的机房是比较多的, 并且某些机房之间的网络较差。业务部署一个服务的时候, 后端的DB也须要部署在多个机房上, 所以这个经常是业务的痛点。所以咱们设计之初就考虑多机房的架构。

 

咱们的多机房架构能保证

  • 用户不用管理多个机房, 任意一个机房数据写入, 其余机房可以读取

  • 在机房存在问题的时候, 咱们能够马上切换机房的流量

  • 提供每个机房之间数据的统计和Check

     

总体实现

这个是目前LBS业务的场景

能够看出咱们这里有一个专门的队列用于同步机房之间的数据。这个QBus 是咱们团队内部基于kafka开发的消息队列服务。

 

目前主流的机房同步方法也是两种:

  • 节点负责机房数据的同步, 好比Cassandra, CouchBase, Riak

  • 由外部的队列来同步机房之间的数据, 好比 Yahoo pnuts

     

Cassandra 作法

在写入的时候, 每个机房的协调者。好比这个图里面10这个节点。会把写入发送给其它机房的某一个节点, 这个时候Client这边收到的只是根据配置的一致性级别就能够返回, 好比这里配置的只要1个返回便可, 那么Client写入成功10这个节点之后,便可返回。至于与其余机房同步是10这个节点的事情, 这样子客户端的写入就能够在本地写入, 不用管多机房的latency。

 

这里咱们能够看到是Eventual Consistency. 那么Cassandra是如何作到冲突修复的呢. 这里Cassandra 读的时候有一个Read Repair 机制, 就是读取的时候读取本地多个副本. 若是副本不一致, 那么就选时间戳最新的从新写入. 让数据从新同步, 这里Cassandra只是说修复本地多副本数据不一致的方法, 一样的方法咱们也能够用在多个IDC里面, 能够同时跑多个任务check不一样机房的数据, 而后修复他们。

 

CouchBase 作法

Continuous Replication提供配置的不一样Server之间同步的Stream的个数,也就是不一样的机房之间链接的数目是可配置的。解决冲突办法.CouchBase提供的是最终一致性的方法,不一样的版本之间首先根据修改的次数, 而后是修改时间等信息。

 

咱们最后考虑的是使用团队内部的QBus做为咱们通讯的队列, 主要考虑

  • 省去了本身实现队列的麻烦

  • 稳定运行于线上, 有专门的同事维护. 减小的不少问题

     

Bada 目前线上3种多机房的使用场景

  • 单机房写入, 任意机房读取

  • 跨机房写入, 任意机房读取

  • 任意机房写入, 任意机房读取

咱们的实现方案也是经过QConf来实现。客户端访问的时候, 从QConf中读取目前须要访问的机房, 默认是访问本机房, 若是须要跨机房访问, 将QConf中的配置制定成须要访问的机房就能够了。

 

多机房写入的冲突解决方案

时间戳最新

任意机房写入数据, 根据时间戳来进行冲突解决。

 

Yahoo Pnuts Primary Key

这里咱们对每个Key 有一个Primary IDC, 也就是这个Key的修改删除等操做都只会在当前这个IDC完成, 而后读取能够有多个IDC去读取. 那么由于对于同一个Key的修改, 咱们都在同一个IDC上. 咱们经过给每个Key加上一个Version信息, 相似Memcached的cas操做, 那么咱们就能够保证作到支持单条数据的事务。若是这条数据的Primary IDC是在本机房, 那么插入操做很快。

 

若是这条数据的Primary IDC不是本机房, 那么就有一个Cross IDC的修改操做, 延迟将会比较高。不过咱们考虑一下咱们大部分的应用场景,好比微博, 90%的数据的修改应该会在同一个机房。好比一个用户有一个profile信息, 那么和修改这个信息的基本都是这个用户本人, 90%的状况下应该就是在同一个地点改, 固然写入也会在同一个机房. 因此大部分的修改应该是同一个机房的修改。可是访问可能来自各个地方,固然为了作优化, 有些数据可能在一个地方修改过了之后, 屡次在其余地方修改, 那么咱们就能够修改这个Key的Primary IDC到另外这个机房。

 

Vector Lock

Vector Lock的核心思想就是Client对这个数据的了解是远远超过服务端的, 由于对于服务端而言, 这个Key 对应的Value 对于Server 端只是一个字符串。而Client端可以具体了解这个Value所表明的含义, 对这个Value进行解析。那么对于这个例子,当这两个不同的Value写入到两个副本中的时候, Client进行一次读取操做读取了多个副本。

 

Client发现读到的两个副本的结果是有冲突的, 这里咱们假设原始的Key的Vector Lock信息是[X:1], 那么第一次修改就是[X:1,Y:1], 另外一个客户端是基于[X:1]的Vector Lock修改的, 因此它的Vector Lock信息就应该是[X:1,Z:1]。这个时候咱们只要检查这个Vector Lock信息就能够能够发现他们冲突, 这个就是就交给客户端去处理这个冲突.并把结果从新Update便可。

 

咱们线上目前支持的是时间戳最新, 以及Primary Key的方案. 大部分使用的是时间戳最新来进行冲突解决。

 

多数据结构支持

  • 咱们开发了一套基于leveldb的多数据结构的引擎。目前支持 Hash, List, Set, Zset等结构。

  • 主要是因为用户习惯了Redis提供的多数据结构, 可以知足用于快速开发业务的过程, 所以咱们也提供了多数据结构的支持。

     

为何不使用ZooKeeper

  • ZooKeeper 和 mnesia对比, ZooKeeper 是一个服务, 而 mnesia是一个库, 所以若是使用ZooKeeper的话, 咱们须要额外的维护一套服务。而 mnesia能够直接集成在代码里面,使用更方便。

  • mnesia和 Erlang 集成的更好,mnesia自己就是用Erlang 来开发。

     

Bada 和 MongoDB对比

  • 360的MongoDB 以前也是咱们团队在维护, 在使用MongoDB的过程当中, 咱们也遇到一些问题, 好比MongoDB 的扩容很是不方便, 扩容须要很长的时间, 由于MongoDB 扩容的过程是将一条一条的数据写入的. 咱们开发的时候考虑到这些问题, 所以Bada 使用的是leveldb, 当须要扩容的时候, 只要将某一个分片下面的数据文件拷贝过去便可. 前提是初始化的时候分片设置的足够大, 咱们现实默认的分片是1024

  • MongoDB 的数据膨胀度比较大, 由于MongoDB 毕竟是文档型数据库, 确定会保持一些冗余信息. 咱们底下使用leveldb, leveldb 自己的压缩功能基于snappy 压缩. 仍是作的比较好. 线上实际的磁盘空间大小相对于MongoDB 4:1

     

Bada 和 Cassandra 对比

Cassandra的定位和Bada是不同的, 咱们面向的是线上频繁访问的热数据, 所以咱们偏向于存储小value数据, 热数据, 对latency 的要求会苛刻。

 

好比在云盘的场景, 咱们存储的就是文件的索引信息, 而Cassandra存储的是具体的Cassandra的数据, 也所以咱们线上部署Bada的机器是挂载SSD盘的。

 

Bada 和 Redis 对比

  • Bada 的性能比Redis 低, 可是目前redis cluster 还没发展完善. 咱们公司的DBA也在跟进Redis cluster之中. 因此当数据量比较大的时候, Redis可能就不适用于这么大量的数据存储。

  • Bada 的多数据结构支持不如Redis来得完善. 所以咱们也在逐步的支持Bada的多数据结构。

  • Redis 毕竟是内存型的服务. 所以假如用户是偏向于存储持久化数据, 可能Redis不太合适。

     

一些非技术的经验

技术是为业务服务, 包括咱们Bada在公司内部推广的过程当中也发现, 咱们不少业务很头疼的问题在于360的机房较多, 每个小业务都须要维护在多个机房, 所以为了下降用户的开发试错成本, 咱们将能标准化的事情都作了。包括咱们组的定位也是专一底层技术, 加速产品团队开发效率, 尽量下降业务对服务端集群架构的关注。

 

Q&A:

Q1:客户端访问Bada时,怎么确保数据的均衡?从qconf拿到的是一个ip列表吧?

是的。从QConf 中得到是随机的一个节点的ip,因此对每个节点的访问基本的均衡的。服务端这边, 由于咱们是有主从结构的。可是咱们的主从是分片级别的主从,这点和redis cluster 不同。好比 Redis cluster 有Master 节点, slave节点,通常状况slave 节点不接受任何的线上访问,可是从下面的图中能够看到 Bada 每个节点都有主, 从分片。 由于每个节点的访问基本是均衡的。

 

Q2:我有一个问题,对于kv存储,选择leveldb的动机是什么?其余leveldb分支是否考虑过?

对于存储的考虑, 咱们以前对 Rocksdb 和 leveldb 作过对比.在数据量小的状况下, leveldb 的性能和 Rocksdb 性能差很少. 数据量大的时候 Rocksdb 会有性能优点. 由于咱们以前对leveldb 作了修改. 因此后续咱们会迁移过去。 这里咱们的读写都走的是 Master 节点. 只有当主节点挂掉之后, 才会访问从节点。

这个截图是以前对 leveldb 和 rocksdb 在数据量比较小的状况下的对比

 

Q3:可否说一下扩容,新增节点,以及摘除失效节点的处理?

从上面两张图中能够看出, 咱们会将新增的节点中, 均衡的将新的主节点迁移的新节点上。目前扩容的过程是这样 咱们先把当前这个节点加入到集群。而后经过 rebalance 来进行平衡。咱们通常预先分配1024 个分配。这个应该也是业内场景的作法, 以前对腾讯的CKV 也是这么作,Riak 也是这么作。

 

Q4:迁移是直接对leveldb复制,延时会有多少,在迁移过程当中的访问如何处理呢?

迁移是直接对 leveldb 的文件进行复制, 这个时候性能是取决于网络的开销。这也是咱们比mongo扩容快的地方, mongo 在扩容的时候须要将数据一条一条写。迁移以前, 咱们会将当前这个节点进行切主操做, 就是将全部的主切走。那么这个时候是不会影响线上访问,带来的最多的影响就是这个节点的网络有额外的开销,可是这个节点不是面向用户的请求的,因此影响不 大。

 

Q5 :主切走也须要有一个时间吧?这个时间段内,若是要访问原来主上的数据,怎么处理?

这里是这样的一个过程, 迁移的时候好比A 节点。 那么A节点上有主分片, 那么在迁移以前,咱们会先将A节点上的主让给其余节点。这里就涉及到追Binlog 的问题,若是这个时候用户有大量的数据写入, 会致使Binlog 一直追不齐。确实会致使没法迁移。

 

Q6:关于leveldb的迁移,可否详细介绍一下?

leveldb 的迁移很简单,就是直接经过scp 就能够了。这个是leveldb 自己的功能,就是经过scp leveldb 对应的数据文件就能够。其实咱们在binlog 这块也作了挺多的事情, 不过太细了有机会下次讲。使用binlog 来同步的副本策略之中, 常见的问题好比,分布式系统中因为主从切换致使的数据丢失,而后咱们也开发了binlog merge 来减小这种问题带来的影响。

 

Q7:leveldb的部分数据在内存中,这个迁移的时候怎么解决的?

这个没有影响。由于leveldb 的memtable 的数据在磁盘上有对应的.log 文件。leveldb 启动的时候会默认读取.log文件, 将里面的内容加载到内存中。

 

Q8 : 我仍是没太明白,扩容的时候,A节点切到其余节点,是把A的meta信息作切换,而后再复制数据,最后再映射meta?

扩容的时候是这样一个过程。先将新增的节点加入到现有的集群,不过这个节点不负责任何的分片, 所以没有任何数据在这个节点上;而后咱们迁移的过程是节点上的一个个的分片进行迁移。好比A 这个节点有 10~20 这几个分片, 而且这个时候 10~20 这个分片是主, 那么依次咱们先将A这个节点的10~20变成从, 这个时候须要修改meta信息。而后接下来是复制对应的数据文件到新节点, 复制结束之后, 修改10~20 这几个分片到新的主上.最后修改meta 信息 ,和大部分系统比最大的不一样在于 Bada 的主从是分片级别的主从, 不是节点级别的主从.这样任何操做形成的影响都是很是小. 而且能够作到每一个节点的负载尽量的均衡。

 

Q9:mnesia用来存储meta信息吗?

mnesia 对于咱们的定位就相似于ZooKeeper。有两个用途, 一个是选主的过程提供一个全局的锁, 一个是保存元信息。

为何不使用ZooKeeper

  • ZooKeeper 和 mnesia 对比, ZooKeeper 是一个服务, 而mnesia是一个库, 所以若是使用ZooKeeper的话, 咱们须要额外的维护一套服务. 而mnesia能够直接集成在代码里面. 使用更方便

  • mnesia 和 erlang 集成的更好. mnesia自己就是用Erlang 来开发

 

Q10:meta信息是存储在单独的机器上,而不是分布在存储节点上吗?

不是, 存储在每个节点上. 每个节点都部有mnesia

 

Q11:既然用mnesia,那你前端机器连在一个集群?规模多大?

前端是按照业务划分的,最大的有36个节点.

 

感谢王杰的记录与整理,国忠和四正的校对,其余多位编辑组志愿者对本文亦有贡献。更多关于架构方面的内容,读者能够经过搜索“ArchNotes”或长按下面图片,关注“高可用架构”公众号,查看更多架构方面内容,获取通往架构师之路的宝贵经验。转载请注明来自“高可用架构(ArchNotes)”微信公众号。

相关文章
相关标签/搜索