书籍作者:Francesco Cesarini | ISBN:9787121337475 |
书籍语言:简体中文 | 连载状态:全集 |
电子书格式:pdf,txt,epub,mobi,azw3 | 下载次数:7029 |
创建日期:2021-02-14 | 发布日期:2021-02-14 |
运行环境:PC/Windows/Linux/Mac/IOS/iPhone/iPad/Kindle/Android/安卓/平板 |
《高伸缩性系统:Erlang/OTP大型分布式容错设计》是一本罕见的站在核心设计者而非普通开发者角度介绍 Erlang/OTP系统的优质书籍。两位作者均是深耕分布式计算领域超过20年的专家。《高伸缩性系统:Erlang/OTP大型分布式容错设计》内容兼具深度与广度,不仅带领读者通过一步步实践的方式深入剖析了 Erlang/OTP中各类核心进程的行为模式的设计原理,并且还介绍了特殊进程、自定义行为模式、发行包制作等高级主题。除此之外,还用了大量篇幅向读者介绍了 Erlang/OTP系统中的设计原则、架构分布式系统的方法,以及在此基础上实现容错和规模伸缩所需了解的相关知识。
对于任何一位渴望基于 Erlang/OTP构建出商业级的分布式、高伸缩性、容错型系统的开发者,《高伸缩性系统:Erlang/OTP大型分布式容错设计》都是不容错过的经典之作。
林建入,是一家远程工作的资深程序员,熟悉网络协议与分布式系统设计以及其他。优点是开朗、话多,热爱家庭,志存高远,亲切友好。缺点是一有机会就溜去游戏厅打拳皇,玩了20年竟也不腻,我都拍着胸口佩服我自己,但是最近总被爱妻抓回家有点困扰。如果你不是资深硬核小众精英程序员,我不建议你买这本 Erlang 神书,因为它会让你太快地完成工作导致空闲时间过多不知如何是好,或者让你过度沉迷其源码与实现机制,废寝忘食导致身体机能下降和精神过度亢奋。
如果你需要设计一套规模可伸缩且具有高可用性的容错系统,那么 Erlang/OTP 平台值得你去深入了解,因为其适用领域广阔、技术积累深厚,具备丰富功能的同时又贯彻了高度一致的设计思想。这本实践指南展示了使用 Erlang 编程语言及其 OTP 框架(其中包含许多可复用的库、工具和设计原则等),你将可以基于简单的理念开发出复杂的商业级别的系统,并具备故障免疫能力。
√ 探索 OTP 的基石:Erlang 编程语言、相关工具、可复用的库集合,以及相关抽象理念与设计规则。
√ 深入 OTP 实现可复用性的核心机制:各类进程行为模式内部涉及的 Erlang 进程结构。
√ 理解 OTP 中进程行为模式是如何为客户端-服务器结构、有限状态机模式、事件处理、运行时、代码集成等功能提供支持的。
√ 编写自己的进程行为模式以及特殊进程。
√ 使用 OTP 提供的工具、技术与架构来处理部署、监视和运维等。
译者序
这是一本值得每个 Erlang程序员阅读的好书,因为它深入透彻地讲解了 Erlang程序员进阶过程中最为关键的一环——对 OTP框架的深入理解。
众所周知,与一些火热的流行语言相比, Erlang书籍一直以来数量不多,并且其中大多数以介绍入门级内容为主,意在引起读者的兴趣。尽管这些书籍也各具特色,不少堪称佳作,但对于真正需要从事 Erlang进行开发的程序员来说,仅仅了解基本内容是远远不够的。因此长期以来,要想进一步学习,就需要自己在 Erlang文档中摸索。客观地说, Erlang拥有非常完善的文档,并且其源代码很容易读懂,因此只要有好奇心,你可以深入了解任何你感兴趣的细节。但是,了解细节是一回事,了解细节背后的设计动机又是另一回事。从这个角度来看,文档与源代码虽然将核心机制毫无保留地呈现在我们面前,但仍然有所欠缺。欠缺的是一条线索,一条能够贯穿系统设计中重大决策背后动机的线索。而本书的出版,终于补上了这缺失的一环。
我想强调,本书对于 OTP的讲解,并非局限于讲解其“用法”——如果真是如此,那么阅读文档便足够——而是更注重其“原理”。此原理即是指其工作流程,更是指其设计动机。正因为如此,本书的内容才显得独特而可贵。具体来说,本书的前半部分,在作者的带领下,读者可跟随其指导重新实现 OTP中最核心的构成要件。这一过程并非平庸的代码罗列然后逐句解读,而是首先从背景出发,遵循一定的设计理念,先带领读者设计出一个小型的模型,其虽然看似简陋,但已能够实现基本功能,然后进一步指出其不足,并将其改进为符合 OTP理念的实现。与 OTP内部真正的实现相比,显然读者的实现依然是简陋的,但是却深刻地反映了真实系统运作时的核心原理。倘若读者有心,能够认真跟着作者的指点完成整个过程,那么不仅能够轻松理解这些 OTP框架中核心构件的使用方法,知其然;并且能够明白其背后的工作原理,知其所以然。
完整介绍完 OTP后,本书的篇幅已过大半。我想,本书内容即使自此戛然而止,也不愧列入经典之列了。但两位作者 Francesco和 Steve却选择更进一步,带领读者探索更深的主题。于是在第 11章,我们不仅可学习到 OTP系统的核心设计原则,并且还跟着作者一步步手工完成了 OTP发行包(Release)的制作。这一章我特别喜欢。因为我和很多读者一样,能使用 rebar3之类的工具自动完成发行包制作,但对其中过程却不甚清楚。作者为什么要花费很长的篇幅介绍如何手工制作发行包呢?因为通过这个过程,读者能够深入理解 Erlang系统的构成,及其启动过程。如果不了解这些内容,就无法理解和应对一些比较棘手的启动阶段的问题,同时也丧失了利用这个过程完成一些定制化能力的机会。并且,理解这些内容,对于那些想进一步探索 Erlang核心机制的硬核程序员来说,也极有帮助。这一章我个人认为是本书中特别重要的一章,并且实践性极强,建议读者跟随作者的指引一步步完成实验。
而第 12章,更进一步,向读者介绍了如何进行系统升级。我相信很多人都听过 Erlang支持热更新,但是对它的认识仅限于模块级的热更新。你想知道如何升级 application,甚至升级整个 Erlang虚拟机吗?事实上一点也不难,作者将告诉你最佳做法,你不用担心升级时 application间的依赖、数据库模式变化等诸多问题。一切答案都在这里。剩下的第 13章到第 16章同样不容错过。分别介绍了分布式系统架构方案、容错性设计、规模伸缩方法,以及监视与抢救性支持等内容。
每一章都很精彩,我很想向读者一一介绍,但我想更好的做法是让读者自己去领略吧。在这篇译者序里我就不“剧透”了。
交流与反馈
我在 GitHub上建立了一个项目,如果你希望与我或者其他读者交流,这是一个不错的方式。其中还整理了一些与本书相关的资料(代码、勘误和相关文档等)链接,方便查阅。这个项目会长期维护,欢迎随时来访,共同交流。
https://github.com/Jianru-Lin/scalabilitywitherlangotp
回顾与感谢
作为一篇译者序来说,感谢部分一般的做法是优雅而礼貌的寥寥数语带过即可。少则三两句,多则一两段足矣。先是感谢编辑,然后是感谢家人和朋友。这样做或许没有问题,但我仔细想想倘若多年后自己翻起本书,却看不到自己当时真正想说的话,会很遗憾吧。所以还是想把自己真实的想法写下来。
两年前张春雨老师找到我,问我有没有兴趣翻译一本 Erlang的书。我当然开心的答应了,因为我很喜欢 Erlang。但由于个人业余时间有限,最终花了两年时间才完成。这期间并非一帆风顺,有很多波折,主要是我个人工作环境发生变化,业余时间有时候很紧张,而且身体有一段时间也有一些不适,综合各种因素导致翻译的进度时好时坏。拖稿也从偶尔有之,到家常便饭了。编辑从时不时查阅进度,到不断的催稿。
刚开始,也是客气的催稿。我则客气的回应。但是次数多了,有时候确实给编辑着急得不行:“这都周三了,说好周末交的呢?”,“最迟这周五,不能再拖了!”,我也压力山大,只能赶紧抽时间处理,有时候一再拖延,真的是很不好意思回编辑的微信了。于是有“林老师,干什么去了?弄完了吗”,“稿子什么时候能给,急死了!”。刚开始是张春雨老师催,后来和刘舫老师两位一起交替催,催得厉害了,有一天,刘舫编辑自己笑着打趣说:“天天追杀啊”。大家都笑了,我也笑了。
我记得很多次,我白天工作抽不出时间,只能深夜处理。于是把稿子发给刘舫编辑的时候,已经是凌晨四五点了。可是令我惊奇的是,经常很快就收到了回复。聊了两句后,我准备休息,心里嘀咕着,刘舫老师现在还醒着?深夜交稿尚且如此,周末和节假日更别说了。想想自己尚且有周末休息,可是编辑却一直处于工作状态,心里觉得自己的辛苦其实和他们还是比不了。所以对于催稿这件事,也不能说是编辑施压译者,其实编辑同样不容易。
说起来还闹过一个笑话,因为我偶尔会去北京,于是也会想见见张春雨和刘舫老师。于是有一次就和刘舫老师提起见面吃饭的事,当时文字交谈过程中感觉刘舫老师似乎不太方便。后来才知道原来刘舫老师是女编辑!我和人家沟通了一段时间连对方性别都没搞清楚,真是十分尴尬。但是这也不能完全怪我,因为每次我发的稿件刘舫老师总是细细阅读后给出很多专业的修改意见,让我觉得很厉害,潜移默化习惯性以为是男同胞。怪只能怪自己有错误的刻板印象。而且后来发现很多技术书籍背后的编辑都是女性,心里就更惊讶和钦佩了。
其实张春雨老师联系我之前,我早就知道他了,因为我读过的不少优秀引进书籍的策划编辑都是他,我书架上的《游戏引擎架构》和《 Clojure编程》就是,(后者的责任编辑还是刘舫老师),这些书都属高水准作品。而其中每一本的译者序里都有“感谢张春雨老师”的话语,这就是为什么我对他有印象的原因。提到这一点,张春雨老师幽默的开玩笑说“呵呵,他们没有感谢我,是我事后加进去的”。把我和刘舫老师都逗乐了。
好吧,不管怎么说本书终于译完了。我写了这么长的一段,其实只是希望下次您看到书籍时,不仅能注意到作者和译者的名字,也应当留意编辑们的名字。作为译者,我可以留下一些文字。但作为编辑,就很少让读者意识到他们幸苦的付出了。所以,感谢张春雨老师和刘舫老师,你们幸苦了。
另外,要特别感谢我的妻子,是你一直在催稿,催得比编辑还紧(二位编辑万万没想到吧?其实你们有个不花钱的手下天天跟着我,我逃得过你们却逃不过她),所以现在终
于完成了,而不是再多三个月。当然,当我完成这一切,也是你比我还要开心。说起来我还欠你一条比目鱼,咱们说好了完成后就买一条尝尝的。你还说,很期待书印刷出来后,捧在手里的感觉,你要看看我在里面是怎么感谢你的。仔细想想这些年我做到的每一件事情背后其实都离不开你的支持,但我觉得这还不够,我们还要一起再翻译更多书,一起完成更多想做的事,一起去更多想去的地方。我写下这些文字的时候你就躺在我身后,不亦乐乎的玩着手机。我没有让你看到我写的内容,不过我猜你看到这段的时候一定会高兴的。因为我也是。
最后,感谢我的父母和家人,尤其是保慈林女士、保慈芬女士、张绍光先生,是你们令我能接受好的教育,并教会我勇敢追求渴望的人生。而我的丈母娘在我工作繁忙期间,生活上给我很多的关照,减轻了我的很多负担,为我节约了很多时间,对交稿功不可没,我心里也很感激。
说得有些冗长,深感抱歉,但这些都是我的真实想法。因此我想即使再过很多年,读起这段文字还是会很快乐。我很满足。
林建入 2018年 5月 6日深夜于海口
序言
本书为你提供的,是一名自 1996 年从 R1 版便开始接触 Erlang的爱好者,钻研十多年后终于成长为一位分布式系统专家,在这一过程中所获得的宝贵知识和经验,让你明白为何 Erlang/OTP能够使你更容易地专注应对系统开发中那些真正的挑战。
通过描述如何构建 OTP行为模式(behavior)以及为什么需要 OTP行为模式,我们向你展示了如何使用它们构建独立节点。这就是最初我们向 O’Reilly提供的草案,内容仅限于此。但是在编写本书时,我们决定将内容更进一步,记录下我们的实践经验、设计决策过程和架构分布式系统时常见的一些问题。通过我们所做的这一系列设计选择和折中,这些模式为我们提供了 Erlang/OTP众所周知的可伸缩性、可靠性和可用性。与流行的观点相反,这一切并非魔法般地开箱即得的,但获得它们确实比其他任何——非语义级别模拟 Erlang 的,或者不是运行在 BEAM 虚拟机上的——编程语言要容易得多。
Francesco:为什么写这本书
有人曾告诉我,写书有点像生孩子。一旦你写完一本书,拿到纸质图书的那一刻,脑子里有的只是兴奋和激动,而曾经付出的艰辛将统统被忘掉,只渴望着赶快开始写另一本。自从 2009 年 6 月首次拿到纸质书以来,我一直有编写 Erlang Programming(O’Reilly)续作的打算。在我开始这个项目时,我还没有自己的孩子,但最终这一项目花了如此长的时间以至于我的第二个孩子都已经快出生了。美好的事物值得我们等待,谁说不是呢?
与第一本书一样,本书是围绕我在 Erlang Solutions公司所做的 OTP培训材料中的示例编写的,我将使用这些例子时我的讲解和教学过程转化为文字。每当完成一章后,我都会回顾并确保那些学生较难理解的部分我的讲解是清晰的。最好的学生通常会问的那些问题最终被放到了补充材料部分,而篇幅较长的章则被分解成一些较短小的章。原本一切都很顺利,直到我们到达第 11章和第 12 章,因为发行包制作和软件升级没有一种统一的方法,而是存在许多种工具。有些工具需要集成到客户的构建和发布过程中,而其他一些则是开箱即用的。还有一些已无法使用。对于任何想要理解系统的发行包制作和软件升级包括其幕后工作原理的人,我们希望这两章成为他们的终极指南。此外,如果你必须对现有工具进行故障排除或编写自己的工具,其中还介绍了你所需要了解的内容。
但真正的麻烦从第 13 章才开始。由于没有任何示例和培训材料,我发现自己必须将头脑中的内容形式化,将构建 Erlang/OTP系统时所采取的方法落实为文档,并尝试将其与分布式计算理论结合起来。最终第 13 章变成了 4个章节,并且花了写出本书前 10章那么长的时间才完成。对于那些购买了早期访问(early access)的读者,我希望没有辜负你们的等待。对于那些明智地等我们写完才购买的朋友,希望你们喜欢这些内容!
Steve:为什么写这本书
我第一次发现 Erlang/OTP是在 2006 年,当时我正在研究如何能更快、更便宜、更好地开发企业集成软件。无论我从哪个方面考察,Erlang/OTP都明显优于我和我的同事当时一直使用的 C++ 和 Java 语言。2007 年,我加入了一家新公司,开始在商业产品中使用 Erlang/OTP,事实证明,我之前考察所发现的一切优势都是真的。我教一些同事使用了这种语言,不久后,我们开发的软件比其他大多数人开发的都更强大、更可靠、更容易演进,并且能更快地投入生产环境,甚至与人员规模大得多的 C++ 团队相比优势依然明显。直到今天,我仍然完全信赖 Erlang/OTP在实践中表现出的令人印象深刻的高效性。
多年来我发表了不少技术资料,而我的目标读者一直都是像我这样的其他从业者。这本书也不例外。在前面的 12 章中,我们提供了许多深入的实践性细节,使开发人员能够充分理解 OTP的基本设计原则。在这些细节中包含了大量极具实用价值的知识——各类模块、函数和方案等——它们将为你的日常设计、开发和调试工作节省大量时间和精力。在最后的 4章中,我们将转变方向,聚焦于宏观的层面,探讨可伸缩分布式应用在开发、部署和运行时涉及的各种权衡取舍。由于与分布式系统、容错和 DevOps相关的知识、方案和需考虑的权衡数量着实庞大,所以要想将这些章节简洁地写出来难度可想而知,但我相信本书为此达到了适当的平衡,既为读者提供了大量好的建议,同时又能避免读者迷失其中。
我希望这本书能帮助读者提高所开发的软件和系统的质量及效用。
本书的读者对象
本书的目标受众主要针对 Erlang 和 Elixir 开发人员和架构师——已经完整阅读过一本以上入门书籍,并准备将知识提升到一个新的水平的人。这不是一本带你入门的初等水平的书籍,而是一本涵盖了许多其他书籍未涉及的高级内容、让你远超同行水平的书。其中第 3 章至第 12 章存在依赖关系,应该顺序阅读,第 13 章至第 16章也是如此。如果你不需要回顾 Erlang 初级知识,可以跳过第 2 章。
如何阅读本书
本书中的内容兼容 Erlang 18.2。书中涉及的绝大部分功能同样适用于先前版本的 Erlang;针对不适用的功能在书中均有指出。对于未来版本的不兼容性虽然在写书时尚不可知,但将会在本书的勘误页面上进行详细说明,并修复本书 GitHub仓库中的对应代码。我们鼓励你从我们的 GitHub仓库下载本书的示例代码,并亲自运行以更好地理解相关知识。
致谢
撰写本书是一个漫长的旅程。在进行这项工作时,我们得到了许多优秀人士的帮助。编辑 Andy Oram给予了我们无数的想法和建议,耐心地指导我们,给予我们反馈,并且不断鼓励我们。 Andy,谢谢你,没有你,我们无法完成此书! Simon Thompson—— Erlang Programming一书的合著者帮助了本书的构思和起草,并为第 2章奠定了基础。非常感谢 Robert Virding贡献的一些例子。我们还得到了很多读者、审稿人、贡献者的帮助,为我们提供了许多反馈,有了这些,我们才能让每一章的内容充实。在此我们列出他们的姓名,并忐忑地希望没有遗漏任何一位: Richard Ben Aleya、Roberto Aloi、Jesper Louis Andersen、Bob Balance、Eva Bihari、Martin Bodocky、Natalia Chechina、Jean-Fran.ois Cloutier、Richard Croucher、ViktóriaF.rd.s、Heinz Gies、 JoacimHalén、Fred Hebert、Csaba Hoch、Torben Hoffmann、Bob Ippolito、Aman Kohli、 Jan Willem Luiten、Jay Nelson、Robby Raschke、Andrzej .liwa、David Smith、Sam Tavakoli、Premanand Thangamani、Jan Uhlig、John Warwick、David Welton、Ulf Wiger和 Alexander Yong。如果我们在名单中遗漏了您,我们真诚地向您道歉!给我们发一封电子邮件,我们会将您添加进去。对 Erlang Solutions 职员中那些阅读了本书早期撰写过程中的手稿,以及其他早期为本书提供勘误的人,我们必须大声地向你们表示感谢。此外还要特别感谢所有通过社交媒体渠道鼓励过我们的人,特别是其他作者。你知道我说的就是你们!最后但同样重要的一点是,感谢 O’Reilly的制作、营销和会议团队,不断提醒我们“只要尚未付印工作就不算结束”。我们非常感谢你们的支持!
第 1章 概述 1
定义问题 2
OTP 4
Erlang 6
工具和库 7
系统设计原则 9
Erlang 节点 10
分布式、基础设施、多核 11
总结 12
通过本书你将学到什么 13
第 2章 Erlang.简介. 18
递归与模式匹配 18
受函数式的影响 22
玩转匿名函数 22
列表推导:生成与测试 23
进程与消息传递 25
不怕出错 30
用于监督的链接与监视器 31
链接 31
监视器 33
记录 34
映射组 37
宏 38
模块升级 39
ETS:Erlang 元素存储 41
分布式 Erlang 44
命名与通信 45
节点间的连接与可见性 45
总结 47
接下来是什么 47
第 3章 行为模式. 49
进程的骨架 49
设计模式 52
回调模块 53
抽取出通用的行为模式 56
启动 server 57
client函数 60
server循环 62
server内部函数 64
通用服务器 65
消息传递:冰山之下 68
总结 71
接下来是什么 72
第 4章 通用型服务器.gen_server. 73
gen_server 73
behavior指令 74
启动一个 server 75
消息传递 77
同步式消息传递 78
异步式消息传递 79
其他消息 81
未处理的消息 82
同步客户端 83
终止 84
调用超时 86
死锁 89
通用型 server的超时问题 90
使 behavior休眠 92
全局化 92
链接 behavior 94
总结 94
接下来是什么 95
第 5章 深入控制 OTP行为模式 96
sys模块 96
追踪与记录 96
系统消息 98
你自己的追踪函数 98
统计信息和当前状态 99
sys 模块总结 102
分裂时的可选项 103
内存管理与垃圾回收 104
分裂时应该避免使用的可选项 108
超时 109
总结 109
接下来是什么 109
第 6章 有限状态机. 110
Erlang 风格的有限状态机 111
Coffee FSM 112
硬件桩 114
Erlang 版咖啡机 114
gen_fsm 118
一个基于行为模式的例子 119
启动 FSM 119
发送事件 123
终止 132
总结 133
亲力亲为 134
电话控制器 134
让我们测试一下 136
接下来是什么 138
第 7章 事件处理器. 139
事件 139
通用事件管理器/处理器 141
启动/停止事件管理器 141
添加事件处理器 142
删除事件处理器 144
发送同步的或异步的事件 145
获取数据 148
对错误以及无效返回值的处理 150
交换事件处理器 152
融会贯通 154
SASL警报处理器 157
总结 159
接下来是什么 159
第 8章 监督者 160
监督树 161
OTP监督者 165
监督者行为模式 166
启动监督者 166
监督者规格 169
动态子进程 176
非 OTP兼容进程 184
可伸缩性和短期进程 186
确定性同步启动 187
测试你的监督策略 188
与传统方法相比又如何 190
总结 190
接下来是什么 191
第.9.章 OTP.application 192
OTP application是如何运行的 193
OTP application的结构 194
回调模块 198
启动和停止 application 198
application资源文件 202
基站控制器的 application文件 204
启动 application 205
环境变量 208
application的类型与终止策略 210
分布式 application 211
分阶段启动 215
内含型 application 217
内含型 application 的分阶段启动 217
将监督者与 application组合到一起 219
SASL应用 220
进度报告 224
错误报告 225
崩溃报告 226
监督者报告 227
总结 228
接下来是什么 229
第.10.章 基于特殊进程打造自己的 behavior 230
特殊进程 230
互斥体 231
启动特殊进程 232
互斥体的状态 235
处理退出 236
系统消息 237
跟踪与日志事件 238
合在一起 239
动态模块和休眠 243
属于你自己的 behavior 244
创建 behavior 时的要求 245
一个处理 TCP流的例子 245
总结 249
接下来是什么 250
第 11章 系统原则与发行包制作. 251
系统原则 252
发行包目录结构 253
发行包资源文件 257
创建发行包 260
创建 boot 文件 262
打包发行包 271
启动脚本以及目标上的配置 275
参数和标志 277
init模块 289
rebar3 290
生成一个 rebar3 发行包项目 292
使用 rebar3 创建发行包 295
使用 rebar3 处理制作发行包时的项目依赖问题 298
总结 300
接下来是什么 304
第 12章 发行包升级 305
软件升级 305
第一个版本的咖啡机 FSM 308
添加一个新状态 311
为发行包创建升级 314
负责升级的代码 318
应用程序升级文件 322
高级指令 325
发行包升级文件 328
低级指令 330
安装升级 332
发行包处理器 334
升级环境变量 338
升级特殊进程 338
在分布式环境下升级 339
升级模拟器和核心 application 340
使用 Rebar3进行升级 341
总结 344
接下来是什么 346
第 13章 分布式架构 347
节点类型与家族 348
联网 351
分布式 Erlang 353
套接字与 SSL 359
面向服务和微服务的架构 361
点对点 362
接口 364
总结 366
接下来是什么 367
第.14.章 永不停止的系统 368
可用性 368
容错 369
弹性 370
可靠性 371
数据共享 375
一致性和可用性之间的权衡 383
总结 384
接下来是什么 385
第.15.章 水平规模伸缩 386
水平规模伸缩与垂直规模伸缩 386
容量规划 390
容量测试 392
平衡你的系统 394
找寻瓶颈 396
系统蓝图 398
负载调节与背压 399
总结 401
接下来是什么 403
第 16章 监视与抢救性支持 404
监视 405
日志 406
指标 411
警报 414
抢救性支持 416
总结 418
接下来是什么 420
索引 421
翻译略有瑕疵。书很棒!
2018-07-21
翻译水平高,绝对是目前讲解OTP最好的书
2018-07-15