惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

U
Unit 42
C
Cybersecurity and Infrastructure Security Agency CISA
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Know Your Adversary
Know Your Adversary
S
Securelist
I
Intezer
AWS News Blog
AWS News Blog
L
LINUX DO - 热门话题
P
Privacy International News Feed
Recent Announcements
Recent Announcements
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
博客园 - 聂微东
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Attack and Defense Labs
Attack and Defense Labs
N
News and Events Feed by Topic
The GitHub Blog
The GitHub Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Schneier on Security
Schneier on Security
N
Netflix TechBlog - Medium
爱范儿
爱范儿
B
Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
C
CERT Recently Published Vulnerability Notes
Hacker News: Ask HN
Hacker News: Ask HN
Google DeepMind News
Google DeepMind News
Engineering at Meta
Engineering at Meta
Blog — PlanetScale
Blog — PlanetScale
WordPress大学
WordPress大学
S
Secure Thoughts
K
Kaspersky official blog
N
News | PayPal Newsroom
O
OpenAI News
Last Week in AI
Last Week in AI
C
Check Point Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Cyberwarzone
Cyberwarzone
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
Tor Project blog
大猫的无限游戏
大猫的无限游戏
Vercel News
Vercel News
D
Docker
Hugging Face - Blog
Hugging Face - Blog
T
Threat Research - Cisco Blogs
Cisco Talos Blog
Cisco Talos Blog
The Register - Security
The Register - Security
博客园 - 司徒正美
Martin Fowler
Martin Fowler
人人都是产品经理
人人都是产品经理
P
Palo Alto Networks Blog

Mobility

从薅 token 到管 skill:我的 pks 工具落地实践 把笔记、微信读书、知乎装进 Obsidian:我基于llm-wiki知识中枢搭建实录 免费AI视频生成器:我如何用零成本做出带旁白字幕的多场景AI视频 Agnes免费模型真能白嫖视频?我改造了ViMax来试试 教你薅token(二):构建agent无关的skills管理工作流 教你薅token:构建agent无关的AI工作流 用 AI Agent 完成 Hexo 主题迁移:从 Next 到 Butterfly 的全自动化实践 Vercel封禁163邮箱后,我是怎么恢复博客的 用LLM管理安全开发规范:一次llm-wiki实践 Vaadin框架教程:Java工程师的前端开发秘籍 hexo多语言方案总结及最佳实践 知乎增强工具-评论时间精确到秒 怎么理解数据库的四个隔离级别 kubernetes是什么-实用向教程 怎么更科学的用知乎摸鱼 读书笔记《系统之美》,如何面对现实中的复杂问题 高并发解决方案很难吗?轻松聊清楚高并发设计 SSP,DSP,RTB,ADX都是什么? 讲讲互联网广告的概念与发展 从redolog,undolog到隔离级别,刨根问底,讲清楚事务和ACID java项目低学习成本使用kubernetes的实践经验 剧变中的2021-一个中年工程师的年终总结 kubernetes环境下做金丝雀发布的一种思路 prometheus教程: 一篇文章讲懂prometheus 实现一个简单的java版本高性能获取ip地址所属国家工具 iterm2配置ssh书签, 实现记住密码和自动登录 怎样做一个好的技术分享 云原生究竟是什么 读书笔记 稻盛和夫《干法》-思考应该怎样去工作 review的个人价值 户口?大厂?高薪?生活?聊聊应届程序员的职业选择 RPC接口将所有输入输出封装成类是合理设计吗 程序员做业务开发的价值 设计模式的原则,设计模式究竟是什么 我提升开发效率的经验 通过合理的设计降低软件开发复杂度 信息传播过程中的衰减 看房小记-2020北京学区 读书感悟:文明、现代化、价值投资与中国 读书笔记-我曾走在崩溃的边缘 [翻译][关于分布式架构和系统设计]分布式系统的模式-综述 redis的对象 redis里的数据结构 设计模式备忘录 聊聊程序员的职业生涯,我对程序员这个职业的理解 通过mysql批量操作不生效问题聊聊java里mysql的batch 【实践经验】单元测试怎么写 关于rocketmq的readQueue和writeQueue 数据分析的利器-clickhouse介绍 通过位运算转换大小写 activemq多线程消费的不同处理方式 高并发下作余额扣减的一些经验 java细节:三目运算符和自动拆箱 设计一个开源的北京地铁路线规划小工具 java版本 redis cluster的数据迁移 jstorm源码解析之循环任务AsyncLoopThread activemq特性之持久化 activemq的安装及基本使用 activemq系列-概述 hexo教程:博客系统搭建及部署到github jstorm的监控metrics数据输出到第三方存储介质 camel系列之camel debugger的使用 jstorm源码解析之bolt异常处理 log4j动态添加appender 【翻译】java里编写基准测试的一些经验 java线程池:获取运行线程数并控制线程启动速度 maven里的mirror和repository: 配置多repository 使用maven shade plugin 打可执行Jar包 activemq plugin开发指南及示例 通过加入classpath的形式实现命令行运行java程序时引入第三方jar包 解决fatjar的 “java.lang.SecurityException: Invalid signature file digest for Manifest main attributes” 问题 通过实际操作理解redis cluster原理 一种实现在hbase中存储set的思路 log4j2.xml配置示例及与log4j的区别 不实现equals方法的情况下比较java list 使用lua脚本和jedis实现redis的hmsetnx命令,操作hash表时不覆盖原有数据 vitualbox虚拟机安装centos 7 及ssh访问、自启动等配置 在java独立进程(standalone app)中嵌入hawtio监控 java日志系统简介: 从tomcat大量打印debug日志说起 Java kryo/protobuf/protostuff序列化 or Json 性能对比 java分布式锁入门实战 leetcode第三题: 输出不包含重复字母的最长子串 java多线程实现三个字母顺序输出 一次kafka空间激增排查:kafka的数据压缩、批量发送等 java里128有何魔力? 聊聊Integer的缓存 storm/jstorm生态与周边工具,storm连接activemq,kafka,hdfs等 jstorm UI 介绍 五分钟学会写storm代码: jstorm/storm编码原理与普通java程序的区别 activemq web console的权限配置 storm ui 中一些关键属性的含义 activemq 5.6 连接池的内存泄露问题 关于apache camel的消息转发效率 一篇不错的lda模型入门文档 wordcount代码 hadoop基本的学习资料
分布式系统设计中的通用方法
流沙 · 2022-04-13 · via Mobility

之前翻译过一篇关于分布式系统的文章 https://lichuanyang.top/posts/3914/ ,在各个平台都取得了不错的反响。因此,最近又重新整理了一下相关的知识,结合一些这一年多里新的理解,重新整理了下这篇文章。

首先我们需要明确本文要讨论的分布式系统是什么,简单的说,就是满足多节点和有状态这两个条件即可。多节点很好理解,有状态则是指这个系统要维护一些数据,不然的话,其实我们无脑的水平扩容就没有任何问题,也就不存在分布式系统的问题了。

常见的分布式系统, 无论是mysql, cassandra, hbase这些数据库,还是rocketmq, kafka, pulsar这样的消息队列,还是zookeeper之类的基础设施,其实都满足这两个条件。

这些分布式系统的实现通常来说主要需要关注两个方面:一是自己本身功能的实现,二是在分布式环境下保持良好的性能与稳定性;即便是两个功能完全不一样的系统,其对第二类问题的处理方式也会有很多相似之处。本文的关注重点也即在对第二类问题的处理上。

接下来,我们列举一下分布式系统都有哪些常见目标,包括而不限于:

  • 大量普通的服务器通过网络互联,对外作为整体提供服务;
  • 随着集群规模增长,系统整体性能表现为线性增长;
  • 能够自动容错,故障节点自动迁移,不同节点的数据要能保持一致性;

要达成这些目标,又有哪些挑战呢?大概有以下这些:

  1. 进程崩溃: 原因很多,包括硬件故障、软件故障、正常的例行维护等等,在云环境下会有一些更加复杂的原因;
    进程崩溃导致的最大问题就是会丢数。出于性能的考虑,很多情况下我们不会进行同步的写磁盘,而是会将数据暂时放在内存的缓冲区,再定期刷入磁盘。而在进程崩溃的时候,内存缓冲区中的数据显然会丢失。

  2. 网络延迟和中断: 节点的通信变到很慢时,一个节点如何确认另一个节点是否正常;

  3. 网络分区: 集群中节点分裂成两个子集,子集内通信正常,子集之间断开(脑裂),这时候集群要如何提供服务。

这里插一个彩蛋,在CAP理论的前提下,现实中的系统通常只有两种模式:放弃高可用的CP模式和放弃强一致性的AP模式。为什么没有一种放弃分区容忍性的CA模式?就是因为我们无法假设网络通信一定正常,而一旦接受了集群变成两个分区,再想合并回来就不现实了。

  1. 进程暂停:比如full gc之类的原因导致进程出现短暂的不可用后又迅速恢复,不可用期间集群有可能已经做出了相关的反应,当这个节点再恢复的时候如何维持状态的一致性。

  2. 时钟不同步和消息乱序:集群内不同节点的操作,我们希望它的顺序是明确的;不同节点之间的时钟不同步,会导致我们无法利用时间戳确保这件事。而消息的乱序就给分布式系统的处理带来了更大的难度。

下面,我们就依次介绍,针对这些问题,都有什么处理方式。

对于进程崩溃的问题,首先要明确的是,单纯实现进程崩溃下不丢数,没有任何难度,重要的是怎么在保证系统性能的前提下达到这个目标。

首先要介绍的就是write-ahead log这种模式,服务器将每个状态更改作为命令存储在硬盘上的仅附加(append-only)文件中。 append操作由于是顺序的磁盘写,通常是非常快的,因此可以在不影响性能的情况下完成。 在服务器故障恢复时,可以重播日志以再次建立内存状态。

其关键思路是先以一个小成本的方式写入一份持久化数据,不一定局限于顺序写磁盘,此时就可以向client端确认数据已经写入,不用阻塞client端的其他行为。server端再异步的去进行接下来高消耗的操作。

典型场景及变体:mysql redo log; redis aof; kafka本身 ;业务开发中的常见行为:对于耗时较高的行为,先写一条数据库记录,表示这个任务将被执行,之后再异步进行实际的任务执行;

write-ahead log会附带一个小问题,日志会越攒越多,要如何处理其自身的存储问题呢?有两个很自然的思路: 拆分和清理。

拆分即将大日志分割成多个小日志,由于WAL的逻辑一般都很简单,所以其拆分也不复杂,比一般的分库分表要容易很多。这种模式叫做 Segmented Log, 典型的实现场景就是kafka的分区。

关于清理,有一种模式叫做low-water mark(低水位模式), 低水位,即对于日志中已经可以被清理的部分的标记。标记的方式可以基于其数据情况(redolog), 也可以基于预设的保存时间(kafka),也可以做一些更精细的清理和压缩(aof)。

再来看网络环境下的问题,首先使用一个非常简单的心跳(HeartBeat)模式,就可以解决节点间状态同步的问题。一段时间内没有收到心跳,就将这个节点视为已宕机处理。

而关于脑裂的问题,通常会使用大多数(Quorum)这种模式,即要求集群内存活的节点数要能达到一个Quorum值,(通常集群内有2f+1个节点时,最多只能容忍f个节点下线,即quorum值为f+1),才可以对外提供服务。我们看很多分布式系统的实现时,比如rocketmq, zookeeper, 都会发现需要满足至少存活多少个节点才能正常工作,正是Quorum模式的要求。

Quorum解决了数据持久性的问题,也就是说,成功写入的数据,在节点失败的情况下,是不会丢失的。但是单靠这个,无法提供强一致性的保证,因为不同节点上的数据是会存在时间差的,client连接到不同节点上时,会产生不同的结果。可以通过主从模式(Leader and Followers) 解决一致性的问题。其中一个节点被选举为主节点,负责协调节点间数据的复制,以及决定哪些数据对client是可见的。

高水位(High-Water Mark)模式是用来决定哪些数据对client可见的模式。一般来说,在quorum个从节点上完成数据写入后,这条数据就可以标记为对client可见。完成复制的这条线,就是高水位。

主从模式的应用范围实在太广,这里就不做举例了。分布式选举算法很多,比如bully, ZAB, paxos, raft等。其中,paxos无论是理解还是实现难度都太大,bully在节点频繁上下线时会频繁的进行选举,而raft可以说是一种稳定性、实现难度等各方面相对均衡,使用也最广泛的一种分布式选举算法。像elastic search, 在7.0版本里,将选主算法由bully更换为raft;kafka 2.8里,也由利用zk的ZAB协议,修改为raft.

到这儿,我们先总结一下。实际上,一个对分布式系统的操作,基本上就可以概括为下边这么几步:

  1. 写主节点的Write-Ahead Log;
  2. 写1个从节点的 WAL
  3. 写主节点数据;
  4. 写1个从节点数据
  5. 写quorum个子节点WAL
  6. 写quorum个子节点数据

其中,2-5步之间的顺序不是固定的。分布式系统平衡性能和稳定性的最重要方式,实质上就是决定这几步操作的顺序,以及决定在哪个时间点向client端返回操作成功的确认信息。例如,mysql的同步复制、异步复制、半同步复制,就是典型的这种区别的场景。

关于进程暂停,造成的主要的问题场景是这样的:假如主节点暂停了,暂停期间如果选出了新的主节点,然后原来的主节点恢复了,这时候该怎么办。这时候,使用Generation Clock这种模式就可以,简单的说,就是给主节点设置一个单调递增的代编号,表示是第几代主节点。像raft里的term, ZAB里的epoch这些概念,都是generation clock这个思路的实现。

再看看时钟不同步问题,在分布式环境下,不同节点的时钟之间必然是会存在区别的。在主从模式下,这种问题其实已经被最大限度的减少了。很多系统会选择将所有操作都在主节点上进行,主从复制也是采取复制日志再重放日志的形式。这样,一般情况下,就不用考虑时钟的事情了。唯一可能出问题的时机就是主从切换的过程中,原主节点和新主节点给出的数就有可能存在乱序。

一种解决时钟不同步问题的方案就是搞一个专门的服务用来做同步,这种服务叫做NTP服务。但这种方案也不是完美的,毕竟涉及到网络操作,所以难免产生一些误差。所以想依靠NTP解决时钟不同步问题时,系统设计上需要能够容忍一些非常微弱的误差。

其实,除了强行去把时钟对齐之外,还有一些简单一些的思路可以考虑。首先思考一个问题,我们真的需要保证消息绝对的按照真实世界物理时间去排列吗?其实不是的,我们需要的只是 一个自洽、可重复的确定消息顺序的方式,让各个节点对于消息的顺序能够达成一致即可。也就是说,消息不一定按照物理上的先后排列,但是不同节点排出来的应该一样。

有一种叫Lamport Clock的技术就能达到这个目标。它的逻辑很简单,如图所示

lamport stamp

就是本机上的操作会导致本机上的stamp加1,发生网络通信时,比如C接收到B的数据时,会比较自己当前的stamp, 和B的stamp+1, 选出较大的值,变成自己当前的戳。 这样一个简单的操作,就可以保证任何有相关性的两个操作(包括出现在同一节点、有通信两种情况)的顺序在不同节点之间看来是一致的。

另外,还有一些相对简单些的事情,也是分布式系统设计中经常要考虑的,比如怎么让数据均匀的分布在各个节点上。对于这个问题,我们可能需要根据业务情况去找一个合适的分片key, 也可能需要找到一个合适的hash算法。另外,也有一致性哈希这种技术,让我们控制起来更自如。

分布式系统设计中还需要重点考虑的一块就是如何衡量系统性能,指标包括性能(延迟、吞吐量)、可用性、一致性、可扩展性等等,这些说起来都比较好理解,但要是想更完善的去衡量,尤其是想更方便的去观测这些指标的话,也是一个很大的话题。

原文地址: https://lichuanyang.top/posts/45718/