分布式系统遇到的十个问题

网站建设4年前发布
39 0 0

我们都在讨论分布式,特别是面试的时候,不管是招初级软件工程师还是高级,都会要求懂分布式,甚至要求用过。传得沸沸扬扬的分布式到底是什么东东,有什么优势?,20230305203915d21f3ac25af8d46babd150b79d97d4dfdaed1d249,风遁·螺旋手里剑,看过火影的同学肯定知道漩涡鸣人的招牌忍术:多重影分身之术。,这两个忍术和分布式有什么关系?,案例:,那多重影分身之术有什么缺点?,讲到分布式不得不知道 CAP 定理和 Base 理论,这里给不知道的同学做一个扫盲。,在理论计算机科学中,CAP 定理指出对于一个分布式计算系统来说,不可能通是满足以下三点:,BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的缩写。BASE 理论是对 CAP 中 AP 的一个扩展,通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但要保证核心功能可用,允许数据在一段时间内是不一致的,但最终达到一致状态。满足 BASE 理论的事务,我们称之为柔性事务。,将消息队列里面的消息分摊到多个节点(指某台机器或容器)上,所有节点的消息队列之和就包含了所有消息。,所谓幂等性就是无论多少次操作和第一次的操作结果一样。如果消息被多次消费,很有可能造成数据的不一致。而如果消息不可避免地被消费多次,如果我们开发人员能通过技术手段保证数据的前后一致性,那也是可以接受的,这让我想起了 Java 并发编程中的 ABA 问题,如果出现了 [​​ABA问题​​),若能保证所有数据的前后一致性也能接受。,场景分析,RabbitMQ、RocketMQ、Kafka 消息队列中间件都有可能出现消息重复消费问题。这种问题并不是 MQ 自己保证的,而是需要开发人员来保证。,这几款消息队列中间都是是全球最牛的分布式消息队列,那肯定考虑到了消息的幂等性。我们以 Kafka 为例,看看 Kafka 是怎么保证消息队列的幂等性。,Kafka 有一个 偏移量 的概念,代表着消息的序号,每条消息写到消息队列都会有一个偏移量,消费者消费了数据之后,每过一段固定的时间,就会把消费过的消息的偏移量提交一下,表示已经消费过了,下次消费就从偏移量后面开始消费。,坑:当消费完消息后,还没来得及提交偏移量,系统就被关机了,那么未提交偏移量的消息则会再次被消费。,如下图所示,队列中的数据 A、B、C,对应的偏移量分别为 100、101、102,都被消费者消费了,但是只有数据 A 的偏移量 100 提交成功,另外 2 个偏移量因系统重启而导致未及时提交。,20230305203812b37826229a157659d16541e1e37d4a7d186bbd494系统重启,偏移量未提交,重启后,消费者又是拿偏移量 100 以后的数据,从偏移量 101 开始拿消息。所以数据 B 和数据 C 被重复消息。,如下图所示:,2023030520381323f819c590563aae89b4121192b1707c53122d866重启后,重复消费消息,微信官方文档上提到微信支付通知结果可能会推送多次,需要开发者自行保证幂等性。第一次我们可以直接修改订单状态(如支付中 -> 支付成功),第二次就根据订单状态来判断,如果不是支付中,则不进行订单处理逻辑。,坑:消息丢失会带来什么问题?如果是订单下单、支付结果通知、扣费相关的消息丢失,则可能造成财务损失,如果量很大,就会给甲方带来巨大损失。,那消息队列是否能保证消息不丢失呢?答案:否。主要有三种场景会导致消息丢失。,20230305203814a30d7fe64bc7f6e48910129c9dbb0c41c46bc9465,消息队列之消息丢失,2023030520381499fb0e4125f73a3f2f2632a764022b326f2be3874,生产者丢失消息,对于 RabbitMQ 来说,生产者发送数据之前开启 RabbitMQ 的事务机制channel.txselect ,如果消息没有进队列,则生产者受到异常报错,并进行回滚 channel.txRollback,然后重试发送消息;如果收到了消息,则可以提交事务 channel.txCommit。但这是一个同步的操作,会影响性能。,我们可以采用另外一种模式:confirm 模式来解决同步机制的性能问题。每次生产者发送的消息都会分配一个唯一的 id,如果写入到了 RabbitMQ 队列中,则 RabbitMQ 会回传一个 ack 消息,说明这个消息接收成功。如果 RabbitMQ 没能处理这个消息,则回调 nack 接口。说明需要重试发送消息。,也可以自定义超时时间 + 消息 id 来实现超时等待后重试机制。但可能出现的问题是调用 ack 接口时失败了,所以会出现消息被发送两次的问题,这个时候就需要保证消费者消费消息的幂等性。,2023030520381452af55448d364632e43662be9e5e3f4cd40103423,消息队列丢失消息,消息队列的消息可以放到内存中,或将内存中的消息转到硬盘(比如数据库)中,一般都是内存和硬盘中都存有消息。如果只是放在内存中,那么当机器重启了,消息就全部丢失了。如果是硬盘中,则可能存在一种极端情况,就是将内存中的数据转换到硬盘的期间中,消息队列出问题了,未能将消息持久化到硬盘。,解决方案,20230305203915458c17952e160dc3ce72587921bd742a903658969,消费者丢失消息,消费者刚拿到数据,还没开始处理消息,结果进程因为异常退出了,消费者没有机会再次拿到消息。,解决方案,问题: 那这种主动 ack 有什么漏洞了?如果 主动 ack 的时候挂了,怎么办?,则可能会被再次消费,这个时候就需要幂等处理了。,问题: 如果这条消息一直被重复消费怎么办?,则需要有加上重试次数的监测,如果超过一定次数则将消息丢失,记录到异常表或发送异常通知给值班人员。,20230305203816e68fb6113685f118009641edd18786efed8f12479RabbitMQ 丢失消息的处理方案,场景:Kafka 的某个 broker(节点)宕机了,重新选举 leader (写入的节点)。如果 leader 挂了,follower 还有些数据未同步完,则 follower 成为 leader 后,消息队列会丢失一部分数据。,解决方案,坑: 用户先下单成功,然后取消订单,如果顺序颠倒,则最后数据库里面会有一条下单成功的订单。,RabbitMQ 场景:,20230305203816f9475f9451b389a09367768d245ecf0621d64a870,RabbitMQ消息乱序场景,20230305203917e2e49d9733c5e824279419375d680e3b63ea14304,RabbitMQ 消息乱序场景,RabbitMQ 解决方案:,2023030521340389d4fd0516030954228733a9c1988e233a1092570,RabbitMQ 解决方案,Kafka 场景:,2023030520381832669b446fb7777241b472bfd4e8d1a94b3fc5678,Kafka 消息丢失场景,Kafka 解决方案:,20230305203818882462327a722f46f8b830debaba76c3e8a743155,Kafka 消息乱序解决方案,消息积压:消息队列里面有很多消息来不及消费。,场景 1: 消费端出了问题,比如消费者都挂了,没有消费者来消费了,导致消息在队列里面不断积压。,场景 2: 消费端出了问题,比如消费者消费的速度太慢了,导致消息不断积压。,坑:比如线上正在做订单活动,下单全部走消息队列,如果消息不断积压,订单都没有下单成功,那么将会损失很多交易。,20230305203819544f4cc194e5cded12a0495b8d50aa81603d71532,消息队列之消息积压,解决方案:解铃还须系铃人,2023030520381966d4755880cc7a0de9740649c0622348ea900f464,消息积压解决方案,坑:RabbitMQ 可以设置过期时间,如果消息超过一定的时间还没有被消费,则会被 RabbitMQ 给清理掉。消息就丢失了。,20230305213404f3d3d63263d2e615095287a201b8f0dcff4103492,消息过期失效,解决方案:,20230305203918037037a32f8364019e89327cad21d1b230fcb9375,消息过期失效解决方案,坑:当消息队列因消息积压导致的队列快写满,所以不能接收更多的消息了。生产者生产的消息将会被丢弃。,解决方案:,闲时重导消息到消息队列。,在高频访问数据库的场景中,我们会在业务层和数据层之间加入一套缓存机制,来分担数据库的访问压力,毕竟访问磁盘 I/O 的速度是很慢的。比如利用缓存来查数据,可能5ms就能搞定,而去查数据库可能需要 50 ms,差了一个数量级。而在高并发的情况下,数据库还有可能对数据进行加锁,导致访问数据库的速度更慢。,分布式缓存我们用的最多的就是 Redis了,它可以提供分布式缓存服务。,Redis 可以实现利用哨兵机制实现集群的高可用。那什么十哨兵机制呢?,坑: 当主节点发生故障时,需要进行主备切换,可能会导致数据丢失。,主节点异步同步数据给备用节点的过程中,主节点宕机了,导致有部分数据未同步到备用节点。而这个从节点又被选举为主节点,这个时候就有部分数据丢失了。,主节点所在机器脱离了集群网络,实际上自身还是运行着的。但哨兵选举出了备用节点作为主节点,这个时候就有两个主节点都在运行,相当于两个大脑在指挥这个集群干活,但到底听谁的呢?这个就是脑裂。,那怎么脑裂怎么会导致数据丢失呢?如果发生脑裂后,客户端还没来得及切换到新的主节点,连的还是第一个主节点,那么有些数据还是写入到了第一个主节点里面,新的主节点没有这些数据。那等到第一个主节点恢复后,会被作为备用节点连到集群环境,而且自身数据会被清空,重新从新的主节点复制数据。而新的主节点因没有客户端之前写入的数据,所以导致数据丢失了一部分。,注意:缓存雪崩、缓存穿透、缓存击穿并不是分布式所独有的,单机的时候也会出现。所以不在分布式的坑之列。,根据租户来分库、分表。,利用时间范围来分库、分表。,利用 ID 取模来分库、分表。,坑:分库分表是一个运维层面需要做的事情,有时会采取凌晨宕机开始升级。可能熬夜到天亮,结果升级失败,则需要回滚,其实对技术团队都是一种煎熬。,坑: 分库分表看似光鲜亮丽,但分库分表会引入什么新的问题呢?,坑: 唯一 ID 的生成方式有 n 种,各有各的用途,别用错了。,     多个库的 ID 可能重复,这个方案可以直接否掉了,不适合分库分表后的 ID 生成。,     信息不安全,     缺点,     UUID 太长、占用空间大。,     不具有有序性,作为主键时,在写入数据时,不能产生有顺序的 append 操作,只能进行 insert 操作,导致读取整个 B+ 树节点到内存,插入记录后将整个节点写回磁盘,当记录占用空间很大的时候,性能很差。,     缺点,     高并发时,1 ms内可能有多个相同的 ID。,     信息不安全,     缺点,snowflake 算法,基本原理和优缺点:,1 bit:不用,统一为 0,41 bits:毫秒时间戳,可以表示 69 年的时间。,10 bits:5 bits 代表机房 id,5 个 bits 代表机器 id。最多代表 32 个机房,每个机房最多代表 32 台机器。,12 bits:同一毫秒内的 id,最多 4096 个不同 id,自增模式。,优点:,毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。,• 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。,• 可以根据自身业务特性分配bit位,非常灵活。,缺点:,• 强依赖机器时钟,如果机器上时钟回拨(可以搜索 2017 年闰秒 7:59:60),会导致发号重复或者服务会处于不可用状态。,百度的 UIDGenerator 算法。,2023030520391954a6d8a43620b29bb75535c459b479d1d9222c431,美团的 Leaf-Snowflake 算法。,2023030520410382c7ad9591a665455a7707d6366d2569a49d6f232,     Leaf服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景。,     ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。,     容灾性高:Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务。,     可以自定义max_id的大小,非常方便业务从原有的ID方式上迁移过来。,     即使DB宕机,Leaf仍能持续发号一段时间。,     偶尔的网络抖动不会影响下个号段的更新。,     ID号码不够随机,能够泄露发号数量的信息,不太安全。,怎么选择:一般自己的内部系统,雪花算法足够,如果还要更加安全可靠,可以选择百度或美团的生成唯一 ID 的方案。,坑:如何保证分布式中的事务正确执行,是个大难题。,202303052039207629ff233c39bf85cef14734f577cc221b79ab305,XA 方案,应用场景:,缺点:,基本原理:,适用场景:,优势:,缺点:,2023030520410376faee2299ea70380c9465f113f4e494ea3349685,可靠消息一致性方案,基本原理:,基本原理:,分布式还有很多坑,这篇只是一个小小的总结,从这些坑中,我们也知道分布式有它的优势也有它的劣势,那到底该不该用分布式,完全取决于业务、时间、成本以及开发团队的综合实力。后续我会继续分享分布式中的一些底层原理,当然也少不了分享一些避坑指南。

© 版权声明

相关文章