<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ 面试 - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ 面试 - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 30 May 2026 19:37:19 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/job-interview/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 大科技公司（FAANG）的编码面试准备，以及我如何成为一名谷歌工程师 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：Coding Interview Prep for Big Tech (FAANG) – And How I Became A Google Engineer [https://www.freecodecamp.org/news/coding-interview-prep-for-big-tech/] ，作者：Zubin Pratap [https://www.freecodecamp.org/news/author/zubin/] 当我成功从一名律师转行成为 Google 软件工程师的时候，我发表了帮助我成功转型的 10 个想法 [https://www.freecodecamp.org/news/from-lawyer-to-google-engineer/] 这篇文章，之后我收到许多提问：  * 如何自学新技能  * 我怎么知道 37 岁学代码“不晚”  * 我是怎么准备大科技公司的面试  * 我是如何分析和最小化转行的风险  * 我是如何发现软件工程师这个职业适合我  * 我专注于哪个语言  * 成为  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/coding-interview-prep-for-big-tech/</link>
                <guid isPermaLink="false">62e3e4b98d13aa0845c63215</guid>
                
                    <category>
                        <![CDATA[ 面试 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ PapayaHUANG ]]>
                </dc:creator>
                <pubDate>Fri, 29 Jul 2022 07:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/07/Google-Mel.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/coding-interview-prep-for-big-tech/">Coding Interview Prep for Big Tech (FAANG) – And How I Became A Google Engineer</a>，作者：<a href="https://www.freecodecamp.org/news/author/zubin/">Zubin Pratap</a></p><!--kg-card-begin: markdown--><p>当我成功从一名律师转行成为 Google 软件工程师的时候，我发表了帮助我成功转型的 <a href="https://www.freecodecamp.org/news/from-lawyer-to-google-engineer/">10 个想法</a>这篇文章，之后我收到许多提问：</p>
<ul>
<li>如何自学新技能</li>
<li>我怎么知道 37 岁学代码“不晚”</li>
<li>我是怎么准备大科技公司的面试</li>
<li>我是如何分析和最小化转行的风险</li>
<li>我是如何发现软件工程师这个职业适合我</li>
<li>我专注于哪个语言</li>
<li>成为 FANG/FAMGA（Facebook/Amazon/Netflix/Google/Microsoft/Apple）的软件工程师是否适合每一个人（提示：这些公司听上去很诱人，但事实证明并不适合一些人）</li>
</ul>
<p>注意：我认为 “FAANG/FAMGA” 不足以概括所有好公司，所以我倾向于使用“大科技公司”，来指代包括这四五个令人着迷的公司在内的所有颇具盛名的公司。</p>
<h2 id="">我为什么写这篇文章</h2>
<p>虽然时下流行“推特长度的建议”，但我认为上述的每一个问题都值得用一整篇文章来回答。有意义的技能可不是几百个字就能讲清楚并被读者吸收的。</p>
<p>所以在这篇文章中我将回答其中一个问题：</p>
<ol>
<li>当 38 岁时，我从律师转行为软件工程师</li>
<li>以及 39 岁时，我准备大科技公司面试，仅有不到两年的相关经验</li>
</ol>
<p>我所采用的方法。</p>
<p>如果你想要我详细回答其他问题，可以告诉我。我会在这篇文章中穿插我的联系方式，只有通读全文才能找到。😊 这样做也是为了鼓励你仔细阅读这篇文章，而不只是为了一些“小贴士”略读全文。</p>
<h2 id="">发现真正的目标</h2>
<p>我持以下观点：获得面试机会比学习代码难；在面试中表现出色和获得面试机会一样难；如果你不具备竞争对手同样的扎实经验，对于你来说，行为面试很困难。</p>
<p>当我零基础转行时，以及当我将目标设定在大科技公司的时候，我知道我将和博士生、从青少年时期就开始写代码（通常写了 20 多年）以及在技术上有相当成就的人同台竞技，而当时的我只有一年的相关经验。</p>
<p>同时我不在美国，给挑战又增加了难度。</p>
<p>所以我制定了一个不仅仅是“学习写代码”的计划。</p>
<p>首先，让我详细介绍一下为什么我认为“学习写代码”是准备环节中最简单的部分。虽然从 2012 到 2018 年，我反复尝试失败了 4 次。</p>
<p>这个想法是 2018 年产生的。当时我的创业公司正在垂死挣扎，为此我损失了大量的资金，2 年没有任何收入。</p>
<p>但是我决定从我的信贷中取出 4 万多美元，为什么？去参加一个旧金山的训练营（bootcamp）。</p>
<p>我离开了家人，只身前往旧金山，本来要参加为期 14 周的课程，但是在加入这个顶级训练营的第一周我就放弃了，返回澳大利亚。</p>
<p>我曾十分期待这个训练营（也为贷款感到焦虑），但是对训练营的教学策略产生怀疑。我注意到老师和课程设置是帮助学员“学习写代码”而不是“成为一个程序员”。</p>
<p>根据我在 4 个国家以及其他 3 个行业的工作经验来判断，这种策略是一个错误。</p>
<p>学习写代码也是一种形式的“识字能力”，识字能力并不是技能。</p>
<p>我自己就是一个活生生的例子：这 4 次尝试中，每当我专注于“学习写代码”，我都会在一个非常小的领域里获得成功，要么是学会了 HTML 或者 Java，要么就是跟着书本编写了一个安卓应用，我总是能够学会读写一些基本的内容。但是我不知道怎么自己搭建一个有用的东西，当需要真正应用技能的时候我感觉绝望——我没有真正的“技能”。</p>
<p>在这个时代，我们并不因为我们知道什么而被雇佣，我们因为我们的技能被雇佣。</p>
<p>我马上列出代码训练营对于我来说是一个错误决定的原因。</p>
<p>这个昂贵的训练营可能会带给我一些基础的技能，甚至可以帮我找到“入门级别”的工作。但是我可以预见学习会变得非常仓促、标准化以及专注于“正确答案”。</p>
<p>我并不想在任务清单上“打勾”，我想要获得技能、竞争力和信心。</p>
<p>另外，训练营默认将每一位学员打造符合“初级程序员”的标准。</p>
<p>我当时已经 37 岁了，不满足于“入门级别工作”的思维方式。另外即便是行业新人，我不相信任何有三年以上工作经验的人是“初级”员工。</p>
<p>然后我发现训练营的一些老师和助教是没有找到工作的一些前学员。他们没有转行的经验，有一些甚至没有过“职业经历”，职业辅导员甚至从来没有过科技行业面试别人的经验。</p>
<p>我怎么从一些没有经验的人身上学习到我要的经验呢？</p>
<p>还有地理上的困难。本来就生活在旧金山的学员有本地优势，来自美国其他地区的学员没那么容易找到工作，并且在毕业后和拿到第一份工作前的这段时间很容易把钱花光。见鬼，我生活在澳大利亚，这可怎么办？</p>
<p>由此我得出清晰的目标。不是“学习写代码”——而是建立一个可以实现自我的职业。</p>
<p>另外，在研究训练营的时候，我看到了“兼职学习”的可能性。我可以找一份工作，然后在晚上和周末学习，这样做更容易坚持。两年没有收入之后，我必须有一点现金流来消除恐惧，才能专注成为一名专业的软件工程师。</p>
<p>正如传奇商人哈维·费尔斯通（Harvey Firestone）所言：</p>
<blockquote>
<p><em>“据我所知，盈余是对商业判断的最大帮助。”</em></p>
</blockquote>
<p>在学习期间有收入会让我有信心做出更好的判断。更好的判断对于需要通过长期努力来实现的职业很重要。</p>
<p>毫无疑问，如果我在训练营中学习 3-4 个月，我会“学习写代码”。</p>
<p>但是我学到的东西足以让一个优秀的团队买单吗？我不相信训练营和在线代码学习网站会帮助我实现这一目标。</p>
<p>学写代码不会让我走得太远。我必须足够优秀才能击败拥有相关学位、经验和人脉的竞争对手。我想在代码领域获得一份职业。</p>
<p>所以我离开了训练营，浪费了大约 9000 美元，回到澳大利亚。当然，我已经掌握了基本的代码知识，可以通过训练营的入学考试。但我远非能胜任工作。</p>
<p>如果你是职场新手，可能很难理解这里的分析。但你是否注意到许多人都玩音乐，但不是所有人都能胜任乐队成员的职位。正如我的导师所说：</p>
<blockquote>
<p><em>"迈克尔·乔丹不想学打篮球，他想进入 NBA。这两者差异巨大。"</em></p>
</blockquote>
<h2 id="">如何扬长避短</h2>
<p>这一想法改变了一切。在 2019 年的 8 个月内，我获得了我申请的所有 4 个开发岗位的 offer。而在这个过程中，我仅遵从了我和我的教练（非技术）制定的发展计划。</p>
<p>不过不要被误导。我有意想不到的优势。使我受益的两个主要优势，一开始看上去是我的劣势。</p>
<p>这不是我的第一次转行，在曾经的职业生涯中我有近 10 年的招聘经验。今天我也做技术方面的招聘，这些招聘模式非常类似。</p>
<p>我最大的优势是我不以候选人的角度考虑招聘，而是从招聘经理的角度考虑招聘。这对我的计划产生了重大影响，我了解招聘经理的想法——他们的限制、优先级、价值观、业务需求、不喜欢的点、危险信号……</p>
<p>我的经验（年龄？）让我知道可以对团队和组织产生什么样的影响，我可以向哪些人学习，谁可以给我提供想法、指导、建议和引荐，劣势成了优势 – <a href="https://www.freecodecamp.org/news/why-career-changers-have-an-advantage/">转行的优势</a>。</p>
<p>关于优势，我想指出一些重要的事情。</p>
<p>“杀手级别”的优势显而易见，让人脱颖而出。耸耸肩说“当然——这就是他们成功的原因”很简单。但人们常常忽略非常引人注目且严重的缺点。</p>
<p>对我来说，我的“缺点”是：</p>
<ul>
<li>地理位置——我不住在美国或其他科技中心</li>
<li>没有正式的计算机科学资格认证</li>
<li>零技术基础</li>
<li>抵押贷款和财务责任</li>
<li>我的 “年龄” – 40 岁时学习新技能比 25 岁时更难</li>
<li>来自文化和社会的期望、判断、消极情绪</li>
<li>招聘人员和老板比我年轻，不确定如何与我打交道</li>
<li>与其他候选人相比，人们认为转行非常，非常“有风险”</li>
<li>软件工程的收入将低于做律师的收入</li>
</ul>
<p>每天我都听说上述原因是人们转行的绊脚石。虽然我无法评论这些是否是真正的原因，但我确定，如果我们对自己的局限性喋喋不休，这些局限就会永远伴随着我们。</p>
<p>固守我们的劣势并不能帮助我们克服它们。</p>
<p>在我的教练的帮助下和大量的心态/心理培训，我挖掘出自己的劣势并将其中的一些（不是很多！）转化为主要优势。就在那时，我意识到我在面试、招聘和资源配置方面的经验对我制定策略有很大帮助。</p>
<h3 id="">优先事项一：职业转变</h3>
<p>在学习写代码并将成为专业软件工程师设为目标时，我发现我习惯性使用短线思维，并且对第一份工作抱有不切实际的幻想。</p>
<p>我希望我的第一份工作是光鲜亮丽的、证明所有对我的批判都是错误的、给我挣一大笔钱，让我免于在余生中自我怀疑和挣扎。</p>
<p>我必须训练自己以不同的方式看待事物。我的第一份工作必须是让我收获学习和成长，为未来的成功做好准备。工资是市场水平，但如果团队很棒并且确定可以从中收获成长学习，我很乐意接受略低的薪水。它 <em>不需要</em> 是我梦寐以求的工作。</p>
<p>我非常明确地做出了权衡：</p>
<ul>
<li>团队比品牌更重要</li>
<li>团队比金钱更重要</li>
<li>品牌比金钱更重要（因为它会让我为未来的机会做好准备）</li>
<li>金钱比股票更重要（这适用于第一份开发工作，因为这使我的学习计划可持续，尽管股票可能会在未来给我带来更多的财务收益）</li>
<li>但是，学习比品牌或金钱更重要——因为学习可以节省更多时间，这比省钱更重要</li>
<li>我不会在团队和学习之间进行权衡。我需要两者（但我更倾向于一个可靠的团队，而不是在工作中实际学到东西）</li>
</ul>
<p>“搞定难题”或舒适不舒适不是首要任务。我的首要任务是成功地改变职业。</p>
<p>接受一份垃圾代码工作（这种工作有很多......）对我来说并不是一个“成功的”职业转变。但同样，我转行“成功”与否不取决于是否加入了大科技公司。成功的标准非常个人——对我来说，成功意味着热爱我所做的工作并从中学到很多东西。</p>
<h3 id="">我如何做定制化计划</h3>
<p>在分析了我的优势和“可转换”的劣势之后，接下来我给自己定制了一个计划。一个为我量身定做的，我可以相信的。</p>
<p>我需要一个考虑我的具体情况的计划，包括我的脾性、经验、信仰、价值观、目标和技能。</p>
<p>请注意，我还没有谈到技术面试、算法和数据结构等等。在制定我的计划时，我必须关注有与我的代码技能或技术能力无关的部分。</p>
<p>计划还需要考虑到我的心理“跑道”——在我放弃、失去希望或改变主意之前，我准备投入多少时间来进行职业转变？如果不了解我需要多长时间来学习最低要求的技能，我无法回答这个问题。</p>
<p>为了回答这个问题，我需要研究和分析市场需要的最低技能有什么。</p>
<p>为了回答这个问题，我需要分析市场上的几十个软件工程领域，哪些适合我的脾性、兴趣、热情和优势。并找到我可以专注的领域。</p>
<p>我必须找到我的兴趣、我的能力和市场价值之间的重叠。</p>
<p>同样，我在招聘方面的经验给了我（小但重要的）优势。我知道纯粹的技术技能是不够的——这只是起点，“舞会的邀请”。</p>
<p>我也知道，优秀的团队不仅会为技术技能招聘，也会为非技术属性而招聘。这些属性是什么取决于技术领域、团队文化、团队组成等等。</p>
<p>或许你已经猜到了。定制化计划是多维的，把事情做对只会提供少许帮助，而做错会导致方向的巨大错误和时间的浪费。</p>
<p>由于我的职业生涯几乎走过了一半，我决心不再重复过去的错误。我将：</p>
<ul>
<li>目标非常具体。</li>
<li>选择和行为目的性强。</li>
<li>专注于我想要的，不关注我不想要的。</li>
<li>愿意改变自己、我的习惯和消极信念，这样我就可以改变我周围的世界。</li>
<li>准备好专注于建立一个有回报和有成就感的职业，而不仅仅是“获得下一份工作”。</li>
<li>准备好专注于为我未来的团队创造价值，而不是“对我有什么好处”的心态。</li>
<li>愿意打持久战——具备 5-10-25 年的视野，而只是接下来的几周。</li>
</ul>
<p>我必须承认，坚持这些原则比我预期的要难得多。我失败了很多次，尤其是前三点。但既然我已经写下了定制计划，它就是我的行为指导和真理的唯一来源。</p>
<p>我的计划要求我专注于基本的编程技能，然后将其缩小到我认为与我的长期目标和技能相匹配的部分。对我来说，就是 web 开发。所以我要完全且无情地避免所有“光鲜亮丽的新玩意儿”，比如 Python 或 Java。</p>
<p>教程和没完没了的视频不会让我击败竞争对手。我计算出达成我所在城市开发岗位所需最低技能，需要 900-1100 小时专注学习代码，以正确的顺序练习正确的内容。</p>
<p>准备计划花费了数周时间。我不断地修改和强化它，不慌不忙。亚伯拉罕·林肯（另一位转行的律师！）给我很大启发，他曾经说过“给我六个小时砍一棵树，我会用前四个小时磨斧头”。</p>
<p>我很想直接进入我的计划并变得“忙碌”起来，但忙碌并不等同于高效。一旦在我的认知范围内，我确定计划完善了，我就转向全神贯注地执行该计划。</p>
<p>执行意味着很多牺牲，很多很多天的自我怀疑，与改计划的诱惑作斗争，学会管理精力。在此期间，我养成了一些惊人的习惯，但这是事后诸葛。在这6个月的执行中，我不断受到不确定性、恐惧和偶尔失去希望的打击。</p>
<p>后来，我在这个计划的基础上做了调整，为大型科技公司，尤其是 Google 制定了一个计划，花费了 500-600 小时的刻意学习，这与我成为开发人员的计划完全不同。稍后再谈。</p>
<h2 id="google">早期成果，以及……Google 工程师</h2>
<p>我从4年的试验、错误和失败中学到的另一个教训是，我容易在中途改变计划，转换资源、课程或重点。</p>
<p>这是一个非常严重的问题，因为每次我们切换焦点或计划时，我们都会放弃我们已经完成的辛勤工作，回到起点并……重新开始……全部……重新开始。</p>
<p>想象一下，如果你从 A 开车到 B 并不断掉头并返回并重新启动。你永远不会到达任何地方。</p>
<p>但我给自己做了一个承诺（信守一个承诺比信守一大堆更容易！）：我要完成我的计划，然后再决定是否继续。这一次直到完成我的计划，我都不会停下来。</p>
<p>我不必喜欢做这些事情，我只需要喜欢这些事带来的可能性。</p>
<p>我的计划有一个特定的时间，即使我还没有准备好面试，我也会开始面试。但要达到那个阶段，我必须善于创造面试机会。</p>
<p>我过去在其他职业中的经历再一次帮助了我。我应用了我过去 18 年所学的知识，在几周内获得了 4 次面试机会，并获得了全部四份 offer，尽管有很多候选人比我拥有更多的经验、技能和资格。</p>
<p>这不是因为我更擅长写代码。怎么可能是呢？我只有几个月的经验。</p>
<p>我相信拿到四个 offer 是因为 <em>在面试过程中</em> ， <em>在招聘官面前</em> 我表现出了自己是更好的候选人的样子。这是展示自己极为重要的一种方法。</p>
<p>收到 offer 很棒，但我遇到了一个意想不到的问题。由于我的计划要求我对工作职位有清晰的认识，所以我相信这四个职位都是我新职业生涯的绝佳开始。我将如何选择？</p>
<p>是的，这是一个甜蜜的烦恼，但这个决定不容易做！</p>
<p>为了聪明地解决这个问题，我学会了问自己一个非常重要的问题：</p>
<blockquote>
<p><em>我是<strong>知道</strong>这一点还是</em> <em>我只是这么<strong>想</strong></em> <em>?</em></p>
</blockquote>
<p>很多时候，我们根据完全未经检验的假设和信念做决定，我们将我们的意见或幻想误认为是现实。我们实际知道的远比我们未经验证的想法要少得多。</p>
<p>有意识地要求我专注于我所知道的，而不是我希望的，或者仅仅是想的。我必须要么找到证据来支持我的想法，要么根据我的了解无视一些想法。</p>
<p>这个框架培养了我的分析能力，并帮助我在 2019 年初正确地选择了第一份工作，那时我已经 39 岁了。</p>
<p>直到今天我还在使用 <em>知道 vs 想</em> 框架来思考日常决策，我发现它是分析复杂决策权衡取舍的绝佳框架。</p>
<p>回顾过去，我积累了很多关于如何坚持我的计划、重新审视我的目标、自我意识和练习 <em>有目的性</em> 的经验。</p>
<p>进入新岗位几个月后，很多律师开始联系我，问我是怎么做到的。奇怪的是，他们中的许多人曾经坚持认为我犯了一个巨大的错误，在人生这个阶段想要学习代码是不成熟和鲁莽的。</p>
<p>现在他们想要“学习写代码”，让人深思。</p>
<p>他们说“不可能”的时候，是 <em>他们<strong>知道</strong>这一点还是</em> <em>他们只是这么<strong>想</strong></em> <em>?</em></p>
<p>我的目标没有改变，对我来说，学习、成长和团队仍然比品牌或金钱更重要。但我快 40 岁了，我还想以一种我在职业生涯的前半段从未有过的勇气去探索生活。</p>
<p>所以我决定给自己设定一个新目标：我想了解在大科技公司做一名软件工程师是什么样的。我以前在大公司工作过，我知道这并不适合所有人——这就是我一开始决定进入初创公司和小公司的原因。</p>
<p>但是，被所在领域（从工程到产品和销售）“最佳”人的包围，我不会学到更多东西并收获更多成长吗？我是知道这点，还是我只是这么想的？</p>
<p>我进行了研究，发现一般来说人们对大型科技公司很满意。但我也发现大多数人并不像我那样目的性强。所以我把我的研究限制在那些对自己的职业非常有目标的人身上。他们（几乎普遍）表示，即使他们决定离开，他们也从大科技公司中成长了很多。离开大型科技公司也是有意为之，以追求他们的最终目标。</p>
<p>所以我决定申请在硅谷、纽约和西雅图的大型科技公司（包括 3 家 FAMGA 公司）和其他几家公司。我仍然在澳大利亚，所以这是一个巨大的挑战。</p>
<p>我重新设计了我的计划。有几个步骤是相同的​​，但必须对代码课程进行大调整。我还需要了解美国大型科技公司的招聘情况，并使自己值得被推荐。</p>
<p>大约 7 个月后，我开始接受面试。在这 7 个月里，我非常努力地证明自己值得被推荐，人们根据我的努力和被验证的决心给我推荐工作。</p>
<p>我被引荐到了 Meta（当时它被称为 Facebook），但我没有得到面试机会，因为我的技能不匹配。这对我来说是一个宝贵的经验，我一直认为自己小心翼翼地只申请与技能相匹配的职位——我错了。</p>
<p>我意识到，职位描述对招聘公司来说一件事，对 <em>公司之外</em> 的人来说是完全不同的另一件事。这是因为不同公司使用相同的语言来描述不同的职位。招聘方和应聘方可能都不知道这一点！</p>
<p>汇总这些经验之后，在 3 个月内，我收到了 2 家大型科技公司的 offer，还有一家没有通过终面，因为我不知道如何从头开始编写文件系统（我对 Linux 世界一点也不了解！）。</p>
<p>然后我收到了 Google 的 offer。</p>
<p>再一次，我面临着一个非常艰难的决定。Google 的名声响亮，我很难客观权衡，但我真的很想忠于我的目标、我的计划和我的意图。</p>
<p>当涉及到 Google 时，试图将我所知道的与我想的区分开来非常困难。但我十分确定：面试我的团队都是很棒的人。</p>
<p>这是我认为运气很重要的地方。无论人们如何强调技能、大脑、智慧等，运气和“魔法”在生活中都扮演着重要的角色。</p>
<p>我在Google遇到的面试官是友好、善良、开朗且高度专注的工程师。在面试中，他们不想证明我不行，而是帮助我证明我可以。他们热情地回答了我的问题，让我从一开始就感受到相互合作。</p>
<p>这是 Google 特有的吗？也许。但后来，当我在 Google 接受技术面试培训时，我见识到了多种多样的面试官/招聘经理的风格和信念，我看到技能高超的候选人控制不了情绪，沟通不了解答过程等等。所以我很感恩机遇在我找工作中起到的作用。</p>
<p>所以是的——我很幸运，我遇到我的面试官，并且在面试那天我碰巧知道如何回答那些代码题。</p>
<p>这也是我对 <em>工作类型</em> 和 <em>相关技能</em> 的超专注准备得到回报的地方。 大科技公司有很多不适合我的职位（比如 Meta 的那个）。即使我之前有其他职业经验，我也不知道软件工程师的世界有多大，有多少种职业类型和偏好，区分它们有多难。</p>
<p>通过强迫自己有超强的目标感，而不是随机、盲目地申请大科技公司职位，我抓住了小但重要的机会。我深入挖掘了每个职位，并通过与业内朋友交谈来仔细研究它们（再次强调，我的年龄和经验是一项资产，因为我已经建立了超过 15 年的关系，从没想过它们以后会如此有用！）。</p>
<p>对于我得到的每一个 offer，我都进行了深入的研究，并为技术面试做好了充分的准备。在面试当天，就万事俱备，只欠东风。虽然我不认为我“完美搞定了”我的面试，但我做得很好，传达出我是 <em>满足团队需求</em> 的合适人选的信息。</p>
<p>这就来到下一个问题：我是如何为大科技公司的面试做准备的？</p>
<h2 id="">如何为大科技公司面试做准备</h2>
<p>答：分两个阶段，我花了 500 多个小时执行。</p>
<h3 id="1">第 1 阶段：了解现实和竞争格局</h3>
<p>如果我想斩获另一个国家的大型科技公司的职位，而我拥有不到一年的行业经验，有15年以上不相关工作经验，没有计算机科学学位。我需要对现实有一个非常清晰的认识，尤其是竞争格局。</p>
<p>这意味着有准备的希望可能降临，但白日梦，天上掉馅饼这类事情不会发生。</p>
<p>艰难的道理（可以查看<a href="https://www.youtube.com/playlist?list=PLAPuklwJx5V3XZS19AlJQayZFpiZyDT9C">我的 YouTube 视频</a>）、艰难的现实、艰难的工作。</p>
<p>我必须完全接受并且内化以下内容：</p>
<ul>
<li>写代码只是起点，不是终点。学习写代码是大难题的一小部分，是解密的第一步。换句话说，对于获得工程师工作是必要的，但还 <em>不够</em>（尤其是在大科技公司）。</li>
<li>我最大的敌人是我自己的想法。要么是让我消沉的消极情绪，要么是对我信心的破坏和瓦解，我必须养成习惯培养自己的韧性。从挫折中恢复，而不是逃避它们。</li>
<li>我的竞争对手可能不是转行人员，如果他们是，他们<a href="https://www.linkedin.com/posts/zubinpratap_software-engineering-computer-engineering-activity-6946411823759810560-VvA-?utm_source=linkedin_share&amp;utm_medium=member_desktop_web">将来自密切相关的领域，如计算机工程、机械工程或电子工程</a>。绝大多数人拥有技术资格，甚至可能拥有博士学位（事实证明这是真的！），以及数年的行业经验。</li>
<li>作为一个“异常值”和“通配符”，最难的部分是获得面试机会。学习算法和数据结构更容易，“破解代码面试”（无论这意味着什么……）也会更容易。为什么？代码是确定性的——相同的代码通常会产生相同的结果。但生活不是确定的，是否能获得面试机会非常主观。在就业市场，相同的行为不会产生相同的结果。</li>
<li>我需要把自己塑造成经验丰富的工程师愿意与之共事的 <em>那种人</em>。</li>
<li>我假设我的大多数竞争对手至少有 3-5 年的经验。我追不上他们，更别说 <em>超过他们</em> 了。相反，我需要在非技术技能上超越他们，并在技术方面有得比（如果不超越他们）。</li>
<li>我必须比其他人更善于沟通。如果我不知道某事，我需要说出来，然后说明如果有合适的时间和机会，我将如何解决它。我还必须与面试官沟通，以表明我了解他们的 <em>业务需求</em>，而不仅仅是专注于我自私的梦想。</li>
<li>这意味着我必须真正努力了解招聘团队看中的、正在寻找、想要和需要的东西。</li>
<li>我无法控制我的竞争对手（他们的技能、他们的表现、他们知道多少等等），或者我的面试官在想什么、想要什么、他们看重什么，或者他们是否喜欢转行的候选人。我无法控制 <em>大部分</em> 事情。我只能优化我的努力、我的注意力、我的心态，以及我从每次经历有所收获，无论好坏。我只能控制自己的选择和行动。因此，专注于任何外部的事情都是对宝贵精力的浪费。</li>
<li>我得意识到运气的存在。乔丹、<a href="https://zh.wikipedia.org/zh-cn/%E6%B2%99%E5%A5%87%C2%B7%E5%BE%B7%E9%B2%81%E5%8D%A1">坦都卡</a>、费德勒——他们都有运气不好的时候。我也会的。或者，也许我会做得很好，但别人做得更好。其他人会更适合团队的需要，这时不要觉得伤心也不要嫉妒，我自己做面试官时也做过无数次艰难的决定，所以我知道这常常发生。</li>
<li>如果我斩获超过一个 offer，我需要 <em>预先</em> 考虑并 <em>预先</em> 与自己达成一致，以决定我将使用哪些信号和因素来做决定（从我之前四个 offer 中学到的！）。<br>
如果你好奇我是这么做成的……其实我不知道。并不是一蹴而就的。这些都是事后总结。这些经验大部分都来自于“实时”应用<a href="https://fs.blog/first-principles/">第一原则思维</a>。我的导师帮助我一起，缩小范围确定目标，这花了很多时间，我急不可耐地想“开始写代码”。但是……我知道亚伯拉罕·林肯会给我什么建议……</li>
</ul>
<h3 id="2">第 2 阶段：我如何选择学习资源</h3>
<p>我知道每个人都期望有一份“灵丹妙药”。或许是一些博客、视频、资源、教程、播客、PDF清单……揭开代码的所有“秘密”，学习效果立竿见影。</p>
<p>没有。</p>
<p>直到入土，我都认为——信息是一种商品。</p>
<blockquote>
<p>学习很困难，但由于免费信息太多，学习变得 <em>更加困难</em>。</p>
</blockquote>
<p>我们都陷入了这样的陷阱，认为自己了解的信息不够。其实没有。</p>
<p>为什么？因为无论你住在哪里，你说什么语言，你的眼睛、皮肤或头发是什么颜色，你的性别认同是什么——所有资源都会教给你“有用”的东西。在一个非常基本的层面上，它们都是“相同的”。</p>
<p>它们 <em>必须</em> 如此，因为这就是计算机的工作方式。</p>
<p>如果你和我用 JavaScript、Python 或 Java 编写相同的函数，我们将得到相同的结果。这就是计算机的工作方式——它们是<a href="https://en.wikipedia.org/wiki/Deterministic_algorithm">确切的</a> <a href="https://en.wikipedia.org/wiki/Deterministic_algorithm">算法</a>。</p>
<p>但是生活（和面试）<strong>绝对不是</strong>确切的。相同的努力、成绩、技能、智力不会产生相同的结果。</p>
<p>再次强调，我必须 <em>自学</em> 。我必须将注意力从资源/博客/网站/课程上转移开，放在建立坚实的思考模型、识别相关技能、深入研究概念而不是代码实现、以新的方式应用我已经知道的东西，锻炼推理、解决问题的能力，<em>并</em> 在推理时沟通出来我的推理过程。</p>
<p>你会惊讶于我在准备 Google 和其他大科技公司面试时使用的资源。</p>
<p>是的，我使用过 <a href="http://leetcode.com">Leetcode</a>、<a href="http://algoepxert.io">Algoexpert</a>、<a href="http://interviewcake.com">InterviewCake</a> 和 <a href="https://www.youtube.com/c/JennyslecturesCSITNETJRF">Jenny 的 CS 讲座</a>可能还有一些其他的资源，但我没有完整完成一个资源。</p>
<p>这不是因为我突然转移了重点，我是故意的。我意识到他们都教同样的东西，只是风格和内容略有不同。所以我使用这些资源来学习概念，并根据我对面试 <em>模式</em> 的分析混合和搭配这些资源。</p>
<p>我的理由很简单。从招聘角度来看，候选人（在优秀公司）的质量每年都会提高。我个人认为专注于公司是一个巨大的错误——我们应该专注于团队、人和工作。</p>
<p>但世界有自己的运行规律，因此所有人都涌向大公司。竞争的增强使招聘经理更难评估候选人。</p>
<p>招聘经理处理这个问题的唯一方法是提高标准，候选人的处境就更难。候选人的总数不断增加，但被邀请参加面试的候选人“人才库”保持不变，依旧很狭小——通常是 2-10 人。不管有几百人申请，参与面试的人数永远这么多。</p>
<p>因此，没有收到回复或遭到拒绝的候选人就越来越多，尤其是在牛市中。</p>
<p>如果竞争越来越激烈，互联网上的资源也越来越多，但入围人数几乎保持不变，那么“多学”就不是解决办法。每个人都在“学习更多”，就相当于没有提高竞争力。</p>
<p>我还意识到大科技公司会有面试问题清单（这是“高效的”，因为面试— <em>非常</em> 耗时，因此通过拥有一个面试官可以使用的问题库来节省时间是有意义的）。自然，如果这些问题被“泄露”，他们就不会使用这些问题——这会破坏面试过程。</p>
<p>因此，从逻辑上讲，招聘经理不会问 Leetcode 或 Algoexpert 或其他网站上提供的问题。这就产生了一种“军备竞赛”——公开的问题越多，题库中的问题就越多。这导致问题和招聘策略的更多创新和变化。</p>
<p>这让我只有一个选择。我必须学会使用思维模型和对问题的归类总结。我可能永远不会被要求对链表进行排序或实现戴克斯特拉的最短路径算法。相反，我需要知道如何将这些算法应用于“现实世界”的实际问题。</p>
<p>通常，现实世界的问题看起来、听起来或闻起来都不像我们研究的练习题。练习题和竞赛代码题往往被“整齐”地打包好，有明确的限制。</p>
<p>但作为一名面试官，我想知道候选人是如何思考、推理、分析、解释信息和合作的。解决问题的能力是一个亮点。候选人如果采用正确的解决方案，但时间不够用——但会提出很好的问题，并且清楚地知道如何解决问题。就仍然可以获得 offer。</p>
<p>后来，作为 Google 的一名工程师，我总能判断出有人是否 <em>知道</em> 如何解决问题，即使他们无法及时解决。同样，当应聘者不知道如何解决某事时（这没关系——我们都在学习），这一点也很明显。</p>
<p>通过采用我理解问题类型和解决方案，而不是特定代码实现的方法，我可以专注于学习推理而不是学习编写特定算法。</p>
<p>这种方法意味着我完成了不到 40% 的 Algoexpert（当时它的问题是现在的一半）。我还完成了 Leetcode 上大约 50-60 个问题，其中大多数都不是“难”的问题。</p>
<p>我认为“难”问题可能会出现在 45 分钟的面试中，大约有 20% 的时间出现，这意味着 80% 的时间它们会是简单问题或中等问题。所以优化 80% 更有意义，因为我还是个新人，专注于困难的问题会妨碍理解简单和中等的问题。</p>
<p>我使用这些资源来识别问题模式，而不仅仅是“做完”，并获得认证。这就是为什么我没有完成任何一个题库。我也没有使用 “<a href="https://www.amazon.com/Cracking-Coding-Interview-Programming-Questions/dp/0984782850">Cracking the coding interview</a>” 这本书。</p>
<p>在此过程中，我还开始了理解系统设计问题。并将此记录在了<a href="https://chinese.freecodecamp.org/news/systems-design-for-interviews/">系统设计面试问题这个长博客中</a>。</p>
<p>我还决定专注于一种语言：JavaScript。这不是面试的最佳选择（经验丰富的工程师在 Quora 和其他地方不鼓励使用它），但我觉得不重要。面试的目的不是为了测试我对语言的选择——而是为了测试我抽象思考和解决复杂的计算机科学问题的能力。</p>
<p>语言只是一种工具（我持有的另一个核心信念）。事实上，使用像 JS 这样的无类型语言会让我有机会谈论它的局限性或优势，证明我理解如何权衡语言选择的利弊。这样，我就可以展示更广泛的知识和洞察力，而无需实际在代码中体现。</p>
<p>但是我从使用 Java 和 C++ 学习了很多资源。这些语言是 Google 的主要语言。因此，被迫阅读这些语言并理解背后的原则使我不要过多关注“写出代码”，而更多地关注代码背后的推理，使我能够真正写出来。</p>
<p>那是我的全部计划。实践、模式识别、思维模型/第一性原理思维、系统设计、做好更少的事，专注于获得面试机会，而不仅仅是学习更多的代码。</p>
<h3 id="">如何在面试中脱颖而出</h3>
<p>正如我所提到的，我们都有优点和缺点。我们都认为我们的缺点是特殊的、巨大的，而我们的优点是普遍的、不起眼的，而且可能没有多大用处。</p>
<p>这不是真的。从逻辑上讲，如果我们都认为自己的缺点很严重，那么我们都应该屈服于它们。然而，有些人克服了它们。并发现其他人的情况更糟并克服了 <em>这些缺点</em>。</p>
<p>专注于我们能做好的事情是更好的选择。对我来说，我真的相信我可以为团队带去更多价值。我不在乎自己是最聪明的还是最好的。但我期望成为一名优秀的学习者，不惜一切代价保持我的<a href="https://www.youtube.com/watch?v=hiiEeMN7vbQ">成长心态</a>。</p>
<p>所以我试着用它来尽可能地脱颖而出。我学会了向招聘人员、面试官和招聘经理提出非常好的问题。</p>
<p>但这有更深层次的原因。问好问题是我面试公司的方式。就像我说的，我不想重复我职业生涯前半段的错误。因此，提出真正好的问题对我来说很重要，以评估这家公司是否 <em>适合</em> 我。</p>
<p>因为我已经决定重视团队和学习，所以在招聘人员提出薪酬之前，我从未询问过。无论如何，这将低于我作为律师的收入！</p>
<p>相反，我非常专注于了解团队、它的动力、团队信念和价值观、经理如何解决问题（尤其是人的问题）、团队对什么感兴趣、公司部门对什么感兴趣、资产负债表如何，战略是什么，它的资源分配和预算是什么样的，等等。</p>
<p>所有这些都是我在其他行业学到的东西，作为个人贡献者、经理、高管、创始人等等。</p>
<p>所有这些事情也表明我的目的性很强。我对团队、公司、产品和未来真的很感兴趣。这不仅仅是我申请的另一份工作。这是主动的和个人的……而不是被动的和机会主义的。</p>
<p>我相信这帮助我脱颖而出。虽然不是我面试的所有职位，但大部分面试我都获得了 offer。</p>
<p>当我在招聘方面，我总是更喜欢那些真正对职位、人员、产品和公司感兴趣的候选人。那些只是为了“找到工作”的人并没有我想要的能量和动力。</p>
<h3 id="">面试计划和策略</h3>
<p>我的路线图的最后一部分要求我深入了解不同类型的面试过程。</p>
<p>这包括技术和非技术面试、面试的形式、公司组织、运行、计划、配备、评估和权衡面试的方式。但这也要求我了解自己的长处和短处。</p>
<p>在美国寻找大科技公司时，我的目标是每月进行 2-3 次面试——这是一个巨大的挑战，因为我不在美国，而且时区比西海岸早 17 小时。</p>
<p>我必须在奇怪的时间计划和安排面试，这样我才能配合我作为开发人员的日常工作和学习时间。有的面试需要 6 个小时，有的需要 10 个小时或更多。有些是“一整天结对编程”类型的面试。</p>
<p>所有这一切都需要大量的计划和心理训练。我必须有足够的睡眠，足够的锻炼，保持我的心态和信心，完成我的日常工作，陪伴我的家人，学习并专注于我的目标。</p>
<p>为此，我必须对自己擅长的事情诚实。例如，我不是一个早起的人。但我可以忍受深夜。所以我相应地安排了面试、工作、睡眠甚至锻炼。</p>
<p>有一些面试在凌晨2点或更晚，我之前不会睡觉（因为我真的不擅长按时起床！）。因此，我会在凌晨1点锻炼以获取能量并集中注意力，然后进行面试，然后睡到上午 10 点，去上班并管理我的日程安排。</p>
<p>我也会小心地计划面试，这样我就不会背靠背地做两个，除非它们非常相似且有时间限制。例如，在同一周内进行带回家的测试和定时测试需要与带回家和实时代码面试不同的计划——同时还要管理工作和家庭。</p>
<p>为了适当地安排面试，我会与招聘人员密切合作，并对他们保持透明。这有两个好处：我在招聘人员中获得了协作和沟通的信誉和信任，他们还看到我还有其他机会，这增加了我的成果的机会。竞争是好事。</p>
<h2 id="">总结</h2>
<p>我相信你们中的许多人都期待这篇文章提供“内幕”技巧和某种特定的语言，以及要学习的 DSA 问题。我相信我给了你更好的东西。授人以鱼不如授人以渔。</p>
<p>除了道德原因之外，内幕消息的价值本身有限，尤其是在大科技公司。在大型公司中，团队与团队、城市与城市的情况可能大不相同。你需要了解招聘和职业发展的原则，而不仅仅是特定的语言和算法。假设所有的面试都是一样的，是一个很大的错误。</p>
<p>至于我们对数据结构和算法的痴迷……管理你的职业生涯是终极算法。你的头脑是最终的数据结构。学习如何应付他们，尽管偶尔会失败，但你总是会做得很好。<a href="https://twitter.com/ZubinPratap/status/1534341043344252929">强大的想法并不宏大——它们是优雅的极简主义。</a></p>
<p>在阅读的过程中，您会注意到我链接的一些内容，可以通过这些链接找到我。 如果你想不仅仅只“学习代码”，想要学习如何打造 <em>适合自己</em> 的职业，你也可以订阅我的 <a href="https://futurecoderstraining.com">webinars、小课程和邮件</a> 。</p>
<p>也许我能给你留下的最重要的信息是，痴迷于大科技公司是错误的。是的，成为大公司的一员是一件美差，但如果相信他们这是唯一的选择，我们将错过所有其他令人惊叹的机会。</p>
<p>由于当今的文化趋势，大科技公司具有相当的魅力。当然，为大公司工作很好，但是很多伟大的组织并不为人所知。甲之蜜糖，乙之砒霜。</p>
<p>你的第一要务是快乐、充实并过上你想要的生活。这不是来自公司。这来自与您共度时光的人（尤其是同事）和您所做的工作。与其他任何公司一样，大科技公司也有糟糕的经理、队友和工作。</p>
<p>如果你培养技能，在正确的心态之上制定一个正确的计划，并训练自己设定正确的目标，无论有没有大科技公司，你都可以超越自己的梦想。</p>
<h3 id="">后记</h3>
<p>如果你想要了解更多我从律师转行成为软件工程师的过程，可以收听播客 <a href="http://podcast.freecodecamp.org/53-zubin-pratap-from-lawyer-to-developer">episode 53</a> 来自 <a href="http://podcast.freecodecamp.org/">freeCodeCamp podcast</a> 以及 <a href="https://lessonsfromaquitter.com/episode207/">Episode 207</a> 来自 Lessons from a Quitter。这两期播客大概介绍了我的成长蓝图。</p>
<p>如果你对自学代码、转行和 <a href="https://www.freecodecamp.org/news/non-technical-and-looking-for-a-technical-co-founder-2c212c01d6da/">成为自己的代码合伙人</a>感兴趣，可以查阅<a href="http://linktree.com/zubinpratap">这里</a>。如果转行是你梦寐以求的事情，也可以查看我在 free webinar 上的<a href="http://futurecoderstraining.com/">转码文章</a>。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 一个月之内参加 60 多场软件开发面试，我收获了什么？ ]]>
                </title>
                <description>
                    <![CDATA[ 在这篇文章中，我将分享我是怎么动力满满地在一个月内参加 60 多场面试的。更重要的是，我将分享从成功和失败中吸取的经验教训。 我将文章分成三个部分，对应典型招聘流程的不同阶段。 本文的大多数经验值得软件工程师和专业技术从业者借鉴，同时，这些经验背后的原理适用于所有的职业。我希望你能从中找到一些对你职业生涯有帮助的东西。 我是怎么开始的 跟别的软件工程师一样，我经历了许多不同类型的技术面试 —— 从可怕的白板面试到在类似 HackerRank 等平台上的 45 分钟虚拟编码挑战。其中一些面试我的感觉很好，有一些我却感觉非常糟糕。 但是我想要变得非常善于面试。我想要克服面试恐惧症，在面试中表现自信。就像一个冲浪老手一样，我想要学习如何在波涛汹涌的面试高压浪潮中乘风破浪，同时我也想换一份工作。 所以，从 2020 年的 1 月份到 3 月初，我申请了在美国和欧洲的公司（并且获得面试机会），包括：早期初创公司（如 Coda），创业后期的公司（如 Crunchbase），中型公司（如 Affirm），超大型公司（如 Amazon），甚至是允许员工远程办公的公司（如 Webflow）。  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/60-interviews-in-one-month/</link>
                <guid isPermaLink="false">5fcefed039641a0517d51b26</guid>
                
                    <category>
                        <![CDATA[ 面试 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 职业发展 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jiawei Pan ]]>
                </dc:creator>
                <pubDate>Tue, 13 Apr 2021 09:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/12/maranda-vandergriff-7aakZdIl4vg-unsplash--1-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>在这篇文章中，我将分享我是怎么动力满满地在一个月内参加 60 多场面试的。更重要的是，我将分享从成功和失败中吸取的经验教训。</p><p>我将文章分成三个部分，对应典型招聘流程的不同阶段。</p><p>本文的大多数经验值得软件工程师和专业技术从业者借鉴，同时，这些经验背后的原理适用于所有的职业。我希望你能从中找到一些对你职业生涯有帮助的东西。</p><h2 id="-">我是怎么开始的</h2><p>跟别的软件工程师一样，我经历了许多不同类型的技术面试 —— 从可怕的白板面试到在类似 HackerRank 等平台上的 45 分钟虚拟编码挑战。其中一些面试我的感觉很好，有一些我却感觉非常糟糕。</p><p>但是我想要变得非常善于面试。我想要克服面试恐惧症，在面试中表现自信。就像一个冲浪老手一样，我想要学习如何在波涛汹涌的面试高压浪潮中乘风破浪，同时我也想换一份工作。</p><p>所以，从 2020 年的 1 月份到 3 月初，我申请了在美国和欧洲的公司（并且获得面试机会），包括：早期初创公司（如 Coda），创业后期的公司（如 Crunchbase），中型公司（如 Affirm），超大型公司（如 Amazon），甚至是允许员工远程办公的公司（如 Webflow）。</p><p>在投递超过 109 份面试申请后，我获得了 60 多场面试机会，其中包括 60 多场电话面试，50 多场视频技术面试，18 个可以线下在家写的代码项目，11 个在线编码挑战，以及 8 场现场面试（其中包括 3 场虚拟现场面试）。</p><h2 id="--1">我学到了什么</h2><p>为了便于大家阅读，我将文章分成三个部分，对应典型招聘流程的不同阶段。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/12/kevin-ku-w7ZyuGYNpRQ-unsplash.jpg" class="kg-image" alt="kevin-ku-w7ZyuGYNpRQ-unsplash" width="600" height="400" loading="lazy"></figure><h2 id="--2">面试前</h2><p>这部分涵盖了从和一家公司首次建立联系到得到第一次面试邀请的所有内容。</p><h3 id="--3">我从面试申请中学到了什么</h3><p>当我开始向公司发送面试申请时，我以为发送的申请越多，获得面试机会的可能性就越高。看上去很合理，对吧？所以我设定了每天发送 5 份简历的计划，希望每 5 份申请可以带给我 1 个面试机会。</p><p>但是计划没有如我所愿。面试邀请的数量经常达不到我的目标。这个比例大概是 1:12 —— 每发送 12 份申请，获得 1 个面试机会。</p><p>我面临的问题是：我应该增加每天投递简历的数量吗，比如每天 10 家公司？或者有什么其他方面是我需要改变的？</p><p>随着申请失败的次数增加，我发现有些东西需要改变。</p><p>我抽出一天暂停投递简历，并且重新思考我简历投递的方式，然后意识到需要改变。我开始将每份简历看成是向招聘经理（或者任何将要阅读我简历的人）的推销，而现在出售的商品是我自己。</p><p>如果一家公司需要填补人才缺口，而我说我有这个能力，那么我需要找到一个方法让 TA 们相信我所说的。</p><p>我给自己一个新的任务，那就是找到一个能有效地推销<strong>我的独特能力、经验和性格</strong>的方法，让招聘经理相信我是这份工作的合适人选。</p><p>这里是我<em>推销</em>自己的一个例子：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/12/image-7.png" class="kg-image" alt="image-7" width="600" height="400" loading="lazy"></figure><p>在我的简历的加持下，这份求职信帮助我获得面试机会的成功率达到 95%。其中有一次不成功，但是招聘经理仍然回复我说那个岗位已经停止招聘了，但是他也很愿意和我继续保持联系。</p><p>这里的经验是，你需要非常注意简历的内容 —— 质量比数量更重要。当然能兼顾两者就更好了。了解你自己的特有的经验和能力，以一种符合公司要求的方式表现出来，且不失个性。</p><p>了解你投递公司的背景和特殊需要同样也很重要。一家初创公司或者中小型公司可能与大公司的需求不同，因此需要一份不同的技能清单。</p><p>推销你自己，并确保有实质的内容能在面试中支撑你的推销。</p><h3 id="--4">我通过邮件对招聘人员了解更多</h3><p>在此期间，我收到了一堆来自招聘人员的邮件（大部分是不相关的），其中大部分职位是我不感兴趣的。</p><p>阅读这些邮件花了我不少时间，但是我学着具有同理心，去理解这些招聘人员只是在完成 TA 们的本职工作。</p><p>我不再将这些邮件视为噪音，并开始努力回复这些招聘者，甚至回复那些我不感兴趣的岗位的相关邮件。这样一来，我成功地与这些招聘者建立了联系。如果将来我需要转行，这将为我提供丰富的资源。</p><p>你可能不会回复每一封你收到的邮件。但是，你可能会觉得有意思的是，我上面说的其中一些面试机会是通过回复一些不相关的邮件而获得的。回复邮件并没什么坏处。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/12/azharul-islam-9LMGWHqUwnc-unsplash--1-.jpg" class="kg-image" alt="azharul-islam-9LMGWHqUwnc-unsplash--1-" width="600" height="400" loading="lazy"></figure><h2 id="--5">面试阶段</h2><p>这部分包含面试过程中的所有内容，我把它们分为几种不同的面试类型。</p><h3 id="--6">电话面试</h3><p>是的，我知道，你很忙，你需要处理许多事情。但是，你同样是一个专业人士，因此当你接到电话前，你至少需要知道两件事：</p><ul><li>面试官姓什么，怎么称呼</li><li>至少一件关于公司的事情 —— 公司是做什么的，地址在哪，关于公司的新闻，或者其他和公司相关的事</li></ul><p>我注意到，当我向面试中询问这些事情时，给人的感觉是我真的对那家公司感兴趣。招聘人员通常会在面试中关注这一点。</p><h3 id="--7">视频技术面试</h3><p>是否能够清晰表达自己的想法，基本上就决定了你在视频技术面试中表现得怎样。</p><p>你之前可能听过这个说法：</p><p>“<em>面试官关心的是你的思考过程。是的，TA 们能看到你的代码，但是更重要的是 TA 们想要知道你</em><strong><em>为什么</em></strong><em>这样写代码。</em>”</p><p>面试官不在你旁边，所以 TA 们看不到你的非语言提示，例如手势或者其他细微动作。面试官能了解到的只有你用来表达思维的声音。</p><p>现在你知道了如何进行这次对话，接下来的问题是怎么熟练应对？实际上，虽然表达自己的想法对某些人来说是很自然的事情，但对其他人来说却不然，比如我。</p><p>所以 —— 练习！练习！练习！</p><p>多练习模拟面试。我跟朋友进行模拟面试练习，使自己在表达观点的时候更加自信。更有趣的是，这也帮助我建立了一个新的面试思维。</p><p>我开始将面试看成是一场与朋友或者团队成员间的对话，将面试官看成是我的朋友（我有时候在心里会给面试官取个名字）。因此，原本是一场超高压力的面试，我现在会把它看做是一次关于技术的友善的“聊天”。</p><p>基于这个新的理念，在很多场模拟面试之后，我树立了极大的信心，开始享受面试，不好意思，我是说技术聊天。</p><h3 id="--8">如何开始处理一个问题</h3><p>绝对不要在没有了解清楚问题说明的情况下，就开始解决它。如果你先向面试官询问清楚问题是什么意思，那么你的答案也基本不会错。你通过提问来明确问题而不是自己在那儿猜测，也能让面试官对你留有好印象。</p><h3 id="--9">如何解决问题</h3><p>好的候选人知道如何解决问题（例如一个排序问题），但是优秀的候选人知道一个问题的多种解决方案，并且能权衡每种方案的利弊。</p><p>我表现最好的一场面试（思如泉涌）是我不但解决了逻辑问题 —— 我还提供了替代方案，并且讨论了它们的利弊。</p><p>你的目标是提供多种解决方案，愿意讨论它们之间的区别，并且能够至少实现其中一种方案。</p><p>对于技术面试，你需要编写简洁的代码。很多面试官都关心你的代码质量和解决方案的正确性。你的目标是——编写模块化代码，拆分可复用的逻辑单元为一个个方法，正确的变量和方法名——就像老板管理公司一样管理你的代码！</p><h3 id="--10">当你被问题困住时怎么办</h3><p>有时候你会卡在一个问题上，原因有很多：你没有相应的知识，做了错误的假设，缺少细节，等等。</p><p>我曾经认为，在这种情况下，考验我能力的标准是我处理问题有多快，所以我会安静地思考，不和面试官交流，只是自己思考。</p><p>这就是我们很多人误解的地方。我知道，你需要一段单独思考的时间，但是，很抱歉地告诉你——在面试时最好不要单独思考。</p><p>是的，你的面试官希望看到你想出解决方案，但是有一件事你不能忘记—— TA 们也希望看到你能够<strong>与团队同事协作</strong>讨论出解决方案。虽然公司希望有大牛，但 TA 们也想要团队合作。</p><p>你的面试官就是你的朋友、伙伴、你的团队成员。所以，在处理问题时，你可以大胆和 TA 们交流。</p><p>在遇到困难时，自信地分享你的思考过程，而不是那种嚷着要人帮忙。通过这样的方式，你可能找到解决方案。我在面试 Coda 时就是这样做的。</p><h3 id="--11">如何应对编码挑战</h3><p>这部分内容适用于在像 Hackerrank，Codility 等平台上进行编码挑战形式的面试。通常这些是定时挑战，比如 45 分钟或者更长的时间。</p><p>我之前分享的一些内容在这里很有用，但是其他的诸如“明确问题的意思”等在这里并不适用，因为这个时候没有你能问的人。所以我建议你采用这些步骤：</p><ol><li>通读并明确问题</li><li>先编写能运行的代码，即便是<a href="https://www.freecodecamp.org/news/brute-force-algorithms-explained/" rel="nofollow">暴力匹配算法</a>，它可能不会跑通所有的测试用例，但是首先要有能够运行的代码，理想的情况是在 15-20 分钟内编写出来</li><li>通过输入不同的用例测试你的代码，因为这有助于你处理边界情况</li><li>优化执行效率</li><li>重复步骤 4 和 5，直到最后一分钟</li></ol><p>这里的关键是掌握良好的计算机基础知识。我在文末添加了一些相关的资料。</p><h3 id="--12">如何应对线下编写代码的面试题</h3><p>带回家写的项目是一个能展示你的亮点的机会，因为你有更多的时间。这同样意味着这种面试可能会比较花时间。</p><p>我面试的其中一家公司是按时薪（大概每小时 68 美元）支付给员工，然后员工在家里写代码—— TA 们很认真地对待这个项目，所以你也应该认真对待。在你开始花时间写项目前，请想清楚你是否真的想成为这家公司的一员。</p><p>不要在带回家的项目中牺牲代码质量。对你的整体框架设计，命名约定，代码结构等要非常用心，并且准备坚持自己的选择。</p><h3 id="--13">你应该使用哪些技术</h3><p>在面试 Coures Hero 时，我使用了<a href="https://en.wikipedia.org/wiki/Regular_expression" rel="nofollow">正则表达式</a>解决问题，我本可以使用相对简单的字符串解析法。事实证明这是一个错误的决定，结果我没有通过面试。</p><p>教训：只用你用起来<strong>非常</strong>熟悉的且有<strong>很多相关</strong>经验的技术。</p><h3 id="--14">现场面试</h3><p>前一晚睡个好觉，面试的那天早点到，多微笑（这有助于提升自信，但是更重要的是能够帮助你保持轻松的状态并控制自己）。</p><p>直面你的恐惧，明白即使不能解决问题，也不会是世界末日——毕竟你只是进行了一场技术交流——然后沉浸到纯粹的交谈中。</p><p><strong>如何进行虚拟现场面试</strong></p><p>这可能会和直接的线下面试有很大的不同，因为所有人的眼睛都看着你，并且会令人不安。</p><p>我经历过 3 次虚拟现场面试，而且没有通过任意一场。抱歉，我不是你要寻找的那个对虚拟现场面试很在行的人，但是我在文末分享了一些可能对你有帮助的资源。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/12/essentialiving-yvG7vDXCzDE-unsplash.jpg" class="kg-image" alt="essentialiving-yvG7vDXCzDE-unsplash" width="600" height="400" loading="lazy"></figure><h2 id="--15">面试后</h2><h3 id="--16">如何面对失败</h3><p>有很多原因导致你没有通过面试。有些我知道的最好的工程师在某些时候也没有通过面试，而且现在依然如此。</p><p>所以，忘掉那些失败的面试，从每次失败的面试中吸取经验，并利用这些让自己进步。就像有些人说的——我们继续前进！</p><h3 id="--17">那么如何面对成功呢</h3><p>庆祝你的成功，无论是你认为它是多么小的成功。我有一些庆祝的点子。</p><h2 id="--18">做了这些之后，我会变得更好吗</h2><p>我不是要告诉你，我顺利通过了每次面试。但是可以肯定的是，我在每场面试之后都变得更擅长面试，而且我的自信心也真的提升了。是的，我也同时获得了很多入职邀请。</p><h2 id="--19">接下来做什么</h2><ul><li>和朋友反复练习模拟面试。练习能够帮助你<strong>快速</strong>地辨识出面试问题的模式，熟练掌握，最终帮助你树立自信。</li><li>对于技术面试，熟练掌握数据结构和算法能够打败一切。我在文末分享了一些可能对你有帮助的资源。</li><li>开始面试，并持续面试，即使你有一份工作，也要不停的面试——可能每个月一次或者每个季度一次面试。面试是一种技巧，所以要不断磨练。</li></ul><p>我真心希望这篇文章对你有所帮助，希望这里分享的一些经验教训将有助于你在面试时更加自信，面试表现更好——最终将帮助你找到心仪的工作。</p><p>如果你需要一个人来和你进行模拟面试，可以随时通过 Twitter<a href="https://twitter.com/meekg33k" rel="nofollow"><strong>@meekg33k</strong></a> 联系我。</p><p>后会有期！</p><h2 id="--20">有用的资源</h2><ul><li><a href="https://learntocodewith.me/posts/technical-interview/" rel="nofollow"><strong>技术面试终极指南 | 与我一起学习编码</strong></a></li><li><a href="https://www.themuse.com/advice/how-to-ace-your-technical-interview" rel="nofollow"><strong>如何进行技术面试</strong></a></li><li><a href="https://www.freecodecamp.org/news/the-essential-guide-to-take-home-coding-challenges-a0e746220dd7/" rel="nofollow"><strong>带回家的编码挑战的基本指南</strong></a></li><li><a href="https://firstround.com/review/The-anatomy-of-the-perfect-technical-interview-from-a-former-Amazon-VP/" rel="nofollow"><strong>前亚马逊副总裁剖析完美技术面试</strong></a></li><li><a href="https://online.hbs.edu/blog/post/virtual-interview-tips" rel="nofollow"><strong>掌控下一次线上面试的 9 个技巧 | HBS 在线</strong></a></li><li><a href="https://www.salary.com/passages/8-tips-for-acing-virtual-interviews/3/" rel="nofollow"><strong>8 个 Skype 面试技巧：轻松完成虚拟面试</strong></a></li></ul><p>原文：<a href="https://www.freecodecamp.org/news/what-i-learned-from-doing-60-technical-interviews-in-30-days/">What I Learned from Doing 60+ Technical Interviews in 30 Days</a>，作者：Uduak Obong-Eren</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 系统设计面试问题：必备概念 ]]>
                </title>
                <description>
                    <![CDATA[ 你或许听说过“架构”或“系统设计”这两个术语，它们在开发者的求职面试中经常出现，尤其是大型科技公司的招聘人员喜欢提这方面的问题。 这篇教程将深入软件架构的基本概念，帮你做好系统设计面试的准备。 因为系统设计是一个庞大的主题，所以这篇文章并不会面面俱到。但如果你是一名初中级开发者的话，这应该能为你奠定坚实的基础。 你可以从这里深入挖掘其它资源。我已经在文章底部列出了一些我喜爱的资源。 我已经将这篇教程按照主题划分成了很多小模块，我建议你将它加入到书签。我发现 间隔性学习与重复 [https://www.freecodecamp.org/news/use-spaced-repetition-with-anki-to-learn-to-code-faster-7c334d448c3c/]   是获取知识的宝贵工具，真让人难以置信。我已将本教程设计成很多小片段，以便你进行间隔性重复记忆。 第一节：网络与协议（IP、DNS、HTTP、TCP 等） 第二节：存储、延迟与吞吐量 第三节：可用性 第四节：缓存 第五节：代理 第六节：负载均衡 第七节：一致性哈希 第八节：数据库 第九节：领导选 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/systems-design-for-interviews/</link>
                <guid isPermaLink="false">600d32445f61e30501b5c0fe</guid>
                
                    <category>
                        <![CDATA[ 面试 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 系统架构 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nicholas Zhan ]]>
                </dc:creator>
                <pubDate>Sun, 24 Jan 2021 08:46:44 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/02/photo-1573497491208-6b1acb260507.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>你或许听说过“架构”或“系统设计”这两个术语，它们在开发者的求职面试中经常出现，尤其是大型科技公司的招聘人员喜欢提这方面的问题。</p>
<p>这篇教程将深入软件架构的基本概念，帮你做好系统设计面试的准备。</p>
<p>因为系统设计是一个庞大的主题，所以这篇文章并不会面面俱到。但如果你是一名初中级开发者的话，这应该能为你奠定坚实的基础。</p>
<p>你可以从这里深入挖掘其它资源。我已经在文章底部列出了一些我喜爱的资源。</p>
<p>我已经将这篇教程按照主题划分成了很多小模块，我建议你将它加入到书签。我发现 <a href="https://www.freecodecamp.org/news/use-spaced-repetition-with-anki-to-learn-to-code-faster-7c334d448c3c/">间隔性学习与重复</a> 是获取知识的宝贵工具，真让人难以置信。我已将本教程设计成很多小片段，以便你进行间隔性重复记忆。</p>
<p><a href="#section1">第一节：网络与协议（IP、DNS、HTTP、TCP 等）</a><br>
<a href="#section2">第二节：存储、延迟与吞吐量</a><br>
<a href="#section3">第三节：可用性</a><br>
<a href="#section4">第四节：缓存</a><br>
<a href="#section5">第五节：代理</a><br>
<a href="#section6">第六节：负载均衡</a><br>
<a href="#section7">第七节：一致性哈希</a><br>
<a href="#section8">第八节：数据库</a><br>
<a href="#section9">第九节：领导选取</a><br>
<a href="#section10">第十节：轮询、流、套接字</a><br>
<a href="#section11">第十一节：端点保护</a><br>
<a href="#section12">第十二节：消息与发布-订阅</a><br>
<a href="#section13">第十三节：必备小知识</a></p>
<p>咱们开始吧！</p>
<h2 id="section1">第一节：网络与协议</h2>
<p>“协议（protocol）”是一个很花哨的词，它在英语中的含义与在计算机科学中的含义截然不同。它表示一套管理事物的规章制度，一种“官方步骤”或“做事情时必须采用的官方做法”。</p>
<p>为了让人们连接到彼此通信的机器和代码，他们需要一个可以在其上进行通信的网络。但是通信也需要一些规则、结构和协商好的步骤。</p>
<p>因此，网络协议（network protocols）是管理给定网络上机器与软件之间如何通信通信的协议。我们热爱的万维网就是网络的一个例子。</p>
<p>你可能听过因特网时代中一些最常见的网络协议，比如 HTTP、TCP/IP 等等。让我们将它们分解成基础构造块吧。</p>
<h3 id=""><a href="https://chinese.freecodecamp.org/news/systems-design-for-interviews/15">因特网协议</a></h3>
<p>把因特网协议（IP，Internet Protocol）视为是协议层的基础吧，它是一个基础协议，指导我们如何实现几乎所有的因特网内通信。</p>
<p>因特网协议上的消息（message）通常以“数据包”的形式进行通信。数据包（packet）是一小团信息（2^16 字节），它的<a href="https://en.wikipedia.org/wiki/IPv4#Packet_structure">核心结构</a>包含两节：协议头（Header）和数据（Data）。</p>
<p>协议头包含与数据包及其内部数据的有关的元数据，比如源 IP 地址（数据包源自哪里）和目的 IP 地址（数据包的目的地）。很明显，这是将信息从一个点发送到另一个点的基础——你需要“从哪来”的地址和“去往哪”的地址。</p>
<p>每个连接到使用 IP 协议通信的 <a href="https://en.wikipedia.org/wiki/Computer_network">计算机网络</a>的设备都会被赋予一个数字标签，这个标签就是 <a href="https://en.wikipedia.org/wiki/IP_address">IP 地址</a>。IP 地址分为公有地址和私有地址，当前有两个版本 IP 协议。新版本被称为 IPv6，它正被逐渐采用，因为 IPv4 地址快耗尽了。</p>
<p>我们在这篇文章中要考虑的其它协议都建立在 IP 之上，就像你最最喜欢的软件语言有建立在它上面的库和框架一样。</p>
<h3 id=""><a href="https://chinese.freecodecamp.org/news/systems-design-for-interviews/19">传输控制协议</a></h3>
<p>传输控制协议（TCP，Transmission Control Protocol）是一个建立在 IP 之上的实用程序。通过阅读我的文章，你可能知道：我坚信你若要真正理解某个东西是做 <em>什么</em> 的，你就要先理解它 <em>为什么</em> 被发明出来。</p>
<p>创建 TCP 是为了解决 IP 的一个问题。通过 IP 传输的数据通常在多个数据包中发送，由于每个数据包都相当小（2^16 字节），所以多个数据包可能出现：(A) 数据包丢失；(B) 乱序。因此导致传输数据损坏。 TCP 通过保证数据包的 <em>有序传输</em> 解决了这些问题。</p>
<p>因为是建立在 IP 之上的，所以 TCP 数据包除了有 IP 头外，还有 TCP 头。这个 TCP 头包含数据包的顺序和数据包的数量等信息。这保证了另一端接收到的数据是可靠的。TCP 通常因建立在 IP 之上而被称为 TCP/IP。</p>
<p>TCP 需要在传输数据包之前建立源端与目的端之间的连接，这是通过“握手”完成的。连接本身是通过使用数据包建立的：源端告知目的端它想打开一个连接，目的端表示同意，然后一个连接就打开了。</p>
<p>这实际上就是服务器“监听”某个端口时发生的事情——在它开始监听前，会先进行一次握手，然后连接被打开（监听开始）。类似地，连接的一端给另一端发送一个打算关闭连接的消息，这就会终止该连接。</p>
<h3 id=""><a href="https://chinese.freecodecamp.org/news/systems-design-for-interviews/20">超文本传输协议</a></h3>
<p>超文本传输协议（HTTP，Hyper Text Transfer Protocol）是一种建立在 TCP/IP 之上的抽象，它引入了一个被称为请求-响应模式非常重要的模式，专门用于客户端-服务端交互。</p>
<p>客户端通常是请求信息的机器或系统，而服务器是以信息进行响应的机器或系统。浏览器是客户端，而 Web 服务器是服务器。当一台服务器向另一台服务器请求数据时，前者是客户端，后者是服务器（赘述，我知道）。</p>
<p>所以这个请求-响应环路在 HTTP 下有自己的规则，这也标准化了因特网间的信息传输方式。</p>
<p>在这层抽象上，我们通常不需要过于担心 IP 和 TCP。然而，HTTP 中的请求与响应不仅有头部，还有主体部分。它们包含了能被开发者设置的数据。</p>
<p>HTTP 请求与响应可以被看作键-值对形式的消息，它们和 JavaScript 中的对象以及 Python 中的字典非常相似，但又有所不同。</p>
<p>下图展示了 HTTP 报文的内容和 HTTP 请求与响应消息中的键-值对。</p>
<figure>
    <img src="https://www.freecodecamp.org/news/content/images/2020/03/image-44.png" width="600" height="400" alt="image-44" loading="lazy">
    <figcaption>来自：<a>https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages</a></figcaption>
</figure>
<p>同 HTTP 一起的还有一些“动词”或“方法”，它们是一些指令，告诉你将要执行何种操作。例如，常见的 HTTP 方法有“GET”、“POST”、“PUT”、“DELETE”和“PATCH”，但远不止这些。找一下上图中起始行中的 HTTP 动词。</p>
<h2 id="section2">第二节：存储、延迟与吞吐量</h2>
<h3 id="">存储</h3>
<p>存储（storage）和保存信息有关。你编写的任何应用、系统或服务都需要保存和检索数据，这正是存储的两个基本功能。</p>
<p>但是存储并不只和保存数据有关，它还涉及到数据的取出。我们使用数据库（database）来实现存储。数据库是一个软件层，它帮助我们保存和检索数据。</p>
<p>保存（storing）和检索（retrieving）这两种主要的操作类型又被称为放置（set）和获取（get）、存储（store）和取出（fetch）、写（write）和读（read），等等。要与存储交互，你需要经过数据库，它是你执行这些基础操作的中间人。</p>
<p>“存储”一词有时能让我们傻傻地从物理的角度思考它。如果我把我的自行车“存储”在仓库，我就能预料到下次打开仓库时它就在那里。</p>
<p>但是，计算机世界中有时并不会这样。存储可以大体分为两种类型：“内存（Memory）”存储和“磁盘（Disk）”存储。</p>
<p>在这两种类型中，磁盘存储往往更稳健，也更具“永久性”（并不是真的永久（permanent），所以我们经常使用“持久化的（persistent）”存储取而代之）。磁盘存储是一种持久化存储，这意味着只要你把东西保存到磁盘，不管断电，还是重启服务器，那些数据都将“持久保存”，不会丢失。</p>
<p>然而，如果你让数据驻留在“内存”中，数据通常会在关机、重启或断电时被擦除。</p>
<p>你每天都在使用的计算机同时拥有这两种类型的存储。你的硬盘是“持久化的”磁盘存储，而你的 RAM 是瞬时的内存存储。</p>
<p>在服务器上，如果你正在记录的数据只在该服务器的会话（session）中有用，那么将它保存在内存中是合理的。这种方式比将东西写入到持久化数据库更快，更实惠。</p>
<p>例如，单个会话可能指用户登录，使用你的网站。在用户退出之后，你可能并不需要紧紧抓住这次会话期间收集到的数据不放。</p>
<p>但是，你会把你任何你想留住的数据（比如购物车历史）放到持久化的磁盘存储中。这样，你可以在用户再次登录时访问那些数据，给用户提供一个无缝的使用体验。</p>
<p>好了，这些似乎非常简单和基础，但实际上并非如此。这是个入门。存储可以变得非常复杂，如果你看一看存储产品和解决方案的规模，很快就会头晕目眩了。</p>
<p>这是因为不同的使用场景需要不同类型的存储。为你的系统选择正确的存储方式的关键取决于很多因素和应用的需求，还有用户如何与之交互。其它的因素包括：</p>
<ul>
<li>数据的形状（结构），或者</li>
<li>数据需要具有的何种可用性（对你的存储来说，什么级别的停机时间是可以出现的），或者</li>
<li>可伸缩性（你需要怎样的数据读写速度，以及这些读写是并发（同时）进行的还是顺序进行的）等等，或者</li>
<li>持久性——如果你使用分布式存储处理停机，那么各存储之间的数据一致性如何？</li>
</ul>
<p>这些问题和结论需要你仔细权衡。一致性的重要程度高于速度吗？你需要数据库抗住每分钟数百万次操作，还是仅用于每晚更新？我将会在后续章节中讨论这些概念，如果你不知道它们是什么，也不用担心。</p>
<h3 id="">延迟</h3>
<p>随着你在支撑前端应用方面的系统设计经验的增加，你会不断听到“延迟”或“吞吐量”这两个术语。总的来说，它们对应用和系统的使用体验和性能至关重要。这些术语的使用普遍都倾向于超出预期范围或脱离上下文，但让我们来解决掉这个问题吧。</p>
<p><strong>延迟（Latency）</strong> 只是对持续时间的度量。什么是持续时间呢？某个动作的持续时间是指完成某事或产生结果所经历的时间。例如：对于从系统中的一个地方移动到另一个地方的数据来说，你可以将持续时间看成延迟（lag），也可以简单地把它看成是完成某个操作所花费的时间。</p>
<p>延迟最常见的理解就是“往返”的网络请求——前端网站（客户端）从给服务器发送查询，到收到服务器返回的响应所花费的时长。</p>
<p>当加载一个网站时，你想要加载过程尽可能的快、整个过程尽可能的流畅。换句话说，你想要的是 <em>低</em> 延迟。快速查找就意味着低延迟。所以在数组中（高延迟，因为你需要迭代数组中的每个元素，找出你想要的那一个）寻找一个值比在哈希表中（低延迟，因为你只需要通过键就可以在“常量”时间内获得数据，不需要进行迭代）更慢。</p>
<p>类似地，从内存读取数据比从磁盘读取数据要快得多（<a href="https://stackoverflow.com/questions/1371400/how-much-faster-is-the-memory-usually-than-the-disk">阅读更多</a>）。但是这两种方式都存在延迟，你的需求会决定为何种数据选择何种类型的存储。</p>
<p>这样看来，延迟是速度的对立面。你想要更高的速度，就是想要更低的延迟。速度（尤其是像通过 HTTP 这样的网络调用）也由距离决定。所以，<a href="https://wondernetwork.com/pings/London">伦敦到另一座城市的延迟</a> 将受该城市与伦敦之间的距离的影响。</p>
<p>想象一下，如果你要设计一个避免 ping 远程服务器的系统，但是为你的系统将数据保存到内存又不太可行。这些都是权衡点，它们让系统设计变得复杂、充满挑战，并且趣味十足！</p>
<p>例如，新闻网站比起加载速度可能更看重正常运行的时间和可用性，而多人游戏可能要求可用性和超低延迟。这些需求将决定基础设施的设计和相关投资，从而支持系统的特殊需求。</p>
<h3 id="">吞吐量</h3>
<p>吞吐量（Throughput）可以被理解为机器或系统的最大容量。它经常在工厂中使用，用于计算装配线一个小时或一天或其它时间计量单位内能完成的工作量。</p>
<p>例如，对一条每小时能够组装二十辆汽车的装配线来说，每小时二十辆汽车就是它的吞吐量。在计算机中，吞吐量指的是单位时间内传送的数据量。所以一个 512 Mbps 的连接就是一种吞吐量的度量方式——512 Mb（兆字节）每秒。</p>
<p>现在想象一下 freeCodeCamp 的服务器。如果它每秒收到一百万个请求，而它只能处理八十万个请求，它的吞吐量就是八十万每秒。你最终可能会用二进制位（bit）的形式而不是请求数量来衡量吞吐量，所以它将会是 N 位每秒。</p>
<p>这个示例中有一个 <em>瓶颈（bottleneck）</em>，因为服务器一秒内最多只能 N 位的数据，但是请求的数据比这个值要高。瓶颈因此成为了系统的约束，系统能达到的最快速度就是它的 <em>最慢瓶颈</em>。</p>
<p>如果一台服务器每秒可以处理一百位，另一台服务器每秒可以处理一百二十位，第三台服务器每秒只能处理五十位，那么整个系统将会以 50bps 的速度进行处理，因为它就是整个系统的约束——它阻挡住了系统中其它服务器的速度。</p>
<p>所以，在瓶颈之外的任何地方增加吞吐量都可能会徒劳无功，你可以先在 <em>最低瓶颈</em> 处增加 <em>吞吐量</em>。</p>
<p>为了增加吞吐量，你可以购买更多的硬件（横向伸缩），也可以增加现有硬件的容量和性能（纵向伸缩），还可以采用另外几种方式。</p>
<p>有时候，增加吞吐量可能只是一个临时的解决方案，所以一个好的系统设计师会全面考虑伸在伸缩给定系统的最佳方式，包括分离请求（或任何其它形式的“负载”），并把他们分发到其它资源上，等等。要牢记的关键点就是：什么是吞吐量，约束或瓶颈又是什么，这些约束和瓶颈是如何影响系统的。</p>
<p>固定延迟和吞吐量并不是独立的、通用的解决方案，它们也不互相关联。它们在整个系统中都有影响和需要考虑的地方，所以了解整个系统以及随时间加入到系统中的需求的本质非常重要。</p>
<h2 id="section3">第三节：系统可用性</h2>
<p>软件工程师的目标是建立可靠系统。可靠系统始终能够满足用户需要，任何时候只要用户有需要，都会被满足。可靠性（reliability）的一个关键部分是可用性（availability）。</p>
<p>将可用性看成系统的弹性（resiliency）是很有帮助的。如果系统足够稳健，能够处理好网络、数据库和服务器等中的故障，就可以被看成是一个容错（fault-tolerant）系统——容错让系统变得可靠。</p>
<p>当然，从多种意义上来说，系统其各个部件的总和，如果可用性关系到终端用户在网站或应用上的使用体验，那么每个部件都需要是 <a href="https://en.wikipedia.org/wiki/High_availability">高可用的</a>。</p>
<h3 id="">量化可用性</h3>
<p>为了量化一个系统的可用性，我们计算给定时间段内系统主要功能和操作处于可用状态下的时长（正常运行时间）所占的百分比。</p>
<p>大多数关键业务系统都需要具有近乎完美的可用性。那些支持具有尖峰和低谷的高可变需求和负载的系统，在非高峰时期的可用性可以稍微低一点。</p>
<p>这一切都取决于系统的使用和性质。但是一般来说，即便是那些有着不变的需求或只“按需”得到保障的系统也需要有高可用性。</p>
<p>想想一个你用来备份照片的网站。你并不总是需要来这个网站检索数据——它主要是用来为你存储东西的。你还是希望每次登录网站时它都是可用的，哪怕只是下载一张照片。</p>
<p>一种不同类型的可用性可以放在像黑色星期五或网络星期一这样的大型电商购物日中进行理解。在这些特定的日子中，需求猛涨，成千上万的用户尝试同时访问订单。这就需要极其可靠和高度可用的系统设计来支撑那些负载了。</p>
<p>高可用性的一个商业原因很简单：网站的任何停机时间都会导致金钱的损失。并且，这会对网站的声誉造成非常恶劣的影响。例如，在一个服务是被 <em>其它</em> 业务用来提供服务的场景中，如果 AWS S3 挂了，那么包括 Netflix 在内的很多公司都会遭殃，这可 <em>不是什么好事儿</em>。</p>
<p>所以上线时间对成功尤为重要。值得牢记的一点是：商业可用性数字是根据年度可用性计算的，所以 0.1% 的停机时间（即 99.9% 的可用性）就是 <a href="https://en.wikipedia.org/wiki/High_availability">一年 8.77 小时</a>。</p>
<p>因此，上线听起来时间极高。看见像 99.99% 上线时间（每年的停机时间仅为 52.6 分钟）的东西是很普遍的，这就是为什么现在普遍使用术语“nines”；来指代正常运行时间——担保正常运行时间中（<a href="https://en.wikipedia.org/wiki/High_availability#%22Nines%22">9 的个数</a>）。</p>
<p>当今世界中，大规模或关键服务宕机是不可接受的。这就是为何现在将“five nines”看成了理想的可用性标准，因为它表示 <em>每年</em> 的停机时间只有五分钟多一点。</p>
<h3 id="">服务级别协议</h3>
<p>为了使得在线服务具有竞争力并满足市场的期望，在线服务提供商通常会提供一个服务级别协议/保证（Service Level Agreements/Assurances）。它们是一系列保证的服务级别指标，99.999% 的正常运行时间就是其中之一，它也经常作为高级订阅服务的一部分。</p>
<p>至于数据库和云服务提供商，如果客户的核心用途可以证明了该指标是符合期望的，它甚至可以在试用或免费套餐中提供。</p>
<p>在很多情况下，如果未能满足 SLA，客户会因提供商未能满足保证而得到信用或其它形式的补偿。<a href="https://cloud.google.com/maps-platform/terms/sla">Google's SLA for the Maps API</a> 就是一个例子。</p>
<p>SLA 因此成为了设计系统时整个商业和技术要考虑的关键部分。考虑可用性是否是系统某个部分的关键需求，以及系统的哪些部分需要具有高可用性尤其重要。</p>
<h3 id="">设计高可用系统</h3>
<p>在设计一个高可用（HA，high availability）系统时，你需要减少或消除“单点故障”。<a href="https://en.wikipedia.org/wiki/Single_point_of_failure">单点故障</a>是系统中的一个部件，它的故障会导致系统丢失可用性。</p>
<p>你可以通过在设计系统时引入“冗余”来去除单点故障。冗余（redundancy）就是为对高可用性有影响的关键元素准备一个或多个替代品（即备份）。</p>
<p>所以，如果你的应用要求用户在使用前必须得到认证，但你又只有一个认证服务和后端，然后它挂了，那么由于它是单点故障，你的系统就没法被使用了。通过准备两个或多个处理认证的服务，你添加了冗余，消除（或减少）了单点故障。</p>
<p>因此，你需要理解并将你的系统拆分为各个部件。找出最有可能导致单点故障的那些部件（那些无法容忍这种错误的部件），以及那些可以容忍错误的部件。因为高可用工程需要进行权衡，而有些权衡可能会在时间、金钱和资源方面非常昂贵。</p>
<h2 id="section4">第四节：缓存</h2>
<p>缓存（caching）是一项用于提升系统性能的技术，非常基础，也很容易理解。它帮助降低系统中的<a href="#section2">“延迟”</a>。</p>
<p>在我们的日常生活中，缓存在大多数时候都是用作一种常识。如果我们住在超市的隔壁，我们还是会想要购买一些基本用品，并将它们保存在冰箱或者柜橱中，这就是缓存。我们总是可以在每次想要食物的时候走出门，来到隔壁，然后购买它们——但是如果储藏室或冰箱中有这些东西的话，我们就减少了做东西吃的时间，那就是缓存。</p>
<h3 id="">常见缓存场景</h3>
<p>类似地，在软件方面，如果我们经常依赖某些数据，我们就会想把那些数据缓存起来，以便应用可以执行得更快。</p>
<p>从内存中检索数据比<a href="#section2">磁盘</a>要快的多，因为网络请求中存在延迟。实际上，很多网站都使用 <a href="https://www.cloudflare.com/learning/cdn/what-is-caching/">CDN</a> 缓存数据（尤其是那些很少改变的内容），以便为最终用户提供更快的服务，降低后端服务器的负载。</p>
<p>缓存的另一个使用场景就是后端需要进行一些计算密集型和耗时的工作。缓存之前的结果可以让你在线性 O(N) 到常量 O(1) 的时间内进行查找，非常有帮助。</p>
<p>同样地，如果你的服务器需要进行多次网络请求和 API 调用才能拿到响应请求所需要的完整数据，缓存数据就能帮你减少网络调用的次数，还有延迟。</p>
<p>如果你的系统有客户端（前端）、服务器和数据库（后端），那么缓存可以被放到客户端上（比如浏览器缓存），也可以放到客户端和服务器之间（比如 CDN），还可以放到服务器本身。这将减少去往数据库的网络调用。</p>
<p>所以缓存可以位于系统中的多个位置或多个级别，包括硬件（CPU）级别。</p>
<h3 id="">处理过期数据</h3>
<p>你可能已经注意到了上面的示例对“读”操作的处理是隐式的。写操作在主要原则上也没什么不同，只是加了以下的考虑：</p>
<ul>
<li>写操作要求缓存与数据库保持同步</li>
<li>这可能会增加复杂度，因为有更多的操作要执行，对未同步或“过期”数据的处理需要仔细分析</li>
<li>新的设计原则可能需要实现对同步的处理——它应该是同步进行，还是异步进行？如果异步进行，那么时间间隔取多大？</li>
<li>数据“驱逐”或更新和数据的刷新，从而保证缓存数据是最新的。这包括像 <a href="https://en.wikipedia.org/wiki/Cache_replacement_policies#Last_in_first_out_(LIFO)_or_First_in_last_out_(FILO)">LIFO</a>、<a href="https://en.wikipedia.org/wiki/Cache_replacement_policies#First_in_first_out_(FIFO)">FIFO</a>、<a href="https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)">LRU</a> 和 <a href="https://en.wikipedia.org/wiki/Cache_replacement_policies#Least-frequently_used_(LFU)">LFU</a> 这样的技术。</li>
</ul>
<p>所以让我们用一些概括性的、非约束性的结论结束这一节吧。通常来说，在保存静态或不经常改变的数据时，以及变化源很有可能是单个操作而不是用户产生的操作时，缓存表现最好。</p>
<p>在数据的一致性和新鲜度非常关键的场合下，缓存可能就不是最优的解决方案了，除非系统中有一个一个部件在高效地刷新着缓存，并且刷新间隔还不会反过来影响到应用的用户体验。</p>
<h2 id="section5">代理</h2>
<p>代理。啥？我们中的很多人都听说过代理服务器（proxy servers）。我们或许已经在一些自己的 PC 或 Mac 软件上见过关于添加和配置代理服务器的配置项，或者是一些关于如何“通过代理”访问的配置。</p>
<p>所以让我们来看看这个相对简单而又被广泛使用的的重要技术吧。英语中的代理一词与计算机中的代理毫不相关，所以我们先从它的<a href="https://www.merriam-webster.com/dictionary/proxy">定义</a>开始。</p>
<figure>
    <img src="https://www.freecodecamp.org/news/content/images/2020/03/Screen-Shot-2020-03-08-at-12.57.03-pm.png" width="600" height="400" alt="Screen-Shot-2020-03-08-at-12.57.03-pm" loading="lazy">
    <figcaption>来自：<a href="https://www.merriam-webster.com/dictionary/proxy">https://www.merriam-webster.com/dictionary/proxy</a></figcaption>
</figure>
<p>现在你可以将大多数含义从脑海中驱逐出去，只留下一个关键词：“代替（substitute）”。</p>
<p>在计算机中，代理通常是一个服务器，并且是一个扮演客户端与另一台服务器之间的中间人角色的服务器。它就是位于客户端与服务器之间的一段代码，这就是代理的关键。</p>
<p>也许你需要复习一下，或者还不太确定客户端和服务器的定义：“客户端”是一个进程（代码）或机器，它从另一个进程或机器（“服务器”）请求数据。当浏览器向一个后端服务器请求数据时，它就是客户端。</p>
<p>服务器为客户端提供服务，但它也可以是客户端——当它从数据库检索数据时，数据库就是服务器，服务器本身既是（数据库的）客户端，<em>又是</em> 前端客户端（浏览器）的服务器。</p>
<figure>
    <img src="https://www.freecodecamp.org/news/content/images/2020/03/image-22.png" width="600" height="400" alt="image-22" loading="lazy">
    <figcaption>来自：<a href="https://teoriadeisegnali.it/appint/html/altro/bgnet/clientserver.html#figure2">https://teoriadeisegnali.it/appint/html/altro/bgnet/clientserver.html#figure2</a></figcaption>
</figure>
<p>如你所见，客户端-服务器关系是双向的。所以一个东西可以同时是客户端和服务器。如果有一个接收请求的中间人服务器，它将请求发给另一个服务，然后转发这个服务给源客户端的响应，那么它就是一个代理服务器。</p>
<p>后面我们将把客户端称作客户端，把服务器称作服务器，把代理称作客户端和服务器之间的东西。</p>
<p>所以当客户端通过代理给服务器发送请求时，代理有时会对服务器掩盖客户端的身份，请求中携带的 IP 地址可能是代理的而不是源客户端的。</p>
<p>对于你们当中那些想要访问网站或下载受限资源（例如从种子网络或被所在国家禁止的网站）的人来说，你们可以识别出这个模式——它就是建立 VPN 的原理。</p>
<p>在我们继续深入之前，我想先说一下：术语代理在使用时通常指的是“转发”代理。转发代理就是在客户与服务器通信过程中代表（或代替）客户端的代理。</p>
<p>这与反向代理不同，反向代理代表的是服务器。图上它看起来和转发代理一样是位于客户端和服务器之间的代理，数据流与客户端&lt;-&gt;代理&lt;-&gt;服务器一样。</p>
<p>二者关键的区别是：反向代理是设计用来代替服务器的。通常客户端根本不知道网络请求是通过代理路由并且是代理将其传递给内部服务器的（对服务器的响应也是这么做的）。</p>
<p>所以，在转发代理中，服务器将不会知道客户端的请求和响应经过了代理。而在反向代理中，客户端经不会知道请求和响应都是经过代理路由的。</p>
<p>代理有点偷偷摸摸的 :)</p>
<p>但是在系统设计中，尤其是对复杂系统来说，代理很有用，反向代理特别有用。你的反向代理可以委派大量你不希望在主服务器处理的任务，它可以是一个网关、一个筛选器、一个负载均衡器和全能助手。</p>
<p>所以代理非常有用，但你可能并不知道为何如此。再一次，如果你读过我的其它材料，你就会知道我坚信：你只有在知道某些东西 <em>为何</em> 存在之后，才能对它们有正确的认识——只知道它们做 <em>什么</em> 并不够。</p>
<h2 id="section6">第六节：负载均衡</h2>
<p>如果你考虑一下负载（load）和均衡（balance）这两个词，你就会开始对它们在计算机世界中所做的事情有了一个直觉。当服务器同时收到大量请求时，它可能会慢下来（吞吐量下降、延迟上升）。达到一定程度之后，服务器甚至会宕机（丧失可用性）。</p>
<p>你可以赋予服务器更多的能力（纵向伸缩），也可以添加更多的服务器（横向伸缩）。但是现在你要解决的是如何将进来的请求分布到不同的服务器上去——哪些请求被路由到哪些服务器以及如何保证这些服务器也不会过载？换句话说，你如何均衡并分配请求负载？</p>
<p>我们现在进入负载均衡器（load balancer）部分。由于这篇文章是一篇对原则和概念的介绍，所以不可避免地，它们都是非常简单的解释。负载均衡器的任务是位于客户端和服务器之间（但是它们也可以被放在其它的地方）并负责将传入请求分发到多个服务器上去，从而不断给终端用户（客户端）带来快速、流畅和可靠的体验。</p>
<p>所以负载均衡器就像指挥交通的交管人员一样。它们这样做是为了维持<a href="#section2">可用性</a>和<a href="#section3">吞吐量</a>。</p>
<p>在理解负载均衡器被放置到系统架构中的哪个位置时，你可以看到负载均衡器可以被看作 <a href="#section5">反向代理</a>。但是负载均衡器也可以被插入到其它地方——其它有着信息交换的部件之间，例如你的服务器和数据库之间。</p>
<h3 id="">均衡操作——服务端选择策略</h3>
<p>所以负载均衡器是如何决定如何路由和分配请求流量的呢？首先，你每新增一台服务器，就需要让负载均衡器知道又多了一台可以将流量路由过去的候选服务器。</p>
<p>如果你去掉了一台服务器，负载均衡器也需要知道。配置信息确保负载均衡器知道它的访问列表中有多少台服务器以及哪些服务器是可用的。持续通知负载均衡器每台服务器的负载级别、状态、可用性、当前任务等信息也是可能的。</p>
<p>将负载均衡器配置为知道可以重定向到哪些服务器后，我们需要制定最佳路由策略，以确保请求被适当地分配到可用服务器之间。</p>
<p>一种简单的方法就是：负载均衡器随机选择一个服务器，然后将每个传入请求转发过去。但是可想而知，随机化会导致负载的“不均衡”分配，一些服务器获得的负载比其它服务器多，这可能会给整个系统的性能带来负面影响。</p>
<h3 id="">轮询和加权轮询</h3>
<p>另一种可以被直观理解的方法叫做“轮询（round robin）”。这是很多人处理循环处理列表的方式。你从列表中的第一个元素开始，依次向下移动，在抵达最后一个元素之后，回到顶点，再次向下处理这个列表。</p>
<p>负载均衡器也可以这么做，它只需要以固定的顺序循环迭代所有的可用服务器。这种负载方式以一种易于理解和可预测的模式完成了多个服务器之间负载的均匀分发。</p>
<p>通过给一些服务“加权”，你可以轮询会变得更加“花哨”。在标准的轮询中，每台服务器都被赋予了相等的权重（所有的服务器的权重都为 1）。但是当你对服务器分开加权后，就能让一些服务器有着更低的权重（比如 0.5，如果它们更弱的话），而另一些服务器会有更高的权重，比如 0.7 或 0.9 甚至 1。</p>
<p>然后整个流量将会根据权重的比例被划分并根据服务器对大量请求的处理能力呈比例分配。</p>
<h3 id="">基于负载的服务器选择</h3>
<p>更加复杂的负载均衡器可以根据当前的容量、性能和列表中的服务器负载进行决策，根据当前负载并计算出如何才能达到最高吞吐量、最低延迟等，然后动态分配负载。负载均衡器监控每台服务器的性能，据此决定哪些服务器不能处理新的请求。</p>
<h3 id="ip">基于 IP 哈希的选择</h3>
<p>你可以配置负载均衡器对传入请求的 IP 地址进行<a href="https://www.cs.cmu.edu/~adamchik/15-121/lectures/Hashing/hashing.html">哈希</a>，然后使用哈希值决定将请求转发到哪台服务器。如果我有五台可用的服务器，那么哈希函数就会被设计返回五个哈希值中的一个，所以肯定会有一台服务器被指派去处理这个请求。</p>
<p>如果你想让来自某个国家或地区的请求从该地区中最能满足需求的服务器获取数据，或者你的服务器通过缓存请求来实现更快的处理，基于 IP 哈希的路由策略会非常有用。</p>
<p>在后一种场景中，你可能想确保请求去往那台之前已经缓存相同请求的服务器，因为这会提高处理和响应请求的速度与性能。</p>
<p>如果你的每台服务器都维护着独立的缓存，负载均衡器也不总是将相同的请求转发到同一台服务器，结果可能就是服务器重复做着之前已经做过的工作，因为之前的请求去了另一台服务器，而你又不能利用那些缓存数据进行优化。</p>
<h3 id="">基于路径或服务的选择</h3>
<p>你也可以让负载均衡器根据请求的“路径”或功能或被提供的服务路由请求。例如，如果你正从一家网上花店买花，加载“Bouquets on Special”的请求可能被发送到一台服务器，而信用卡支付的请求可能被发送到另一台服务器。</p>
<p>如果二十位访问者中实际上只有一位买了花，那么你可以让一个较小的服务器处理支付，让一个更大的服务器处理所有的浏览流量。</p>
<h3 id="">混合包</h3>
<p>和其它东西一样，你可以达到更高和更细层次的复杂度。你可以有多个负载均衡器，它们各自有着不同的服务器选择策略！此外，如果你的系统是一个非常大的高流量系统，你或许需要 <em>负载均衡器的负载均衡器……</em></p>
<p>最终，你需要不断地往系统中添加组件，直到系统的性能适应了你的需求（你的需求可能看起来平缓，或者随时间缓慢上升，或者容易出现峰值）。</p>
<p>我们已经讲了 VPN（用于转发代理）和负载均衡（用于反向代理），但是 <a href="https://www.quora.com/What-are-the-different-uses-of-proxy-servers">这里</a> 还有更多的例子。</p>
<h2 id="section7">第七节：一致性哈希</h2>
<p>负载均衡中的哈希是我们要理解的稍微有点棘手的概念之一，因此它有了自己单独的一节。</p>
<p>为了理解它，请首先了解 <a href="https://www.cs.cmu.edu/~adamchik/15-121/lectures/Hashing/hashing.html">哈希在概念上是如何工作的</a>。简单来说，哈希将输入转换成一个固定长度的值，通常是一个整数（哈希值）。</p>
<p>对于一个好的哈希算法或函数而言，一个关键的原则是函数本身必须是<a href="https://en.wikipedia.org/wiki/Hash_function#Deterministic">可确定的</a>，也就是相同的输入在传入函数之后会得到相同的输出。所以，可确定性（determinstic）表示：如果我传入了一个字符串“Code”（大小写敏感）然后函数生成的哈希值为 11002，那么每次我传入“Code”时，它就必须生成整数“11002”。如果我传入“code”，它就会生成另一个不同的数字（这个数字也是不变的）。</p>
<p>有时，哈希函数可能会为多个输入生成相同的哈希值——不要担心，我们还有应对方式。实际上，唯一输入的范围越大，出现这种情况的可能性就越大。但是当不只一个输入生成相同的输出时，就产生了“冲突（collision）”。</p>
<p>牢记这一点，让我们把它用到路由和将请求转发到服务器的过程中。假设你有五台用于分配负载的服务器，一种容易理解的方法就是对传入请求（可能是 IP 地址，或者一些其它的客户端信息）进行哈希，为每个请求生成哈希值。然后你就可以对这个哈希值进行取模运算，运算的右操作数为服务器的数量。</p>
<p>例如，你的负载均衡器的伪代码可能长这样：</p>
<pre><code class="language-javascript">request#1 =&gt; hashes to 34
request#2 =&gt; hashes to 23
request#3 =&gt; hashes to 30
request#4 =&gt; hashes to 14

// 你有五台服务器 =&gt; [Server A, Server B, Server C, Server D, Server E]
// 所以对每个请求的哈希值进行模 5 运算……
request#1 =&gt; hashes to 34 =&gt; 34 % 5 = 4 =&gt; send this request to servers[4] =&gt; Server E
request#2 =&gt; hashes to 23 =&gt; 23 % 5 = 3 =&gt; send this request to servers[3] =&gt; Server D
request#3 =&gt; hashes to 30 =&gt; 30 % 5 = 0 =&gt; send this request to  servers[0] =&gt; Server A

</code></pre>
<p>如你所见，哈希函数生成了大量可能的值，取模运算符把这个值的范围变小了，刚好映射到了服务器的数量。</p>
<p>你肯定会遇到不同的请求被映射到同一台服务器，这还好，只要在所有服务器上的所有分配是“<a href="https://en.wikipedia.org/wiki/Hash_function#Uniformity">一致的</a>”就行。</p>
<h3 id="">添加服务器，处理故障服务器</h3>
<p>所以，如果我们向其发送流量的服务器挂了会怎样呢？哈希函数（我们上面的那一段伪代码）仍然认为还有五台服务器，取模运算符会生成 0-4 这个范围的数字。但是由于一台服务器挂了，我们只有四台服务器了，可我们还是向那台挂了的服务器发送流量。糟糕。</p>
<p>相反，我们可以加入第六台服务器，但它 <em>永远</em> 都不会收到任何流量，因为我们的模操作数为 5，它永远也不会产生一个将第六台服务器包括进来的数字。更糟糕了。</p>
<pre><code>// 添加第六台服务器
servers =&gt; [Server A, Server B ,Server C ,Server D ,Server E, Server F]
// 将模操作数改成 6
request#1 =&gt; hashes to 34 =&gt; 34 % 6 = 4 =&gt; send this request to servers[4] =&gt; Server E
request#2 =&gt; hashes to 23 =&gt; 23 % 6 = 5 =&gt; send this request to servers[5] =&gt; Server F
request#3 =&gt; hashes to 30 =&gt; 30 % 6 = 0 =&gt; send this request to  servers[0] =&gt; Server A

</code></pre>
<p>我们发现服务器数字在取模后变了（尽管，在这个示例中，request#1 和 request#3 <em>没有改变</em>——但这只是一个特例）。</p>
<p>于是，现在总共有一半的请求（其它示例中可能会更多！）被路由到了新的服务器，我们因此失去了之前缓存在服务器上的数据的所带来的好处。</p>
<p>例如，request#4 之前去往 Server E，但是现在去了 Server C。Server E 中所有与 request#4 有关的缓存数据都没用了，因为现在请求都去了 Server C。你可以为一台宕机的服务器计算一个类似的问题，但是取模函数仍然给它发送请求。</p>
<p>在这个微型系统中，它看似没啥影响。但在一个超大规模的系统中，这就是一个糟糕的结果。系统设计失败。</p>
<p>显然，一个简单地通过哈希分配请求的系统不能很好地处理错误。</p>
<h3 id="">流行的解决方案——一致性哈希</h3>
<p>不幸的是，我觉得用文字并不足以描述这一部分。一致性哈希（consistent hashing）最好是通过可视化进行理解。但是到目前为止，本篇文章的目的是给你关于该问题的直观想法，为什么会出现这个问题，以及常规解决方案可能的不足。牢记这一点。</p>
<p>正如我们讨论的，普通哈希的关键问题在于：当 (A) 一个服务器宕机了，仍然有流量被路由到它，并且 (B) 你添加了一台新的服务器，分配方式本质上已经变了，因此你失去了之前的缓存带来的好处。</p>
<p>在深入一致性哈希时，有两个重要的点需要牢记：</p>
<ol>
<li>一致性哈希 <em>没有消除问题</em>，尤其是 B。但是它确实减少了很多问题。一开始你可能想知道为什么一致性哈希那么重要，底层的缺点依然存在，确实如此，但是它们变少了，并且一致性哈希本身也是对大规模系统的一个有效改进。</li>
<li>一致性哈希对传入请求 <em>和服务器</em> 进行哈希操作，哈希结果因此陷入一系列（连续）的值。这个细节非常重要。</li>
</ol>
<p>请在观看下面推荐的解释一致性哈希的视频时牢记这一点，否则它的效果就没那么明显了。</p>
<p>我强烈推荐这个视频，它融入了这些原则，却没有过多烦扰你的细节。</p>
<figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.25%;" class="fluid-width-video-wrapper">
            <iframe width="560" height="315" src="https://www.youtube.com/embed/tHEyzVbl4bg" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" name="fitvid0"></iframe>
          </div>
        </div>
      </figure>
<center>一个关于一致性哈希的简单介绍，来自 Hannah Barton</center>
<p>如果你在理解消化为什么这个策略在负载均衡中很重要时遇到了任何问题，我建议你先休息一下，然后回到 <a href="#section6">负载均衡一节</a>，再次阅读。除非你在工作中遇到过这个问题，否则所有的这些都会变得非常抽象，这并不罕见。</p>
<h2 id="section8">第八节：数据库</h2>
<p>我们简要地<a href="#section2">考虑了</a>为满足许多不同使用场景设计的不同类型的存储方案（数据库），并且其中的一些和其它的相比，更适用于特定的任务。站在一个非常高的视角，数据库可以被分类成两种类型：关系型（Relational）和非关系型（Non-Relational）。</p>
<h3 id="">关系型数据库</h3>
<p><strong><a href="https://en.wikipedia.org/wiki/Relational_database">关系型数据库</a></strong> 是一种严格执行数据库中所存事物之间关系的数据库。这些关系通常可能会要求数据库将每个东西（称为“实体”）表示成一个结构化表，表中有零行或多行（“记录”，“条目”）和一列或多列（“属性”，“字段”）。</p>
<p>通过在实体上强制推行这种结构，我们可以确保每个数据项/条目/记录都具有正确的数据。它带来了更好的一致性，以及形成实体间更精密关系的能力。</p>
<p>你可以从下面记录“Baby”（实体）数据的表中看到这个结构。表中的每条记录（“条目”）都有四个字段，它们表示与那个宝宝有关的数据。这是一个经典的关系型数据库结构（也是一个被称为<a href="https://en.wikipedia.org/wiki/Database_schema">模式</a>的规范实体结构）。</p>
<figure>
    <img src="https://www.freecodecamp.org/news/content/images/2020/03/image-46.png" width="600" height="400" alt="image-46" loading="lazy">
    <figcaption style="text-align: center">
        来自：<a href="https://web.stanford.edu/class/cs101/table-1-data.html">https://web.stanford.edu/class/cs101/table-1-data.html</a>
    </figcaption>

<p>所以理解关系型数据库的关键在于它们是高度结构化的，并且将结构强加给所有的实体。通过确保添加到表的数据符合该结构来强制实施此结构。添加一个模式不允许的高度字段到表将不被准许。</p>
<p>大多数关系型数据库都支持数据库查询语言，叫做 SQL——<a href="https://en.wikipedia.org/wiki/SQL">结构化查询语言</a>。这是一门专为与结构化（关系型）数据库内容交互设计的语言。这两个概念的联系非常紧密，以至于很多人通常将关系型数据库称作“SQL 数据库”（有时发音为“sequel”数据库）。</p>
<p>通常认为，SQL（关系型）数据库比非关系型数据库支持更加复杂的查询（组合不同的字段、过滤器和条件）。数据库本身处理这些查询，然后返回满足的结果。</p>
<p>很多 SQL 数据库迷都说，如果没有这个功能（指复杂查询）的话，你将不得不先取出 <em>所有的</em> 数据，让服务器或客户端把数据加载进 “<a href="#section2">内存</a>”，然后应用过滤条件——这在小数据集上还好，但是对于有着成千上万的记录和行的大型复杂数据集来说，就会对性能带来非常严重的影响。然而，并不总是这样，我们将会在学习 NoSQL 数据库时看到原因。</p>
<p>一个常见而又倍受喜爱的关系型数据库是 <a href="https://en.wikipedia.org/wiki/PostgreSQL">PostgreSQL</a>（通常称为“Postgres”）数据库。</p>
<h3 id="acid">ACID</h3>
<p>ACID 事务描述的是良好关系型数据库都将支持的事务的一组特征。<a href="https://en.wikipedia.org/wiki/ACID">ACID = "Atomic, Consistent, Isolation, Durable"</a>。事务是与数据库的交互，通常是读或写操作。</p>
<p><strong>原子性（Atomicity）</strong> 要求：当单个事务包含到超过一个操作时，数据库必须保证如果有一个操作失败，<em>整个</em> 事务（所有的操作）也会失败。它就是“要么全部，要么一个也不”。这样，如果事务成功了，那么你会知道所有的子操作都成功了，如果某个操作失败了，那么你会知道所有同它一起的操作都失败了。</p>
<p>例如，如果一个事务包括从两个表中读数据并往三个表写数据，那么如果它们中的任意一个操作失败了，整个事务就失败了。这意味着一个独立的操作都不应该完成。你甚至不希望三个写操作中的一个成功——这会“弄脏”数据库内的数据！</p>
<p><strong>一致性（Consistency）</strong> 要求：数据中的每个事务都符合数据库定义的规则，当数据库改变状态（一些信息改变了）时，这个改变是有效的，不会损坏数据。每个事务都将数据库从一个 <em>有效的</em> 状态移动到另一个 <em>有效的</em> 状态。可以这么看待一致性：每个“读”操作都收到最近一次“写”操作的结果。</p>
<p><strong>隔离性（Isolation）</strong> 表示你可以在数据库“并发（concurrently）”（同时）运行多个事务，但是数据库最终的状态看起来就像每个操作串行（按顺序，就像一个操作队列一样）运行结束后一样。我个人认为“隔离性”对概念的描述性不是非常强，但是我猜 ACCD 比 ACID 说起来更难……</p>
<p><strong>持久性（Durability）</strong> 保证：一旦数据被保存到数据库中，就会一直在那里。它将会是“<a href="#section2">持久的</a>”——保存在硬盘，而不是“内存”中。</p>
<h3 id="">非关系型数据库</h3>
<p>相反，<strong>非关系型数据库</strong> 就没有那么死板了，或者，换句话说，它的数据有着更加灵活的结构。数据通常以“键-值”对的形式呈现。这种方式的一个简化形式就是一个由“键-值”对对象构成的数组（列表），例如：</p>
<pre><code class="language-javascript">// baby names
[
	{ 
    	name: "Jacob",
        rank: ##,
        gender: "M",
        year: ####
    },
    { 
    	name: "Isabella",
        rank: ##,
        gender: "F",
        year: ####
    },
    {
      //...
    },
    
    // ...
]
</code></pre>
<p>非关系型数据库也被称为“NoSQL”数据库，当你不想或不需要结构一致的数据时，它能给你提供便利。</p>
<p>和 ACID 性质类似，NoSQL 数据库的性质有时候被称为 BASE：</p>
<p>**基本可用（Basically Abailable）**规定系统保证可用性</p>
<p><strong>软状态（Soft State）</strong> 表示系统的状态可能随时间改变，即使没有输入也是如此</p>
<p><strong>最终一致性（Eventual Consistency）</strong> 规定系统在一段（短暂）的时间间隔之后会达到一致的状态，除非收到了其它的输入。</p>
<p>由于这些数据库的内核都采用类哈希表结构保存数据，所以它们会非常快、简单、易用，完美适用于缓存、环境变量、配置文件和会话状态等使用场景。这种灵活性使得它们在内存中（比如 <a href="https://en.wikipedia.org/wiki/Memcached">Memcached</a>）和持久化存储（比如 <a href="https://en.wikipedia.org/wiki/Amazon_DynamoDB">DynamoDb</a>）中的使用非常完美。</p>
<p>还有很多“类 JSON” 的数据库，它们被称为文档数据库，比如倍受喜爱的 <a href="https://www.mongodb.com/document-databases">MongoDb</a>，这些数据库的内核也采用“键-值”存储。</p>
<h3 id="">数据库索引</h3>
<p>这是一个复杂的话题，所以为了给你一个关于系统设计面试需要哪些知识的高度概述，我将会简单地给出冰山一角。</p>
<p>想象一个有一亿行数据的数据库表，这个表主要被用来查找每条记录的一个或者两个值。为了得到特定行的值，你需要迭代整个表，如果它是非常靠后的记录，那将会花费很长时间！</p>
<p>索引（indexing）是记录的一种快捷方式，它在匹配值时比逐行检查的更加高效。索引通常是一种被添加到数据库的数据结构，它专为促进数据库内 <em>特定</em> 属性（字段）上的快速搜索而设计。</p>
<p>所以，如果人口统计局有一亿两千万条具有名字和年龄的记录，而你最常需要的就是检索属于某个年龄组的人员列表，那么就可以在该数据库的年龄属性上建立索引。</p>
<p>索引是关系型数据库的核心，在非关系型数据库中也广泛使用。因此，从理论上讲，索引的好处可用于两种类型的数据库，这对优化查找时间非常有利。</p>
<h3 id="">复制和分片</h3>
<p>虽然这些听起来像是一部生物恐怖电影中的东西，但你更有可能每天都在数据库扩展中听到它们。</p>
<p>复制（replication）是指复制（duplicate, make copies of, replicate）你的数据库。你或许记得我们在<a href="#section3">可用性</a>一节讨论过它。</p>
<p>我们已经考虑了在系统内进行冗余以维持高可用带来的好处。复制在单个数据库宕机时保证数据库的冗余。但是它也引发了如何在副本中同步数据的问题，因为它们要有相同的数据。数据库写操作和更新操作的复制可以同步进行（主数据库发生改变的同时），也可以异步进行。</p>
<p>主库与从库之间同步数据的可接受时间间隔取决于具体的需求——如果你确实需要两个数据库之间的状态是一致的，那么复制操作必须非常快。你也想确保从库上的写操作失败时，主库上的写操作也失败（原子性）。</p>
<p>但是，当你拥有如此多的数据，以至于简单地进行复制可能会解决可用性问题，但不能解决吞吐量和延迟问题（速度）时，你会怎么做？</p>
<p>这个时候你可能会考虑对你的数据进行“分类”，形成“分片（shard）”。一些人也管这个叫数据划分（partitioning），它与硬盘分区完全不同。</p>
<p>数据分片将大数据库拆分为较小的数据库。你可以根据数据结构决定如何对数据进行分片。分片可以像每五百万行一个分片这么简单，也可以采用最适用于当前数据、需要和服务位置的其它策略。</p>
<h2 id="section9">第九节：领导选举</h2>
<p>让我们再次回到服务器，讨论一个稍微高级点的话题。我们已经了解了 <a href="#section3">可用性</a>的原则，以及冗余是如何作为增加可用性的一种手段的。在处理到冗余服务器集群的<a href="#section6">请求路由</a>时，我们还介绍了一些实践中的注意事项。</p>
<p>但是有时候，在这种设置中，多个服务器中的事情没多大差别，这可能会出现只需要一台服务器带头的情况。</p>
<p>例如，你想确保只有一台服务器负责更新某些第三方 API，因为来自不同服务器的多次更新可能导致问题，或者增加第三方的成本。</p>
<p>在这种情况下，你需要选择一台主服务器，然后将更新职责委派给它。这个过程被称为 <a href="https://en.wikipedia.org/wiki/Leader_election">领导选举</a>。</p>
<p>当一个集群中有多台服务器提供冗余时，可以配置它们之间有且只有一个领导。它们也会在检测到领导服务器宕机时，指定另一台服务器顶替领导服务器的位置。</p>
<p>原理非常简单，但细节才是最让人头疼的。真正棘手的部分在于：确保服务器之间保持数据、状态和操作的同步。</p>
<p>例如，始终存在某些事故导致一台或两台服务器断开与其他服务器之间的连接的风险。在这种情况下，工程师们最终会使用一些区块链中的基本理念让服务器集群达成共识。</p>
<p>换句话说，<a href="https://en.wikipedia.org/wiki/Consensus_algorithm">共识算法</a> 用于告知所有服务器一个“达成一致的”值，它们都可以在识别领导服务器的逻辑中依赖这个值。</p>
<p>领导选举通常使用像 <a href="https://etcd.io/">etcd</a> 这样的软件实现，它是一个键-值对存储，通过使用领导选举本身和共识算法，它同时提供了可用性 <em>和</em> 强一致性（这很有价值，也是不寻常的组合）。</p>
<p>所以工程师们可以依赖 etcd 自己的领导选举架构，在他们的系统中进行领导选举。这是通过在像 etcd 这样的服务中存储一个表示当前领导的键-指对来完成的。</p>
<p>因为 etcd 是高可用 <em>和</em> 强一致性的，所以你总是可以在自己的系统中依赖那个键-值对，它包含集群中关于当前领导是哪台服务器的最终“事实来源”。</p>
<h2 id="section10">第十节：轮询、流、套接字</h2>
<p>在这个不断更新、推送通知、流内容和实时数据的现代时代，掌握支撑这些技术的基本原理非常重要。要定期或立即更新应用中的数据，你需要使用以下两种方法中的一种。</p>
<h3 id="">轮询</h3>
<p>这种方法很简单。如果查看 <a href="https://en.wikipedia.org/wiki/Polling_(computer_science)">维基百科词条</a>，你会发现它讲的有点多。所以还是看一看词典中是如何解释它吧，尤其是计算机科学背景下的含义。牢记这个简单的基础。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/Screen-Shot-2020-03-14-at-10.25.44-am.png" alt="Screen-Shot-2020-03-14-at-10.25.44-am" width="600" height="400" loading="lazy"></p>
<p>轮询（polling）就是简单地让客户端检查服务器，客户端发送一个网络请求请求更新的数据。这些请求通常按照固定的时间间隔发出，比如五秒、十五秒、一分钟或用例要求的任何时间间隔。</p>
<p>每几秒钟进行一次轮询还是和实时大有不同，还以下缺点，特别是你的并发用户在一百万以上的时候：</p>
<ul>
<li>几乎不变的网络请求（不利于客户端）</li>
<li>几乎不变的入站请求（不利于服务端负载——每秒超过一百万次请求！）</li>
</ul>
<p>所以快速轮询并不是真的很高效，轮询最好是用在数据更新中的小差别对应用来说不是问题的场景中。</p>
<p>例如，如果你克隆了 Uber，你或许会让司机端应用每五秒发送一次司机的位置数据，让乘客端应用每五秒轮询一次司机的位置。</p>
<h3 id="">流</h3>
<p>流（streaming）解决了不断轮询的问题。如果有必要不断访问服务器，最好使用像 <a href="https://en.wikipedia.org/wiki/WebSocket">web-socket</a> 这样的东西。</p>
<p>这是一个旨在 TCP 上工作的网络通信协议。它在客户端和服务器之间打开一个双向的专用通道（套接字），有点像在两个端点之间的开放热线。</p>
<p>与通常的 TCP/IP 通信不同，这些套接字是“长久的（long-lived）”，以便它向服务器发出的单个请求打开了进行双向数据传输的热线，而不是采用多个独立的请求。我们使用长久一词表示机器之间的套接字连接将会一致持续到连接的一端关闭它，或者网络故障。</p>
<p>你可能还记得我们对 IP、TCP 和 HTTP 的讨论，这些操作在每个请求-响应环中都发送数据的“数据包”。Web-socket 意味着只有一个请求-响应交互（不是你认为的环！），它打开了一个通道，通道两端数据都以“流”的形式发送。</p>
<p>轮询与所有“常规”的基于 IP 的通信的一大区别就是：轮询让客户端定时给服务器发送获取数据的请求（“拉取”数据），而在流中，客户端“随时等待”着服务器将一些数据“推送”给它。服务器会在数据改变时将其发出，并且客户端总是在监听它。因此，如果数据不断变化，那么它就成为了一个“流”，这可能更加符合用户的需求。</p>
<p>例如，在使用 <a href="https://www.freecodecamp.org/news/p/51a1d601-c57e-48cf-8f8d-9bb1c333d64d/repl.it">协作式编码 IDE</a> 时，只要有用户输入了东西，它就可以立马被其它人看到，这就是通过 web-socket 实现的，因为你想要的是实时协作。如果我输入的内容在你输入同样的内容之后才在你的屏幕上显示，或者你等了三分钟才知道我干了啥，那就太糟糕了！</p>
<p>或者想一下多人在线游戏——那是在玩家之间的流式传输游戏数据的绝佳案例！</p>
<p>总之，使用场景决定选择轮询还是流。通常，如果你希望数据是“实时的”，就用流；如果滞后（十五秒也是滞后）对你来说是可以的，那么轮询也是一个不错的选择。但是这完全取决于你有多少并发用户，以及他们是否期望数据是实时的。流式服务的一个常见示例就是 <a href="https://en.wikipedia.org/wiki/Apache_Kafka">Apache Kafka</a>。</p>
<h2 id="section11">第十一节：端点保护</h2>
<p>当你构建大规模系统时，保护系统免受过多操作（使用系统时实际上不需要这些操作）就变得很重要了。现在听起来还是非常抽象。但是想一下这个——你有多少次疯狂点击着一个按钮，以为它将会使系统更快？想象一下如果每次点击都 ping 一次服务器，服务器也尝试全部处理它们！如果系统的吞吐量由于某些原因很低（比如服务器正在异常负载下挣扎），那么每次点击都会使系统变得更慢，因为系统必须处理所有的点击！</p>
<p>有时它甚至与保护系统无关。有时你想要限制操作次数，因为那是你提供服务的一部分。例如，你使用了第三方 API 服务的免费套餐，他们只允许你每三十分钟进行二十次请求。如果你在三个分钟内进行了二十一次或三百次请求，那么在前二十次请求后，服务器将不再处理你的请求。</p>
<p>这被称为限流（rate-limiting）。服务器可以通过限流手段限制客户端给定时间段内尝试进行的操作数量。限流可以根据用户计算，也可以根据请求、次数、负载或任何其它东西。通常，一旦某个时间段内的操作超出了限制，服务器会在剩余的时间内返回错误。</p>
<p>好了，现在你可能认为端点“保护”有些夸张。你只是在限制用户从端点获取某些东西的能力。的确如此，但是它面对恶意用户（客户端）时也能提供保护——就像一个机器人正在攻击你的端点一样。为什么会发生那种事情呢？因为用超过服务器处理能力的大量请求淹没服务器是一种策略，这是恶意攻击者让服务器宕机的一种方式，这种方式也能有效击垮服务。它正是 <a href="https://en.wikipedia.org/wiki/Denial-of-service_attack">拒绝服务（DoS，Denial of Service）攻击</a>。</p>
<p>虽然限流可以抵御 DoS 攻击，但是它本身并不能把你从高级版本的 DoS 攻击——<em>分布式</em> DoS 中解救出来。这里的分布式只是简单地表示攻击来自多个看似毫不相关的客户端，也没有识别它们是被单个恶意代理控制的有效方式。需要采取其它方式来防止这种协作型分布式攻击。</p>
<p>但是无论如何，对于像我在上面提到的 API 限制这些没那么恐怖的场景而言，限流是一种有用且流行的方式。考虑到限流的工作原理，由于服务器必须先检查限制条件并在必要时强制实行，所以你需要考虑使用何种数据结构和数据库才能使这个检查超级快，以便允许范围内的请求不会拖慢处理该请求的处理速度。另外，如果你将其存储在服务器本身的内存中，那么你就需要保证所有来自给定客户端的请求都会抵达服务器，以便它可以正确地进行限制。为了处理像这样的情形，使用位于服务器之外的独立 <a href="https://en.wikipedia.org/wiki/Redis">Redis 服务</a>非常流行，但是他们将用户详情保存在内存中，这能快速决定用户是否在允许的范围内。</p>
<p>限流可以和你想要的规则一样复杂，但是本节应该涵盖的是基础知识和最常见的用例。</p>
<h2 id="section12">第十二节：消息与发布-订阅</h2>
<p>当你设计和构建大规模 <a href="https://blog.stackpath.com/distributed-system/">分布式系统</a>时，为了让系统可以一起流畅工作，进行系统组件与服务之间信息交换是很重要的。但是如我之前所讲的那样，依赖网络的系统遭受着和网络一样的弱点——它们可经不起折腾。网络故障经常发生。当网络故障时，系统中的组件就无法进行通信，系统可能会降级（最好的情况）或者跟着发生故障（最差的情况）。所以分布式系统需要健壮的机制来确保通信持续进行，或者从停止点恢复，即使系统中的组件之间存在“任意分区”（即故障）也能做到。</p>
<p>举个例子，想象你正预定飞机票。你得到了一个好价钱，选座，确认订票，甚至已经用信用卡付完款。现在你正等待票证的 PDF 被发到你的收件箱。你等呀等，却一直等不到它的出现。系统中的某个地方出现了无法处理或不能正确恢复的故障。订票系统通常会联系航空公司，访问价格 API，处理实际的航班选择——费用汇总、航班的日期和时间等。当你点击网站的预定界面时，一切都会办理妥当。但是，它不必在几分钟后将票证的 PDF 发送给你。相反，界面可以简单地对你的预定进行完成确认，你可以期待票证稍后出现在收件箱中。对订票来说，这是合理也很常规用户体验，因为支付和票证收据不必同时进行——这两个事件是异步的。这样的系统需要发送消息，确认生成 PDF 的异步服务（服务端点）收到已确认的预定付款和所有详情的通知，然后 PDF 可以被自动生成并发送给你。但是如果这个消息系统出现故障，邮件服务永远都不会知道你的预定信息，自然也就不会生成票证了。</p>
<p><strong>发布者/订阅者消息传递</strong></p>
<p>这是一个非常流行的消息范式（模型），关键概念在于：发布者“发布”消息，订阅者订阅消息。为了提供更好的粒度，消息可以属于某个“主题（topic）”，它就像是一个目录。这些主题就像专用的“频道（channel）”或管道一样，每个管道只处理属于特定主题的消息。订阅者选取想要订阅的主题，然后从中获取消息通知。这个系统的优势就是发布者与订阅者之间是完全解耦的——它们不需要了解彼此。发布者播报，订阅者监听其正在寻找的主题的通知。</p>
<p>服务器通常是消息的发布者，它们通常会发布好几种主题（频道）。特定主题的消费者订阅对那些主题进行订阅。服务器（发布者）与订阅者（可以是另一个服务器）之间没有直接的通信。交互只在发布者与主题之间，主题与订阅者之间发生。</p>
<p>主题中的消息只不过是需要通信的数据，可以呈现为任何你需要的形式。这样一来，你的发布/订阅中的就有四个参与者：发布者、订阅者、主题和消息。</p>
<h3 id="">比数据库更好</h3>
<p>所以为什么要在这上面费心呢？为什么不把所有的数据都持久化到数据库，然后直接从数据库消费呢？你需要一个对消息进行排队的系统，因为每个消息都对应着任务，这个任务需要该消息的数据才能完成。所以在我们订票的例子中，如果三十五分钟内有一百个人订票，将所有这些都放到数据库并不能解决给这一百个人发邮件的问题。它只不过是保存一百个事务。发布/订阅（Pub/Sub ）系统处理通信、对任务进行串行排序，并且将消息持久化到数据库。所以这个系统可以提供很多有用的特性，如“至少一次”交付（消息将不会丢失）、持久化存储、消息排序、“重试”、消息的“重放”，等等。若没有这个系统，只是将消息存储在数据库中并不会帮你确保消息被交付（被消费）并成功完成任务。</p>
<p>有时同一个消息可能会不只一次被一个订阅者消费——通常是因为瞬时网络故障，尽管订阅者消费了消息，但是它没有让发布者知道这一点。所以发布者会把消息再次重发给订阅者，这就是为什么保证“至少一次”而不是“有且只有一次”。多次消费的问题在分布式系统中是不可避免的，因为网络天生就是不可靠的。这会让情况变得更复杂，消息在订阅者一侧触发一个操作，那个操作可以改变数据库内的东西（改变整个应用的状态）。如果单个操作被重复多次，并且每次应用的状态都改变了会怎样？</p>
<h3 id="">控制结果——一个或多个结果？</h3>
<p>这个新问题的解决方案被称为幂等性（idempotency）——这是一个很重要的概念，但是在你前几次检查它时没那么直观。这个概念看起来可能很复杂（特别是你读维基百科词条的时候），所以就当前的目的而言，这里是 <a href="https://stackoverflow.com/questions/1077412/what-is-an-idempotent-operation">来自 StackOverflow</a> 的一个更加友好的简化版本：</p>
<blockquote>
<p><em>在计算机中，幂等性指使用相同的参数多次调用某个操作的结果与调用一次的结果相同。</em></p>
</blockquote>
<p>所以当订阅者处理一个消息两次或三次时，应用的整体状态与消息 <em>第一次</em> 被处理后的状态完全相同。例如，如果在预定机票快要完成时，你输入了信用卡信息，由于系统真的太慢了，你点击了三次“立即支付”按钮……你总不想支付三倍的机票钱吧？你需要幂等性确保 <em>第一次</em> 点击后的每次点击都不会产生其它的购买行为，不会不止一次地从你的信用卡扣款。相反，你可以给你最要好的朋友的 newsfeed （译者注：<a href="https://en.wikipedia.org/wiki/News_Feed">newsfeed</a> 是 Facebook 的一个功能）发送 N 次完全相同的评论。它们都将显示为单独的评论，除了令人讨厌之外，它们并没有 <em>错</em>。另一个例子是在 Medium 文章上“鼓掌（clap）”——每次鼓掌都表示增加一次鼓掌的数量，而不是仅仅一次（译者注：<a href="https://help.medium.com/hc/en-us/articles/115011350967-Claps#">Claps</a> 是 Medium 的一个功能，允许你用它来表示对某篇文章的支持，类似于微信朋友圈的点赞）。后面的这两个例子并不要求幂等性，但是支付那个例子是需要的。</p>
<p>有很多不同的消息系统，系统的选取取决于要解决的用例。通常，人们会参考“基于事件的”架构，即系统依赖“事件（event）”（比如支付票证）消息处理操作（比如发送票证邮件）。最常讨论的消息服务有 Apache Kafka、RabbitMQ、Google Cloud Pub/Sub 和 AWS SNS/SQS。</p>
<h2 id="section13">第十三节：必备小知识</h2>
<h3 id="">日志</h3>
<p>随着时间的推移，你的系统会收集大量的数据。大多数数据都是极其有用的，让你了解系统的健康情况、性能和问题，给你提供有价值的见解，帮你了解谁在使用你的系统、如何使用的、多久使用一次、哪些部分被使用得更多或更少，等等。</p>
<p>这些数据对分析、性能优化和产品改进很有价值。它们还有着巨大的调试价值，不论在开发过程中打印到控制台时，还是在测试和生成环境中找出 bug 时，都非常有用。所以日志也帮助你追溯和审计。</p>
<p>记录日志时要记住的要诀就是将它看成一系列连续事件，这意味着数据变成时间序列数据，并且应该使用经过特别设计的工具和数据库来帮你处理此类数据。</p>
<h3 id="">监控</h3>
<p>这是日志之后的下一步。它回答了“我使用所有的日志数据干啥？”这个问题。你监控并分析它。你可以建立或使用解析此类数据的工具和服务，将数据呈现为仪表盘或图表，或其它人类可读的方式。</p>
<p>通过将数据保存到专为此类数据（时间序列数据）设计的数据库中，你可以插入其它基于该数据结构和意图构建的工具。</p>
<h3 id="">预警</h3>
<p>当你积极监控时，也应该安放另一个可以提醒你重大事件的系统。就像一个股票价格超过某个上限或低于某个阈值时出现的警告一样，如果你正在观察的某个指标过高或过低，也可能会发送警报。响应时间（延迟）或错误和故障都是设置警告的好东西，如果它们超过了“可接受”级别，就发送警告。</p>
<p>良好的日志和监控的关键就是确保你的数据随时间推移变得非常一致，因为使用不一致的数据可能导致字段缺失，进而破坏分析工具，或者减少日志带来的好处。</p>
<h2 id="">资源</h2>
<p>正如所承诺的，一些有用的资源如下：</p>
<ol>
<li>一个出色的 <a href="https://github.com/donnemartin/system-design-primer">Github 仓库</a>，充满了概念、图和预学习资料</li>
<li>Tushar Roy 的 <a href="https://www.youtube.com/watch?v=UzLMhqg3_Wc">系统设计介绍</a></li>
<li>Gaurav Sen 的 <a href="https://www.youtube.com/watch?v=quLrc3PbuIw&amp;list=PLMCXHnjXnTnvo6alSjVkgxV-VH6EPyvoX">YouTube 播放列表</a></li>
<li><a href="https://www.sitepoint.com/sql-vs-nosql-differences/">SQL vs NoSQL</a></li>
</ol>
<p>我希望你喜欢这份长篇指南！</p>
<p>你可以<a href="https://twitter.com/ZubinPratap">在 Twitter 上向我提问</a>。</p>
<p><strong>附言：献给 freeCodeCamp 学员</strong></p>
<p>我真的，真的完全相信你最宝贵的资源就是你的时间、精力和金钱。其中，最重要的资源是时间，因为其他两个都可以得到更新或恢复。所以，如果你在某些事情上面花了时间，确保它会让你离目标更进一步。</p>
<p>考虑到这一点，如果你想在我的身上投入三个小时，找出学习编码的最佳路径（特别是如果你像我一样转了行话），那么就去 <a href="https://matchfitmastery.weebly.com/">我的课程网站</a> 填写表单注册吧（不是弹出框！）。如果你在消息（Leave us a message *）中添加 “I LOVE CODE”，我就会知道你是一个 freeCodeCamp 读者，我将你给发送一个优惠码。因为 <a href="https://medium.freecodecamp.org/what-learning-to-code-actually-taught-me-a11fd850af0a">freeCodeCamp</a> 给我了一个良好的开始，就像它帮助你一样。</p>
<p><strong>此外</strong>：如果你想了解更多信息，可以查阅 <a href="http://podcast.freecodecamp.org/">freeCodeCamp 播客</a> 的 <a href="http://podcast.freecodecamp.org/53-zubin-pratap-from-lawyer-to-developer">第 53 集</a>，我在那里同 Quincy（FreeCodeCamp 的创始人）一起分享了我们转行的经验，这些经验可能会对你有所帮助。你也可以访问 <a href="https://itunes.apple.com/au/podcast/ep-53-zubin-pratap-from-lawyer-to-developer/id1313660749?i=1000431046274&amp;mt=2">iTunes</a>、<a href="https://www.stitcher.com/podcast/freecodecamp-podcast/e/59201373?autoplay=true">Stitcher</a> 和 <a href="https://open.spotify.com/episode/4lG0RGpzriG5vXRMgza05C">Spotify</a> 上的播客。</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/systems-design-for-interviews/">System Design Interview Questions – Concepts You Should Know</a>，作者：<a href="https://www.freecodecamp.org/news/author/zubin/">Zubin Pratap</a></p></figure> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 求职面试中的高频 Java 问题 ]]>
                </title>
                <description>
                    <![CDATA[ 大家好！在过去的几年里，我分享了很多 Java 面试题 [http://javarevisited.blogspot.sg/2015/10/133-java-interview-questions-answers-from-last-5-years.html]  和相关讨论。我的很多读者都要求我把它们整理在一起，以便他们能够在一个地方查看，因此这篇文章应运而生。 这篇文章包含了超过 50 个 Java 面试问题，涵盖了所有重要主题，例如 Java 基础知识、Java 集合框架 [https://javarevisited.blogspot.com/2011/11/collection-interview-questions-answers.html] 、Java 多线程与并发 [https://javarevisited.blogspot.com/2014/07/top-50-java-multithreading-interview-questions-answers.html#axzz5ghebTpxm] 、Java IO [https://javarevisited.blogs ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/review-these-50-questions-to-crack-your-java-programming-interview/</link>
                <guid isPermaLink="false">5fe4726339641a0517d52611</guid>
                
                    <category>
                        <![CDATA[ Java ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 面试 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 后端 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nicholas Zhan ]]>
                </dc:creator>
                <pubDate>Thu, 24 Dec 2020 07:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/12/1_s73cLB7vYz05f-aw_QAgFw.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>大家好！在过去的几年里，我分享了很多 <a href="http://javarevisited.blogspot.sg/2015/10/133-java-interview-questions-answers-from-last-5-years.html" rel="nofollow">Java 面试题</a> 和相关讨论。我的很多读者都要求我把它们整理在一起，以便他们能够在一个地方查看，因此这篇文章应运而生。</p><p>这篇文章包含了超过 <strong>50 个 Java 面试问题</strong>，涵盖了所有重要主题，例如 Java 基础知识、<a href="https://javarevisited.blogspot.com/2011/11/collection-interview-questions-answers.html" rel="nofollow">Java 集合框架</a>、<a href="https://javarevisited.blogspot.com/2014/07/top-50-java-multithreading-interview-questions-answers.html#axzz5ghebTpxm" rel="nofollow">Java 多线程与并发</a>、<a href="https://javarevisited.blogspot.com/2014/08/socket-programming-networking-interview-questions-answers-Java.html" rel="nofollow">Java IO</a>、<a href="https://javarevisited.blogspot.com/2012/12/top-10-jdbc-interview-questions-answers.html" rel="nofollow">JDBC</a>、<a href="http://www.java67.com/2016/08/10-jvm-options-for-java-production-application.html" rel="nofollow">JVM 内部原理</a>、<a href="http://www.java67.com/2018/06/data-structure-and-algorithm-interview-questions-programmers.html" rel="nofollow">编码问题</a>和<a href="http://www.java67.com/2015/12/top-30-oops-concept-interview-questions-answers-java.html" rel="nofollow">面向对象编程</a>等等。</p><p>这些问题也在面试中经常出现，但它们并不是很难。你可能已经在电话或现场面试中碰到过它们了。</p><p>这些问题也非常适用于回顾像多线程和集合这样的重要主题。此外，我还分享了一些很有用的进阶资源，例如 <a href="https://click.linksynergy.com/fs-bin/click?id=JVFxdTr9V80&amp;subid=0&amp;offerid=323058.1&amp;type=10&amp;tmpid=14538&amp;RD_PARM1=https%3A%2F%2Fwww.udemy.com%2Fjava-the-complete-java-developer-course%2F" rel="nofollow"><strong>The Complete Java MasterClass</strong></a>，它们不仅能帮你提高 Java 技能，还能填补技能空白。</p><p>那我们还等什么呢？这里列出了一些面试中最常问的 Java 问题，这些问题对初学者和有经验的 Java 开发者都适用。</p><h3 id="-2-3-50-java-">为 2-3 年经验程序员准备的 50+ Java 面试题</h3><p>我就不浪费你的时间了，这里就是我为初级程序员（初学者和 2-3 年经验的开发者）整理的常见 <a href="http://www.java67.com/2018/03/top-50-core-java-interview-questions.html" rel="nofollow">Java 面试题</a>。</p><p><strong>1) Java 是如何实现跨平台的？（<a href="http://www.java67.com/2012/08/how-java-achieves-platform-independence.html" rel="nofollow">答案</a>）</strong><br>提示：字节码和 Java 虚拟机。</p><p><strong>2) Java 中的 <code>ClassLoader</code> 是什么？（<a href="http://javarevisited.blogspot.sg/2012/12/how-classloader-works-in-java.html#axzz59AWpr6cb" rel="nofollow">答案</a>）</strong><br>提示：它是 JVM 的一部分，用来帮类加载字节码。你可以写自己的 <code>ClassLoader</code>。</p><p><strong>3) 写一个 Java 程序判断一个数字是偶数还是奇数？（<a href="http://javarevisited.blogspot.sg/2013/04/how-to-check-if-number-is-even-or-odd.html#axzz59AWpr6cb" rel="nofollow">答案</a>）</strong><br>提示：你可以使用位运算符，例如按位与（AND）。记住，偶数的二进制格式的末尾为 0，而奇数的二进制格式末尾为 1。</p><p><strong>4) <code>ArrayList</code> 与 <code>HashSet</code> 有什么不同？（<a href="http://www.java67.com/2012/07/difference-between-arraylist-hashset-in-java.html" rel="nofollow">答案</a>）</strong><br>提示：<code>List</code> 和 <code>Set</code> 之间的所有差异在这里都适用，例如有序性、元素的重复性、随机搜索等等。若想学习更多有关 ArrayList、HashSet 和 Java 中其它重要集合的知识，你可以查看 Richard Warburton 的<a href="https://pluralsight.pxf.io/c/1193463/424552/7490?u=https%3A%2F%2Fwww.pluralsight.com%2Fcourses%2Fjava-fundamentals-collections" rel="nofollow"><strong>Java Fundamentals: Collections</strong></a>。</p><figure class="kg-card kg-image-card"><img src="https://camo.githubusercontent.com/7a50edce96d734cb67d4678f02ddc9c9a682ae3c2e915974a3d1b37b65ed7f01/68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f75654f774d4164354742647734626c434f704542704f644d4f7463732d6574366e505941" class="kg-image" alt="68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f75654f774d4164354742647734626c434f704542704f644d4f7463732d6574366e505941" width="600" height="400" loading="lazy"></figure><p><strong>5) 什么是单例模式中的双重检查锁（double checking locking）？（<a href="http://www.java67.com/2016/04/why-double-checked-locking-was-broken-before-java5.html" rel="nofollow">答案</a>）</strong><br>提示：两次检查一个实例是否初始化，第一次无锁，而第二次有锁。</p><p><strong>6) 你如何在 Java 中创建一个线程安全的单例？（<a href="http://javarevisited.blogspot.sg/2012/12/how-to-create-thread-safe-singleton-in-java-example.html" rel="nofollow">答案</a>）</strong><br>提示：方法有很多，例如使用枚举类、双重检查锁或嵌套静态类。</p><p><strong>7) 何时在 Java 中使用 volatile 变量？（<a href="http://www.java67.com/2012/08/what-is-volatile-variable-in-java-when.html" rel="nofollow">答案</a>）</strong><br>提示：当你需要告知 JVM 某个变量可能被多个线程修改，并提示 JVM 不要缓存该变量的值的时候。</p><p><strong>8) 何时使用 transient 变量？（<a href="http://www.java67.com/2012/08/what-is-transient-variable-in-java.html" rel="nofollow">答案</a>）</strong><br>提示：当你想让一个实现了 <code>Serializable</code> 接口的类中的某个变量不会被序列化的时候。换句话说，当你不想在序列化对象时保存某个变量的值，你就可以用 <code>transient</code> 关键字修饰这个变量。若想了解更多有关 Java 中 transient 变量的信息，你可以查看 <a href="https://click.linksynergy.com/fs-bin/click?id=JVFxdTr9V80&amp;subid=0&amp;offerid=323058.1&amp;type=10&amp;tmpid=14538&amp;RD_PARM1=https%3A%2F%2Fwww.udemy.com%2Fjava-the-complete-java-developer-course%2F" rel="nofollow"><strong>The Complete Java MasterClass</strong></a>。</p><p><strong>9) Java 中的 transient 变量和 volatile 变量有什么不同？（<a href="http://www.java67.com/2012/11/difference-between-transient-vs-volatile-modifier-variable-java.html" rel="nofollow">答案</a>）</strong><br>提示：完全不同，一个用于序列化，而另一个用于并发。</p><p><strong>10) Java 中的 Serializable 和 Externalizable 有什么区别？（<a href="http://www.java67.com/2012/10/difference-between-serializable-vs-externalizable-interface.html" rel="nofollow">答案</a>）</strong><br>提示：Externalizable 可以让你更好地控制序列化过程。</p><p><strong>11) 我们能重写 Java 中的私有方法吗？（<a href="http://www.java67.com/2013/08/can-we-override-private-method-in-java-inner-class.html" rel="nofollow">答案</a>）</strong><br>提示：不能。因为 Java 中要求被重写的父类方法在子类中是可见的，而父类的私有方法对子类不可见。</p><p><strong>12) Java 中的 <code>Hashtable</code> 与 <code>HashMap</code> 有什么不同？（<a href="http://javarevisited.blogspot.sg/2010/10/difference-between-hashmap-and.html#axzz53B6SD769" rel="nofollow">答案</a>）</strong><br>提示：不同点有几个，但最重要的是：<code>Hashtable</code> 是同步的，而 <code>HashMap</code> 不是。和 <code>HashMap</code> 相比，<code>Hashtable</code> 是历史遗留类并且很慢。</p><p><strong>13) Java 中的 <code>List</code> 与 <code>Set</code> 有什么不同？（<a href="http://javarevisited.blogspot.sg/2012/04/difference-between-list-and-set-in-java.html#axzz53n9YK0Mb" rel="nofollow">答案</a>）</strong><br>提示：<code>List</code> 有序并允许重复元素，<code>Set</code>无序并不允许重复元素。</p><p><strong>14) Java 中的 <code>ArrayList</code> 与 <code>Vector</code> 有什么不同（<a href="http://www.java67.com/2012/09/arraylist-vs-vector-in-java-interview.html" rel="nofollow">答案</a>）</strong><br>提示：不同点有很多，但最重要的是：<code>ArrayList</code> 是非同步的并且很快，而 <code>Vector</code> 是同步的并且很慢。<code>Vector</code> 也和 <code>Hashtable</code> 一样是历史遗留类。</p><p><strong>15) Java 中的 <code>Hashtable</code> 与 <code>ConcurrentHashMap</code> 有什么不同？（<a href="http://javarevisited.blogspot.sg/2011/04/difference-between-concurrenthashmap.html#axzz4qw7RoNvw" rel="nofollow">答案</a>）</strong><br>提示：后者的可扩展性更强。查看 Richard Warburton 的 <a href="https://pluralsight.pxf.io/c/1193463/424552/7490?u=https%3A%2F%2Fwww.pluralsight.com%2Fcourses%2Fjava-fundamentals-collections" rel="nofollow"><strong>Java Fundamentals: Collections</strong></a> 以了解更多相关信息。</p><p><strong>16) <code>ConcurrentHashMap</code> 是如何实现可扩展性的？（<a href="http://javarevisited.blogspot.sg/2017/08/top-10-java-concurrenthashmap-interview.html#axzz50U9xyqbo" rel="nofollow">答案</a>）</strong><br>提示：通过将 map 分割为段（segment）并且只在写操作时加锁。</p><p><strong>17) 要将某个 <code>Object</code> 用作 <code>HashMap</code> 的 <code>Key</code>，你需要重写哪两个方法？（<a href="http://www.java67.com/2013/06/how-get-method-of-hashmap-or-hashtable-works-internally.html" rel="nofollow">答案</a>）</strong><br>提示：equals 和 hashcode。</p><p><strong>18) Java 中的 wait 和 sleep 有什么不同？（<a href="http://www.java67.com/2012/08/what-are-difference-between-wait-and.html" rel="nofollow">答案</a>）</strong><br>提示：<code>wait()</code> 方法会释放锁或 monitor，而 sleep 不会。</p><p><strong>19) Java 中的 <code>notify</code> 和 <code>notifyAll</code> 有什么不同？（<a href="http://www.java67.com/2013/03/difference-between-wait-vs-notify-vs-notifyAll-java-thread.html" rel="nofollow">答案</a>）</strong><br>提示：<code>notify</code> 随机通知一个等待该锁的线程，而 <code>notifyAll</code> 通知所有等待该锁的线程。如果你非常确定只有一个线程在等待锁，就使用 <code>notify</code>，否则使用 <code>notifyAll</code> 会更好。若想了解更多线程相关的基础知识，你可以参阅 Java 大师 Heinz Kabutz 的 <a href="https://javaspecialists.teachable.com/p/threading-essentials/?product_id=539197&amp;coupon_code=SLACK100?affcode=92815_johrd7r8" rel="nofollow"><strong>Threading Essentials Mini-Course</strong></a>。</p><p><strong>20) 在 Java 中，为什么你要一起重写 hashcode 和 <code>equals()</code> ？（<a href="http://javarevisited.blogspot.sg/2015/01/why-override-equals-hashcode-or-tostring-java.html#axzz55oDxm8vv" rel="nofollow">答案</a>）</strong><br>提示：为了符合 equals 和 hashcode 的约定，当你将对象存储到集合类（例如 <code>HashMap</code> 或 <code>ArrayList</code>）中时，你就需要这么做。</p><p><strong>21) <code>HashMap</code> 的负载因子（load factor）的有什么含义？（<a href="http://www.java67.com/2017/08/top-10-java-hashmap-interview-questions.html" rel="nofollow">答案</a>）</strong><br>提示：负载因子就是触发 <code>HashMap</code> 重新调整容量的阈值，通常为 0.75，表示 <code>HashMap</code> 在容量达到了 75% 后就会调整自身大小。</p><p><strong>22) Java 中的 <code>ArrayList</code> 与 <code>LinkedList</code> 有什么区别？（<a href="http://www.java67.com/2012/12/difference-between-arraylist-vs-LinkedList-java.html" rel="nofollow">答案</a>）</strong><br>提示：与数组和链表一样，一个允许随机搜索而另一个不允许。链表上的插入和删除很容易，而数组上的查询很容易。若想了解更多有关 Java 数据结构的信息，你可以查看 Richard Warburton 在 Pluralsight 上的课程 <a href="https://pluralsight.pxf.io/c/1193463/424552/7490?u=https%3A%2F%2Fwww.pluralsight.com%2Fcourses%2Fjava-fundamentals-collections" rel="nofollow"><strong>Java Fundamentals: Collections</strong></a>。</p><p><strong>23) Java 中的 <code>CountDownLatch</code> 与 <code>CyclicBarrier</code> 有什么不同？（<a href="http://www.java67.com/2012/08/difference-between-countdownlatch-and-cyclicbarrier-java.html" rel="nofollow">答案</a>）</strong><br>提示：你可以在屏障（barrier）被打破之后重用 <code>CyclicBarrier</code>，但你不能在计数达到零之后重用 <code>CountDownLatch</code>。</p><p><strong>24) 在 Java 中，何时使用 <code>Runnable</code> 而不是 <code>Thread</code> ?（<a href="http://www.java67.com/2016/01/7-differences-between-extends-thread-vs-implements-Runnable-java.html" rel="nofollow">答案</a>）</strong><br>提示：永远都使用 <code>Runnable</code>。</p><p><strong>25) 为什么说 Java 中的枚举类（Enum）是类型安全的？（<a href="http://www.java67.com/2014/04/what-java-developer-should-know-about-Enumeration-type-in-Java.html" rel="nofollow">答案</a>）</strong><br>提示：这意味着你不能将其它枚举类型的实例赋值给一个枚举变量。例如，如果你已经有了一个叫做 <code>DayOfWeek</code> 的枚举变量，你就不能再将 <code>DayOfMonth</code> 的值赋给它了。</p><p><strong>26) Java 中 Integer 的自动装箱是如何实现的？（<a href="http://www.java67.com/2014/04/what-java-developer-should-know-about-Enumeration-type-in-Java.html" rel="nofollow">答案</a>）</strong><br>提示：通过使用 <code>valueOf()</code> 方法。</p><p><strong>27) <code>PATH</code> 与 <code>ClassPath</code> 有什么区别？（<a href="http://www.java67.com/2012/08/what-is-path-and-classpath-in-java-difference.html" rel="nofollow">答案</a>）</strong><br>提示：<code>PATH</code> 由操作系统使用，而 <code>Classpath</code> 是 JVM 用来定位 Java 二进制文件（例如 JAR 文件或 Class 文件）的。查看 <strong><a href="https://pluralsight.pxf.io/c/1193463/424552/7490?u=https%3A%2F%2Fwww.pluralsight.com%2Fcourses%2Fjava-fundamentals-core-platform" rel="nofollow">Java Fundamentals: The Core Platform</a></strong> 以了解更多有关 <code>PATH</code>、<code>Classpath</code> 和其它 Java 环境变量的信息。</p><figure class="kg-card kg-image-card"><img src="https://camo.githubusercontent.com/788714bddf839642e2aaac0d674a4cf471503b6ac7397bdc456e1913df6e5041/68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f696f2d6c504536376f4d47316f42683230344c76506d363174376b41634c4676702d4236" class="kg-image" alt="68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f696f2d6c504536376f4d47316f42683230344c76506d363174376b41634c4676702d4236" width="600" height="400" loading="lazy"></figure><p><strong>28) Java 中方法的重载与重写有什么区别？（<a href="http://www.java67.com/2015/08/top-10-method-overloading-overriding-interview-questions-answers-java.html" rel="nofollow">答案</a>）</strong><br>提示：重写发生在子类中，而重载发生在同一个类中。此外，重写发生在运行时，而重载在编译时就解析了。</p><p><strong>29) 在 Java 中，如何防止一个类被继承？（<a href="http://www.java67.com/2017/06/10-points-about-final-modifier-in-java.html" rel="nofollow">答案</a>）</strong><br>提示：直接把该类的构造函数设置为私有的。</p><p><strong>30) 如何防止你的类被客户端使用？（<a href="http://javarevisited.blogspot.sg/2016/01/why-jpa-entity-or-hibernate-persistence-should-not-be-final-in-java.html" rel="nofollow">答案</a>）</strong><br>提示：将构造函数设置为私有，或直接在构造函数中抛出一个异常。</p><p><strong>31) Java 中的 <code>StringBuilder</code> 和 <code>StringBuffer</code> 有什么不同？（<a href="http://www.java67.com/2016/10/5-difference-between-stringbuffer.html" rel="nofollow">答案</a>）</strong><br>提示：<code>StringBuilder</code> 不是同步的，而 <code>StringBuffer</code> 是同步的。</p><p><strong>32) Java 中的多态与继承有什么区别？（<a href="http://www.java67.com/2014/04/difference-between-polymorphism-and-Inheritance-java-oops.html" rel="nofollow">答案</a>）</strong><br>提示：继承允许代码重用并能建立类之间的关系，这正是多态所需要的。多态提供动态行为。查看 <a href="https://pluralsight.pxf.io/c/1193463/424552/7490?u=https%3A%2F%2Fwww.pluralsight.com%2Fcourses%2Fjava-fundamentals-object-oriented-design" rel="nofollow"><strong>Java Fundamentals: Object-Oriented Design</strong></a> 以了解更多 OOP 的特性。</p><p><strong>33) 在 Java 中，我们能重写静态方法吗？（<a href="http://www.java67.com/2012/08/can-we-override-static-method-in-java.html" rel="nofollow">答案</a>）</strong><br>提示：不能，因为重写在运行时才解析，而静态方法调用在编译期间就解析了。</p><p><strong>34) 在 Java 中，我们能访问私有方法吗？（<a href="http://www.java67.com/2012/08/can-we-override-private-method-in-java.html" rel="nofollow">答案</a>）</strong><br>提示：能，在同一个类中就可以，在类之外则不行。</p><p><strong>35) Java 中的接口与抽象类有什么不同？（<a href="http://www.java67.com/2017/08/difference-between-abstract-class-and-interface-in-java8.html" rel="nofollow">答案</a>）</strong><br>提示：从 <a href="https://dzone.com/articles/5-courses-to-crack-java-certification-ocajp-1z0-80" rel="nofollow">Java 8</a> 开始，二者的差异不再那么明显了。然而，一个 Java 类还是可以实现多个接口，却只能继承一个类。</p><p><strong>36) Java 中的 DOM 和 SAX 解析器有什么区别？（<a href="http://www.java67.com/2012/09/dom-vs-sax-parser-in-java-xml-parsing.html" rel="nofollow">答案</a>）</strong><br>提示：DOM 将整个 XML 文件都载入内存，而 SAX 没有这么做。SAX 是一个基于事件的解析器，可以解析大文件，但 DOM 更快，是小文件的首选。</p><p><strong>37) Java 中的 throw 和 throws 关键字有什么区别？（<a href="http://www.java67.com/2012/10/difference-between-throw-vs-throws-in.html" rel="nofollow">答案</a>）</strong><br>提示：throws 声明一个方法在出错时可能抛出的异常，而 throw 关键字实际上会抛出一个异常。查看 <a href="https://pluralsight.pxf.io/c/1193463/424552/7490?u=https%3A%2F%2Fwww.pluralsight.com%2Fcourses%2Fjava-fundamentals-exception-handling" rel="nofollow"><strong>Java Fundamentals: Exception Handling</strong></a> 以了解更多有关 Java 异常处理的信息。</p><figure class="kg-card kg-image-card"><img src="https://camo.githubusercontent.com/47510df43dbafa457c335fa2e44f1f17c0ccbfa2c6034b0d07a77655381891f5/68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f5153714b442d623937447233366b5669563165546476714e564e67645a5270353244376e" class="kg-image" alt="68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f5153714b442d623937447233366b5669563165546476714e564e67645a5270353244376e" width="600" height="400" loading="lazy"></figure><p><strong>38) Java 中的 fail-safe 和 fail-fast 迭代器有什么区别？（<a href="http://www.java67.com/2015/06/what-is-fail-safe-and-fail-fast-iterator-in-java.html" rel="nofollow">答案</a>）</strong><br>提示：fail-safe 不会抛出 <code>ConcurrentModificationException</code>，而 <code>fail-fast</code> 在迭代集合的过程中检测到集合被修改时会抛出这个异常。</p><p><strong>39) Java 中的 Iterator 与 Enumeration 有什么区别？（<a href="http://javarevisited.blogspot.sg/2010/10/what-is-difference-between-enumeration.html#axzz59AWpr6cb" rel="nofollow">答案</a>）</strong><br>提示：Iterator 还允许你在迭代时删除元素，而 Enumeration 不允许你这么做。</p><p><strong>40) Java 中的 <code>IdentityHashMap</code> 是什么？（<a href="http://www.java67.com/2016/09/difference-between-identityhashmap-weakhashmap-enummap-in-java.html" rel="nofollow">答案</a>）</strong><br>提示：它是一个用 <code>==</code> 而不是 <code>equals()</code> 方法判断相等性的 <code>Map</code>。</p><p><strong>41) Java 中的 <code>String</code> 池是什么？（<a href="http://javarevisited.blogspot.sg/2016/07/difference-in-string-pool-between-java6-java7.html#axzz4pGGwsyna" rel="nofollow">答案</a>）</strong><br>提示：一个 <code>String</code> 字面量池。记住， JDK 7 已经将它从永久代移到堆中了。</p><p><strong>42) 在 Java 中，一个实现了 <code>Serializable</code> 接口的类可以包含不可序列化的字段吗？（<a href="http://javarevisited.blogspot.sg/2016/09/how-to-serialize-object-in-java-serialization-example.html" rel="nofollow">答案</a>）</strong><br>提示：可以，但是你需要将它设置为 static 的或者 transient 的。</p><p><strong>43) Java 中的 this 和 super 有什么区别？（<a href="http://www.java67.com/2013/06/difference-between-this-and-super-keyword-java.html" rel="nofollow">答案</a>）</strong><br>提示：this 指向的是当前实例，而 super 指向的是父类的一个实例。</p><p><strong>44) Java 中的 <code>Comparator</code> 与 <code>Comparable</code> 有什么区别？（<a href="http://www.java67.com/2013/08/difference-between-comparator-and-comparable-in-java-interface-sorting.html" rel="nofollow">答案</a>）</strong><br>提示：<code>Comparator</code> 定义的是自定义顺序，而 <code>Comparable</code> 定义的是对象的自然顺序，例如 <code>String</code> 的字母顺序。若想了解更多有关 Java 中排序的信息，你可以查看 <strong><a href="https://click.linksynergy.com/fs-bin/click?id=JVFxdTr9V80&amp;subid=0&amp;offerid=323058.1&amp;type=10&amp;tmpid=14538&amp;RD_PARM1=https%3A%2F%2Fwww.udemy.com%2Fjava-the-complete-java-developer-course%2F" rel="nofollow">The Complete Java MasterClass</a></strong>。</p><figure class="kg-card kg-image-card"><img src="https://camo.githubusercontent.com/5b18ba36d2efb387a444d8d3f2b736ba6d13a63efd23eb972e4a476bd83f05cb/68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f444f4347467464544d686a6a3366615241695136395a535478663270666679726f466676" class="kg-image" alt="68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f444f4347467464544d686a6a3366615241695136395a535478663270666679726f466676" width="600" height="400" loading="lazy"></figure><p><strong>45) Java 中的 <code>java.util.Date</code> 和 <code>java.sql.Date</code> 有什么不同？（<a href="http://javarevisited.blogspot.sg/2012/04/difference-between-javautildate-and.html" rel="nofollow">答案</a>）</strong><br>提示：前者既包含日期又包含时间，而后者只包含日期部分。</p><p><strong>46) 为什么 Java 中的 wait 和 notify 方法被声明在 <code>Object</code> 类中？（<a href="http://javarevisited.blogspot.sg/2012/02/why-wait-notify-and-notifyall-is.html" rel="nofollow">答案</a>）</strong><br>提示：因为它们需要锁，而这个锁只能从对象上获取。</p><p><strong>47) 为什么 Java 不支持多继承？（<a href="http://javarevisited.blogspot.sg/2011/07/why-multiple-inheritances-are-not.html" rel="nofollow">答案</a>）</strong><br>提示：因为多继承在 C++ 中使用很糟糕。但是在 Java 8 中，多继承在某种程度上是支持的——现在的 Java 中只是不支持 <code>Type</code> 的多继承。</p><p><strong>48) Java 中的受检异常和非受检异常有什么区别？（<a href="http://javarevisited.blogspot.sg/2011/12/checked-vs-unchecked-exception-in-java.html" rel="nofollow">答案</a>）</strong><br>提示：如果是受检异常，你必须使用 catch 块进行处理。而非受检异常的没这个限制，编译过程中也不会出现任何问题。</p><p><strong>49) Java 中的错误（Error）和异常（Exception）有什么区别？（<a href="http://www.java67.com/2012/12/difference-between-error-vs-exception.html" rel="nofollow">答案</a>）</strong><br>提示：我打字打累了，直接看答案吧。</p><p><strong>50) Java 中的竞态条件（Race condition）和死锁（Deadlock）有什么区别？（<a href="http://javarevisited.blogspot.sg/2012/02/what-is-race-condition-in.html#axzz59AbkWuk9" rel="nofollow">答案</a>）</strong><br>提示：二者都是并发程序中出现的问题，前者发生在线程调度的过程中，而后者是糟糕的代码导致的。查看 <a href="https://click.linksynergy.com/fs-bin/click?id=JVFxdTr9V80&amp;subid=0&amp;offerid=323058.1&amp;type=10&amp;tmpid=14538&amp;RD_PARM1=https%3A%2F%2Fwww.udemy.com%2Fmultithreading-and-parallel-computing-in-java%2F" rel="nofollow">Multithreading and Parallel Computing in Java</a> 以了解更多有关死锁、竞态条件和其它多线程问题的信息。</p><h3 id="-">结语</h3><p>感谢你阅读本文！祝你面试顺利！虽然面试很难，这份路线图的指导会让你离 <a href="https://hackernoon.com/10-free-courses-to-learn-docker-for-programmers-and-devops-engineers-7ff2781fd6e0" rel="nofollow">DevOps 工程师</a> 更进一步。</p><p>如果你喜欢这篇文章，请把它分享给你的朋友和同事，别忘了在 Twitter 上关注 <a href="https://twitter.com/javinpaul" rel="nofollow">javinpaul</a> 哦！</p><h4 id="--1">更多资源</h4><ul><li><a href="https://click.linksynergy.com/fs-bin/click?id=JVFxdTr9V80&amp;subid=0&amp;offerid=323058.1&amp;type=10&amp;tmpid=14538&amp;RD_PARM1=https%3A%2F%2Fwww.udemy.com%2Fjava-interview-questions-and-answers%2F" rel="nofollow">Java 面试指南：200+ 面试问题和答案</a></li><li><a href="https://click.linksynergy.com/fs-bin/click?id=JVFxdTr9V80&amp;subid=0&amp;offerid=323058.1&amp;type=10&amp;tmpid=14538&amp;RD_PARM1=https%3A%2F%2Fwww.udemy.com%2Fspring-interview-questions-and-answers%2F" rel="nofollow">Spring 框架面试指南：200+ 问题和答案</a></li><li><a href="https://pluralsight.pxf.io/c/1193463/424552/7490?u=https%3A%2F%2Fwww.pluralsight.com%2Fcourses%2Fdeveloper-job-interviews" rel="nofollow">John Sonmez：如何准备求职面试</a></li><li><a href="http://www.amazon.com/Java-Programming-Interviews-Exposed-Markham/dp/1118722868?tag=javamysqlanta-20" rel="nofollow">Markham：Java 面试揭秘</a></li><li><a href="http://www.amazon.com/Cracking-Coding-Interview-6th-Edition/dp/0984782850/?tag=javamysqlanta-20" rel="nofollow">通关编程面试 —— 189 个问题和答案</a></li><li><a href="https://click.linksynergy.com/fs-bin/click?id=JVFxdTr9V80&amp;subid=0&amp;offerid=323058.1&amp;type=10&amp;tmpid=14538&amp;RD_PARM1=https%3A%2F%2Fwww.udemy.com%2Fdata-structure-and-algorithms-analysis%2F" rel="nofollow">求职面试中的数据结构与算法分析</a></li><li><a href="http://javarevisited.blogspot.sg/2015/10/133-java-interview-questions-answers-from-last-5-years.html" rel="nofollow">过去五年中的 130+ Java 面试问题</a></li></ul><p><strong>P.S. —</strong> 如果你需要一些 <strong>免费</strong> 的 Java 学习资源，你可以从 <a href="http://www.java67.com/2018/08/top-10-free-java-courses-for-beginners-experienced-developers.html" rel="nofollow"><strong>free Java courses</strong></a> 开始。</p><p><strong>P. S. S. —</strong> 我还没有回答图片“How many String objects are created in the code?”中的那个面试问题。你能试着回答一下吗？</p><p>原文：<a href="https://www.freecodecamp.org/news/review-these-50-questions-to-crack-your-java-programming-interview-69d03d746b7f/">Review these 50 questions to crack your Java programming interview</a>，作者：javinpaul</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 自学编程 8 个月后，我拿到谷歌的软件工程师 offer ]]>
                </title>
                <description>
                    <![CDATA[ 找工作期间我曾拿到谷歌、网约车巨头 Lyft、最大的点评网站 Yelp、云计算独角兽 Rubrik、IBM 人工智能部门、摩根大通等公司的 offer。如果你正在找软件工程相关工作---特别是非计算机专业背景的人们——我希望我的故事可以激励你。 简介 你可能会想：怎么可能 8 个月就做到？ 在这篇文章中，我会详细介绍我的决策过程和思考过程，也会提供更多的阅读资料。  首先，我必须承认，很多因素赋予我很大优势：我是个白人，直男，本科就读于美国排名前 20 的高校，旧金山湾区工作的三年也让我积累了自己的人脉。 每个求职者的故事都不太一样，希望我的故事也能对你有所启发。 决定冒险 2018 年 7 月 29 日，我做了一个不理智的决定。 当时我面临着两个选择：一是去一家快速增长的广告技术公司担任办公室主任，二是参加编程训练营。办公室主任的薪水颇丰，发展稳定，似乎还是进入硅谷工作的快速通道，而编程训练营看起来代价又高、风险又大。 6 月 24 日，也就是做决定的 5 周前，我才用 JavaScript 写下了我的第一行代码，但也并没有成为一名工程师的打算。本科时，我读的是经济学专 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/first-line-of-code-to-226k-job-offer-in-8-months/</link>
                <guid isPermaLink="false">5f56f15bcd07b005bfb5aec5</guid>
                
                    <category>
                        <![CDATA[ 自学编程 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 求职 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 面试 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miaomiao Ma ]]>
                </dc:creator>
                <pubDate>Tue, 08 Sep 2020 03:10:57 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/09/Love-to-learn_tim-mossholder_unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>找工作期间我曾拿到谷歌、网约车巨头 Lyft、最大的点评网站 Yelp、云计算独角兽 Rubrik、IBM 人工智能部门、摩根大通等公司的 offer。如果你正在找软件工程相关工作---特别是非计算机专业背景的人们——我希望我的故事可以激励你。</p><h2 id="-">简介</h2><p>你可能会想：怎么可能 8 个月就做到？</p><p>在这篇文章中，我会详细介绍我的决策过程和思考过程，也会提供更多的阅读资料。 </p><p>首先，我必须承认，很多因素赋予我很大优势：我是个白人，直男，本科就读于美国排名前 20 的高校，旧金山湾区工作的三年也让我积累了自己的人脉。</p><p>每个求职者的故事都不太一样，希望我的故事也能对你有所启发。</p><h2 id="--1"><strong>决定冒险</strong></h2><p>2018 年 7 月 29 日，我做了一个不理智的决定。</p><p>当时我面临着两个选择：一是去一家快速增长的广告技术公司担任办公室主任，二是参加编程训练营。办公室主任的薪水颇丰，发展稳定，似乎还是进入硅谷工作的快速通道，而编程训练营看起来代价又高、风险又大。</p><p>6 月 24 日，也就是做决定的 5 周前，我才用 JavaScript 写下了我的第一行代码，但也并没有成为一名工程师的打算。本科时，我读的是经济学专业，过去三年在一家非营利机构做非技术管理咨询的工作。在湾区，有些职位要跟工程师一起工作，学代码只是因为我想让自己应聘这些职位时更有竞争力。</p><p>事实上，成为一名工程师像是天方夜谭。尽管我之前听说过许多人用一年时间通过编程训练营自学了编程，但我还是很难相信自己也可以达到同样水平。</p><p>据我了解，大多数工程师本科专业就是计算机科学，他们有着多年的工作经验，还会讲一门外语，我怎么能在这么短的时间内学会这么多东西呢？另外，要是我选择学习编程，我就要换行，为一个不确定的回报冒这么高的风险，值得吗？</p><p>办公室主任一职，则更像是梦想实现了：薪水比预想的要好，而且公司正在筹备不久后的收购计划，我将参与这一计划，几年后我也有望管理自己的部门。</p><p>但是，开始编程后，我就不想停下来。我热爱技术挑战，喜欢迎难而上。我还发现，<a href="https://dilbertblog.typepad.com/the_dilbert_blog/2007/07/career-advice.html">发展第二个专业技能</a>让我做足准备，准备开启一段不平凡的有影响力的职业生涯。</p><p>我内心里也有个声音在问：成为一名工程师会不会既能提升专业度，又能完成个人转型？如果我能学会编程，有什么是我学不会的呢？正是这种学习态度，支撑我坚持走完了整个旅程。</p><p>我选择了线上的编程课，连续三周每周编程时间不低于 40 个小时，之后我向 <a href="https://www.hackreactor.com/onsite-immersive">Hack Reactor</a> （被誉为“编程训练营中的哈佛”）提交申请，想试试看我是否能成为其中的一份子。侥幸通过测试后，同一周，我拿到了办公室主任的工作 offer。</p><p>思考了 72 个小时，我最后再次查看了自己的银行账户，算了算 Hack Reactor 项目结束后，我可以支付三个月的房租和生活开支。我想，三个月时间足够我找到工作了。</p><p>于是，我选择了编程训练营。</p><p>我给广告技术公司打电话拒绝了他们的 offer。挂断电话后我的情绪很复杂，一方面我很害怕：我可是为一个疯狂的决定放弃了一生仅一次的工作机会！另一方面我又激情满满：为了不让自己后悔，冒险前进吧！</p><p>没过多久，我的激情就消耗殆尽了。不过再没有回头路了：我已经踏上了冒险旅程。</p><h2 id="--2"><strong>学习编程</strong></h2><blockquote><strong>“你不必一开始就优秀，但你要开始变得优秀。”— Zig Ziglar，国际知名演说家、作家</strong></blockquote><p>Hack Reactor 和其他一些编程训练营都力求在三个月内完成传统大学计算机科学专业需要四年才能完成的教学任务，以让学员具备竞争一线软件工程工作的能力。</p><p>目标很宏大，所以得分秒必争。三个月的课程安排中，一周 6 天，一天上 12-14 小时的课，时间很紧，所以我们每天都要做大量任务。</p><p>每次收到任务时，我都会心里一惊：这根本不可能在截止期限内完成！我连从哪儿下手都没有头绪。但很神奇，每次到截止期限时，我总是能想出来或基本想出来一个解决方案。</p><p>经历足够的震惊与不可能之后，我开始感到一丝丝的激动：挑战看起来有多不可能，找到解决方案的时候就有多心满意足。</p><p>Hack Reactor 不仅仅教授编程知识，它还会教给我们<a href="https://en.wikipedia.org/wiki/Grit_(personality_trait)">咬紧牙关坚持的精神</a>，以及<a href="https://www.mindsetworks.com/science/">成长型思维</a>，过程十分令人激动。</p><p>我仍然怀疑自己是否能在存款用完之前找到工作，于是我养成了最好的学习和生活习惯。</p><p>充足的睡眠才能保证<a href="https://news.berkeley.edu/2011/03/08/sleep-brainwaves/">学习效率</a>，所以我规定了严格的睡眠时间。</p><p>为了缓解经常性压力，每隔一天我都会锻炼身体<a href="https://www.fastcompany.com/3025957/what-happens-to-our-brains-when-we-exercise-and-how-it-makes-us-happier">促进健康</a>、<a href="http://www.educationalneuroscience.org.uk/resources/neuromyth-or-neurofact/exercise-enhances-learning/">支持学习</a>。</p><p>为了提升记忆力，让每一天的学习都比前一天有进步，几乎每天晚上我都会复习核心课程，思考哪些事情做得比较好，哪些还不够好。</p><p>最重要的是，为了在一个如此紧张的安排中保持基本健康水平，每天早上上课之前我都会花一个小时来做<a href="https://ggia.berkeley.edu/practice/body_scan_meditation">禅修</a>和<a href="https://jackkornfield.com/meditation-lovingkindness/">慈爱冥想</a>。</p><p>有的人可能会对最后一个习惯感到惊讶，但是有充足的<a href="https://nccih.nih.gov/health/meditation/overview.htm">证据</a>表明“冥想能改善健康”，尤其是<a href="https://www.psychologytoday.com/us/blog/feeling-it/201409/18-science-backed-reasons-try-loving-kindness-meditation">慈爱冥想</a>。它帮助我保持对编程学习的好奇和愉悦，缓解了我面对编程学习挑战和工作的不确定性时的忧虑，对我来说十分有价值。</p><p>因为交通问题，我没有选择面对面授课。所以尽管我离校园步行只需十分钟，我仍然选择了网课。因为我上课无需通勤，吃饭方便，公寓环境十分安静，所以，我每天多出了 90 分钟不被打扰的<a href="https://medium.com/@dsilvestre/deep-work-by-cal-newport-lessons-305a375c41b8">深入学习时间</a>。</p><p>刚开始的 6 周，有两天是在未完成代码库的基础上，进行结对编程冲刺。两天时间里，我们重写了JavaScript <a href="https://underscorejs.org/">Undercore 库</a>，从零开始建立了基础数据结构，学习了面向对象编程和函数式编程，计算了时间和空间复杂度，构建了一个包含从客户端到服务器端到数据库的全栈应用。我们组有 24 人，每天视频会议时间不下 10 小时，逐渐成为朋友，可以互相开开玩笑。</p><p>学习了三周以后，我担心我可能不会通过中期考评，所以我<a href="https://www.creativityatwork.com/2003/01/10/art-of-possibility-ben-zander/">给未来的自己写了一封信</a>，告诉自己要想通过考评需要如何做。</p><p>我提到了我现有的生活和学习习惯，又增加了一些新的习惯，比如，如果没有能力在脑海里给一个虚构的小女孩（<a href="https://fs.blog/2012/04/feynman-technique/">费曼学习法</a>）解释核心代码，那就要复习这些我没有完全理解的代码，直到有能力给她解释为止。</p><p>3 周以后，在 24 小时内，我从零建立起了自己的第一个全栈应用，出色地通过了考评。大学教育十分重要，但这里的教育是另一种体验。</p><p>第二个 6 周里，有更多的自由形式小组项目。我从正向偏差中汲取灵感，追随着训练营营友的步伐，跟他们进行了交流。这些营友找的工作都十分成功。根据他们的指导建议，我设定了严格的 deadline 以应对巨大的技术挑战，在求职市场上挑选出最受欢迎的职业技能，例如能使用 Docker 和微服务系统工作，在不同的项目中扮演不同的角色。我还请一个营友对我进行了一次辅导，我用两天时间成功运行一项新性能，这位营友刚开始却花费了数周时间。</p><p>Hack Reactor 会从每支队伍的毕业营员中雇佣一些作为短期兼职助教，来支持全职员工的工作。毕业以后，我在这里担任了 6 周的助教，每周工作 35 个小时。工作内容包括：帮助制定课程安排、进行独立调研、基于调研结果帮助在找工作的营员、面试前来应聘的候选人等。</p><p>我跟训练营负责人商量只做 6 周的助教，而不是通常的 12 周。助教工作让我接触到了新的课程，同时也让我兼顾找工作和全职学习两件事，没有太多耽搁。非常感激能在助教工作中学习，助教工作确实进一步<a href="https://medium.com/accelerated-intelligence/memory-learning-breakthrough-it-turns-out-that-the-ancients-were-right-7bbd3090d9cc">加深了我自己的学习</a>，开出的薪水也足够我两个多月的开支，极大地缓解了我的压力。</p><h2 id="--3"><strong>求职旅程</strong></h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/09/image.png" class="kg-image" alt="image" width="600" height="400" loading="lazy"><figcaption>给我工作 offer 的公司</figcaption></figure><p>12 月 7 日，距离我写下第一行代码已经过去了 165 天，我的 Hack Reactor 训练营也结束了，剩下的存款还够我承担 4 个月的房租和生活开支。训练营告诉学员要预留 6 个月的时间来找工作。倒计时正式开始！</p><p>即使困难重重，我还是想要放手试一试，所以我给自己制定了以下目标：</p><ul><li>拿到的薪酬在训练营排名要在前 25%，目标是年薪超过 12 万美金</li><li>工作尽可能具备最刺激的学习体验，希望职位既能让我充分发挥主观能动性，也有机会接触到行业大牛</li><li>工作团队和公司的文化以技术为重，同时要以人为本</li><li>工作要有趣也有意义</li><li>要做后端开发工作，至少要是全栈开发（大多数训练营毕业生都是从事前端开发工作）</li></ul><p>我从未设想过我的目标能全部实现。我知道找工作一定会像过山车一般跌宕起伏。</p><p>通过在 Hack Reactor 这段短时间的学习，我的基础并不扎实，但是在去一流公司面试之前，我还有几周时间可以全心学习。</p><p>而且，我可能会面对数不清的拒绝。尽管有调查显示，在面试中，<a href="https://triplebyte.com/blog/bootcamps-vs-college">训练营学员跟四年计算机专业科班出身的学生表现一样优秀</a>，大部分公司仍然不愿意聘用从编程训练营毕业的学员。软件工程面试中牵扯到的话题范围特别广泛，这也让我根本没办法面面俱到地准备。</p><p>等到求职之旅终于尘埃落定时，我总共申请了 44 家公司，其中 41 家是熟人帮忙介绍的，遇见了 14 次或编程挑战或技术电话面试，其中有 8 次进入了现场面试。截至 2019 年 2 月 15 日，我共收到了 7 个 offer。这距离我写下第一行代码，已经过去了 245 天。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/09/image-1.png" class="kg-image" alt="image-1" width="600" height="400" loading="lazy"><figcaption>有 16% 的工作 offer 都是在我第一次写下代码 8 个月后拿到的</figcaption></figure><h2 id="--4">开始找工作，从失败中汲取教训</h2><blockquote><strong>“学习不是偶然获得的，必须保持热情，勤于钻研”—— </strong>Abigail Adams，美国第一夫人</blockquote><p>刚开始找工作的几周是最艰难的。12 月一整月，我只获得了几个编程测试机会，只拿到了 IBM 人工智能业务部的面试机会。</p><p>前两个编程测试的反馈就不如人意，我做的时候超时了，还被告知我的代码“准确性不够，性能也不够高”。</p><p>第三个编程测试有几个小时的时间，但我仍然心跳加速。我在时间结束前几秒完成了测试，却没来得及点击提交！</p><p>我告诉自己量变才能引起质变，现在我需要积累。每天早上一个小时的慈爱冥想结束后，我都会花一分钟提醒自己两件事：</p><ul><li>第一，目标确实给人方向。但如果只关注目标，在面对自己当前的水平和想要达到的目标之间的巨大差距时，我就会十分不满和沮丧。我要养成<a href="https://fs.blog/2017/06/habits-vs-goals/">不被目标束缚的习惯</a>。</li><li>第二，我认为，不管面试的是什么工作，经历的真正价值在于个人提升而非专业提升。我很幸运，能用几个月的时间全职学习如何学习，我现在也完全沉迷于此。</li></ul><p>我知道失败是成功之母，但前提是得认真对待失败。每次编程测试或者面试失败后，我都会反省，然后在我的文本编辑器里再次开始解决问题，直到完全解决。面试时的一个需要在 20 分钟内完成的任务，我在 3 天内花了 9 个小时来解决！</p><p>每解决一个问题，我都会记录下新概念和灵光一现的时刻，把它们看成是宝贵的<a href="https://fs.blog/mental-models/">思维模型</a>。我希望在面试时匹配到相应的模型。我会按照<a href="https://artofmemory.com/wiki/Spaced_Repetition_and_Recall">记忆重复间隔</a>定期回顾它们，以便形成长期记忆。</p><p>如果问题涉及新的编程句法结构，我会把问题按照时间紧急程度重做一遍，这样才能保证，面试再次碰到这些问题时轻松应对。</p><p>这些习惯不仅仅是增强了我的记忆力，还帮我建立了自信。我不知道在积蓄花完之前能否找到一份工作，但我知道哪怕是最令人失望的经历也在帮助我变得更好，这种感觉让我知足。</p><p>我的每日规划十分平稳，一周不少于 5 天，每天 8 小时：学习或者面试，总结失败或者成功经验，思考，重复。 面试中我可能会遇到诸如数据结构、算法、前端 DOM 操作、系统设计等等。按照这些可能，我不断地调整自己的学习计划以便面试时呈现最佳状态。之后我会预估接下来的面试遇到各种类型考题的可能性有多大，评估自己的面试表现，决定下一步的学习任务。</p><p>为了确保用到最好的学习资源，我按照主题分门别类，整理了其他学习者们推荐的各种资源，列成清单。需要学习这个主题时，我会再去谷歌快速搜索一下，看看有没有什么新的资源可以更新到清单上，然后找出最好的一到两个资源进行深入学习。前一天我会做好各项事件优先级排序，第二天就能确保，清晨冥想之后，进行 2-3 个小时的不被打扰的深入学习。</p><p>每天我还会花 2-3 个小时寻找目标公司，找出重点目标，然后投递简历。每次投递简历花费的时间比我预想的要多，而且可供我选择的一流公司的数量有限，因此，我尽所能通过以下方式提升我从投递简历到拿到面试机会的概率：</p><ul><li>简历中<a href="https://resumegenius.com/how-to-write-a-resume/accomplishments-on-resume-quantify-achievements">强调自己工作取得的成果或者采取的行动</a></li><li>研究邮件和领英哪个渠道和信息流能最快给我推荐相关公司</li><li>整个流程写在电子表格上随时回顾</li><li>跟进所有正在进行的沟通</li></ul><p>我几乎把所有投递简历的过程当做实验，希望投入时间带来的回报能不断提高。我的好习惯也帮了我很多——把已投递简历的公司写入表格意味着，在我被拒绝之后，我可以立即寻找下一个机会。</p><p>我也尽可能地安排好时间，首先面试一些不那么想去的公司，缓慢推进意愿度高的公司的面试进程，比如谷歌。 </p><p>IBM 的面试（包括一次电话面试和三次现场面试）是我坎坷求职之路上的转折点，给我上了一堂课，教会我如何处理不确定性。。</p><p>每次面试一开始，我都会经历那种现在无比熟悉的感觉，默默对自己说：我不知道怎么做。 但我每次都会深呼吸，提醒自己：在家里，从不知所措到想出解决方案都如此有趣，所以，在这里——挑战更大，工作触手可及——想出解决方案的那种感觉该会有多美妙！</p><p>前两次面试之后，我猜想也许我的热情和学习导向的态度是具有感染力的。面试官当然知道我并没有一开始就找到答案，但他们似乎很享受我在接近解决方案时话语间体现出的活力。</p><p>面试结束离开 IBM 的时候，我给自己加油打气：就算没有拿下这份工作，我也很享受压力下的思维提升。我坚信这种信念会帮助我找到工作。</p><p>幸运的是，12 月的失败浇灌了 1 月的成功之花。</p><h2 id="--5">良好势头和早期成果</h2><blockquote><strong>“最艰难的日子过后就是你人生的高光时刻，继续前进吧。” —— <strong>Roy T. Bennet</strong></strong></blockquote><p>在 1 月的第一周，IBM 打电话通知我被录用了，具体的正式录用细节会之后另行通知。“我可以利用一下这个 offer 吗？” 我问自己。随后我跟谷歌招聘人员分享了我拿到 IBM offer 的消息，他回复说让我跳过电话面试直接现场面试。</p><p>突然我就引起了招聘人员的关注。我立即跟所有我正在联系的公司说我已经拿到 IBM offer 的消息。新年伊始，整个找工作的节奏就这样被调动起来了。</p><p>第二周，去参加摩根大通的四场现场技术面试时，我第一次信心满满，甚至还提前完成了任务。12 月的辛苦付出终于有了收获，我欣喜若狂。</p><p>几天以后是谷歌面试，难度令人咋舌。第二场面试涉及到异步 JavaScript promise，我发挥得很差。</p><p>午饭休息时间，我在休息室里花了一点时间稍作休整，判断了一下形势觉得自己无望拿到 offer，我决定把目标调整成尽可能地从失败中多学经验。 我知道晚上回家后我要做个事后总结。但同时我也想挑战一下，看自己在如此高压环境下能保持几分镇定与平静。毕竟，面试谷歌多么令人激动。</p><p>这样的想法把我从慌乱状态中解救出来，下午的表现可圈可点。离开的时候，我甚至抱有一丝希望，觉得谷歌会给我 offer。</p><p>根据事后总结的习惯，回到家后我找到关于 JavaScript promise 的线上资源从零学起。第二天我又参加了三次电话面试，表现一般。</p><p>面试一家安全创业公司时，我表现很好；但面试一家小型能源创业公司时，我表现很差。电话面试 Rubrik——一家云存储独角兽公司——又给了我极大的信心。</p><p>Rubrik 的面试问题也是关于 promise，比前一天谷歌面试的还要难。但因为我前一天晚上的总结，现场创新，在面试结束之前刚刚完成。</p><p>面试官说我是他们面试的第一个编程训练营出身的人——他们之前只从名校招人——并且他们一点都不相信我从去年夏天才开始写代码。我开心得甚至在房间里跳了几步舞。</p><p>过了一周，摩根大通打电话给我，说要录用我，年薪 11 万美金，不包含股票和奖金。但我觉得我不太适应摩根大通的企业文化，而且这个薪资距离我 12 万年薪的目标还有差距。但因为这是我的第一个正式 offer，我还是特别激动，终于有人雇我写代码了！</p><p>我开始接到好多来自面试官、招聘人员和各个顾问的电话，结果仍然是有好有坏。谷歌打电话说面试结果仍不明朗，可以去招聘委员会询问；Uber 给了我面试机会；我觉得电话面试亚马逊时表现很好，但没有拿到面试机会；我觉得我搞砸了 Yelp 的电话面试，但却拿到了面试机会。</p><p>在一场场的面试中，我非常注意我面对招聘人员时的措辞。那家安全创业公司说他们担心没有办法给出像 IBM 和摩根大通一样的薪水，问我拿到的 offer 开出的薪水是多少。</p><p>我几乎都要上钩了，但我停下来想了想之前 Hack Reactor 训练营指导过的关于薪水问题的回答方式，于是我说，“其实我觉得不如这样，你告诉我打算开多少薪资，我看看在不在我的理想范围之内”，“当然可以，我们的薪资起薪是 12.5 万。”</p><p>12.5 万美元！这个数字超出了我的目标！</p><p>我望向别处来掩盖自己激动的心情，装作正在思考。然后我扭过头，镇定地说：“如果这样的话，我想我们可以进一步谈谈。”招聘人员说：“那好，只要还能谈我就放心了。”我也是，我在心里默默想到。</p><p>几天后我拿到正式 offer：年薪 12.5 万美元，外加每年当年市值的 6000 美元的股票期权。但是这些钱根本不重要，我特别中意公司的企业文化，职位也是非常有吸引力的后端开发，还有绝佳的指导机会。团队中 40 个工程师至少都有两年的工作经验，大部分都来自像麻省理工、斯坦福或者伯克利之类的顶尖名校。这些跟我憧憬的工作都十分契合！</p><p>但这一切，才刚刚开始。</p><h2 id="--6">跟公司谈判，做出最后决定</h2><blockquote><strong>“高效的谈判者会回顾对方所说的话，并挖掘对方深层的动机……他们拥有无穷的好奇心。”—— </strong>Chris Voss<strong>，</strong>国际危机谈判专家</blockquote><p>两天后 Rubrik 给我回了电话，我无比惊讶。我也将会是他们聘用的第一个从编程训练营毕业的员工。Rubrik 市值早已达到 33 亿美元，现在是一家炙手可热的新型独角兽公司。我可以和经验丰富的软件工程师共事，工作环境也让人心动。我回复招聘人员说要考虑一下，其实心里早已乐开了花：这么有竞争力的公司也想要我！挂断电话以后我仍然十分激动，差点忘记自己漏接了谷歌的电话。</p><p>我深呼吸几下，把电话回拨了过去。招聘人员倒是开门见山：“根据招聘委员会的通知，很开心地通知你被谷歌录用了，我们开出的薪酬是---”</p><p>我完全控制不住自己的尖叫声，蹦蹦跳跳进了厨房！天哪！谷歌！那家软件工程行业的标杆公司！他们的面试官是我整个面试经历中我遇见过的最严厉的！他们决定要我了！之后招聘人员告诉了我公司开出的薪酬，事情变得更加不真实了。年薪 16.3 万美元，包括：基础工资 12 万，奖金最低 1.8 万，每年公司股票 2.5 万，总共 16.3 万美元。</p><p>我心里想：你们疯了吗？毕竟我最近的报税单上的年薪只有 7.7 万美元。</p><p>我给自己放了一下午的假，想着应该没人看到，便在房子周围抑制不住兴奋地跳来跳去。我也给家人打了电话分享这个天大的好消息。</p><p>第二天上午，我又回归到辛苦学习当中。只不过这次不是学习算法，而是学习如何谈判。一夜之间，那些在面试过程中指导我的面试官成为了我在谈判中的对手。我觉得自己就像在狼群中求生存的一只羔羊——周围都是专家，几分钟的谈话之间就可能决定我的薪酬能不能多几千美元。</p><p>刚开始我很害怕，担心这样做会让自己显得比较贪婪，但 Hack Reactor 的教练态度特别坚定。她说这特别正常，谈判不仅仅是为了更高的薪酬。在谈判过程中，一个人有没有认真思考、是否自信都可以在谈判过程中得到显现。同时也会让老板对你刚进入公司时的工作有所期待。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://camo.githubusercontent.com/887472c9bbceb40319125d002f26ebbba9ad2960/68747470733a2f2f6c68332e676f6f676c6575736572636f6e74656e742e636f6d2f67543641667977356a66356342366b346777467732317a6769596f4d362d48713636703734617545627045366f555a4a666e585554595434655f577465574b6f456663574450354c6239736e434e37794c595863676d5f4f32453139544c5f62586b2d4c4136434b38676c4b5a5f2d5466566e4765627658576d696268364d335939494167523046" class="kg-image" alt="68747470733a2f2f6c68332e676f6f676c6575736572636f6e74656e742e636f6d2f67543641667977356a66356342366b346777467732317a6769596f4d362d48713636703734617545627045366f555a4a666e585554595434655f577465574b6f456663574450354c6239736e434e37794c595863676d5f4f32453139544c5f62586b2d4c4136434b38676c4b5a5f2d5466566e4765627658576d696268364d335939494167523046" width="600" height="400" loading="lazy"><figcaption>我发现基本 90 %的工资上涨都是因为有我第一份工作开出薪酬的比较</figcaption></figure><p>接下来的几天，我都在跟招聘人员和顾问的电话沟通中度过，不断学习为谈判做准备，也在谈判后及时复盘吸取经验。我把希望在谈判中呈现出的内容写在了一页纸上，每次谈判过后也会反思哪部分进行得好，哪部分有待提升，就像我之前复盘面试失败教训一样。</p><p>我学着去爱上这个过程。每次谈判都会出现不同的令人着迷同时也令人困惑的问题，涉及层面多样，例如高水平策略方面——有关我分享信息的时间和方式，例如当下策略方面——我应该用什么样的语气沟通。能解决这么多的问题非常有趣。有时我能在一天中跟多个招聘人员聊天，每次电话沟通既能让我练习新学到的谈判技巧，同时也能让我重新积累教训。</p><p>读大学时我就读完了哈佛大学谈判项目的两部分：<em><em>Getting to the Yes</em> </em>和 <em><em>Getting Past the No</em></em>，对最佳替代方案（BATNA）和双赢解决方案之类的概念非常熟悉。但我还是从<em> </em>Chris Voss 的<em>《<em>Never Split the Difference</em>》</em>书中获得最多的灵感<em>。</em>拿到谷歌的 offer 以后我又把这本书读了一遍。 另外，我还阅读了 <a href="https://haseebq.com/farewell-app-academy-hello-airbnb-part-ii/">Haseeb Qureshi</a> 写的所有博客，他也是编程训练营出身，把赚来的钱捐给慈善事业。我也会时常跟训练营的职业教练沟通，在我之前，她有着成百上千的谈判指导经验。</p><p>Rubrik 刚开始薪酬报价是 16.3 万美元，跟谷歌不相上下。之后，Yelp 打电话过来，上演了剧情大逆转。他们把我申请的职位提升到一个非初级岗，然后给我开出了 16 万美元的年薪，另外还有 2 万美元的入职奖金——第一年总共 18 万美元。</p><p>18 万美元和非初级岗，怎么样？</p><p>我在 Yelp 面试中表现出了自己的最佳水平——在给定时间内完成了所有任务，为满足新的需求重新调整了代码使之无缝衔接，通过粗略计算评估了系统架构。面试官甚至都因此感到吃惊。但这些并不能改变我没有任何工作经验的事实。谷歌和 Rubrik 马上就回复说他们会调整薪水。</p><p>最终，找工作的最激动人心的部分来了。</p><p>Lyft 发邮件说要电话沟通。迄今为止，我的面试最好体验就来自 Lyft，但我觉得我的面试表现不能让我拿到他们的 offer。面试时我几乎是立马就想出了解决方案，但写的代码却始终没有运行出来。后来我又想出一个改进办法，但超时了没有提交成功。每天车轮般的谈判让我身心俱疲，所以我回复给 Lyft 的邮件如下：</p><p><em>“我现在在和好几家公司沟通。你介不介意用邮件直接通知我结果呢？我大概猜到我应该没有通过面试，但即便这样，我还是想从每次面试中获得一两句评价反馈，麻烦你了。非常感谢你百忙之中抽出空来对我进行指导。”</em></p><p>Lyft 的回复只有一行：“你通过面试了。”</p><p>什么？我通过面试了？我简直不敢相信，我以为已经没有希望的最中意的选择竟然兜兜转转回来了！第二天，我们谈定了薪酬：各项加起来总计年薪 21 万美元。</p><p>21 万美元！</p><p>想一想，这可是 Lyft！这是我朝思暮想可以工作的公司，工资什么的一点都不重要。几个在 Lyft 工作的朋友是我最喜欢的其中一部分人——很难说是因为他们善良还是聪明——我的面试官们似乎也如出一辙。</p><p>和 Lyft 确定薪酬后，我告知了每个正在沟通的公司，并且告诉他们我有最后一周的考虑时间，想鼓励他们给出最终确定薪酬。持续不断的谈判让我身心俱疲，我认为有截止期限在，可以大大减少每个公司在我身上投入的时间。Voss 也建议说，截止期限可以作为杀手锏使出。</p><p>谷歌给出了 18.9 万美元的薪酬，高于 Yelp 的报价，并且表示会根据 Lyft 的报价重新作出调整。Rubrik 同意电话沟通。Yelp 和那家安全创业公司表示不会继续沟通。我也不打算继续跟进摩根大通和 IBM。Uber，虽然给了我面试机会，却并没有给我发 offer。</p><p>Lyft 团队邀我共进午餐，我受宠若惊。不管是谷歌、Yelp 还是安全创业公司的团队都让人特别舒服，但是 Lyft 团队来了九个人跟我一起吃午饭，气氛十分融洽，饭桌上大家谈笑风生，好像我早就已经是团队中的一份子。他们说招我进公司是 2019 年公司的头等大事。一名高级工程师表示他十分乐意做我的导师。此时距离 Lyft 首次公开募股的日期，已经没有几个月了。</p><p>在 Lyft，我什么都有了：导师指导、快速成长的环境、以人为本的企业文化、令人激动的工作以及现在如此之高的薪酬。</p><p>Rubrik 没有及时给出薪酬报价。谷歌最后的报价是 23.3 万美元，如果算上 401K 养老保险制度和慈善机构项目就是 21.6万美元。因为我的目标就是想拿更高的薪酬，同时也能捐赠更多，并且这一年我将会捐出我税前收入的 25%，所以我觉得这一报价还可以。我也很赞同谷歌的企业文化，并且谷歌把中级工程师培养成一流天才专家的能力依然一流。</p><p>我在谷歌和 Lyft 之间摇摆不定，纠结了几天。慢慢地，我越来越觉得，抛开薪酬不谈，Lyft 绝对是我不能错过的机会。经过最后谈判，Lyft 的薪酬定在了 22.6 万美元，包括 13.5 万美元的基本工资，IPO 之前估价 7.1 万的股票，和 2 万美元的入职奖金。2 月 25 号，是周四，那时距离我写下我的第一行代码已经过去了 245 天，我接受了 Lyft 的 offer，为这一段求职旅程划上句点。</p><p>在 Lyft 工作的 6 个月，是我最开心的工作时光：团队互相扶持，工作内容吸引人，薪酬待遇也好。但是，就成为一名工程师而言，宝贵之处在于，我爱上了学习。既然我已经为学习痴迷，我就不打算与它分离。</p><h2 id="--7">找到一份软件工程师工作</h2><p>我把之前用来准备面试的资源列成了清单分享给有需要的人。同时我也会为求职者提供求职指导课程，参加课程的学员需要把自己未来收入的 10% 或者更多投入到高影响力的公益事业。</p><p><strong>点击<a href="http://www.swejobsearch.com/resources/">这里</a>获得资源清单和求职指导课程</strong>。</p><p>注意：希望我的私人生活可以跟作为求职教练的生活有所分割，因此我使用了笔名。</p><p>在这篇文章中，我给到了详尽的薪酬数目，有两个原因：一，我希望这些信息对于非科班出身的求职者有所帮助，因为他们之中大部分人对此了解甚少；二，详尽的<a href="https://www.nytimes.com/2018/08/31/smarter-living/pay-secrecy-national-labor-rights-act.html">薪酬数目</a>也有助于消除薪酬不平等现象，让每个人都能得到公平对待，尤其是<a href="https://leanin.org/equal-pay-data-about-the-gender-pay-gap">少数群体</a>。</p><p>反对薪酬不平等现象是 Lyft 一直以来的使命，同时也符合 Lyft 的企业文化。Lyft 在反对这一不平等方面投入了很多努力，每年都会进行三方薪酬股权审计。就在去年，Lyft 官方调查显示“<a href="https://www.forbes.com/sites/maggiemcgrath/2019/07/31/lyfts-2019-pay-equity-audit-has-a-surprising-result-its-male-and-female-employees-earn-the-same-thing/#56e6e0357a8d">不存在系统性薪酬差距</a>”，这家公司便成为了一家独特的独角兽公司。准确的薪酬信息在很多网站都可以找到，例如 <a href="https://www.paysa.com/salaries/software-engineer--t">paysa</a>，<a href="https://www.levels.fyi/">levels</a> 和 <a href="https://www.teamblind.com/search/software%20engineer%20lyft">blind</a>，我在文中分享的信息在这些平台都可以找到。</p><p>最后，很感谢在这段旅程中一直有很多人给予我支持，特别是求职教练兼谈判顾问 Lena Johnson 和技术导师 Robin Kim。再次衷心感谢大家！</p><p>原文：<a href="https://www.freecodecamp.org/news/first-line-of-code-to-226k-job-offer-in-8-months/">How I Went from Writing My First Line of Code to a $226K Job Offer in Just 8 Months</a>，作者：Jason Zedde</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
