分布式系统常见问题和解决方案

如何从零开始造个云存储系统?

单硬盘扩展成RAID磁盘阵列,再从单机RAID扩展到集群多机器,再到引入副本存储提高可靠性,再到引入一致性哈希进行动态扩展,再到跨机房多AZ备份和CDN区域加速。

如何实现靠谱的分布式锁?

分布式锁是用来控制分布式系统中互斥访问共享资源的一种手段(进程之间共享资源),避免多线程并行访问导致结果不可控。基本的实现原理和单进程锁是一致的,通过一个共享标识来确定唯一性,对共享标识进行修改时能够保证原子性和和对锁服务调用方的可见性。由于分布式环境需要考虑各种异常因素,为实现一个靠谱的分布式锁服务引入了一定的复杂度。

分布式锁一般需要能够保证以下几点:

  1. 同一时刻只能有一个线程持有锁

  2. 锁能够可重入

  3. 不会发生死锁

  4. 具备阻塞锁特性,且能够及时从阻塞状态被唤醒

  5. 锁服务保证高性能和高可用

  6. 锁数据本身的安全性

基于 Redis 实现的锁服务
  1. 加锁:SETNX key value资源不存在时才能够成功执行 set 操作,用于保证锁持有者的唯一性;同时设置过期时间用于防止死锁;记录锁的持有者,用于防止解锁时解掉了不符合预期的锁。
  2. 解锁:只需要删除这个key就可以了,不过删除之前需要判断,这个key对应的value是当初自己设置的那个
  3. Lua脚本对比解锁者是否所有者、解锁是一个原子操作
  4. 通过过期时间PX millisecond来避免死锁,时间选择很关键
  5. Redis 的主从异步复制机制可能丢失数据,造成 A、B 两个线程并发访问同一个资源
基于 ZooKeeper 实现的锁服务
  1. 加锁是线程去zookeeper上的某个指定节点的目录下创建一个唯一的临时有序节点,确定当前线程创建节点序号是否最小,是则加锁成功;否则去序列中寻找并监听序号较小的前一个节点。当监听到这个节点被删除了,那就再去判断一次自己当初创建的节点是否变成了序列中最小的。如果是,则获取锁,如果不是,则重复上述步骤。
  2. 解锁流程是删除当前线程创建的临时接点。
基于数据库实现的锁服务
  1. 乐观锁机制:表中每条记录添加version字段,每次更新操作需要CAS
  2. 悲观锁机制:在Mysql中是基于 for update 来实现加锁的

如何实现分布式文件系?

分布式文件系统是分布式领域的一个基础应用,其中最著名的毫无疑问是 HDFS/GFS。

DFS特性要求
  • 符合 POSIX 的文件接口标准,兼容易用
  • 对用户透明,能够像使用本地文件系统那样直接使用
  • 持久化,保证数据不会丢失
  • 具有伸缩性,当数据压力逐渐增长时能顺利水平扩容
  • 具有可靠的安全机制,保证数据安全
  • 数据一致性,只要文件内容不发生变化,什么时候去读,得到的内容应该都是一样的

  • 支持的空间越大越好

  • 支持的并发访问请求越多越好
  • 性能越快越好
  • 硬件资源的利用率越高越合理,就越好
DFS架构

从业务模型和逻辑架构上,分布式文件系统需要这几类组件:

  • 存储组件:负责存储文件数据,它要保证文件的持久化、副本间数据一致、数据块的分配 / 合并等等;
  • 管理组件:负责 meta 信息,即文件数据的元信息,包括文件存放在哪台服务器上、文件大小、权限等,除此之外,还要负责对存储组件的管理,包括存储组件所在的服务器是否正常存活、是否需要数据迁移等;
  • 接口组件:提供接口服务给应用使用,形态包括 SDK(Java/C/C++ 等)、CLI 命令行终端、以及支持 FUSE 挂载机制。
GFS —- 有中心节点

中心节点负责文件定位、维护文件 meta 信息、故障检测、数据迁移等管理控制的职能。一般中心节点并不参与真正的数据读写,而是将文件 meta 信息返回给 Client 之后,即由 Client 与数据节点直接通信。其主要目的是降低中心节点的负载,防止其成为瓶颈。这种有中心节点的方案,在各种存储类系统中得到了广泛应用,因为中心节点易控制、功能强大。

Ceph —- 无中心节点

每个节点都是自治的、自管理的,整个 ceph 集群只包含一类节点 —- RADOS 就是 ceph 定义的“同时包含 meta 数据和文件数据”的节点。无中心化的最大优点是解决了中心节点自身的瓶颈,这也就是 ceph 号称可以无限向上扩容的原因。CRUSH算法解决meta查找数据位置的问题

内部DFS

原来大量使用SAS磁盘和Raid卡。SAS盘+Raid的成本直逼SSD,但性能比SSD有数量级的落后。如果这些业务 把状态从本地磁盘转移到分布式文件系统,则不再依赖本地磁盘的可靠性,不再需要SAS盘和Raid卡。因此诞生了NFS。

NFS提供Posix接口,支持随机写操作,并针对这种访问模式做了大量的 优化工作;而AFS提供API访问接口,不支持文件的随机写操作。因此两者没有替代关系。从长远来看,NFS和AFS会长期并存,独立发展,不存在谁取代谁的关系。不仅如此,在实现上,NFS是AFS的底层,AFS自身的元信息,是存储在下层的NFS集群的。因此NFS除了继续提供Posix访问接口,取代本地硬盘这一目标外,还会进一步优化可用性。

持久化数据
  • 如何保证每个副本的数据是一致的? 同步写入或者W+R>N 的方式
  • 如何分散副本,以使灾难发生时,不至于所有副本都被损坏? 两地三中心
  • 怎么检测被损坏或数据过期的副本,以及如何处理?
    • 如果有中心节点,则数据节点定期和中心节点进行通信,汇报自己的数据块的相关信息,中心节点将其与自己维护的信息进行对比
    • 如果没有中心节点,以 ceph 为例,它在自己的节点集群中维护了一个比较小的 monitor 集群,数据节点向这个 monitor 集群汇报自己的情况,由其来判定是否被损坏或过期
    • FailOver机制
  • 该返回哪个副本给 Client? round-robin、速度最快的节点、成功率最高的节点、CPU 资源最空闲的节点、甚至就固定选第一个作为主节点,也可以选择离自己最近的一个
存储节点的伸缩性
  • 如何尽量使各存储节点的负载相对均衡?
  • 怎样保证新加入的节点,不会因短期负载压力过大而崩塌? 预热时间
  • 如果需要数据迁移,那如何使其对业务层透明?
中心节点的伸缩性

HDFS 的数据块的大小是 64M,ceph 的的数据块的大小是 4M,都远远超过单机文件系统的 4k。它的意义在于大幅减少 meta data 的数量,使中心节点的单机内存就能够支持足够多的磁盘空间 meta 信息

中心节点的高可用

当前内存服务 + 日志文件持久化是主流方式。为了解决日志文件会随着时间增长越来越大的问题,以让系统能以尽快启动和恢复,需要辅助以内存快照的方式——定期将内存 dump 保存,只保留在 dump 时刻之后的日志文件

万兆网卡每秒传输大约 1250M 字节的数据,而 SATA 磁盘的读写速度这些年基本达到瓶颈,在 300-500M/s 附近。

安全性

主流文件系统的权限模型:DAC、MAC、RBAC

面向小文件的分布式文件系统

主流的实现方式是仍然是以大数据块的形式存储,小文件以逻辑存储的方式存在,即文件 meta 信息记录其是在哪个大数据块上,以及在该数据块上的 offset 和 length 是多少,形成一个逻辑上的独立文件。这样既复用了大数据块系统的优势和技术积累,又减少了 meta 信息。

文件指纹和去重

文件指纹就是根据文件内容,经过算法,计算出文件的唯一标识。如果两个文件的指纹相同,则文件内容相同。在使用网络云盘的时候,发现有时候上传文件非常地快,就是文件指纹发挥作用。云盘服务商通过判断该文件的指纹,发现之前已经有人上传过了,则不需要真的上传该文件,只要增加一个引用即可。在文件系统中,通过文件指纹可以用来去重、也可以用来判断文件内容是否损坏、或者对比文件副本内容是否一致,是一个基础组件。指纹算法有md5、sha256、simhash 和 minhash