25、分布式数据库实现解析

Redis与MongoDB分布式数据库解析

分布式数据库实现解析

在分布式系统中,数据库的选择至关重要。不同的分布式数据库在数据模型、性能、一致性、可用性等方面存在差异。下面将为大家介绍Redis和MongoDB这两种广泛使用的分布式数据库。

1. Redis数据库

1.1 Redis概述

自2009年首次发布以来,Redis越来越受欢迎,成为最广泛部署的分布式数据库之一。它的主要吸引力在于既可以作为分布式缓存,也可以作为数据存储。Redis维护一个内存中的数据存储,即数据结构存储,客户端向Redis服务器发送命令来操作其保存的数据结构。

Redis用C语言实现,使用单线程事件循环来处理客户端请求。在6.0版本中,该事件循环增加了额外的线程来处理网络操作,以便为事件循环提供更多带宽来处理客户端请求,从而使Redis服务器能更好地利用多核节点,提供更高的吞吐量。

为确保数据安全,单个Redis服务器维护的内存数据结构可以通过以下两种方法持久化:
- 快照(Snapshot) :配置一个定期的后台线程将内存内容转储到磁盘。此快照过程使用 fork() 系统调用,如果内存内容很大,成本可能较高。在高吞吐量系统中,通常每隔几十秒配置一次快照。也可以在写入一定次数后触发快照,以限制潜在的数据丢失。
- 追加仅写文件(AOF) :配置Redis将每个命令记录到一个追加仅写文件中。这本质上是一个操作日志,默认每秒持久化一次。同时使用快照和操作日志可以提供最大的数据安全保证。在服务器崩溃时,可以根据最新的快照重放AOF来重建服务器内存中的数据内容。

1.2 数据模型和API

Redis是一个键值存储,它提供了一组小型的数据结构,应用程序可以使用这些结构创建与唯一键关联的数据对象。每个数据结构都有一组定义好的命令,用于创建、操作和删除数据对象。命令简单,且针对由键标识的单个对象进行操作。核心的Redis结构包括:
- 字符串(Strings) :在Redis中用途广泛,能够存储文本和二进制数据,最大长度为512 MB。例如,可以使用 get() set() 操作对指定子范围进行随机访问,还可以用于表示和操作计数器。
- 链表(Linked lists) :由字符串组成的列表,可对列表的头部、尾部和中间元素进行操作。
- 集合和有序集合(Sets and sorted sets) :集合表示唯一字符串的集合,有序集合为每个元素关联一个分数值,并按分数升序维护字符串,这样可以按分数或排名顺序高效访问集合中的元素。
- 哈希(Hashes) :类似于Python的字典,Redis哈希将字符串表示的键值映射到一个或多个字符串值。哈希是Redis表示应用程序数据对象(如用户配置文件或库存清单)的主要结构。

单个键上的操作是原子的,也可以使用 multi exec 命令指定一组操作需要原子执行。在 multi exec 之间的所有命令称为Redis事务,它们会按顺序序列化执行。例如:

multi
lpush neworders “orderid 600066 customer 89788 item 788990 amount 11 date 12/24/21”
hmset user:89788 lastorder 600066
exec

不过,Redis事务只有在所有命令都成功时才提供原子性。如果一个命令失败,没有回滚功能,即使一个命令失败,事务中的其余命令仍会执行。如果在服务器执行事务时发生崩溃,服务器将处于未知状态,可以使用AOF持久化机制在重启时进行管理修复。实际上,Redis事务并非严格意义上的ACID事务。

1.3 分布和复制

最初,Redis是单服务器数据存储,可扩展性有限。2015年,Redis Cluster发布,用于在多个节点之间进行数据分区和复制。Redis Cluster为集群定义了16,384个哈希槽,每个键通过对16,384取模映射到一个特定的槽,该槽被配置为驻留在集群中的一个主机上。

每个节点运行一个Redis服务器和一个处理集群内节点通信的组件。Redis使用Cluster总线协议实现集群中每个节点之间的直接TCP通信,节点通过流言协议维护集群中所有其他节点的状态信息,包括每个节点服务的哈希槽。

客户端可以连接到集群中的任何节点并提交命令。如果命令到达的节点不管理给定对象的哈希槽,它会查找托管所需哈希槽的服务器地址,并向客户端返回 MOVED 错误和该节点的地址,客户端必须将命令重新发送到正确的节点。通常,Redis客户端驱动程序会维护一个内部目录,将哈希槽映射到服务器节点,以便在集群稳定时避免重定向。

这种架构还意味着事务中的命令必须访问位于同一哈希槽中的键,Redis无法对位于不同哈希槽和不同节点的对象执行命令,需要仔细进行数据建模来解决这个限制。Redis提供了哈希标签(hash tags)的概念,根据键的相同子字符串强制键进入同一哈希槽。

可以调整Redis Cluster的大小,添加或删除节点。发生这种情况时,需要使用 CLUSTER 管理命令修改节点的集群配置信息,将哈希槽分配给新节点或从删除的节点移动到现有节点。一旦哈希槽重新分配到不同的节点,Redis会自动迁移迁移哈希槽中的对象。对象被序列化并从现有主节点发送到新主节点,成功确认后,从原主节点删除并在新位置对客户端可见。

还可以使用主从架构复制集群中的每个节点,主节点异步更新从节点以提供数据安全。为扩展读工作负载,可以配置从节点处理读命令。默认情况下,主节点在向客户端返回成功之前不会等待从节点确认更新。客户端可以在更新后发出 WAIT 命令,指定应确认更新的从节点数量和超时时间。例如:

WAIT 2 500

表示客户端会阻塞,直到两个从节点确认更新,或者500毫秒超时,无论哪种情况,Redis都会返回已更新的从节点数量。

在主节点故障时,从节点会被提升为主节点。Redis使用自定义的主节点选举算法,检测到主节点故障的从节点会发起选举,并尝试从集群中的多数主节点获得投票。如果获得多数票,它会将自己提升为主节点并通知集群中的节点。不过,不能保证最新的从节点最终会被提升为主节点,因此如果过时的从节点成为主节点,可能会导致数据丢失。

1.4 优缺点分析

优点 缺点
设计用于低延迟响应和高吞吐量,主要数据存储在内存中,数据对象访问速度快。有限的数据结构和操作使Redis能够优化请求并使用节省空间的数据对象表示。 为了性能牺牲了数据安全性。默认配置下,AOF写入之间有1秒的时间窗口,在此期间崩溃可能导致数据丢失。提高数据安全性(每次写入都持久化AOF)会在高写入负载下对性能造成重大影响。
Redis Cluster允许最多1000个节点托管分布在16,384个哈希槽上的分片数据库,从节点可以处理读请求,扩展读工作负载。 使用专有复制和主节点选举算法,过时的从节点可能被选为领导者,导致之前领导者持久化的数据丢失。
默认基于异步复制提供最终一致性,使用 WAIT 命令可使复制方法变为有效同步,但会增加延迟,且仅保证数据存在于从节点的内存中,从节点在下次快照或AOF写入之前崩溃可能导致更新丢失。 写可用性会受到领导者故障的影响,在从节点被提升为领导者之前,给定分片的写入将不可用。网络故障可能导致集群分区,分区修复后,之前领导者在分区期间的写修改可能会丢失。

1.5 Redis操作流程

graph LR
    A[客户端发送命令] --> B{节点是否管理哈希槽}
    B -- 是 --> C[执行命令]
    B -- 否 --> D[查找正确节点地址]
    D --> E[返回MOVED错误和地址]
    E --> F[客户端重新发送命令到正确节点]
    F --> C

2. MongoDB数据库

2.1 MongoDB概述

自2009年首次发布以来,MongoDB一直处于NoSQL数据库运动的前沿。它通过将数据库模型与对象模型相协调,直接解决了著名的对象关系阻抗不匹配问题。得到的文档数据库可以被看作是一个JSON数据库,你可以将业务对象转换为JSON并直接作为文档存储、查询和操作数据,无需复杂的对象关系映射器,从而使业务逻辑更直观、更简单。

早期版本的MongoDB使用的MMAPv1存储引擎存在一些不足。MMAPv1使用 mmap() 系统调用实现内存映射文件,同一逻辑分组(即集合)中的文档在磁盘上连续分配,有利于顺序读取性能。但如果对象大小增长,需要分配新空间并更新所有文档索引,这可能是一个成本高昂的操作,并导致磁盘碎片化。为了最小化成本,MMAPv1最初会为文档分配额外的空间以适应增长,但这可能不是最节省空间和可扩展的解决方案。此外,更新文档时的锁粒度较粗(如在不同版本中,锁的级别为服务器、数据库、集合),导致写入性能不佳。

大约在2015年,开发团队对MongoDB进行了重新设计,以支持可插拔的存储引擎架构。不久后,新的存储引擎WiredTiger在MongoDB v3.2中成为默认引擎。WiredTiger解决了MMAPv1的许多缺点,引入了乐观并发控制和文档级锁、压缩、操作日志和检查点以进行崩溃恢复,以及自己的内部缓存以提高性能。

2.2 数据模型和API

MongoDB文档基本上是JSON对象,具有Binary JSON(BSON)规范中定义的一组扩展类型。文档以BSON格式存储,并组织在包含一个或多个集合的数据库中。集合相当于关系数据库中的表,但没有定义的模式,这意味着MongoDB集合不强制文档具有特定的结构,不同结构的文档可以存储在同一个集合中,这是一种无模式或读时模式的方法,需要应用程序在访问时解释文档结构。

MongoDB文档由名值对组成,字段的值可以是任何BSON数据类型。文档还可以包含其他文档(嵌入式或嵌套文档)以及值或文档的数组。每个文档都有一个 _id 字段作为主键,应用程序可以在创建文档时设置该键值,也可以让MongoDB客户端自动分配唯一值。还可以在集合中的任何字段、子字段或多个字段(复合键)上定义二级索引。

以下是一个可能在滑雪者管理系统中找到的文档示例:

{
    _id: 6788321471,
    name: { first: "Ian", last: "Gorton" },
    location: "USA-WA-Seattle",
    skiresorts: ["Crystal Mountain", "Mission Ridge"],
    numdays: 2,
    season21: [
        {
            day: 1,
            resort: "Crystal Mountain",
            vertical: 30701,
            lifts: 27,
            date: "12/1/2021"
        },
        {
            day: 2,
            resort: "Mission Ridge",
            vertical: 17021,
            lifts: 10,
            date: "12/8/2021"
        }
    ]
}

由于集合中没有统一的文档结构,存储引擎需要为每个文档持久化字段名和值。对于小文档,长字段名可能占文档大小的大部分,较短的字段名可以减少文档在磁盘上的大小,在包含数百万个文档的集合中,这种节省会变得很显著。优化文档大小可以减少磁盘使用、内存和缓存消耗以及网络带宽。

MongoDB提供了用于基本CRUD操作的API。有一个 .find() 方法,带有一组广泛的条件和运算符,可模拟SQL中对单个集合中文档的 SELECT 语句。MongoDB支持使用 $match $group 运算符的聚合查询, $lookup 运算符提供了跨同一数据库中集合的类似SQL JOIN 的行为。例如,查询 skiers2021 集合中注册滑雪天数超过20天的所有滑雪者文档:

db.skiers2021.find({ numdays: { $gt: 20 } })

MongoDB中对单个文档的写入操作是原子的。因此,如果对数据模型进行非规范化处理,大量使用嵌套文档,可以避免在应用程序代码中更新多个文档和处理分布式事务的复杂性。在MongoDB 4.0版本之前,这基本上是在不使用复杂应用程序逻辑处理故障的情况下确保多文档更新一致性的唯一方法。

自4.0版本以来,MongoDB实现了对ACID多文档事务的支持。MongoDB事务使用两阶段提交,并利用底层WiredTiger存储引擎的快照隔离功能。快照隔离比ACID语义所暗示的序列化保证更弱,这使得性能比序列化更高,并避免了大多数(但不是全部)序列化可避免的并发异常。实际上,许多关系数据库(包括Oracle和PostgreSQL)默认使用快照隔离。

2.3 MongoDB操作流程

graph LR
    A[应用程序操作文档] --> B{是否为单文档写入}
    B -- 是 --> C[原子操作]
    B -- 否 --> D{是否为4.0+版本}
    D -- 是 --> E[使用两阶段提交和快照隔离进行事务处理]
    D -- 否 --> F[需复杂应用逻辑处理多文档更新]

综上所述,Redis和MongoDB在分布式数据库领域各有特点和优势。Redis适合对性能要求高、能容忍一定数据丢失的场景,而MongoDB则更适合处理复杂的文档数据和需要多文档事务支持的应用。在选择数据库时,需要根据具体的业务需求和场景进行综合考虑。

2.4 分布和复制

MongoDB支持分片集群架构,以实现数据的水平扩展。分片集群由三个主要组件组成:
- 分片(Shards) :存储实际的数据,每个分片可以是一个副本集,以提供数据冗余和高可用性。
- 配置服务器(Config Servers) :存储集群的元数据,包括分片的位置和数据的分布信息。
- 路由服务器(Mongos) :客户端与集群交互的入口点,负责将客户端的请求路由到正确的分片。

客户端连接到路由服务器,路由服务器根据元数据将请求转发到相应的分片。如果请求涉及多个分片,路由服务器会协调多个分片的操作并合并结果。

MongoDB使用副本集来实现数据的复制和高可用性。副本集是一组维护相同数据集的MongoDB实例,其中一个实例作为主节点(Primary),其余实例作为从节点(Secondary)。主节点处理所有的写操作,并将更新异步复制到从节点。从节点可以处理读操作,以分担主节点的负载。

当主节点发生故障时,副本集会自动进行选举,选择一个从节点作为新的主节点。选举过程基于多数原则,即需要大多数节点参与选举并达成一致。在选举过程中,客户端的写操作会被阻塞,直到新的主节点被选举出来。

2.5 优缺点分析

优点 缺点
解决了对象关系阻抗不匹配问题,无需复杂的对象关系映射器,业务逻辑更直观、简单。 早期的MMAPv1存储引擎存在磁盘碎片化和写性能不佳的问题,虽然WiredTiger解决了这些问题,但可能需要一定的迁移成本。
支持可插拔的存储引擎架构,用户可以根据需求选择不同的存储引擎。 多文档事务使用快照隔离,虽然性能较高,但不能完全避免并发异常。
提供了丰富的查询和聚合功能,支持类似SQL的操作。 分片集群的管理和维护相对复杂,需要一定的技术经验。
对单个文档的写入操作是原子的,4.0版本后支持ACID多文档事务。

2.6 与Redis的对比

特性 Redis MongoDB
数据模型 键值存储,提供简单的数据结构 文档数据库,支持复杂的嵌套文档和数组
性能 低延迟、高吞吐量,主要基于内存 性能较好,支持磁盘存储和内存缓存
一致性 默认最终一致性,可通过 WAIT 命令提高一致性 支持ACID多文档事务,提供更强的一致性保证
可用性 主从复制,故障时可能有数据丢失 副本集和分片集群,自动故障转移和数据冗余
适用场景 缓存、会话管理、计数器等 内容管理系统、日志记录、实时分析等

2.7 选择建议

在选择Redis和MongoDB时,可以参考以下建议:
1. 性能需求 :如果需要极低的延迟和极高的吞吐量,且数据量较小,Redis可能更适合;如果数据量较大,且对性能要求不是极端苛刻,MongoDB可以满足需求。
2. 数据模型 :如果数据结构简单,主要是键值对,Redis是一个不错的选择;如果数据结构复杂,需要存储和查询嵌套文档和数组,MongoDB更合适。
3. 一致性要求 :如果对数据一致性要求不高,能够容忍一定的数据丢失,Redis的最终一致性可以满足;如果需要强一致性保证,MongoDB的ACID多文档事务是更好的选择。
4. 可用性和扩展性 :如果需要高可用性和水平扩展能力,MongoDB的分片集群和副本集可以提供更好的支持;如果只是简单的主从复制,Redis也可以满足。

2.8 操作步骤总结

Redis操作步骤
  1. 安装和配置 :下载并安装Redis,根据需求配置持久化方式(快照或AOF)和集群参数。
  2. 连接和操作 :使用客户端连接到Redis服务器,使用命令操作数据结构,如 set get lpush 等。
  3. 集群管理 :使用 CLUSTER 命令管理集群,如添加或删除节点、迁移哈希槽等。
  4. 主从复制 :配置主从架构,使用 WAIT 命令控制复制的一致性。
MongoDB操作步骤
  1. 安装和配置 :下载并安装MongoDB,配置存储引擎(如WiredTiger)和副本集或分片集群。
  2. 连接和操作 :使用客户端连接到MongoDB,使用API进行CRUD操作,如 find insert update 等。
  3. 事务处理 :在4.0版本后,使用两阶段提交和快照隔离进行多文档事务处理。
  4. 集群管理 :使用 mongos 路由服务器管理分片集群,监控和维护副本集的状态。

2.9 流程图总结

Redis操作流程图
graph LR
    A[客户端发送命令] --> B{节点是否管理哈希槽}
    B -- 是 --> C[执行命令]
    B -- 否 --> D[查找正确节点地址]
    D --> E[返回MOVED错误和地址]
    E --> F[客户端重新发送命令到正确节点]
    F --> C
MongoDB操作流程图
graph LR
    A[应用程序操作文档] --> B{是否为单文档写入}
    B -- 是 --> C[原子操作]
    B -- 否 --> D{是否为4.0+版本}
    D -- 是 --> E[使用两阶段提交和快照隔离进行事务处理]
    D -- 否 --> F[需复杂应用逻辑处理多文档更新]

总之,Redis和MongoDB都是优秀的分布式数据库,它们在不同的场景下具有各自的优势。在实际应用中,需要根据具体的业务需求、性能要求、数据模型和一致性要求等因素,综合考虑选择合适的数据库。同时,合理的数据库设计和优化也是提高系统性能和可靠性的关键。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值