2022::书单
书名 分类 Pulsar in Action 编程 第三时效 推理 设计模式之禅(第 2 版) 编程 深入理解 Kafka:核心设计与实践原理 编程 领域驱动设计 编程 UNIX 环境高级编程(第 3 版) 编程 Linux/UNIX 系统编程手册 编程 发布!设计与部署稳定的分布式系统(第 2 版) 编程 是我把你蠢哭了吗 认知 饱食穷民 社会 妻子们的思秋期 社会 流星之绊 推理 你的孩子不是你的孩子 教育
书名 分类 Pulsar in Action 编程 第三时效 推理 设计模式之禅(第 2 版) 编程 深入理解 Kafka:核心设计与实践原理 编程 领域驱动设计 编程 UNIX 环境高级编程(第 3 版) 编程 Linux/UNIX 系统编程手册 编程 发布!设计与部署稳定的分布式系统(第 2 版) 编程 是我把你蠢哭了吗 认知 饱食穷民 社会 妻子们的思秋期 社会 流星之绊 推理 你的孩子不是你的孩子 教育
书名 分类 绝叫 推理 软技能 编程 MySQL 技术内幕 编程 Redis 设计与实现 编程 System Design Interview 编程 东方快车谋杀案 推理 凤凰架构 编程 屍人莊殺人事件 推理 白夜行 推理 嫌疑人 X 的献身 推理 字母表谜案 推理 密室收藏家 推理 尸体变化图鉴 科普 深入理解 Java 虚拟机(第 3 版) 编程 心灵侦探城塚翡翠 推理 恶意 推理 操作系统导论 编程 数据密集型应用系统设计 编程 编码 编程 现代操作系统(原书第 4 版) 编程 绝对不在场证明 推理 编程之道 编程 被讨厌的勇气 心理 计算机网络(原书第 7 版) 编程 金色麦田 推理 乌合之众 心理 诡计博物馆 推理 许三观卖血记 文学 活着 文学
职业 你所能犯的最大错误就是相信自己是在为别人工作。这样一来你对工作的安全感已然尽失。职业发展的驱动力一定是来自个体本身。记住:工作是属于公司的,而职业生涯却是属于你自己的。 成功的软件开发人员之所以能成功都不是偶然的。他们目标明确,为了达成目标,他们制订了坚实可靠而又深思熟虑的计划。 要做什么,什么时候做,以及如何义无反顾。 从非同凡响开始:绝不要做他人都在做的事 只有你开始把自己当作一个企业去思考时,你才能开始做出良好的商业决策。 软件开发人员售卖的就是他们把一个想法变成一个数字化的现实产品的能力。 专注于你正在提供怎样的服务,以及如何营销这项服务; 想方设法提升你的服务; 思考你可以专注为哪一特定类型的客户或行业提供特定的服务; 集中精力成为一位专家,专门为某一特定类型的客户提供专业的整体服务(记住,作为一个软件开发人员,你只有真正专注于一类客户,才能找到非常好的工作)。 我的服务是管理开发人员,招聘培训人才,建立流程,发现问题,提供解决方案,和其他部门合作,保证结果 大多数成功的公司都会开发出让客户主动上门购买的产品或服务,它们才不会一个接一个地追逐客户。 优秀的人才是被主动挖掘而不是自我寻找 思考未来:你的目标是什么 起步阶段最简单的就是在心中树立一个大目标,然后再建立能帮你达成这个大目标的小目标。 人际交往能力:远比你想象的重要 如果你希望人们接受你的想法,并认可其中的价值,首先你最好先主动给他人相同的礼遇。如果你不能保全他人的自尊,那你永远也不可能赢得他的心。 聚精会神地聆听,当轮到你发言的时候,娓娓道来,一语中的。(实际运用中,你可以提前排练一下这种场景,提前准备好如何进行这种谈话。) 直截了当地告诉老板为什么你喜欢想用某种方式实现某个功能,这并不明智。更好的办法是从对方的心态出发提出建议,阐明为什么采用你建议的方法实现该功能对老板非常有用。理由可能是“让软件更稳定”,或者“能让软件按时交付”。 我们必须要不惜一切代价避免争吵。 在小事情上,任何放弃立场或承认错误的机会对你而言可能没什么大不了的,但对他人却可能是举足轻重的,这么做不仅能为你赢得不可估量的尊重,也能为你的未来积蓄财富,形势逆转时即可兑现使用。 破解面试之道 通过面试的最快捷的方式是让面试官对你怀有好感。 事先就确定我要为这家公司工作的 面试开始之前就思考应对面试的策略。 你必须要突破常规,想尽办法与公司内部人员建立联系。 看看能不能在面试之前得到预面试的机会 能够自发地、无需过问就能做事的员工通常能增加公司的净收入,此外,他们也让老板少操心,只占用少量的管理资源。 与雇用技术高超但需要生拉硬拽才能干活的人相比,我宁愿雇用这样的开发人员:知道的东西可以少一点,但是明确知道要做什么,以及怎样去做。从某种程度上,在你可控的范围之内,面试的时候你要集中精力证明自己就是无需督促也能自动自发做好事情的员工。 集中精力推销自己会对你大有裨益 就业选择:列出你的选择 选择 1:雇员 选择 2:独立咨询师 选择 3:创业者 你是哪类软件开发人员 只要你专业能力雄厚,市场没有过渡饱和,与那些自称为“软件开发人员”的人相比,你能更轻松地找到工作或者赢得客户。 成为这个领域的专家,你就会获得大量业务。 攀登晋升阶梯 在任何公司里能让你脱颖而出的最重要法宝就是承担更多的责任。 没有人愿意涉足的领域是搜寻机会最好的地方。 另一种间接承担责任的方式是成为团队中其他人的导师,自愿帮助新人加速成长,为任何有需要的人提供帮助。 保证“曝光度”——定期与老板会面,确保你经常被注意到。 千万不要忘记分享自己学到的东西。 你要成为那个永远能为各种问题找到解决方案的人,要成为勇于执行这些解决方案以获得成果的人。 你应该对所在组织的政治气候保持警觉。尽管不能完全避开政治,但至少应该知道会发生什么,哪种人需要避开,哪种人永远不要有交集。 确定要自学的最有价值的东西是什么,制订一份下一年的自学计划。 成为专业人士 成为专业人士是一种心态。如果我们总是与恐惧、自毁、拖延和自我怀疑作斗争,那么问题就是:我们正在像外行那样思考问题。外行毫不起眼,外行人废话连篇,外行屈从于逆境。专业人士可不这么想。不管怎样,他引人注目,他恪尽职守,他始终如一。成为专业人士的全部在于:引人注目,恪尽职守,以及不屈服于挫折。成为专业人士,需要你克服自身的缺点,静下心来创作出尽可能最好的作品。 专业人士会坦承自己不知道答案,但是你可以信赖他会找到答案。 每天提前做好计划,就能养成有效管理时间的习惯。专业人士知道每天必须要做什么工作,并且能估算出每项工作大约要花多长时间。 成为自由职业者:开启自己的一片天地 “吸引式营销”基本上就是让潜在的客户主动送上门,而不是你去找他们。你要做的事情就是免费提供有价值的东西。 创建你的第一个产品 在创建产品之前,先筛选出一组特定的受众,他们也是你的解决方案的目标用户。 你打算开始创业吗 很好的创业候选是能够申请专利或受保护的新技术和新方法,而糟糕的创业候选则包括餐厅或其他缺乏独创、很容易被复制的服务。 好的创业项目要有规模扩张的潜力——想想 Twitter、Dropbox 和 Facebook 等。 假装自己能成功 有目的地将自己置于困境,演练一下自己既定的应对策略。 单调乏味的简历——如何修改 beautiful-resumes 上列出了许多漂亮、充满创意的简历,你可以从中得到一些启发。 自我营销 营销就是一场争夺人们注意力的竞赛。 针对“码农”的营销基础课 营销的核心在于将一些人所需要的所期待的产品或者服务与产品或服务本身连接起来。 成功进行自我营销的关键在于:如果想让别人喜欢你,想和你一起工作,你就必须要为他们提供价值。 无论你身在何处都要营销。 自我营销的基本机制是,要想让人们追随你、倾听你,你就要带给他们价值:你能为他们的问题提供答案,甚至是给他们带去欢乐。 打造引人注目的品牌 但品牌不只是商标,更是一项承诺。品牌树立了客户对你的期望,而且这些期望也必须能够实现。 品牌所要传递的信息、品牌的视觉符号、品牌的一致性和品牌的曝光率。 明确要传达的品牌信息。挑选细分市场。创建品牌口号。创建电梯内销售概要。创建视觉符号(即标识)。 接下来,你应该创建所谓“电梯内销售概要”。电梯内销售概要是一段能够快速描述你是谁、能做什么的宣传文字,乘坐电梯的工夫就可以浏览完毕。想想在晚宴上,或者就在电梯里,当有人问起来“你是做什么的”的时候你该怎样回答人家。 创建大获成功的博客 提高你的沟通技巧。组织自己的思想,并将其转化为文字,是一项颇具难度却也极具价值的技能。 持之以恒地坚持写作,坚持不懈地产生高品质的内容,如果你做到了这两点,基本上你就成功了。 你的主要目标:为他人增加价值 不要努力成为一个成功的人,而要努力成为一个有价值的人。——阿尔伯特·爱因斯坦 要想让自我营销的所有努力奏效,基本的方法就是帮助他人获得成功。 你提供的内容应该直接瞄准你所选定的研究领域,为该领域带来价值。 免费内容比付费内容更容易被分享。 最富有创造力的人也是最乐于助人的人。 善于运用社交媒体 发布你认为有用或有趣的。你自己觉得有价值东西,在很大概率上别人也会认同。 演讲、报告和培训:做“说话的极客” 演讲也是一种互动媒介,或者至少你能将其作为媒介使用。 最好从小规模的场合做起,逐渐完善你的演讲技能。要想能在公众面前从容自如地发表演说,需要很长时间的刻苦练习。 作为人类,我们拥有良好的适应能力。只要你把一件事情重复足够多次,你自然就会接纳它。如果你一直坚持在公共场合发表演说,你一定会应对自如,恐惧感终将消散。 著书立说,吸引追随者 要想让自己有机会出书,最好的办法就是明确一个有市场需求的主题,同时也能够充分展示你作为该领域专家的学识。 最后,你应当准备一份翔实的写作提纲(文章摘要),清晰地概括自己的写作目的,明确本书的目标读者,以及你为何认为这本书会成功,为何你是写作这本书的最佳人选。你的提纲写得越好,它被出版商接受的可能性就越大。 百折不挠,越挫越勇 如果你想成功,你必须要学会收起自己脆弱的自尊心,勇敢走出去,别害怕让自己出丑。 学习 教育就是当一个人把在学校所学全部忘光之后剩下的东西。 学习怎样学习:如何自我教育 如果我告诉你该怎么做,你可能会忘掉,但如果你自己动手做一次,你可能就记住了。如果你能将自己所学的东西教给别人,你不仅能记住,还能理解得更深刻。 我的“十步学习法” 要对自己要学的内容有个基本的了解——了解自己不知道什么就足矣。然后,利用这些信息勾勒出学习的范围,即需要学哪些内容,以及学成之后又会获得什么。 第 1 步到第 6 步:这些步骤只做一次 在这一步,你要做的就是了解自己将要学习的主题的全局。这个主题宏观上什么样?你能从中学到足够丰富的知识以了解自己所不知道的吗?以及自己所不知道的有多少? 研究。通常你可以使用网络搜索来完成大部分研究。如果你碰巧有一本关于该主题的书,那么你就可以只读一下其中的介绍性章节,粗略浏览一下内容,但是不要在这一步上花费太多时间。 了解如何设置和安装 Ubuntu Linux,以及如何使用它的基本特性 为了学习该主题下的不同子主题,你可能会扩张你的学习范围而不够聚焦,但是请务必抵制住这个诱惑,尽可能地保持专注。 最后,在这一步中一定要注意:明确学习范围的时候要考虑时间因素。如果你只有一周时间,你需要本着实事求是的态度确定自己能在这段时间内学到什么。如果你有几个月的时间,你也许能攻克一个更大的主题。你的学习范围务必大小适当,既能符合你的学习理由,又能符合你的时间限制。 ...
经济机器是怎样运行的 经济就像一部简单的机器那样运行,但很多人不懂得这一点,或是对经济的运行方式持有不同观点,于是导致很多不必要的经济损失,我深感有责任与大家分享,我的简单但是实用的经济分析模式。这个模式虽然不符合常规传统经济学,但是已经帮助我预测和躲避了全球金融危机,30 多年来对我一直很有用,我们开始吧! 经济虽然可能看起来复杂,但其实是以简单和机械的方式运行。经济由几个简单的零部件和无数次重复的简单交易组成,这些交易首先是由人的天性所驱动的,因而形成三股主要的经济动力。 1、生产率的提高 2、短期债务周期 3、长期债务周期 下面我们谈一下这三股动力,并介绍如何把他们组合在一起得出一个良好的模型,便于我们跟踪经济走势,并理解当前正在发生的事情。我们先来说一下经济中最简单的部分 — — 交易。 交易 经济不过是无数交易的总和,而交易是一件非常简单的事情,交易时刻都在发生,你每次买东西都是进行一笔交易。在每次交易中,买方使用货币或信用向卖方交换商品、服务或金融资产。信用在使用时和货币一样,因此把花费的货币和信用加在一起就可以得出支出总额。 支出总额是经济的驱动力,如果用支出金额除以销量就得出价格,就是这么简单,这就是交易。交易是经济机器的最基本零件,所有的经济周期和动力都是交易造成的,所以理解了交易就理解了整个经济。 一个市场由买卖同一种商品的所有买方和卖方组成,例如小麦市场、汽车市场、股票市场和千百万种其他市场,经济就是由所有市场内的全部交易构成。把全部市场的总支出和销量加在一起就得到了了解经济运行所需要的全部信息,就这么简单。 个人、企业、银行和政府都在以上述方式从事交易,用货币和信用交换商品、服务和金融资产。政府是最大的买方和卖方,而政府有两个组成部分,即收税和花钱的中央政府和中央银行。央行控制着经济中的货币和信贷数量,因此不同于其他买方和卖方,央行通过影响利率和发行更多货币来实行这种控制。我们在下面会看到,正因如此,央行在信贷流通当中发挥着重要作用。 信贷 请诸位注意信贷,信贷是经济中最重要的组成部分,但也许是人们最不了解的部分,它之所以最重要是因为它是经济中最大且最为变幻莫测的一部分,贷款人和借款人与在市场中进行交易的买方和卖方没有两样。通常贷款人希望自己的钱生出更多的钱,而借款人则想购买当前无法负担的某种东西,比如房子、汽车或是进行投资,比如开办企业,借贷可以同时满足贷款人和借款人的需要。 借款人保证偿还借款称为本金,并支付额外的款额称为利息。利率高时借贷就会减少,因为贷款变得昂贵,当利率低时借贷就会增加,因为贷款变得便宜。如果借款人保证偿还债务而且贷款人相信这一承诺,信贷就产生了。任何两个人都可以通过协定凭空创造出信贷,信贷看似简单实则复杂,因为信贷还有其它名称,信贷一旦产生,立即成为债务。债务是贷款人的资产,是借款人的负债,等到借款人今后偿还了贷款并支付了利息,这些资产和负债将消失,交易得以完成。 那么为什么信贷如此重要?这是因为,借款人一旦获得信贷,便可以增加自己的支出。不要忘记,支出是经济的驱动力,这是因为一个人的支出是另一个人的收入。想想看,你每花一块钱另一个人就挣了一块钱,而你每挣一块钱,必定有别人花了一块钱,所以你花的越多,别人挣的就越多。如果某人收入增加,其信用度就会提高,贷款人就更愿意把钱借给他。信用良好的借款人具备两个条件,偿还能力和抵押物。收入债务比率高,借款人就具备偿还能力,如果无法偿还,借款人还可以用有价值可以出售的资产作为抵押物,这样贷款人可以放心的把钱借给他们。所以收入增加使得借贷也增加,从而能够增加支出。由于一个人的支出是另一个人的收入,这将导致借贷进一步增加,并不断循环,这一自我驱动的模式导致经济增长,也正是因为如此才产生了经济周期。 经济周期 在一项交易中,为了获得某样东西,你必须付出另一样东西,长期来看你得到多少取决于你生产多少。我们的知识随时间而逐渐增多,知识的积累会提高我们的生活水平,我们将此称为生产率的提高。一个善于创新和勤奋的人,将比那些自满和懒惰的人,更快的提高生产率和生活水平,但在短期内不一定体现出来,生产率在长期内最关键,但信贷在短期内最重要。这是因为生产率的提高不会剧烈波动,因此不是经济起伏的一个重要动力,但是债务是这种动力。因为我们能够通过借贷让消费超过产出,但是在还贷时不得不让消费低于产出。 债务量的波动有两大周期,其中一个周期持续大约 5–8 年,另一个持续大约 75–100 年。大部分人虽然能够感受到波动,但由于离波动太近,每天每周都身临其境,通常比不认为这是周期,我们将在本章考察这三股主要动力,并观察它们如何相互作用,以及它们在日常经济中的表现。 如上所述,经济的上下起伏,不是取决于人们多么善于创新或是勤奋工作,而是主要看信贷的总量。 我们先想象一下一个没有信贷的经济运行。在这样的经济运行中,增加支出的唯一办法是增加收入。因此,需要提高生产率和工作量,提高生产率是经济增长的唯一途径,由于我的支出是另一个人的收入,当我或者另一个人提高生产率的时候,经济就会增长。我们如果观察各种交易加以总结,就会发现一条类似于生产率增长轨迹的渐进线,但是由于我们借贷于是产生了周期。原因并不是任何法规,而是人的天性和信贷的运作方式。 借债不过是提前消费,为了购买现在买不起的东西,你的支出必然超过收入,因此你需要借钱,实质上是向未来的自己借钱。你给自己设定了一个未来的时间,到那个时候你的支出必须少于收入,以便偿还债务,这样马上就形成了一个周期。通常一旦你借钱就制造了一个周期,对于个人是这样,对于整个经济运行也是这样。这就是为什么必须理解信贷,因为信贷触发了一系列机械和可以预料的将在未来发生的事件。这就是信贷不同于货币的地方。 完成交易需要使用货币,当在酒吧用货币买一瓶啤酒时,交易立即完成。但是如果你用信用来买一瓶啤酒,比如赊账,你相当于承诺今后为这瓶啤酒付钱,你和酒吧一起创造了一笔资产和一笔负债,你们凭空制造出了信贷。只有在你今后清偿了这笔赊账之后,上述资产和负债才会消失,债务才会还清,交易才会了结。 现实生活中大部分所谓钱实际上是信贷,美国国内的信贷总额大约 50 万亿美元,而货币总额只有大约 3 万亿美元。不要忘记,在没有信贷的经济运行中,增加支出的唯一办法是增加生产,但是在有信贷的经济运行中,还可以通过借债来增加支出。因此,有信贷的经济运行能增加支出,使得收入的增长速度在短期内超过生产率的增长,但在长期内并非如此。但是请不要误解我的意思,信贷不一定是坏事,只是会导致周期性变化。 信贷如果造成超过偿还能力的过度消费就是不良信贷,但是信贷如果高效率的分配资源和产生收入,让你能偿还债务就是良性信贷。 例如如果你借钱买一台大彩电,电视机不会带来任何收入让你偿还债务,但是你如果借钱买一台拖拉机,用它来收获更多的庄稼,赚更多的钱,你就能偿还债务,提高生活水平。 在有信贷的经济运行中,我们可以跟踪各种交易,观察信贷如何带来经济增长。 我举一个例子,假设你每年挣 10 万美元,没有任何债务,你有不错的信用可以借 1 万美元,比如用信用卡借。因此你每年可以花 11 万美元,即时你的收入只有 10 万美元。由于你的支出是别人的收入,另一个人因此挣了 11 万美元,这个挣了 11 万美元的人如果没有任何债务,可以借 1.1 万美元,他可以消费 12.1 万美元,即使他的年收入只有 11 万美元。由于他的支出是另一个人的收入,而我们通过跟踪个人的交易可以看到这个过程不断自我强化。但不要忘记借债形成,周期会上升最终也会下降。 短期债务周期 下面我们谈谈短期债务周期,随着经济活动的增加,出现了扩张,这是短期债务周期的第一阶段。支出继续增加,价格开始上涨,原因是导致支出增加的是信贷,而信贷可以即刻凭空产生。如果支出和收入的增长速度超过所出售商品的生产速度,价格就会上涨,我们把价格的上涨称为通货膨胀。 央行不希望通货膨胀过高,因为这会导致许多问题。央行在看到价格上涨时就会提高利率,随着利率上升,有能力借钱的人就会减少,同时现有的债务成本也会上升,就等于你每个月的信用卡还款额会增加。由于人们减少借债,还款额度增长,剩下来用于支出的资金就减少,因此支出速度放慢,而由于一个人的支出是另一个人的收入,环环相扣,人们的收入将下降。由于支出减少价格将下跌,我们称之为通货紧缩,经济活动减少,经济便进入衰退。 如果衰退过于严重,而且通货膨胀不再成为问题,央行将降低利率,使经济活动重新加速。随着利率降低,偿债成本下降,借债和支出增加,出现另一次经济扩张。可见经济像一部机器一样运行,在短期债务周期中,限制支出的唯一因素是贷款人和借款人的贷款和借款意愿。如果信贷易于获得,经济就会扩张,如果信贷不易获得,经济就会衰退。请注意这个周期主要由央行控制,短期债务周期通常持续 5–8 年,在几十年里不断重复。 但是请注意在每个周期的低谷和高峰后,经济增长和债务都超过前一个周期。为什么会这样,这是人促成的,人具有借更多钱和花更多钱的倾向,而不喜欢偿还债务。这是人的天性,因此在长期内债务增加的速度超过收入,从而形成长期债务周期。 长期债务周期 尽管人们的债务增加,但贷款人会提供更宽松的信贷条件,这是为什么?这是因为,大家都以为形式一片大好,人们仅注意最近出现的情况,最近的情况是什么呢?收入一直在增加,资产价值不断上升,股票市场欣欣向荣,现在是繁荣时期,用借来的钱购买商品、服务和金融资产很划算,当人们过度借贷消费时,泡沫便产生了。因此尽管债务一直增加,但收入也以相近的速度增加,从而抵消了债务。我们把债务与收入比例称为债务负担,只要收入继续上升,债务负担就可以承受。 与此同时,资产价值迅猛上升,人们大量借钱来购买资产,因为投资促使资产价格日益升高。人们感觉自己很富有,因此尽管积累了大量债务,收入和资产价值的上升帮助借贷人在长期内保持良好的信用度。 但是这种情况显然无法永久持续下去,也确实没有持续下去,几十年来债务负担缓慢增加使偿债成本越来越高,到了一定的时候,偿债成本的增加速度超过收入,迫使人们削减支出。由于一个人的支出是另一个人的收入,收入开始下降。人们的信用因此降低,致使借贷减少,偿债成本继续增加,使得支出进一步减少。周期开始逆转,这时到达长期债务的顶峰,债务负担变得过重。 美国、欧洲和世界上很多其他地区在 2008 年发生了这一情况,日本在 1989 年和美国在 1929 年因同样原因发生这一情况,现在经济进入去杠杆化时期。 ...
MySQL 体系结构和存储引擎 定义数据库和实例 数据库 物理操作系统文件和其他形式文件类型的集合 frm、MYD、MYI、ibd 结尾的文件 实例 MySQL 数据库由后台线程和一个共享内存组成 数据库实例才是真正用来操作数据库的 实例与数据库一一对应 MySQL 数据库实例在系统上的表现就是一个进程 配置加载顺序 /etc/my.cnf => /etc/mysql/my.cnf => /usr/local/mysql/etc/my.cnf => $HOME/.my.cnf MySQL 体系结构 组成 连接池组建 管理服务和工具组建 SQL 接口组件 查询分析器组件 优化器组件 缓冲组件 插件式存储引擎 物理文件 存储引擎是基于表的,而不是数据库 存储引擎 InnoDB 存储引擎 支持事务,面向 OLTP 应用 行锁,外键 支持裸设备 多版本并发控制,四种隔离级别 next-key locking 避免幻读 插入缓冲、二次写、自适应哈希索引、预读 采用索引组织表,每张表的存储都是按照转的顺序存放的 MyISAM 存储引擎 不支持事务 表锁设计 支持全文索引 面向 OLAP 只缓存索引文件 MyISAM 存储引擎表由 MYD 和 MYI 组成 MYD 存放数据文件 MYI 存放索引文件 NDB 存储引擎 集群存储引擎 share nothing 架构 Memory 存储引擎 表数据放在内存 使用哈希索引 用于存放临时结果集 Archive 存储引擎 只支持 INSERT 和 SELECT 使用 zlib 压缩数据,有较好的压缩率 适合归档数据 Federated 存储引擎 指向一台远程 MySQL 数据库服务器上的表 Maria 存储引擎 目标取代 MyISAM 特性同 InnoDB 存储引擎 其他存储引擎 Merge CSV Sphinx Infobright 存储引擎比较 MySQL :: 16 Alternative Storage Engines 连接 MySQL TCP/IP 不同机器之间 在客户端和服务器端连接 命名管道和共享内存 在同一台机器上 通过 –enable-named-pipe 启用 UNIX 域套接字 同一台机器上使用 通过 –socket=/etc/mysql.sock 指定套接字文件 InnoDB 存储引擎 InnoDB 存储引擎概述 是第一个支持完整 ACID 事务的 MySQL 存储引擎 其特点是行锁设计、支持 MVCC、支持外键、提供一致性非锁定读 高效利用内存和 CPU InnoDB 体系架构 后台线程 描述 负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据 此外将以修改的数据文件刷新到磁盘文件 同时保证在数据库发生异常的情况下 InnoDB 能恢复到正常运行状态 不同的线程 Master Thread 核心后台线程,负责将缓存池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新,合并插入缓冲,UNDO 页的回收等 IO Thread InnoDB 中大量使用 AIO 处理请求 IO Thread 的工作是负责这些 IO 请求的回调处理 Purge Thread 回收已经使用并分配的 undo 页 Page Cleaner Thread 将之前版本中脏页的刷新操作放入到单独的线程中完成 内存 缓冲池 通过内存的速度弥补磁盘速度较慢对数据库性能的影响 对内容的读取先判断是否在缓冲池中,否则从磁盘读取到缓冲池中,再从缓冲池中读取 对内容的更性操作先在缓冲池中进行,然后通过 checkpoint 机制写入磁盘 缓冲池的大小直接影响数据库的性能 数据页类型 索引页 数据页 undo 页 插入缓冲 自适应哈希索引 InnoDB 存储的锁信息 数据字典信息 等 LRU List、Free List 和 Flush List 数据库中的缓冲池通过 LRU 算法来管理 最频繁使用的页面在最前面,最少使用的在 LRU 列表的尾端 当缓冲池不能存放新读取到的页面时,将首先释放 LRU 列表中尾端的页 优化,最新读区到的页放到列表中间位置,避免新页导致热点页被刷到磁盘 LRU 列表从 Free 列表分配页面 可以使用`show engine innodb status 来观察 LRU 列表和 Free 列表的使用情况和状态 非 16KB 的压缩页面通过伙伴算法分配 Flush List 用来管理需要被刷回磁盘的脏页 redo 日志缓存 InnoDB 会把 redo 日志放入这个缓冲区,然后刷到磁盘上 时机 Master Thread 每一秒将重做日志缓存刷新到重做日志文件 每个事务提交前会将重做日志缓存刷新到重做日志文件 当重做日志缓冲池小于一半时,重做日志缓存刷新到重做日志文件 额外的内存池 在对一些数据结构本身的内存进行分配时,会从额外的内存池申请。 Checkpoint 技术 WAL 策略 当事务提交时,先写重做日式,再修改页 当发生宕机事故时,通过重做日志来完成对数据的恢复 目标 缩短数据库的恢复时间 当数据库发生宕机时,数据库只需对 Checkpoint 后的重做日志进行恢复 缓冲池日志不够用时,将脏页刷新到磁盘 根据 LRU 算法会移除最近最少使用的页,若此页为脏页,需要强制 Checkpoint,将脏页刷回磁盘 重做日志不够用时,刷新脏页 重做日志被设计成可以循环使用,重做日志可以覆盖已经刷到磁盘的部分 LSN InnoDB 通过 LSN 来管理版本 每个页有 LSN,重做日志有 LSN,Checkpoint 也有 LSN InnoDB 内部有两种 Checkpoint Sharp Checkpoint 当数据库关闭时,把所有脏页刷新到磁盘 Fuzzy Checkpoint Master Thread Checkpoint FLUSH_LRU_LIST Checkpoint Async/Sync Flush Point Dirty Page too mush Checkpoint Master Thread 工作 最高级别线程 主循环 每一秒 日志缓冲刷新到磁盘,即使这个事务没有提交 合并插入缓冲 之多刷新 100 个 InnoDB 的缓冲池中的脏页到磁盘 如果没有互动切换到后台循环 每十秒 刷新 100 个脏页到磁盘 合并至多 5 个插入缓冲 将日志缓冲刷新到磁盘 删除无用的 Undo 页 刷新 10 个或 100 个脏页到磁磁盘 后台循环 删除无用的 Undo 页 合并 20 个插入缓冲 跳到主循环 不断刷新 100 个页直到符合条件 刷新循环 刷新页面到磁盘 暂停循环 将 Master Thread 挂起 Master Thread 根据数据库状态在几个循环之间切换 InnoDB 关键特性 插入缓冲 InnoDB 存储引擎开创性地设计了 Insert Buffer,对于非聚集索引的插人或更新操作,不是每一次直接插人到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在,则直接捅人:若不在,则先放人到一个 Insert Buffer 对象中,好似欺骗。数据库这个非聚集的索引已经插到叶子节点,而实际并没有,只是存放在另一个位置。然后再以-定的频率和情况进行 Insert Buffer 和辅助索引页子节点的 merge(合并)操作,这时通常能将多个插人合并到一个操作中 (因为在一个素引页中),这就大大提高了对于非聚集索引插人的性能。 两次写 当发生数据库宕机时,可能 InnoDB 存储引擎正在写人某个页到表中,而这个页只写了一部分,比如 16KB 的页,只写了前 4KB,之后就发生了容机,这种情况被称为部分写失效 〈partial page write)。在 InnoDB 存储引擎未使用 doublewrite 技术前,曾经出现过因为部分写失效而导致数据丢失的情况。 doublewrite 由两部分组成,一部分是内存中的 doublewrite buffer,大小为 2MB,另一部分是物理磁盘上共享表空间中连续的 128 个页,即 2 个区 (extent),大小同样为 2MB。在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过 memcpy 函数将脏页先复制到内存中的 doublewrite buffer,之后通过 doublewrite buffer 再分两次,每次 1MB 顺序地写人共享表空间的物理磁盘上,然后马上调用 fsync 函数,同步磁盘,避免缓冲写带来的问题。 自适应哈希索引 InnoDB 存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引 (Adaptive Hash Index,AHI)。AHI 是通过缓冲池的 B+ 树页构造而来,因此建立的速度很快,而且不需要对整张表构建哈希索引。InnoDB 存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引。 异步 IO AIO 不会阻塞读写请求 AIO 可以合并多个 IO 为 1 个 IO,提高 IOPS 的性能 刷新邻接页 InnoDB 存储引擎还提供了 Flush Neighbor Page (刷新邻接页)的特性。其工作原理为:当刷新一个脏页时,InnoDB 存储引擎会检测该页所在区 (extent)的所有页,如果是脏页,那么一起进行刷新。这样做的好处品而易见,通过 AIO 可以将多个 IO 操作合并为一个 IO 操作,故该工作机制在传统机械磁舟下有著显著的优势。 启动、关闭与恢复 正常关闭时,下次启动也会很正常 非正常退出,下次启动时会进行数据恢复 文件 参数文件 告诉 MySQL 实例启动时在哪里可以找到数据库文件,并且指定某些初始化参数,这些参数定义了某种内存结构的大小等设置,还会介绍各种参数的类型。 非必需,可以通过启动参数来配置 参数类型 动态参数:可以在运行中进行修改 静态参数:启动时读取,不可修改 日志文件 用来记录 MySQL 实例对某种条件做出响应时写人的文件,如错误日志文件、二进制日志文件、慢查询日志文件、查询日志文件等。 错误日志 对 MySQL 的启动、运行、关闭过程进行了记录,也记录一些告警信息和正确的信息,用来排查问题 SHOW VARIABLES LIKE 'log_error' 慢查询日志 可以在 MySQL 启动时设置一个阈值,将运行时间超过该值的所有 SQL 语句都记录到慢查询日志中。 SHOW VARIABLES LIKE 'log_slow_queries' log_queries_not_using_indexes 如果查询没有使用索引也会被记录 mysqldumpslow 可以导出慢 sql 二进制日志 记录了堆 MySQL 数据库执行更改的所有操作 作用 恢复(recovery):某些数据的恢复需要二进制日志,例如,在一个数据库全备文件恢复后,用户可以通过二进制日志进行 point-in-time 的恢复。 复制(replication):其原理与恢复类似,通过复制和执行二进制日志使一台远程的 MySQL 数据库(一般称为 slave 或 standby) 与一台 MySQL 数据库(一般称为 master 或 primary) 进行实时同步。 审计 (audit):用户可以通过二进制日志中的信息来进行审计,判断是否有对数据库进行注入的攻击 当使用事务的表存储引擎时,所有未提交的二进制日志会被记录到一个缓存中,等到事务提交的时候将缓存中的二进制日志写入二进制日志文件 当写入二进制文件一半时,数据库奔溃,写入的部分不能撤销,可以使用 innodb_support_xa 设置为 1 解决 binlog_format 参数 STATEMENT 格式和之前的 MySQL 版本一样,二进制日志文件记录的是日志的逻辑 SQL 语句。 在 ROW 格式下,二进制日志记录的不再是简单的 SQL 语句了,而是记录表的行更改情况。基于 ROW 格式的复制类似于 Oracle 的物理 Standby (当然,还是有些区别)。同时,对上述提及的 Statement 格式下复制的问题予以解决。从 MySQL 5.1 版本开始,如果设置了 binlog_format 为 ROW,可以将 InnoDB 的事务隔离基本设为 READ COMMITTED,以获得更好的并发性。 在 MIXED 格式下,MySQL 默认采用 STATEMENT 格式进行二进制日志文件的记录,但是在一些情况下会使用 ROW 格式,可能的情况有: 表的存储引擎为 NDB,这时对表的 DML 操作都会以 ROW 格式记录。 使用了 UUID()、USER()、CURRENT_USER()、FOUND_ROWS()、ROW_COUNT() 等不确定函数。 使用了 INSERT DELAY 语句。 使用了用户定义函数 (UDF)。 使用了临时表(temporary table)。 ROW 格式可以为数据库的恢复和复制带来更好的可靠性,但是会增加二进制的大小,复制的网络开销也会变大。 采用 mysqlbinlog 工具查看 socket 文件 当用 UNIX 域套接字方式进行连接时需要的文件。 pid 文件 MySQL 实例的进程 ID 文件。 MySQL 表结构文件 用来存放 MySQL 表结构定义文件。 InnoDB 存储引擎文件 因为 MySQL 表存储引擎的关系,每个存储引擎都会有自己的文件来保存各种数据。这些存储引擎真正存储了记录和索引等数据。 表空间文件 InnoDB 采用将存储的数据按表空间进行存放的设计,每张表都有独立的表空间可以分担负载。 重做日志 每个 InnoDB 存储引擎至少有 1 个重做日志文件组,每个文件组下至少有 2 个重做日志文件。 重做日志设置太大恢复需要很长时间,设置太小导致一个事务的日志需要多次切换重做日志文件,还会频繁发生 async checkpoint,造成性能的抖动。 目录结构 redo_log_type | space | page_no | redo_log_type redo_log_type 占用 1 字节,表示重做日志的类型 space 表示表空间的 ID,但采用压缩的方式,因此占用的空间可能小于 4 字节 page_no 表示页的偏移量,同样采用压缩的方式 redo_log_body 表示每个重做日志的数据部分,恢复时需要调用相应的函数进行解析 为了保证事务 ACID 的持久性,每次提交事务都要保证重做日志已经刷入磁盘 表 索引组织表 在 InnoDB 存储引擎中,表都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表 在 InnoDB 存储引擎表中,每张表都有个主键,如果在创建表时没有显式地定义主键,InnoDB 会优先使用第一个定义的非空唯一索引,否则自动添加一个 6 字节大小的指针。 InnoDB 逻辑存储结构 所有数据被存放在一个空间内,称之为表空间 表空间又由段、区、页、行组成 表空间 InnoDB 存储引擎结构的最高层 每张表的表空间只存放数据,其他信息存放在共享表空间 段 表空间是由各个段组成的 常见的段有数据段、索引段、回滚段、等 数据段为 B+ 树的非子节点 数据段即为 B+ 树的子节点 区 由连续的页组成,在任何情况下每个区的大小都是 1MB InnoDB 一次性申请 4 到 5 个区 一般 InnoDB 引擎存储页的大小为 16KB,一个区有 64 个页,启用压缩页或者调小默认页大小也保持 1MB 总大小 页\块 页是 InnoDB 磁盘管理的最小单位 默认 16KB,可以调整为 4KB、8KB、16KB 常见类型 数据页 (B-tree Node) undo 页 (undo Log Page) 系统页 (System Page) 事务数据页 (Transaction system Page) 插人缓冲位图页 (Insert Buffer Bitmap) 插入缓冲空闲列表页 (Insert Buffer Free List) 未压缩的二进制大对象页 (Uncompressed BLOB Page) 压缩的二进制大对象页 (compressed BLOB Page) 行 InnoDB 数据是按行存放的 InnoDB 行记录格式 InnoDB 数据页结构 Named File Formats 机制 约束 视图 分区表 索引与算法 InnoDB 存储引擎索引概述 InnoDB 存储引擎支持以下几种常见的索引 B+ 树索引 全文索引 哈希索引 人为不能干预存储引擎是否生成哈希索引 InnoDB 存储引擎以页为单位查找数据 数据结构与算法 二分查找法 基本思想:将记录按有序化(递增或递减)排列,在查找过程中采用跳跃式方式查找,即先以有序数列的中点位置为比较对象,如果要找的元素值小于该中点元素,则将待杳序列缩小为左半部分,否则为右半部分。通过一次比较,将查找区间缩小一半。 二叉查找树与平衡二叉树 二叉查找树:在二叉查找树中,左子树的键值总是小于根的键值,右子树的键值总是大于根的键值。 平衡二叉树:首先符合二叉树的定义,其次必须满足任何节点的两个子树的高度最大差为 1。 B+ 树 B+ 树索引 Cardinality 值 哈希算法 全文检索 锁 什么是锁 锁机制用于管理对共享资源的并发访问 InnoDB 在数据库内部多处使用锁,从而允许对多种不同资源提供并发访问,提供数据的完整性和一致性。 不同的数据库系统实现锁的机制不同 lock 与 latch lock 的对象时事务,用来锁定的是数据库中的对象,如表、页、行。lock 多在事务 commit 或 rollback 后释放,有死锁检测机制。 latch 一般称为闩锁,持续时间非常短,在 InnoDB 引擎中,latch 又可分为 mutex 和 rwlock。其目的是用来保证并发线程操作临界资源的正确性,无死锁检测。 InnoDB 存储引擎中的锁 锁的分类 共享锁(S 锁),允许事务读一行数据 排他锁(X 锁),允许事务删除或更新一行数据 只有 SS 是兼容的,兼容是指对同一行记录的锁的兼容性问题 InnoDB 支持多粒度锁定,允许事务在行级和表级上的锁同时存在 意向锁 在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行所在在数据表的对应意向锁(表级锁) 意向共享锁(IS Lock),事务想要获得一张表中某几行的共享锁 意向排他锁(IX Lock),事务想要获得一张表中某几行的排他锁 意向锁是表级锁,不会与行级锁发生冲突 一致性非锁定读 InnoDB 存储引擎通过多版本控制的方式来读取当前执行时间数据库中行的数据。如果读取的数据正在执行 DELETE 或者 UPDATE 操作,这时候 InnoDB 不会因此区等待行上的锁释放。相反,InnoDB 存储引擎会去读取行的一个快照数据。 快照数据读取通过 undo 段来实现。 一个行记录可能有多个版本 在 RC 和 RR 的隔离级别下,InnoDB 使用非锁定的一致性读。在 RR 下,读取历史快照,在 RC 下,读取最新的一份快照。 一致性锁定读 用户需要显式地对数据库中的读取操作加锁保证数据逻辑的一致性,这需要数据库支持加锁语句 SELECT…FOR UPDATE,其他事务不能对该行加锁 SELECT…LOCK IN SHARE MODE,可以加 S 锁,不能加 X 锁 自增长与锁 自增操作会在执行语句后立即释放锁 自增的列必须是索引,必须是索引的第一列 外键与锁 InnoDB 会自动给外键列加索引,避免锁表 执行 SELECT 操作时,先给父表加 S 锁,避免数据不一致 锁的算法 行锁的三种算法 Record Lock:单个行记录上的锁 Record Lock 总是会去锁住索引记录,如果 InnoDB 存储引擎表在建立的时候没有设置任何一个索引,那么这时 InnoDB 存储引擎会使用隐式的主键来进行锁定 Gap Lock:间隙锁,锁定一个范围,但不包括记录本身 Next-Key Lock:Gap Lock + Record Lock,锁定一个范围,并且锁定记录本身 唯一索引,命中数据,Next-Key Lock 降级为 Record Lock 唯一索引,未命中数据,Next-Key Lock 退化成间隙锁 解决 Phantom Problem Phantom Problem 是指在同一个事务下,连续执行两次同样的 SQL 语句可能导致不同的结果,第二次的 SQL 语句可能返回之前不存在的行。 InnoDB 存储引擎采用 Next-Key Locking 算法避免 Phantom Porblem 锁问题 脏读 一个事务读到了另一个事务未提交的数据 不可重复读 不可重复是一个事务在事务期间读到了其他事物提交的数据 InnoDB 中通过 Next-Key Lock 算法来避免不可重复读的问题 丢失更新 通过 SELECT…FOR UPDATE 避免该问题 加入版本号,通过乐观锁 阻塞 因为不同锁之间的兼容性关系,在有些时刻一个事务中的锁需要等待另一个事务中的锁释放它所占用的资源,这就是阻塞。阻塞并不是一件坏事,其是为了确保事务可以并发且正常地运行。 死锁 死锁的概念 死锁是指两个或两个以上的事务在执行过程中,因争夺资源而造成的一种互相等待的现象 解决死锁最简单的方式是超时回滚 用等待图的方式来进行死锁检测,在每个事务请求锁并发生等待时都会判断是否存在回路,如果有死锁,InnoDB 通常选择回滚代价最小的事务进行回滚操作 死锁概率 系统中事务的数量越多,发生死锁的概率越大 每个事务操作的数量越多,发生死锁的概率越大 操作数据的集合,越小则发生死锁的概率越大 事务 认识事务 概述 事务是访问并更新数据库中各种数据项的一个程序执行单元。在事务中的操作,要么都做修改,要么都不做,这就是事务的目的,也是事务模型区别与文件系统的重要特征之一 ACID 原子性(Atomicity) 原子性指整个数据库事务是不可分割的工作单位。只有使事务中所有的数据库操作都执行成功,才算整个事务成功。事务中任何一个 SQL 语句执行失败,已经执行成功的 SQL 语句也必须撤销,数据库状态应该退回到执行事务前的状态 一致性(Consistency) 一致性指事务将数据库从一种状态转变为下一种一致的状态。在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏 隔离性(Isolation) 事务的隔离性要求诶个读写事务的对象对其他事物的操作对象能相互分离,即该事务提交前对其他事务都不可见。 持久性(Durability) 事务一旦提交,其结果就是永久的。即使发生宕机等故障,数据库也能将数据恢复。 分类 扁平事务 最简单的一种,其操作是原子性的,要么都执行要么都会滚 扁平事务时应用程序成为原子操作的基本组成模块 主要限制是不能提交和会滚事务的某一部分,或者分几个步骤提交 带有保存点的扁平事务 在扁平事务的基础上,允许事务执行过程中会滚到同一个事务较早的一个状态 保存点在事务内部是递增的,ROLLBACK 不影响保存点的递增 嵌套事务 是一个层次结构框架,由一个顶层事务控制着各个层次的事务,顶层事务之下嵌套的事务被称为子事务,其控制每一个局部的变换 可以用支持保存点的事务模拟 子事务可以时扁平事务也可以是嵌套事务 子事务既可以回滚也可以提交,但是它的提交要到父事务提交后才生效 任何一个事务回滚会引起它所有子事务一起回滚 链事务 在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传递给下一个要开始的事务 提交事务操作和开始下一个事务操作将合并成为一个院子操作 分布式事务 通常是一个分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。 事务的实现 概述 原子性、一致性、持久性通过数据库的 redo log 和 undo log 来完成。redo log 称为重做日志,用来保证事务的原子性和持久性。undo log 用来保证事务的一致性。 redo 基本概念 用来实现事务的持久性 由内存中的重做日志缓冲(易失)和重做日志文件(持久)组成 当事务提交时,必须先将事务的所有日志写入到重做日志文件进行持久化,待事务的 commit 操作完成才算完成 顺序写,使用 fsync 操作保证数据落盘,fsync 效率取决于磁盘性能,间接影响数据库的性能 物理格式日志,记录针对每个页的修改 binlog 在提交时写入,redo log 在整个事务执行过程中都在写入 log block 以 512 字节的块进行保存,与磁盘扇区大小一致,不需要 doublewrite 由日志块头(12 字节),日志块内容(492 字节),日志块尾(8 字节)组成 log group 重做日志组,其中有多个重做日志文件 存储的就是之前的 log buffer 中保存的 log block,块大小为 512 字节 写盘时机 事务提交时 当 log buffer 中有一半的内存空间已经被使用 当 log checkpoint 时 通过 round-robin 的方式写入文件 每个 log group 的第一个文件的前 2kb 保存文件的元信息,不写入内容 log file header 的后面部分是 InnoDB 保存的 checkpoint 值,其设计是交替写入的,避免因为介质失败找不到可用的值 redo log 格式 redo_log_type | space | page_no | redo_log_type redo_log_type 占用 1 字节,表示重做日志的类型 space 表示表空间的 ID,但采用压缩的方式,因此占用的空间可能小于 4 字节 page_no 表示页的偏移量,同样采用压缩的方式 redo_log_body 表示每个重做日志的数据部分,恢复时需要调用相应的函数进行解析,根据不同的类型会有不同的内容 LSN Log Sequence Number 的缩写,代表日志序列号,单调递增 含义 重做日志写入的总量 例如当前重做日志的 LSN 为 1000,有一个事务 T1 写入了 100 字节的重做日志,那么 LSN 就变为了 1100,若又有事务 7 写人入 200 字节的重做日志,那么 LSN 就变为了 1300。可见 LSN 记录的是重做日志的总量,其单位为字节。 checkpoint 的位置 页的版本 页头部的 FIL_PAGE_LSN 记录了该页的 LSN 用来判断是否需要进行恢复操作 如果重做日志日志中的 LSN 大于页的 LSN,并且事务已经提交则需要进行恢复操作 恢复 由于 checkpoint 表示已经被刷到磁盘上的 LSN,因此在恢复过程中仅需恢复 checkpoint 开始的日志部分 当数据库在 checkpoint 为 LSN 10000 时宕机器,只需恢复到 10000 到 13000 的数据 undo 基本概念 记录了事务的行为,用来把数据恢复回去 当 InnoDB 存储引擎回滚时,它实际上做的是与先前相反的工作。对于每个 INSERT, InnoDB 存储引擎会完成一个 DELETE;对于每个 DELETE, InnoDB 都会执行一个 INSERT;对于每一个 UPDATE InnoDB 引擎会执行一个相反的 UPDATE,将修改前的行放回去 undo 的另一个作用时 MVCC,InnoDB 存储引擎中的 MVCC 通过 undo 实现的 undo log 也会产生 redo log,undo log 也需要持久化的保护 undo 存储管理 事务控制语句 STARTTRANSACTION |BEGIN: 显式地开启一个事务。 COMMIT : 要想使用这个语句的最简形式,只需发出 COMMIT。也可以更详细一些,写为 COMMIT WORK,不过这二者几乎是等价的。COMMIT 会提交事务,并使得已对数据库做的所有修改成为永久性的。 ROLLBACK : 要想使用这个语句的最简形式,只需发出 ROLLBACK。同样地,也可以写为 ROLLBACK WORK,但是二者几乎是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。 SAVEPOINT identifier : SAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT。 RELEASE SAVEPOINT identitier : 删除一个事务的保存点,当没有一个保存点执行这句语句时,会抛出一个异常。 ROLLBACK TO [SAVEPOINT] identifier: 这个语句与 SAVEPOINT 命令一起使用。可以把事务回滚到标记点,而不回滚在此标记点之前的任何工作。例如可以发出两条 UPDATE 语句,后面跟一个 SAVEPOINT,然后又是两条 DELETE 语句。如果执行 DELETE 语句期间出现了某种异常情况,并且捕获到这个异常,同时发出了 ROLLBACK TO SAVEPOINT 命令,事务就会回滚到指定的 SAVEPOINT,撤销 DELETE 完成的所有工作,而 UPDATE 语句完成的工作不受影响。 SET TRANSACTION : 这个语句用来设置事务的隔离级别。InnoDB 存储引你提供的事务隔离级别有: READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。 隐式提交的事务 以下这些 SQL 语句会产生一个隐式的提交操作,即执行完这些语名后,会有一个隐式的 COMMIT 操作。 DDL 语句 : ALTER DATABASE…UPGRADE DATA DIRECTORY NAME,ALTER EVENT,ALTER PROCEDURE,ALTER TABLE,ALTER VIEW,CREATE DATABASE,CREATE EVENT,CREATE INDEX,CREATE PROCEDURE,CREATE TABLE,CREATE TRIGGER,CREATE VIEW,DROP DATABASE,DROP EVENT,DROP INDEX,DROP PROCEDURE,DROP TABLE,DROP TRIGGER,DROP VIEW,RENAME TABLE,TRUNCATE TABLE。 用来隐式地修改 MySQL 架构的操作 : CREATE USER、DROP USER、GRANT、RENAME USER、REVOKE、SET PASSWORD。 管理语句,ANALYZE TABLE、CACHE INDEX、CHECK TABLE、LOAD INDEX INTO CACHE、OPTIMIZE TABLE、REPAIR TABLE。 对于事务操作的统计 TPS = (com_commit + com_rollback) / time 事务的隔离级别 四个隔离级别 READ UNCOMMITTED READ COMMITTED REPEATABLE READ SERIALIZABLE 分布式事务 MySQL 分布式事务 XA 事务由一个或多个资源管理器、一个事务管理器以及一个应用程序组成。 资源管理器:提供访问事务资源的方法。通常一个数据库就是一个资源管理器。 事务管理器:协调参与全局事务中的各个事务。需要和参与全局事务的所有资源管理器进行通信。 应用程序:定义事务的边界,指定全局事务中的操作。 分布式事务使用两段式提交 (two-phase commit)的方式。在第一阶段,所有参与全局事务的节点都开始准备 (PREPARE),告诉事务管理器它们准备好提交了。在第二阶段,事务管理器告诉资源管理器执行 ROLLBACK 还是 COMMIT。如果任何一个节点显示不能提交,则所有的节点都被告知需要回滚。可见与本地事务不同的是,分布式事务需要多一次的 PREPARE 操作,待收到所有节点的同意信息后,再进行 COMMIT 或是 ROLLBACK 操作。 内部 XA 事务 最为常见的内部 XA 事务存在于 binlog 与 InnoDB 存储引擎之间。由于复制的需要,因此目前绝大多数的数据库都开启了 binlog 功能。在事务提交时,先写二进制日志,再写 InnoDB 存储引擎的重做日志。对上述两个操作的要求也是原子的,即二进制日志和重做日志必须同时写人。若二进制日志先写了,而在写人 InnoDB 存储引擎时发生了宕机,那么 slave 可能会接收到 master 传过去的二进制日志并执行,最终导致了主从不一致的情况 MysQL 数据库在 binlog 与 InnoDB 存储引擎之间采用 XA 事务。当事务提交时,InnoDB 存储引攀会先做一个 PREPARE 操作,将事务的 xid 写人,接着进行二进制日志的写人.如果在 InnoDB 存储引擎提交前,MysQL 数据库宕机了,那么 MySQL 数据库在重启后会先检查准备的 UxID 事务是否已经提交,若没有,则在存储引擎层再进行一次提交操作。 不好的事务习惯 在循环中提交 使用自动提交 使用自动回滚 长事务 对于长事务的问题,有时候可以转换为小批量的事务来进行
分布式系统大纲 介绍分布式系统基础。直观地了解关键的分布式系统术语,概述算法领域,并探索生产环境的问题。 转载 分布式系统大纲 - iswade’s blog,补充了落后的部分以及部分翻译和格式修正 分布式系统大纲 - XMind 为什么需要分布式? Lamport, 1987: 分布式系统是:你不知道存在计算机故障会导致您自己的计算机无法使用的系统。 首先,在托管中心运行*nix 的盒子,进程通过 TCP 或者 UDP 通信。 或者 EC2, Rackspace 的盒子等等 也许通过 InfiniBand 通信 以短距离的局域网分割 或者以数千公里互联网分割 许多移动应用也参与分布式系统 通过糟糕的网络进行通信 桌面 Web 浏览器也是如此 这不仅仅是服务器 —— 它也是客户端 更一般地说,分布式系统具有如下特征 由交互的组件构成 很慢 不可靠 无论那些对你意味着什么 还有: 飞机上的冗余 CPU ATM 和销售点终端 太空探测器 支付账单 医生进行推荐 醉酒的朋友试图通过短信制定计划 每次商务会议 节点和网络 我们将分布式系统中的每个部分叫做 节点 也称之为 进程,代理,参与者 节点 延迟特征 在一个节点内部的操作很快 节点之间的操作很慢 快和慢取决于系统的目的 节点是可靠的 作为一个故障单元 你知道什么时候发生问题 状态是连贯的 状态转换以优雅有序的方式进行 典型的模型是某种类型的单线程状态机 节点可以自己组成一个分布式系统 但只要该系统作为一个整体提供“快速,连贯”的操作,我们就可以将其视为单个节点。 进程模型 顺序进程通信模型 Pi-演算 Ambient 演算 Actor 模型 节点故障模型 故障停止 Crash-stop 故障恢复 Crash-recover 故障遗忘 Crash-amnesia 拜占庭 Byzantine 消息流通的网络 节点通过网络交互 人类通过口头语言进行交互 粒子通过磁场交互 计算机通过 IP,TCP,UDP,SCTP 或者其它协议交互 在节点之间发送的离散 消息 消息需要 时间 来传播 这是分布式系统中比较慢的部分 我们称之为 延迟 消息可能会丢失 这是分布式系统中另一个不可靠的部分。 网络几乎不会是同构的 一些连接比其它的连接速度更慢、带宽更小、更容易出错 因果关系图 我们可以将节点和网络交互表示为图表 时间从左到右或者从上到下表示 节点是时间方向上的线(因为它们保持不动) 消息通过倾斜的路径 连接 节点 同步网络 节点以锁步方式执行:节点步骤之间的时间始终为 1 消息延迟有限 有效的完美的全球时钟 易于证明的 你可能没有 半同步网络 像同步一样,但时钟只是近似的,例如在 [c,1] 异步网络 独立执行,无论何时:步进时间在[0,1]中的任何位置 无限制的消息延迟 没有全球时钟 比半同步或同步网络弱 意味着某些算法效率不高 意味着某些算法是 不可能的 参见例如 Attiya&Mavronicolas,“半同步与异步网络的效率“ IP 网络肯定是异步的 但 在实践中 真正的病态事情不会发生 大多数网络在几秒到几周内恢复,而不是“从不” 相反,人类的时间尺度大约为几秒到几周 所以我们不能臆想不存在的问题 当网络出错时 异步网络允许 重复 延迟 丢失 重排 丢失和延迟是很难区分的 拜占庭网络被允许随意乱序 包括重写内容 在真实的网络中几乎不会出现 大多数情况 https://www.pagerduty.com/blog/the-discovery-of-apache-zookeepers-poison-packet/ https://tech.vijayp.ca/linux-kernel-bug-delivers-corrupt-tcp-ip-data-to-mesos-kubernetes-docker-containers-4986f88f7a19 底层协议 TCP TCP 有效 。 用它。 不完美;你可以更快 但你会知道什么时候是这种情况 实际上,TCP 可以在单个 TCP 连接中防止重复和重新排序 但是你可能会打开多个连接 如果没有其他原因,TCP 连接最终会失败 当发生这种情况时,你可以 a)错过消息;b)重试 您可以在 TCP 连接之上构建序列号来保证有序 UDP 与 TCP 相同的规则,但没有流的不变性 很多人都希望 UDP “速度” 不要认为路由器和节点可以并且会随意丢弃数据包 不要认为他们的包会被重复 并重新排序 “但至少它是公正的吗?” 这会导致各种各样的混乱,例如,指标收集 调试很难 TCP 为您提供流量控制并将逻辑消息重新打包成数据包 通过基于 UDP 的 TLS 很难 在 TCP 有限状态机开销过高的情况下,UDP 非常有用 内存压力 复用大量短连接和套接字 在系统目标是尽力传输的场景下尤其有用 语音通话:人们会道歉并重复自己 游戏:口吃和滞后,但后来会追上 更高级别的协议对底层的混乱增加了可靠性 时钟 当一个系统被分割为独立的部分时,我们还期望对事件有某种类型的顺序 时钟可以帮助我们排序:先是这个,然后是那个 墙上时钟 理论上,操作系统的时钟为你提供了系统事件的部分顺序。 注意事项:NTP 可能没有你想象的那么好 注意事项:节点之间的同步性并不好 注意事项:硬件可以漂移 多份关于超微公司 TSC 导致系统时钟运行速度达到 26ms/s 的报告 https://groups.google.com/forum/#!search/Supermicro$20SYS-1029UX-LL1-S16$20system$20clock$20running$20too$20fast/mechanical-sympathy/oG9vLZVYjVA/DU-T9QpBAgAJ 注意事项:跨越几个世纪 NTP 不被关注 http://rachelbythebay.com/w/2017/09/27/2153/ 注意事项:NTP 依然可能将时钟向后跳动(默认:delta > 128 ms)。 https://www.eecis.udel.edu/~mills/ntp/html/clock.html 注意事项:根据定义,POSIX 时间不是单调的 Cloudflare 2017 年。午夜 UTC 的闰秒意味着时间向后流动 当前 Go 并没有提供对 CLOCK_MONOTONIC 的访问。 计算出一个负的持续时间,然后将其送入 rand.int63n(),后者惊慌失措。 导致 DNS 解析失败。1%的 HTTP 请求受到影响,持续数小时 https://blog.cloudflare.com/how-and-why-the-leap-second-affected-cloudflare-dns/ 注意事项:你想测量的时间尺度可能无法实现 注意事项:线程会休眠 注意事项:运行时会休眠 注意事项:操作系统会休眠 注意事项:“硬件"会休眠 注意事项:管理程序可以对你撒谎 在 15 分钟内有 16 秒以上的时间延迟!? https://gist.github.com/sandfox/32e749b5eac861c93f1bbeb8782ae8fd 不要使用 至少操作系统的单调时钟是单调的,对吗? 哦不: https://github.com/rust-lang/rust/blob/eed12bcd0cb281979c4c9ed956b9e41fda2bfaeb/src/libstd/time.rs#L201-L232 Lamport 时钟 Lamport 1977:“时间,时钟和分布式系统中事件的排序” 每个进程一个时钟 每个状态转换,时间单调递增: t'= t + 1 包含在发送的每条消息中 t'= max(t,t_msg + 1) 如果我们有进程的全序,则我们可以对事件实现全序 向量时钟 将 Lamport 时钟推广到所有进程时钟的向量 t_i’= max(t_i, t_msg_i) 对于每个操作,在向量中增加该进程的时钟 提供部分因果顺序 A < B 当且仅当所有 A_i <= B_i,并且至少一个 A_i < B_i 具体而言,给定一对事件,我们可以确定因果关系 B 的因是 A,则意味着 A < B A 的因是 B,则意味着 B < A 否则独立 务实地:过去是共享的; 现在是独立的 只有“现在”,需要保留独立状态 祖先状态可以被丢弃 让我们对过去做垃圾收集 空间:O(进程数) 对于 GC 需要协调 或者牺牲正确性并修剪旧的 vclock 条目 变种 虚线版本矢量 - 用于客户端/服务器系统,对更多的事件排序 区间树时钟 - 用于进程进入和离开 GPS 和原子钟 比 NTP 好 毫秒精度的全球分布的全序 将异步网络提升为半同步网络 解锁更高效的算法 当前只有 Google 有这个能力 Spanner:全球分布的强一致事务 并且他们不共享 比你想要的要贵 每个 GPS 几百个接受者 原子钟用于本地的正确性: 很多钱 需要多个类型的 GPS:供应商可能会出错 https://rachelbythebay.com/w/2015/09/07/noleap/ 混乱的供应商确认框,它将 UTC 校正应用于 GPS 时间 我不知道是谁在做这件事,但我敢打赌数据中心未来将为有限精度时间提供专用的硬件接口 回顾 我们已经介绍了分布式系统的基本原理。节点通过网络交换消息,节点和网络都可能以各种方式失败。TCP 和 UDP 等协议为我们提供了原始信道通信流程,我们可以使用时钟排序时间。现在,我们会讨论分布式系统的一些高级 属性 。 可用性 可用性基本上是尝试成功操作的一部分 完全可用 原始想法:每次操作都成功 一致性:非故障节点上的每个操作都成功 您无法对故障节点做任何事情 粘性可用 对于非故障节点每次操作都成功 约束:客户端总是可以和相同的节点交互 高可用 最好,如果系统没有被分布。 例如 容忍多达 f 次失败,但不能再多 也许有些操作失败了 多数可用 如果操作的节点可以跟集群中的多数通信,则操作可以成功 操作少数节点会失败 量化可用性 我们谈论了很多 运行时间 如果没有人用,系统是运行的吗? 在高峰时期会比宕机更差吗? 可以 “在时间窗口内满足的请求的比例” 做衡量 然后在不同时间在窗口上绘制该比例 时间刻度会影响报告的正常运行时间 Apdex 不是所有的成功都是等价的 将操作分为“好,“乏味”和“糟糕” Apdex = P(OK) + P(meh) / 2 再次,可以每年报告 我们实现了 99.999 apdex 在这一年 并且在更精细的时间尺度上! “用户服务的 Apdex 刚下降到 0.5;页面操作!” 理想情况:您的服务提供的整体很好? 一致性 一致性模型是系统中事件的“安全”历史集 单调读 一旦我读取了一个值,任何后续读取都将返回该状态或以后的值 单调写 如果进行写操作,所做的任何后续写操作都将在第一次写入后的值上写入 读自己 一旦一个值写入后,任何后续的读取都会返回写入的值(或者后续的值) 写后读 一旦一个值被读取,后续的写入只会在读取后的位置进行 串行化 所有的操作(事务)都是原子执行的 以某种顺序 对顺序没有限制 例如可以从过去的历史读取也没有问题 因果一致性 假设操作可以由 DAG 因果关系连接 例如,读取之后的写入与因果关系有关 假设进程不只是丢弃读取数据 该 DAG 中未连接的操作是 并发的 约束:在进程可以执行操作之前,所有之前的操作已经在该节点上执行 并发操作可以自由重新排序 顺序一致性 跟因果一致性类似,限制了可能的顺序 所有操作必须原子执行 所有进程对操作的顺序达成一致 对于一个进程而言,操作总是按顺序出现 但是节点可以落后 线性化 所有操作必须原子执行 每个进程对操作顺序达成一致 每个操作必须在调用和完成时间之间进行 实时性,玩不的约束可以让我们构建强壮的系统 ACID 隔离级别 ANSI SQL 的 ACID 隔离级别是很奇怪的 基本上是已有厂商实现的效果 规范中的定义含糊不清 Adya 1999:Weak Consistency: A Generalized Theory and Optimistic Implementations for Distributed Transactions 每个 ANSI SQL 隔离级别都禁止出现奇怪的现象 读未提交 防止 P0:脏写 w1(x) … w2(x) 在提交之前,无法覆盖其他事务的数据 在事务仍在修改时可以读取数据 可以读取即将回滚的数据 读已提交 防止 P1:脏读 w1(x) … r2(x) 不能读取一个事务未提交的值 可重复读 防止 P1:模糊读 (update) r1(x) … w2(x) 一旦一个事务读取到一个值,事务在提交之前都不能变 串行化 防止 P3:幻读 (select id < 2 from t; insert into t values(1);) 给出一些条件 P r1(x) … w2(y in P) 一旦读取了满足查询条件的集合,这个集合不能变化,直到事务提交 不仅仅是值,是那些会参与到集合中的值 游标稳定 事务有一个游标的集合 一个游标引用一个事务访问的对象 持有读锁,直到游标被释放或者提交 在提交的时,游标被升级为写锁 防止丢失更新 快照隔离 事务总是从已经提交的快照读取数据,快照从事务开始时获取 仅当没有其他具有重叠[start..commit]间隔的已提交事务已写入我们编写的任何对象时,才会发生提交 第一个提交者成功 这实际上有什么关系吗? 真实世界并不是那样并发的 很多公司选择读已提交 但恶意攻击者可以诱导并发 Flexcoin 比特币交换,允许用户通过在账户之间进行混洗来创造资金 2014 年遭遇袭击,365,000 英镑被盗 交易所完全崩溃了 Poloniex 并发提款被错误地隔离,允许用户超支 安全审核未发现负余额 12.3%的外汇资金被盗; 损失在用户之间传播 Warszawski&Bailis 2017:ACIDrain 自动识别 Web 应用程序中的一致性违规 例如 购买一张礼品卡,然后无限次使用 例如 购买笔,在结账时添加笔记本电脑到购物车,获得免费的笔记本电脑 超过 50%的电子商务网站中都存在漏洞 弱 DB 隔离默认值 事务范围的不当使用 未能使用任何事务 Chase Bank’s credit card rewards system 余额之间的并发转账获得了价值 70000 美金的旅行券 现金可以赎回 权衡 理想情况下,我们需要完全可用性和线性化 一致性需要协调 如果允许每个顺序,我们不需要做任何工作! 如果我们想要禁止某些事件顺序,我们必须交换消息 协调(通常)都有代价 更多的一致性更慢 更多的一致性更直观 更多的一致性更少的可用性 可用性和一致性 CAP 理论:线性化或者完全可用 但是等等,这里还有更多! Bailis 2014:Highly Available Transactions: Virtues and Limitations 其他理论不允许完全可用或粘性可用…… 强串行化 串行化 可重复读 游标稳定 快照隔离 你可以粘性可用… 因果关系 PRAM 读你的写 你可以完全可用… 读未提交 读已提交 单调原子视图 写后读 单调读 单调写 收获与收益 Fox & Brewer, 1999: Harvest, Yield, and Scalable Tolerant Systems 收益:请求完成的概率 收获:响应中反映的数据部分 例子 搜索引擎中的节点故障可能导致某些结果丢失 更新可能反映在某些节点上,但不反映在其他节点上 考虑一个被分区的 AP 系统 你可以写入数据一些人不能读取到 流式视频降级以保持低延迟 这不是违反安全不变量的借口 只是帮助您量化您可以 超过 安全不变量的数量 例如,99%时间,你可以读取到前面 90%的写入 强依赖负载,硬件,拓扑,等 可以根据每个请求调整收获与收益 尽可能在 10ms 以内 我需要一切,我理解你可能不会应答 混合系统 有一系列的选择 不同部分设施有不同的需求 选择可以满足你的约束最弱的模型 但是考虑概率界限,可见性延迟可能会令人望而却步 请参阅 Dynamo Quorums 中的概率有界陈旧性 不是所有的数据都是一样的 大数据通常不是很重要 小数据通常很重要 线性化用户的操作,因果关一致性社交信息 回顾 可用性衡量运营成功的频率。 一致性模型是管理可能发生的操作以及何时发生的规则。 更强的一致性模型通常以性能和可用性为代价。 接下来,我们将讨论构建系统的不同方法,从弱到强一致性。 尽可能避免共识 CALM 猜想 Consistency As Logical Monotonicity 如果你可以证明一个系统是逻辑单调的,则不需要协调 什么是“协调”? 什么是“单调”? 单调性,非正式的,是不会回退的 从部分信息推论永远不会因为新信息而无效 没有否定的关系代数和 Datalog 是单调的 Ameloot, et al, 2011: Relational transducers for declarative networking 理论上,不知道网络范围的无协调网络进程只能计算 Datalog 中的单调查询 这读起来不容易 无协调并不意味着没有通信 即使面对任意水平分区,算法也能成功 用非常宽松的实际术语 尝试说明您的问题,以便您只 向系统添加 新的事实 当您根据当前已知的事物计算新事实时,您是否可以确保永远不会撤回事实? 考虑特殊的“密封事实”,将一系列事实标记为完整 这些“仅增长”算法通常更容易实现 可能的权衡:不完整的读取 Bloom 语言 带流分析的无序编程 可以告诉你哪里要协调 Gossip 消息广播系统 对于集群管理、服务发现、健康、传感器、CDN 等很有用 通用的若一致性/高可用 全球广播 发送消息给每个其它节点 O(节点数) 网状网络 流行病模型 与邻居接力 传播时间按最大自由路径的顺序排列 生成森林 替代网络,使用树 跳到连接节点,该节点继续连接到其他连接节点 减少多余的消息 减少时延 Plumtree (Leit ̃ao, Pereira, & Rodrigues, 2007: Epidemic Broadcast Trees) Push-Sum 等 对您收到数据的每个人的输入求和 将其广播到随机的同等节点 最小值,最大值,平均值的扩展 有助于实时指标,速率限制,路由,识别群集热点 CRDTs 无序的数据类型 计数器,集合,映射等等 容忍欺骗,延迟和重新排序 与顺序一致的系统不同,没有“单一的事实来源” 但不像天真的最终一致的系统,永远不会丢失信息 除非你明确让他们丢失信息 我们称这个属性为“合并” 在高可用系统中工作的很好 网页/移动客户端 Dynamo Gossip INRIA: Shapiro, Preguiça, Baquero, Zawirski, 2011: “A comprehensive study of Convergent and Commutative Replicated Data Types” 由数据类型 X 和合并函数 m 组成,是: 关联:m(x1, m(x2, x3)) = m(m(x1, x2), x3) 交换:m(x1, x2) = m(x2, x1) 幂等:m(x1, x1) = m(x1) 容易构建。容易推理。 摆脱各种头痛的问题。 沟通失败了吗? 重试! 它会收敛! 消息是否无序到达? 没关系! 如何同步两个副本? 合并! 缺点 有些算法 需要 顺序,不能用 CRDT 表示 读取可能是任意旧值 更高的空间成本 HATs Bailis, Davidson, Fekete, et al, 2013: “Highly Available Transactions, Virtues and Limitations” 从任何副本确保响应 低延迟(比可序列化协议快 1-3 个数量级!) 读已提交 单调原子视图 非常适用于可交换/单调系统 多个项更新的外键约束 有限的唯一性约束 可以确保给定任意有限延迟的收敛(“最终一致性”) 地理分布系统的良好候选 可能最好与更强大的事务系统协调一致 另见:COPS,Swift,Eiger,Calvin 等 需要共识,怎么办? 共识问题 三种进程类型 提议者:提出一个值 接受者:选择一个值 学习者:读取选择的值 接受者的分类 N 个接受者 F 个允许失败 M 个恶意的接受者 三个变体 非平凡性:只能学习提出的值 安全性:最多可以学习一个值 活跃性:如果一个提议者 p,一个学习者 l 和一组 N-F 各接受者没有错误并且可以相互通信,并且如果 p 提出一个值,则 l 最终将学习一个值。 系统类型与共识问题等价 所以我们这里的任何证明也适用于那些系统 锁服务 排序的日志 复制状态机 FLP 告诉我们不可能在异步网络中实现共识 在正确的时间杀死一个进程,你可以打破 任何 共识算法 是这样,但情况也不是你想的那样坏 实际上,网络 通常足以 达成共识 此外,FLP 假设确定的进程 实际的计算机系统不是确定的 Ben-Or 1983: “Another Advantage of free choice” 非确定性算法 可以 达成共识 Lamport 2002: tight bounds for asynchronous consensus 至少有两个提议者或一个恶意提议者,N > 2F + M 需要大多数 对于至少 2 个提议者或一个恶意提议者,学习提案至少需要 2 个消息延迟。 这是一个实用的可操作范围 在稳定的集群中,可以使用一次往返大多数节点的方式。 群集转换期间需要更多。 Paxos Paxos 是共识算法的黄金标准 Lamport 1989 - The Part Time Parliament 对想象中的希腊民主的描述方式来写作 Lamport 2001 - Paxos Made Simple “用于实现容错分布式系统的 Paxos 算法一直被认为难以理解,也许是因为原始表示对于许多读者来说是希腊语[5]。事实上,它是最简单和最明显的分布式算法之一。 最后一节解释了完整的 Paxos 算法,它是通过直接应用协议来建立分布式系统的状态机方法得到的 - 这种方法应该是众所周知的,因为它是最可能的主题。 经常被引用的关于分布式系统理论的文章[4]。“ Google 2007 - Paxos Made Live 来自谷歌锁服务 Chubby 的笔记 Van Renesse 2011 - Paxos Made Moderately Complex 事实证明你必须优化 伪代码也会有所帮助 一个伪代码页 -> 几千行 C ++ 就独立提案达成共识 通常部署在多数仲裁中,5 或 7 个节点 几个优化 Multi-Paxos Fast Paxos Generalized Paxos 并不总是清楚使用哪种优化,哪些可以安全地组合 每种实现都使用略有不同的风格 Paxos 实际上更像是一系列算法,而不是一个描述良好的单一实体 用于各种生产系统 Chubby Cassandra Riak FoundationDB WANdisco SVN servers 新研究:Paxos 法定人数不需要占多数:可以优化快速阶段 2 法定人数 Howard, Malkhi, and Spiegelman 我们还不确定如何使用它 持久性仍然需要分发 ZAB ZAB 是 Zookeeper 原子广播协议 Junqueira, Reed, and Serafini 2011 - Zab: High-performance broadcast for primary-backup systems 与 Paxos 有区别 提供顺序一致性(可线性化写入,滞后有序读取) 很有用,因为 ZK 客户端通常需要快速本地读取 但是还有一个 SYNC 命令可以保证实时可见性 (SYNC + op)允许线性化读取 仍然是多数派,5 个或者 7 个节点 Humming 共识 用于管理分布式系统重新配置的元数据存储 看起来有点像 CORFU 的复制日志 另见:链式复制 Viewstamped 复制 作为复制协议提供,但也是共识算法 事务处理加视图变更算法 保证多数达成的值可以将来继续生存 我不知道任何生产系统,但我确定它们在那里 与 Paxos 一起以某种方式影响了 Raft Raft Ongaro & Ousterhout 2014 - In Search of an Understandable Consensus Algorithm Lamport 说 Paxos 很容易,但我们仍然有各种问题 如果有一个我们可以理解的一致性算法怎么办? 当我们 想要 是状态机时,Paxos 接近独立决策 改为维护状态机转换的复制 日志 还构建了集群成员转换,这对于真实系统来说是关键 非常新,但是我们有一个核心算法的 Coq 证明 可用于顺序或可线性化的状态机 RethinkDB etcd Consul 事务呢 迭代共识使我们对单一的总操作顺序达成一致 在 可以 独立执行的事务之间进行不必要的阻塞 我们如何提升性能? Distributed Transaction Architectures 单个写操作 所有更新都要经过一个队列,读操作在快照上执行 通常涉及某种持久性的数据结构 串行化到严格串行化 Datomic OK but multiple writers? 多个写操作 一般来说,有几个分片,每个分片运行一个有共识支持的 FSM 某种支持跨分片事务的协议 独立分片 A sort of halfway-step to general-purpose transactions 一种为了通用事务的中间步骤 不允许跨分片事务 运行一堆独立的共识状态机 Can add a single global consensus group for cross-shard transactions 可以为跨分片事务加单个全局共识组 虽然吞吐量有限! VoltDB Percolator 基于线性分片的快照隔离 时间戳 Oracle 分配连续事务时间戳(使用共识) 读时间戳,从领导者读,预写, 提交时间戳,提交, 完成 14 个网络跳跃,可能都是跨数据中心的 TiDB Spanner “外部一致性” (严格串行化?) 通过使用 GPS 和原子钟加速时间戳分配 基本上是基于 paxos 组的两阶段提交 Locks on Paxos leaders 在 Paxos 领导上加锁 选取一个 paxos 组为整个事务提交记录提供服务 固定延迟底线保证时间戳单调性 Yugabyte DB CockroachDB Calvin Order transactions in a log using consensus 使用共识算法在日志中记录订单交易 分片日志为了提高到任意高的吞吐 定期关闭志窗口并将事务应用到分片 应用程序不需要沟通! 严格串性化 1 次数据中心间往返,更多本地通信跳数 可扩展的吞吐量 最低延迟底线 事务必须是纯粹的,预先声明 可以与协议的扩展进行交互 Fauna 回顾 只添加而不是收回的系统需要较少的协调就能构建。我们可以使用 gossip 系统向其他进程广播消息,CRDT 用于合并来自对端的更新,以及用于弱隔离事务的 HAT。可串行化和线性化需要 共识 ,我们可以通过 Paxos,ZAB,VR 或 Raft 获得。现在,我们将讨论分布式系统的不同规模。 时延特征 时延不会为 0 带宽一直在增加,但是正在接近光和电子的物理极限 延迟预算决定了您的系统设计 你可以负担多少次网络调用 对于慢,不同类型的系统有不同的定义 不同的目标 不同的算法 多核系统 多核(尤其是 NUMA)架构是类似的分布式系统 节点不会病态故障,但是消息传递很慢 同步网络通过一个总线提供(例如,Intel QPI) 硬件和微代码中的整个复杂的协议集使内存看起来很清晰 非临时存储指令(例如,MOVNTI) 提供了屏蔽分布特性的抽象 MFENCE/SFENCE/LFENCE 引入针对加载/存储指令的序列化点 延迟特征: ~100 周期 / ~30ns 依赖硬件,cache,指令等等 CMPXCHG 比较和交换(顺序一致修改内存) LOCK 跨核心锁定整个内存子系统 但是抽象的同时也伴随着开销 HLE 可能会有所帮助,但尚未成熟 Blog: Mechanical Sympathy 在可能的地方,避免协调 上下文转换(进程或者线程)代价很高 处理器固定可以真正改善东西 当编写多线程程序时,将你的工作切分成独立的块 尝试将内存屏障与工作单元边界对齐 允许处理器在工作单元内尽可能多地作弊 See Danica Porobic, 2016: High Performance Transaction Processing on Non-Uniform Hardware Topologies 本地网络 你通常会在局域网内部部署复制系统 消息延迟可以低至 100 us 但是,在任何规模较大的网络(EC2)中,最少在 ms 范围 又是,包可能会被延迟五分钟 对这种情况需要做规划 与未命中缓存的磁盘查找相比,网络在 mega 数量级 或者更快,在 EC2 中 EC2 磁盘延迟大约是 20ms 200 ms ? 20000 ms ? 但是 EBS 实际是其它计算机 笑死,如果你认为 EC2 中一切都是真实的 等等,实际的磁盘也会这样做? 到底什么是 IO 调度程序? 但是网络比内存/计算更慢 如果你的目标是吞吐量,工作单位应该花费超过一毫秒 但是分布还有其它原因 资源分片 故障隔离 地理复制 全球范围部署的两个原因 最终用户的延迟 人类可以探测到 ~10ms 的延迟,容忍 ~100ms 的延迟 SF–Denver: 50ms SF–Tokyo: 100ms SF–Madrid: 200ms 灾难恢复 数据中心是很好的,但是不是完美的 飓风是一个问题 整个亚马逊的区域可能会故障 是的,是区域,不是可用区(AZ) 最少一轮的共识 差的情况下可能需要 4 轮 如果你有一个糟糕的 Paxos 实现(例如 Cassandra),也许总是 4 轮 所以如果想在数据中心之间使用 Paxos,准备好应对这种开销 因为最小的延迟比用户可容忍的延迟还要高 cache cache cache 写入队列,异步传递 考虑减少一致性保证以换取低延迟 CRDT 可以保证安全的本地写入 因果一致性和 HAT 可以成为好的方式 强一致性呢? 地理分布的服务具有天然的分裂性 EU 用户在 EU 服务器上;US 用户在 US 的服务器上 使用共识在不同的数据中心中间迁移用户 固定/代理 更新到所在地数据中心 很有希望是最近的数据中心 但也可能不是。我认为 Facebook 仍然将所有写入推送到一个数据中心 当顺序一致性没问题时,将读取在本地缓存 回顾 我们讨论了分布式系统的三个特征尺度:与同步网络耦合的多核处理器,由 LAN 链接的计算机,以及通过互联网或专用光纤链接的数据中心。 CPU 的主要后果是性能问题:了解如何最小化协调。 在 LAN 上,在用户注意到之前,延迟对于许多网络跃点来说足够短。 在地理复制系统中,高延迟最终会推动一致的固定数据中心的解决方案。 常见的分布式系统 外置堆 Redis,memcached,… 数据适合放到内存中,复杂的数据结构 当你的数据结构的内置数据结构很慢/丑陋时很有用 跟缓存一样优秀 或者作为平台之间共享状态的快速便捷的暂存器 不是特别安全 KV 存储 Riak,Couch,Mongo,Cassandra,RethinkDB,HDFS,… 通常 1,2,3 维度的键 O(1)访问时间,又是 O(range)根据 ID 的范围扫描 值之间没有强依赖关系 对象可以是不透明的或结构化的 大数据集 通常是可线性扩展的 通常没有事务 一致性模型范围 - 通常是可选的线性化/顺序操作 SQL 数据库 Postgres, MySQL, Percona XtraDB, Oracle, MSSQL, VoltDB, CockroachDB, … 通过关系代数定义:restrictions of products of records 等 中等大小的数据集 通常包含多记录事务 关系和事务需要协调,减少扩展性 许多系统是主从切换 访问代价依赖索引 典型的强一致性(SI,可串行化,阉割的可串行化) 搜索 Elasticsearch, SolrCloud, … 索引引用的文件 中等到很大的数据集 使用 O(1)文档访问,日志化的搜索 很好的扩展性 典型的弱一致性 协调服务 Zookeeper、ETCD、Consul,… 典型的强(顺序或者线性化)一致性 小数据集 作为无状态服务的协调原语很有用 流系统 Storm、Spark … 通常是定制设计、或者使用工具包来构建 典型的小内存数据容量 低延迟 高吞吐 弱一致性 分布式队列 Kafka, Kestrel, Rabbit, IronMQ, ActiveMQ, HornetQ, Beanstalk, SQS, Celery, … 在多个节点将日志写入磁盘来实现冗余 当您需要立即确认工作,并在以后使用很有用 在无状态服务之间可靠地发送数据 我知道的仅有的一个不会在分区时丢失数据的是 Kafka SQS 也能? 队列不会提高端到端延迟 总是更快地立即完成工作 队列不会提高平均吞吐 消费者的限制 当消费者并发时,队列不提供总事件排序 您的消费者几乎肯定是并发的 同样,队列不保证异步使用者的事件顺序 因为消费者的副作用可能无序发生 所以,不要依赖顺序 队列不能提供最多一次或者最少一次的递送 任何声称不这样做的人都试图向你推销一些东西 恢复一次性递送需要仔细控制副作用 使您的排队操作具有幂等性 队列确实提高了突发吞吐量 分布式队列还提高了容错(如果不丢失数据) 如果不需要容错或者大缓冲,使用 TCP 很多人使用具有六个磁盘写入和十五个网络跃点的队列,其中单个套接字 write()可能已经足够 当您选择了糟糕的运行时时,队列可以让您解脱 回顾 我们使用数据结构存储作为外置堆:它们是分布式系统的管道磁带。 KV 存储和关系数据库通常被部署为记录系统; KV 存储使用独立 key,不太适合关系数据,但与 SQL 存储相比提供了更好的可伸缩性和部分容错性,SQL 存储提供了丰富的查询和强大的事务保证。分布式搜索和协调服务完善了我们构建应用程序的基本工具包。 流系统应用于数据集的连续,低延迟处理,并且往往看起来更像框架而不是数据库。 分布式队列专注于 消息 而不是 转换 。 模式语言 构建分布式系统的一般建议 来之不易的经历 重复其他专家告诉我的内容 在酒桌上 道听途说 过度简化 Cargo-culting 我刚做的东西 你可能已经从别的渠道听说了 不要用分布式 规则 1:如果不是必须,请不要使用分布式 本地系统有可靠的原语。锁、线程、队列、事务。 当你迁移到分布式系统时,你必须从头开始 这个问题是否很小可以在一个节点上完成? 我有一个很大的数据问题 Softlayer 将以每箱 5000 美元每月的价格出租一箱 3TB 的内存 Supermicro 将以约 115,000 美元的价格出售 6TB 的盒子 现代计算机很快 我所知道的生产 JVM HTTP 服务已经可以支持 50K 请求/秒 解析 JSON 时间,记日志到磁盘,推送到 S3 使用 TCP 的协议缓冲区:1000 万/秒 的事件 这项服务能否容忍单个节点的保证? 如果它奔溃了,我们可以再起来吗? 手动干预可以取代分布式算法吗? 使用已经存在的分布式系统 如果必须分布,可以将任务推送到其它软件? 分布式数据库或者日志怎么样? 可以给亚马逊付钱让他们来做吗? 相反,维护费用是多少? 学习使用/操作该分布式系统多少钱? 永不故障 购买非常昂贵的硬件 以受控方式更改软件和硬件 针对临时环境的试运行部署 可以构建非常可靠的网络和机器 以降低成本为代价,购买更昂贵的硬件,寻找人才 硬件/网络故障仍然 发生 ,但足够罕见 => 低优先级 接受故障 分布式系统不仅以 延迟 为特征,但以 经常性,部分故障为特征 我们能接受这种失败并继续我们的生活吗? 我们的 SLA 是什么? 可以手动恢复吗? 可以给人付钱来修复吗? 保险可以承担损害吗? 我们可以打电话给客户并道歉吗? 听起来很傻,但是更加便宜 我们永远也不能阻止 100%的系统故障 有意识地选择恢复高于系统的水平 这就是金融公司和零售商的做法 优先恢复 Assume a failure has just occurred: how will you recover? 假设一个故障刚刚发生了:你怎么恢复? Make this recovery the default path of execution 使这个恢复成为程序的必经之路 编写恢复优先的代码可以让你避免错误处理 默认情况下执行恢复代码意味着您知道它有效 默认情况下恢复意味着您不必担心在真正故障期间的不同语义 如有必要,引入性能优化的快乐路径 但是你失去了其中的一些优势 核对环路 你有一个复杂的、有状态的系统,并想把它迁移到某个地方 可以制定变更计划,并按顺序应用这些变更 但是,如果某些更改中断怎么办? 你怎么恢复? 相反,维护一个目标:表示您希望系统成为什么 接下来,编写一个查看当前状态的函数,并将其与目标进行比较 使用该差异找到使系统更接近目标的步骤 无限重复 抗故障和抗干扰能力强 如果您的管理员手动调整内容怎么办? 如果控制系统的两个实例同时运行怎么办? 在 Borg & Kubernetes 等系统中部署效果显着 也适用于保持系统之间的数据同步 例如,确保每个订单都已发货和计费 备份 备份基本是顺序一致的,但是可能丢失一个操作窗口的数据 当正确完成时 一些备份程序没有快照状态,导致文件系统或者数据库损坏 破坏外键关系,丢失文件等… 允许恢复到几分钟或者几天前 但是除了故障恢复,可以让你按时间返回 从逻辑故障中恢复很有用 分布式数据库正确完成其任务,但是告诉数据库删除了关键数据 冗余 好的,所以失败不是一个选择 希望降低故障的可能性 在几个节点上进行相同的状态和相同的计算 我不是 主-备 的忠实信徒 备机可能有冷缓存,损坏的磁盘,旧版本等 备机在变为主机时往往会失败 尽可能主-主 效率的可预测性 我也不是只有两个副本的忠实的粉丝 节点故障概率太高 对于不重要的数据还可以 我一般都想要 3 份数据副本 对于重要的数据,4 或者 5 分副本 对于 Paxo 或者其它的多数派系统,3,5,7 很常见 常见的策略:Paxos 跨越 5 个节点,3 个或者 4 个在本地数据中心 操作可以在本地节点确认后立即完成;低延迟 适应单节点故障(虽然延迟会出现峰值) 但是在另一个 DC 中仍然有一个顺序一致的备份 所以在最后你失去了整个 DC,一切也都不会丢失 参见 Camille Fournier 关于 ZK 部署的会谈 只要故障不相关,冗余就可以提高可用性 失败并非不相关 来自同一批次的磁盘同时失败 当架顶式交换机断开时,同一机架节点出现故障 UPS 断电时,相同 DC 节点发生故障 查看整个 EC2 AZ 故障 在每个节点上运行相同的错误计算将中断每个节点 昂贵的查询 Riak list-keys Cassandra doomstones 级联故障 Thundering-herd TCP incast 分片 这个问题很大,你忍一下 将问题分解成足够小的部分以适合节点 不小:小零件=>高开销 不太大:需要逐个从节点到节点重新平衡工作单元 大约 10-100 个工作单位/节点是理想的 理想的:工作单元相同的大小 小心热点 小心随时间变化的工作量 提前了解你的界限 压倒一个节点之前单个部分有多大? 我们如何在它节点上出现之前,如何强制执行该限制? 然后在系统重新平衡时一个接一个地下沉所有其他节点 分配分片给节点 在数据库中内置 很好的候选,ZK,Etcd 等等 参见 Boundary’s Ordasity 独立的域 分片是一般模式避免协调的一个特殊场景 保持尽可能独立 提高容错 提高性能 减少复杂性 分片提高伸缩性 通过 CRDT 避免协调 Flake ID:mostly time-ordered identifiers, zero-coordination 参考: http://yellerapp.com/posts/2015-02-09-flake-ids.html 部分可用:用户可以继续使用系统的一些部分 处理队列:更多消费者减少昂贵事件的影响 ID 结构 在我们的世界中,一定需要唯一的标识 在规模上,ID 结构可以决定你的成败 考虑下面的开销 扫描 排序 分片 顺序 ID 需要协调:我们可以避免吗? Flake ID,UUID 对于可分片,你的 ID 可以直接映射到一个分片吗? SaaS 应用:对象 ID 也可以编码客户 ID Twitter:推特 ID 可以编码用户 ID 不变的值 从不更改的数据存储起来很简单 不需要协调 复制和恢复开销很小 在磁盘上很小的重新打包代价 对于 Cassandra,Riak,和 LSM 树 DB 很有用 或者 kafka 的日志 原因很简单:要么是存在,要么不存在 消除各种事务的头痛问题 非常容易缓存 高可用和持久性,可调节的写入延迟 低写入延迟:可以从最近的副本做应答 对于地理分布尤其有价值 需要垃圾回收 但有很好的方法可以做到 可变值 指向变值的指针 指针很小!仅元数据 可以在小 DB 中存储很多的指针 对于协调服务或者关系 DB,是很好的候选 典型地,在系统中没有很多指针 你的全部 DB 可以在用一个指针表示 Datomic 只有 ~5 个标识 对标识的强一致性操作可以由不可变的 HA 存储支持 利用 AP 存储延迟和规模 利用共识系统提供的小数据集的强一致性 写的可用性受到标识存储的限制 但是,如果你只需要顺序一致性,就可以读缓存 如果您只需要序列化就可以更容易 参见 Rich Hickey 关于 Datomic 架构的演讲 参见 Pat Helland 在 2013 年关于 Salesforce 存储的 Ricon West 主题演讲 汇合 与顺序无关的系统更容易构造和推理 因此,可以避免协调 CRDT 是汇合的,这意味着我们可以应用更新而不必等待 不变的值通常是汇合的:一旦存在,就固定了 流媒体系统也可以利用汇合: 当你知道你已经看到了所有内容时,缓冲事件和计算+刷新 发出部分结果,以便您现在可以采取操作,例如用于监视 当完整数据可用时,通过加法或者最大值来合并 银行分类账(大部分)是汇合的:事务顺序不影响余额 但当你需要执行一个最小余额时,就不再是汇合了 结合密封事件(例如当天结束)以恢复汇合 参考 Aiken, Widom, & Hellerstein 1992, “Behavior of Database Production Rules” 背压 交互的服务通常通过队列连接 服务和队列的容量是有限的 当下游服务无法处理负载时,您如何处理它? 消耗资源并爆炸 摆脱负载,开始删除请求 拒绝请求,忽略任务,应答客户端失败 给客户端背压,告诉客户端放慢速度 2-4 允许系统追赶并恢复 背压取决于生产者的选择:组成成分 减载系统的客户端被锁定在减载中 他们没有办法知道系统已被冲洗过 背压系统的客户端可以将背压应用到他们的客户端 或卸载,依赖他们选择 如果你在实现一个异步系统,一定要包括反压力 你的用户会感谢你 基本上:边界资源 请求超时(有界时间) 指数退避(有界使用) 有界队列 有界并发 参考:Zach Tellman, “Everything Will Flow” 域模型服务 问题由相互作用的逻辑部分组成 每个逻辑部分具有不同的代码,性能和存储需求 单体应用程序基本上是 多租户 系统 多租户很难 但通常可以在同一个过程中运行多个逻辑“服务” 将您的系统划分为域的离散部分的逻辑服务模型 OO 方法:每个 名词 是一种服务 用户服务 视频服务 索引服务 功能方法:每个 动词 是一项服务 验证服务 搜索服务 调度/路由服务 我所知道的大多数大型系统都使用混合方式 名词服务是强制执行 数据类型不变量的好方法 动词服务是强制执行 转换不变量的好方法 所以有一个基本的用户服务, 由 Auth 服务使用 你画线的地方……那很棘手 服务带来开销:尽可能少 考虑工作单元 需要独立扩展的独立服务 具有严格依赖性和严格延迟预算的服务 使用补充资源(例如磁盘和 CPU)的协同服务 手动:在渲染节点上运行 memcache 较新的商店:Google Borg,Mesos,Kubernetes 服务应该封装和抽象 尝试建造树而不是网 避免让外人直接操纵服务的数据存储 服务之间的协调需要特殊协议 必须重新发明事务 尽可能去通信 萨加斯 是为单节点世界编写的:我们必须在分布式环境中聪明一点 事务必须是幂等的,或者一起回滚 Typhon / Cerberus 多个数据存储的因果一致性协议 结构遵循社交空间 制作软件是一种基本的社交工具 自然对齐:团队或个人拥有特定服务 乔·弗里曼,“无结构的暴政” 责任和权力应该是明确的 通过角色轮换人员以防止领地 促进信息共享 但不要经常转换 软件的增加成本非常高 随着团队的成长,其使命和思想将正式化 服务和他们的界限也是如此 逐渐积累关于与世界的服务关系的假设 重写以应对不断变化的外部压力 Tushman&Romanelli,1985:组织演变 服务可以是库 最初,您的所有服务应该是库 完全可以依赖多个服务中的用户库 具有明确界限的库很容易被提取出来成为服务 社会结构管理库/服务边界 由于库的用户很少,或者用户协调紧密,因此更改很容易 但是在许多团队中,用户有不同的优先级,必须让他们信服 为什么用户应该做一些工作来升级到新的库版本? 服务 强制 通过定义的 API 弃用生命周期进行协调 您还可以通过代码审查和工具对库强制执行此操作 服务支持集中控制 您的性能改进会立即影响每个人 逐步转换为新的磁盘格式或支持数据库 在一个地方使用该服务 更难用库做这些事情 服务有成本 网络调用的故障复杂性和延迟开销 服务依赖的混乱 很难静态分析代码路径 您认为库 API 版本很难 额外的仪器/部署 服务可以使用良好的客户端库 该库可能是“打开套接字”或 HTTP 客户端 利用 HTTP 标头! 接受标题版本 对缓存和代理的大量支持 Haproxy 是 HTTP 和 TCP 服务的优秀路由器 最终,库可能包含模拟 IO 服务团队负责测试服务是否提供 API 当已知 API 稳定时,每个客户都可以 假设 它有效 无需在测试套件中进行网络呼叫 大幅减少测试运行时和开发环境的复杂性 跨服务协调 服务之间的协调需要特殊的协议 必须重新发明事务 尽可能交换信息 Sagas 是为单节点世界编写的:我们必须在分布式环境中保持聪明 事务须是幂等的,或者可以回滚 Typhon/Cerberus 基于多个数据存储的因果一致性协议 例如:如果露皮塔屏蔽了安吉拉小姐,然后发帖,安吉拉小姐就看不到了 Typhon:单个逻辑实体在不同的数据存储中有数据项表示 假设数据存储是可序列化的或对项目提供原子读取/cas 访问同一实体且 T1 发生的事务- 在 T2 获得之前 因果依赖边 T1 -> T2 Cerberus:涉及单个实体的事务协议 x 写入只能影响 x 的一种表示 跨 x 表示的任意数量的读取 全局元数据:每个实体的版本向量 (GVV) 每个表示元数据: 更新版本向量 (UVV):上次更新时已知的版本 读取版本向量 (RVV):上次读取时已知的版本 当 GVV < UVV/RVV 时检测到冲突 两个阶段: 读 检查实体 x 的 GVV 在每个(要求的)表示上执行 x 的读取 在每个表示中:检查 RVV <= GVV,更新 RVV 写 发送写到代表 检查 UVV <= GVV 和 RVV <= GVV 提交 更新表示和 RVV/UVV 确保 RVV/UVV 不变 通过增加现有 UVV 的第 i 个条目构建的新 UVV 通用事务 加尔文 可序列化(或严格 1SR)事务 确定性 txns 进入分片全局日志 日志确保事务顺序 在副本/分片上的应用不需要进一步的协调 日志窗口的最小延迟下限 CockroachDB 可序列化 假设线性化商店 假设半同步时钟 类似于更易于处理的扳手 迁移 迁移很困难 没有银弹 但有些技巧可以让你的生活更轻松 硬切换 编写新系统并迁移以将旧数据复制到其中 让依赖服务与两者对话——但在实践中,只有一个。 关闭旧系统 复制数据 启动新系统 权衡! 不必担心传输中的数据 简单的迁移脚本:只需读取所有数据并写入新的数据存储 需要与迁移脚本成比例的停机时间 有时可以一次将其范围限定为单个分片/用户/域 增量 编写新系统 B 与原始 A 一起部署 依赖服务与两者交谈 理想:找到所有读者,让每个读者都与 A 和 B 交谈 然后开始写信给 B 这让您不必担心只知道 A 的读者 一致性噩梦;需要追踪所有数据依赖 权衡! 减少/无停机时间 但需要复杂的数据依赖推理 包装服务 从操作上讲,查找和更改 A 的所有用户可能很棘手 所以……不要。引入代理 A 的包装服务 W 引入 B,并进行更改,以便 W 也与 B 交谈 当 A 被淘汰时,移除 W 并直接与 B 交谈。 允许集中度量、错误、行为比较等 最终的原子性 假设您将每次更新写入旧服务 A,然后写入新服务 B 在某些时候,您对 A 的写入会成功,而 B 会失败。然后怎样呢? 可以使用读修复:读取 A 和 B,填写缺失的更新 但这需要可合并性:仅适用于 CRDT 之类的东西 可以使用核对流程 遍历整个数据库,寻找变化,同时适用于两者。 还需要像 CRDT 这样的东西 可以使用 Sage 所有更新都进入持久队列 队列工作者重试直到更新应用于 A 和 B 可能需要订购更新以避免状态分歧 潜在的 全局 列化 注意数据库的一致性模型 隔离 想象一下 Jane 将 w1 写入 A,然后写入 B 同时,Naomi 将 w2 写入 B,然后写入 A 结果:A = w2,B = w1 最终的原子性不足以防止分歧 可以通过选择标准订单来减少问题:总是 A 然后 B(或 B 然后 A) 但是想象一下 Jane 写 A = w1 Naomi 写 A = w2 Naomi 写 B = w2 简写 B = w1 我们又得到了喜忧参半的结果。射击。 可以使用 CRDT 缓解 或者,如果 A 和 B 顺序一致,可以使用 CaS 操作来 确保就订单达成一致 “如果最后一次写入是 w1,则写入 w2” 如果操作影响多个键,则必须在该处应用 CaS 逻辑 增量迁移的有用属性 决定论 避免让数据库生成随机数、自动 ID、时间戳 更容易将更新应用到两个数据存储并获得相同的结果 幂等性 让您自由重试更新 交换性 无需序列化更新 CRDTs:结合性、交换性、幂等性。 不变性:平凡的 CRDT 无状态:无需担心状态! 确保你以同样的方式与外部有状态的东西交谈 换出队列怎么样? 正如我们所提到的,队列系统应该已经被设计为幂等性, 和理想的交换性 如果是这样,这(相对)容易 工作人员从两个队列中消费 翻转生产者只向新队列发送消息 等待旧队列耗尽 解除旧队列 但我们想要订单??? 你将需要重建它 一种选择:单个生产者与队列紧密耦合 将每条消息 m 写入 A 和 B;直到双方都确认才继续前进 这强制 A 和 B 就订单达成一致 消费者可以一视同仁地对待 A 和 B:只从 A 或 B 中消费 再次假设幂等性! 另一种选择:序列号,在客户端重建的顺序 例如为每个值分配序列号 使用 A 的队列偏移量? 使用共识系统? 客户端读取序列号,存储在内部缓冲区中,按顺序应用 r 回顾 如果可能,请尝试使用单个节点而不是分布式系统。 接受一些失败是不可避免的:SLA 和道歉可能具有成本效益。 为了处理灾难性故障,我们使用备份。为了提高可靠性,我们引入了冗余。 为了扩展到大的问题域,我们将问题分成片。不可变值易于存储和缓存,并且可以通过可变身份引用,允许我们在大规模上构建强大一致的系统。 随着软件的发展,不同的组件必须独立扩展,我们将库分解为不同的服务。 服务结构与团队密切配合。 生产问题 不仅仅是设计考虑 证明很重要,但真正的系统需要 IO 您的文化支持分布式系统 理解生产中的分布式系统需要具有多种角色的人员密切合作。 部署 QA 操作 共性问题 开发需要关注产品 运行需要关注实现 好的沟通可以更快地诊断问题 测试一切 类型系统非常适合防止逻辑错误 减少了测试负担 然而,它们在预测或控制运行时性能方面并不是很好 所以,需要可靠的测试套件 理想情况下,您需要一个严格不同类型的测试 几秒钟内运行的快速基于示例的测试 可以在一夜之间运行的更彻底的基于属性的测试 能够在进程中模拟整个集群 控制与模拟网络的并发交织 自动化的硬件故障 测试分布式系统比测试本地系统要困难得多 你从未听说过的大量失败模式 组合状态空间 错误只能表现为小大中间时间空间并发 “这很慢” 杰夫霍奇斯:你会听到的最糟糕的错误是“它很慢” 一直发生,很难本地化 由于系统是分布式的,因此必须分析多个节点 为此建立的分析工具并不多 Sigelman 等,2010:Dapper,一种大规模分布式系统跟踪 基础设施 Zipkin 大工具投资 分析工具擅长发现 CPU 问题 但高延迟通常是 IO 的标志,而不是 CPU 磁盘延迟 网络延迟 GC 延迟 队列延迟 尝试使用应用程序级指标本地化问题 然后深入了解流程和操作系统性能 执行相同工作的节点之间的延迟差异是一个重要信号 1/3 节点慢:可能是节点硬件,重新路由 3/3 节点缓慢:可能是逻辑错误:查看分片大小,工作负载,查询 扇出工作负载放大了尾部延迟 Jeff Dean,2013:The Tail at Scale 考虑推测并行性 测量一切 生产中的缓慢(和彻头彻尾的错误)源于 相互作用 系统 为什么?因为您的全面测试套件可能验证了单一系统大多是正确的 所以我们需要一种方法来了解系统在生产中做了什么 在某种程度上,良好的监控就像持续测试 但不是替代品:这些是不同的领域 两者均可确保您的更改正常 想要高频监控 生产行为可以在 1 毫秒的规模上进行 TCP incast 理想情况下~~ 1ms 分辨率 在极限情况下,Ops 响应时间与观察延迟呈线性关系 约 1 秒的端到端延迟 理想情况下,毫秒级延迟,也可能是 ms 分辨率 通常成本过高;回到 1 或 10 秒 有时你可以忍受 60 秒 对于容量规划,每小时/每日季节性更有用 测量应与应用程序紧密耦合 只衡量重要的事情 回应请求很重要 节点 CPU 并不重要 大多数系统的关键指标 Apdex:在延迟 SLA 内使用成功的应答 延迟分布:0,0.5,0.95,0.99,1 百分位数,而不是平均数 顺便说一句,你不能采取百分位数的均值 总吞吐量 队列统计 其他系统延迟/吞吐量的主观体验 数据库可能认为它很健康,但客户可能会觉得它很慢 组合爆炸 —— 在深入故障时最好使用它 您可能必须自己编写此测量 投资指标库 开箱即用的监控通常无法衡量真正重要的因素:您的 应用程序的行为 但它在追踪问题原因方面非常有用 主机指标,如 CPU,磁盘等 你的应用程序做了一些常见的事情(例如 rails 应用程序)工具,如 New Relic,运作良好 客户端的分片指标 当用户具有不同的工作负载时很有用 可以调整阈值以适应该客户端 主要客户端分一些,“其余”的另一个桶 超级工具:分布式跟踪(Zipkin,Dapper 等) 大量时间投资 神秘机器 从跟踪数据自动推断服务之间的因果关系 识别关键路径 在实施之前对新算法进行性能建模 日志 记录在大规模系统上不太有用 问题可能未本地化到一个节点 当请求触及更多服务时,必须跟踪许多日志文件 投资日志收集基础设施 ELK,Splunk,etc 非结构化信息难以汇总 记录结构化事件 影子流量 负载测试仅在模拟负载与实际负载匹配时才有用 考虑转储生产流量 太棒了:使用 SIGUSR1 终止一个进程,它会转储五分钟的请求加载 太棒了:tcpdump / tcpreplay 用于请求的限制 太棒了:将实时流量映射到您的暂存/ QA 节点 见 Envoy from Lyft 版本 据我所知,协议版本控制是一个广泛存在的问题 包括所有消息的版本标记 包括兼容性逻辑 当客户的请求无法处理时通知客户 并对此进行检测,以便了解哪些系统必须升级 推广 推广通常是您如何解决问题的方法 花时间进行自动化,可靠的部署 放大你做的其他事情 节点平滑循环以防止流量中断 这意味着您将同时运行多个版本的软件 版本控制开的坏头 通知负载均衡器会退出 协调以防止级联故障 仅推出一小部分负载或部分用户 逐渐增加新软件的用户数量 当您看到错误时,可以还原或向前回滚 考虑在生产环境中跟踪流量并比较旧/新版本 确定新代码是否更快和正确的好方法 自动化控制 自动化故障处理很不错 但还不够 “Ironies of Automation”, Bainbridge 1983 https://pdfs.semanticscholar.org/0713/bb9d9b138e4e0a15406006de9b0cddf68e28.pdf 特性标志 我们希望在部署后逐步推出变更集 逐个引入功能,以了解它们对指标的影响 逐步将负载从一个数据库转移到另一个数据库 当推广出错时禁用功能 我们希望在某些服务降级时获得部分可用性 禁用代价昂贵的功能以在故障期间加速恢复 使用高度可用的协调服务来确定要使用的代码路径或多久一次 此服务应具有最小的依赖性 不要使用主 DB 当出现问题时,您可以调整系统的行为 协调服务停止时,失败 安全 ! 混沌工程 打破生产中的东西 迫使工程师适当地 现在 处理故障,而不是稍后对事件的响应 识别关键路径中的意外依赖关系 “当新的统计数据服务出现故障时,需要使用 API。是吗? 确定 那是必要的吗? 需要良好的仪表和警报,因此您可以衡量事件影响 爆炸半径有限 不要每五分钟核对整个数据中心 但是每季度确实地试一次 不要破坏复制组中的许多节点 一次只打破一小部分请求/用户 哦不,队列 每个队列都是一个可怕的地方,可怕的错误 没有节点有无限制的内存。你的队列 必须 有界限 但有多大?没人知道 在生产环境中检测你的队列以找出答案 利特尔定律:平均队列深度 = 平均到达率 * 平均延迟 这是分布式无关的 使用队列以平滑负载的波动 以延迟为代价提高吞吐量 如果您的负载高于容量,则没有队列可以节省您的费用 当队列变满时,卸载负荷或施加背压 测量这个 当发生卸载负荷时,警铃响起 背压作为上游延迟可见 测量队列深度 高深度是您需要添加节点容量的线索 端到端队列延迟应小于波动时间尺度 提高队列大小可能很诱人,但这是一个恶性循环 所有这一切都很难。我没有你的好答案 问 Jeff Hodges 为什么这很难:看他 2013 年的 RICON West 谈话 见 Zach Tellman —— 一切都会流动 回顾 运行分布式系统需要开发人员,QA 和运营工程师之间的合作。静态分析和包括基于示例和属性的测试的测试套件可以帮助确保程序的正确性,但了解生产行为需要全面的测量和警报。成熟的分布式系统团队经常投资工具:流量阴影,版本控制,增量部署和功能标记。最后,队列需要特别小心。 进一步阅读 线上 Mixu has a delightful book on distributed systems with incredible detail. http://book.mixu.net/distsys/ Jeff Hodges has some excellent, production-focused advice. https://www.somethingsimilar.com/2013/01/14/notes-on-distributed-systems-for-young-bloods/ The Fallacies of Distributed Computing is a classic text on mistaken assumptions we make designing distributed systems. http://www.rgoarchitects.com/Files/fallacies.pdf Christopher Meiklejohn has a list of key papers in distributed systems. http://christophermeiklejohn.com/distributed/systems/2013/07/12/readings-in-distributed-systems.html Dan Creswell has a lovely reading list. https://dancres.github.io/Pages/ 丛书 Martin Kleppmann 的 数据密集型应用设计为从业者提供了分布式系统的全面介绍。 Nancy Lynch 的 “Distributed Algorithms” 更理论的角度对该领域进行了全面概述
原始论文 HowtoReadPaper.pdf 摘要 研究人员花了大量的时间阅读研究论文。然而,这种技能很少被传授,导致了浪费了很多精力。本文概述了一种实用而有效的阅读研究论文的三段式方法。我还描述了如何使用这种方法来进行文献调查。 类别和主题描述符。A.1 [介绍性和调查]。 一般术语。文档。 关键词: 论文,阅读,提示。 1 介绍 研究人员必须阅读论文的原因有几个:为会议或课程审阅论文,在他们的领域保持领先,或对一个新领域进行文献调查。一个典型的研究人员每年可能要花数百个小时来阅读论文。 学会有效地阅读论文是一项重要的技能,但却很少有人教。因此,初学的研究生必须通过试验和错误来自学。在这个过程中,学生们浪费了大量的精力,而且经常被逼得灰心丧气。 多年来,我一直使用一种简单的方法来有效地阅读论文。本文介绍了 “三段式 “方法及其在进行文献调查时的应用。 2 三段式方法 关键的想法是,你应该最多分三遍来阅读论文,而不是从头开始,一路耕耘到最后。每一遍都要达到特定的目标,并在前一遍的基础上进行。第一遍让你对这篇论文有一个大致的了解。第二遍让你掌握论文的内容,但不是其细节。第三遍帮助你深入了解该论文。 2.1 第一遍 第一遍是快速扫描,以获得纸张的一个概览。你也可以决定是否需要做更多的扫描。这一遍应该需要大约 5 到 10 分钟,包括以下步骤。 仔细阅读标题、摘要和导言 阅读章节和分节的标题,但忽略其他内容 阅读结论 扫一眼参考资料,在心里勾出你已经读过的资料 在第一遍结束时,你应该能够回答这五个问题 类别。这是什么类型的论文?一份测量论文?对一个现有系统的分析?对一个研究原型的描述? 背景。它与哪些其他论文有关?使用了哪些理论基础来分析问题? 正确性。假设是有效的吗? 贡献。该论文的主要贡献是什么? 清晰度。论文写得好吗? 利用这些信息,你可以选择不再继续阅读。这可能是因为你对该论文不感兴趣,或者你对该领域的了解不足以理解该论文,或者作者做出了无效的假设。对于那些不属于你的研究领域,但有一天可能被证明是相关的论文,第一遍就足够了。 顺便提一下,当你写一篇论文时,你可以期望大多数审稿人(和读者)只看一遍。请注意选择连贯的章节和分节标题,并写出简明而全面的摘要。如果审稿人在看完一遍后不能理解要点,论文很可能会被拒绝;如果读者在五分钟后不能理解论文的重点,论文很可能永远不会被阅读。 2.2 第二遍 在第二遍时,要更仔细地阅读论文,但忽略诸如证明等细节。在阅读过程中,记下关键点,或在空白处做评论,会有帮助。 仔细观察论文中的数字、图表和其他插图。要特别注意图表。轴的标记是否正确?显示的结果是否有误差条,从而使结论具有统计学意义?诸如此类的常见错误会将仓促的、低劣的工作与真正优秀的工作区分开来。 记得标记相关的未读参考文献以便进一步阅读(这是了解论文背景的一个好方法) 第二遍应该花上一个小时。经过这一关,你应该能够掌握论文的内容。 你应该能够向别人总结论文的主旨,并提供支持性证据。这种详细程度适合于你感兴趣的论文,但不属于你的研究专长。 有时,即使在第二遍结束时,你也无法理解一篇论文。这可能是因为该主题对你来说是新的,有不熟悉的术语和缩略语。或者作者可能使用了你不理解的证明或实验技术,因此,论文的大部分内容是无法理解的。论文可能写得很差,没有事实依据的断言和大量的参考文献。也可能只是因为现在是深夜,你很累。你现在可以选择。( a)把论文放在一边,希望你不需要理解这些材料就能在事业上取得成功,(b)以后再回来看这篇论文,也许是在阅读背景材料之后,或者(c) 坚持下去,进入第三关。 2.3 第三遍 要完全理解一篇论文,特别是如果你是审稿人,需要第三遍。第三遍的关键是试图重现该论文:也就是说,在与作者相同的假设下,重新创造该工作。通过比较这种再创造和实际的论文,你不仅可以很容易地发现论文的创新,还可以发现其隐藏的失败和假设。 这一关需要非常注意细节。你应该找出并挑战每一个陈述中的假设。此外,你应该思考你自己会如何陈述一个特定的想法。这种实际与虚拟的比较使你对论文中的证明和表述技巧有了敏锐的洞察力,你很可能将其加入你的工具库。在这个过程中,你还应该记下对未来工作的想法。 这一关对于初学者来说可能需要四五个小时,而对于有经验的读者来说则需要一个小时左右。在这一阶段结束时,你应该能够根据记忆重建论文的整个结构,并能够确定其强点和弱点。特别是,你应该能够指出隐含的假设、缺少对相关工作的引用,以及实验或分析技术的潜在问题。 3 进行文献调查 在做文献调查时,论文阅读能力受到考验。这将要求你阅读数十篇论文,也许是在一个不熟悉的领域。你应该阅读哪些论文?以下是你如何使用三段式方法来帮助。 首先,使用学术搜索引擎,如 Google Scholar 或 CiteSeer 和一些精心选择的关键词,找到该领域的三到五篇最新论文。对每篇论文进行一次检索以了解其工作情况,然后阅读其相关工作部分。你会发现最近工作的缩略图,如果你幸运的话,也许会有一个指向最近的调查报告的指针。如果你能找到这样一份调查报告,你就完成了。阅读调查报告,祝贺自己的好运气。 否则,在第二步,在书目中找到共同的引文和重复的作者名字。这些是该领域的关键论文和研究人员。下载关键论文并将其放在一边。然后进入关键研究人员的网站,看看他们最近在哪里发表过文章。这将帮助你确定该领域的顶级会议,因为最好的研究人员通常在顶级会议上发表文章。 第三步是进入这些顶级会议的网站,查看他们最近的会议记录。快速扫描通常会发现最近的高质量相关工作。这些论文,连同你之前搁置的论文,构成了你调查的第一个版本。对这些论文进行两次扫描。如果他们都引用了你之前没有发现的关键论文,那么就获取并阅读它,必要时进行迭代。 4 经验之谈 在过去的 15 年里,我一直使用这种方法来阅读会议记录,写评论,做背景研究,以及在讨论前快速审查论文。这种有规律的方法可以防止我在得到一个概览之前迷失在细节中。它使我能够估计审查一组论文所需的时间。此外,我可以根据自己的需要和时间的多少来调整论文评估的深度。 5 相关工作 如果你阅读论文是为了做评论,你还应该阅读 Timothy Roscoe 关于 “为系统会议撰写评论 “的论文[2]。如果你打算写一篇技术论文,你应该同时参考 Henning Schulzrinne 的综合网站[3]和 George Whitesides 对这个过程的出色概述[4]。最后,Simon Peyton Jones 有一个涵盖整个研究技能的网站 [1]。 ...
第 1 册 - 寂静的虚空 主程序员如是说: “当你学会了从陷阱帧中抓取错误代码时,你就该离开了。“ 1.1 某种神秘的东西形成,在寂静的虚空中诞生。独自等待,一动不动,它既静止又不断运动。它是所有程序的来源。我不知道它的名字,所以我称之为编程之道。 如果道是美好的,那么操作系统是美好的。如果操作系统是美好的,那么编译器也是美好的。如果编译器是美好的,那么应用程序也是美好的。用户满意,世界和谐。 编程之道远流归于晨风。 1.2 道催生了机器语言。机器语言催生了汇编程序。 汇编器催生了编译器。现在有一万种语言。 每种语言都有其目的,无论多么卑微。每种语言都表达了软件的阴阳。每种语言在道中都有它的位置。 但是,如果可以避免,请不要在 COBOL 中编程。 1.3 起初是道。道催生出了空间和时间。所以空间和时间是编程的阴阳。 不懂道的程序员总是没有时间和空间来编写他们的程序。懂道的程序员总有足够的时间和空间来实现他们的目标。 不然呢? 1.4 聪明的程序员被告知道并遵循它。普通程序员被告知道并搜索它。愚蠢的程序员被告知道并嘲笑它。 若非欢笑,便无道。 曲高和寡。 进亦是退。 大器晚成。 即使是完美的程序仍然存在错误。 第 2 册 - 古代大师 主程序员如是说: “三天没有编程,生活变得毫无意义。” 2.1 古代的程序员是神秘而深刻的。我们无法揣摩他们的想法,所以我们所做的只是描述他们的外表。 谨觉,就像一只过水的狐狸。机警,就像战场上的将军。亲切,就像一位女大师迎接她的客人。简单,就像未雕刻的木块。浑浊,就像黑暗洞穴中的黑色水池。 谁能说出他们内心深处的秘密? 答案只存在于道中。 2.2 图灵大师曾经梦想自己是一台机器。当他醒来时,他惊呼道: “我不知道我是图灵在梦想我是机器,还是机器在梦想我是图灵!” 2.3 一个很大的电脑公司的程序员去参加了一个软件会议,然后回来向他的经理汇报,他说:“什么样的程序员在其他公司工作?他们行为恶劣,对外表漠不关心。他们的头发又长又乱,衣服又皱又旧。他们毁了我们的招待大厅,并在我的演讲中发出粗鲁的声音。” 经理说:“我不应该派你去参加会议。那些程序员生活在物理世界之外。他们认为生活是荒谬的,是偶然的巧合。他们来来去去,不受限制。毫无顾虑,他们只为自己的程序而活。他们为什么要为社会习俗而烦恼? “他们活在道中。” 2.4 一个新手问大师:“这是一个从不设计、记录或测试他的程序的程序员。然而,所有认识他的人都认为他是世界上最好的程序员之一。这是为什么?" 师父回答:“那个程序员已经掌握了道。他已经超越了设计的需要;当系统崩溃时,他不会生气,而是毫无顾虑地接受宇宙。他已经超越了对文件的需求;他不再关心其他人是否看到他的代码。他已经超越了测试的需要;他的每一个程序都是完美的,宁静而优雅,其目的不言而喻。的确,他进入了道的奥秘。” 第 3 册 - 设计 主程序员如是说: “当程序正在测试时,进行设计更改为时已晚。” 3.1 从前有一个人参加了一个电脑贸易展。每天他一进门,就对门口的守卫说: “我是个大盗,以入店行窃闻名。预先警告,因为这个贸易展不会逃脱不受掠夺。” 这番话让守卫大为不安,因为里面有数百万美元的电脑设备,所以他小心翼翼地看着那人。但那人只是在一个摊位间徘徊,自言自语地低声哼唱着。 男人走后,守卫把他拉到一边,搜了遍他的衣服,却一无所获。 交易会的第二天,那个人回来责备看守说:“我昨天带着一大笔赃物逃了出来,但今天会更好。”于是看守更密切地看着他,但无济于事. 展销会的最后一天,守卫再也抑制不住好奇了。“小偷先生,”他说,“我很困惑,我无法平静地生活。请赐教。你偷的是什么?” 男人笑了。“我在窃取想法,”他说。 3.2 曾经有一位编写非结构化程序的大师级程序员。一个想模仿他的新手程序员也开始编写非结构化程序。当新手要求大师评价他的进步时,大师批评他编写非结构化程序,说:“适合大师的东西不适合新手。在超越结构之前,您必须了解道。” 3.3 从前有一个程序员,隶属于吴军的朝廷。军阀问程序员:“哪个更容易设计:会计软件包还是操作系统?” ...
操作系统设计 设计问题的本质 目标 定义抽象概念 进程、文件、线程、信号量 提供基本操作 每一个抽象概念可以通过具体数据结构的形式来实例化。用户可以创建进程、文件、信号量等。基本操作则处理这些数据结构。例如,用户可以读写文件。基本操作以系统调用的形式实现。从用户的观点来看,操作系统的核心是由抽象概念与其上的基本操作所构成的,而基本操作则可通过系统调用加以利用。 确保隔离 用户之间的隔离 虚拟机之间的隔离 进程之间的隔离 故障隔离 资源共享 管理硬件 提供一个框架,统一管理不同型号的硬件资源 难点 已经发展成了极其复杂的程序 必须处理并发和并发导致竞争条件、死锁等问题。 必须处理可能有敌意的用户 需要提供在不同用户之间共享资源的能力 设计人员需要思考未来的操作系统设计方向 需要提供想当程度的通用性 可移植性 需要保持向后兼容 接口设计 指导原则 简单:一个简单的接口更加易于理解并且更加易于以无差错的方式实现 完备:接口必须能够做用户需要做的一切事情,也就是说,它必须是完备的 效率:如果一个功能特性或者系统调用不能够有效地实现,或许就不值得包含它。 范型 用户界面范型:不管选择什么范型,重要的是所有应用程序都要使用它。因此,系统设计者需要提供库和工具包给应用程序开发人员,使他们能够访问产生一致的外观与感觉的程序。没有工具,应用开发者做出来的东西可能完全不同。 执行范型: 算法范型:启动一个程序是为了执行某个功能,而该功能是事先知道的或者是从其参数获知的。 事件驱动范型:在这里程序执行某种初始化(例如通过显示某个屏幕),然后等待操作系统告诉它第一个事件。事件经常是键盘敲击或鼠标移动。这一设计对干高度交互式的程序是十分有益的。 数据范型: 一切皆磁带:在早期的 FORTRAN 批处理系统中,所有一切都是作为连续的磁带来建立模型。用于读入的卡片组被看作输入磁带,用于穿孔的卡片组被看作输出磁带,井且打印机给出被看作输出磁带。磁盘文件也被看作磁带 一切皆文件:UNIX 用”所有一切都是文件”的模型一步发展了这一思想。使用这一范型,所有 I/O 设备都被看作文件,井且可以像普通文件一样打开和操作。 一切皆对象:Windows 试图使所有一切看起来像是一个对象。一旦一个进程获得了一个指向文件、进程、信号量、 邮箱或者其他内核对象的有效句柄,它就可以在其上执行操作。这一范型甚至比 UNIX 更加一般化,井且比 FORTRAN 要一般化得多。 一切皆文档:Web 背后的范型是充满了文档的超空间,每一个文档具有一个 URL。通过键人一个 URL 或者点击被 URL 所支持的条目,你就可以得到该文档。 系统调用接口 操作系统应该提供恰好够用的系统调用,井且每个系统调用都应该尽可能简单 在某些情况下,系统调用可能需要若干变体,但是通常比较好的实现是具有处理一般情况的一个系统调用,而由不同的库过程向程序员隐藏这一事实。 添加更多的代码就是添加更多的程序错误 不要隐藏能力,如果硬件具有极其高效的方法做某事,它就应该以简单的方法展露给程序员 任何面向连接的机制与无连接的机制之间的权衡在于建立连接的机制(例如打开文件)要求的额外开销, 与系统调用接口有关的另一个问题是接口的可见性。 POSIX 强制的系统调用列表很容易找到。Microsoft 从未将 Windows 系统调用列表公开。作为替代,Win API 和其他 API 被公开了,但是这些 API 包含大量的库调用(超过 10000 个),只有很少数是其正的系统调用 实现 系统结构 分层系统:对于一个新系统,选择走这一路线的设计人员应该首先非常仔细地选择各个层次,井且定义每个层次的功能。底层应该总是试图隐藏硬件最糟糕的特异性 外内核:他们的观点基干端到端问题,某件事情必须由用户程序本身去完成,在一个较低的层次做同样的事情就是浪费。端到端问题可以扩展到几乎所有操作系统。它主张不要让操作系统做用户程序本身可以做的任何事情。 在让操作系统做每件事情和让操作系统什么也不做之间的折衷是让操作系统做一点事情。这一设计导致微内核的出现,它让操作系统的大部分作为用户级的服务器进程而运行,在所有设计中这是最模块化和最灵活的。在灵活性上的极限是让每个设备驱动程序也作为一个用户进程而运行,从而完全保护内核和其他驱动程序,但是让设备驱动程序运行在内核会增加模块化程度。 可扩展的系统:将更多的模块放到内核中,但是以一种“受保护的”方式。可扩展的系统自身井不是构造一个操作系统的方法。然而,通过以一个只是包含保护机制的 朵小系统为开端,然后每次将受保护的模块添加到内核中,直到达到期望的功能,对千手边的应用而言一个最小的系统就建立起来了 。 内核线程:无论选择哪种结构模型,允许存在与任何用户进程相隔离的内核线程是很方便的。这些线程可以在后台运行,将脏页面写入磁盘,在内存和磁盘之间交换进程,如此等等。实际上,内核本身可以完全由这样的线程构成,所以当一个用户发出系统调用时,用户的线程并不是在内核模式中运行,而是阻塞井且将控制传给一个内核线程,该内核线程接管控制以完成工作。 机制与策略 另一个有助于体系结构一致性的原理是机制与策略的分离,该原理同时还有助于使系统保持小型和良好的结构。通过将机制放入操作系统而将策略留给用户进程,即使存在改变策略的需要,系统本身也可以保持不变。即使策略模块必须保留在内核中,它也应该尽可能地与机制相隔离,这样策略模块中的变化就不会影响机制模块。 对于线程调度,机制负责从高优先级到低优先级调度线程,策略负责制定线程优先级 对于内存分页,机制负责把页面在内存和磁盘之间换入换出,策略绝地交换是局部的还是全局的,是 LRU 还是 FIFO 正交性 良好的系统设计在于单独的概念可以独立地组合。 命名 操作系统大多数较长使用的数据结构都具有某种名字或标识符,通过这些名字或标识符就可以引用这些数据结构。显而易见的例子有注册名、文件名、设备名、进程 ID 等。 绑定的时机 操作系统使用多种类型的名字来引用对象。有时在名字和对象之间的映射是固定的,但是有时不是。在后一种情况下,何时将名字与对象绑定可能是很重要的。一般而言,早期绑定(early binding) 是简单的,但是不灵活,而晚期绑定(late binding) 则比较复杂,但是通常更加灵活。 操作系统对大多数数据结构通常使用早期绑定,但是偶尔为了灵活性也使用晚期绑定。内存分配是一个相关的案例。在缺乏地址重定位硬件的机器上,早期的多道程序设计系统不得不在某个内存地址装载一个程序,井且对其重定位以便在此处运行。如果它曾经被交换出去,那么它就必须装回到相同的内存地址,否则就会出错。相反,页式虚拟内存是晚期绑定的一种形式。在页面被访问并且实际装入内存之前,与一个给定的虚拟地址相对应的实际物理地址是不知道的。 静态与动态结构 操作系统设计人员经常被迫在静态与动态数据结构之间进行选择。静态结构总是简单易懂,更加容易编程井且用起来更快,动态结构则更加灵活。一个显而易见的例子是进程表。早期的系统只是分配一个固定的数组,存放每个进程结构。如果进程表由 256 项组成,那么在任意时刻只能存在 256 个进程。试图创建第 257 个进程将会失败,因为缺乏表空间。类似的考虑对于打开的文件表以及许多其他内核表格也是有效的。 自顶向下与自底向上的实现 虽然最好是自顶向下地设计系统,但是在理论上系统可以自顶向下或者自底向上地实现。在自顶向下的实现中,实现者以系统调用处理程序为开端,并且探究需要什么机制和数据结构来支持它们。接着写这些过程等,直到触及硬件。 这种方法的问题是,由于只有顶层过程可用,任何事情都难于测试。出于这样的原因,许多开发入员发现实际上自底向上地构建系统更加可行。这一方法需要首先编写隐藏底层硬件的代码。中断处理程序和时钟驱动程序也是早期就需要的。 同步通信与异步通信 对干系统设计者,在正确编程模型的决定上是一个艰难但是很重要的问题。这场论战没有冠军。像 apache 这样的 Web 服务器坚决拥护异步通信,但是 lighnpd 等其他服务器则基干事件驱动模式。两者都非常受欢迎。在我们看来,事件相比于线程更加容易理解和调试。只要没有多核并发的需要,事件很可能是一个好的选择。 实用技术 隐藏硬件:许多硬件是十分麻烦的,所以只好尽早将其隐藏起来 引用:通过一个索引来找到另一个实体 可重用性:在略微不同的上下文中重用相同的代码通常是可行的。 重入:重入指的是代码同时被执行两次或多次的能力。 蛮力法:在一些代码上不值得优化或优化代价很大,就不必优化 首先检查错误:系统调用可能由千各种各样的原因而执行失败:要打开的文件属于他人、因为进程表满而创建进程失败、或者因为目标进程不存在而使信号不能被发送。操作系统在执行调用之前必须无微不至地检查每一个可能的错误。 性能 操作系统为什么运行缓慢 硬件检查、初始化、功能多 什么应该优化 唯一的优化应该是那些显而易见要成为不可避免的问题的事情 性能一旦达到一个合理的水平,榨出最后一点百分比的努力和复杂性或许并不值得 空间-时间的权衡 改进性能的一种一般性的方法是权衡时间与空间。在一个使用很少内存但是速度比较慢的莽法与一个使用很多内存但是速度更快的算法之间进行选择,这在计算机科学中是经常发生的事情。在做出重要的优化时,值得寻找通过使用更多内存加快了速度的算法,或者反过来通过做更多的计算节省了宝贵的内存的算法。 缓存 用于改进性能的一项众所周知的技术是缓存。在任何相同的结果可能需要被获取多次的情况下,缓存都是适用的。一般的方法是首先做完整的工作,然后将结果保存在缓存中。对于后来的获取结果的工作,首先要检查缓存。如果结果在缓存中,就使用它。否则、再做完整的工作。 线索 缓存项总是正确的。缓存搜索可能失败,但是如果找到了一项,那么这一项保证是正确的井且无需再费周折就可以使用。在某些系统中,包含线索(hint)的表是十分便利的。这些线索是关于答案的提示,但是它们并不保证是正确的。调用者必须自行对结果进行验证。 利用局部性 进程和程序的行为井不是随机的,它们在时间上和空间上展现出相当程度的局部性,并且可以以各种方式利用该信息来改进性能。空间局部性的一个常见例子是:进程并不是在其地址空间内部随机地到处跳转的。在一个给定的时间间隔内.它们倾向于使用数目比较少的页面。进程正在有效地使用的页面可以被标记为它的工作集,井且操作系统能够确保当进程被允许运行时,它的工作集在内存中,这样就减少了缺页的次数。 局部性起作用的另一个领域是多处理器系统中的线程调度,在多处理器上一种调度线程的方法是试图在最后一次用过的 CPU 上运行每个线程,期望它的某些内存块依然还在内存的缓存中。 优化常见的情况 区分最常见的情况和最坏可能的情况井且分别处理它们,这通常是一个好主意。针对这两者的代码常常是相当不同的。重要的是要使常见的情况速度快。对于最坏的情况,如果它很少发生,使其正确就足够了。 项目管理 人月神话: 工作不可能完全并行化 为了完全利用数目众多的程序员,工作必须划分成数目众多的模块,这样每个人才能有事情做。由于每个模块可能潜在地与每个其他模块相互作用,需要将模块-模块相互作用的数目看成随着模块数目的平方而培长, 调试工作是高度序列化的 团队结构 任何大型项目都需要组织成层次结构。底层是许多小的团队,每个团队由首席程序员领导。在下一层,必须由一名经理人对一组团队进行协调。经验表明,你所管理的每一个人将花费你 10% 的时间,所以每组 10 个团队需要一个全职经理。这些经理也必须被管理。 经验的作用 拥有丰富经验的设计人员对于一个操作系统项目来说至关重要。Brooks 指出,大多数错误不是在代码中,而是在设计中。程序员正确地做了吩咐他们要做的事情,而吩咐他们要做的事情是错误的。再多测试软件都无法弥补糟糕的设计说明书。 没有银弹 或许在下一个十年将会看到一颗银弹,或许我们将只好满足于逐步的、渐进的改进。 操作系统设计的趋势 虚拟化与云 众核芯片 大型地址空间操作系统 无缝的数据访问 电池供电的计算机 嵌入式系统
安全 环境安全 威胁 很多安全方面的文章将信息系统的安全分解为三个部分:机密性,完整性和可用性。 机密性:指的是将机密的数据置于保密状态。更确切地说,如果数据所有者决定这些数据仅用于特定的人,那么系统就应该保证数据绝对不会发布给未经授权的人。 完整性:指未经授权的用户没有得到许可就擅自改动数据 可用性:没哟人可以扰乱系统使之瘫痪 后来,人们认为三个基本属性不能满足所有场景,因此又增添了一些额外属性,例如真实性、可审 计性、不可否认性、隐私性以及一些其他诸如此类的属性 安全工具对于攻击和防守都是有用的,具有双重用途这一属性 有时候攻击的影响会超越计算计机系统本身,对现实世界也会有影响。 网络战,大型组织之间发动的攻击行为 安全问题的另一个与保密性相关的重要方面是隐私 (privacy). 即保证私人的信息不披滥用 入侵者 从安全性的角度来说,那些喜欢闯入与自已毫不相干区域的人叫作攻击者(anacker)、入侵者(intruder) 或敌人(adversary) 攻击者的范围从技术不是很精湛的黑客爱好者(也称为脚本爱好者),到极其精通技术的黑客。他们可能专门为了罪犯、政府(如警察、军队或者情技部门)或者安全公司工作,或者只是在业余时间开展“黑客”行为的业余爱好者。 操作系统安全 攻击分为被动攻击与主动攻击。被动攻击试图窃取信息,而主动攻击会使计算机程序行为异常。 我们也将加密和程序加固区分开来。加密是将一个消息或者文件进行转码,除非获得密的密钥,否则很难恢复出原信息。程序加固是指在程序中加入保护机制从而使得攻击者很难破坏程序。操作系统在很多地方使用加密:在网络上安全传输据,在硬盘上安全存储文件,将密码存储在密码文件中等。 可信系统 为什么不去做一个安全的操作系统 现代系统虽然不安全但是用户不愿抛弃它们 现在已知的建立安全系统仅有的办法是保持系统的简单性。特性是安全的大敌。 邮件的多媒体文件 HTML 中的可执行脚本 可信计算基 在安全领域中,人们通常讨论可信系统而不是安全系统.这些系统在形式上申明了安全要求并满足了这些安全要求。每一个可信系统的核心是最小的可信计算基,其中包含了实施所有安全规则所必需的硬件和软件。如果这些可信计算基根据系统规约工作,那么,无论发生了什么错误,系统安全性都不会受到威胁。 典型的 TCB 包括了大多数的硬件(除了不影响安全性的 I/O 设备)、操作系统核心中的一部分、大多数或所有掌握超级用户权限的用户程序(如 UNIX 中的 SETUID 根程序)等。必须包含在 TCB 中的操作系统功能有:进程创建、进程切换、内存面管理以及部分的文件和 I/O 管理。在安全设计中、为了减少空间以及纠正错误,TCB 通常完全独立于操作系统的其他部分。 保护机制 保护域 域(domain)是(对象,权限)对的集合。每一对组合指定一个对象和一些可在其上运行的操作子集。这里权限 (right) 是指对某个操作的执行许可。通常域相当于单个用户,告诉用户可以做什么不可以做什么,当然有时域的范围比用户要更广。 任何时间,每个进程会在某个保护域中运行。换句话说,进程可以访问某些对象的集合,每个对象都有一个权限集。进程运行时也可以在不同的域之间切换。 在 UNIX 系统中,使用相同(UID, GID)组合的两个进程访问的是完全一致的对象集合。使用不同(UID, GID)值的进程访问的是不同的文件集合,虽然这些文件有大量的重叠。 而且,每个 UNIX 的进程有两个部分:用户部分和核心部分。当执行系统调用时,进程从用户部分切换到核心部分。核心部分可以访问与用户部分不同的对象集。 访问控制列表 每一个文件关联一个列表,列表里包含了所有可访问对象的域以及这些域如何访问这些对象的方法。这一列表叫作访问控制列表 每个文件对他特定用户和特定组拥有不同的权限 权能字 每一个权能字赋予所有者针对特定对象的权限。一个权能字通常包含了文件(或者更一般的情况下是对象)的标识符和用于不同权限的位图。在类似 UNIX 的系统中,文件标识符可能是 i 节点号。权能字列表本身也是对象,也可以从其他权能字列表处指定,这样就有助于共享子域。ACL 和权能字具有一些彼此互补的特性。在部分操作系统中应用。 安全系统的形式化模型 Harrison 等人 (1976) 在保护矩阵上确定了 6 种最基本的操作,这些操作可用作任何安全系统模型的基准。这些最基本的操作是 create object、delete object、create domain、delete domain、insert right 和 remove right。合并称为保护命令 多级安全 大多数操作系统允许个人用户来决定谁可以读写他们的文件和其他对象。这一策略称为可自由支配的访问控制 部分场景需要强制性的访问控制来确保所阐明的安全策峈被系统强制执行,而不是可自由支配的访问控制。这些强制性的访问控制管理整个信息流,确保不会泄漏那些不应该泄漏的信息。 Bell-LaPadula 模型 简易安全规则:在密级 k 上面运行的进程只能读取同一密级或更低密级的对象。例如,将军可以读取中尉的文档,但中尉却不可以读取将军的文档。 *规则: 在密级 k 上面运行的进程只能写同一密级或更高密级的对象。例如,中尉只能在将军的信箱添加信息告知自己所知的全部,但是将军不能在中尉的信箱里添加信息告知自己所知的全部,因为将军拥有绝密的文档,这些文档不能泄露给中尉。 Biba 模型: 简单完整性规则 :在安全等级 k 上运行的进程只能写同一等级或更低等级的对象(没有往上写)。 完整性*规则:在安全等级 k 上运行的进程只能读同一等级或更高等级的对象(不能向下读)。 隐蔽信道 系统通过精心设计的方式来通信,而不被察觉 隐写术:将信息编码到其他的文件中,用于隐蔽地泄漏信息 密码学原理 加密的目的是将明文一也就是原始信息或文件,通过某种手段变为密文,通过这种手段,只有经过授权的人才知道如何将密文恢复为明文。对无关的人来说,密文是一段无法理解的编码 在算法中使用的加密参数叫作密钥 (key) 。如果 P 代表明文,从代表加密密钥,C 代表密文,E 代表加密算法法(即,函数),那么 \(C=E(P,K_E)\) 。这就是加密的定义 私钥加密技术 对于给定了加密密钥就能够较为容易地找到解密密钥,反之亦然。这样的系统采用了私钥加密技术或对称密钥加密技术 公钥加密技术 这一体系的特点是加密密钥和解密密钥是不同的,并且当给出了一个筛选过的加密密钥后不可能推出对应的解密密钥。在这种特性下,加密密钥可被公开而只有解密密钥处于秘密状态。 一种叫作 RSA 的公钥机制表明:对计算机来说,大数乘法比对大数进行因式分解要容易得多,特别是在使用取换算法进行运算且每个数字都有上百位时 。这种机制广泛应用干密码领域。其他广泛使用的还有离散对数 。公钥机制的主要问题在于运算速度要比对称密钥机制慢数千倍。 当我们使用公钥密码体系时,每个人都拥有一对密钥(公钥和私钥)井把其中的公钥公开.公钥是加密密钥,私钥是解密密钥。通常密钥的运算是自动进行的,有时候用户可以自选密码作为算法的种子.在发送机密信息时,用接收方的公钥将明文加密。由于只有接收方拥有私钥,所以也只有接收方可以解密信息。 单向函数,我们将看到有些函数 f, 其特性是给定 f 和参数 x, 很容易计算出 y =f(x). 但是给定 f(x), 要找到相应的 x 却不可行。这种函数采用了十分复杂的方法把数字打乱 数字签名 MD5,SHA-1,SHA-256,SHA-512 可信平台模块 如何在不安全的系统中安全地保存密钥呢?该方法需要用到一种叫作可信平台模块 (Trusted Platform Modules,TPM) 的芯片。TPM 是一种加密处理器 (cryptoprocessor),使用内部的非易失性存储介质来保存密钥。该芯片用硬件实现数据的加密/解密操作,其效果与在内存中对明文块进行加密或对密文块进行解密的效果相同,TPM 同时还可以验证数字签名。由于其所有的操作都是通过硬件实现,因此速度比用软件实现快许多,也更可能被广泛地应用。 认证 密码登陆认证:弱密码容易被猜到 UNIX 密码,加盐:把密码作为输入,通过一个单项散列函数计算出结果作为密码 一次性密码:提供一个密码列表,每一次登陆都需要使用新的密码 挑战-响应认证:用户事先选择密钥 k, 并手工放置到服务器中。密钥的备份也被安全地存放在用户的计算机里。在登录时,服务器把随机产生的数 r 发送到用户端,由用户端计算出 f(r,k)的值。其中, f 是一个公开已知的函数。然后,服务器也做同样的运算看结果是否一致。 使用物理识别的认证:验证一些用户所拥有的实际物体而不是用户所知道的信息,智能卡,物理密钥 使用生物识别的验证方式:脸部识别,虹膜识别,指纹识别,声音识别 软件漏洞 缓冲区溢出攻击 格式化字符串攻击 悬垂指针 空指针引用攻击 整数溢出攻击 命令注入攻击 检查时间/使用时间攻击 内部攻击 逻辑炸弹 后门陷阱 登录欺诈 恶意软件 特洛伊木马 病毒 共事者病毒 可执行程序病毒 内存驻留病毒 引导扇区病毒 设备驱动病毒 宏病毒 源代码病毒 蠕虫 间谍软件 防御 防火墙 反病毒技术 代码签名 囚禁 基于模型的入侵检测 封装移动代码 关于安全的研究 二进制程序的保护 密码学 权限和访问控制