<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>寒梦——后端技术分享</title>
  
  
  <link href="https://siegelion.cn/atom.xml" rel="self"/>
  
  <link href="https://siegelion.cn/"/>
  <updated>2024-06-07T12:50:45.750Z</updated>
  <id>https://siegelion.cn/</id>
  
  <author>
    <name>siegelion</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>记2023</title>
    <link href="https://siegelion.cn/2024/06/07/%E8%AE%B02023/"/>
    <id>https://siegelion.cn/2024/06/07/%E8%AE%B02023/</id>
    <published>2024-06-07T12:50:45.750Z</published>
    <updated>2024-06-07T12:50:45.750Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>2023 年过去了，我不想怀念他。</p></blockquote><p>这篇 2023 年的总结终于在 2024 年中旬开始动笔。一部分原因是由于懒惰，主要原因是由于这一年来属实精力捉襟见肘，抽不出大块的时间让我回首这一年的点点滴滴，临近毕业我终于可以开始动笔这一年的流水账，回首这段忙里偷闲的日子。</p><p>2023 年作为我人生的又一个关键节点，这一年中走的每一步都影响着但我走出校门迈入社会时到底要走向何方。面对这种关键又没有足够的把握的时刻时，往往都选择尽自己的最大努力而为，虽然往往就结果而言，最终取得的结果对我个人而言还算满意，但不免让人活得很累。以至于我一直深陷一件又一件需要投入精力的事情，无暇开始编辑这篇流水账。</p><p>就如上文所言，今年的大部分精力都被我投入到了找工作这件事，毕竟我这小镇做题家（还是比较垃圾的那种）寒窗 19 载（6+3+3+4+3）就是为了能找一份比较体面的工作，当一个比较体面的牛马。所以对于找工作这件事我还是投入了近 100%的精力。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/7056e090018542ca66ae052b9313ce1.jpg" alt="“关键”"></p><hr><p>从 2022 年底忙完毕业设计的开题，就开始着手投递实习，想要在寒假期间找一份实习，这时候我的准备还比较粗糙，虽然当时 leetcode 刷了一部分，但临场手撕还是会大脑空白，八股也只是潦草一看，东一榔头西一棒槌地看。不过比较幸运的是，我只面了两家就拿到了 SmartX 的实习 offer，虽然工作的部门是 SRE，但当时这家公司对我来说是带有光环的，我一直觉得这家公司是以技术见长的公司，当时想要走基础架构方向的我，对这样的初创还是抱有幻想的，加之当时公司同意我可以先线上实习，所以很愉快地接了 offer。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/a868bcbe792df0296b8171027c20219.jpg" alt="smartx"></p><p>但不幸的是在 SmartX 的工作实在让我提不起兴趣，当是我在天心哥的带领下，基于 cgroup 开发一款可以对系统中的服务进行资源限制和监控的程序，当时基于的技术非常很底层，我需要从头了解 cgroup 的一些基本原理，Linux 中对 cgroup 技术的应用，导致我上手初期十分头大，开始开发后不仅功能测试困难，而且对于程序是否能正常运行我也是没什么信心的。好在我当时的任务压力并不大，加之当时已经准备跑路了，所以我每天的稍微做一些工作就开始摸鱼🐕（当时公司有一个可以站着办公的长桌，我当时特别喜欢站在桌子旁摸鱼，因为背靠窗户，没人知道我在干嘛）。就这样混到了二月底，负责的工作初见成效，我便找了个借口提了离职，准备回学校全心全意准备暑期实习的事宜，其实当时也可以留在 SmartX 转正，但当时我由于 2022 的开源经历，对云原生和开源充满了热情，想着有机会可以去阿里云继续做开源，所以很干脆地提出了离职。</p><p>回校之后，我便开始忙于刷题与背八股，希望在暑期实习阶段可以斩获满意的 offer。当时我憧憬着可以去阿里云的 KubeVela 组实习，后续如果秋招顺利的话，便可以留在这里开始我开源生涯，一想到可以全职做开源便不由觉得这样的工作太酷了。但当我觉得已经准备地足够充分将简历发给天元的时候，收到的却是噩耗：我的第一学历不足以让我通过阿里的简历筛选，即使投递的是实习岗位，这也意味着我的校招将和阿里彻底无缘，这个情况还是很让我焦虑的，因为当时女友已经先一步去了杭州工作，作为杭州互联网中心的阿里，无疑是我去杭州最好的选择，但无奈天不随人愿，经过向多个前辈的确认这件事的真伪后，我也不得不接收这个现实，好在我当时调整的还很快，没有过多纠结这个坏消息，很快投入到了其他公司的简历投递中。</p><blockquote><p>不过没机会去阿里这件事现在看来也算是塞翁失马了。</p></blockquote><p>我第一个面试也是第一个拿到的 offer 来自于快手的容器云部门，当时我的面试水平虽然比年初投递字节时好了很多，但是还是很菜，记得快手一面的时候对方给我出了一道二叉树相关的简单题，但我当时没想法厚着脸皮让对方换了一道，好在顺利的是最后顺利通过，二面也很随意，最终在 4 月初拿到了 offer。由于是第一个 offer，而且当时我由于顺利拿下了第一个 offer，所以对自己开始有了一定的信心，便和对方 HR 说了很多漂亮话把 offer 稳了下来，把它当作备胎继续骑驴找马，又继续投递了美团、百度、腾讯等国内一众国内互联网。比较幸运的是，我的简历相比于其他同学有两端实习经历，加之阿里的开源经历，所以面试时有 20-30 分钟的时间都是在和面试官聊项目，这样问八股的时间便比较少，所以面试的时候还算顺利，差不多在 4 月底的时候又拿到了美团和百度的 offer。后面我又集邮式地面试了腾讯、拼多多、Amazon 和 PayPal，最终拿到的 offer 一共有 5 个，在咨询了之前阿里的导师阳哥的意见后，选择接了美团的 offer，拒掉了百度、快手、Amazon 和 PayPal 的 offer。</p><blockquote><p>具体的面试经历可以参考我的这篇博客：<a href="https://siegelion.cn/2022/12/13/%E4%B8%80%E5%90%8D2024%E5%B1%8A%E6%AF%95%E4%B8%9A%E7%94%9F%E7%9A%84%E6%B1%82%E8%81%8C%E6%97%A5%E8%AE%B0%EF%BC%88%E6%9A%91%E6%9C%9F%E5%AE%9E%E4%B9%A0%EF%BC%89/">一名2024届毕业生的求职日记（暑期实习）</a></p></blockquote><p>当时选择美团的 offer，一个原因是考虑到美团的转正率比较高，秋招的时候算是有一个保底的选择；另一个是当时我对做基础架构方向充满了向往，觉得一直做技术很酷。还有一个情况是，如果接了实习 offer 最后毁约不去，美团会直接拉黑对方，秋招没法再投递了，所以接了美团的 offer 之后我便没有继续邮递了。</p><blockquote><p>现在看这个选择其实也不能算有很大的问题，抛出美团实习体验不佳之外，快手实习强度大，百度做的是业务方向，Amazon 和 PayPal 这两家外企，如果不想实习转正去的意义也不大。</p></blockquote><p>现在想来，说实话我的暑期实习面试不能不说顺利，首先我选择的语言和赛道都非常小众，我的主力语言是 Golang，投递的主要方向是云原生。对于业务岗来说，相比于 Java 的海量岗位，和 C++的万金油，Golang 是一个被大家（学生）劝退的语言。基础架构方向，会设置一些 Golang 相关的岗位，但这些岗位不但只有大厂开设，而且竞争激烈，还要面临“职业选手”的竞争（有的实验室专门做相关方向），这导致我研一研二都在焦虑，要不要再学一个 Java 保底，虽然很焦虑但最后还是坚持走下来了，并且还通过了 Amazon 和 PayPal 这种机会屈指可数外企的面试（外企的工作氛围和国内互联网相比是真的无敌，奈何 23 年还处在 22 年裁员的余波之中，不太敢赌这种公司还能在国内活多久）。</p><blockquote><p>BYR 论坛一篇很火的云原生的劝退贴：<a href="https://static.byr.cn/n/article/Feeling/3196121">云原生劝退长文，秋招暴毙实录</a></p></blockquote><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20240601161851.png" alt="云原生劝退"></p><hr><p>虽然拿到实习 offer 让我很高兴，但后续暑期实习的那段时间，我真的算不上开心，我在美团实习时所在的组是：基础架构部-基础软件中心-弹性系统组（目前由于美团组织架构调整，已经改了名字），组内主要包含三个小组，我们小组开发和维护基础容器中运行的容器 Agent 软件，以及基于 Dragonfly 开发的镜像分发软件。我负责跟着晓凯和智凯参与开发容器 Agent，当时美团内部将容器分为轻容器和富容器两种，富容器可以看作容器化的虚拟机，从虚拟机迁移到容器的成本低，但基础进程和业务进程在一个环境中运行，会导致二者互相影响，所以美团内部想要把容器模式迁移至业务进程和基础进程隔绝的轻容器模式，这被认为是一种更为优雅的模式，我实习的那段时间目前正处于容器范式的迁移进程中。</p><p>这段实习经历给我的感触很深，也让我重新反思自己的职业生涯该如何选择。在美团的工作不同于跟着阿里那边做开源，是两种完全不同的模式和工作，开源软件开发中上层用户的话语权没那么重（至少不是爸爸级别的），不需要对所有的用户负责。但在企业中做基础架构开发，你需要对你所有业务客户负责，极度关注软件的稳定性，极度关注软件的性能损耗，负责响应业务客户的诉求，负责解决业务客户的问题，新增功能的重要性远低于软件稳定性的重要性。所以基础架构部门的工作中开发和 oncall 的比重不相上下，每开发一个功能都需要担心会不会造成事故，代码写的和走钢丝一样 🐶 。</p><blockquote><p>这个时候我已经开始反思自己了，我一直不是一个很细心的人，基础架构岗位中这种几乎不能出错的要求对我来说压力很大，我开始思考自己是否适合这种岗位。</p></blockquote><p>而且！当时美团正处于富容器到轻容器的过渡阶段，想把集群中的容器范式迁移至轻容器，以达到降本增效的目的，但这种大规模的重构无疑面临着包括同时维护两套代码，解决无数的 dirty work 等等任务，当时对于一个实习生来说 cover 目前的代码逻辑（大量适配的补丁代码）已经不是一件容易的事了，更别说开发新的功能并且考虑到代码在各种场景下的适配问题。加上当时的 ld 喜欢 PUA 我，当时我一边实习一边忙于导师的项目，因此需要每周请一天假，ld 对此很不满意，便用转正名额的问题来敲打我。我当时初入职场，对于这种鞭策还没能一眼识破，后续又从其他同事那里得知了部门内部的一切潜规则，遂对该部门很失望，对留在美团转正完全没热情。另外由于美团距离我们学校实在太远，美团打车要一个多小时才能到达位于望京的美团，每天接近三个小时的通勤让我本就因为上班疲惫的身体雪上加霜，每天感到特别疲惫，甚至萌生了去国企养老的想法，不过现在当时特别疲惫应该是新冠的后遗症带来的负面影响（但当时的糟糕体验真的让我萌生了对互联网的抵触，也影响了我秋招时的选择）。</p><blockquote><p>奥对，我当时下班之后，还有帮我导师做他那个破项目，忙到晚上 12 点。</p></blockquote><hr><p>8 月中旬实习满 3 个月之后我便赶紧离开了美团，开始为后续的秋招做准备，当时急于离开的原因时已经有很多公司开始了提前批的招聘，甚至腾讯和字节的 HR 已经联系到了我，并跟我约了面试的时间，这两个公司也是我后面比较顺利拿到的 offer 中的两个，我想能比较顺利的通过面试也和当时面试候选人比较少，hc 尚且充足有很大关系。</p><p>我的秋招考虑到女朋友的原因，所以我只投递了杭州的公司，不过好在杭州的几个大厂（腾讯、字节、快手、华为、滴滴）都给了我面试的机会。腾讯和字节是两个最先找我约面试的公司，腾讯的是 CSIG 的数据库部门，负责数据库内核开发和其上的一些管控组件的开发，如果我最后入职应该也是做一些管控层面的开发，因为我对数据库内核确实是一窍不通。面试流程是我经历过最长的流程，4 面技术面+1 面 HR 面，好在我的实习经历帮了我大忙，当时的我算上阿里的经历已经可以算 4 段实习经历了，每次面试自我介绍+项目经历就会耗时 40 分钟左右，稍作八股考察再加一道算法题时间就到了，自然也不会暴露出太多的问题，一次面试也就顺利通过了。而字节的面试就稍微坎坷了一些，我遭遇了一次转部门的波折，我首先面试的是抖音的风控部门，面试经历了三面（最后一面让我写一个开放的问题，我当时真的没有任何思路，自知凶多吉少，感叹字节的难度确实高），后面被转到了抖音的创作者部门，本来我没抱多大希望（因为是转到新的部门，上一个部门没要，下一个部门要的希望也不大，没人会要其他人不要的东西吧），结果却峰回路转，上午面试完下午便被 HR 加了微信，然后经过一个简答的 HR 面，便给发了意向。有趣的是，当时 HR 面的时候，我表达我想去杭州而不是在北京的时候 HR 表示十分吃惊（因为以北邮的学历留在北京是有单列落户的户口资格的），但我却果断放弃了北京的选项。</p><blockquote><p>由于北邮校园太小，找不到安静的房间面试，大家只能在寝室面试，8-9 月寝室每天都是被面试日程排满的。</p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/d0f805c097bc51c670cc26d2ab38793.jpg" alt="d0f805c097bc51c670cc26d2ab38793" style="zoom:50%;" /></blockquote><p>后续我又参加了包括百度、滴滴、快手、华为、虹软等公司的面试，很幸运地拿到了百度、虹软和华为的 offer。百度和华为的面试体验还很不错，百度捞我的是音视频中台部门，面试官十分友，（二面是一个女组长，很温柔，三面的主管也比较随和，和我聊了很多有关规划、未来的话题）只是之后的工作可能涉及对老的平台做优化重构，以实现降本增效的目的，在美团经历过老代码重构的我对于这种工作可谓之避之唯恐不及，所以面试之后便没太关注。</p><p>而华为我投递的部门是华为云，我觉得国内除了阿里云以外，还有哪家云做的还像那么回事的话，那就是华为云了，华为对于云的投入不想腾讯那么犹疑，而且在 toG 市场华为的认可度很高，此外华为云在云原生的开源领域也有几个十分有名的项目，并且 HR 表示作为校招生也有机会去做相关项目的开发，这不禁又唤起了我对参与开源的向往，有趣的是当时最后的主管面，华为的主管看到我参与过 KubeVela 的开源建设，便问我关于这个项目的看法，并表达了自己对于这个项目的看法，现在看来他的看法真的非常精准，不由得让人佩服。这里就不由得不提及目前 KubeVela 的现状，项目冲击 CNCF Incubation 成功之后，阿里便渐渐放弃了对该项目的投入，社区的中的讨论变少，issue 的回复效率变低，项目组的核心成员也各奔东西，这件事让我觉得十分震惊，也让我意识到开源项目的商业化是多么困难的一件事，在目前的经济下行的周期内，技术、开源这种作为成本的物料完全会因为公司的一声令下而被抛弃，相比较之下业务方向反而是更稳妥的一种选择（如果还算是一个靠谱的业务的话）。</p><hr><p>秋招的历程更像是一段马拉松和摔跤比赛，与体力、耐心、焦虑的比赛，过程中需要不断抵抗来自网络的焦虑，面对面试失利不断鉴定自己的信心，不断学习补齐暴露出的不足，在一场又一场的笔试和面试中做到更好。</p><blockquote><p>详细的面试经历可以参考我的这篇博客：<a href="https://siegelion.cn/2023/10/06/%E4%B8%80%E5%90%8D2024%E5%B1%8A%E6%AF%95%E4%B8%9A%E7%94%9F%E7%9A%84%E6%B1%82%E8%81%8C%E6%97%A5%E8%AE%B0%EF%BC%88%E7%A7%8B%E6%8B%9B%EF%BC%89/">一名2024届毕业生的求职日记（秋招）</a></p></blockquote><p>好在功夫不负有心人，历经近两个月的苦战，10 月中旬各家公司陆续发放 offer 并开始谈薪，最先开奖的是美团，不出我所料，美团给我了一个白菜价，我也十分干脆地拒绝了这份 offer，这份 offer 的价格以及美团的组内氛围实在没什么让我值得惋惜的。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202024-06-04%20161752.png" alt="美团offer"></p><p>后续又是漫长的等待，下一个为我给出 offer 薪水的是腾讯，算是一份不高不低的 offer，基础薪资比美团多 1 k，外加 4 k 的房补，算是一份在我心理预期之内的 offer 了，但人性都是贪婪的，我也不意外，我想观望一下字节薪资再做决定，所以我去催促了一下字节的 HR，希望他近期可以给一个结果，在我的反复催促下字节 HR 给出了字节的答案，基础薪资比美团多 8 k，是一份远超我预期的 offer，因为我事前和 HR 沟通的时候，我报出的价格甚至比这个少 2 k，所以我觉得这个价格让我觉得对方很有诚意，我也没做过多考虑便接下了这份 offer，事后看来当时的决定还是太过草率了，因为还收到了比眼下更高的 offer。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202024-06-04%20164329.png" alt="屏幕截图 2024-06-04 164329"></p><p>接下了字节的 offer 的同时，我和腾讯的 HR 表达了对 offer 薪资不太满意的想法，对方没说什么只是跟我说他向上反馈一下，让我耐心等待，又过了一周左右，幸福的烦恼落在了我身上。腾讯的 HR 跟我联系并给出了新的薪资，新的 offer 不但基础薪资和字节拉平，还多出了 4 k 左右的房补以及额外的股票和签字费。此外，华为的 HR 在咨询了我目前手上的 offer 情况后，也给出了一份基础薪资高于字节 offer 3 k，并且这份 offer 的职级给到了 15 级，这份 offer 真的很让我心动，真的是狠狠心动了，原有有以下几点：首先，因为就如我前文所说的，如果去华为那么可以继续做云原生基础架构相关的工作，还有机会做开源相关的工作；此外，华为的裁员压力也没有那么大；并且华为的薪资和职级给的很有诚意。如果是没有经历过美团实习的我肯定会义无反顾地接下了这份 offer，但现在我的心境发生了变化，我开始考虑更多的因素：“选择云原生、基础架构方向的工作是否适合我？”、“华为云的工作压力是否适合我？”。在纠结了这两点之后，我最终还是选择不舍地放弃了华为的 offer，直至今日落笔之时我还是会有点惋惜放弃这份工作，但人生没有那么多后悔的机会，也没有什么选择可以两全其美，只能说塞翁失马焉知非福走好目前的路才是我应该做的。此外我拒绝华为的原因还有很重要的一点，华为这份 offer 需要我去上海青浦报道，这是华为新建成的华东中心，集团的意志便是希望员工转岗到此，但我由于女朋友的因素去杭州的意向无比强烈，所以对于去上海还是很抵触的。</p><hr><p>说起为了女朋友去杭州，就不得提起我今年在感情上的一系列变故，这个话题至今我都是不想面对的，因为直至现在我都没能处理好这件事。写至此处有很多话想说，但却不知从何下笔…</p><p>一言概之便是，这段感情面临一些来自现实因素的考验，让我对这段感情能否有一个圆满的结局没了信心，所以我做了一个决绝的决定提出了分手，但问题不出在我们两人身上，感情的牵绊两个人都无法割舍，说不清的羁绊下两个人又重新走到了一起，只是没有了原来的名分，在开启下一段感情前做填补彼此空白的 Parterner。但其实我们都知道，两个人只是两头不愿接受现实的鸵鸟。只是作为这段波折的始作俑者，我其实一直觉得很对不起对方，尤其是曾经她可以选择老家的岗位，也是为了和我在一起选择了杭州的工作。但我最终没能履行当初的约定。</p><hr><p>在感情上出现波折后不久，中期答辩结束后，我便去到了杭州实习，这里也将是我接下来一段时间将会生活和工作的城市，我从来没想过会来杭州，就像我当时来北京读书前从来没想过一样。杭州这个城市对我来说不好不坏，它没有北方的粗犷，多了一份江南的温软，如果它的阴雨天可以少那么一些的话，我会多喜欢它一分。</p><p>实习的生活也不好不坏，工作有挑战性但不多，组内同事相处的也还算融洽，ld 相比之前在美团的要好不少，但能力欠佳的产品和自以为是的测试却让我忍不住骂人。我实习了近 5 个月（直至今天），也是忽然就过了那么久，组内的工作强度让我觉得满意，1095 的节奏，加上 5 分钟的通勤时长，在工作之余为我省去了很多不必要的疲惫，甚至中午还可以回家午睡一小时。</p><blockquote><p>但听说字节的华东总部要建好了，到时候可能就要换一个工区了，所以我格外珍惜当下的工作氛围。</p></blockquote><hr><p>这一年我感觉过的很快，甚至觉得有点浑浑噩噩，一直在被一件又一件事情推着走，期待有片刻的安宁，有了机会休息却又觉得什么都没做休息日就结束了，我将这种状态归结为：缺少内心的安宁。我是开始迷茫了吗？我真的有这种感觉，活了 20 多年，我的梦想和对自己的期待一直在缩小，高中时候我有傻傻的远大的理想，大学时我的目标明确和可靠，但随着我离社会越近，我更清楚地认识到这个世界，我却越不知道自己以后应该为什么努力。我清楚自己想要改变世界、成为伟大的人、做出一番大事业已几乎是不可能的使，现在甚至是自由自在都变得那么困难。常听张雪峰说要以终为始，但随着我的理想不断修正，可能我已经和最初的终点渐行渐远，我只期待好好过完我的一生，守护好身边的人，但就这么简简单单的一个愿望，实现起来也并不容易。</p><p>我变得焦虑，开始怕走错每一步路，就像前面提到的我每次都要尽自己最大的努力去做。今年我又将士兵突击看了一遍，也许不是所有人都喜欢这部 10 多年前的军旅电视剧，但是我真的从中看到了很多剧情之外的东西，我很羡慕许三多的安分与不焦虑，无论是身处什么环境，都是那么的始终如一。初中的时候老师们总说我坐不住板凳，说我耐不住寂寞，心静不下来，这么多年下来看来我还是这样。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20240606204023861.png" alt=""></p><p>我常跟人说：“人贵有自知之明，我很讨厌对自己没逼数的人“，但我其实也知道，我对自己的认知也并不完全准确，不过我一直在修正对自己的认知。这么多年过去，我清楚地认知到我并不是特别聪明，也算不上多么优秀，这一年我见识了太多比我能力更强的同辈和前辈，望其项背；我清楚地认识到我不够细心和仔细，需要付出更多的精力专注做好事情不出错。</p><p>这一年的点点滴滴又让我对自己有了更清楚的认知。我认知到自己是一个勇于认错，但很少能拿出努力去改变自己问题的人，我这一点很容易让身边人失望；我认识到我并不算一个特别理性的人，我也有感性的一部分，我也会因为情绪上头做一些不冷静的事，说一些不冷静的话，很容易伤到人；我是一个没什么耐心的人，很容易不耐烦。</p><blockquote><p>对我自己的分析，待续</p></blockquote><hr><p>写到此处，对我这一年做个总结吧。</p><ul><li>参加笔试：8 次</li><li>参加面试：37 次</li><li>拿到 offer：5 个（暑期实习）+ 7 个（秋招）</li><li>实习：3 段（12-2 月、5-8 月、12 月-4 月）</li><li>新城市：青岛、杭州、嘉兴、长沙</li><li>新书：0.2（两本书，但都是草草看了一点）</li></ul><hr><p>回首看自己 2022 年总结中对 2023 年的展望，可以说完全没达成自己的计划（虽然这种计划基本都很难实现）。2022 年我说不想把自己逼得太紧，但还是忙忙碌碌度过了一年；2022 年我说想技术上再深入一点，这方面基本没什么进展，除了八股背的贼溜，其他非功利的技术基本没涉猎；2022 年我说想看点闲书，结果买的那本《北上》也只是翻开过一次。</p><p>即将迎来 2024 年，生活将开启新的篇章。在新的城市开始未知的新生活，我既没有太多的期待，也没有太多的恐惧，只希望工作不要太坎坷，不要太忙碌，心能稍微宁静一些，有一些时间思考自己的方向。如果可以的话，我希望能够补救一下 2022 年的那些未完成的计划。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;2023 年过去了，我不想怀念他。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这篇 2023 年的总结终于在 2024 年中旬开始动笔。一部分原因是由于懒惰，主要原因是由于这一年来属实精力捉襟见肘，抽不出大块的时间让我回首这一年的点点滴滴，临近毕业</summary>
      
    
    
    
    <category term="日记" scheme="https://siegelion.cn/categories/%E6%97%A5%E8%AE%B0/"/>
    
    
    <category term="日记" scheme="https://siegelion.cn/tags/%E6%97%A5%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>一名2024届毕业生的求职日记（秋招）</title>
    <link href="https://siegelion.cn/2023/10/06/%E4%B8%80%E5%90%8D2024%E5%B1%8A%E6%AF%95%E4%B8%9A%E7%94%9F%E7%9A%84%E6%B1%82%E8%81%8C%E6%97%A5%E8%AE%B0%EF%BC%88%E7%A7%8B%E6%8B%9B%EF%BC%89/"/>
    <id>https://siegelion.cn/2023/10/06/%E4%B8%80%E5%90%8D2024%E5%B1%8A%E6%AF%95%E4%B8%9A%E7%94%9F%E7%9A%84%E6%B1%82%E8%81%8C%E6%97%A5%E8%AE%B0%EF%BC%88%E7%A7%8B%E6%8B%9B%EF%BC%89/</id>
    <published>2023-10-06T18:58:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<h1>总结</h1><h2 id="通过">通过</h2><table><thead><tr><th>公司</th><th>部门</th><th>岗位</th><th>进度</th><th>评价</th><th>Offer</th></tr></thead><tbody><tr><td>字节</td><td>抖音风控</td><td>后端</td><td>三面技术，被转了部门</td><td>被主动邀请投了提前批，HR 对于哪个组一直遮遮掩掩，后来自己问了才知道，听说工作节奏还行，本来挺想去的，但是无奈发挥的不好，三面后被转了部门。</td><td></td></tr><tr><td>字节</td><td>抖音创作者</td><td>后端</td><td>转部门后加一面，通过</td><td>被上一个部门挂了之后转到这个部门，本来以为没啥希望，最后居然峰回路转，加面上岸，侥幸侥幸。</td><td>SP 已接</td></tr><tr><td>腾讯</td><td>腾讯云数据库管控</td><td>后台</td><td>四面技术，一面 HR，已经通过</td><td>被主动邀请投了提前批，面试起来还比较顺利，但部门负责人要求 base 成都，最后在 HR 面时表达了想去杭州，本来以为没希望了，没想到最后真的给了 base 杭州的 offer。</td><td>大白菜，A 到了 SSP，但还是拒了，有点心疼</td></tr><tr><td>美团</td><td>基础架构</td><td>后端</td><td>实习转正</td><td>部门在做美团轻容器的落地，目前比较受重视，技术挑战也比较大，但方向可能比较偏。</td><td>烂白菜，头都不回拒了</td></tr><tr><td>百度</td><td>音视频中台</td><td>后端</td><td>三面技术，已经通过</td><td>音视频中台，负责手百的直播和点播（我都没听过…），内部在降本增效，个人意向不大</td><td>白菜 A 了 SP ，拒了</td></tr><tr><td>虹软</td><td>云服务器</td><td>后端</td><td>二面技术，一面 HR</td><td>面试比较流程化，二面的时候很有意思问了一个物理题，要求提前实习才给发 offer。</td><td>意向，没消息默认无了</td></tr><tr><td>电子云</td><td>容器云</td><td>后端</td><td>二面技术，一面 HR</td><td>一面时问了一个 Raft 的问题做了一道题，后续就没了技术面了，emmmm。</td><td>意向，没消息默认无了</td></tr><tr><td>华为</td><td>华为云</td><td>后端</td><td>三面技术</td><td>聊的还可以就是开奖太晚了</td><td>应该也是 SSP 了，15 级，但还是拒了，有点心疼</td></tr></tbody></table><h2 id="其他">其他</h2><table><thead><tr><th>公司</th><th>岗位</th><th>进度</th><th>评价</th></tr></thead><tbody><tr><td>饿了么</td><td>基础架构</td><td>简历挂</td><td></td></tr><tr><td>高德</td><td>基础架构</td><td>简历挂</td><td></td></tr><tr><td>淘天</td><td>基础架构</td><td>简历挂</td><td></td></tr><tr><td>阿里云</td><td>基础架构</td><td>简历挂</td><td></td></tr><tr><td>阿里控股</td><td>基础架构</td><td>简历</td><td></td></tr><tr><td>Shein</td><td>Go</td><td>简历挂</td><td></td></tr><tr><td>京东</td><td>后端</td><td>一面挂</td><td>投递的京东科技，莫名其妙被京东零售捞起来，面试时也非常离谱：说我技术栈不匹配没法给我过、要求 9 月立马到岗实习。</td></tr><tr><td>小红书</td><td>云原生</td><td>笔试后，没后续</td><td></td></tr><tr><td>米哈游</td><td>云原生</td><td>简历挂</td><td></td></tr><tr><td>快手</td><td>云原生</td><td>二面技术挂了，莫名其妙</td><td>一面面完直接流程结束了，隔了两周又捞起来…，部门负责人挺想让我 base 北京的，但我想去杭州。</td></tr><tr><td>滴滴</td><td>云原生</td><td>一面技术，没后续</td><td>部门方向比较多，其中一个组在使用 Kubevela 搭建内部 PaaS，方向比较 match，但个人感觉技术深度一般。</td></tr><tr><td>小米</td><td>云原生</td><td>笔试后，没后续</td><td></td></tr><tr><td>得物</td><td>云原生</td><td>笔试后，没后续</td><td></td></tr></tbody></table><h1>快手容器云二面（2023-9-25）</h1><ol><li>自我介绍</li><li>介绍美团的实习经历</li><li>详细介绍两个项目</li><li>介绍 kubevela 的开源经历</li><li>介绍 openkruise 的开源经历</li><li>介绍 smartx 工作</li><li>介绍 cgroup</li><li>cgroup 和 namespace 的关系和区别</li><li>如果限制一个进程使用的 cpu 核数</li><li>cpu_quota 的原理</li><li>k 8 s 调度器流程</li><li>是否有自己做过调度器和 apiserver 的二开</li><li>webhook 原理</li><li>csi &amp; cni 原理</li><li>pod 创建时是如何分配 ip 的</li><li>是否了解设计模式</li><li>设计时如何提升程序扩展性</li><li>做题: 复制带随机指针的链表</li></ol><h1>滴滴一面（2023-9-25）</h1><ol><li>自我介绍</li><li>美团实习经历介绍</li><li>kubevela 开源经历</li><li>openkruise 开源经历</li><li>smartx 实习经历</li><li>采集了哪些指标</li><li>cgroup v 1 v 2 区别</li><li>k 8 s 中创建 deployment 的流程</li><li>做题: 链表倒数第 n 个节点</li></ol><h1>虹软服务端二面（2023-9-21）</h1><ol><li>做题：二分查找（递归和迭代两种做法）</li><li>做题：二叉树中查找公共父节点</li><li>物理题</li></ol><h1>字节创作者四面加面（2023-9-13）</h1><ol><li>自我介绍</li><li>TinyKV 架构设计以及实现</li><li>数据同步是如何实现的</li><li>MySQL 的主从复制是如何实现的</li><li>为什么一定要使用 Raft 协议</li><li>是否实现了分布式的共识</li><li>是否存在数据落后的情况</li><li>从节点数据落后情况下，如何解决？</li><li>是否了解 Go 中的 <code>sync.ones</code></li><li>做题：SQL 题</li><li>做题：最长递增子序列</li></ol><h1>腾讯云数据库管控四面（2023-9-7）</h1><ol><li>觉得自己前面面试有什么答得不好的地方吗</li><li>前面的面试官有什么问题，当时答得不好，后面回去了解过了吗</li><li>还有什么公司在面</li><li>对于 base 有什么期望吗</li><li>percolator 算法分布式事务实现原理<br><em>6. cockroachDB 如何优化分布式事务</em><br><em>7. 如果两个事务发生死锁如何处理，回滚事务的开销太大，如何不会滚</em></li><li>kubernetes 提交一个 statefulset 会发生什么</li><li>了解 cgroup 底层是如何限制 CPU 的吗</li><li>如何解决 CPU 长时间占用的问题</li><li>了解操作系统中常用的 CPU 的调度算法吗</li><li>可以来提前实习吗</li></ol><h1>快手容器云一面（2023-9-6）</h1><ol><li>自我介绍</li><li>美团容器设施架构</li><li>容器模式的设计思路</li><li>容器 agent 与其他组件的耦合程度，是否依赖其他组件</li><li>容器控制面失效会怎么样</li><li>容器 agent 版本异常兜底是怎么实现的</li><li>对于 sidecar 容器美团这边是如何限制他的资源</li><li>为什么不采用业务服务和基础组件在一个镜像中的模式</li><li>openkruise 中镜像预拉取是怎么实现的<br><em>10. dragonfly 是怎么实现的有了解过吗</em></li><li>pod 的创建过程是怎样的</li><li>operater 的 informer 机制原理<br><em>13. informer watch 的时候如果失败了应该怎么做</em><br><em>14. informer watch 返回版本过老应该怎么处理</em></li><li>kubernetes 资源限制的实现原理</li><li>cgroup 原理</li><li>对于一个容器如果限制他的资源是 1 C 1 G，那么实际上 cgroup 是怎么操作的</li><li>1 C 是怎么限制的</li><li>如何限制 CPU 调度到哪个上核上<br><em>20. 了解 numa 吗</em><br><em>21. numa 中 cache 的</em></li><li>free 命令返回的指标都是什么含义</li><li>做题：反转链表</li></ol><h1>虹软云服务端一面（2023-9-6）</h1><ol><li>自我介绍</li><li>goroutine 的原理</li><li>go 垃圾回收机制</li><li>一个新的 goroutine，是如何决定使用放到 gmp 全局队列还是本地队列的</li><li>一个新的 goroutine，是如何决定分配到哪个 p 的队列中</li><li>goroutine 的状态有哪些</li><li>哪些操作会使 goroutine 陷入阻塞，之后运行时会进行哪些操作</li><li>自旋锁和互斥锁的区别</li><li>CAS 的原理</li><li>CAS 有什么问题，一定会保证并发安全吗</li><li>手写快排</li><li>大文件外部排序去重思路</li><li>redis 如何解决数据不一致的问题</li><li>mysql 有哪些存储引擎</li><li>什么是事务</li><li>什么是存储过程</li></ol><h1>字节内容安全三面（2023-9-6）</h1><ol><li>自我介绍</li><li>介绍一段项目经历</li><li>遇到的问题</li><li>kubevela 的经历介绍</li><li>一道智力题</li><li>两道 SQL 题</li><li>一道设计题</li></ol><h1>百度音视频中台三面（2023-9-4）</h1><ol><li>自我介绍</li><li>美团实习的项目经历</li><li>Kubevela 项目经历</li><li>开放性问题：如何看待现在大家这么卷</li><li>如何看待百度</li><li>对 chat gpt 如何看待</li><li>介绍自己使用的场景</li><li>是否了解他的原理和机制</li></ol><h1>腾讯云管控三面（2023-8-31）</h1><ol><li>自我介绍</li><li>做题：<ol><li>对称二叉树</li><li><a href="https://leetcode.cn/problems/remove-k-digits/">402. 移掉 K 位数字</a></li><li><a href="https://leetcode.cn/problems/lexicographical-numbers/">386. 字典序排数</a></li></ol></li><li>第 1 道题，空间复杂度</li><li>第 3 道题，是否可以使用 channel 优化</li><li>channel 原理</li><li>go 垃圾回收原理</li><li>go 内存分批原理</li><li>会不会有内存碎片的问题</li><li>美团 Hulk 团队工作内容</li><li>工作是否涉及到了跨地域路由</li></ol><h1>百度音视频中台二面（2023-9-2）</h1><ol><li>自我介绍</li><li>介绍项目经历</li><li>介绍对方部门工作</li><li>Go 语言并发设计</li><li>协程和线程区别</li><li>Redis 哨兵集群</li><li>MySQL 索引种类</li><li>MySQL 索引总是需要回表吗</li><li>做题：二叉搜索树转双向链表</li></ol><h1>京东零售一面（2023-9-1）</h1><blockquote><p>体验极差，不爱别伤害</p></blockquote><ol><li>美团实习的工作</li><li>对联合索引的理解</li><li>两张表联查，是先查询第一个表然后再查第二个，还是两张表 join 之后查询</li><li>翻页查询一个超大表，怎么对查询优化</li><li>Redis 缓存击穿、缓存雪崩、缓存穿透</li><li>推荐我使用 Java，说这样路子广一点，言下之意说我现在语言选择的不行，给我挂了</li></ol><h1>百度音视频中台一面（2023-8-31）</h1><ol><li>自我介绍</li><li>实习经历介绍，学到了什么</li><li>Go 中 <code>map</code> 以及 <code>sync.map</code> 原理</li><li>Go</li></ol><h1>字节内容安全二面（2023-8-30）</h1><ol><li>自我介绍</li><li>介绍一个项目</li><li>遇到的困难如何解决的</li><li>Redis 如何做分布式缓存相关</li><li>Redis 如何做到那么高效</li><li>做题：一道 SQL</li><li>做题：最长无重复子序列</li></ol><h1>腾讯云管控二面（2023-8-25）</h1><ol><li>自我介绍</li><li>全程项目</li></ol><h1>字节内容安全一面（2023-8-24）</h1><ol><li>介绍实习经历</li><li>是否遇到了问题，怎么解决的</li><li>go slice 原理</li><li>go slice 是否是并发安全的</li><li>多个协程 slice 并发写入的问题</li><li>mysql 的隔离机制</li><li>rc 和 rr 的区别</li><li>做题：股票买入的最佳时机，要求输出买入点和卖出点</li></ol><h1>腾讯云管控一面（2023-8-21）</h1><ol><li>自我介绍</li><li>做题：二叉树的最大深度</li><li>GMP 模型</li><li>Go 的垃圾回收机制</li><li>mysql 事务特性如何实现的</li><li>为什么有 redolog 还要有 binlog</li><li>操作系统的内存管理</li><li>linux 可以更改页的大小吗</li><li>32 位机器和 64 位机器最大的内存</li><li>用户空间可以访问全部的内存吗？（这里没答上）<ol><li>32 位</li><li>64 位</li></ol></li><li>TCP/IP 模型</li><li>TCP 存在的问题&amp;QUIC 如何解决的</li><li>TCP 可靠传输实现</li><li>vela top 工作内容</li><li>反问</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;总结&lt;/h1&gt;
&lt;h2 id=&quot;通过&quot;&gt;通过&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;公司&lt;/th&gt;
&lt;th&gt;部门&lt;/th&gt;
&lt;th&gt;岗位&lt;/th&gt;
&lt;th&gt;进度&lt;/th&gt;
&lt;th&gt;评价&lt;/th&gt;
&lt;th&gt;Offer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thea</summary>
      
    
    
    
    <category term="日记" scheme="https://siegelion.cn/categories/%E6%97%A5%E8%AE%B0/"/>
    
    
    <category term="面试" scheme="https://siegelion.cn/tags/%E9%9D%A2%E8%AF%95/"/>
    
  </entry>
  
  <entry>
    <title>Go panic&amp; recover 原理与用法</title>
    <link href="https://siegelion.cn/2023/07/28/Go%20panic&amp;recover/"/>
    <id>https://siegelion.cn/2023/07/28/Go%20panic&amp;recover/</id>
    <published>2023-07-28T15:32:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<h1>基本用法</h1><ul><li><code>panic</code> 能够改变程序的控制流，调用 <code>panic</code> 后会立刻停止执行当前函数的剩余代码，并在当前 Goroutine 中递归执行调用方的 <code>defer</code>。</li><li><code>panic</code> 只会触发<strong>当前</strong> Goroutine 的 <code>defer</code>，<code>panic</code> 允许在 <code>defer</code> 中嵌套多次调用；</li><li><code>recover</code> 可以中止 <code>panic</code> 造成的程序崩溃。它是一个只能在 <code>defer</code> 中发挥作用的函数，在其他作用域中调用不会发挥作用；</li></ul><h1>数据结构</h1><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> _panic <span class="hljs-keyword">struct</span> &#123;<br>argp      unsafe.Pointer<br>arg       <span class="hljs-keyword">interface</span>&#123;&#125;<br>link      *_panic<br>recovered <span class="hljs-keyword">bool</span><br>aborted   <span class="hljs-keyword">bool</span><br>pc        <span class="hljs-keyword">uintptr</span><br>sp        unsafe.Pointer<br>goexit    <span class="hljs-keyword">bool</span><br>&#125;<br></code></pre></td></tr></table></figure><ol><li><code>argp</code> 是指向 <code>defer</code> 调用时参数的指针；</li><li><code>arg</code> 是调用 <code>panic</code> 时传入的参数；</li><li><code>link</code> 指向了更早调用的 <code>runtime._panic</code> 结构；</li><li><code>recovered</code> 表示当前 <a href="https://draveness.me/golang/tree/runtime._panic"><code>runtime._panic</code></a> 是否被 <code>recover</code> 恢复；</li><li><code>aborted</code> 表示当前的 <code>panic</code> 是否被强行终止；</li><li><code>pc</code> 程序计数器；</li><li><code>sp</code> 堆栈指针；</li><li><code>goexit</code> 是为了配合 <code>pc</code> 和 <code>sp</code> 修复 <a href="https://draveness.me/golang/tree/runtime.Goexit"><code>runtime.Goexit</code></a> 带来的问题引入的；</li></ol><h1>原理</h1><h2 id="defer">defer</h2><p>defer 是一个面向编译器的声明，他会让编译器做两件事：</p><ol><li>编译器会将 defer 声明编译为 <code>runtime.deferproc(fn)</code>，这样运行时，会调用 <code>runtime.deferproc</code>，在 <code>deferproc</code> 中将 <code>defer</code> 挂到 Goroutine 的 <code>defer</code> 链上；</li><li>编译器会在函数 <code> return</code> 之前（注意，是 <code>return</code> 之前，而不是 <code>return xxx</code> 之前，后者不是一条原子指令），增加 <code>runtime.deferreturn</code> 调用；这样运行时，开始处理前面挂在 defer 链上的所有 defer。</li></ol><blockquote><p><code>deferreturn</code> 会先判断链表有没有 <code>defer</code>，然后 <code>jmpdefer</code> 去做 <code>defer</code> 声明的事情。</p></blockquote><h2 id="panic">panic</h2><p>编译器会将关键字 <code>panic</code> 转换成 <code>runtime.gopanic</code> 该函数的执行过程包含以下几个步骤：</p><ol><li>创建新的 <code>runtime._panic</code> 并添加到所在 Goroutine 的 <code>_panic</code> 链表的最前面；</li><li>在循环中不断从当前 Goroutine 的 <code>_defer</code> 中链表获取 <code>runtime._defer</code> 并调用 <code>runtime.reflectcall</code> 运行 <code>defer</code> 函数；</li><li>如果循环退出，则调用 <a href="https://draveness.me/golang/tree/runtime.fatalpanic"><code>runtime.fatalpanic</code></a> 中止整个程序；</li><li><code>runtime.fatalpanic</code> 实现了无法被恢复的程序崩溃，它在中止程序之前会通过 <code>runtime.printpanics</code> 打印出全部的 <code>panic</code> 消息以及调用时传入的参数；</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">gopanic</span><span class="hljs-params">(e <span class="hljs-keyword">interface</span>&#123;&#125;)</span></span> &#123;<br>gp := getg()<br>...<br><span class="hljs-keyword">var</span> p _panic<br>p.arg = e<br>p.link = gp._panic<br>gp._panic = (*_panic)(noescape(unsafe.Pointer(&amp;p)))<br><br><span class="hljs-keyword">for</span> &#123;<br>d := gp._defer<br><span class="hljs-keyword">if</span> d == <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">break</span><br>&#125;<br><br>d._panic = (*_panic)(noescape(unsafe.Pointer(&amp;p)))<br><br>reflectcall(<span class="hljs-literal">nil</span>, unsafe.Pointer(d.fn), deferArgs(d), <span class="hljs-keyword">uint32</span>(d.siz), <span class="hljs-keyword">uint32</span>(d.siz))<br><br>d._panic = <span class="hljs-literal">nil</span><br>d.fn = <span class="hljs-literal">nil</span><br>gp._defer = d.link<br>        <br>        ...<br>        <br>        pc := d.pc<br>        sp := unsafe.Pointer(d.sp)<br><br>freedefer(d)<br><span class="hljs-keyword">if</span> p.recovered &#123;<br>gp._panic = p.link<br><span class="hljs-keyword">for</span> gp._panic != <span class="hljs-literal">nil</span> &amp;&amp; gp._panic.aborted &#123;<br>gp._panic = gp._panic.link<br>&#125;<br><span class="hljs-keyword">if</span> gp._panic == <span class="hljs-literal">nil</span> &#123;<br>gp.sig = <span class="hljs-number">0</span><br>&#125;<br>gp.sigcode0 = <span class="hljs-keyword">uintptr</span>(sp)<br>gp.sigcode1 = pc<br>mcall(recovery)<br>throw(<span class="hljs-string">&quot;recovery failed&quot;</span>)<br>&#125;<br>&#125;<br><br>fatalpanic(gp._panic)<br>*(*<span class="hljs-keyword">int</span>)(<span class="hljs-literal">nil</span>) = <span class="hljs-number">0</span><br>&#125;<br></code></pre></td></tr></table></figure><h2 id="recover">recover</h2><p>到这里我们已经掌握了 <code>panic</code> 退出程序的过程，接下来将分析 <code>defer</code> 中的 <code>recover</code> 是如何中止程序崩溃的。编译器会将关键字 <code>recover</code> 转换成 <code>runtime.gorecover</code>：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">gorecover</span><span class="hljs-params">(argp <span class="hljs-keyword">uintptr</span>)</span> <span class="hljs-title">interface</span></span>&#123;&#125; &#123;<br>gp := getg()<br>p := gp._panic<br><span class="hljs-keyword">if</span> p != <span class="hljs-literal">nil</span> &amp;&amp; !p.recovered &amp;&amp; argp == <span class="hljs-keyword">uintptr</span>(p.argp) &#123;<br>p.recovered = <span class="hljs-literal">true</span><br><span class="hljs-keyword">return</span> p.arg<br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br></code></pre></td></tr></table></figure><p>该函数的实现很简单，如果当前 Goroutine 没有调用 <code>panic</code>，或 <code>panic</code> 已经被 <code>recover</code> 恢复过了那么该函数会直接返回 <code>nil</code>，这也是崩溃恢复在非 <code>defer</code> 中调用会失效的原因。</p><p>在正常情况下，它会修改 <code>runtime._panic</code> 的 <code>recovered</code> 字段，<code>runtime.gorecover</code> 函数中并不包含恢复程序的逻辑，程序的恢复是由 <a href="https://draveness.me/golang/tree/runtime.gopanic"><code>runtime.gopanic</code></a> 函数负责的。</p><h2 id="二者配合">二者配合</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">gopanic</span><span class="hljs-params">(e <span class="hljs-keyword">interface</span>&#123;&#125;)</span></span> &#123;<br>...<br><br><span class="hljs-keyword">for</span> &#123;<br><span class="hljs-comment">// 执行延迟调用函数，可能会设置 p.recovered = true</span><br>...<br><br>pc := d.pc<br>sp := unsafe.Pointer(d.sp)<br><br>...<br><span class="hljs-keyword">if</span> p.recovered &#123;<br>gp._panic = p.link<br><span class="hljs-keyword">for</span> gp._panic != <span class="hljs-literal">nil</span> &amp;&amp; gp._panic.aborted &#123;<br>gp._panic = gp._panic.link<br>&#125;<br><span class="hljs-keyword">if</span> gp._panic == <span class="hljs-literal">nil</span> &#123;<br>gp.sig = <span class="hljs-number">0</span><br>&#125;<br>gp.sigcode0 = <span class="hljs-keyword">uintptr</span>(sp)<br>gp.sigcode1 = pc<br>mcall(recovery)<br>throw(<span class="hljs-string">&quot;recovery failed&quot;</span>)<br>&#125;<br>&#125;<br>...<br>&#125;<br></code></pre></td></tr></table></figure><p>当 <code>gorecover</code> 设置了 <code>p.recovered</code>，<code>gopanic</code> 便会执行 <code>p.recovered=true</code> 对应的分支，它从 <code>runtime._defer</code> 中取出了 <code>defer</code> 的程序计数器 <code>pc</code> 和栈指针 <code>sp</code> 并调用 <code>runtime.recovery</code> 函数触发 Goroutine 的调度，调度之前会准备好 <code>sp</code>、<code>pc</code> 以及函数的返回值：</p><h3 id="recovery">recovery</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">recovery</span><span class="hljs-params">(gp *g)</span></span> &#123;<br>sp := gp.sigcode0<br>pc := gp.sigcode1<br><br>gp.sched.sp = sp<br>gp.sched.pc = pc<br>gp.sched.lr = <span class="hljs-number">0</span><br>gp.sched.ret = <span class="hljs-number">1</span><br>gogo(&amp;gp.sched)<br>&#125;<br></code></pre></td></tr></table></figure><p><code>runtime.recovery</code> 将调用 defer 的 pc 和 sp 设置到了当前 goroutine 的 sched 上，并且将 ret 设置为 1，然后执行 <code>gogo</code> 调度。</p><p>从 <code>runtime.deferproc</code> 的注释中我们会发现，当 <code>runtime.deferproc</code> 函数的返回值是 1 时，编译器生成的代码会直接跳转到调用方函数返回之前执行，</p><blockquote><p>根据我们之前的在 <code>defer</code> 章节的论述（编译器会在函数 <code> return</code> 之前，增加 <code>runtime.deferreturn</code> 调用。</p></blockquote><p>因此，程序会跳转到 <code>runtime.deferreturn</code> 调用，<code>runtime.deferreturn</code> 会开始处理下一个 <code>defer</code>，直到 <code>defer</code> 的链表为空，继续执行程序的正常流程，也即是退出。</p><h1>小结</h1><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/d1d14f733a9185b72e942599d48bf2b2.png" alt="img"></p><h1>无法捕获的 panic</h1><h2 id="内存溢出">内存溢出</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> &#123;<br><span class="hljs-keyword">defer</span> errorHandler()<br>_ = <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">int64</span>, <span class="hljs-number">1</span>&lt;&lt;<span class="hljs-number">40</span>)<br>fmt.Println(<span class="hljs-string">&quot;can recover&quot;</span>)<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">errorHandler</span><span class="hljs-params">()</span></span> &#123;<br><span class="hljs-keyword">if</span> r := <span class="hljs-built_in">recover</span>(); r != <span class="hljs-literal">nil</span> &#123;<br>fmt.Println(r)<br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="map-并发读写">map 并发读写</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs go"><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> &#123;<br><span class="hljs-keyword">defer</span> errorHandler()<br>m := <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">int</span>&#123;&#125;<br><br><span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> &#123;<br><span class="hljs-keyword">for</span> &#123;<br>m[<span class="hljs-string">&quot;x&quot;</span>] = <span class="hljs-number">1</span><br>&#125;<br>&#125;()<br><span class="hljs-keyword">for</span> &#123;<br>_ = m[<span class="hljs-string">&quot;x&quot;</span>]<br>&#125;<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">errorHandler</span><span class="hljs-params">()</span></span> &#123;<br><span class="hljs-keyword">if</span> r := <span class="hljs-built_in">recover</span>(); r != <span class="hljs-literal">nil</span> &#123;<br>fmt.Println(r)<br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="栈内存耗尽">栈内存耗尽</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> &#123;<br><span class="hljs-keyword">defer</span> errorHandler()<br><span class="hljs-keyword">var</span> f <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(a [1000]<span class="hljs-keyword">int64</span>)</span></span><br>f = <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(a [1000]<span class="hljs-keyword">int64</span>)</span></span> &#123;<br>f(a)<br>&#125;<br>f([<span class="hljs-number">1000</span>]<span class="hljs-keyword">int64</span>&#123;&#125;)<br>&#125;<br></code></pre></td></tr></table></figure><blockquote><p>对于栈不熟悉的同学可以看这篇文章： <a href="https://cloud.tencent.com/developer/tools/blog-entry?target=https://www.luozhiyun.com/archives/513">一文教你搞懂 Go 中栈操作</a> 。下面我简单说一下，栈的基本机制。</p><p>在Go中，Goroutines 没有固定的堆栈大小。相反，它们开始时很小（比如4KB），在需要时增长/缩小，似乎给人一种 &quot;无限 &quot;堆栈的感觉。但是增长总是有限的，但是这个限制并不是来自于调用深度的限制，而是来自于堆栈内存的限制，在Linux 64位机器上，它是1GB。</p></blockquote><h2 id="尝试将-nil-函数交给-goroutine-启动">尝试将 nil 函数交给 goroutine 启动</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs javascript">func <span class="hljs-function"><span class="hljs-title">main</span>(<span class="hljs-params"></span>)</span> &#123;<br>defer errorHandler()<br><span class="hljs-keyword">var</span> f func()<br>go f()<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="所有线程都休眠了">所有线程都休眠了</h2><p>正常情况下，程序中不会所有线程都休眠，总是会有线程在运行处理我们的任务，例如：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs javascript">func <span class="hljs-function"><span class="hljs-title">main</span>(<span class="hljs-params"></span>)</span> &#123;<br>defer errorHandler()<br>go <span class="hljs-function"><span class="hljs-title">func</span>(<span class="hljs-params"></span>)</span> &#123;<br><span class="hljs-keyword">for</span> <span class="hljs-literal">true</span> &#123;<br>fmt.Println(<span class="hljs-string">&quot;alive&quot;</span>)<br>time.Sleep(time.Second*<span class="hljs-number">1</span>) <br>&#125;<br>&#125;()<br>&lt;-make(chan int)<br>&#125;<br></code></pre></td></tr></table></figure><h1>能够被捕获的异常</h1><h2 id="数组-slice-下标越界">数组 ( slice ) 下标越界</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs javascript">func <span class="hljs-function"><span class="hljs-title">foo</span>(<span class="hljs-params"></span>)</span>&#123;<br>defer <span class="hljs-function"><span class="hljs-title">func</span>(<span class="hljs-params"></span>)</span> &#123;<br><span class="hljs-keyword">if</span> r := recover(); r != nil &#123;<br>fmt.Println(r)<br>&#125;<br>&#125;()<br><span class="hljs-keyword">var</span> bar = []int&#123;<span class="hljs-number">1</span>&#125;<br>fmt.Println(bar[<span class="hljs-number">1</span>])<br>&#125;<br><br>func <span class="hljs-function"><span class="hljs-title">main</span>(<span class="hljs-params"></span>)</span>&#123; <br>foo()<br>fmt.Println(<span class="hljs-string">&quot;exit&quot;</span>)<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="空指针异常">空指针异常</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs javascript">func <span class="hljs-function"><span class="hljs-title">foo</span>(<span class="hljs-params"></span>)</span>&#123;<br>defer <span class="hljs-function"><span class="hljs-title">func</span>(<span class="hljs-params"></span>)</span> &#123;<br><span class="hljs-keyword">if</span> r := recover(); r != nil &#123;<br>fmt.Println(r)<br>&#125;<br>&#125;()<br><span class="hljs-keyword">var</span> bar *int<br>fmt.Println(*bar)<br>&#125;<br><br>func <span class="hljs-function"><span class="hljs-title">main</span>(<span class="hljs-params"></span>)</span>&#123;<br>foo()<br>fmt.Println(<span class="hljs-string">&quot;exit&quot;</span>)<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="往已经-close-的-chan-中发送数据">往已经 close 的 chan 中发送数据</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs javascript">func <span class="hljs-function"><span class="hljs-title">foo</span>(<span class="hljs-params"></span>)</span>&#123;<br>defer <span class="hljs-function"><span class="hljs-title">func</span>(<span class="hljs-params"></span>)</span> &#123;<br><span class="hljs-keyword">if</span> r := recover(); r != nil &#123;<br>fmt.Println(r)<br>&#125;<br>&#125;()<br><span class="hljs-keyword">var</span> bar = make(chan int, <span class="hljs-number">1</span>)<br>close(bar)<br>bar&lt;-<span class="hljs-number">1</span><br>&#125;<br><br>func <span class="hljs-function"><span class="hljs-title">main</span>(<span class="hljs-params"></span>)</span>&#123;<br>foo()<br>fmt.Println(<span class="hljs-string">&quot;exit&quot;</span>)<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="类型断言">类型断言</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs javascript">func <span class="hljs-function"><span class="hljs-title">foo</span>(<span class="hljs-params"></span>)</span>&#123;<br>defer <span class="hljs-function"><span class="hljs-title">func</span>(<span class="hljs-params"></span>)</span> &#123;<br><span class="hljs-keyword">if</span> r := recover(); r != nil &#123;<br>fmt.Println(r)<br>&#125;<br>&#125;()<br><span class="hljs-keyword">var</span> i interface&#123;&#125; = <span class="hljs-string">&quot;abc&quot;</span><br>_ = i.([]string)<br>&#125;<br><br>func <span class="hljs-function"><span class="hljs-title">main</span>(<span class="hljs-params"></span>)</span>&#123;<br>foo()<br>fmt.Println(<span class="hljs-string">&quot;exit&quot;</span>)<br>&#125;<br></code></pre></td></tr></table></figure><h1>参考</h1><ul><li><a href="https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-panic-recover/">https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-panic-recover/</a></li><li><a href="https://ieevee.com/tech/2017/11/23/go-panic.html">https://ieevee.com/tech/2017/11/23/go-panic.html</a></li><li><a href="https://cloud.tencent.com/developer/article/1905179">https://cloud.tencent.com/developer/article/1905179</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;基本用法&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;panic&lt;/code&gt; 能够改变程序的控制流，调用 &lt;code&gt;panic&lt;/code&gt; 后会立刻停止执行当前函数的剩余代码，并在当前 Goroutine 中递归执行调用方的 &lt;code&gt;defer&lt;/code&gt;。&lt;/l</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Go" scheme="https://siegelion.cn/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>浅析 Go 垃圾回收</title>
    <link href="https://siegelion.cn/2023/03/20/Go%20%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%8E%9F%E7%90%86/"/>
    <id>https://siegelion.cn/2023/03/20/Go%20%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%8E%9F%E7%90%86/</id>
    <published>2023-03-20T15:32:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<h1>垃圾回收</h1><h1>垃圾回收器种类</h1><p>GC 实现方式包括：</p><ul><li>追踪式，分为多种不同类型，例如：<ul><li>标记清扫：从根对象出发，将确定存活的对象进行标记，并清扫可以回收的对象。</li><li>标记 <strong>整理</strong>：为了解决内存碎片问题而提出，在标记过程中，将对象尽可能整理到一块连续的内存上。</li><li>增量式：将标记与清扫的过程分批执行，每次执行很小的部分，从而增量的推进垃圾回收，达到近似实时、几乎无停顿的目的。</li><li>增量整理：在增量式的基础上，增加对对象的 <strong>整理</strong> 过程。</li><li>分代式：将对象根据存活时间的长短进行分类，存活时间小于某个值的为年轻代，存活时间大于某个值的为老年代，永远不会参与回收的对象为永久代。并根据分代假设（如果一个对象存活时间不长则倾向于被回收，如果一个对象已经存活很长时间则倾向于存活更长时间）对对象进行回收。</li></ul></li><li>引用计数：根据对象自身的引用计数来回收，当引用计数归零时立即回收。</li></ul><h1>Go 的垃圾回收机制</h1><p>Go 的 GC 目前使用的是无分代（对象没有代际之分）、不整理（回收过程中不对对象进行移动与整理）、并发（与用户代码并发执行）的三色标记清扫算法。原因 [1] 在于：</p><ol><li>不整理：Go 使用的是基于 tcmalloc 的现代内存分配算法，基本没有内存碎片问题，对对象进行整理不会带来实质性的性能提升。</li><li>不分代：但 Go 的编译器会通过 <strong>逃逸分析</strong> 将大部分新生对象存储在栈上（栈直接被回收），只有那些需要长期存在的对象才会被分配到需要进行垃圾回收的堆中。因此在 Go 中性能提升不大，并且 Go 设计团队的关注点并不在这。</li></ol><h2 id="三色标记法">三色标记法</h2><p>理解 <strong>三色标记法</strong> 的关键是理解对象的 <strong>三色抽象</strong> 以及 <strong>波面（wavefront）推进</strong> 这两个概念。</p><p>从垃圾回收器的视角来看，三色抽象规定了三种不同类型的对象，并用不同的颜色相称：</p><ul><li>白色对象（可能死亡）：未被回收器访问到的对象。在回收开始阶段，所有对象均为白色，当回收结束后，白色对象均不可达。</li><li>灰色对象（波面）：已被回收器访问到的对象，但回收器需要对其中的一个或多个指针进行扫描，因为他们可能还指向白色对象。</li><li>黑色对象（确定存活）：已被回收器访问到的对象，其中所有字段都已被扫描，黑色对象中任何一个指针都不可能直接指向白色对象。</li></ul><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230228123631.png" alt="image.png"></p><h2 id="STW">STW</h2><p>垃圾回收的过程中涉及两种流程：赋值器和染色器，为了防止在染色的同时，某些对象之间的关系发生改变，进而导致垃圾回收发生错误，需要进行 STW。STW 可以是 Stop The World 的缩写，也可以是 Start The World 的缩写。通常意义上指的是从 Stop The World 到 Start The World 这一段时间间隔。垃圾回收过程中为了保证准确性、防止无止境的内存增长等问题而不可避免的需要停止赋值器进一步操作对象图以完成垃圾回收。这一动作发生时这一段时间间隔，即万物静止。</p><h2 id="并发标记清楚法">并发标记清楚法</h2><p>传统的垃圾收集算法会在垃圾收集的执行期间暂停应用程序，一旦触发垃圾收集，垃圾收集器会抢占 CPU 的使用权占据大量的计算资源以完成标记和清除工作，然而很多追求实时的应用程序无法接受长时间的 STW。</p><p>并发（Concurrent）的垃圾收集不仅能够减少程序的最长暂停时间，还能减少整个垃圾收集阶段的时间，通过开启读写屏障、<strong>利用多核优势与用户程序并行执行</strong>，并发垃圾收集器确实能够减少垃圾收集对应用程序的影响：</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731202430.png" alt="image.png"></p><p>但并发标记清除中面临的一个根本问题就是如何保证标记与清除过程的正确性。所以需要引入屏障技术保证染色器和赋值器可以正确工作。</p><h2 id="屏障机制">屏障机制</h2><p>写屏障是一个在并发垃圾回收器中才会出现的概念，垃圾回收器的正确性体现在：<strong>不应出现对象的丢失，也不应错误的回收还不需要回收的对象。</strong></p><p>可以证明，当以下两个条件同时满足时会破坏垃圾回收器的正确性：</p><ul><li><strong>条件 1</strong>: 赋值器修改对象图，导致某一黑色对象引用白色对象；</li><li><strong>条件 2</strong>: 从灰色对象出发，到达白色对象的、未经访问过的路径被赋值器破坏。</li></ul><p>只要能够避免其中任何一个条件，则不会出现对象丢失的情况，因为：</p><ul><li><p>如果条件 1 被避免，则所有白色对象均被灰色对象引用，没有白色对象会被遗漏；</p></li><li><p>如果条件 2 被避免，即便白色对象的指针被写入到黑色对象中，但从灰色对象出发，总存在一条没有访问过的路径，从而找到到达白色对象的路径，白色对象最终不会被遗漏。</p></li><li><p>强三色不变性：条件 1，条件 2 都不能出现。</p></li><li><p>弱三色不变性：可以允许条件 1 出现。</p></li></ul><p>有两种非常经典的写屏障：Dijkstra 插入屏障和 Yuasa 删除屏障。</p><h4 id="插入屏障（Dijkstra）-灰色赋值器">插入屏障（Dijkstra）- 灰色赋值器</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-comment">// 灰色赋值器 Dijkstra 插入屏障  </span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">DijkstraWritePointer</span><span class="hljs-params">(slot *unsafe.Pointer, ptr unsafe.Pointer)</span></span> &#123;  <br>    shade(ptr) <span class="hljs-comment">//先将新下游对象 ptr 标记为灰色  </span><br>    *slot = ptr  <br>&#125;  <br>  <br><span class="hljs-comment">//说明：  </span><br>添加下游对象(当前下游对象slot, 新下游对象ptr) &#123;     <br>  <span class="hljs-comment">//step 1  </span><br>  标记灰色(新下游对象ptr)     <br>    <br>  <span class="hljs-comment">//step 2  </span><br>  当前下游对象slot = 新下游对象ptr                      <br>&#125;  <br>  <br><span class="hljs-comment">//场景：  </span><br>A.添加下游对象(<span class="hljs-literal">nil</span>, B)   <span class="hljs-comment">//A 之前没有下游， 新添加一个下游对象B， B被标记为灰色  </span><br>A.添加下游对象(C, B)     <span class="hljs-comment">//A 将下游对象C 更换为B，  B被标记为灰色</span><br></code></pre></td></tr></table></figure><p>Dijkstra 插入屏障的基本思想是避免满足条件 1 以保证垃圾回收的正确性。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731203107.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731203118.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731203127.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731203150.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731203205.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731203217.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731203227.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731203244.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731203254.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731203303.png" alt="image.png"></p><h5 id="缺点">缺点</h5><ul><li>由于 Dijkstra 插入屏障的“保守”，在一次回收过程中可能会残留一部分对象没有回收成功，只有在下一个回收过程中才会被回收。</li><li>在标记阶段中，每次进行<strong>指针赋值操作</strong>时，都需要<strong>引入写屏障</strong>，这无疑会增加大量性能开销。</li><li>为了避免造成性能问题，<code>Go</code> 团队在最终实现时，<strong>没有为所有栈上的指针写操作，启用写屏障</strong>，但需要标记终止阶段 STW 时对这些栈进行<strong>重新扫描</strong>。</li></ul><ol><li><strong>为何需要在一遍标记清扫后进行 STW 然后扫描一次栈？</strong><ul><li>个人理解是：因为，由于没有对栈应用写屏障，导致如果存在栈上对象引用了白色的堆上对象，如果不 STW 重新扫描，会导致堆上正在使用的对象被错误回收。</li></ul></li><li><strong>堆上的对象为什么会存在这个白色对象？</strong><ul><li>个人理解是：对象不会凭空产生，这个对象是被其他对象创建的，和其它对象有引用关系，但在染色器尚未覆盖到的情况下，父对象便删除和它的引用，由于目前是纯插入屏障，没有删除屏障，因此会导致这个白色对象的产生。</li></ul></li></ol><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230801103457.png" alt="image.png"></p><h4 id="删除屏障-（Yuasa）-黑色赋值器">删除屏障 （Yuasa）- 黑色赋值器</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-comment">// 黑色赋值器 Yuasa 屏障  </span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">YuasaWritePointer</span><span class="hljs-params">(slot *unsafe.Pointer, ptr unsafe.Pointer)</span></span> &#123;  <br>shade(*slot) 先将*slot标记为灰色  <br>*slot = ptr  <br>&#125;  <br>  <br><span class="hljs-comment">//说明：  </span><br>添加下游对象(当前下游对象slot， 新下游对象ptr) &#123;  <br><span class="hljs-comment">//step 1  </span><br><span class="hljs-keyword">if</span> (当前下游对象slot是灰色 || 当前下游对象slot是白色) &#123;  <br>标记灰色(当前下游对象slot) <span class="hljs-comment">//slot为被删除对象， 标记为灰色  </span><br>&#125;  <br><span class="hljs-comment">//step 2  </span><br>当前下游对象slot = 新下游对象ptr  <br>&#125;  <br>  <br><span class="hljs-comment">//场景  </span><br>A.添加下游对象(B, <span class="hljs-literal">nil</span>) <span class="hljs-comment">//A对象，删除B对象的引用。B被A删除，被标记为灰(如果B之前为白)  </span><br>A.添加下游对象(B, C) <span class="hljs-comment">//A对象，更换下游B变成C。B被A删除，被标记为灰(如果B之前为白)</span><br></code></pre></td></tr></table></figure><p>另一种比较经典的写屏障是黑色赋值器的 Yuasa 删除屏障，其基本思想是避免满足条件 2。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731204602.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731204611.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731204707.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731204715.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731204727.png" alt="image.png"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230731204746.png" alt="image.png"></p><h5 id="缺点-v2">缺点</h5><ol><li><p>删除写屏障也叫基于快照的写屏障方案，必须在起始时，STW 扫描整个栈（注意了，是所有的 goroutine 栈），保证<strong>所有堆上在用的对象</strong>都处于灰色保护下，保证的是弱三色不变式。</p></li><li><p><strong>为何需要在初始状态时扫描整个栈？</strong></p><ul><li>堆内存在被栈对象引用的白色堆对象，无法解决堆内黑色对象引用白色对象的问题。</li></ul></li></ol><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230801104501.png" alt="image.png"></p><h3 id="混合屏障">混合屏障</h3><p>实际上 Go 中并没有真正实现过删除写屏障，Go 在 <code>v1.8</code> 引入了混合屏障，混合屏障将插入屏障和删除屏障结合了起来。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-comment">// 添加下游对象的函数, 当前下游对象slot, 新下游对象ptr</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">HybridWritePointerSimple</span><span class="hljs-params">(slot *unsafe.Pointer, ptr unsafe.Pointer)</span></span> &#123;<br>    <span class="hljs-comment">// 1) 将被删除的下游对象标记为灰色</span><br>    shade(*slot)<br>    <span class="hljs-comment">// 2) 将新下游对象标记为灰色</span><br>    shade(ptr)<br>    <span class="hljs-comment">// 3) 当前下游对象slot = 新下游对象ptr</span><br>    *slot = ptr<br>&#125;<br></code></pre></td></tr></table></figure><p>混合写屏障逻辑如下：</p><ul><li><code>GC</code> 开始时只需将当前栈上所有对象标记为黑色，无须 <code>STW</code>。</li><li><code>GC</code> 期间在栈上创建的新对象均标记为黑色。</li><li>将被删除的下游对象标记为灰色。</li><li>将被添加的下游对象标记为灰色。</li></ul><h5 id="优点">优点</h5><ol><li>由于结合了删除屏障和插入屏障，导致混合屏障没有了插入屏障扫描一次后 STW 的问题。</li><li>由于结合了删除屏障和插入屏障，导致在初始时可以不初始化整个栈，而是只初始化一个 goroutine 的栈。</li></ol><p><strong>优化 1</strong></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230801105336.png" alt="image.png"></p><p><strong>优化 2</strong></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230801105439.png" alt="image.png"></p><h1>参考</h1><ul><li><a href="https://liangyaopei.github.io/2021/01/02/golang-gc-intro/">https://liangyaopei.github.io/2021/01/02/golang-gc-intro/</a></li><li><a href="https://colobu.com/2022/07/16/A-Guide-to-the-Go-Garbage-Collector/">https://colobu.com/2022/07/16/A-Guide-to-the-Go-Garbage-Collector/</a></li><li><a href="https://zhuanlan.zhihu.com/p/297177002">https://zhuanlan.zhihu.com/p/297177002</a></li><li><a href="https://liqingqiya.github.io/golang/gc/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/%E5%86%99%E5%B1%8F%E9%9A%9C/2020/07/24/gc5.html">https://liqingqiya.github.io/golang/gc/垃圾回收/写屏障/2020/07/24/gc5.html</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;垃圾回收&lt;/h1&gt;
&lt;h1&gt;垃圾回收器种类&lt;/h1&gt;
&lt;p&gt;GC 实现方式包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;追踪式，分为多种不同类型，例如：
&lt;ul&gt;
&lt;li&gt;标记清扫：从根对象出发，将确定存活的对象进行标记，并清扫可以回收的对象。&lt;/li&gt;
&lt;li&gt;标记 &lt;strong</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Go" scheme="https://siegelion.cn/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>浅析 Go 语言中的锁</title>
    <link href="https://siegelion.cn/2023/03/14/Go%20%E5%90%8C%E6%AD%A5%E5%8E%9F%E8%AF%AD%E4%B8%8E%E9%94%81/"/>
    <id>https://siegelion.cn/2023/03/14/Go%20%E5%90%8C%E6%AD%A5%E5%8E%9F%E8%AF%AD%E4%B8%8E%E9%94%81/</id>
    <published>2023-03-14T17:24:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<h1>锁</h1><h2 id="互斥锁与自旋锁">互斥锁与自旋锁</h2><ul><li>互斥锁：互斥锁又被称为无忙等待锁，当获取不到锁的时候，线程会被阻塞，等待锁空闲后被唤醒，线程被阻塞是由操作系统实现的，这个过程涉及到由用户态陷入内核态，涉及线程的切换。</li><li>自旋锁：自旋锁又被称为忙等待锁，获取不到锁的时候，一直占用 CPU 进行自旋检测锁的状态。</li></ul><blockquote><p>自旋锁和互斥锁的选择也是一个值得考虑的问题。<br>如果一个资源被占用一次的时间过短，那么自旋等待的代价要低于加锁后进行线程切换的代价，这时最好选用自旋锁。反之，使用互斥锁让线程阻塞并让出 CPU 是更好的选择。</p></blockquote><h2 id="读写锁">读写锁</h2><p>对于一些操作由于他们不会修改数据，因此这种对于这类操作可以并行执行，但同时我们也要保证修改数据的操作不能与其他操作并行执行，这就需要更加细粒度的锁，进入引出了读写锁。<br>读写锁还可以继续细分为：读优先锁和写优先锁，这二者都无法做到兼顾到写者和读者不会出现饥饿的情况，因此更好的办法是使用一个公平读写锁，按照 FIFO 的方式处理读写操作。</p><h2 id="悲观锁与乐观锁">悲观锁与乐观锁</h2><p><strong>悲观锁与乐观锁</strong></p><ul><li>悲观锁：假定发生冲突的概率会很高，因此读写数据前需要加锁，保证读写数据期间，数据不会被其他人修改，上文提到的互斥锁、自旋锁、读写锁都属于悲观锁。</li><li>乐观锁：假定发生冲突的概率较低，先修改完共享资源，再验证这段时间内有没有发生冲突，如果没有其他线程在修改资源则操作成功，如果发现有其他线程已经修改过这个资源，就放弃本次操作。</li></ul><blockquote><p>对于悲观锁和乐观锁的选择问题，只有在冲突概率非常低，且加锁成本非常高的场景时，才考虑使用乐观锁。</p></blockquote><h1>基本原语</h1><p>Go 语言中提供了两种类型的锁：互斥锁 <code>Mutex</code> 与读写锁 <code>RWMutex</code></p><h2 id="Mutex">Mutex</h2><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230730112203.png" alt="image.png"></p><h3 id="初版">初版</h3><p>Mutex 初版的实现比较简单，通过设置一个 key 字段上。key 的含义比较简单，就是一个标志位，等于 0 表示锁未被持有，1 表示被某个 goroutine 持有，等于 n 表示还有 n-1 个等待者。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230730113300.png" alt="image.png"></p><h4 id="加锁">加锁</h4><ul><li>加锁（Lock）的过程首先是给 key 加 1：<ul><li>如果 key 返回 1，则表示当前 goroutine 占有了这把锁，其它 goroutine 只能做候选者。</li><li>如果 key 返回 n（n &gt; 1），这说明当前有其它 gorutine 正在占用这把锁，所以接下来需要通过信号量机制将当前 goroutine 挂起，加到等待队列，进入阻塞状态。</li></ul></li></ul><h4 id="解锁">解锁</h4><ul><li>解锁（Unlock）的过程是给 key 减 1：<ul><li>如果 key 返回 0，表示当前没有其它 goroutine 在等待，可以直接返回。</li><li>如果 key 返回 n (n &gt; 0)，说明还有其它 goroutine 在等待，因此需要通过信号量机制将等待队列中的其它 goroutine 唤醒。</li></ul></li></ul><h4 id="问题">问题</h4><p>初版 Mutex 在实现的时候，有两个问题：</p><ul><li>Unlock 调用无限制。</li><li>goroutine 唤醒机制性能低下。</li></ul><blockquote><p>Mutex ==本身并没有包含当前 goroutine 的任何信息==，因此 Unlock 方法能被任意的 goroutine 调用。这样会导致一个问题，如果某个 goroutine 不按套路来，随便调用 Unlock 函数，让标志位 key 清零，那么数据竞争的问题还是会出现。Mutex 的这个特性一直保留至今。因此使用 Mutex 的时候，一定要遵循 “==谁加锁，谁解锁==” 的原则。</p></blockquote><h3 id="第二版">第二版</h3><p>这一版 Mutex 的核心特点是一个 goroutinue 被唤醒后，不是立即执行任务，而是仍然和其他 goroutine 重复一遍和抢占锁的流程，这样新来的 goroutine 就有机会获取到锁，这就是所谓的<strong>给新人机会</strong>。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230730113311.png" alt="image.png"></p><h4 id="问题-v2">问题</h4><p>虽然这一版改进了第一版中只能按照 FIFO 方式进行抢锁，导致的后续的新来的锁等待时间过长的问题。但引入了新的问题：</p><ul><li>在加锁解锁的过程中，涉及到 goroutine 阻塞和唤醒的过程，系统调用涉及到不小的系统开销。如果 Lock 和 Unlock 之间的代码耗时很短，那么让新来的 goroutine 或者是醒着的 goroutine 抢占锁失败后，不立即睡眠，而是再尝试几次，说不定就能拿到锁了，尝试一定的次数之后，再进行原有的逻辑。</li></ul><h3 id="第三版">第三版</h3><p>这一版和第二版区别不大，改动是如果被唤醒的协程或者是新来的协程没有抢到锁，就会通过自旋的方式尝试检查锁是否被释放。尝试了一定的次数后，会再继续原有的逻辑。这里的自旋是指循环尝试。</p><h4 id="问题-v3">问题</h4><p>第三版在性能优化的道路上又前进了一步，几乎走到头了。但是有一种极端情况，新来的协程每次都能抢占到锁，那么等待中的协程就会一直处于等待之中，这就是所谓饥饿问题。</p><h3 id="第四版">第四版</h3><p>Go 语言的 <code>sync.Mutex</code> 由两个字段 <code>state</code> 和 <code>sema</code> 组成。其中 <code>state</code> 表示当前互斥锁的状态，而 <code>sema</code> 是用于控制锁状态的信号量。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> Mutex <span class="hljs-keyword">struct</span> &#123;<br>state <span class="hljs-keyword">int32</span><br>sema  <span class="hljs-keyword">uint32</span><br>&#125;<br></code></pre></td></tr></table></figure><h4 id="state">state</h4><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230221205514.png" alt=""></p><p>在默认情况下，互斥锁的所有状态位都是 0，<code>int32</code> 中的不同位分别表示了不同的状态：</p><ul><li><code>waitersCount</code> — 当前互斥锁上等待的 Goroutine 个数；</li><li><code>mutexStarving</code> — 当前的互斥锁进入饥饿状态；</li><li><code>mutexWoken</code> — 表示从正常模式被从唤醒；</li><li><code>mutexLocked</code> — 表示互斥锁的锁定状态；</li></ul><h4 id="正常模式与饥饿模式">正常模式与饥饿模式</h4><p><strong>正常模式</strong></p><p>锁的等待者会按照先进先出的顺序获取锁。但是刚被唤起的 Goroutine 与新创建的 Goroutine 竞争时，大概率会获取不到锁，为了减少这种情况的出现，一旦 Goroutine 超过 1 ms 没有获取到锁，它就会将当前互斥锁切换饥饿模式。</p><blockquote><p>因为锁被解锁的时候队头的 Goroutine 可能被阻塞了，这时候如果有一个正在获取锁的 Goroutine，为了提高吞吐量自然会交给这个正在运行且在索要锁的 Goroutine。</p></blockquote><p><strong>饥饿模式</strong></p><p>互斥锁会直接交给等待队列最前面的 Goroutine。新的 Goroutine 在该状态下不能获取锁、也不会进入自旋状态，它们只会在队列的末尾等待。如果一个 Goroutine 获得了互斥锁并且它在队列的末尾或者它等待的时间少于 1 ms，那么当前的互斥锁就会切换回正常模式。</p><blockquote><p>与正常模式不同的是，饥饿模式下锁会一直持有直到第一个等待者准备好获取锁。</p></blockquote><p><strong>评价</strong></p><p>与饥饿模式相比，正常模式下的互斥锁能够提供 <strong>更好的性能</strong>，饥饿模式的能 <strong>避免</strong> Goroutine 由于陷入等待无法获取锁而造成的 <strong>高尾延时</strong>。</p><h4 id="加锁与解锁">加锁与解锁</h4><blockquote><p>CAS ： <code>atomic.CompareAndSwapInt32(addr, old, new) bool</code> 方法，这个方法会先比较传入的地址的值是否是 old，如果是的话就尝试赋新值，如果不是的话就直接返回 <code>false</code>，保证数据只会被修改一次。</p></blockquote><p>我们在这一节中将分别介绍互斥锁的加锁和解锁过程，它们分别使用 <code>sync.Mutex.Lock</code> 和 <code>sync.Mutex.Unlock</code> 方法。</p><h5 id="加锁-v2">加锁</h5><p>互斥锁的加锁是靠 <code>sync.Mutex.Lock</code> 完成的：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(m *Mutex)</span> <span class="hljs-title">Lock</span><span class="hljs-params">()</span></span> &#123;<br><span class="hljs-keyword">if</span> atomic.CompareAndSwapInt32(&amp;m.state, <span class="hljs-number">0</span>, mutexLocked) &#123;<br><span class="hljs-keyword">return</span><br>&#125;<br>m.lockSlow()<br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230314172939.png" alt="image.png"></p><p><strong>快速加锁</strong></p><p>利用 CAS 函数，判断锁的状态是否处于初始状态，如果是则直接对锁上锁。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(m *Mutex)</span> <span class="hljs-title">Lock</span><span class="hljs-params">()</span></span> &#123;<br><span class="hljs-keyword">if</span> atomic.CompareAndSwapInt32(&amp;m.state, <span class="hljs-number">0</span>, mutexLocked) &#123;<br><span class="hljs-keyword">return</span><br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><strong>慢速加锁</strong></p><p>如果互斥锁的状态不是 0（初始状态） 时就会调用 <code>sync.Mutex.lockSlow</code> 尝试通过自旋（Spinnig）等方式等待锁的释放，该方法的主体是一个非常大 for 循环，这里将它分成几个部分介绍获取锁的过程：</p><ol><li>判断当前 Goroutine 能否进入自旋。</li><li>通过自旋等待互斥锁的释放：一旦当前 Goroutine 能够进入自旋就会执行 30 次的 <code>PAUSE</code> 指令，该指令只会占用 CPU 并消耗 CPU 时间。处理了自旋相关的特殊逻辑之后，互斥锁会根据上下文计算当前互斥锁最新的状态。如果最终没有通过获得锁，不断尝试获取锁并陷入休眠等待信号量的释放，一旦当前 Goroutine 可以获取信号量，它就会立刻返回。</li></ol><p><strong>如何判断当前 Goroutine 能否进入自旋？</strong></p><ol><li>互斥锁只有在正常模式才能进入自旋；</li><li><code>runtime.sync_runtime_canSpin</code> 需要返回 <code>true</code>：<ol><li>运行在多 CPU 的机器上；</li><li>当前 Goroutine 为了获取该锁进入自旋的次数小于四次；</li><li>当前机器上至少存在一个正在运行的处理器 P 并且处理的运行队列为空；</li></ol></li></ol><h5 id="解锁-v2">解锁</h5><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230314173046.png" alt="image.png"></p><p>互斥锁的解锁过程相对简单，该过程会先使用 <code>sync/atomic.AddInt32</code> 函数快速解锁。</p><ol><li>如果该函数返回的新状态等于 0（回到了初始状态），当前 Goroutine 就成功解锁了互斥锁。</li><li>如果该函数返回的新状态不等于 0，这段代码会调用 <code>sync.Mutex.unlockSlow</code> 开始慢速解锁。<ol><li>会先校验锁状态的合法性：<ol><li>如果当前互斥锁已经被解锁过了会直接抛出异常 <code>sync: unlock of unlocked mutex</code> 中止当前程序。</li></ol></li><li>在正常模式下，上述代码会使用如下所示的处理过程：<ol><li>如果互斥锁不存在等待者或者互斥锁的 <code>mutexLocked</code>、<code>mutexStarving</code>、<code>mutexWoken</code> 状态不都为 0，那么当前方法可以直接返回，不需要唤醒其他等待者；</li><li>如果互斥锁存在等待者，唤醒等待者，等待者按照正常模式的抢锁方法竞争锁。</li></ol></li><li>在饥饿模式下，将当前锁交给下一个正在尝试获取锁的等待者，等待者被唤醒后会得到锁，在这时互斥锁还不会退出饥饿状态，通过判断条件确认是否退出饥饿模式。</li></ol></li></ol><h2 id="RWMutex">RWMutex</h2><p>读写互斥锁 <code>sync. RWMutex</code> 是细粒度的互斥锁，它不限制资源的并发读，但是读写、写写操作无法并行执行。</p><p><code>sync. RWMutex</code> 中总共包含以下 5 个字段：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> RWMutex <span class="hljs-keyword">struct</span> &#123;<br>w           Mutex<br>writerSem   <span class="hljs-keyword">uint</span> <span class="hljs-number">32</span><br>readerSem   <span class="hljs-keyword">uint</span> <span class="hljs-number">32</span><br>readerCount <span class="hljs-keyword">int</span> <span class="hljs-number">32</span><br>readerWait  <span class="hljs-keyword">int</span> <span class="hljs-number">32</span><br>&#125;<br></code></pre></td></tr></table></figure><ul><li><code>w</code>： 复用互斥锁提供的能力；</li><li><code>writerSem</code> 和 <code>readerSem</code> ： 信号量，分别用于写等待读和读等待写：</li><li><code>readerCount</code>： 存储了当前正在执行的读操作数量；</li><li><code>readerWait</code> ：表示当写操作被阻塞时等待的读操作个数；</li></ul><h3 id="写锁">写锁</h3><h4 id="加锁-v3">加锁</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(rw *RWMutex)</span> <span class="hljs-title">Lock</span> <span class="hljs-params">()</span></span> &#123;<br>    <span class="hljs-comment">// 首先解决其他 writer 竞争问题</span><br>    rw.w.Lock ()<br>    <span class="hljs-comment">// 反转 readerCount，告诉 reader 有 writer 竞争锁</span><br>    r := atomic.AddInt32 (&amp;rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders<br>    <span class="hljs-comment">// 如果当前有 reader 持有锁，那么需要等待</span><br>    <span class="hljs-keyword">if</span> r != <span class="hljs-number">0</span> &amp;&amp; atomic.AddInt32 (&amp;rw.readerWait, r) != <span class="hljs-number">0</span> &#123;<br>        runtime_SemacquireMutex (&amp;rw.writerSem, <span class="hljs-literal">false</span>, <span class="hljs-number">0</span>)<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>首先调用互斥锁的 lock，获取到互斥锁之后：</p><ol><li><code>atomic.AddInt32(&amp;rw. readerCount, -rwmutexMaxReaders)</code>  调用这个函数阻塞后续的读操作。</li><li>如果计算之后当前仍然有其他 Goroutine 持有读锁，那么就调用 <code>runtime_SemacquireMutex</code>  休眠当前的 Goroutine 等待所有的读操作完成。</li></ol><h4 id="解锁-v3">解锁</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(rw *RWMutex)</span> <span class="hljs-title">Unlock</span> <span class="hljs-params">()</span></span> &#123;<br>    <span class="hljs-comment">// 告诉 reader 没有活跃的 writer 了</span><br>    r := atomic.AddInt32(&amp;rw.readerCount, rwmutexMaxReaders)<br>    <br>    <span class="hljs-comment">// 唤醒阻塞的 reader 们</span><br>    <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; <span class="hljs-keyword">int</span> (r); i++ &#123;<br>        runtime_Semrelease(&amp;rw.readerSem, <span class="hljs-literal">false</span>, <span class="hljs-number">0</span>)<br>    &#125;<br>    <span class="hljs-comment">// 释放内部的互斥锁</span><br>    rw.w.Unlock ()<br>&#125;<br></code></pre></td></tr></table></figure><p>解锁的操作，会先调用 <code>atomic.AddInt32(&amp;rw. readerCount, rwmutexMaxReaders)</code>  将恢复之前写入的负数，然后根据当前有多少个读操作在等待，循环唤醒</p><h3 id="读锁">读锁</h3><h4 id="加锁-v4">加锁</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(rw *RWMutex)</span> <span class="hljs-title">RLock</span> <span class="hljs-params">()</span></span> &#123;<br>    <span class="hljs-keyword">if</span> atomic.AddInt32(&amp;rw. readerCount, <span class="hljs-number">1</span>) &lt; <span class="hljs-number">0</span> &#123;<br>        <span class="hljs-comment">// rw.readerCount 是负值的时候，意味着此时有 writer 等待请求锁，因为 writer 优先级高，所以把后来的 reader 阻塞休眠</span><br>        runtime_SemacquireMutex (&amp;rw. readerSem, <span class="hljs-literal">false</span>, <span class="hljs-number">0</span>)<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>首先是读锁， <code>atomic.AddInt32(&amp;rw.readerCount, 1)</code>  调用这个原子方法，对当前在读的数量加一，如果返回负数，那么说明当前有其他写锁，这时候就调用 <code>runtime_SemacquireMutex</code>  休眠 Goroutine 等待被唤醒。</p><h4 id="解锁-v4">解锁</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(rw *RWMutex)</span> <span class="hljs-title">RUnlock</span> <span class="hljs-params">()</span></span> &#123;<br>    <span class="hljs-keyword">if</span> r := atomic.AddInt32(&amp;rw.readerCount, <span class="hljs-number">-1</span>); r &lt; <span class="hljs-number">0</span> &#123;<br>        rw.rUnlockSlow (r) <span class="hljs-comment">// 有等待的 writer</span><br>    &#125;<br>&#125;<br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(rw *RWMutex)</span> <span class="hljs-title">rUnlockSlow</span> <span class="hljs-params">(r <span class="hljs-keyword">int</span> 32)</span></span> &#123;<br>    <span class="hljs-keyword">if</span> atomic.AddInt32(&amp;rw.readerWait, <span class="hljs-number">-1</span>) == <span class="hljs-number">0</span> &#123;<br>        <span class="hljs-comment">// 最后一个 reader 了，writer 终于有机会获得锁了</span><br>        runtime_Semrelease(&amp;rw.writerSem, <span class="hljs-literal">false</span>, <span class="hljs-number">1</span>)<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>解锁的时候对正在读的操作减一，如果返回值小于 0 那么说明当前有在写的操作，这个时候调用 <code>rUnlockSlow</code>  进入慢速通道。慢速解锁时，会先对 <code>readerWait</code> 减一，当该值为 0 说明没有在读的操作了，可以唤醒等待的写 Goroutine。</p><h3 id="总结">总结</h3><p>Go 中的读写锁，写写冲突是通过封装在读写锁里的互斥锁解决的。读写冲突，是通过 <code>readerCount</code> 和 <code>readerWait</code> 这两个变量解决的。</p><ul><li>写者获取锁：<ol><li>写者获取到互斥锁后会将 <code>readerCount</code> 加负数最大值，并将原 <code>readerCount</code> 记录在 <code>readerWait</code> 中。</li><li>读者想要获取锁时，对 <code>readerCount</code> 加 1，发现为负则阻塞。</li><li>写者退出时，对 <code>readerCount</code> 取反，依次唤醒读者。</li></ol></li><li>读者获取锁：<ol><li>读者会对 <code>readerCount</code> 加 1。</li><li>写者这个时候会将 <code>readerCount</code> 加负数最大值，然后再取反根据取反值是否为 0，判断是否要阻塞。</li><li>但这个时候如果再有读者进来，对 <code>readerCount</code> 加 1，肯定发现为负则阻塞，因此即使现在是前面的读者持有锁，后面的读者也无法运行。</li><li>因此写者的优先级会高于读者，读者退出之后会优先唤醒写者，而后释放互斥锁，因此先被唤醒的写者会阻塞在他们之前的读者。</li></ol></li></ul><h4 id="例子">例子</h4><ul><li>初始：<code>readerCount</code>：0，<code>readerWait</code>：0。</li><li>读者 1：当有一个读者便会将 <code>readerCount</code> 变量加一，<code>readerCount</code>：1。</li><li>写者 1：如果这时候出现了一个写者，便会将 <code>readerCount</code> 的值，保存在 <code>readerWait</code> 中，然后将 <code>readerCount</code> 赋一个最大读者数量的负值，<code>readerCount</code>：-10000，<code>readerWait</code>：1</li><li>写者 2：这时候如果再出现一个写者，由于先前的写者 1 获取了互斥锁，因此写者 2 会直接阻塞。</li><li>读者 2：这样后续的再出现新的读者，后续的读者 <code>readerCount</code> 变量加一后这个变量也是负的，读者就知道存在一个写者，读者便会阻塞自己，<code>readerCount</code>：-9999，<code>readerWait</code>：1</li><li>读者 1 完成：读者 1 完成自己的操作之后，将 <code>readerCount</code> 减一，但发现这时候的值是负的，就会知道存在等待的写者，进入慢速解锁模式，将 <code>readerWait</code> 减一，减一后如果为 0 则唤醒写者 1。</li></ul><h1>参考</h1><ul><li><a href="https://nxw.name/2021/golang-mutexde-shi-xian-yuan-li-1ef30cc7">Golang Mutex 的实现原理</a></li><li><a href="https://zhuanlan.zhihu.com/p/342706674">Go 中的 Mutex 设计原理详解（三）</a></li><li><a href="https://zhuanlan.zhihu.com/p/344977623">Go 中的 Mutex 设计原理详解（四）</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;锁&lt;/h1&gt;
&lt;h2 id=&quot;互斥锁与自旋锁&quot;&gt;互斥锁与自旋锁&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;互斥锁：互斥锁又被称为无忙等待锁，当获取不到锁的时候，线程会被阻塞，等待锁空闲后被唤醒，线程被阻塞是由操作系统实现的，这个过程涉及到由用户态陷入内核态，涉及线程的切换。&lt;/li&gt;
</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Go" scheme="https://siegelion.cn/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>Go 中的“面向对象”</title>
    <link href="https://siegelion.cn/2023/03/14/Go%20%E6%8E%A5%E5%8F%A3%E5%8E%9F%E7%90%86/"/>
    <id>https://siegelion.cn/2023/03/14/Go%20%E6%8E%A5%E5%8F%A3%E5%8E%9F%E7%90%86/</id>
    <published>2023-03-14T11:28:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<h1>Go 中的”面向对象“</h1><p><code>Golang</code> 不同于 <code>Java</code>，并不是完全面向对象的，他没有对象和类的概念。</p><p>而是如引入了 <code>struct</code> 和 <code>interface</code> 的概念，曾经看到过一个说法，我觉得说的还挺形象的，Go 没有面向对象只有字段集和方法集🐶。</p><h2 id="鸭子类型">鸭子类型</h2><p>鸭子类型的设计哲学是：如果某个东西，它具备鸭子拥有的一切能力，那么这个东西他就是一只鸭子。也即是说一个 <code>struct</code> 如果他实现了一个 <code>interface</code> 对应方法集中的全部方法，那么就可以被认为实现了这个接口。</p><p>这种设计思路关注对象的行为，而不是类型。这是动态类型语言如 <code>Python</code> 崇尚的一种设计哲学。例如，在 Python 中当调用此函数的时候，可以传入任意类型，只要它实现了 <code>say_hello()</code> 函数就可以。如果没有实现，运行过程中会出现错误。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">hello_world</span>(<span class="hljs-params">coder</span>):</span><br>    coder.say_hello()<br></code></pre></td></tr></table></figure><p><code>Golang</code> 虽然本身又是一门静态类型语言，但在其设计时借鉴了动态类型语言的设计方式，也实现了这种设计。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> duck <span class="hljs-keyword">interface</span> &#123;<br>eat()<br>swim()<br>&#125;<br><br><span class="hljs-keyword">type</span> maleDuck <span class="hljs-keyword">struct</span> &#123;<br>name <span class="hljs-keyword">string</span><br>&#125;<br><span class="hljs-keyword">type</span> femaleDuck <span class="hljs-keyword">struct</span> &#123;<br>name <span class="hljs-keyword">string</span><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s maleDuck)</span> <span class="hljs-title">eat</span><span class="hljs-params">()</span></span> &#123;<br>&#125;<br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s maleDuck)</span> <span class="hljs-title">swim</span><span class="hljs-params">()</span></span> &#123;<br>&#125;<br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s femaleDuck)</span> <span class="hljs-title">eat</span><span class="hljs-params">()</span></span> &#123;<br>&#125;<br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s femaleDuck)</span> <span class="hljs-title">swim</span><span class="hljs-params">()</span></span> &#123;<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">beDuck</span><span class="hljs-params">(aDuck duck)</span></span> &#123;<br>    aDuck.eat()<br>    aDuck.swim()<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> &#123;<br>    <span class="hljs-keyword">var</span> m maleDuck&#123;<span class="hljs-string">&quot;A&quot;</span>&#125;<br>    <span class="hljs-keyword">var</span> f femaleDuck&#123;<span class="hljs-string">&quot;B&quot;</span>&#125;<br>    beDuck(m)<br>    beDuck(f)<br>&#125;<br><br></code></pre></td></tr></table></figure><h2 id="方法调用">方法调用</h2><h3 id="值类型与指针类型">值类型与指针类型</h3><p>在调用方法的时候，值类型既可以调用 <code>值接收者</code> 的方法，也可以调用 <code>指针接收者</code> 的方法；指针类型既可以调用 <code>指针接收者</code> 的方法，也可以调用 <code>值接收者</code> 的方法。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">package</span> main<br><br><span class="hljs-keyword">import</span> <span class="hljs-string">&quot;fmt&quot;</span><br><br><span class="hljs-keyword">type</span> Person <span class="hljs-keyword">struct</span> &#123;<br>age <span class="hljs-keyword">int</span><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p Person)</span> <span class="hljs-title">howOld</span><span class="hljs-params">()</span> <span class="hljs-title">int</span></span> &#123;<br><span class="hljs-keyword">return</span> p.age<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *Person)</span> <span class="hljs-title">growUp</span><span class="hljs-params">()</span></span> &#123;<br>p.age += <span class="hljs-number">1</span><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> &#123;<br><span class="hljs-comment">// qcrao 是值类型</span><br>qcrao := Person&#123;age: <span class="hljs-number">18</span>&#125;<br><span class="hljs-comment">// 值类型 调用接收者也是值类型的方法</span><br>fmt.Println(qcrao.howOld())<br><span class="hljs-comment">// 值类型 调用接收者是指针类型的方法</span><br>qcrao.growUp()<br>fmt.Println(qcrao.howOld())<br><span class="hljs-comment">// ----------------------</span><br><span class="hljs-comment">// stefno 是指针类型</span><br>stefno := &amp;Person&#123;age: <span class="hljs-number">100</span>&#125;<br><span class="hljs-comment">// 指针类型 调用接收者是值类型的方法</span><br>fmt.Println(stefno.howOld())<br><span class="hljs-comment">// 指针类型 调用接收者也是指针类型的方法</span><br>stefno.growUp()<br>fmt.Println(stefno.howOld())<br>&#125;<br></code></pre></td></tr></table></figure><p>这其实是语法糖在起作用。</p><h3 id="值接收者与指针接收者">值接收者与指针接收者</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">package</span> main<br><br><span class="hljs-keyword">import</span> <span class="hljs-string">&quot;fmt&quot;</span><br><br><span class="hljs-keyword">type</span> coder <span class="hljs-keyword">interface</span> &#123;<br>code()<br>debug()<br>&#125;<br><br><span class="hljs-keyword">type</span> Gopher <span class="hljs-keyword">struct</span> &#123;<br>language <span class="hljs-keyword">string</span><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p Gopher)</span> <span class="hljs-title">code</span><span class="hljs-params">()</span></span> &#123;<br>fmt.Printf(<span class="hljs-string">&quot;I am coding %s language\n&quot;</span>, p.language)<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *Gopher)</span> <span class="hljs-title">debug</span><span class="hljs-params">()</span></span> &#123;<br>fmt.Printf(<span class="hljs-string">&quot;I am debuging %s language\n&quot;</span>, p.language)<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> &#123;<br><span class="hljs-keyword">var</span> c1 coder = &amp;Gopher&#123;<span class="hljs-string">&quot;Go&quot;</span>&#125;<br>c1.code()<br>c1.debug()<br><br>    <span class="hljs-keyword">var</span> c2 coder = Gopher&#123;<span class="hljs-string">&quot;Go&quot;</span>&#125;<br>c2.code()<br>c2.debug() <span class="hljs-comment">// fatal</span><br><br></code></pre></td></tr></table></figure><p>当一个将一个结构体的 <strong>值</strong> 或者 <strong>指针</strong> 赋值给一个 <strong>接口</strong> 后，就会展示出值/指针类型的接收者是否真正实现了一个接口的方法，因为并没有直接调用方法，因此这个时候语法糖便无法起作用了，也就露出了小犄角：</p><ul><li>实现了接收者是值类型的方法，页会隐含地也实现了接收者是指针类型的方法。</li><li>但实现了接收者是指针类型的方法，却不会实现了接收者是值类型的方法。</li></ul><h1>接口</h1><p><code>iface</code> 和 <code>eface</code> 都是 Go 中描述接口的底层结构体，区别在于 <code>iface</code> 描述的接口包含方法，而 <code>eface</code> 则是不包含任何方法的空接口：<code>interface&#123;&#125;</code>。</p><h2 id="空接口的结构">空接口的结构</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> eface <span class="hljs-keyword">struct</span> &#123; <br>_type *_type<br>data  unsafe.Pointer<br>&#125;<br></code></pre></td></tr></table></figure><ul><li><code>_type</code> 是 Go 语言类型的运行时表示，表示的是赋给空接口的对象的类型，其中包含了很多类型的元信息，例如：类型的大小、哈希、对齐以及种类等。</li><li><code>data</code> 则指向接口具体的值，一般而言是一个指向堆内存的指针。</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> _type <span class="hljs-keyword">struct</span> &#123;<br>size       <span class="hljs-keyword">uintptr</span><br>ptrdata    <span class="hljs-keyword">uintptr</span><br>hash       <span class="hljs-keyword">uint32</span><br>tflag      tflag<br>align      <span class="hljs-keyword">uint8</span><br>fieldAlign <span class="hljs-keyword">uint8</span><br>kind       <span class="hljs-keyword">uint8</span><br>equal      <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(unsafe.Pointer, unsafe.Pointer)</span> <span class="hljs-title">bool</span></span><br>gcdata     *<span class="hljs-keyword">byte</span><br>str        nameOff<br>ptrToThis  typeOff<br>&#125;<br></code></pre></td></tr></table></figure><ul><li><code>size</code> 字段存储了类型占用的内存空间，为内存空间的分配提供信息。</li><li><code>hash</code> 字段能够帮助我们快速确定类型是否相等。</li></ul><h2 id="非空接口的结构">非空接口的结构</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> iface <span class="hljs-keyword">struct</span> &#123;<br>tab  *itab<br>data unsafe.Pointer<br>&#125;<br></code></pre></td></tr></table></figure><p><code>iface</code> 内部维护两个指针：</p><ul><li><code>tab</code> 指向一个 <code>itab</code> 实体， 它表示接口的类型以及赋给这个接口的实体类型。</li><li><code>data</code> 则指向接口具体的值，一般而言是一个指向堆内存的指针。</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> itab <span class="hljs-keyword">struct</span> &#123;<br>inter  *interfacetype<br>_type  *_type<br>link   *itab<br>hash   <span class="hljs-keyword">uint32</span> <span class="hljs-comment">// copy of _type.hash. Used for type switches.</span><br>bad    <span class="hljs-keyword">bool</span>   <span class="hljs-comment">// type does not implement interface</span><br>inhash <span class="hljs-keyword">bool</span>   <span class="hljs-comment">// has this itab been added to hash?</span><br>unused [<span class="hljs-number">2</span>]<span class="hljs-keyword">byte</span><br>fun    [<span class="hljs-number">1</span>]<span class="hljs-keyword">uintptr</span> <span class="hljs-comment">// variable sized</span><br>&#125;<br></code></pre></td></tr></table></figure><ul><li><code>inter</code> 它描述的是接口的类型。</li><li><code>_type</code> 同上文空接口中的 <code>_type</code> 字段。</li><li><code>hash</code> 同上文中空接口中的 <code>hash</code> 字段。</li><li><code>fun</code> 是一个动态大小的数组，存储了一组函数指针。虽然该变量被声明成大小固定的数组，但是在使用时会通过原始指针获取其中的数据，所以 <code>fun</code> 数组中保存的元素数量是不确定的。</li></ul><p>重点看一下 <code>interfacetype</code> 类型，它描述的是接口的类型，可以看到，它包装了 <code>_type</code> 类型，<code>_type</code> 实际上是描述 Go 语言中各种数据类型的结构体。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> interfacetype <span class="hljs-keyword">struct</span> &#123;<br>typ     _type<br>pkgpath name<br>mhdr    []imethod<br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20230314115702.png" alt="image.png"></p><h2 id="接口的动态类型和动态值">接口的动态类型和动态值</h2><p>从源码里可以看到：<code>iface</code> 包含两个字段：<code>tab</code> 是接口表指针，指向类型信息；<code>data</code> 是数据指针，则指向具体的数据。它们分别被称为 <code>动态类型</code> 和 <code>动态值</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">package</span> main<br><br><span class="hljs-keyword">import</span> <span class="hljs-string">&quot;fmt&quot;</span><br><br><span class="hljs-keyword">type</span> Coder <span class="hljs-keyword">interface</span> &#123;<br>code()<br>&#125;<br><br><span class="hljs-keyword">type</span> Gopher <span class="hljs-keyword">struct</span> &#123;<br>name <span class="hljs-keyword">string</span><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(g Gopher)</span> <span class="hljs-title">code</span><span class="hljs-params">()</span></span> &#123;<br>fmt.Printf(<span class="hljs-string">&quot;%s is coding\n&quot;</span>, g.name)<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> &#123;<br><span class="hljs-keyword">var</span> c Coder<br>fmt.Println(c == <span class="hljs-literal">nil</span>)<br>fmt.Printf(<span class="hljs-string">&quot;c: %T, %v\n&quot;</span>, c, c)<br><br><span class="hljs-keyword">var</span> g *Gopher<br>fmt.Println(g == <span class="hljs-literal">nil</span>)<br><br>c = g<br>fmt.Println(c == <span class="hljs-literal">nil</span>)<br>fmt.Printf(<span class="hljs-string">&quot;c: %T, %v\n&quot;</span>, c, c)<br>&#125;<br></code></pre></td></tr></table></figure><p><strong>输出</strong></p><figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs crystal"><span class="hljs-literal">true</span><br><span class="hljs-symbol">c:</span> &lt;<span class="hljs-literal">nil</span>&gt;, &lt;<span class="hljs-literal">nil</span>&gt;<br><span class="hljs-literal">true</span><br><span class="hljs-literal">false</span><br><span class="hljs-symbol">c:</span> *main.Gopher, &lt;<span class="hljs-literal">nil</span>&gt;<br></code></pre></td></tr></table></figure><p>实际上对一个接口进行多次赋值的时候，实际进行修改的内容，也即动态类型与动态值这两个内容。</p><h2 id="接口断言">接口断言</h2><h3 id="非空接口">非空接口</h3><p>类型断言时会将目标类型的 <code>hash</code> 与接口变量中的 <code>itab.hash</code> 进行比较。</p><h3 id="空接口">空接口</h3><p>从 <code>eface._type</code> 中获取类型，仍然会使用 <code>_type.hash</code> 与变量的类型比较。</p><h2 id="接口转换原理">接口转换原理</h2><p>从 <code>iface</code> 的源码可以看到，实际上它包含接口的类型 <code>interfacetype</code> 和 实体类型的类型 <code>_type</code>，也就是说生成一个 <code>itab</code> 同时需要接口的类型和实体的类型。</p><p>当判定一种类型是否满足某个接口时，Go 使用类型的方法集和接口所需要的方法集进行匹配，如果类型的方法集完全包含接口的方法集，则可认为该类型实现了该接口。例如某类型有 <code>m</code> 个方法，某接口有 <code>n</code> 个方法，则很容易知道这种判定的时间复杂度为 <code>O(mn)</code>，Go 会对方法集的函数按照函数名的字典序进行排序，所以实际的时间复杂度为 <code>O(m+n)</code>。</p><h2 id="动态派发">动态派发</h2><p>动态派发（Dynamic dispatch）是在运行期间选择具体多态操作（方法或者函数）执行的过程，它是面向对象语言中的常见特性。Go 语言虽然不是严格意义上的面向对象语言，但是接口的引入为它带来了动态派发这一特性，调用接口类型的方法时，如果编译期间不能确认接口的类型，Go 语言会在运行期间决定具体调用该方法的哪个实现。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> &#123;<br><span class="hljs-keyword">var</span> c Duck = &amp;Cat&#123;Name: <span class="hljs-string">&quot;draven&quot;</span>&#125;<br>c.Quack()<br>c.(*Cat).Quack()<br>&#125;<br></code></pre></td></tr></table></figure><ol><li>第一次以 <code>Duck</code> 接口类型的身份调用，调用时需要经过运行时的动态派发。</li><li>第二次以 <code>*Cat</code> 具体类型的身份调用，编译期就会确定调用的函数。</li></ol><h1>参考</h1><ul><li><a href="https://qcrao.com/post/dive-into-go-interface/">深度解密 Go 语言之关于 interface 的 10 个问题</a></li><li><a href="https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-interface/#42-%E6%8E%A5%E5%8F%A3">Go 语言设计与实现</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;Go 中的”面向对象“&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;Golang&lt;/code&gt; 不同于 &lt;code&gt;Java&lt;/code&gt;，并不是完全面向对象的，他没有对象和类的概念。&lt;/p&gt;
&lt;p&gt;而是如引入了 &lt;code&gt;struct&lt;/code&gt; 和 &lt;code&gt;interface</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Go" scheme="https://siegelion.cn/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>记2022</title>
    <link href="https://siegelion.cn/2023/01/22/%E8%AE%B02022/"/>
    <id>https://siegelion.cn/2023/01/22/%E8%AE%B02022/</id>
    <published>2023-01-22T17:49:00.000Z</published>
    <updated>2024-06-07T12:50:45.750Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>2022 年过去了，我不想怀念他。</p></blockquote><p>一年一更的年记（流水账），终于在大年初一开始动笔。最近先后忙于论文开题与实习，终于有精力抽出时间开始回首这一年的林林总总点点滴滴，每年一更的年记既是记录也是总结更是对自己的督促。</p><p>如果让我来评价我的 2022，我会用稳中向好来描述它，这一年不紧不慢地过去了。这一年由于疫情管控的原因，我的大部分时间都是在校园中度过的，校园生活相比以前可以出校浪荡的时光总是那么的单调乏味，好在我的生活可以被很多东西填满，因此这一年过的并没有想象中的那么无聊。但这一年也没有想象中的热火朝天忙得不可开交，一切都是有条不紊的进行，如果让我给 2022 年打一个分我觉得会是 6.8 分吧。</p><p>反反复复的疫情让我没有太多时间可以去校外放荡，因此这生活只能就只能在西土城路 10 号院这片小天底下展开了。我的校园生活现在想来是那么的单调，每天宿舍——食堂——实验室——体育馆几点一线，白天在实验室坐牢，午饭晚饭和女朋友厮守一下，晚饭后健身房羽毛球运动一下，洗完澡后和女朋友游戏电影，这一年下来几乎没有几天跳出了这个模板。对我来说这种生活是没什么的，可能本身我也算不上一个爱玩的人，但是女朋友时常的抱怨加之今年她并不顺利的求职经历，不由得让我担心是否应该尽量给她的生活注入一点欢乐？</p><h1>摸索</h1><p>自己看来我应该是一个对事业看的比较重的人，这一年大部分的时间都花在了提升自己能力上，2023 届毕业生的惨淡求职情况不由得不让人担心，因此丝毫不敢松懈，尽力为自己的简历再添上一点经历，以便自己可以在求职时有更多的选择。2022 年上半年对互联网的唱衰便此起彼伏，论坛里几百楼回复的那个关于劝退互联网的贴子至今仍可以轻易找到，不由得不让人对就业前景产生担忧，那时候技术上基本只停留在了低并发的 WEB 系统的开发上（🐶），于是想要开始尝试做一些能够提升自己能力的事，迷茫的我在 2021 年底开始接触分布式系统，翻了翻野猪书，也开始写 MIT 6.824，最后报名参与了 Talent Plan 开始写 TinyKV，但遗憾由于第一次接触相关内容在 Project 2 处花了大量时间最终没能完成，于是 2021 年记中给自己定下的目标便是可以完成 6.824 和 TinyKV 中的一个。</p><p>2022 年春节过后回到学校后无可事事的我，每天在实验室的生活便是代码随想录与 TinyKV，虽然不知道怎么做是最有用的，但起码不敢让自己停下来，就这样平淡的度过了开学前的两个月，慢慢悠悠的写着，到五月中旬的时候，我把之前的代码都梳理了一遍，也基本完成了 Project 2 的内容（参照了大量前人的文档，基本每天都花了大量时间在 debug 上），恰好这时候我听说了 <a href="https://developers.google.com/open-source/gsoc/">GSOC</a> 由于向往谷歌这块招牌，我对此跃跃欲试，但无奈浏览了相关的介绍后发现难度过大而且很多项目都是老外主导的，沟通起来也存在一定障碍，并且由于接收的是来自全世界学生的申请导致他们的要求比较高，对于第一次参加开源项目的我来说能够申请到的可能性并不高因此遗憾放弃。</p><img src="https://summerofcode.withgoogle.com/assets/media/logo.svg" alt="Google Summer of Code"><p>不过巧的是后面在 Talent Plan 的群里又见到有同学推荐 <a href="https://summer-ospp.ac.cn/#/homepage">开源之夏</a>，这个相当于一个国内版的 GSOC，形式类似：报名申请——为开源项目贡献代码——结项发钱，不过由于申请者主要来自国内竞争压力小了很多，所以见到这个的第一时间我就决定要报名了，不想错过这次宝贵的机会，因为说起来研一结束的暑假是我开始实习之前最后一个可以用来做一个完整项目的时间了，那时候基本隔个几天就要打开项目列表的界面，看看又多了几个项目，有哪些项目是可以申请的，每个项目看起来都非常高大上，让我望而却步，但又由于想要挑战一下自己而跃跃欲试。</p><p><img src="https://summer-ospp.ac.cn/img/logo_zh.png" alt="img"></p><p>后面在 6 月初的时候豪哥又向我们安利了字节青训营，抱着试试看的心态也交了报名申请，很巧的是在组队的时候遇到了一个曾经翻过我博客的学弟，他也尝试参加过 Talent Plan，因此碰巧看到了我博客上的内容，正巧夏季的新一轮 Talent Plan 又开始了报名，因此不甘心的他又想拉着我参加这次的 Talent Plan，盛情难却最终我开始三线并行。</p><blockquote><p>不由得让人感叹圈子真小，这一点我现在更有感触了。</p><p>通过认识一个这个圈子里的大佬，通过他的社交圈子，就会发现他的好友是你曾经关注过的另一位大佬。</p></blockquote><p>我在开源之夏的导师阳哥便也参加过 Talent Plan 并且最终拿到了 90 多分的好成绩，阳哥在知乎发的文章我以前曾经看到过，但只见其文未见其人，这次真正结识了。</p><p>在申请项目的时候我也没有太明确的目标，只是筛选出一些技术要求我能够达到的项目，并且不需要太多的前置知识，由于我的主力开发语言是 Go 因此适合我的大多数项目都集中在了云原生领域。几经筛选之后我确定了几个项目，起初我对于申请项目还是很小心，为了确保在前期与项目导师交流的的时候可以有的放矢，我对于申请的项目都进行了研究，我首先看了看 Dubbo go 的 SDK 移植的任务，因为觉得 Dubbo 的名气比较大，如果可以承担这个任务后面可以算是很有含金量的项目，但看了下对于 rpc 理解不深的我并没有得出什么有意义的想法，然后又胡乱看了看混沌工程的项目，但最后也不了了之。最终由于我之前听说过 KubeVela 于是就将目光集中在了这个项目上，我首先尝试的是雾雾的项目：<a href="https://summer-ospp.ac.cn/#/org/prodetail/22ab00015">为 KubeVela 中的 Cue Actions 添加版本管理</a>，我当时对于 CUE 是在 KubeVela 中发挥什么样的功能并没有一个很明确的概念，只是觉得既然是版本控制那似乎可以参照 Git 的实现原理来做，就给导师发了一封邮件大概说了下自己的想法，但加了微信详细交流后发现，似乎跟我想的相去甚远，于是备受打击觉得可能这个项目的申请可能要凉凉了。</p><blockquote><p>不过我看了下后续申请者的申请书，他也提到了参照 Git 的原理来做，或许我的总体思路还没错？</p></blockquote><p>由于当时已经简单看了 KubeVela 的文档，当时的项目的功能还没有现在这么多，大概看了下如何发布应用、组件、工作流等内容就大概清楚了该项目的主要作用。由于有了一定的基础所以就打算再看看社区的其他项目，这时候就看到了阳哥担任导师的项目：<a href="https://summer-ospp.ac.cn/#/org/prodetail/22ab00016">提升KubeVela生态下应用状态的可观测能力</a>，其实我一开始也对这个项目具体要做成什么样没什么概念，但我看到了这个任务的雏形：Github 上有一个相关的 Issue，里面有关于这个任务的具体描述，这样我就对这个任务有了大致的想法。为了得到导师的认可，我去阅读了 VelaQL 的相关代码，然后给阳哥发了邮件，阳哥很快来加了我微信，并对我表示了赞许，后面交流中才知道，他对我一开始就有耐心看源码的行为很欣赏，这一点也是他后面选择我而不是其他同学的重要因素。</p><p>这里其实还有一个小插曲，我一开始以为我做的东西和 VelaQL 关联性很紧密所以就去翻看了相关的 Issue，我突然发现有一名中科院的同学在做相关的开发，这时候我就有点慌了，我担心她把这块工作做了那我后面岂不是没东西做了，如果做别的我可能还要重新上手。好在阳哥跟我说那个同学参加的是一个别的项目，他的工作完成的不太好，只做了很小一部分（甚至最终烂尾了），所以阳哥希望我后面可以把这个项目做好，以至于后来他对于我是否有在推进这件事很关心。</p><p>最终在和阳哥交流了几次后，我选择参加了 <a href="https://kubevela.io/" title="https://kubevela.io">KubeVela</a> 社区的活动。</p><blockquote><p>KubeVela 是一个现代化的软件交付平台，它可以让你的应用交付在当今流行的混合、多云环境中变得更加 * 简单、高效、可靠。</p><p>KubeVela 目前是 <a href="https://cncf.io/">CNCF</a> SandBox 项目，但已经开始提交申请成为 Incubating 项目了。</p></blockquote><h1>成长</h1><p>我负责的项目是为 KubeVela 开发一个类似一个 k9s 的 CLI 工具，该工具不与于 k9s 的是，该工具是与 KubeVela 资源层级紧密适配的，最终这个工具被命名为 <code>vela top</code>，在 KubeVela 1.6 版本中被推出，我作为该工具的维护者负责后续代码的维护工作。整个暑假以及开学初两个月的大部分时间我都花在了 <code>vela top</code> 的开发上，从 7 月初开始构建项目的雏形，到 8 月底完成了工具雏形提交了关于 vela top 的第一个 PR，再到 9 月 10 月不断迭代项目，逐渐完成了资源展示、资源筛选、YAML 查看、日志浏览、资源拓扑等一系列功能，原本以为会在 9 月份收尾的项目因为一直进行迭代，到项目截止前的 10 月底才算是基本完成。当然后续 11 月我又修复了资源拓扑由于网络 I/O 导致加载过慢的 bug，12 月完成了自定义主题的功能。</p><blockquote><p><a href="https://mp.weixin.qq.com/s/fJUmufSdhfncz91surJ96A">项目经验分享｜韩孟男：提升 KubeVela 生态下应用状态的可观测能力</a></p></blockquote><p>我也真的很荣幸能有阳哥作为我的导师，虽然阳哥比较忙但是还是坚持让我隔一段时间汇报下进度，在和阳哥的交流过程中我学到了很多，阳哥不单在完成项目方面给了我一些指导，而且在职业发展方面也不吝啬于给我很多建议，在和他相处的过程中丝毫感受不到导师带来的压力。在完成项目的过程中，社区的其他成员也对我的工作表达了认可，虽然在我看来他们都是很厉害的大佬，并且自己完成的工作其实在技术上并算不得什么，但是他们还是对我这样一个开源小白表达了鼓励和认可，增加了我参与开源建设的热情。很荣幸项目负责人天元对我开发的工具表示了肯定，甚至还将我的 Demo GIF 发到了推特上，我也和 KubeVela 组内的同事都混了个脸熟。2022 年我一共向社区贡献了 27 个 PR，也很荣幸从社区 Member 成为了 Reviewer，又进一步成为了 Approver。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20230101092733860.png" alt="github"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/21fa4a2f303828f00b1519719477373.jpg" alt="社区证书"></p><p>在得到社区认可的同时，社区向开源之夏的组委会推荐了我，进一步对我的工作给予了肯定，在社区的推荐下，我很荣幸获得了本届开源之夏的突出贡献奖。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/e7bc6e34decd5a3ef993be7416de2de.jpg" alt="开源之夏证书"></p><p>今年除了参与开源之外，另外一件让我自己觉得还算成功的事情便是今年一直在坚持刷 leecode，虽然没能做到每天都刷，但是整体上也算是坚持了一年（最近开始松懈了，已经半个月没刷了😢），但自己感觉还是很菜，犹记字节一面反转链表写了半天没有 AC，希望能继续坚持把，至少保持现在的水平吧。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20230101092700593.png" alt="leecode"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20230103085324616.png" alt="leecode"></p><p>12 月初，在疫情管控解除也就是北京疫情大爆发前夕我从北京匆匆逃离，在和豪哥在牛街短暂合住一晚，并在第二天一早在街头就着清晨的冷风与晨曦吃过早饭后，我踏上了回家的旅途并顺利在到家的第三天被告知成为了一名密接，又在社区与疾控的来回踢皮球中度过了居家隔离的反复被蹂躏心态的一周。不过相比我的好多同学，我还算得上幸运感谢我的城市并没有兴建方舱，不然我也许也会和他们一样成为方舱的最后一批入住者。</p><p>居家隔离的一周，我也并没有闲下来，在准备面试和参加面试中度过了这一周，由于事发突然本来做好 1 月份放假的打算的我，在离校之前并没有投递太多的面试，因此在离校期间匆匆投递的几家公司，有回应的也寥寥几个，不过这也可能与寒假期间各家公司并没有太多招人的打算有关。最先联系我的是 smartx，对于以技术见闻的这家公司我早有耳闻因此对于这次面试也尽力好好准备，面试过程也还算顺利因为有前面两个看起来还不那么水的项目经历，因此除了一面以外对于八股的拷打并不算很多，因而我侥幸顺利通过了面试，一周 OC，由于当时 smartx 同意我年前可以先远程实习，此外考虑到我的寒假时间本就不多，加之我迫切想要在寒假添加一段实习，因此在顺利通过面试后我并没有太多犹豫就接下了 offer，在 12 月中旬入职了公司。</p><blockquote><p>不得不说上班真的是太累了。</p></blockquote><h1>遗憾</h1><p>虽然今年收获了很多，但也并不是没有遗憾，还好我对人生中遗憾才是常态已是了然，塞翁失马焉知非福，今日的遗憾改日看来也许是另一种幸运，因此我并不因为去年的遗憾而后悔，姑且记录而已。</p><ol><li>TinyKV 烂尾：上半年在实验室的时间大部分花在了完成 TinyKV 上，但最后实际上也没有全部完成，Project3 的部分并没有全部实现，后来参加了新一届的 Talent Plan 花了一周时间搞定了 Project 4。算是有一些烂尾。</li><li>实习计划搁浅：上半年时本打算找个实习，但也只是有这个想法，并没有实际的计划，只是觉得应该做点什么，让自己以后走的容易些，但迟迟没有行动，后来忙于先后参加了字节青训营、新一届 Talent Plan、开源之夏，加之准备的并不充分因此迟迟没有开始投递，最后这个计划也不了了之，不过后来听说蒋总找到了微软的实习不由得羡慕。💔</li><li>字节青训营烂尾：这个在我预料之中，这种组队完成的项目需要队长需要对团队有较强的把控（团队分工、进度把控等），当时团队缺少一个这样的队长，我有心但苦于没有精力当这个队长，所以烂尾在意料之中。</li><li>实习投递过少：由于离校突然没有投递太多简历，准备的也不充分，收到 smartx 的 offer 之后也没有太考虑便接下了。也许再试一试会有更好的也说不准？</li><li>开题过于草率：这也是无奈之举，不过开题这件事身不由己，最后只得开了一个水题目，后续还不知道该怎么办。💔</li><li>能力提升有限：今年这一年在技术上并没有太多的长进，接触到了 k8s 但也仅限于了解概念，对于内部的原理知之甚少，想要探究的 operater 也最终计划搁浅，假期想要补充一手 Java 计划也不了了之，恨自己精力有限也恨自己学习能力不行。</li></ol><h1>展望</h1><p>2023 年对我来说也算是非常关键的一年，这一年实习、秋招、中期答辩会接踵而来，估计会是非常忙碌的一年很多重要的事情都会在这一年发生，但现在看来这些重要的事情我准备的一点都算不上充分，主要是实在精力有限，加之这两年愈发觉得健康比薪水更重要，所以还是打算不把自己逼得那么紧，一步一步慢慢来，真正重要的东西其实在我身边，舍本逐末并不可取，临渊羡鱼不如退而结网。</p><p>明年有精力的话我希望能够弥补的一个遗憾是多读一些闲书，看着身边的大佬们有时间完成工作任务的同时还有时间抽出来陶冶一些情操，对此我十分羡慕。今年基本没人怎么摸闲书，更别说完整看完一本书了，明年争取弥补这个遗憾吧。</p><p>除了闲书之外，技术方面我也希望可以再深入一点，想来目前在技术上游荡无所事事的主要原因还是没有一个很好的机会来深入接触某个领域，一个很好的例子便是，通过开源之夏帮我迅速加深了对 k8s 的理解，这段在 smartx 实习的经历也让我从零到一了解了 cgroup 相关的知识。所以 2023 年暑期实习的经历也许还会帮我深入接触某个领域。</p><p>此外，如果今年秋招顺利的话，我想在寒假再能有一个出游的机会，工作之后可能这样的机会并不多了，所以能有一次机会我觉得应该珍惜，至于是和朋友一起或是和女朋友一起就再决定了。</p><hr><p>祝好，2023！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;2022 年过去了，我不想怀念他。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一年一更的年记（流水账），终于在大年初一开始动笔。最近先后忙于论文开题与实习，终于有精力抽出时间开始回首这一年的林林总总点点滴滴，每年一更的年记既是记录也是总结更是对自己</summary>
      
    
    
    
    <category term="日记" scheme="https://siegelion.cn/categories/%E6%97%A5%E8%AE%B0/"/>
    
    
    <category term="日记" scheme="https://siegelion.cn/tags/%E6%97%A5%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>一名2024届毕业生的求职日记（暑期实习）</title>
    <link href="https://siegelion.cn/2022/12/13/%E4%B8%80%E5%90%8D2024%E5%B1%8A%E6%AF%95%E4%B8%9A%E7%94%9F%E7%9A%84%E6%B1%82%E8%81%8C%E6%97%A5%E8%AE%B0%EF%BC%88%E6%9A%91%E6%9C%9F%E5%AE%9E%E4%B9%A0%EF%BC%89/"/>
    <id>https://siegelion.cn/2022/12/13/%E4%B8%80%E5%90%8D2024%E5%B1%8A%E6%AF%95%E4%B8%9A%E7%94%9F%E7%9A%84%E6%B1%82%E8%81%8C%E6%97%A5%E8%AE%B0%EF%BC%88%E6%9A%91%E6%9C%9F%E5%AE%9E%E4%B9%A0%EF%BC%89/</id>
    <published>2022-12-13T18:58:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<h1>总结</h1><h2 id="通过">通过</h2><table><thead><tr><th>公司</th><th>岗位</th><th>时间</th><th>进度</th><th>评价</th></tr></thead><tbody><tr><td>SmartX</td><td>SRE</td><td>2022-12</td><td>三面通过，一周后直接入职</td><td>寒假接了，实习到开学之前，总体体验很好，氛围很棒，但因为学校有事提前离职了，虽然组长说后面处理完学校的事情，还可以回来，但由于想试试大厂就没回去。</td></tr><tr><td>字节</td><td>飞书后端</td><td>2022-12</td><td>一面侥幸通过，主动拒了二面</td><td>当时想去 SmartX 所以就拒掉了，没想到开学后字节根本不招人了。</td></tr><tr><td>快手</td><td>容器云开发</td><td>2023-2</td><td>两面通过，很快发了 offer</td><td>流程走的很快，今年招了很多人，不清楚进去做什么</td></tr><tr><td>百度</td><td>风控研发后端</td><td>2023-3</td><td>三面通过，养鱼两周发了 offer</td><td>还是想做基础架构，感觉这个岗位更多是调用算法的能力，实现内容审核，更像是传统后端，所以拒了</td></tr><tr><td>拼多多</td><td>后端</td><td>2023-3</td><td>一面过，主动拒了二面</td><td>听了室友的面试，自知顶不住 PDD 的工作强度，怕了怕了</td></tr><tr><td>美团</td><td>基础架构</td><td>2023-3</td><td>二面通过，养鱼两周发了 offer</td><td>做轻容器和富容器相关的工作，据说就招一个人</td></tr><tr><td>Amazon</td><td>全栈</td><td>2023-4</td><td>二面通过，养鱼一个月，发了offer</td><td>Amazon 还是香的，就是要进去转全栈 Java+React</td></tr><tr><td>PayPal</td><td>PaaS 后端</td><td>2023-4</td><td>二面通过，拒了 HR 面</td><td>团队规模有点小，主要是因为国内安全审查无法使用 AWS，只能找人自己做基础架构</td></tr><tr><td>腾讯</td><td>腾讯计费后端</td><td>2023-4</td><td>一面通过，主动拒了二面</td><td>要我去深圳实习，溜了溜了</td></tr></tbody></table><h2 id="其他">其他</h2><table><thead><tr><th>公司</th><th>岗位</th><th>日期</th><th>评价</th><th></th></tr></thead><tbody><tr><td>米哈游</td><td>云游戏后端</td><td>2023-3</td><td>笔试挂，是我不配了</td><td></td></tr><tr><td>蚂蚁</td><td>基础架构</td><td>2023-3</td><td>垃圾系统，有 BUG 说我走了别人的内推码，直接挂简历，后面转 BU 就一直跑池子</td><td></td></tr><tr><td>字节</td><td>后端</td><td>2023-3</td><td>投了好多意向，全部没消息，卡在简历评估</td><td></td></tr><tr><td>携程</td><td>基础架构</td><td>2023-3</td><td>简历挂，是我不配了</td><td></td></tr><tr><td>蔚来</td><td>基础架构</td><td>2023-3</td><td>简历挂，是我不配了</td><td></td></tr><tr><td>阿里</td><td>本地生活基础架构</td><td>2023-4</td><td>二面通过，HR 面挂了</td><td>做 Service Mesh 相关的工作，二面挺有意思的</td></tr><tr><td>腾讯</td><td>天美 QQ 飞车</td><td>2023-4</td><td>被问到不会 C++，果然挂了</td><td></td></tr></tbody></table><h1>腾讯天美（2023-4-25）</h1><ol><li>只记得自己最后反问技术栈，结果面试官问我会不会 C++，会不会高性能编程，当然是不会啦😧</li></ol><h1>腾讯计费一面（2023-4-13）</h1><ol><li>自我介绍</li><li>介绍一个 kubevela 的项目</li><li>介绍遇到的困难，以及如何解决的</li><li>介绍 kubevela 的作用</li><li>介绍 k8s 的组件</li><li>介绍调度器的原理</li><li><em>介绍 k8s 集群中，Pod 的数量和 Node 的数量受什么影响（这个不太清楚）</em></li><li>介绍 service 的原理，外界访问一个 Pod 的流程</li><li>介绍 TinyKV 项目为什么要基于 etcd 的架构来做</li><li>联合主键 <code>(A,B,C)</code>，查询 <code>SELECT * FROM TABLE1 WHERE B=1 AND C=2</code>，走不走主键</li><li><em>查询 <code>SELECT * FROM TABLE1 WHERE B=1 AND A=2</code>，走不走主键（这个应该和优化器有关，答错了）</em></li></ol><h1>阿里本地生活 Golang 二面（2023-4-13）</h1><ol><li>自我介绍</li><li>介绍 vela top 的工作</li><li>遇到的困难，如何解决的</li><li>一直追问有了 WEB 的方式，并且 WEB 端已经支持了这种能力，为什么还要在 CLI 端做 vela top。</li><li><em>上面的问题实际上一直在追问我如何理解 vela top 的需求。</em></li><li>介绍 Raft 协议</li><li>Raft 协议中选举是如何做的</li></ol><h1>PayPal 二面（2023-4-12）</h1><ol><li>自我介绍</li><li>slice 原理</li><li>map 原理</li><li>channel 原理</li><li>做题：手写一个排序算法</li><li>做题：两个排序数组找中位数</li></ol><h1>PayPal 一面（2023-4-10）</h1><ol><li>自我介绍</li><li>介绍 KubeVela 的项目</li><li>介绍如何使用 KubeVela 交付一个应用</li><li>介绍 vela top 项目的意义</li><li><em>借助 KubeVela 如何实现滚动发布</em></li><li>滚动发布需要借助 K8S 中哪种资源</li><li>Deployment 滚动升级的策略有哪些</li><li>Deployment 交付时的哪个组件起的作用</li><li>Scheduler 调度 Pod 的过程</li><li>Pod 被调度到节点之上后的过程</li><li><em>Ingress 原理</em></li><li><em>Endpoint 原理</em></li><li>使用 Go 实现一个功能：获取一个 Linux 主机上全部的进程，并获取该进程的 <code>cmd line</code>。并暴露该指标成为一个服务，该服务需要提供一个接口，可以获取一个小时内的情况。</li></ol><h1>Amazon 二面（2023-4-07）</h1><ol><li>自我介绍</li><li>介绍一个项目</li><li>介绍项目的难点以及如何解决的</li><li>介绍实习中项目遇到的困难并且是如何解决的</li><li>介绍 cgroup 这种技术</li><li>如何获取一个 Linux 主机上的所有进程</li><li>实现一个计算器</li><li>反问</li></ol><h1>Amazon 一面（2023-4-07）</h1><ol><li>自我介绍</li><li>介绍一个项目</li><li>介绍项目中的难点如何解决的</li><li>删除链表的倒数第 N 个节点</li><li>HashMap 的实现原理</li><li>反问</li></ol><h1>阿里本地生活 Golang 一面（2023-3-29）</h1><ol><li>自我介绍</li><li>叙述一下 Go 中的协程，协程是如何实现抢占式调度的</li><li>叙述一下 Go 中 channel 的原理，有缓冲和无缓冲的区别</li><li>叙述一下 Go 接口的原理</li><li>叙述一下 Go 垃圾回收的原理</li><li>介绍一下 Go 逃逸分析的过程</li><li>介绍一下 defer 的原理，defer 的调用顺序，嵌套 defer 如何调用</li><li>介绍一下 Go 中字符串拼接的几种方式，几种方式的性能开销如何排序（这个确实听说过，不记得了）</li><li>介绍一下 K8S 的基本组件（基本都介绍了，但是他问我还有没有其他的，说不出来了）</li><li>etcd 为什么可以实现分布式一致性</li><li>etcd 可以单机部署吗</li><li>go-client 原理</li><li><em>WebHook 的原理（这个确实没了解过）</em></li><li>介绍 KubeVela 的项目</li><li>参与过程中遇到的最大的挑战，如何解决的</li><li>如何把开源项目结合到实际的项目中</li></ol><h1>美团基础架构二面（2023-3-27）</h1><ol><li>自我介绍</li><li>介绍 KubeVela 这个项目</li><li>项目过程中遇到了什么问题，如何解决的</li><li>项目设计的过程中，社区成员有没有给一些指导</li><li>KubeVela 这个项目它为开发和运维人员提供哪些能力</li><li>如何看待项目的成熟度，有哪些企业采用这个项目</li><li><em>这个项目对这些企业带来了多少效率的提升，或者带来了多大的成本节省</em></li><li>进程间通信方式，实际应用中是如何使用的</li><li>消息队列的方式和共享内存的方式有什么区别</li><li>TCP 为什么会粘包</li><li>你在 SmartX 和阿里云都体验过，你如何看待大公司和小公司</li><li>你如何看待美团和阿里云</li><li>你能说一下你人生的至暗时刻吗</li></ol><h1>腾讯笔试（2023-3-26）</h1><blockquote><p>5 道题：100%、100%、100%、32%、5%</p></blockquote><h1>小红书笔试（2023-3-26）</h1><blockquote><p>3 道题：100%、100%、100%</p></blockquote><h1>拼多多后端一面（2023-3-23）</h1><blockquote><p>出了一道 leetcode 上没有的链表相减😢</p></blockquote><ol><li>自我介绍</li><li>根据自我介绍中提到的内容，详细介绍一段经历，有无遇到什么问题，如何解决的</li><li>介绍最近一段实习经历，业务场景是什么，最后离职的时候，做到什么程度</li><li>手撕：链表相减</li></ol><blockquote><p>A-B 的场景下有可能出现两种情况：A &gt; B 或 A &lt; B，但后者处理明显难度更大，做的时候不知道该怎么解决这个问题，所以说要事先判断一下，保证大数减小数。</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">package</span> main<br><span class="hljs-keyword">import</span> (<br>    <span class="hljs-string">&quot;fmt&quot;</span><br>)<br><br><span class="hljs-keyword">type</span> ListNode <span class="hljs-keyword">struct</span> &#123;<br>    val  <span class="hljs-keyword">int</span><br>    next *ListNode<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> &#123;<br>    array1 := []<span class="hljs-keyword">int</span>&#123;<span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">2</span>, <span class="hljs-number">7</span>&#125;<br>    array2 := []<span class="hljs-keyword">int</span>&#123;<span class="hljs-number">4</span>, <span class="hljs-number">6</span>, <span class="hljs-number">5</span>&#125;<br><br>    lst1 := initListNode(array1)<br>    lst2 := initListNode(array2)<br>    <br>    lst1 = reverseList(lst1)<br>    lst2 = reverseList(lst2)<br>    <br>    ans := subList(lst1, lst2)<br><br>    <span class="hljs-keyword">for</span> ans != <span class="hljs-literal">nil</span> &#123;<br>        fmt.Print(ans.val, <span class="hljs-string">&quot; &quot;</span>)<br>        ans = ans.next<br>    &#125;<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">initListNode</span><span class="hljs-params">(array []<span class="hljs-keyword">int</span>)</span> *<span class="hljs-title">ListNode</span></span> &#123;<br>    newHead := <span class="hljs-built_in">new</span>(ListNode)<br>    point := newHead<br><br>    <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; <span class="hljs-built_in">len</span>(array); i++ &#123;<br>        newNode := &amp;ListNode&#123;<br>            val: array[i],<br>        &#125;<br>        point.next = newNode<br>        point = point.next<br>    &#125;<br>    <span class="hljs-keyword">return</span> newHead.next<br><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">reverseList</span><span class="hljs-params">(head *ListNode)</span> *<span class="hljs-title">ListNode</span></span> &#123;<br>    <span class="hljs-keyword">if</span> head == <span class="hljs-literal">nil</span> || head.next == <span class="hljs-literal">nil</span> &#123;<br>        <span class="hljs-keyword">return</span> head<br>    &#125;<br>    ans := reverseList(head.next)<br>    head.next.next = head<br>    head.next = <span class="hljs-literal">nil</span><br>    <span class="hljs-keyword">return</span> ans<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">subList</span><span class="hljs-params">(lst1, lst2 *ListNode)</span> *<span class="hljs-title">ListNode</span></span> &#123;<br>    newHead := <span class="hljs-built_in">new</span>(ListNode)<br>    point := newHead<br>    x, y, z := <span class="hljs-number">-1</span>, <span class="hljs-number">-1</span>, <span class="hljs-number">0</span><br>    <span class="hljs-keyword">for</span> x != <span class="hljs-number">0</span> &amp;&amp; y != <span class="hljs-number">0</span> &#123;<br>        <span class="hljs-keyword">if</span> lst1 != <span class="hljs-literal">nil</span> &#123;<br>            x = lst1.val<br>            lst1 = lst1.next<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            x = <span class="hljs-number">0</span><br>        &#125;<br>        <span class="hljs-keyword">if</span> lst2 != <span class="hljs-literal">nil</span> &#123;<br>            y = lst2.val<br>            lst2 = lst2.next<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            y = <span class="hljs-number">0</span><br>        &#125;<br>        tmp := x - y - z<br>        <span class="hljs-keyword">if</span> tmp &lt; <span class="hljs-number">0</span> &#123;<br>            tmp = <span class="hljs-number">10</span> + tmp<br>            z = <span class="hljs-number">1</span><br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            z = <span class="hljs-number">0</span><br>        &#125;<br>        newNode := <span class="hljs-built_in">new</span>(ListNode)<br>        newNode.val = tmp<br>        point.next = newNode<br>        point = point.next<br>    &#125;<br>    <span class="hljs-keyword">return</span> newHead.next<br><br>&#125;<br></code></pre></td></tr></table></figure><blockquote><p>后面通知一面过了，但是考虑到这个是 PDD，加上已经有别的公司的 OC 了，放弃二面。</p></blockquote><h1>阿里巴巴笔试（2023-3-22）</h1><blockquote><p>3 道题：100%、50%、0%</p></blockquote><h1>美团基础架构一面（2023-3-21）</h1><blockquote><p>正好是做容器调度和限制的，所以方向比较契合，最后还问我感不感兴趣这个方向</p></blockquote><ol><li>自我介绍</li><li>SmartX 实习经历，介绍背景、解决的问题、遇到的问题、如何解决的</li><li>cgroup 原理</li><li>你提到了你们这个采集服务，最后也用 cgroup 进行资源限制，那么进行限制后不会出现采集数据不准确的问题吗</li><li>使用的是 cgroup v1 还是 cgroup v2，知道他们的原理或者区别吗</li><li>你们有没有遇到过服务启动较慢，然后没有来得及加入到 cgroup 管理中的情况，如何解决的</li><li>那如果出现这个情况，没有加入到 cgroup 之前，不会出现短时间资源短时间不受控的情况吗</li><li>介绍 kubevela</li><li>遇到过什么问题吗，如何解决的</li><li>了解 MySQL 中的 B 树与 B+ 树吗，这个两个的区别吗</li><li>知道数据库的隔离级别吗</li><li><em>了解 Redis 吗？除了使用还知道底层原理吗，知道跳表的原理吗（正好不知道，尴尬🐶）</em></li><li>知道 go 的 map 吗？为什么是并发不安全的</li><li>知道 go 的内存分配机制吗</li><li>go 多线程之间是如何通信或者共享内存的</li><li>手撕： <a href="https://leetcode.cn/problems/zigzag-conversion/">N 字形变换</a>（没看懂题目的意思）</li><li>场景题：1T 大小的文件，1G 的内存，如何排序</li></ol><h1>百度后端三面（2023-3-18）</h1><blockquote><p>一天连续三面，顶不住了，前面聊的挺好，反手出了道 Hard 😧</p></blockquote><ol><li>自我介绍</li><li>介绍 KubeVela 的工作</li><li>项目中的难点是什么，如何解决的</li><li>还在维护这个项目吗</li><li>除了你之外还有人在维护这个项目吗</li><li>平时的兴趣爱好</li><li>以后的职业发展计划是怎么样的</li><li>是想做业务还是做基础架构</li><li>为什么不选择使用 C++ 作为主力语言</li><li>研究生读的什么方向</li><li>为什么不介绍实验室的项目</li><li>保研的时候，为什么选择这个方向</li><li>算法题：编辑距离</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">minDistance</span><span class="hljs-params">(word1 <span class="hljs-keyword">string</span>, word2 <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">int</span></span> &#123;<br>    size1,size2:=<span class="hljs-built_in">len</span>(word1),<span class="hljs-built_in">len</span>(word2)<br>    dp:=<span class="hljs-built_in">make</span>([][]<span class="hljs-keyword">int</span>,size1+<span class="hljs-number">1</span>)<br>    <span class="hljs-keyword">for</span> i:=<span class="hljs-number">0</span>;i&lt;=size1;i++&#123;<br>        dp[i]=<span class="hljs-built_in">make</span>([]<span class="hljs-keyword">int</span>,size2+<span class="hljs-number">1</span>)<br>        dp[i][<span class="hljs-number">0</span>]=i<br>    &#125;<br>    <span class="hljs-keyword">for</span> j:=<span class="hljs-number">0</span>;j&lt;=size2;j++&#123;<br>        dp[<span class="hljs-number">0</span>][j]=j<br>    &#125;<br>    <br>    <span class="hljs-keyword">for</span> i:=<span class="hljs-number">1</span>;i&lt;=size1;i++&#123;<br>        <span class="hljs-keyword">for</span> j:=<span class="hljs-number">1</span>;j&lt;=size2;j++&#123;<br>            <span class="hljs-keyword">if</span> word1[i<span class="hljs-number">-1</span>]==word2[j<span class="hljs-number">-1</span>]&#123;<br>                dp[i][j]=min(dp[i<span class="hljs-number">-1</span>][j]+<span class="hljs-number">1</span>,min(dp[i][j<span class="hljs-number">-1</span>]+<span class="hljs-number">1</span>,dp[i<span class="hljs-number">-1</span>][j<span class="hljs-number">-1</span>]))<br>            &#125;<span class="hljs-keyword">else</span>&#123;<br>                dp[i][j]=min(dp[i<span class="hljs-number">-1</span>][j],min(dp[i][j<span class="hljs-number">-1</span>],dp[i<span class="hljs-number">-1</span>][j<span class="hljs-number">-1</span>]))+<span class="hljs-number">1</span><br>            &#125;<br>        &#125;<br>    &#125;<br>    <span class="hljs-comment">// fmt.Println(dp)</span><br>    <span class="hljs-keyword">return</span> dp[size1][size2]<br>&#125;<br><br>  <br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">min</span><span class="hljs-params">(a,b <span class="hljs-keyword">int</span>)</span><span class="hljs-title">int</span></span>&#123;<br>    <span class="hljs-keyword">if</span> a&lt;b&#123;<br>        <span class="hljs-keyword">return</span> a<br>    &#125;<br>    <span class="hljs-keyword">return</span> b<br>&#125;<br></code></pre></td></tr></table></figure><h1>百度后端二面（2023-3-18）</h1><ol><li>自我介绍</li><li>介绍一下对云原生的理解</li><li>云原生除了微服务、容器化这些点，还带来了哪些提升</li><li>以往的企业中不上云的情况下，是怎么部署项目的，运维是怎么进行的</li><li>云原生的缺点是什么</li><li>介绍一下 KubeVela 的工作</li><li>这个工作完全是你自己做的吗</li><li>采集的数据的粒度是什么样的，主要采集的指标有哪些</li><li>介绍一下 SmartX 的工作，这个公司是做什么的</li><li>采集的指标的有哪些，粒度是怎么样的</li><li>你做的工作涵盖面很广，但都没有特别深入，你是怎么考虑这件事的</li><li>算法题：重排链表</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">reorderList</span><span class="hljs-params">( head *ListNode )</span></span>  &#123;<br>    <span class="hljs-comment">// write code here</span><br>    <span class="hljs-keyword">if</span> head==<span class="hljs-literal">nil</span> || head.Next==<span class="hljs-literal">nil</span>&#123;<br>        <span class="hljs-keyword">return</span> <br>    &#125;<br>    m:=middleNode(head)<br>    <br>    l1,l2:=head,m.Next<br>    m.Next=<span class="hljs-literal">nil</span><br>    l2=reverseList(l2)<br>    mergeList(l1,l2)<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">middleNode</span><span class="hljs-params">(head *ListNode)</span>*<span class="hljs-title">ListNode</span></span>&#123;<br>    fast,slow:=head,head<br>    <span class="hljs-keyword">for</span> fast.Next!=<span class="hljs-literal">nil</span> &amp;&amp; fast.Next.Next!=<span class="hljs-literal">nil</span>&#123;<br>        fast=fast.Next.Next<br>        slow=slow.Next<br>    &#125;<br>    <span class="hljs-keyword">return</span> slow<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">reverseList</span><span class="hljs-params">(head *ListNode)</span> *<span class="hljs-title">ListNode</span></span>&#123;<br>    <span class="hljs-keyword">var</span> pre,cur *ListNode=<span class="hljs-literal">nil</span>,head<br>    <span class="hljs-keyword">for</span> cur!=<span class="hljs-literal">nil</span>&#123;<br>        tmp:=cur.Next<br>        cur.Next=pre<br>        pre=cur<br>        cur=tmp<br>    &#125;<br>    <span class="hljs-keyword">return</span> pre<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">mergeList</span><span class="hljs-params">(l1,l2 *ListNode)</span></span> &#123;<br>    <span class="hljs-keyword">var</span> tmp1,tmp2 *ListNode<br>    <span class="hljs-keyword">for</span> l1!=<span class="hljs-literal">nil</span> &amp;&amp; l2!=<span class="hljs-literal">nil</span>&#123;<br>        tmp1=l1.Next<br>        tmp2=l2.Next<br>        l1.Next=l2<br>        l1=tmp1<br>        l2.Next=l1<br>        l2=tmp2<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h1>百度后端一面（2023-3-18）</h1><ol><li>自我介绍</li><li>PingCAP 的这个项目是在实习的时候做的吗</li><li>这个项目的结构是怎样的</li><li>既然项目结构完全参照了 etcd，那他的意义是什么</li><li>你知道 PingCAP 基于 TiKV 又做了一个什么产品吗</li><li>介绍 KubeVela 中做的工作</li><li>vela top 如何获取资源状态</li><li>介绍 SmartX 实习的工作</li><li>介绍 cgroup 原理</li><li>介绍网易有道实习的工作</li><li>MySQL 索引的数据结构</li><li>为什么使用 B+ 树，和 B 树的区别是什么</li><li>MySQL 中索引结构对 B+ 树做了什么优化</li><li>HTTP2 做了什么优化</li><li>介绍一下 HTTP3</li><li>WebSocket 的原理</li><li>为什么选择使用 WebSocket</li><li>WebSocket 和 HTTP2 的主动推送的区别是什么</li><li>go 中在数据冲突的情况下保证数据一致，平时是怎么做的</li><li>go 的 gmp 模型</li><li>场景题：一个没有使用 k8s 的 PaaS 平台，有 8 个服务，怎么保证正常的服务数量，服务宕机之后可以恢复，该怎么设计</li><li>算法题：二叉树的层序遍历</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">levelOrder</span><span class="hljs-params">( root *TreeNode )</span> [][]<span class="hljs-title">int</span></span> &#123;<br>    <span class="hljs-comment">// write code here</span><br>    <span class="hljs-keyword">if</span> root==<span class="hljs-literal">nil</span>&#123;<br>        <span class="hljs-keyword">return</span> [][]<span class="hljs-keyword">int</span>&#123;&#125;<br>    &#125;<br>    <br>    ans:=<span class="hljs-built_in">make</span>([][]<span class="hljs-keyword">int</span>,<span class="hljs-number">0</span>)<br>    nodes:=<span class="hljs-built_in">make</span>([]*TreeNode,<span class="hljs-number">0</span>)<br>    nodes=<span class="hljs-built_in">append</span>(nodes,root)<br>    <br>    <span class="hljs-keyword">for</span> size:=<span class="hljs-built_in">len</span>(nodes);size!=<span class="hljs-number">0</span>;size=<span class="hljs-built_in">len</span>(nodes)&#123;<br>        tmp:=<span class="hljs-built_in">make</span>([]<span class="hljs-keyword">int</span>,<span class="hljs-number">0</span>)<br>        <span class="hljs-keyword">for</span> i:=<span class="hljs-number">0</span>;i&lt;size;i++&#123;<br>            tmp=<span class="hljs-built_in">append</span>(tmp,nodes[i].Val)<br>            <span class="hljs-keyword">if</span> nodes[i].Left!=<span class="hljs-literal">nil</span>&#123;<br>                nodes=<span class="hljs-built_in">append</span>(nodes,nodes[i].Left)<br>            &#125;<br>            <span class="hljs-keyword">if</span> nodes[i].Right!=<span class="hljs-literal">nil</span>&#123;<br>                nodes=<span class="hljs-built_in">append</span>(nodes,nodes[i].Right)<br>            &#125;<br>        &#125;<br>        nodes=nodes[size:]<br>        ans=<span class="hljs-built_in">append</span>(ans,tmp)<br>    &#125;<br>    <span class="hljs-keyword">return</span> ans<br>&#125;<br></code></pre></td></tr></table></figure><h1>百度笔试（2023-3-12）</h1><blockquote><p>3 道题：100%、100%、0%<br>最后一道题有思路，也是正确的，可惜没做出来，恨！</p></blockquote><h1>拼多多笔试（2023-3-11）</h1><blockquote><p>4 道题：100%、100%、100%、75%</p></blockquote><h1>美团笔试第一次（2023-3-10）</h1><blockquote><p>5 道题：100%、100%、73%、55%、0%</p></blockquote><h1>快手容器云二面（2023-02-28）</h1><ul><li>自我介绍</li><li>介绍一个参与过的项目</li><li>参与项目中遇到的困难，是如何解决的？</li><li>了解数据库吗？了解索引吗？</li><li>了解联合索引吗？</li><li>联合索引的字段顺序会如何影响索引使用。</li><li>了解网络吗？介绍一个 HTTP 与 HTTPS 的区别。</li><li>TLS 在网络中的那一层？</li><li>TCP 是如何通信的？序列号的作用。</li><li>做题，根据一个父子节点的关系表，重建一棵多叉树。</li></ul><blockquote><p>OC 审批走流程</p></blockquote><h1>快手容器云一面（2023-02-23）</h1><p><strong>项目</strong></p><ul><li>自我介绍</li><li>介绍一个参与过的项目</li><li>过程中的遇到的困难</li><li>项目能够支撑的应用数量级别</li><li>实习经历</li><li>cgroup 的理解</li></ul><p><strong>语言</strong></p><ul><li>channel 原理</li><li>向一个空的 channel 写入数据，会发生什么？（分有缓存和无缓存两种）</li><li>向一个未初始化的 channel 写入数据，会发生什么？（这个没答好，提示写出来了）</li><li>向一个关闭的 channel 写入数据，会发生什么？</li><li>context 原理</li><li>context 作用</li><li>go 的垃圾回收</li><li>什么时候会触发写屏障？</li><li>go 垃圾回收的问题，平时需要注意什么避免 GC 带来的性能问题</li><li>goroutine 的原理，线程模型理解</li><li>线程通信方式</li><li>go 协程通信方式</li></ul><p><strong>操作系统</strong></p><ul><li>什么时候会从用户态陷入内核态</li><li>线程通信方式</li></ul><p><strong>计算机网络</strong></p><ul><li>拥塞控制的原理</li><li>慢启动阶段的作用</li></ul><p><strong>k8s</strong></p><ul><li>kube-proxy 的原理</li><li>kube-proxy 三种模式</li><li><em>ivs 相比于 iptable 通过 hash 的方式带来了性能提升，那么还有什么方式可以提升性能？（不懂）</em></li><li>schuduler 的原理</li></ul><p><strong>做题</strong></p><ul><li>二叉树任意两个节点间的最大距离（没思路 g）</li><li>最长递增子序列</li></ul><h1>字节跳动飞书后端一面（2022-12-13）</h1><ol><li>自我介绍</li><li>KubeVela 介绍</li><li>Raft 选举原理</li><li>Raft 3 个节点的集群，挂掉一个两外两个节点可以选举出新的 Leader 吗</li><li>Golang Chan 的原理</li><li>HTTPS 原理</li><li>Hash 冲突的解决办法</li><li>做题：链表求和（反转链表写的有 Bug…）</li><li>做题：简单 SQL（太久没写不记得 <code>HAVING</code> 和 <code>Group BY</code> 了）</li></ol><blockquote><p>体验很好，自己傻逼，这种简单题都没顺利完成，给我机会我不中用啊。<br>不过后续通知我通过了，不过没兴趣，直接去 SmartX 了不面了。</p></blockquote><h1>SMARTX 分布式管理平台三面 （2022-12-9）</h1><ol><li>自我介绍</li><li>介绍一下在 KubeVela 社区所做的工作。</li><li>工作中的难点？</li><li>提到的对网络 IO 造成界面阻塞，那界面阻塞是什么原因造成的？</li><li>既然界面阻塞是因为单线程阻塞，那么不能采用多线程的方式吗？</li><li>TinyKV 的主要内容？</li><li>给定一个场景：5 个节点全部掉线，又全部同时恢复，在 Raft 协议下，会发生什么？</li><li>掉线节点如何知道自己的身份？</li><li>持久化 leader 的状态的作用是什么？</li><li>重新选举是如何进行的？</li><li>为什么在判断消息是否过时要同时使用 index 和 term？</li><li>给定一个场景：3 台机器，两个存储副本该如何实现？</li><li>Go 中 channel 的原理？</li></ol><blockquote><p>一周 OC，还算顺利</p></blockquote><h1>SMARTX 分布式管理平台二面 （2022-12-8）</h1><ol><li>自我介绍和从事开发的介绍。</li><li>KubeVela 社区做的主要工作。</li><li>KubeVela 如何做资源采集。</li><li>K8S 中的组件。</li><li>控制器是如何通知调度器，进行相应操作的。</li><li>在网易搭建性能测试平台时所做的主要工作。</li><li>Jenkins 是多实例的吗？</li><li><em>MySQL 和 MongoDB 的区别和业务选型上的考量。</em></li><li>Docker 是如何做资源隔离的。</li><li>平时使用 Linux 从事哪些开发。</li><li>使用 Linux 内核的版本。</li><li>Linux 主要使用哪些命令。</li><li>如何配置一个网卡的静态 IP。</li><li>统计出现频率最高的 K 个单词。</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">package</span> main<br><br><span class="hljs-keyword">import</span> (<br>    <span class="hljs-string">&quot;fmt&quot;</span><br>    <span class="hljs-string">&quot;strings&quot;</span><br>)<br><br><span class="hljs-keyword">type</span> HeapNode <span class="hljs-keyword">struct</span> &#123;<br>    word <span class="hljs-keyword">string</span><br>    fre  <span class="hljs-keyword">int</span><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> &#123;<br>    content := <span class="hljs-string">&quot;a b c a b b s&quot;</span><br>    ans := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">string</span>, <span class="hljs-number">2</span>)<br>    freCountMap := countFre(content)<br>    freArray := convertMapToArray(freCountMap)<br>    size := <span class="hljs-built_in">len</span>(freArray)<br>    buildHeap(freArray)<br>    <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">2</span>; i++ &#123;<br>        ans[i] = freArray[<span class="hljs-number">0</span>].word<br>        freArray[size<span class="hljs-number">-1</span>-i], freArray[<span class="hljs-number">0</span>] = freArray[<span class="hljs-number">0</span>], freArray[size<span class="hljs-number">-1</span>-i]<br>        justify(freArray, <span class="hljs-number">0</span>, size<span class="hljs-number">-1</span>-i)<br><br>    &#125;<br>    fmt.Println(ans)<br>&#125;<br><br>  <br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">countFre</span><span class="hljs-params">(content <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">map</span>[<span class="hljs-title">string</span>]<span class="hljs-title">int</span></span> &#123;<br>    countMap := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">int</span>)<br>    words := strings.Split(content, <span class="hljs-string">&quot; &quot;</span>)<br>    <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; <span class="hljs-built_in">len</span>(words); i++ &#123;<br>        countMap[words[i]]++<br>    &#125;<br>    <span class="hljs-keyword">return</span> countMap<br><br>&#125;<br><br>  <br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">convertMapToArray</span><span class="hljs-params">(m <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">int</span>)</span> []<span class="hljs-title">HeapNode</span></span> &#123;<br>    array := <span class="hljs-built_in">make</span>([]HeapNode, <span class="hljs-built_in">len</span>(m)+<span class="hljs-number">1</span>)<br>    index := <span class="hljs-number">1</span><br>    <span class="hljs-keyword">for</span> key, value := <span class="hljs-keyword">range</span> m &#123;<br>        array[index] = HeapNode&#123;<br>            word: key,<br>            fre:  value,<br>        &#125;<br>        index++<br>    &#125;<br>    <span class="hljs-keyword">return</span> array<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">buildHeap</span><span class="hljs-params">(wordArray []HeapNode)</span></span> &#123;<br>    size := <span class="hljs-built_in">len</span>(wordArray)<br>    <span class="hljs-keyword">for</span> i := <span class="hljs-built_in">len</span>(wordArray) / <span class="hljs-number">2</span>; i &gt;= <span class="hljs-number">0</span>; i-- &#123;<br>        justify(wordArray, i, size)<br>    &#125;<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">justify</span><span class="hljs-params">(wordArray []HeapNode, top, size <span class="hljs-keyword">int</span>)</span></span> &#123;<br>    left, right, k := top*<span class="hljs-number">2</span>, top*<span class="hljs-number">2</span>+<span class="hljs-number">1</span>, top<br><br>    <span class="hljs-keyword">if</span> left &lt; size &amp;&amp; wordArray[left].fre &gt; wordArray[k].fre &#123;<br>        k = left<br>    &#125;<br><br>    <span class="hljs-keyword">if</span> right &lt; size &amp;&amp; wordArray[right].fre &gt; wordArray[k].fre &#123;<br>        k = right<br>    &#125;<br>    <span class="hljs-keyword">if</span> k != top &#123;<br>        wordArray[k], wordArray[top] = wordArray[top], wordArray[k]<br>        justify(wordArray, k, size)<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><h1>SMARTX 分布式管理平台一面 （2022-12-5）</h1><ul><li>Raft 主要的内容，并分别阐述。</li><li>为 KubeVela 贡献的内容？</li><li>KubeVela 资源信息采集是怎么做的？</li><li>K8s 组件都有哪些？</li><li>K8s 上 POD 的部署过程。</li><li>K8s 如何减少 etcd 和 apiserver 的通信？</li><li>K8s Service 是怎么将一个请求从 Service 重定向到容器的。</li><li>Linux 上容器是怎么实现的？</li><li>Linux 内存分配。</li><li>如果内存不足了这时候再使用 malloc 分配会怎么样。</li><li>Linux 上减少 I/O 的方式有哪些？</li><li>cache 是怎么减少 I/O 的？</li><li>HTTP 长连接如何是实现的？</li><li>HTTPS 是如何实现加密通信的？</li><li>非对称加密出了加密还有什么用途？</li><li>WebSocket 原理。</li><li>TCP 三次握手。</li><li>TCP 半连接攻击。</li><li>实现一个多线程的一个生产者多个消费者场景。生产者有 n 的任务，最多同时有 m 个消费者。</li><li>如果不使用锁如何实现。</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">package</span> main<br><br><span class="hljs-keyword">import</span> (<br>    <span class="hljs-string">&quot;fmt&quot;</span><br>    <span class="hljs-string">&quot;sync&quot;</span><br>)<br><br><span class="hljs-keyword">type</span> Task <span class="hljs-keyword">struct</span> &#123;<br>    sum           <span class="hljs-keyword">int</span><br>    finishJobNum  <span class="hljs-keyword">int</span><br>    RunningWorker <span class="hljs-keyword">int</span><br>    jobs          <span class="hljs-keyword">chan</span> <span class="hljs-keyword">int</span><br>    workerSignal  <span class="hljs-keyword">chan</span> <span class="hljs-keyword">int</span><br>    syncSignal    <span class="hljs-keyword">chan</span> <span class="hljs-keyword">int</span><br>    wg            sync.WaitGroup<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> &#123;<br>    <span class="hljs-keyword">var</span> n, m <span class="hljs-keyword">int</span><br>    t := Task&#123;<br>        jobs:         <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-keyword">int</span>, n),<br>        workerSignal: <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-keyword">int</span>, m),<br>        syncSignal:   <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-keyword">int</span>, <span class="hljs-number">1</span>),<br>    &#125;<br><br>    t.Productor(n)<br>    t.syncSignal &lt;- <span class="hljs-number">1</span><br>    <br>    <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; m; i++ &#123;<br>        t.workerSignal &lt;- <span class="hljs-number">1</span><br>    &#125;<br><br>    t.wg.Add(n)<br><br>    <span class="hljs-keyword">for</span> &#123;<br>        &lt;-t.syncSignal<br>        <span class="hljs-keyword">if</span> t.finishJobNum &gt;= n &#123;<br>            <span class="hljs-keyword">break</span><br>        &#125;<br><br>        t.syncSignal &lt;- <span class="hljs-number">1</span><br>        &lt;-t.workerSignal<br><br>        <span class="hljs-keyword">select</span> &#123;<br>        <span class="hljs-keyword">case</span> num := &lt;-t.jobs:<br>            <span class="hljs-keyword">go</span> t.Consumer(num)<br>        &#125;<br>    &#125;<br><br>    t.wg.Wait()<br><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(t Task)</span> <span class="hljs-title">Consumer</span><span class="hljs-params">(num <span class="hljs-keyword">int</span>)</span></span> &#123;<br>    t.sum += t.DoTask(num)<br>    &lt;-t.syncSignal<br>    fmt.Println(t.finishJobNum, t.sum)<br>    t.finishJobNum++<br>    t.RunningWorker++<br>    t.finishJobNum--<br>    t.syncSignal &lt;- <span class="hljs-number">1</span><br>    t.workerSignal &lt;- <span class="hljs-number">1</span><br>    t.wg.Done()<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(t Task)</span> <span class="hljs-title">Productor</span><span class="hljs-params">(n <span class="hljs-keyword">int</span>)</span></span> &#123;<br>    <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; n; i++ &#123;<br>        t.jobs &lt;- i<br>    &#125;<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(t Task)</span> <span class="hljs-title">DoTask</span><span class="hljs-params">(k <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">int</span></span> &#123;<br>    <span class="hljs-keyword">return</span> k + <span class="hljs-number">1</span><br>&#125;<br></code></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;总结&lt;/h1&gt;
&lt;h2 id=&quot;通过&quot;&gt;通过&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;公司&lt;/th&gt;
&lt;th&gt;岗位&lt;/th&gt;
&lt;th&gt;时间&lt;/th&gt;
&lt;th&gt;进度&lt;/th&gt;
&lt;th&gt;评价&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;</summary>
      
    
    
    
    <category term="日记" scheme="https://siegelion.cn/categories/%E6%97%A5%E8%AE%B0/"/>
    
    
    <category term="面试" scheme="https://siegelion.cn/tags/%E9%9D%A2%E8%AF%95/"/>
    
  </entry>
  
  <entry>
    <title>线程 I/O 模型</title>
    <link href="https://siegelion.cn/2022/11/13/%E7%BA%BF%E7%A8%8B%20IO%20%E6%A8%A1%E5%9E%8B/"/>
    <id>https://siegelion.cn/2022/11/13/%E7%BA%BF%E7%A8%8B%20IO%20%E6%A8%A1%E5%9E%8B/</id>
    <published>2022-11-13T12:55:00.000Z</published>
    <updated>2024-06-07T12:50:45.750Z</updated>
    
    <content type="html"><![CDATA[<p>UNIX 网络编程中共提到了 5 种 I/O 模型，分别为阻塞 I/O、非阻塞 I/O、I/O 多路复用、信号驱动 I/O、异步 I/O，前四种都属于同步 I/O。</p><p>因为实际上 I/O 操作实际上分为两个阶段：将数据从 I/O 设备复制到内核区，将数据从内核区复制到用户区。前四种模型无法做到在两个阶段都不需要阻塞等待，他们都需要在第二个阶段阻塞等待数据从内核中复制到用户区，只有异步 I/O 模型可以做到无阻塞因此才被成为异步。</p><h1>阻塞 I/O</h1><p>最简单的一种 I/O 模型，当用户线程发出 I/O 请求之后，内核会去查看数据是否就绪，如果没有就绪就会等待数据就绪，而用户线程就会处于阻塞状态，用户线程交出 CPU。当数据就绪之后，内核会将数据拷贝到用户线程，并返回结果给用户线程，用户线程才解除阻塞状态。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20221111153558.png" alt=""></p><h1>非阻塞 I/O</h1><p>当用户线程发起一个 I/O 操作后，并不需要等待，而是马上就得到了一个结果。如果结果指示错误，它就知道数据还没有准备好，于是它可以再次发送 I/O 操作。一旦内核中的数据准备好了，并且又再次收到了用户线程的请求，那么它马上就将数据拷贝到了用户线程，然后返回。</p><p>在非阻塞 I/O 模型中，用户线程需要不断地询问内核数据是否就绪，也就说非阻塞 I/O 不会交出 CPU，而会一直占用 CPU。过程中会反复执行系统调用询问数据是否已经可以读取，这样会导致 CPU 占用率非常高。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20221111154019.png" alt=""></p><h1>I/O 多路复用</h1><h2 id="Select">Select</h2><p>select 是操作系统提供的系统调用函数，select 用来等待文件描述词（普通文件、终端、伪终端、管道、FIFO、套接字及其他类型的字符型）状态的改变。是一个轮循函数，循环询问文件节点，可设置超时时间，超时时间到了就跳过代码继续往下执行。</p><p>通过 select，我们可以把一个文件描述符的数组发给操作系统， 让操作系统去遍历，确定哪个文件描述符可以读写， 然后告诉我们去处理：</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/737b74ad42211c34b6af242e74528726.gif" alt=""></p><p><strong>原理</strong></p><p>select 中描述符集合 fd_set 通过 bitmap 的方式进行存储。</p><blockquote><p>bitmap 的基本思想就是用一个 bit 位来标记某个元素是否存在，由于采用了 bit 为单位来存储数据，因此可以大大节省存储空间。</p><p>例如利用一个 32bit 的整数的每一个 bit 便可以表示 0~31 范围内的哪些数字存在哪些数字不存在。</p></blockquote><p>fd_set 则是一个长度确定 unsigned long 数组，一共组成一个 1024bit 的连续空间，每一 bit 可以对应一个描述符 fd，因此一共可以对应 1024 个描述符。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> FD_SETSIZE 1024 </span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> NFDBITS (8 * sizeof(unsigned long)) </span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> __FDSET_LONGS (FD_SETSIZE/NFDBITS) </span><br><br><span class="hljs-comment">// 数据结构 (bitmap) </span><br><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> &#123;</span> <br><span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span> fds_bits[__FDSET_LONGS]; <br>&#125; fd_set;<br><br></code></pre></td></tr></table></figure><p><strong>相关函数</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">select</span><span class="hljs-params">(</span></span><br><span class="hljs-params"><span class="hljs-function"><span class="hljs-keyword">int</span> maxfdp ,</span></span><br><span class="hljs-params"><span class="hljs-function">fd_set *readset , </span></span><br><span class="hljs-params"><span class="hljs-function">fd_set *writeset , </span></span><br><span class="hljs-params"><span class="hljs-function">fd_set *exceptset ,</span></span><br><span class="hljs-params"><span class="hljs-function">struct timeval *timeout</span></span><br><span class="hljs-params"><span class="hljs-function">)</span></span>;<br></code></pre></td></tr></table></figure><p><strong>参数</strong></p><ul><li>maxfdp：被监听的文件描述符的最大值，它比所有文件描述符集合中的文件描述符的最大值大 1，因为文件描述符是从 0 开始计数的；</li><li>readfds、writefds、exceptset：分别指向可读、可写和异常等事件对应的描述符集合。</li><li>timeout：用于设置 select 函数的超时时间，即告诉内核 select 等待多长时间之后就放弃等待。timeout == NULL 表示等待无限长的时间，timeout == 0，select 立即返回</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">FD_ZERO</span><span class="hljs-params">(<span class="hljs-keyword">int</span> fd, fd_set *fdset)</span></span>; <span class="hljs-comment">//一个 fd_set类型变量的所有位都设为 0 </span><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">FD_CLR</span><span class="hljs-params">(<span class="hljs-keyword">int</span> fd, fd_set *fdset)</span></span>; <span class="hljs-comment">//清除某个位时可以使用 </span><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">FD_SET</span><span class="hljs-params">(<span class="hljs-keyword">int</span> fd, fd_set *fd_set)</span></span>; <span class="hljs-comment">//设置变量的某个位置位 </span><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">FD_ISSET</span><span class="hljs-params">(<span class="hljs-keyword">int</span> fd, fd_set *fdset)</span></span>; <span class="hljs-comment">//测试某个位是否被置位</span><br></code></pre></td></tr></table></figure><p><strong>例如</strong></p><p>设置 fd_set 长度为 8，则对应 8 个 fd。</p><ol><li>执行 <code>FD_ZERO(&amp;set);</code> 则 fd_set 用位表示是 00000000。</li><li>用户传关注的文件描述符 <code>fd＝5,</code> 执行 <code>FD_SET(fd,&amp;set);</code> 后 fd_set 变为 00010000(第 5 位为 1)。</li><li>若再加入 <code>fd＝2，fd=1</code>，则 fd_set 变为 00010011。</li><li>执行 <code>select(6,&amp;set,0,0,0)</code> 阻塞等待。</li><li>若 <code>fd=1,fd=2</code> 上都发生可读事件，则 select 返回，此时 set 变为 00000011。没有事件发生的 fd=5 被清空。</li></ol><p><strong>评价</strong></p><ol><li>select 本质上是设置与判断 bitmap 每一位上的值来判断对应描述符是否可以执行下一步的操作的。因为 bitmap 的大小是确定的，因此单个进程所打开的 fd 的数量是有限制的。</li><li>每次调用 select，都需要把 fd 集合从用户态拷贝到内核态。</li><li>select 是采用轮询的方式判断描述符对应的硬件是否已经就绪，因此效率太低。</li></ol><h2 id="Poll">Poll</h2><p>select 和 poll 系统调用的本质一样，poll 的机制与 select 类似，与 select 在本质上没有多大差别，管理多个描述符也是进行轮询，根据描述符的状态进行处理，但是 poll 没有最大文件描述符数量的限制（ 基于链表来存储的，但是数量过大后性能也是会下降）。</p><p>poll 和 select 同样存在一个缺点就是，包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间，而不论这些文件描述符是否就绪，它的开销随着文件描述符数量的增加而线性增大。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">poll</span><span class="hljs-params">(struct pollfd *fds, <span class="hljs-keyword">nfds_t</span> nfds, <span class="hljs-keyword">int</span> timeout)</span></span>;<br><br><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">pollfd</span>&#123;</span> <br><span class="hljs-keyword">int</span> fd; <span class="hljs-comment">//文件描述符 </span><br><span class="hljs-keyword">short</span> events; <span class="hljs-comment">//等待的事件 </span><br><span class="hljs-keyword">short</span> revents; <span class="hljs-comment">//实际发生的事件 </span><br>&#125;;<br></code></pre></td></tr></table></figure><p><strong>参数</strong></p><ul><li>fds ： 指向一个 pollfd 数组的指针，用于指定测试某个给定的 fd 的条件。</li><li>nfds ： 指定 fds 数组元素个数 ，相当于要检查多少个文件描述符。</li><li>timeout：指定等待的毫秒数，无论 I/O 是否准备好，poll() 都会返回。</li></ul><blockquote><p>在用户态时描述符是通过数组方式存储的，在复制进内核后会被转换为链表的形式存储，因此没有存储大小的限制。（<a href="https://www.doudaxia.club/index.php/archives/97/">关于poll的疑问？数组还是链表？</a>）</p><p>每个结构体的 events 域是由用户来设置，告诉内核我们关注的是什么，而 revents 域是返回时内核设置的，以说明对该描述符发生了什么事件。</p></blockquote><p><strong>返回值</strong></p><p>成功时，poll() 返回结构体中 revents 域不为 0 的文件描述符个数；如果在超时前没有任何事件发生，poll() 返回 0。</p><h2 id="ePoll">ePoll</h2><p>epoll 没有对描述符数目的限制，<strong>它所支持的文件描述符上限是整个系统最大可以打开的文件数目</strong>，例如，在 1GB 内存的机器上，这个限制大概为 10 万左右。</p><p>epoll 只有 <code>epoll_create</code>，<code>epoll_ctl</code> 和 <code>epoll_wait</code> 这三个系统调用。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/fb2a1a9fba7fd722c2e6ce1ae8ba2ee1.gif" alt=""></p><p>当某一进程调用 epoll_create 方法时，Linux 内核会创建一个 eventpoll 结构体，这个结构体也即是 epoll 对象的一个句柄，会通过 <code>epoll_create</code> 函数返回。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">epoll_create</span><span class="hljs-params">(<span class="hljs-keyword">int</span> size)</span></span>; <span class="hljs-comment">// 返回值就是 epoll 对象的句柄</span><br></code></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">eventpoll</span> &#123;</span> <br><span class="hljs-comment">// 监听列表：红黑树的根节点，这颗树中存储着所有添加到 epoll 中的需要监控的事件</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rb_root</span> <span class="hljs-title">rbr</span>;</span> <br><span class="hljs-comment">// 就绪列表：双链表中则存放着将要通过 epoll_wait 返回给用户的满足条件的事件</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">list_head</span> <span class="hljs-title">rdlist</span>;</span> <br>&#125;;<br></code></pre></td></tr></table></figure><p>每一个 epoll 对象都有一个对应的 eventpoll 结构体，用于存放通过 <code>epoll_ctl</code> 方法向 epoll 对象中添加进来的事件，这些事件都会挂载在红黑树中。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">epoll_ctl</span><span class="hljs-params">(<span class="hljs-keyword">int</span> epfd, <span class="hljs-keyword">int</span> op, <span class="hljs-keyword">int</span> fd, struct epoll_event *event)</span></span>; <br></code></pre></td></tr></table></figure><p>而所有添加到 epoll 中的事件都会与设备驱动程序建立回调关系，也就是说，当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫 <code>ep_poll_callback</code>，它会将发生的事件添加到 rdlist 双链表中。</p><p><strong>参数</strong></p><ul><li>epfd：如何确定将事件加入那个 epoll 对象呢？就是通过上文提到的句柄实现的。</li><li>op：操作类型<ul><li>EPOLL_CTL_ADD：注册新的 fd 到 epfd 中</li><li>EPOLL_CTL_MOD：修改已经注册的 fd 的监听事件</li><li>EPOLL_CTL_DEL：从 epfd 中删除一个 fd</li></ul></li><li>fd：需要注册监视对象文件描述符。</li><li>event：告诉内核需要监听什么事件。</li></ul><p><code>epoll_wait</code> 的作用相当于 <code>select</code>，当调用 <code>epoll_wait</code> 检查是否有事件发生时，只需要检查 eventpoll 对象中的 rdlist 双链表中是否有事件元素即可。如果 rdlist 不为空，则把发生的事件复制到用户态，同时将事件数量返回给用户。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">epoll_wait</span><span class="hljs-params">(<span class="hljs-keyword">int</span> epfd, struct epoll_event *events, <span class="hljs-keyword">int</span> maxevents, <span class="hljs-keyword">int</span> timeout)</span></span>;<br></code></pre></td></tr></table></figure><p><strong>参数</strong></p><ul><li>epfd：epoll_create 函数的返回值，epoll 对象的句柄</li><li>events：是分配好的 epoll_event 结构体数组，epoll 将会把发生的事件赋值到 events 数组中</li><li>maxevents：告诉内核这个 events 数组有多大，这个 maxevents 的值不能大于创建 <code>epoll_create</code> 时的 size。</li><li>timeout：是超时时间，如果函数调用成功，则返回对应 I/O 上已准备好的文件描述符数目，如果返回 0 则表示已经超时。</li></ul><h1>信号驱动 I/O</h1><p>在信号驱动 I/O 模型中，当用户线程发起一个 I/O 请求操作，会给对应的 I/O 操作注册一个信号函数，然后用户线程会继续执行，当内核数据就绪时会发送一个信号给用户线程，用户线程接收到信号之后，便在信号函数中调用中断程序，中断原本正在执行的操作，转而进行实际的 I/O 请求操作。</p><p>信号驱动 I/O 的前半部分是异步进行的，不需要阻塞等待 I/O 设备数据的写入，也不需要通过轮询的方式判断数据是否就绪，但将数据从内核种复制出来的过程，依然需要中断原本的操作，转而去读取数据，这部分的操作导致这种模型依然不是全异步的。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/20221111154708.png" alt=""></p><h1>异步 I/O</h1><p><strong>Reactor</strong></p><p>在 Reactor 模式中，会先对每个 client 注册感兴趣的事件，然后有一个线程专门去轮询每个 client 是否有事件发生，当有事件发生时，便处理每个事件，当所有事件处理完之后，便再转去继续轮询。从这里可以看出，<strong>多路复用 I/O 就是采用 Reactor 模式。</strong></p><p><strong>Proactor</strong></p><p>在 Proactor 模式中：当检测到有事件发生时，会新起一个异步操作，然后交由 <strong>内核线程</strong> 去处理，当内核线程完成 I/O 操作之后，发送一个通知告知操作已完成；可以得知，<strong>异步 I/O 模型采用的就是 Proactor 模式。</strong></p><p>异步 I/O 模型是理想的 I/O 模型，在异步 I/O 模型中，当用户线程发起读取数据的操作之后，立刻就可以开始去做其它的事。因此不会对用户线程产生任何阻塞。</p><p>内核收到 read 请求之后，会生成一个内核线程，该线程会等待数据准备完成，然后将数据拷贝到用户区，当这一切都完成之后，内核会给用户线程发送一个信号，告诉它 read 操作完成了。</p><p>用户线程甚至不用阻塞去到内核区拷贝数据，因为内核线程已经将数据拷贝至用户区了。因此异步 I/O 才是真正的异步实现，在两个阶段都做到了异步。</p><h1>参考</h1><p><a href="https://www.cnblogs.com/schips/p/12575933.html">Linux 网络编程的5种IO模型：异步IO模型</a></p><p><a href="https://cloud.tencent.com/developer/article/1922028">IO多路复用API总结</a></p><p><a href="https://juejin.cn/post/6882984260672847879">彻底理解 IO 多路复用实现机制</a></p><p><a href="https://blog.csdn.net/weixin_52622200/article/details/126369699">Linux I/O 多路复用，select / poll / epoll 详解</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;UNIX 网络编程中共提到了 5 种 I/O 模型，分别为阻塞 I/O、非阻塞 I/O、I/O 多路复用、信号驱动 I/O、异步 I/O，前四种都属于同步 I/O。&lt;/p&gt;
&lt;p&gt;因为实际上 I/O 操作实际上分为两个阶段：将数据从 I/O 设备复制到内核区，将数据从内核区</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="操作系统" scheme="https://siegelion.cn/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    
  </entry>
  
  <entry>
    <title>KubeVela 源码阅读——控制器处理</title>
    <link href="https://siegelion.cn/2022/07/15/KubeVela%20%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E2%80%94%E2%80%94%E6%8E%A7%E5%88%B6%E5%99%A8%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B/"/>
    <id>https://siegelion.cn/2022/07/15/KubeVela%20%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E2%80%94%E2%80%94%E6%8E%A7%E5%88%B6%E5%99%A8%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B/</id>
    <published>2022-07-15T15:15:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<p>KubeVela 以应用为中心，因此对控制器处理逻辑的分析中选取 Application 控制器作为主要的分析对象。</p><h1>控制器启动</h1><p>Application 控制器是 OAM 控制集中的一个控制器，在控制器启动的流程中，通过调用了 OAM 控制集的启动，间接启动了该控制器。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-comment">// Setup adds a controller that reconciles AppRollout.</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Setup</span><span class="hljs-params">(mgr ctrl.Manager, args core.Args)</span> <span class="hljs-title">error</span></span> &#123;<br>reconciler := Reconciler&#123;<br>Client:   mgr.GetClient(),<br>Scheme:   mgr.GetScheme(),<br>Recorder: event.NewAPIRecorder(mgr.GetEventRecorderFor(<span class="hljs-string">&quot;Application&quot;</span>)),<br>dm:       args.DiscoveryMapper,<br>pd:       args.PackageDiscover,<br>options:  parseOptions(args),<br>&#125;<br><span class="hljs-keyword">return</span> reconciler.SetupWithManager(mgr)<br>&#125;<br></code></pre></td></tr></table></figure><p>启动控制的最后一步，将初始化的控制器安装至控制器管理器。</p><h1>安装至管理器</h1><p>将控制器安装至管理器的流程如下图所示：</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220716195614713.png" alt="image-20220716195614713"></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-comment">// SetupWithManager install to manager</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *Reconciler)</span> <span class="hljs-title">SetupWithManager</span><span class="hljs-params">(mgr ctrl.Manager)</span> <span class="hljs-title">error</span></span> &#123;<br><span class="hljs-comment">// If Application Own these two child objects, AC status change will notify application controller and recursively update AC again, and trigger application event again...</span><br><span class="hljs-keyword">return</span> ctrl.NewControllerManagedBy(mgr).<br>Watches(&amp;source.Kind&#123;<br>Type: &amp;v1beta1.ResourceTracker&#123;&#125;,<br>&#125;, ctrlHandler.Funcs&#123;<br>...<br>&#125;).<br>WithOptions(controller.Options&#123;<br>...<br>&#125;).<br>WithEventFilter(predicate.Funcs&#123;<br>            ...<br>&#125;).<br>For(&amp;v1beta1.Application&#123;&#125;).<br>Complete(r)<br>&#125;<br></code></pre></td></tr></table></figure><p><code>Watch</code>函数是一个较低层次的，用于被监控资源类型的函数，<code>For</code> 函数与 <code>Own</code> 函数的功能也可以使用 <code>Watch</code> 函数实现。但不同的是，<code>Watch</code> 函数可以自行提供 <code>Handler</code> 函数。此处<code>Watch</code>函数关注的<code>ResourceTracker</code>类型，用于实现垃圾回收。</p><p><code>For</code> 函数的对象是 reconcile 的对象，该类型的对象的 create、delete、update 事件会触发对应的<code>Reconcile</code>函数，一个 controller builder 中不能不存在 <code>For</code> 函数。</p><h1>Reconcile</h1><p><code>Reconcile</code>函数会在控制器对应类型对象的状态与声明状态不符时自动被调用。下图是<code>Reconcile</code>的流程示意图。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220713120400517.png" alt="Reconcile"></p><h2 id="appfile">appfile</h2><h3 id="生成-parser">生成 parser</h3><p>生成parser的过程比较简单，较为关键的是parser实例化的过程中<code>tmplLoader</code>装载了一个<code>LoadTemplate</code>函数，这个函数作为一个回调函数会在后续的流程中调用，用于解析component、workflowStep等的定义。</p><p>该函数会根据传入capability definition的类型，获取对应的definition，然后生成对应的template。</p><hr><p>生成appfile有两种途径，一种是从appRevision生成，另一种是从app生成，appRevision可以看作是app的一种快照。生成appfile前首先需要判断一下，当前的appRevision的版本是否与发布app的版本对应，若是则选择从appResion生成，否则则选择从app生成。</p><p>为何有了appRevision这一中间产物，为什么不每次都从app生成，这个答案可以从两者结构体的不同中找到答案。</p><ul><li><p>app.Spec</p>  <figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> ApplicationSpec <span class="hljs-keyword">struct</span> &#123;<br>    Components []common.ApplicationComponent <br>    Policies   []AppPolicy                   <br>    Workflow   *Workflow                    <br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>appRevision.Spec</p>  <figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> ApplicationRevisionSpec <span class="hljs-keyword">struct</span> &#123;<br>    Application             Application                 <br>    ComponentDefinitions    <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]ComponentDefinition     <br>    WorkloadDefinitions     <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]WorkloadDefinition    <br>    TraitDefinitions        <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]TraitDefinition         <br>    ScopeDefinitions        <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]ScopeDefinition         <br>    PolicyDefinitions       <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]PolicyDefinition        <br>    WorkflowStepDefinitions <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]WorkflowStepDefinition  <br>    ScopeGVK                <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]v1.GroupVersionKind<br>    Policies                <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]v1alpha1.Policy         <br>    Workflow                *v1alpha1.Workflow                 <br>    ReferredObjects         []common.ReferredObject           <br>&#125;<br></code></pre></td></tr></table></figure></li></ul><p>appRevision.Spec相当于是对app.Spec进行了一个拆解转化，这个过程需要一定的运算，后续步骤中进行的运算也即是进行这个过程，这个运算的消耗可以通过快照的方法进行节省，所以在判断快照的版本合法的情况下直接从快照生成appfile。</p><h3 id="从-app-生成-appfile">从 app 生成 appfile</h3><p>我们可以通过官网提供的一个定义 app 的 yaml 文件与源码中 Application 结构体相对应来了解 app 的数据结构。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220715115300290.png" alt="app 结构"></p><p>此处我们以从app生成为例，分析appfile的生成流程。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220715113128207.png" alt="appfile 生成流程"></p><h4 id="workload">workload</h4><p>其中比较重要的步骤为<code>parseWorkload</code>，在装载步骤步骤中的三种Definition也是来自解析出的workload。</p><p><code>parseWorkload</code>的作用可以从函数的注释中获取到，其作用是”解析一个 ApplicationComponent 并生成一个包含 Appfile 所需的所有信息的 Workload ”。</p><blockquote><p>parseWorkload resolve an ApplicationComponent and generate a Workload containing ALL information required by an Appfile.</p></blockquote><ol><li><p>生成workload的过程中，首先需要利用我们上文 parser 提到的 <code>LoadTemplate</code>函数，此处使用该函数是传入的capability definition 为 componentDefinition，因此是生成 component 对应的 template。</p></li><li><p>然后使用 template 解析 component 中的参数将其转化为 workload，也即<code>component.properties</code>部分。参数详细信息参照：<a href="https://kubevela.io/zh/docs/next/end-user/components/references%E3%80%82">https://kubevela.io/zh/docs/next/end-user/components/references。</a></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220715122650234.png" alt="component.properties"></p></li><li><p>加载部分参数，包括加载 trait，加载scope。</p></li></ol><h4 id="workflowStep">workflowStep</h4><p><code>parseWorkflowSteps</code>是另一个较为重要的步骤，关于工作流的详细信息可以参照官网文档：<a href="https://kubevela.io/zh/docs/next/end-user/workflow/overview%E3%80%82">https://kubevela.io/zh/docs/next/end-user/workflow/overview。</a></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220715180203860.png" alt="workflowStep"></p><ul><li><p><code>loadWorkflowToAppfile</code>的设计非常巧妙，使用了函数链式调用的设计，将多个步骤进行了串联：</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220715183409001.png" alt="workflowStep"></p><ul><li><p>其中<code>DeployWorkflowStepGenerator</code>会将 policy，转换为<code>deploy</code>类型的 step，这里进行转换的 policy  的类型为 override 与 topology 两种类型。</p></li><li><p><code>ApplyComponentWorkflowStepGenerator</code>则将会将<code>app.component</code>转换为<code>apply-component</code>类型的 step。</p></li></ul></li><li><p><code>parseWorkflowStep</code>则遍历上文生成的 step，获取对应类型的definition，将其装载至 appfile 的<code>RelatedWorkflowStepDefinitions</code>字段中。</p></li></ul><h2 id="workflow-step">workflow step</h2><h3 id="生成-taskRunner">生成 taskRunner</h3><p>这部分对应的函数是<code>GenerateApplicationSteps</code>及相关函数，这里所做的处理是将上文 的step （<code>v1beta1.WorkflowStep</code>），转换为一种新类型的 step（<code>wfTypes.TaskRunner</code>）。</p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220716173000475.png" alt="image-20220716173000475" style="zoom:50%;" /><p>在生成 taskRunner 之前，首先要注册数个Provider，这些 Provider 会提供相应的能力，后续会使用他们的能力去实现相应的操作。</p><p>在 taskDiscover 的过程中，实际上在其内部还封装了一个 TaskLoader，这二者的作用都是在下面的流程中加载tempplate。</p><p>GetTaskGenerator 的过程中，会使用分别使用上文提到的taskDiscover 与 TaskLoader 加载 CUE 类型的 template，并作为参数传入 makeTaskGenerator，makeTaskGenerator 的逻辑比较复杂，这里它并没有直接运行任何函数，而是返回了一个闭包函数，这个函数被赋值给 genTask 变量，接下来会执行 genTask，进而调用这个闭包函数。</p><p>这个闭包函数中主要创建了 taksRunner 类型的变量 tRunner，并初始化了他的三个字段，其中我们重点关注<code>run</code> 这个字段，该字段为一个函数类型的字段，这里会将一个匿名函数赋值给他，tRuner 作为返回值进行返回，至此便将 step 成功转换为了 taskRunner。</p><h3 id="执行-task">执行 task</h3><p>该过程对应<code>ExecuteSteps</code>所进行的操作。执行该函数时会初始化一个 workflow 的 engine，然后调用 engine 的 Run 方法。Run 方法会运行上文生成的 taskRunner ，执行方式有两种：StepByStep 顺序执行以及 DAG 并行执行，关于 workflow 的执行方式的详细信息可以参考：<a href="https://kubevela.io/zh/docs/next/end-user/workflow/overview%E3%80%82">https://kubevela.io/zh/docs/next/end-user/workflow/overview。</a></p><p>这两个执行方式最后都会遍历 taskRunner，然后调用其 Run 方法，该方法实际上也即上文我们在创建 taksRunner 时，设置在其 run 字段上的函数。</p><p>现在我们可以回头重新来分析一下，这个 run 字段上的函数做了怎样的操作：</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220716193644472.png" alt="image-20220716193644472"></p><p>实际上该函数的操作分为两大部分：渲染CUE value与解析CUE value执行相应操作。</p><p>渲染CUE value前会将上文我们获取到的workflowStep definition的与workflow.Properties（参数）组合在一起，这两个部分组合在一起模板+参数就构成一个确定的workflowStep，之后将其加载为CUE value。</p><p>解析参数并执行相应操作的过程，主要由<code>doStep</code>函数完成，该函数的主要思路即为根据CUE value中的相关定义，获取相应的Provider中的相应能力（函数），执行相应的能力（函数）。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;KubeVela 以应用为中心，因此对控制器处理逻辑的分析中选取 Application 控制器作为主要的分析对象。&lt;/p&gt;
&lt;h1&gt;控制器启动&lt;/h1&gt;
&lt;p&gt;Application 控制器是 OAM 控制集中的一个控制器，在控制器启动的流程中，通过调用了 OAM 控制集</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="KubeVela" scheme="https://siegelion.cn/tags/KubeVela/"/>
    
    <category term="云原生" scheme="https://siegelion.cn/tags/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    
  </entry>
  
  <entry>
    <title>KubeVela 源码阅读——控制器启动</title>
    <link href="https://siegelion.cn/2022/07/10/KubeVela%20%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E2%80%94%E2%80%94%E6%8E%A7%E5%88%B6%E5%99%A8%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/"/>
    <id>https://siegelion.cn/2022/07/10/KubeVela%20%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E2%80%94%E2%80%94%E6%8E%A7%E5%88%B6%E5%99%A8%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/</id>
    <published>2022-07-10T15:15:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<h1>KubeVela</h1><p>KubeVela以应用为中心，通过OAM（开放应用模型）作为核心API进行构建，同时KubeVela也是OAM的最佳实践。</p><p>KubeVela中将应用拆分为各种细粒度的组件，并通过CRD注册到K8S中。同时KubeVela通过引入CUE作为模板引擎，提供了对CRD的抽象，用户可以用过编写CUE定义的应用模板，将多种K8S资源组装在一起构成应用。</p><h1>控制器启动</h1><p>我们从<code>cmd/core/main.go</code>文件的<code>main</code>函数入手，这里是核心资源以及控制器启动的入口。控制器启动是一个漫长的过程，有很多步骤，下图是控制器启动的流程示意图。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220713083026298.png" alt="控制器启动流程"></p><h2 id="绑定变量">绑定变量</h2><p>从命令行读入参数或采用默认值绑定至变量</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-comment">// 是否开启 Admission Webhook</span><br>flag.BoolVar(&amp;useWebhook, <span class="hljs-string">&quot;use-webhook&quot;</span>, <span class="hljs-literal">false</span>, <span class="hljs-string">&quot;Enable Admission Webhook&quot;</span>)<br><span class="hljs-comment">// Admission webhook 证书目录</span><br>flag.StringVar(&amp;certDir, <span class="hljs-string">&quot;webhook-cert-dir&quot;</span>, <span class="hljs-string">&quot;/k8s-webhook-server/serving-certs&quot;</span>, <span class="hljs-string">&quot;Admission webhook cert/key dir.&quot;</span>)<br><span class="hljs-comment">// Admission webhook 监听端口</span><br>flag.IntVar(&amp;webhookPort, <span class="hljs-string">&quot;webhook-port&quot;</span>, <span class="hljs-number">9443</span>, <span class="hljs-string">&quot;admission webhook listen address&quot;</span>)<br><span class="hljs-comment">// Prometheus exporter 监听地址</span><br>flag.StringVar(&amp;metricsAddr, <span class="hljs-string">&quot;metrics-addr&quot;</span>, <span class="hljs-string">&quot;:8080&quot;</span>, <span class="hljs-string">&quot;The address the metric endpoint binds to.&quot;</span>)<br><span class="hljs-comment">// 是否开启控制器主阶段选举。开启之后同一时间只允许一个控制器处于激活状态， 其他副本控制器将处于休眠状态。</span><br>flag.BoolVar(&amp;enableLeaderElection, <span class="hljs-string">&quot;enable-leader-election&quot;</span>, <span class="hljs-literal">false</span>,<br>             <span class="hljs-string">&quot;Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.&quot;</span>)<br><span class="hljs-comment">// 定义主节点选举 configmap 所在 namespace 默认处于 default namespace 下</span><br>flag.StringVar(&amp;leaderElectionNamespace, <span class="hljs-string">&quot;leader-election-namespace&quot;</span>, <span class="hljs-string">&quot;&quot;</span>,<br>               <span class="hljs-string">&quot;Determines the namespace in which the leader election configmap will be created.&quot;</span>)<br><span class="hljs-comment">// 日志落盘目录</span><br>flag.StringVar(&amp;logFilePath, <span class="hljs-string">&quot;log-file-path&quot;</span>, <span class="hljs-string">&quot;&quot;</span>, <span class="hljs-string">&quot;The file to write logs to.&quot;</span>)<br><span class="hljs-comment">// 日志最大体积</span><br>flag.Uint64Var(&amp;logFileMaxSize, <span class="hljs-string">&quot;log-file-max-size&quot;</span>, <span class="hljs-number">1024</span>, <span class="hljs-string">&quot;Defines the maximum size a log file can grow to, Unit is megabytes.&quot;</span>)<br><span class="hljs-comment">// 是否开启debug日志</span><br>flag.BoolVar(&amp;logDebug, <span class="hljs-string">&quot;log-debug&quot;</span>, <span class="hljs-literal">false</span>, <span class="hljs-string">&quot;Enable debug logs for development purpose&quot;</span>)<br>flag.IntVar(&amp;controllerArgs.RevisionLimit, <span class="hljs-string">&quot;revision-limit&quot;</span>, <span class="hljs-number">50</span>,<br>            <span class="hljs-string">&quot;RevisionLimit is the maximum number of revisions that will be maintained. The default value is 50.&quot;</span>)<br><span class="hljs-comment">// 没有使用应用的版本，可能用于垃圾回收，下同</span><br>flag.IntVar(&amp;controllerArgs.AppRevisionLimit, <span class="hljs-string">&quot;application-revision-limit&quot;</span>, <span class="hljs-number">10</span>,<br>            <span class="hljs-string">&quot;application-revision-limit is the maximum number of application useless revisions that will be maintained, if the useless revisions exceed this number, older ones will be GCed first.The default value is 10.&quot;</span>)<br>flag.IntVar(&amp;controllerArgs.DefRevisionLimit, <span class="hljs-string">&quot;definition-revision-limit&quot;</span>, <span class="hljs-number">20</span>,<br>            <span class="hljs-string">&quot;definition-revision-limit is the maximum number of component/trait definition useless revisions that will be maintained, if the useless revisions exceed this number, older ones will be GCed first.The default value is 20.&quot;</span>)<br>flag.StringVar(&amp;controllerArgs.CustomRevisionHookURL, <span class="hljs-string">&quot;custom-revision-hook-url&quot;</span>, <span class="hljs-string">&quot;&quot;</span>,<br>               <span class="hljs-string">&quot;custom-revision-hook-url is a webhook url which will let KubeVela core to call with applicationConfiguration and component info and return a customized component revision&quot;</span>)<br>flag.BoolVar(&amp;controllerArgs.AutoGenWorkloadDefinition, <span class="hljs-string">&quot;autogen-workload-definition&quot;</span>, <span class="hljs-literal">true</span>, <span class="hljs-string">&quot;Automatic generated workloadDefinition which componentDefinition refers to.&quot;</span>)<br><span class="hljs-comment">// 健康检查接口监听地址</span><br>flag.StringVar(&amp;healthAddr, <span class="hljs-string">&quot;health-addr&quot;</span>, <span class="hljs-string">&quot;:9440&quot;</span>, <span class="hljs-string">&quot;The address the health endpoint binds to.&quot;</span>)<br><span class="hljs-comment">// 如果spec没有发生变化，不允许进行apply操作</span><br>flag.StringVar(&amp;applyOnceOnly, <span class="hljs-string">&quot;apply-once-only&quot;</span>, <span class="hljs-string">&quot;false&quot;</span>,<br>               <span class="hljs-string">&quot;For the purpose of some production environment that workload or trait should not be affected if no spec change, available options: on, off, force.&quot;</span>)<br><span class="hljs-comment">// 是否禁用的内置功能列表</span><br>flag.StringVar(&amp;disableCaps, <span class="hljs-string">&quot;disable-caps&quot;</span>, <span class="hljs-string">&quot;&quot;</span>, <span class="hljs-string">&quot;To be disabled builtin capability list.&quot;</span>)<br><span class="hljs-comment">// Application 文件存放的存储介质，默认为本地存放</span><br>flag.StringVar(&amp;storageDriver, <span class="hljs-string">&quot;storage-driver&quot;</span>, <span class="hljs-string">&quot;Local&quot;</span>, <span class="hljs-string">&quot;Application file save to the storage driver&quot;</span>)<br>flag.DurationVar(&amp;commonconfig.ApplicationReSyncPeriod, <span class="hljs-string">&quot;application-re-sync-period&quot;</span>, <span class="hljs-number">5</span>*time.Minute,<br>                 <span class="hljs-string">&quot;Re-sync period for application to re-sync, also known as the state-keep interval.&quot;</span>)<br>flag.DurationVar(&amp;commonconfig.ReconcileTimeout, <span class="hljs-string">&quot;reconcile-timeout&quot;</span>, time.Minute*<span class="hljs-number">3</span>,<br>                 <span class="hljs-string">&quot;the timeout for controller reconcile&quot;</span>)<br>...<br></code></pre></td></tr></table></figure><h2 id="配置日志设置">配置日志设置</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">if</span> logDebug &#123;<br>    _ = flag.Set(<span class="hljs-string">&quot;v&quot;</span>, strconv.Itoa(<span class="hljs-keyword">int</span>(commonconfig.LogDebug)))<br>&#125;<br>...<br><span class="hljs-keyword">if</span> logFilePath != <span class="hljs-string">&quot;&quot;</span> &#123;<br>    _ = flag.Set(<span class="hljs-string">&quot;logtostderr&quot;</span>, <span class="hljs-string">&quot;false&quot;</span>)<br>    _ = flag.Set(<span class="hljs-string">&quot;log_file&quot;</span>, logFilePath)<br>    _ = flag.Set(<span class="hljs-string">&quot;log_file_max_size&quot;</span>, strconv.FormatUint(logFileMaxSize, <span class="hljs-number">10</span>))<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="pprof-服务启动">pprof 服务启动</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-comment">// pprof server setup</span><br><span class="hljs-keyword">if</span> pprofAddr != <span class="hljs-string">&quot;&quot;</span> &#123;<br>    <span class="hljs-comment">// Start pprof server if enabled</span><br>    mux := http.NewServeMux()<br>    mux.HandleFunc(<span class="hljs-string">&quot;/debug/pprof/&quot;</span>, pprof.Index)<br>    mux.HandleFunc(<span class="hljs-string">&quot;/debug/pprof/cmdline&quot;</span>, pprof.Cmdline)<br>    mux.HandleFunc(<span class="hljs-string">&quot;/debug/pprof/profile&quot;</span>, pprof.Profile)<br>    mux.HandleFunc(<span class="hljs-string">&quot;/debug/pprof/symbol&quot;</span>, pprof.Symbol)<br>    mux.HandleFunc(<span class="hljs-string">&quot;/debug/pprof/trace&quot;</span>, pprof.Trace)<br>    pprofServer := http.Server&#123;<br>        Addr:    pprofAddr,<br>        Handler: mux,<br>    &#125;<br>    klog.InfoS(<span class="hljs-string">&quot;Starting debug HTTP server&quot;</span>, <span class="hljs-string">&quot;addr&quot;</span>, pprofServer.Addr)<br><br>    <span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> &#123;<br>        <span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> &#123;<br>            ctx := context.Background()<br>            &lt;-ctx.Done()<br><br>            ctx, cancelFunc := context.WithTimeout(context.Background(), <span class="hljs-number">60</span>*time.Minute)<br>            <span class="hljs-keyword">defer</span> cancelFunc()<br><br>            <span class="hljs-keyword">if</span> err := pprofServer.Shutdown(ctx); err != <span class="hljs-literal">nil</span> &#123;<br>                klog.Error(err, <span class="hljs-string">&quot;Failed to shutdown debug HTTP server&quot;</span>)<br>            &#125;<br>        &#125;()<br><br>        <span class="hljs-keyword">if</span> err := pprofServer.ListenAndServe(); !errors.Is(http.ErrServerClosed, err) &#123;<br>            klog.Error(err, <span class="hljs-string">&quot;Failed to start debug HTTP server&quot;</span>)<br>            <span class="hljs-built_in">panic</span>(err)<br>        &#125;<br>    &#125;()<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="加载restConfig">加载restConfig</h2><p>各个控制器作为API-Server的客户端，这里加载的配置即为API-Server与客户端交互的配置，这些配置会影响客户端与API-Server之间的交互。</p><p>这里对<code>UserAgent</code>设置的目的是为了方便在日志中排查是哪个 pod 对 API-server 发起了请求。对 QPS 和 Burst 的设置限制了 API-Server 从客户端收到请求的每秒最高并发数，通过限制从客户端处进行限流保证 API-Server 的处于一个可用的状态，不会因为某些控制器的高频请求而导致整个集群的宕机。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs go">restConfig := ctrl.GetConfigOrDie()<br>restConfig.UserAgent = types.KubeVelaName + <span class="hljs-string">&quot;/&quot;</span> + version.GitRevision<br>restConfig.QPS = <span class="hljs-keyword">float32</span>(qps)<br>restConfig.Burst = burst<br>restConfig.Wrap(auth.NewImpersonatingRoundTripper)<br></code></pre></td></tr></table></figure><h2 id="多集群环境配置">多集群环境配置</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-comment">// wrapper the round tripper by multi cluster rewriter</span><br><span class="hljs-keyword">if</span> enableClusterGateway &#123;<br>    client, err := multicluster.Initialize(restConfig, <span class="hljs-literal">true</span>)<br>    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br>        klog.ErrorS(err, <span class="hljs-string">&quot;failed to enable multi-cluster capability&quot;</span>)<br>        os.Exit(<span class="hljs-number">1</span>)<br>    &#125;<br><br>    <span class="hljs-keyword">if</span> enableClusterMetrics &#123;<br>        _, err := multicluster.NewClusterMetricsMgr(context.Background(), client, clusterMetricsInterval)<br>        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br>            klog.ErrorS(err, <span class="hljs-string">&quot;failed to enable multi-cluster-metrics capability&quot;</span>)<br>            os.Exit(<span class="hljs-number">1</span>)<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="控制器管理器初始化">控制器管理器初始化</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs go">mgr, err := ctrl.NewManager(restConfig, ctrl.Options&#123;<br>    Scheme:                     scheme,<br>    MetricsBindAddress:         metricsAddr,<br>    LeaderElection:             enableLeaderElection,<br>    LeaderElectionNamespace:    leaderElectionNamespace,<br>    LeaderElectionID:           leaderElectionID,<br>    Port:                       webhookPort,<br>    CertDir:                    certDir,<br>    HealthProbeBindAddress:     healthAddr,<br>    LeaderElectionResourceLock: leaderElectionResourceLock,<br>    LeaseDuration:              &amp;leaseDuration,<br>    RenewDeadline:              &amp;renewDeadline,<br>    RetryPeriod:                &amp;retryPeriod,<br>    <span class="hljs-comment">// SyncPeriod is configured with default value, aka. 10h. First, controller-runtime does not</span><br>    <span class="hljs-comment">// recommend use it as a time trigger, instead, it is expected to work for failure tolerance</span><br>    <span class="hljs-comment">// of controller-runtime. Additionally, set this value will affect not only application</span><br>    <span class="hljs-comment">// controller but also all other controllers like definition controller. Therefore, for</span><br>    <span class="hljs-comment">// functionalities like state-keep, they should be invented in other ways.</span><br>    NewClient: ctrlClient.DefaultNewControllerClient,<br>&#125;)<br></code></pre></td></tr></table></figure><h2 id="注册健康检查">注册健康检查</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">if</span> err := registerHealthChecks(mgr); err != <span class="hljs-literal">nil</span> &#123;<br>    klog.ErrorS(err, <span class="hljs-string">&quot;Unable to register ready/health checks&quot;</span>)<br>    os.Exit(<span class="hljs-number">1</span>)<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="检查被禁用控制器是否合法">检查被禁用控制器是否合法</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">if</span> err := utils.CheckDisabledCapabilities(disableCaps); err != <span class="hljs-literal">nil</span> &#123;<br>    klog.ErrorS(err, <span class="hljs-string">&quot;Unable to get enabled capabilities&quot;</span>)<br>    os.Exit(<span class="hljs-number">1</span>)<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="加载控制器应用模式">加载控制器应用模式</h2><p>控制器存在三种应用模式：<code>applyOnceOnly</code>、<code>ApplyOnceOnlyOn</code>、<code>ApplyOnceOnlyForce</code>。</p><table><thead><tr><th>状态</th><th>解释</th></tr></thead><tbody><tr><td>ApplyOnceOnlyOff</td><td>关闭 ApplyOnceOnlyOn</td></tr><tr><td>ApplyOnceOnlyOn</td><td>workload 或 trait 只会应用一次，在没有明确指明对其本身进行修改的情况下，修改其他组件进而影响该组件状态变化时，workload 或 trait 并不会重新应用</td></tr><tr><td>ApplyOnceOnlyForce</td><td>不受其他组件的影响的程度比 ApplyOnceOnlyOn 更加顽固，即使收到其他组件的影响，导致 workload 或 trait 被删除的情况下，依然不会重新应用</td></tr></tbody></table><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">switch</span> strings.ToLower(applyOnceOnly) &#123;<br><span class="hljs-keyword">case</span> <span class="hljs-string">&quot;&quot;</span>, <span class="hljs-string">&quot;false&quot;</span>, <span class="hljs-keyword">string</span>(oamcontroller.ApplyOnceOnlyOff):<br>controllerArgs.ApplyMode = oamcontroller.ApplyOnceOnlyOff<br>klog.Info(<span class="hljs-string">&quot;ApplyOnceOnly is disabled&quot;</span>)<br><span class="hljs-keyword">case</span> <span class="hljs-string">&quot;true&quot;</span>, <span class="hljs-keyword">string</span>(oamcontroller.ApplyOnceOnlyOn):<br>controllerArgs.ApplyMode = oamcontroller.ApplyOnceOnlyOn<br>klog.Info(<span class="hljs-string">&quot;ApplyOnceOnly is enabled, that means workload or trait only apply once if no spec change even they are changed by others&quot;</span>)<br><span class="hljs-keyword">case</span> <span class="hljs-keyword">string</span>(oamcontroller.ApplyOnceOnlyForce):<br>controllerArgs.ApplyMode = oamcontroller.ApplyOnceOnlyForce<br>klog.Info(<span class="hljs-string">&quot;ApplyOnceOnlyForce is enabled, that means workload or trait only apply once if no spec change even they are changed or deleted by others&quot;</span>)<br><span class="hljs-keyword">default</span>:<br>klog.ErrorS(fmt.Errorf(<span class="hljs-string">&quot;invalid apply-once-only value: %s&quot;</span>, applyOnceOnly),<br><span class="hljs-string">&quot;Unable to setup the vela core controller&quot;</span>,<br><span class="hljs-string">&quot;apply-once-only&quot;</span>, <span class="hljs-string">&quot;on/off/force, by default it&#x27;s off&quot;</span>)<br>os.Exit(<span class="hljs-number">1</span>)<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="注册资源发现客户端">注册资源发现客户端</h2><h3 id="K8S资源">K8S资源</h3><p>这个函数返回一个<code>DiscoveryMapper</code>，根据他的注释可以将其看作一个从GVK获取资源的客户端。</p><blockquote><p>DiscoveryMapper is a interface for refresh and discovery resources from GVK.</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs go">dm, err := discoverymapper.New(mgr.GetConfig())<br></code></pre></td></tr></table></figure><h3 id="CUE资源">CUE资源</h3><p>这个函数注册了从K8S集群中获取CUE包的客户端。</p><blockquote><p>PackageDiscover defines the inner CUE packages loaded from K8s cluster</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs go">pd, err := packages.NewPackageDiscover(mgr.GetConfig())<br></code></pre></td></tr></table></figure><h2 id="注册Webhook">注册Webhook</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">if</span> useWebhook &#123;<br>    klog.InfoS(<span class="hljs-string">&quot;Enable webhook&quot;</span>, <span class="hljs-string">&quot;server port&quot;</span>, strconv.Itoa(webhookPort))<br>    oamwebhook.Register(mgr, controllerArgs)<br>    <span class="hljs-keyword">if</span> err := waitWebhookSecretVolume(certDir, waitSecretTimeout, waitSecretInterval); err != <span class="hljs-literal">nil</span> &#123;<br>        klog.ErrorS(err, <span class="hljs-string">&quot;Unable to get webhook secret&quot;</span>)<br>        os.Exit(<span class="hljs-number">1</span>)<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="注册控制器集">注册控制器集</h2><h3 id="OAM控制器集">OAM控制器集</h3><p>内部包含很多核心控制器，包括<code>application</code>、<code>trait</code>、<code>component</code>、<code>policy</code>、<code>workflow</code>多个控制器。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">if</span> err = oamv1alpha2.Setup(mgr, controllerArgs); err != <span class="hljs-literal">nil</span> &#123;<br>    klog.ErrorS(err, <span class="hljs-string">&quot;Unable to setup the oam controller&quot;</span>)<br>    os.Exit(<span class="hljs-number">1</span>)<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="Vela控制器集">Vela控制器集</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">if</span> err = standardcontroller.Setup(mgr, disableCaps, controllerArgs); err != <span class="hljs-literal">nil</span> &#123;<br>    klog.ErrorS(err, <span class="hljs-string">&quot;Unable to setup the vela core controller&quot;</span>)<br>    os.Exit(<span class="hljs-number">1</span>)<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="启动应用指标检测">启动应用指标检测</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs go">informer, err := mgr.GetCache().GetInformer(context.Background(), &amp;v1beta1.Application&#123;&#125;)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br>    klog.ErrorS(err, <span class="hljs-string">&quot;Unable to get informer for application&quot;</span>)<br>&#125;<br>watcher.StartApplicationMetricsWatcher(informer)<br></code></pre></td></tr></table></figure><h2 id="控制器管理器启动">控制器管理器启动</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">if</span> err := mgr.Start(ctrl.SetupSignalHandler()); err != <span class="hljs-literal">nil</span> &#123;<br>    klog.ErrorS(err, <span class="hljs-string">&quot;Failed to run manager&quot;</span>)<br>    os.Exit(<span class="hljs-number">1</span>)<br>&#125;<br></code></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;KubeVela&lt;/h1&gt;
&lt;p&gt;KubeVela以应用为中心，通过OAM（开放应用模型）作为核心API进行构建，同时KubeVela也是OAM的最佳实践。&lt;/p&gt;
&lt;p&gt;KubeVela中将应用拆分为各种细粒度的组件，并通过CRD注册到K8S中。同时KubeVela通过</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="KubeVela" scheme="https://siegelion.cn/tags/KubeVela/"/>
    
    <category term="云原生" scheme="https://siegelion.cn/tags/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    
  </entry>
  
  <entry>
    <title>KubeVela 源码阅读——插件渲染</title>
    <link href="https://siegelion.cn/2022/06/29/KubeVela%20%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E2%80%94%E2%80%94%E6%8F%92%E4%BB%B6%E6%B8%B2%E6%9F%93%E6%B5%81%E7%A8%8B/"/>
    <id>https://siegelion.cn/2022/06/29/KubeVela%20%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E2%80%94%E2%80%94%E6%8F%92%E4%BB%B6%E6%B8%B2%E6%9F%93%E6%B5%81%E7%A8%8B/</id>
    <published>2022-06-29T15:15:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<h1>Addon</h1><blockquote><p>KubeVela本身是一个比较新的项目，正处于高速发展期，因此很多设计可能会在短时间内就迎来变化，因此本文介绍是<code>V1.4~V1.5</code>版本时KubeVela内部加载一个插件的流程。</p></blockquote><h1>插件目录结构</h1><p><a href="https://kubevela.io/zh/docs/platform-engineers/addon/intro">自定义插件 | KubeVela</a></p><p>以上暂时是KubeVela V1.4版本的插件结构，在V1.5版本后会将插件使用的<code>VelaQL</code>实例view文件单独渲染，因此引入了一个新的文件目录views，下文都是以新的目录结构进行论述。新的插件目录结构如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs bash">├── resources/<br>├── definitions/<br>├── schemas/<br>├── views/<br>├── README.md<br>├── metadata.yaml<br>└── template.yaml<br></code></pre></td></tr></table></figure><h1>插件渲染流程</h1><h2 id="加载安装包">加载安装包</h2><p><em><code>EnableAddon</code></em></p><blockquote><p>pkg/addon/helper.go</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">EnableAddon</span><span class="hljs-params">(ctx context.Context, name <span class="hljs-keyword">string</span>, version <span class="hljs-keyword">string</span>, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r Registry, args <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>&#123;&#125;, cache *Cache)</span> <span class="hljs-title">error</span></span> &#123;<br>h := NewAddonInstaller(ctx, cli, discoveryClient, apply, config, &amp;r, args, cache)<br>pkg, err := h.loadInstallPackage(name, version)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> err<br>&#125;<br>err = h.enableAddon(pkg)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> err<br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br></code></pre></td></tr></table></figure><p>这里比较重要的是两个函数：<code>loadInstallPackage</code> 和 <code>enableAddon</code></p><p><code>loadInstallPackage</code></p><blockquote><p>pkg/addon/addon.go</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(h *Installer)</span> <span class="hljs-title">loadInstallPackage</span><span class="hljs-params">(name, version <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(*InstallPackage, error)</span></span> &#123;<br><span class="hljs-keyword">var</span> installPackage *InstallPackage<br><span class="hljs-keyword">var</span> err error<br><span class="hljs-keyword">if</span> !IsVersionRegistry(*h.r) &#123;<br>...<br><span class="hljs-keyword">var</span> uiData *UIData<br>uiData, err = h.cache.GetUIData(*h.r, name, version)<br>...<br><span class="hljs-comment">// enable this addon if it&#x27;s invisible</span><br>installPackage, err = h.r.GetInstallPackage(&amp;meta, uiData)<br>...<br>&#125; <span class="hljs-keyword">else</span> &#123;<br>versionedRegistry := BuildVersionedRegistry(h.r.Name, h.r.Helm.URL, &amp;common.HTTPOption&#123;<br>Username: h.r.Helm.Username,<br>Password: h.r.Helm.Password,<br>&#125;)<br>installPackage, err = versionedRegistry.GetAddonInstallPackage(context.Background(), name, version)<br>...<br>&#125;<br><br><span class="hljs-keyword">return</span> installPackage, <span class="hljs-literal">nil</span><br>&#125;<br></code></pre></td></tr></table></figure><p>这个函数首先检查安装插件的仓库是否是一个支持多版本的仓库，这个检查也很简单直接判断是否设置了<code>helm</code>字段。</p><p>若不支持多版本（本地或者OSS），则直接安装插件，否则构建一个多版本的仓库对象，然后根据需要的addon的<code>name</code>和<code>version</code>安装插件。</p><h2 id="加载插件安装包">加载插件安装包</h2><h3 id="不支持多版本安装包">不支持多版本安装包</h3><p><code>GetInstallPackage</code></p><blockquote><p>pkg/addon/source.go</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-comment">// GetInstallPackage get install package which is all needed to enable an addon from addon registry</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *Registry)</span> <span class="hljs-title">GetInstallPackage</span><span class="hljs-params">(meta *SourceMeta, uiData *UIData)</span> <span class="hljs-params">(*InstallPackage, error)</span></span> &#123;<br>...<br><span class="hljs-keyword">return</span> GetInstallPackageFromReader(reader, meta, uiData)<br>&#125;<br></code></pre></td></tr></table></figure><p><em><code>GetInstallPackageFromReader</code></em></p><blockquote><p>pkg/addon/addon.go</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-comment">// GetInstallPackageFromReader get install package of addon from Reader, this is used to enable an addon</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetInstallPackageFromReader</span><span class="hljs-params">(r AsyncReader, meta *SourceMeta, uiData *UIData)</span> <span class="hljs-params">(*InstallPackage, error)</span></span> &#123;<br>addonContentsReader := <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(a *InstallPackage, reader AsyncReader, readPath <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span>&#123;<br>TemplateFileName: readTemplate,<br>ResourcesDirName: readResFile,<br>DefSchemaName:    readDefSchemaFile,<br>ViewDirName:      readViewFile,<br>&#125;<br>ptItems := ClassifyItemByPattern(meta, r)<br><br><span class="hljs-comment">// Read the installed data from UI metadata object to reduce network payload</span><br><span class="hljs-keyword">var</span> addon = &amp;InstallPackage&#123;<br>Meta:           uiData.Meta,<br>Definitions:    uiData.Definitions,<br>CUEDefinitions: uiData.CUEDefinitions,<br>Parameters:     uiData.Parameters,<br>&#125;<br><br><span class="hljs-keyword">for</span> contentType, method := <span class="hljs-keyword">range</span> addonContentsReader &#123;<br>items := ptItems[contentType]<br><span class="hljs-keyword">for</span> _, it := <span class="hljs-keyword">range</span> items &#123;<br>err := method(addon, r, r.RelativePath(it))<br>...<br>&#125;<br>&#125;<br><br><span class="hljs-keyword">return</span> addon, <span class="hljs-literal">nil</span><br>&#125;<br></code></pre></td></tr></table></figure><p>该函数将template、resource、schema、view分别加载进相应的字段中，用于下面启动插件时加载用。</p><h3 id="多版本安装包">多版本安装包</h3><p><em><code>GetAddonInstallPackage</code></em></p><blockquote><p>pkg/addon/versioned_registry.go</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(i *versionedRegistry)</span> <span class="hljs-title">GetAddonInstallPackage</span><span class="hljs-params">(ctx context.Context, addonName, version <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(*InstallPackage, error)</span></span> &#123;<br><span class="hljs-comment">// 加载插件，重要</span><br>wholePackage, err := i.loadAddon(ctx, addonName, version)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err<br>&#125;<br><span class="hljs-keyword">return</span> &amp;wholePackage.InstallPackage, <span class="hljs-literal">nil</span><br>&#125;<br></code></pre></td></tr></table></figure><p><code>loadAddon</code></p><blockquote><p>pkg/addon/versioned_registry.go</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(i versionedRegistry)</span> <span class="hljs-title">loadAddon</span><span class="hljs-params">(ctx context.Context, name, version <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(*WholeAddonPackage, error)</span></span> &#123;<br>versions, err := i.h.ListVersions(i.url, name, <span class="hljs-literal">false</span>, i.Opts)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err<br>&#125;<br><span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(versions) == <span class="hljs-number">0</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, ErrNotExist<br>&#125;<br>sort.Sort(sort.Reverse(versions))<br>addonVersion, availableVersions := chooseVersion(version, versions)<br><span class="hljs-keyword">if</span> addonVersion == <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">&quot;specified version %s not exist&quot;</span>, version)<br>&#125;<br><span class="hljs-keyword">for</span> _, chartURL := <span class="hljs-keyword">range</span> addonVersion.URLs &#123;<br><span class="hljs-keyword">if</span> !utils.IsValidURL(chartURL) &#123;<br>chartURL, err = utils.JoinURL(i.url, chartURL)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">&quot;cannot join versionedRegistryURL %s and chartURL %s, %w&quot;</span>, i.url, chartURL, err)<br>&#125;<br>&#125;<br>archive, err := common.HTTPGetWithOption(ctx, chartURL, i.Opts)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">continue</span><br>&#125;<br>bufferedFile, err := loader.LoadArchiveFiles(bytes.NewReader(archive))<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">continue</span><br>&#125;<br>addonPkg, err := loadAddonPackage(name, bufferedFile)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err<br>&#125;<br>addonPkg.AvailableVersions = availableVersions<br>addonPkg.RegistryName = i.name<br><span class="hljs-keyword">return</span> addonPkg, <span class="hljs-literal">nil</span><br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">&quot;cannot fetch addon package&quot;</span>)<br>&#125;<br></code></pre></td></tr></table></figure><p><code>loadAddon</code>是一个很重要的代码，被反复使用。</p><ol><li><p>主要的逻辑为首先使用*<code>ListVersions</code>*函数加载插件的多个版本</p><p><em><code>ListVersions</code></em></p> <figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(h *Helper)</span> <span class="hljs-title">ListVersions</span><span class="hljs-params">(repoURL <span class="hljs-keyword">string</span>, chartName <span class="hljs-keyword">string</span>, skipCache <span class="hljs-keyword">bool</span>, opts *common.HTTPOption)</span> <span class="hljs-params">(repo.ChartVersions, error)</span></span> &#123;<br>i, err := h.GetIndexInfo(repoURL, skipCache, opts)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err<br>&#125;<br><span class="hljs-keyword">return</span> i.Entries[chartName], <span class="hljs-literal">nil</span><br>&#125;<br></code></pre></td></tr></table></figure><p>该函数首先获取chart repo的<code>index.yaml</code>文件，该文件对象的类型为<code>*repo.IndexFile</code>该类型的结构为：</p> <figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> IndexFile <span class="hljs-keyword">struct</span> &#123;<br>    ServerInfo  <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>&#123;&#125;   <span class="hljs-string">`json:&quot;serverInfo,omitempty&quot;`</span><br>    APIVersion  <span class="hljs-keyword">string</span>                   <span class="hljs-string">`json:&quot;apiVersion&quot;`</span><br>    Generated   time.Time                <span class="hljs-string">`json:&quot;generated&quot;`</span><br>    Entries     <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]ChartVersions <span class="hljs-string">`json:&quot;entries&quot;`</span><br>    PublicKeys  []<span class="hljs-keyword">string</span>                 <span class="hljs-string">`json:&quot;publicKeys,omitempty&quot;`</span><br>    Annotations <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>        <span class="hljs-string">`json:&quot;annotations,omitempty&quot;`</span><br>&#125;<br></code></pre></td></tr></table></figure><p>该对象的<code>Entries</code>字段为一个map对象，该map的&lt;key,value&gt;对为addon名和该addon的多个版本。</p></li><li><p>然后对多个版本进行由大到小的排序，然后根据<code>version</code>信息确定要获取的对应版本的addon。</p></li><li><p>根据该版本addon的url，获取对应的压缩包文件，然后解压缩后将<code>meta.yaml</code>对应的字节码加载进<code>*WholeAddonPackage</code>类型的addonPkg对象中。</p></li><li><p><code>loadAddonPackage</code> 从压缩包的schemas、resources、views目录中加载相关组件信息。</p><p><code>loadAddonPackage</code></p> <figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">loadAddonPackage</span><span class="hljs-params">(addonName <span class="hljs-keyword">string</span>, files []*loader.BufferedFile)</span> <span class="hljs-params">(*WholeAddonPackage, error)</span></span> &#123;<br>mr := MemoryReader&#123;Name: addonName, Files: files&#125;<br>metas, err := mr.ListAddonMeta()<br>...<br>meta := metas[addonName]<br>addonUIData, err := GetUIDataFromReader(&amp;mr, &amp;meta, UIMetaOptions)<br>...<br>installPackage, err := GetInstallPackageFromReader(&amp;mr, &amp;meta, addonUIData)<br>...<br>&#125;<br></code></pre></td></tr></table></figure><p><code>GetInstallPackageFromReader</code></p><blockquote><p>pkg/addon/addon.go</p></blockquote> <figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-comment">// GetInstallPackageFromReader get install package of addon from Reader, this is used to enable an addon</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetInstallPackageFromReader</span><span class="hljs-params">(r AsyncReader, meta *SourceMeta, uiData *UIData)</span> <span class="hljs-params">(*InstallPackage, error)</span></span> &#123;<br>addonContentsReader := <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(a *InstallPackage, reader AsyncReader, readPath <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span>&#123;<br>TemplateFileName: readTemplate,<br>ResourcesDirName: readResFile,<br>DefSchemaName:    readDefSchemaFile,<br>ViewDirName:      readViewFile,<br>&#125;<br>ptItems := ClassifyItemByPattern(meta, r)<br><br><span class="hljs-comment">// Read the installed data from UI metadata object to reduce network payload</span><br><span class="hljs-keyword">var</span> addon = &amp;InstallPackage&#123;<br>Meta:           uiData.Meta,<br>Definitions:    uiData.Definitions,<br>CUEDefinitions: uiData.CUEDefinitions,<br>Parameters:     uiData.Parameters,<br>&#125;<br><br><span class="hljs-keyword">for</span> contentType, method := <span class="hljs-keyword">range</span> addonContentsReader &#123;<br>items := ptItems[contentType]<br><span class="hljs-keyword">for</span> _, it := <span class="hljs-keyword">range</span> items &#123;<br>err := method(addon, r, r.RelativePath(it))<br>...<br>&#125;<br>&#125;<br><br><span class="hljs-keyword">return</span> addon, <span class="hljs-literal">nil</span><br>&#125;<br></code></pre></td></tr></table></figure><p>该函数将template、resource、schema、view分别加载进相应的字段中，用于下面启动插件时加载用。</p></li><li><p>addonPkg对象的<code>AvailableVersions</code>字段中还存储了该addon中全部可用的版本信息。</p></li></ol><h2 id="启动插件">启动插件</h2><p><code>enableAddon</code></p><blockquote><p>pkg/addon/addon.go</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(h *Installer)</span> <span class="hljs-title">enableAddon</span><span class="hljs-params">(addon *InstallPackage)</span> <span class="hljs-title">error</span></span> &#123;<br><span class="hljs-keyword">var</span> err error<br>h.addon = addon<br><br><span class="hljs-keyword">if</span> !h.skipVersionValidate &#123;<br>err = checkAddonVersionMeetRequired(h.ctx, addon.SystemRequirements, h.cli, h.dc)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br>version := h.getAddonVersionMeetSystemRequirement(addon.Name)<br><span class="hljs-keyword">return</span> VersionUnMatchError&#123;addonName: addon.Name, err: err, userSelectedAddonVersion: addon.Version, availableVersion: version&#125;<br>&#125;<br>&#125;<br><br><span class="hljs-keyword">if</span> err = h.installDependency(addon); err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> err<br>&#125;<br><span class="hljs-keyword">if</span> err = h.dispatchAddonResource(addon); err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> err<br>&#125;<br><span class="hljs-comment">// we shouldn&#x27;t put continue func into dispatchAddonResource, because the re-apply app maybe already update app and</span><br><span class="hljs-comment">// the suspend will set with false automatically</span><br><span class="hljs-keyword">if</span> err := h.continueOrRestartWorkflow(); err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> err<br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br></code></pre></td></tr></table></figure><p>启动插件的流程主要有以下几点：</p><ol><li>检查插件的版本是否满足版本要求</li><li>检查插件是否依赖其他组件，如果有这些组件是否已经安装</li><li>解压渲染相应的资源进行渲染</li><li>继续前面的工作流</li></ol><h3 id="检查版本需求">检查版本需求</h3><p><code>checkAddonVersionMeetRequired</code></p><blockquote><p>pkg/addon/addon.go</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-comment">// checkAddonVersionMeetRequired will check the version of cli/ux and kubevela-core-controller whether meet the addon requirement, if not will return an error</span><br><span class="hljs-comment">// please notice that this func is for check production environment which vela cli/ux or vela core is officalVersion</span><br><span class="hljs-comment">// if version is for test or debug eg: latest/commit-id/branch-name this func will return nil error</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">checkAddonVersionMeetRequired</span><span class="hljs-params">(ctx context.Context, require *SystemRequirements, k8sClient client.Client, dc *discovery.DiscoveryClient)</span> <span class="hljs-title">error</span></span> &#123;<br>...<br><br><span class="hljs-comment">// if not semver version, bypass check cli/ux. eg: &#123;branch name/git commit id/UNKNOWN&#125;</span><br><span class="hljs-keyword">if</span> version2.IsOfficialKubeVelaVersion(version2.VelaVersion) &#123;<br>res, err := checkSemVer(version2.VelaVersion, require.VelaVersion)<br>&#125;<br><br><span class="hljs-comment">// check vela core controller version</span><br>imageVersion, err := fetchVelaCoreImageTag(ctx, k8sClient)<br><br><span class="hljs-comment">// if not semver version, bypass check vela-core.</span><br><span class="hljs-keyword">if</span> version2.IsOfficialKubeVelaVersion(imageVersion) &#123;<br>res, err := checkSemVer(imageVersion, require.VelaVersion)<br>&#125;<br><br><span class="hljs-comment">// discovery client is nil so bypass check kubernetes version</span><br><span class="hljs-keyword">if</span> dc == <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br><br>k8sVersion, err := dc.ServerVersion()<br><br><span class="hljs-comment">// if not semver version, bypass check kubernetes version.</span><br><span class="hljs-keyword">if</span> version2.IsOfficialKubeVelaVersion(k8sVersion.GitVersion) &#123;<br>res, err := checkSemVer(k8sVersion.GitVersion, require.KubernetesVersion)<br>&#125;<br><br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br></code></pre></td></tr></table></figure><p>对插件进行系统版本要求的检查时，主要有两大种类型的版本要求：</p><ul><li>Vela 版本<ul><li>Vela UX 版本</li><li>Vela Core 版本</li></ul></li><li>Kubernetes 版本</li></ul><h3 id="检查其他插件依赖">检查其他插件依赖</h3><p><code>installDependency</code></p><blockquote><p>pkg/addon/addon.go</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(h *Installer)</span> <span class="hljs-title">installDependency</span><span class="hljs-params">(addon *InstallPackage)</span> <span class="hljs-title">error</span></span> &#123;<br><span class="hljs-keyword">var</span> app v1beta1.Application<br><span class="hljs-keyword">for</span> _, dep := <span class="hljs-keyword">range</span> addon.Dependencies &#123;<br>err := h.cli.Get(h.ctx, client.ObjectKey&#123;<br>Namespace: types.DefaultKubeVelaNS,<br>Name:      Convert2AppName(dep.Name),<br>&#125;, &amp;app)<br><span class="hljs-keyword">if</span> err == <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">continue</span><br>&#125;<br><span class="hljs-keyword">if</span> !apierrors.IsNotFound(err) &#123;<br><span class="hljs-keyword">return</span> err<br>&#125;<br><span class="hljs-comment">// always install addon&#x27;s latest version</span><br>depAddon, err := h.loadInstallPackage(dep.Name, <span class="hljs-string">&quot;&quot;</span>)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> err<br>&#125;<br>depHandler := *h<br>depHandler.args = <span class="hljs-literal">nil</span><br><span class="hljs-keyword">if</span> err = depHandler.enableAddon(depAddon); err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> errors.Wrap(err, <span class="hljs-string">&quot;fail to dispatch dependent addon resource&quot;</span>)<br>&#125;<br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br></code></pre></td></tr></table></figure><h3 id="部署插件">部署插件</h3><p><code>dispatchAddonResource</code></p><blockquote><p>pkg/addon/addon.go</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(h *Installer)</span> <span class="hljs-title">dispatchAddonResource</span><span class="hljs-params">(addon *InstallPackage)</span> <span class="hljs-title">error</span></span> &#123;<br>app, err := RenderApp(h.ctx, addon, h.cli, h.args)<br>...<br>defs, err := RenderDefinitions(addon, h.config)<br>...<br>schemas, err := RenderDefinitionSchema(addon)<br>...<br>views, err := RenderViews(addon)<br>...<br><br><span class="hljs-keyword">for</span> _, def := <span class="hljs-keyword">range</span> defs &#123;<br>addOwner(def, app)<br>err = h.apply.Apply(h.ctx, def, apply.DisableUpdateAnnotation())<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> err<br>&#125;<br>&#125;<br><br><span class="hljs-keyword">for</span> _, schema := <span class="hljs-keyword">range</span> schemas &#123;<br>addOwner(schema, app)<br>err = h.apply.Apply(h.ctx, schema, apply.DisableUpdateAnnotation())<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> err<br>&#125;<br>&#125;<br><br><span class="hljs-keyword">for</span> _, view := <span class="hljs-keyword">range</span> views &#123;<br>addOwner(view, app)<br>err = h.apply.Apply(h.ctx, view, apply.DisableUpdateAnnotation())<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> err<br>&#125;<br>&#125;<br><br>...<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br></code></pre></td></tr></table></figure><p>这里就是将加载进、存储在对应字段中的definition、schema、view渲染成k8s对象，然后应用到集群中，至此一个插件的安装流程就结束了。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;Addon&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;KubeVela本身是一个比较新的项目，正处于高速发展期，因此很多设计可能会在短时间内就迎来变化，因此本文介绍是&lt;code&gt;V1.4~V1.5&lt;/code&gt;版本时KubeVela内部加载一个插件的流程。&lt;/p&gt;
&lt;/b</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="KubeVela" scheme="https://siegelion.cn/tags/KubeVela/"/>
    
    <category term="云原生" scheme="https://siegelion.cn/tags/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    
  </entry>
  
  <entry>
    <title>深入理解Go是怎样构建HTTP服务器的</title>
    <link href="https://siegelion.cn/2022/05/08/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E6%98%AF%E6%80%8E%E6%A0%B7%E6%9E%84%E5%BB%BAHTTP%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84/"/>
    <id>https://siegelion.cn/2022/05/08/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E6%98%AF%E6%80%8E%E6%A0%B7%E6%9E%84%E5%BB%BAHTTP%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84/</id>
    <published>2022-05-08T14:00:25.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<p>实际上我很早就看过《Go Web编程》这本书，其中的一章很详细地介绍了Go中构建一个最简单的服务器的方法， Go在标准库中为我们提供了一个<code>net/http</code>包，这个包中提供了完善的功能来帮助我们构建一个Web服务器，Go中很多Web框架的底层实际上也是借助了这个标准库来实现自己的功能。</p><p>当时看完这本书后，觉得自己已经掌握了相关的知识，但在前两天打算写一个最简单的服务器打包成<code>Docker</code>镜像来进行<code>Kubernetes</code>的学习的时候，却在这里犯了难，所以打算重新读一下相关知识，在这里也记录一下。</p><h2 id="一个最简单的HTTP服务器">一个最简单的HTTP服务器</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">import</span> <span class="hljs-string">&quot;net/http&quot;</span><br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">RunServer</span><span class="hljs-params">()</span></span> &#123;<br>http.ListenAndServe(<span class="hljs-string">&quot;0.0.0.0:8080&quot;</span>, <span class="hljs-literal">nil</span>)<br>&#125;<br><br></code></pre></td></tr></table></figure><p>这个服务器调用<code>http</code>包中的<code>ListenAndServe</code>函数，监听8080端口，但不做任何处理。</p><p>我们看一下这个函数的源码：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">ListenAndServe</span><span class="hljs-params">(addr <span class="hljs-keyword">string</span>, handler Handler)</span> <span class="hljs-title">error</span></span> &#123;<br>server := &amp;Server&#123;Addr: addr, Handler: handler&#125;<br><span class="hljs-keyword">return</span> server.ListenAndServe()<br>&#125;<br></code></pre></td></tr></table></figure><p>实际上是创建了一个<code>Server</code>类型的结构体，然后调用了其的<code>ListenAndServe</code>方法。那么我们可以将我们的服务器代码改为：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">import</span> <span class="hljs-string">&quot;net/http&quot;</span><br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">RunServer</span><span class="hljs-params">()</span></span> &#123;<br>server := &amp;http.Server&#123;<br>Addr:    <span class="hljs-string">&quot;0.0.0.0:8080&quot;</span>,<br>Handler: <span class="hljs-literal">nil</span>,<br>&#125;<br>server.ListenAndServe()<br>&#125;<br><br></code></pre></td></tr></table></figure><p>实际上起到的效果是一样的。</p><h2 id="处理器">处理器</h2><p>接下来让我们关注一下<code>http.ListenAndServe</code>函数的第二个参数，这个参数是一个<code>Handler</code>类型，这个类型是一个接口类型，接口的定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> Handler <span class="hljs-keyword">interface</span> &#123;<br>ServeHTTP(ResponseWriter, *Request)<br>&#125;<br></code></pre></td></tr></table></figure><p>这个接口只有一个方法，也即为<code>ServeHTTP</code>。因此我们如果传入的这个参数，只要是一个 拥有该方法的处理器类型即可。我们可以将代码再稍作修改：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">import</span> <span class="hljs-string">&quot;net/http&quot;</span><br><br><span class="hljs-keyword">type</span> MyHandler <span class="hljs-keyword">struct</span>&#123;&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(h *MyHandler)</span> <span class="hljs-title">ServeHTTP</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;<br>w.Write([]<span class="hljs-keyword">byte</span>(<span class="hljs-string">&quot;Hello&quot;</span>))<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">RunServer</span><span class="hljs-params">()</span></span> &#123;<br>handler := &amp;MyHandler&#123;&#125;<br>server := &amp;http.Server&#123;<br>Addr:    <span class="hljs-string">&quot;0.0.0.0:8080&quot;</span>,<br>Handler: handler,<br>&#125;<br>server.ListenAndServe()<br>&#125;<br><br></code></pre></td></tr></table></figure><h2 id="多路复用器">多路复用器</h2><p>但这是有一个问题，我们现在访问8080端口的任何URL返回的结果都是一样的，显然我们不希望如此，那么是否可以像其他Web框架一样，根据不同的路由进行不同的处理呢？答案当然是可以的，这时就需要引入我们的多路复用器，我们来看一个最简单的多路复用器：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">import</span> <span class="hljs-string">&quot;net/http&quot;</span><br><br><span class="hljs-keyword">type</span> HelloHandler <span class="hljs-keyword">struct</span>&#123;&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(h *HelloHandler)</span> <span class="hljs-title">ServeHTTP</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;<br>w.Write([]<span class="hljs-keyword">byte</span>(<span class="hljs-string">&quot;Hello&quot;</span>))<br>&#125;<br><br><span class="hljs-keyword">type</span> HiHandler <span class="hljs-keyword">struct</span>&#123;&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(h *HiHandler)</span> <span class="hljs-title">ServeHTTP</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;<br>w.Write([]<span class="hljs-keyword">byte</span>(<span class="hljs-string">&quot;Hello&quot;</span>))<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">RunServer</span><span class="hljs-params">()</span></span> &#123;<br>helloHandler := &amp;HelloHandler&#123;&#125;<br>hiHandler := &amp;HiHandler&#123;&#125;<br><br>server := &amp;http.Server&#123;<br>Addr: <span class="hljs-string">&quot;0.0.0.0:8080&quot;</span>,<br>&#125;<br>http.Handle(<span class="hljs-string">&quot;/hello&quot;</span>, helloHandler)<br>http.Handle(<span class="hljs-string">&quot;/hi&quot;</span>, hiHandler)<br><br>server.ListenAndServe()<br>&#125;<br></code></pre></td></tr></table></figure><p>我们使用了<code>http</code>包中的<code>Handle</code>函数，将不同的路由绑定到不同的处理器上，实现了我们的需求。我们对这个函数也很好奇，因此我们可以来看一下这个函数的源码：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Handle</span><span class="hljs-params">(pattern <span class="hljs-keyword">string</span>, handler Handler)</span></span> &#123; DefaultServeMux.Handle(pattern, handler) &#125;<br></code></pre></td></tr></table></figure><p>这个函数的源代码很简单，只有一行，也即调用了<code>DefaultServeMux</code>变量的<code>Handle</code>方法，这个变量实际上是一个<code>ServeMux</code>类型的变量，我们查看一下这个变量的初始化过程发现，这个变量仅仅是一个利用默认值初始化后的<code>ServeMux</code>变量，那么我们有理由认为，我们也初始化一个这样的变量，然后调用这个变量的Handle方法也能起到一样的效果：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">import</span> <span class="hljs-string">&quot;net/http&quot;</span><br><br><span class="hljs-keyword">type</span> HelloHandler <span class="hljs-keyword">struct</span>&#123;&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(h *HelloHandler)</span> <span class="hljs-title">ServeHTTP</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;<br>w.Write([]<span class="hljs-keyword">byte</span>(<span class="hljs-string">&quot;Hello&quot;</span>))<br>&#125;<br><br><span class="hljs-keyword">type</span> HiHandler <span class="hljs-keyword">struct</span>&#123;&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(h *HiHandler)</span> <span class="hljs-title">ServeHTTP</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;<br>w.Write([]<span class="hljs-keyword">byte</span>(<span class="hljs-string">&quot;Hello&quot;</span>))<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">RunServer</span><span class="hljs-params">()</span></span> &#123;<br>severMux := http.NewServeMux()<br>helloHandler := &amp;HelloHandler&#123;&#125;<br>hiHandler := &amp;HiHandler&#123;&#125;<br><br>server := &amp;http.Server&#123;<br>Addr: <span class="hljs-string">&quot;0.0.0.0:8080&quot;</span>,<br>&#125;<br>severMux.Handle(<span class="hljs-string">&quot;/hello&quot;</span>, helloHandler)<br>severMux.Handle(<span class="hljs-string">&quot;/hi&quot;</span>, hiHandler)<br><br>server.ListenAndServe()<br>&#125;<br></code></pre></td></tr></table></figure><p>事实上确实如此。</p><p>此外我们还从源码中发现一件有趣的事，那就是<code>ServeMux</code>类型也有一个<code>ServeHTTP</code>方法，根据一开始的例子，我们知道<code>http.Server</code>类型的<code>Handler</code>字段是一个接口，要将变量赋给这个字段，必须拥有一个<code>ServeHTTP</code>方法，这样这个变量才能成为一个处理器，也就是说我们可以直接将一个多路复用器当作一个处理器来使用：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">import</span> <span class="hljs-string">&quot;net/http&quot;</span><br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">RunServer</span><span class="hljs-params">()</span></span> &#123;<br>severMux := http.NewServeMux()<br><br>server := &amp;http.Server&#123;<br>Addr: <span class="hljs-string">&quot;0.0.0.0:8080&quot;</span>,<br><span class="hljs-comment">// Handler: severMux,</span><br>Handler: http.DefaultServeMux,<br>&#125;<br><br>server.ListenAndServe()<br>&#125;<br><br></code></pre></td></tr></table></figure><p>使用这样的方式启动服务器后，我们访问8080端口，会发现虽然不会报错，但是会返回404状态。这是为什么呢？</p><p>我们关注一下<code>ServeMux</code>类型的<code>ServeHTTP</code>方法，这个方法的调用链有点深，我们一点一点看：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(mux *ServeMux)</span> <span class="hljs-title">ServeHTTP</span><span class="hljs-params">(w ResponseWriter, r *Request)</span></span> &#123;<br>...<br>    h, _ := mux.Handler(r)<br>h.ServeHTTP(w, r)<br>&#125;<br></code></pre></td></tr></table></figure><p>这里又出现了一个<code>ServeHTTP</code>函数，我们先跳过其看上面的<code>Handler</code>函数。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(mux *ServeMux)</span> <span class="hljs-title">Handler</span><span class="hljs-params">(r *Request)</span> <span class="hljs-params">(h Handler, pattern <span class="hljs-keyword">string</span>)</span></span> &#123;<br>...<br><span class="hljs-keyword">return</span> mux.handler(host, r.URL.Path)<br>&#125;<br></code></pre></td></tr></table></figure><p>这个函数在末尾调用了一下<code>handler</code>函数。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(mux *ServeMux)</span> <span class="hljs-title">handler</span><span class="hljs-params">(host, path <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(h Handler, pattern <span class="hljs-keyword">string</span>)</span></span> &#123;<br>mux.mu.RLock()<br><span class="hljs-keyword">defer</span> mux.mu.RUnlock()<br><br><span class="hljs-comment">// Host-specific pattern takes precedence over generic ones</span><br><span class="hljs-keyword">if</span> mux.hosts &#123;<br>h, pattern = mux.match(host + path)<br>&#125;<br><span class="hljs-keyword">if</span> h == <span class="hljs-literal">nil</span> &#123;<br>h, pattern = mux.match(path)<br>&#125;<br><span class="hljs-keyword">if</span> h == <span class="hljs-literal">nil</span> &#123;<br>h, pattern = NotFoundHandler(), <span class="hljs-string">&quot;&quot;</span><br>&#125;<br><span class="hljs-keyword">return</span><br>&#125;<br></code></pre></td></tr></table></figure><p><code>handler</code>函数中多次调用了一个<code>match</code>函数，这个<code>match</code>函数的作用就是根据<code>host</code>和<code>path</code>两个变量找到对应的处理器然后一层层返回给上层，这个函数的最后有一个<code>NotFoundHandler</code>函数，生成一个找不到相应处理器时的兜底情况。</p><p>这也就解释了为什么我们访问8080端口时返回404，因为我们使用<code>DefaultServeMux</code>多路复用器作为处理器，实际上这个多路复用器并没有绑定任何处理器，处理请求时只能返回404了。</p><h2 id="多路复用器的匹配原则">多路复用器的匹配原则</h2><p>我们看一下match函数的源码：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(mux *ServeMux)</span> <span class="hljs-title">match</span><span class="hljs-params">(path <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(h Handler, pattern <span class="hljs-keyword">string</span>)</span></span> &#123;<br><span class="hljs-comment">// Check for exact match first.</span><br>v, ok := mux.m[path]<br><span class="hljs-keyword">if</span> ok &#123;<br><span class="hljs-keyword">return</span> v.h, v.pattern<br>&#125;<br><br><span class="hljs-comment">// Check for longest valid match.  mux.es contains all patterns</span><br><span class="hljs-comment">// that end in / sorted from longest to shortest.</span><br><span class="hljs-keyword">for</span> _, e := <span class="hljs-keyword">range</span> mux.es &#123;<br><span class="hljs-keyword">if</span> strings.HasPrefix(path, e.pattern) &#123;<br><span class="hljs-keyword">return</span> e.h, e.pattern<br>&#125;<br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, <span class="hljs-string">&quot;&quot;</span><br>&#125;<br><br></code></pre></td></tr></table></figure><p>根据源码我们可以发现，要匹配多路复用器中的处理器，实际上是通过多路复用器的map一个字典去进行匹配，如果字典中找不到对应的处理器，那么再到一个以”/“结尾的路由切片中寻找，当前的路由是否是任何路由的前缀，如果是那么也可以认为匹配。</p><h2 id="处理器函数">处理器函数</h2><p>除了可以将处理器在多路复用器上绑定路由外，还可以使用处理器函数来绑定路由：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">import</span> <span class="hljs-string">&quot;net/http&quot;</span><br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">sayHello</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;<br>w.Write([]<span class="hljs-keyword">byte</span>(<span class="hljs-string">&quot;Hello&quot;</span>))<br>&#125;<br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">RunServer</span><span class="hljs-params">()</span></span> &#123;<br>severMux := http.NewServeMux()<br>severMux.HandleFunc(<span class="hljs-string">&quot;/&quot;</span>, sayHello)<br>    <br>server := &amp;http.Server&#123;<br>Addr:    <span class="hljs-string">&quot;0.0.0.0:8080&quot;</span>,<br>Handler: severMux,<br>&#125;<br><br>server.ListenAndServe()<br>&#125;<br><br></code></pre></td></tr></table></figure><p>我们也来看一下这个函数的源码：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(mux *ServeMux)</span> <span class="hljs-title">HandleFunc</span><span class="hljs-params">(pattern <span class="hljs-keyword">string</span>, handler <span class="hljs-keyword">func</span>(ResponseWriter, *Request)</span>)</span> &#123;<br><span class="hljs-keyword">if</span> handler == <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-built_in">panic</span>(<span class="hljs-string">&quot;http: nil handler&quot;</span>)<br>&#125;<br>mux.Handle(pattern, HandlerFunc(handler))<br>&#125;<br></code></pre></td></tr></table></figure><p>这个函数将我们传入的处理器函数转为<code>HandlerFunc</code>类型，没错是一个类型转换，然后将这个类型作为第二个参数调用<code>mux.Handle</code>方法，这也就和处理器的调用一致了，但一个处理器是一个拥有<code>SeveHTTP</code>的变量，这里是怎么实现转换的呢？</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> HandlerFunc <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(ResponseWriter, *Request)</span></span><br><br><span class="hljs-comment">// ServeHTTP calls f(w, r).</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(f HandlerFunc)</span> <span class="hljs-title">ServeHTTP</span><span class="hljs-params">(w ResponseWriter, r *Request)</span></span> &#123;<br>f(w, r)<br>&#125;<br><br></code></pre></td></tr></table></figure><p>这里的转换简直让人叹为观止！<code>HandlerFunc</code>这个类型拥有一个<code>ServeHTTP</code>方法，这个方法的实现就是调用<code>HandlerFunc</code>自身。这样就可以实现，处理器函数到一个处理器的转化。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;实际上我很早就看过《Go Web编程》这本书，其中的一章很详细地介绍了Go中构建一个最简单的服务器的方法， Go在标准库中为我们提供了一个&lt;code&gt;net/http&lt;/code&gt;包，这个包中提供了完善的功能来帮助我们构建一个Web服务器，Go中很多Web框架的底层实际上也</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Go" scheme="https://siegelion.cn/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>Docker 镜像体积优化</title>
    <link href="https://siegelion.cn/2022/03/15/Docker%20%E9%95%9C%E5%83%8F%E4%BD%93%E7%A7%AF%E4%BC%98%E5%8C%96/"/>
    <id>https://siegelion.cn/2022/03/15/Docker%20%E9%95%9C%E5%83%8F%E4%BD%93%E7%A7%AF%E4%BC%98%E5%8C%96/</id>
    <published>2022-03-15T16:15:00.000Z</published>
    <updated>2024-06-07T12:50:45.742Z</updated>
    
    <content type="html"><![CDATA[<p>在我使用<code>docker</code>对我目前负责的一个项目进行部署时，遇到了这样的一个问题，使用<code>dockerfile</code>构建出的<code>docker</code>镜像的体积太大了。当时我只觉得这是不可避免的，毕竟容器的底层还是运行着一个操作系统，加之<code>Golang</code>的编译环境，再加上项目的代码以及静态资源，最终构建出的镜像体积为<code>1.1G</code>似乎是一件再正常不过的事情。</p><h3 id="Version-1">Version 1</h3><p>以下是我第一版用于构建<code>docker</code>镜像的<code>dockerfile</code>，可以看到为了保证最终容器的兼容性，我使用<code>1.16</code>版本的<code>Golang</code>镜像作为基础镜像，该镜像的下层为<code>Ubuntu</code>镜像。</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs dockerfile"><span class="hljs-comment"># Version: 0.1</span><br><span class="hljs-keyword">FROM</span> golang:<span class="hljs-number">1.16</span><br><br><span class="hljs-keyword">COPY</span><span class="bash"> . /<span class="hljs-variable">$GOPATH</span>/src/Fouda/</span><br><span class="hljs-keyword">WORKDIR</span><span class="bash"> /<span class="hljs-variable">$GOPATH</span>/src/Fouda/</span><br><br><span class="hljs-keyword">RUN</span><span class="bash"> go env -w GO111MODULE=on</span><br><span class="hljs-keyword">RUN</span><span class="bash"> go env -w GOPROXY=https://goproxy.cn,direct</span><br><br><span class="hljs-keyword">RUN</span><span class="bash"> go mod tidy</span><br><span class="hljs-keyword">RUN</span><span class="bash"> go build ./cmd/main.go</span><br><br><span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8089</span>:<span class="hljs-number">8089</span><br><span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">&quot;go&quot;</span>,<span class="hljs-string">&quot;run&quot;</span>,<span class="hljs-string">&quot;./cmd/main.go&quot;</span>]</span><br></code></pre></td></tr></table></figure><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220315155643212.png" alt="image-20220315155643212"></p><p>打包之后，查看一下生成的镜像，发现该镜像的大小为<code>1.1G</code>，显然达到了一个不能接受的大小，但令人难以置信的是一开始我就将这个镜像推送至<code>DockerHub</code>，然后又将这个镜像拉取到部署到服务器上。</p><h3 id="Version-2">Version 2</h3><p>由于考虑到，上一个版本中我们是以<code>Ubuntu</code>作为底层镜像来进行镜像构建的，但实际上我们并不需要<code>Ubuntu</code>中附加的诸多软件和功能，我们仅仅需要一个能够运行代码的环境，那么有没有这样的操作系统镜像呢？<code>Alpine</code> 操作系统是一个面向安全的轻型 <code>Linux</code> 发行版。它不同于通常 <code>Linux</code> 发行版，<code>Alpine</code> 采用了 <code>musl libc</code> 和 <code>busybox</code> 以减小系统的体积和运行时资源消耗。<code>Alpine Docker</code>镜像也继承了<code>Alpine Linux</code>发行版的这些优势。相比于其他<code> Docker</code>镜像，它的容量非常小，仅仅只有<code>5 MB</code>左右（对比<code>Ubuntu</code>系列镜像接近<code>200 MB</code>），且拥有非常友好的包管理机制。官方镜像来自 <code>docker-alpine</code> 项目。为了减小<code>docker</code>镜像的体积，这次使用了<code>golang:alpine</code>作为基础镜像进行构建，其中移除了非必要的工具，只提供最基础的功能，因此相比我们第一个版本使用的Ubuntu镜像可以节省很多的体积。</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs dockerfile"><span class="hljs-comment"># Version: 0.2</span><br><span class="hljs-keyword">FROM</span> golang:alpine<br><br><span class="hljs-keyword">COPY</span><span class="bash"> . /<span class="hljs-variable">$GOPATH</span>/src/Fouda/</span><br><span class="hljs-keyword">WORKDIR</span><span class="bash"> /<span class="hljs-variable">$GOPATH</span>/src/Fouda/</span><br><br><span class="hljs-keyword">RUN</span><span class="bash"> go env -w GO111MODULE=on</span><br><span class="hljs-keyword">RUN</span><span class="bash"> go env -w GOPROXY=https://goproxy.cn,direct</span><br><br><span class="hljs-keyword">RUN</span><span class="bash"> go mod tidy</span><br><span class="hljs-keyword">RUN</span><span class="bash"> go build ./cmd/main.go</span><br><br><span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8089</span>:<span class="hljs-number">8089</span><br><span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">&quot;go&quot;</span>,<span class="hljs-string">&quot;run&quot;</span>,<span class="hljs-string">&quot;./cmd/main.go&quot;</span>]</span><br></code></pre></td></tr></table></figure><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220315160140580.png" alt=""></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220315162339116.png" alt=""></p><p>如我们所预料的那样，使用这个方法改进后的镜像大小，下降了<code>400M</code>左右，但是<code>700M</code>还是让人无法接受，我们使用命令查看一下镜像构建时每层的大小。可以看到在<code>LABEL</code>命令执行前，文件的体积已经很大了。这是因为我们还是依赖<code>Golang</code>的编译环境，并且在编译<code>Golang</code>时还要通过网络拉取很多的依赖库，导致最终的镜像体积爆炸。</p><h3 id="Version-3">Version 3</h3><p>我们着手想办法继续减小构建出的镜像体积。我了解到一种分阶段构建的方法，也即先使用一个拥有编译环境的镜像对代码进行编译，然后将编译后的代码拷贝至一个新的<code>alpine</code>镜像中运行我们编译后的二进制文件。</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs dockerfile"><span class="hljs-comment"># Version: 0.3</span><br><span class="hljs-keyword">FROM</span> golang:alpine as compiler<br><br><span class="hljs-keyword">WORKDIR</span><span class="bash"> /Fouda/</span><br><span class="hljs-keyword">COPY</span><span class="bash"> . /Fouda/</span><br><br><span class="hljs-keyword">RUN</span><span class="bash"> go env -w GO111MODULE=on</span><br><span class="hljs-keyword">RUN</span><span class="bash"> go env -w GOPROXY=https://goproxy.cn,direct</span><br><br><span class="hljs-keyword">RUN</span><span class="bash"> go mod tidy</span><br><span class="hljs-keyword">RUN</span><span class="bash"> go build -o fouda ./cmd/main.go</span><br><br><span class="hljs-keyword">FROM</span> alpine<br><br><span class="hljs-keyword">WORKDIR</span><span class="bash"> /fouda</span><br><span class="hljs-keyword">COPY</span><span class="bash">  --from=compiler /Fouda/fouda /fouda/fouda</span><br><br><span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8089</span>:<span class="hljs-number">8089</span><br><span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">&quot;./fouda&quot;</span>]</span><br></code></pre></td></tr></table></figure><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220315163227069.png" alt=""></p><p>通过这个方法，我们发现镜像的大小大幅下降，下降至30M。</p><h3 id="Version-4">Version 4</h3><p>到这里镜像的大小已经大幅度压缩了，还有什么办法可以进一步减小镜像的大小吗？办法还真有！<code>Alpine</code>相比于<code>Ubuntu</code>是一个大小足够小的操作系统镜像，但是其中还是携带了一部分的包管理工具，这些包管理工具对我们来说还是多余的，能否将他们也去掉呢？</p><p>这时候就需要用到<code>scratch</code>镜像，官方说明：该镜像是一个空的镜像，可以用作构建<code>docker</code>镜像的最小基础镜像使用，并且在其上可以运行没有依赖的二进制文件。这个镜像无疑是最满足我们需求的。</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs dockerfile"><span class="hljs-comment"># Version: 0.4</span><br><span class="hljs-keyword">FROM</span> golang:alpine as compiler<br><br><span class="hljs-keyword">WORKDIR</span><span class="bash"> /Fouda/</span><br><span class="hljs-keyword">COPY</span><span class="bash"> . /Fouda/</span><br><br><span class="hljs-keyword">ENV</span> CGO_ENABLED=<span class="hljs-number">0</span><br><br><span class="hljs-keyword">RUN</span><span class="bash"> go env -w GO111MODULE=on</span><br><span class="hljs-keyword">RUN</span><span class="bash"> go env -w GOPROXY=https://goproxy.cn,direct</span><br><br><span class="hljs-keyword">RUN</span><span class="bash"> go mod tidy</span><br><span class="hljs-keyword">RUN</span><span class="bash"> go build -o fouda ./cmd/main.go</span><br><br><span class="hljs-keyword">FROM</span> scratch<br><br><span class="hljs-keyword">WORKDIR</span><span class="bash"> /fouda</span><br><span class="hljs-keyword">COPY</span><span class="bash">  --from=compiler /Fouda/fouda /fouda/fouda</span><br><br><span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8089</span>:<span class="hljs-number">8089</span><br><span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">&quot;./fouda&quot;</span>]</span><br></code></pre></td></tr></table></figure><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220315163741050.png" alt=""></p><p>构建完成，镜像的体积又一次减少了<code>7M</code>。</p><h3 id="Version-5">Version 5</h3><p>我们再过分一点，在<code>go build</code>阶段添加参数，去掉了调试信息以减小镜像尺寸，并且禁用<code>cgo</code>。</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs dockerfile"><span class="hljs-comment"># Version: 0.5</span><br><span class="hljs-keyword">FROM</span> golang:alpine as compiler<br><br><span class="hljs-keyword">WORKDIR</span><span class="bash"> /Fouda/</span><br><span class="hljs-keyword">COPY</span><span class="bash"> . /Fouda/</span><br><br><span class="hljs-keyword">ENV</span> CGO_ENABLED=<span class="hljs-number">0</span><br><br><span class="hljs-keyword">RUN</span><span class="bash"> go env -w GO111MODULE=on</span><br><span class="hljs-keyword">RUN</span><span class="bash"> go env -w GOPROXY=https://goproxy.cn,direct</span><br><br><span class="hljs-keyword">RUN</span><span class="bash"> go mod tidy</span><br><span class="hljs-keyword">RUN</span><span class="bash"> go build -ldflags=<span class="hljs-string">&quot;-s -w&quot;</span> -o fouda ./cmd/main.go</span><br><br><span class="hljs-keyword">FROM</span> scratch<br><br><span class="hljs-keyword">WORKDIR</span><span class="bash"> /fouda</span><br><span class="hljs-keyword">COPY</span><span class="bash">  --from=compiler /Fouda/fouda /fouda/fouda</span><br><br><span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8089</span>:<span class="hljs-number">8089</span><br><span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">&quot;./fouda&quot;</span>]</span><br></code></pre></td></tr></table></figure><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220315163801953.png" alt=""></p><p>镜像的体积又稍微减少了一些，至此我停止继续折腾它了。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在我使用&lt;code&gt;docker&lt;/code&gt;对我目前负责的一个项目进行部署时，遇到了这样的一个问题，使用&lt;code&gt;dockerfile&lt;/code&gt;构建出的&lt;code&gt;docker&lt;/code&gt;镜像的体积太大了。当时我只觉得这是不可避免的，毕竟容器的底层还是运行着一个操</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Docker" scheme="https://siegelion.cn/tags/Docker/"/>
    
    <category term="后端" scheme="https://siegelion.cn/tags/%E5%90%8E%E7%AB%AF/"/>
    
  </entry>
  
  <entry>
    <title>动手实现一个Go的协程池</title>
    <link href="https://siegelion.cn/2022/03/04/%E5%8A%A8%E6%89%8B%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAGo%E7%9A%84%E5%8D%8F%E7%A8%8B%E6%B1%A0/"/>
    <id>https://siegelion.cn/2022/03/04/%E5%8A%A8%E6%89%8B%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAGo%E7%9A%84%E5%8D%8F%E7%A8%8B%E6%B1%A0/</id>
    <published>2022-03-04T15:15:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言">前言</h2><p>前几天听学长们回忆面试的经历，其他的内容大多都不记得了，只记得他们提到面试官问了<strong>线程池</strong>的相关概念，线程池对我来说不算熟悉也不算陌生。</p><p>按照我的理解，线程池主要应用在多线程服务器中，是为了解决存在大量请求时，为了使每个请求可以被迅速处理，而为每一个请求创建了一个处理线程，但每个处理请求本身所需要消耗的时间是很短暂的，在处理请求的任务完成后，运行时会销毁线程、进行GC等一系列操作，导致在处理请求的任务中，创建线程、销毁线程所需的时间和内存的开销在整个任务中所占的比例过高。并且对创建线程的数量不加限制容易引起系统资源耗尽的风险。此时可以利用池化的思想，也即存在一个线程的池子，池子中的线程数量是固定的，每当处理一个请求需要一个线程时便从池子中获取一个，任务处理完毕后线程空闲，便重新放回池子中，等待重新复用。</p><p>除了线程池外，还有一些类似的概念，例如内存池、连接池、协程池等，都是在如下的场景中进行使用的：</p><ul><li>单个任务处理时间比较短</li><li>需要处理的任务数量很大</li></ul><p>Go语言中原生提供了一种轻量级的线程实现：协程，虽然协程相较于线程来说更加轻量级导致创建与销毁时消耗的资源更少，但是不可避免的在处理任务的数量过大时一样会导致以上提到的诸多问题。因此本文动手实现了一个协程池，本文的实现参考了<a href="http://www.qinblog.net/Article/article/19.html">100 行写一个 go 的协程池 (任务池)</a>。</p><h2 id="实现">实现</h2><h3 id="任务定义">任务定义</h3><p>协程池目的本身是为了降低处理大量任务时的资源消耗，因此我们首先对任务进行定义，我们将每个任务抽象为一个函数的执行，因此每个任务主要的内容就是执行函数以及函数参数：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> Task <span class="hljs-keyword">struct</span> &#123;<br>Params  []<span class="hljs-keyword">interface</span>&#123;&#125;<br>Handler <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(params ...<span class="hljs-keyword">interface</span>&#123;&#125;)</span></span><br>&#125;<br></code></pre></td></tr></table></figure><h3 id="协程池定义">协程池定义</h3><p>在定义了任务之后，我们对协程池的数据结构进行定义：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> GoroutinesPool <span class="hljs-keyword">struct</span> &#123;<br>capacity  <span class="hljs-keyword">uint64</span> <span class="hljs-comment">// 容量</span><br>workerNum <span class="hljs-keyword">uint64</span> <span class="hljs-comment">// 目前的协程数</span><br>status    <span class="hljs-keyword">uint64</span> <span class="hljs-comment">// 状态</span><br><br>taskChan <span class="hljs-keyword">chan</span> *Task <span class="hljs-comment">//任务池</span><br><br>sync.Mutex <span class="hljs-comment">// 锁</span><br>&#125;<br></code></pre></td></tr></table></figure><p>协程池的状态进行如下定义，此处暂时只定义两种状态：运行和中止：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">const</span> (<br>RUNNING <span class="hljs-keyword">uint64</span> = <span class="hljs-literal">iota</span><br>STOP<br>)<br></code></pre></td></tr></table></figure><h4 id="获取协程池容量">获取协程池容量</h4><p>协程池的容量在初始化后不会发生变化，因此读取协程池的容量不涉及到并发读取的冲突问题，所以可以直接读取。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *GoroutinesPool)</span> <span class="hljs-title">getPoolCapacity</span><span class="hljs-params">()</span> <span class="hljs-title">uint64</span></span> &#123;<br><span class="hljs-keyword">return</span> p.capacity<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="获取已经启动协程数">获取已经启动协程数</h4><p>但启动的协程数会发生变化，因此需要考虑</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *GoroutinesPool)</span> <span class="hljs-title">getPoolWorkerNum</span><span class="hljs-params">()</span> <span class="hljs-title">uint64</span></span> &#123;<br><span class="hljs-keyword">return</span> atomic.LoadUint64(&amp;p.workerNum)<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="修改已经启动协程数">修改已经启动协程数</h4><p>由于在Go语言中就连最简单的赋值操作实际上都不是原子操作，在底层实现时是先向内存的低32位进行赋值，然后再向高32位赋值。因此在Go语言的代码中，就算是简单的读取和赋值，在高并发的情况下也无法保证数据的一致性。因此我们采用<code>atomic</code>包中的函数来保证我们的操作是具备原子性的，避免在改变变量值的 同时有另外一个协程读取了这个还没赋值完成的变量。</p><h5 id="增加">增加</h5><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *GoroutinesPool)</span> <span class="hljs-title">addNewWorker</span><span class="hljs-params">()</span></span> &#123;<br>atomic.AddUint64(&amp;p.workerNum, <span class="hljs-number">1</span>)<br>&#125;<br></code></pre></td></tr></table></figure><h5 id="减少">减少</h5><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *GoroutinesPool)</span> <span class="hljs-title">decWorker</span><span class="hljs-params">()</span></span> &#123;<br>atomic.AddUint64(&amp;p.workerNum, ^<span class="hljs-keyword">uint64</span>(<span class="hljs-number">0</span>))<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="获取协程池状态">获取协程池状态</h4><p>读取协程池的状态时同上，使用<code>atomic</code>保证原子性。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *GoroutinesPool)</span> <span class="hljs-title">getPoolStatus</span><span class="hljs-params">()</span> <span class="hljs-title">uint64</span></span> &#123;<br><span class="hljs-keyword">return</span> atomic.LoadUint64(&amp;p.status)<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="设置协程池状态">设置协程池状态</h4><p>设置协程池的状态时，由于涉及的逻辑判断较多，无法使用单条语句保证原子性，因此此处我们使用锁的方式来保证数据一致性。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *GoroutinesPool)</span> <span class="hljs-title">setPoolStatus</span><span class="hljs-params">(status <span class="hljs-keyword">uint64</span>)</span> <span class="hljs-title">bool</span></span> &#123;<br><span class="hljs-keyword">defer</span> p.Unlock()<br>p.Lock()<br><span class="hljs-keyword">if</span> p.status == status &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br>&#125; <span class="hljs-keyword">else</span> &#123;<br>p.status = status<br><span class="hljs-keyword">return</span> <span class="hljs-literal">true</span><br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="启动一个新的任务">启动一个新的任务</h4><p>启动一个新的任务需要先获取协程池的锁，获取到后判断协程池的状态，若没有处于关闭状态，并且容量充足，则启动一个新的协程并向任务队列中写入新的任务。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *GoroutinesPool)</span> <span class="hljs-title">NewTask</span><span class="hljs-params">(t *Task)</span> <span class="hljs-title">error</span></span> &#123;<br><span class="hljs-keyword">defer</span> p.Unlock()<br>p.Lock()<br><br><span class="hljs-keyword">if</span> p.getPoolStatus() == RUNNING &#123;<br><span class="hljs-keyword">if</span> p.getPoolCapacity() &gt; p.getPoolWorkerNum() &#123;<br>p.newTaskGoroutine()<br>&#125;<br>p.taskChan &lt;- t<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125; <span class="hljs-keyword">else</span> &#123;<br><span class="hljs-keyword">return</span> errors.New(<span class="hljs-string">&quot;goroutines pool has already closed&quot;</span>)<br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><h5 id="启动新的协程">启动新的协程</h5><p>启动新的协程时，需要先将协程池中启动的协程数增加，然后启动一个协程，协程中主要由一个循环监听任务队列的循环组成，一旦监听到任务队列中存在未处理的任务，便取出任务进行执行。</p><p>由于执行任务的协程若在执行任务的过程中<code>panic</code>，会导致整个进程的崩溃，因此我们需要在每个协程执行时加入<code>recover</code>进行兜底。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *GoroutinesPool)</span> <span class="hljs-title">newTaskGoroutine</span><span class="hljs-params">()</span></span> &#123;<br>p.addNewWorker()<br><br><span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> &#123;<br><span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> &#123;<br>p.decWorker()<br><span class="hljs-keyword">if</span> r := <span class="hljs-built_in">recover</span>(); r != <span class="hljs-literal">nil</span> &#123;<br>log.Printf(<span class="hljs-string">&quot;worker %s has panic\n&quot;</span>, r)<br>&#125;<br>&#125;()<br><br><span class="hljs-keyword">for</span> &#123;<br><span class="hljs-keyword">select</span> &#123;<br><span class="hljs-keyword">case</span> task, ok := &lt;-p.taskChan:<br><span class="hljs-keyword">if</span> !ok &#123;<br><span class="hljs-keyword">return</span><br>&#125;<br>task.Handler(task.Params...)<br>&#125;<br>&#125;<br>&#125;()<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="关闭协程池">关闭协程池</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *GoroutinesPool)</span> <span class="hljs-title">ClosePool</span><span class="hljs-params">()</span></span> &#123;<br>p.setPoolStatus(STOP)<br><span class="hljs-keyword">for</span> <span class="hljs-built_in">len</span>(p.taskChan) &gt; <span class="hljs-number">0</span> &#123;<br>time.Sleep(time.Second * <span class="hljs-number">60</span>)<br>&#125;<br><span class="hljs-built_in">close</span>(p.taskChan)<br>&#125;<br></code></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;前几天听学长们回忆面试的经历，其他的内容大多都不记得了，只记得他们提到面试官问了&lt;strong&gt;线程池&lt;/strong&gt;的相关概念，线程池对我来说不算熟悉也不算陌生。&lt;/p&gt;
&lt;p&gt;按照我的理解，线程池主要应用在多线程服务器中，是为了</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="后端" scheme="https://siegelion.cn/tags/%E5%90%8E%E7%AB%AF/"/>
    
    <category term="Go" scheme="https://siegelion.cn/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>Redis 分布式锁的实现</title>
    <link href="https://siegelion.cn/2022/02/10/Redis%20%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E5%AE%9E%E7%8E%B0/"/>
    <id>https://siegelion.cn/2022/02/10/Redis%20%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E5%AE%9E%E7%8E%B0/</id>
    <published>2022-02-10T10:15:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言">前言</h2><blockquote><p>本文是阅读《Redis 实战》期间的学习笔记，书中的主要功能使用使用<code>Python</code>进行实现，本文使用<code>Golang</code>对功能进行复现。</p></blockquote><p>Redis为开发者们提供了事务的功能，具体的实现为：以特殊命令<code>MULTI</code>开始，接着输入要执行的多条命令，最后输入特殊命令<code>EXEC</code>标志着事物的结束，同时还需要配合<code>WATCH</code>命令以保持数据的一致性，在<code>WATCH</code>期间，当客户端执行<code>EXEC</code>时，若<code>Redis</code>检测到，有其他客户端抢先对<code>WATCH</code>的数据进行了修改，则会通知当前客户端本次事务执行失败。所以说本质上，<code>Redis</code>事务的数据一致性是通过借助锁来实现的，只不过这个锁本质上是一把乐观锁。</p><blockquote><ul><li>乐观锁：乐观锁假设数据一般情况不会造成冲突，所以在数据进行提交更新的时候，才会正式对数据的冲突与否进行检测，如果冲突，则返回给用户异常信息，让用户决定如何去做。</li><li>悲观锁：悲观锁，具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务，以及来自外部系统的事务处理)修改持保守态度。因此，在整个数据处理过程中，将数据处于锁定状态。<ul><li>共享锁：多个事务可以同时读取数据，但是不能修改数据。</li><li>排他锁：只有一个事务可以独占数据，其他事务需要阻塞等待。</li></ul></li></ul></blockquote><p>这种乐观锁，由于在执行事务前并不会检测检测数据的一致性，当检测到数据的不一致时，本次事务执行失败，本次事务逻辑计算、网络传输时间的消耗也就失去了意义，因为还需要再一次对事务进行执行直到事务执行通过。当并发量上升时，无可避免地会发生多个事务同时修改同一数据的情况，这时会出现事务大量重复尝试的情况，导致平均每个事务的执行时间大幅度增加。</p><p>这种情况下只借助Redis中提供的Watch机制以达到乐观锁的功能，会对性能造成一定的负担，为了以更好的形式实现想要的功能，我们需要自己动手实现一个悲观锁，虽然多个事务同时读取同一数据时，也会造成多个事务反复尝试的情况，但是悲观锁的先获取锁的机制，节省了逻辑计算与网络传输的时间。</p><hr><p>虽然在逻辑上，悲观锁在高并发情况下的性能理应更优，但是在我的2天的实际的测试中，由于测试方法的不恰当，导致最终的结果，并不如上文预料的那样，反而与之相反。</p><p><img src="https://dl4.weshineapp.com/gif/20210918/174d3c4fad3d8cf5605870a397636923.gif?f=micro_5b+D57Sv" alt="img"></p><h2 id="代码实现">代码实现</h2><p>Anyway！还是分析一下我们的代码实现吧。</p><h3 id="业务场景">业务场景</h3><p>我们进行实验的业务场景为：“一个在线商店，可以供用户出售或者购买商品。”Redis中的数据结构如下：</p><ul><li>用户信息：散列类型<ul><li>key: <code>&lt;users&gt; : &lt;userId&gt;</code></li><li>value:<ul><li>name:</li><li>funds:</li></ul></li></ul></li><li>用户背包：set类型<ul><li>key: <code>inventory : &lt;sellerId&gt;</code></li><li>value: <code>&lt;itemId&gt;</code></li></ul></li><li>市场：<code>zset</code>类型<ul><li>key: <code>market:</code></li><li>value: <code>&lt;itemId&gt; . &lt;sellerId&gt;</code></li><li>分值: <code>&lt;价格&gt;</code></li></ul></li></ul><h3 id="Watch-方式实现的乐观锁">Watch 方式实现的乐观锁</h3><h4 id="使用Watch方式实现上架商品">使用<code>Watch</code>方式实现上架商品</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SaleGoodWithWatch</span><span class="hljs-params">(item <span class="hljs-keyword">string</span>, sellerId <span class="hljs-keyword">int</span>, price <span class="hljs-keyword">float64</span>)</span> <span class="hljs-title">bool</span></span> &#123;<br>ctx := context.TODO()<br>inventory := fmt.Sprintf(<span class="hljs-string">&quot;inventory:%d&quot;</span>, sellerId)<br>marketItemID := fmt.Sprintf(<span class="hljs-string">&quot;%s.%d&quot;</span>, item, sellerId)<br>transactionFuc := <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *redis.Tx)</span> <span class="hljs-title">error</span></span> &#123; <span class="hljs-comment">// 事务函数</span><br>res, _ := myRedis.SIsMember(ctx, inventory, item).Result()<br><span class="hljs-keyword">if</span> res &#123; <span class="hljs-comment">// 用户是否拥有物品</span><br>pipe := myRedis.Pipeline()<br>pipe.ZAdd(ctx, <span class="hljs-string">&quot;market:&quot;</span>, &amp;redis.Z&#123;Score: price, Member: marketItemID&#125;)<br>pipe.SRem(ctx, inventory, item)<br>_, err := pipe.Exec(ctx) <span class="hljs-comment">// 执行事务</span><br><span class="hljs-keyword">return</span> err<br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br><br><span class="hljs-keyword">for</span> &#123;<br>err := myRedis.Watch(ctx, transactionFuc, inventory) <span class="hljs-comment">// Watch</span><br><span class="hljs-keyword">if</span> err == <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">true</span><br>&#125;<br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="使用Watch方式实现购买商品">使用<code>Watch</code>方式实现购买商品</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">PurchaseGoodWithWatch</span><span class="hljs-params">(buyerId, sellerId <span class="hljs-keyword">int</span>, itemId <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">bool</span></span> &#123;<br>ctx := context.TODO()<br>buyer := fmt.Sprintf(<span class="hljs-string">&quot;user:%d&quot;</span>, buyerId)<br>inventory := fmt.Sprintf(<span class="hljs-string">&quot;inventory:%d&quot;</span>, buyerId)<br>seller := fmt.Sprintf(<span class="hljs-string">&quot;user:%d&quot;</span>, sellerId)<br>marketItem := fmt.Sprintf(<span class="hljs-string">&quot;%s.%d&quot;</span>, itemId, sellerId)<br><br>pipe := myRedis.Pipeline()<br>funds, _ := myRedis.HGet(ctx, buyer, <span class="hljs-string">&quot;Funds&quot;</span>).Result() <span class="hljs-comment">// 资金</span><br>price, _ := myRedis.ZScore(ctx, <span class="hljs-string">&quot;market:&quot;</span>, marketItem).Result() <span class="hljs-comment">// 商品价格</span><br>pipe.Exec(ctx)<br><br>f, _ := strconv.ParseInt(funds, <span class="hljs-number">10</span>, <span class="hljs-number">64</span>)<br>transactionFuc := <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *redis.Tx)</span> <span class="hljs-title">error</span></span> &#123; <span class="hljs-comment">// 事务函数</span><br>pipe := myRedis.Pipeline()<br>pipe.HIncrBy(ctx, seller, <span class="hljs-string">&quot;Funds&quot;</span>, <span class="hljs-keyword">int64</span>(price))<br>pipe.HIncrBy(ctx, buyer, <span class="hljs-string">&quot;Funds&quot;</span>, <span class="hljs-keyword">int64</span>(-price))<br>pipe.SAdd(ctx, inventory, itemId)<br>pipe.ZRem(ctx, <span class="hljs-string">&quot;market:&quot;</span>, marketItem).Result()<br>_, err := pipe.Exec(ctx)<br><span class="hljs-keyword">return</span> err<br>&#125;<br><br><span class="hljs-keyword">if</span> <span class="hljs-keyword">float64</span>(f) &gt;= price &#123; <span class="hljs-comment">// 是否有足够的资金</span><br><span class="hljs-keyword">for</span> &#123;<br>err := myRedis.Watch(ctx, transactionFuc, <span class="hljs-string">&quot;market:&quot;</span>, buyer, seller) <span class="hljs-comment">// Watch</span><br><span class="hljs-keyword">if</span> err == <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">true</span><br>&#125;<br>&#125;<br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br>&#125;<br></code></pre></td></tr></table></figure><h3 id="自己实现的悲观锁">自己实现的悲观锁</h3><p><code>Redis</code>中拥有的<code>SetNX</code>命令，是<code>set if not exist</code>的简写，因此当某个键值对不存在时使用该命令会设置键值，指定键对应的键值对存在时，该命令时会返回错误。这可以看作一个原子命令，可以用该命令实现获取锁的功能。</p><p>键值对的键名为锁的名字，键值对的值则为一个不会重复的UUID，并为该键值对设置一个过期时间。设置成功则意味着获取到锁。释放锁的操作，则由删除该键值对的操作实现。</p><p>虽然如上文所说，虽然在我们的测试中，通过<code>SetNX</code>方式实现的悲观锁的性能不如<code>Watch</code>，但并不意味着我们实现锁的方式是错误的。在此记录我们对锁的实现：</p><h4 id="获取锁">获取锁</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">acquireLock</span><span class="hljs-params">(lockName <span class="hljs-keyword">string</span>, acquireTimeout <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">string</span></span> &#123;<br>end := time.Now().Add(time.Duration(acquireTimeout) * time.Second)<br>identifier := uuid.New().String()<br>lockName = fmt.Sprintf(<span class="hljs-string">&quot;lock:%s&quot;</span>, lockName)<br><span class="hljs-keyword">for</span> time.Now().Before(end) &#123;<br>res, _ := myRedis.SetNX(context.TODO(), lockName, identifier, time.Duration(<span class="hljs-number">1</span>)*time.Second).Result()<br><span class="hljs-keyword">if</span> res &#123;<br><span class="hljs-keyword">return</span> identifier<br>&#125; <span class="hljs-keyword">else</span> &#123;<br>time.Sleep(time.Millisecond)<br>&#125;<br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-string">&quot;&quot;</span><br>&#125;<br></code></pre></td></tr></table></figure><h4 id="释放锁">释放锁</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">releaseLock</span><span class="hljs-params">(lockName, identifier <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">bool</span></span> &#123;<br>ctx := context.TODO()<br>lockName = fmt.Sprintf(<span class="hljs-string">&quot;lock:%s&quot;</span>, lockName)<br>transactionFuc := <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *redis.Tx)</span> <span class="hljs-title">error</span></span> &#123;<br>res, _ := t.Get(ctx, lockName).Result()<br><span class="hljs-keyword">if</span> res == identifier &#123;<br>_, err := myRedis.TxPipelined(ctx, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(pipe redis.Pipeliner)</span> <span class="hljs-title">error</span></span> &#123;<br>pipe.Del(ctx, lockName)<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;)<br><span class="hljs-keyword">return</span> err<br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br><br><span class="hljs-keyword">for</span> &#123;<br>err := myRedis.Watch(ctx, transactionFuc, lockName) <span class="hljs-comment">// Watch 锁</span><br><span class="hljs-keyword">if</span> err == <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">true</span><br>&#125;<br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="使用SetNX方式实现上架商品">使用<code>SetNX</code>方式实现上架商品</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SaleGoodWithLock</span><span class="hljs-params">(item <span class="hljs-keyword">string</span>, sellerId <span class="hljs-keyword">int</span>, price <span class="hljs-keyword">float64</span>)</span> <span class="hljs-title">bool</span></span> &#123;<br>ctx := context.TODO()<br>inventory := fmt.Sprintf(<span class="hljs-string">&quot;inventory:%d&quot;</span>, sellerId)<br>marketItemID := fmt.Sprintf(<span class="hljs-string">&quot;%s.%d&quot;</span>, item, sellerId)<br>res, _ := myRedis.SIsMember(ctx, inventory, item).Result()<br><span class="hljs-keyword">if</span> res &#123; <span class="hljs-comment">// 用户是否拥有物品</span><br>identifier := myRedis.acquireLock(inventory, <span class="hljs-number">5</span>) <span class="hljs-comment">// 获取悲观锁</span><br><span class="hljs-keyword">if</span> identifier == <span class="hljs-string">&quot;&quot;</span> &#123;                           <span class="hljs-comment">// 是否获取到锁</span><br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br>&#125;<br>pipe := myRedis.Pipeline()<br>pipe.ZAdd(ctx, <span class="hljs-string">&quot;market:&quot;</span>, &amp;redis.Z&#123;Score: price, Member: marketItemID&#125;).Result()<br>pipe.SRem(ctx, inventory, item)<br>pipe.Exec(ctx)<br><br>myRedis.releaseLock(inventory, identifier) <span class="hljs-comment">// 释放锁</span><br><span class="hljs-keyword">return</span> <span class="hljs-literal">true</span><br>&#125; <span class="hljs-keyword">else</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="使用SetNX方式实现购买商品">使用<code>SetNX</code>方式实现购买商品</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">PurchaseGoodWithLock</span><span class="hljs-params">(buyerId, sellerId <span class="hljs-keyword">int</span>, itemId <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">bool</span></span> &#123;<br>ctx := context.TODO()<br>buyer := fmt.Sprintf(<span class="hljs-string">&quot;user:%d&quot;</span>, buyerId)<br>inventory := fmt.Sprintf(<span class="hljs-string">&quot;inventory:%d&quot;</span>, buyerId)<br>seller := fmt.Sprintf(<span class="hljs-string">&quot;user:%d&quot;</span>, sellerId)<br>marketItem := fmt.Sprintf(<span class="hljs-string">&quot;%s.%d&quot;</span>, itemId, sellerId)<br><br>funds, _ := myRedis.HGet(ctx, buyer, <span class="hljs-string">&quot;Funds&quot;</span>).Result()<br>price, _ := myRedis.ZScore(ctx, <span class="hljs-string">&quot;market:&quot;</span>, marketItem).Result()<br>f, _ := strconv.ParseInt(funds, <span class="hljs-number">10</span>, <span class="hljs-number">64</span>)<br><br><span class="hljs-keyword">if</span> <span class="hljs-keyword">float64</span>(f) &gt;= price &#123; <span class="hljs-comment">// 是否拥有足够的资金</span><br>identifier := myRedis.acquireLock(<span class="hljs-string">&quot;market:&quot;</span>, <span class="hljs-number">5</span>) <span class="hljs-comment">// 获取锁</span><br><span class="hljs-keyword">if</span> identifier == <span class="hljs-string">&quot;&quot;</span> &#123;                           <span class="hljs-comment">// 是否获取到锁</span><br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br>&#125;<br>pipe := myRedis.Pipeline()<br>pipe.HIncrBy(ctx, seller, <span class="hljs-string">&quot;Funds&quot;</span>, <span class="hljs-keyword">int64</span>(price))<br>pipe.HIncrBy(ctx, buyer, <span class="hljs-string">&quot;Funds&quot;</span>, <span class="hljs-keyword">int64</span>(-price))<br>pipe.SAdd(ctx, inventory, itemId)<br>pipe.ZRem(ctx, <span class="hljs-string">&quot;market:&quot;</span>, marketItem)<br>pipe.Exec(ctx)<br>myRedis.releaseLock(<span class="hljs-string">&quot;market:&quot;</span>, identifier) <span class="hljs-comment">// 释放锁</span><br><span class="hljs-keyword">return</span> <span class="hljs-literal">true</span><br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br>&#125;<br></code></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本文是阅读《Redis 实战》期间的学习笔记，书中的主要功能使用使用&lt;code&gt;Python&lt;/code&gt;进行实现，本文使用&lt;code&gt;Golang&lt;/code&gt;对功能进行复现。&lt;/p&gt;
&lt;/blockquo</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="数据库" scheme="https://siegelion.cn/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    <category term="Redis" scheme="https://siegelion.cn/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>Talent Plan（ 路径二）Project 1</title>
    <link href="https://siegelion.cn/2022/01/30/Talent%20Plan%EF%BC%88%20%E8%B7%AF%E5%BE%84%E4%BA%8C%EF%BC%89Project%201/"/>
    <id>https://siegelion.cn/2022/01/30/Talent%20Plan%EF%BC%88%20%E8%B7%AF%E5%BE%84%E4%BA%8C%EF%BC%89Project%201/</id>
    <published>2022-01-30T20:15:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<p>Project1的主要内容是实现一个单机的K/V存储引擎，并通过<a href="https://grpc.io/docs/guides/">gRPC</a>为上层提供服务，通过RPC调用该存储引擎可以执行Put、Delete、Get、Scan四种操作。存储引擎的底层借助<a href="https://github.com/dgraph-io/badger">badger</a>作为底层K/V数据库实现。</p><p>该项目的任务可划分为以下两个步骤，包括：</p><ol><li>实现一个单机的存储引擎。</li><li>借助实现的存储引擎，实现K/V服务。</li></ol><p>虽然在Project1阶段，涉及的代码量并不多，只要明确工作内容，然后在实现时注意细节便可通过全部测试点。但是Project1阶段实现的单机存储引擎与Project4实现的分布式存储引擎的数据结构有异曲同工之妙，所以对在完成Project1的同时，对<code>Storage</code>与<code>StorageReader</code>的设计进行理解对后续任务的完成有一些帮助。</p><h2 id="K-V存储引擎">K/V存储引擎</h2><p>TinyKV中对存储引擎进行了抽象，抽象为以下的接口：</p><blockquote><p>定义在 kv/storage/storage.go</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> Storage <span class="hljs-keyword">interface</span> &#123;<br>Start() error<br>Stop() error<br>Write(ctx *kvrpcpb.Context, batch []Modify) error<br>Reader(ctx *kvrpcpb.Context) (StorageReader, error)<br>&#125;<br></code></pre></td></tr></table></figure><p>本Project中需要实现的<code>StandAloneStorage</code>就是实现了以上接口的一个存储引擎。我们对<code>StandAloneStorage</code>的数据结构进行如下设计：</p><blockquote><p>实现在 kv/storage/standalone_storage/standalone_storage.go</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> StandAloneStorage <span class="hljs-keyword">struct</span> &#123;<br>conf *config.Config  <span class="hljs-comment">// 配置文件</span><br>KvDB *badger.DB     <span class="hljs-comment">// 底层数据库</span><br>&#125;<br></code></pre></td></tr></table></figure><p><code>conf</code>字段保存存储引擎相关的配置信息对象的指针，<code>KvDB</code>字段保存底层数据库对象的指针。</p><p>与<code>StandAloneStorage</code>相关的函数主要有以下几个，简要对其进行说明：</p><ul><li><p><code>NewStandAloneStorage</code></p><p>初始化存储引擎，函数执行时会创建<code>StandAloneStorage</code>对象，初始化<code>conf</code>字段，将<code>KvDB</code>字段暂时设置为<code>nil</code>。</p></li><li><p><code>Start</code></p><p>启动存储引擎，根据<code>conf</code>字段中保存的<code>dbPath</code>信息，设置<code>badger</code>相关的配置信息，根据设置好的配置新建badger数据库，并将数据保存在目标文件夹中。并将<code>StandAloneStorage</code>对象的<code>KvDB</code>字段设置为新建的数据库对象的指针。</p></li><li><p><code>Stop</code></p><p>关闭底层数据库，根据<code>StandAloneStorage</code>对象<code>conf</code>字段中的信息，清空保存数据库的文件夹。</p></li><li><p><code>Reader</code></p><p>该函数的名字并非为<code>Read</code>而为<code>Reader</code>，是因为该函数的作用并不是读取数据，而是向上层返回一个可用来读取数据的<code>StorageReader</code>对象，通过该reader上层在进行调用时可以根据需要实现想要的Get或Scan操作。</p><p><code>StorageReader</code>实际上也实现了一个定义好的接口：</p><blockquote><p>定义在 kv/storage/storage.go</p></blockquote>  <figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> StorageReader <span class="hljs-keyword">interface</span> &#123;<br><span class="hljs-comment">// When the key doesn&#x27;t exist, return nil for the value</span><br>GetCF(cf <span class="hljs-keyword">string</span>, key []<span class="hljs-keyword">byte</span>) ([]<span class="hljs-keyword">byte</span>, error)<br>IterCF(cf <span class="hljs-keyword">string</span>) engine_util.DBIterator<br>Close()<br>&#125;<br></code></pre></td></tr></table></figure><p>为了实现<code>StandAloneStorage</code>的<code>Reader</code>函数，需要一个结构体实现了<code>StorageReader</code>接口，这个数据结构为<code>StandaloneStorageReader</code>：</p><blockquote><p>StorageReader 定义在 /kv/storage/storage.go</p></blockquote>  <figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> StorageReader <span class="hljs-keyword">interface</span> &#123;<br><span class="hljs-comment">// When the key doesn&#x27;t exist, return nil for the value</span><br>GetCF(cf <span class="hljs-keyword">string</span>, key []<span class="hljs-keyword">byte</span>) ([]<span class="hljs-keyword">byte</span>, error)<br>IterCF(cf <span class="hljs-keyword">string</span>) engine_util.DBIterator<br>Close()<br>&#125;<br></code></pre></td></tr></table></figure><blockquote><p><code>storage.StorageReader</code>定义在 kv/storage/standalone_storage/standalone_storage_reader.go</p></blockquote>  <figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> StandaloneStorageReader <span class="hljs-keyword">struct</span> &#123;<br>Txn *badger.Txn<br>&#125;<br></code></pre></td></tr></table></figure><p>该结构体实际上是对<code>*badger.Txn</code>的一个封装，实现接口的函数时实际上也是在通过<code>*badger.Txn</code>来调用badger引擎的方法来实现数据存取。</p><p>需要注意的是：</p><ul><li>GetCF方法，在调用<code>engine_util.GetCFFromTxn</code>方法时，可能返回<code> badger.ErrKeyNotFound</code>的错误，若返回该错误，需要进行特判，将返回的val和err都设为nil。</li><li>IterCF方法，在调用<code>engine_util.NewCFIterator</code>初始化游标后，需要调用<code>Rewind()</code>将游标重置在初始位置。</li><li>Close方法，需要调用<code>Discard()</code>方法将连接关闭。</li></ul></li><li><p><code>Write</code></p><p>该方法接收的参数中，存在一个<code>batch</code>字段，对应一个<code>storage.Modify</code>类型的切片，该切片中保存多种修改，共有两种写操作类型：Put和Delete，需要根据每个修改的类型进行不同的操作，调用<code>engine_util</code>的相应方法，来实现数据写入。</p></li></ul><h2 id="K-V服务">K/V服务</h2><p>即为单机存储引擎对外暴露的gRPC接口，来调用存储引擎进行Get、Put、Delete、Scan四种操作。</p><h3 id="Get">Get</h3><ol><li>调用<code>server.storage.Reader</code>方法初始化一个<code>StorageReader</code>对象。</li><li>调用该<code>StorageReader</code>对象的GetCF方法，返回<code>val</code>和<code>err</code>值。</li><li>根据<code>va</code>l和<code>err</code>的取值情况，来进行返回对象的设置。<ul><li>如上文所说，在Get操作时，可能存在找不到该值的情况，这时返回的<code>val</code>和<code>err</code>都应该为<code>nil</code>。这时需要将返回对象的<code>NotFound</code>字段设置为<code>true</code>。</li><li>当<code>err</code>值不为<code>nil</code>，即可认为出现错误，将返回对象的<code>Error</code>字段设置为<code>err</code>。</li><li>其余情况为成功。</li></ul></li></ol><h3 id="Put">Put</h3><p>初始化<code>storage.Modify</code>对象，将<code>Data</code>字段设置为<code>storage.Put</code>对象，根据操作类型，设置<code>Key</code>、<code>Value</code>、<code>Cf</code>三个字段。然后调用<code>StandAloneStorage</code>的<code>Write</code>方法将数据写入即可。</p><h3 id="Delete">Delete</h3><p>与Put操作类似，只是将<code>Data</code>字段设置为<code>storage.Delete</code>对象。</p><h3 id="Scan">Scan</h3><p>Scan操作同样需要用到我们上文提到的<code>StorageReader</code>对象。</p><ol><li>调用<code>server.storage.Reader</code>方法初始化一个<code>StorageReader</code>对象。</li><li>调用<code>IterCF</code>方法返回一个游标。</li><li>使用迭代该游标，每次从游标中取出<code>key</code>与<code>val</code>。<ul><li>由于Scan操作的请求时会携带一个<code>limit</code>字段，所以在迭代时我们需要对迭代次数进行判断。</li><li>每次还需要对游标的有效性进行判断。</li></ul></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Project1的主要内容是实现一个单机的K/V存储引擎，并通过&lt;a href=&quot;https://grpc.io/docs/guides/&quot;&gt;gRPC&lt;/a&gt;为上层提供服务，通过RPC调用该存储引擎可以执行Put、Delete、Get、Scan四种操作。存储引擎的底层借助&lt;</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Go" scheme="https://siegelion.cn/tags/Go/"/>
    
    <category term="比赛" scheme="https://siegelion.cn/tags/%E6%AF%94%E8%B5%9B/"/>
    
  </entry>
  
  <entry>
    <title>记2021</title>
    <link href="https://siegelion.cn/2022/01/09/%E8%AE%B02021/"/>
    <id>https://siegelion.cn/2022/01/09/%E8%AE%B02021/</id>
    <published>2022-01-09T19:25:47.000Z</published>
    <updated>2022-01-09T19:25:47.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>2021年就要过去了，我不想怀念他。</p></blockquote><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20211230141322.jpg" alt=""></p><p>悄悄地，2021也走到了尾声，不过我的2021相比2020过得更加充实，这一年中我也有了更多的收获与感悟。</p><p>2021年我的生活中少了一些人，也多了一些人，少了一些事，也多了一些事。这些人和事有让我欣喜的、意外的、惋惜的以及怀念的。但我知道这都是我的生命中不可或缺的，他们成为了我人生中的一部分，也在悄悄地改变着我的人生。</p><h1>关于今年</h1><h2 id="与大家的分别">与大家的分别</h2><p>2021年的上半年，我结束了我大学本科四年的生活，告别了当初那个阴差阳错来到的学校——北方工大。这所坐落在石景山的学校，是那么小，在诺大的北京城中的众多高校中是那么不起眼，不起眼到和别人提起时，大家常常以为我指的是名字的北京工业大学。但是我爱它，即使它并不会记得曾有一个名叫韩孟男的学生在这里度过了四年的学习生活。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/722c415725cfe7aa85f8e33c0809265.jpg" alt=""></p><blockquote><p>我曾经在保研结束的时候，对大学的时光进行了回顾，这里算是再多说几句吧。</p></blockquote><p>在这个学校度过的四年中，我觉得最幸运的就是遇见了一群好兄弟吧，这也是毕业的时候，我除了他们并没有和其他人合影留念的想法的原因吧。分别的时候，难免有一些不舍，庆幸的是除了徐浩储以外，大家都留在了北京，因此幸运地我们时常还可以相聚，也许我们有一天会相隔更远的的距离，希望我们可以不相忘于江湖。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/574cde0974c72873b8a8974eb22c011.jpg" alt=""></p><h2 id="毕业设计">毕业设计</h2><p>作为本科四年的结尾，在毕业设计阶段遇到了一个让我十分尊敬的老师——丁维龙老师，跟丁老师的相处十分愉快，丁老师对我的毕业设计出自我研究生导师的项目这一点也十分支持，虽然在完成毕业设计的过程中，我们小组相比于其他小组需要进行每周一次的例行会议，由于这一点我们没法很自由地支配这段时间（外出、回家、实习），但我觉得这是老师负责的表现，所以对这一点保持了客观的看待。此外，毕业设计末期我还是偷偷溜出去实习了（毕竟一周除了一天需要开会，其余四天我还是可以自由支配的嘛）。</p><p>毕业设计的内容，是我研究生导师给出的，这一点在保研的时候就有所耳闻了。导师给出的题目是——多层网络性能展示系统，初来乍到的我以为这个题目需要我平地起高楼，自己去调研，思考，设计。在几周后，我才意识到这个题目来自导师外包清华的一个项目，而我的毕业设计需要和他们的项目同步推进，我参与这个项目的同时也就是在做我的毕设。因为这个项目同时有研一研二研三的学长在参与，所以作为我的毕业设计，我可以说在无形中省了很多力气，但是作为本科阶段的最后一个项目，我暗暗地决定一定要把它做好。</p><blockquote><p>这个项目是一个侧重前端的可视化项目，作为一个一致抵触前端的开发者，我不得不去学习我从未接触过并且不喜欢的东西。</p><p>打心里我其实是抵触的，但是奈何拗不过老师的安排。</p><p>只好端正自己的心态，告诉自己技术没有高下之分，一个好的程序员没必要受语言的约束。</p></blockquote><p>开发中，研三学长可以说是独自Carry，我们几个前期写的代码可以说就是和屎一样，学长需要对我们写的半成品进行重构和修改，然后再整合到整个项目中，但是学长的水平是完全让我折服的，让我以为作为IT黄埔的北邮的学生们的技术都是这个水平的，不由得放低了自己的姿态，也在时刻担心开学之后会不会因为出身学校不好，技术水平也不够高而拖了团队的后退。虽然开学之后，我发现周围人和我的差距并没有那么大，同门中确实是我出身的学校最差，但并不是每个人的技术都在我之上，反之我很自信我的水平可以在他们中做到前列。</p><h3 id="插曲">插曲</h3><p>为了很好的完成毕业设计的任务，也出于认真的态度，我决定好好学习前端，所以在1-2月的假期中我的大部分时间都是在学习前端三件套和<code>Vue.js</code>框架，期间为了带着奇迹搞点东西，我们俩自己还折腾了一个小博客网站，虽然中道放弃，但好在，在年末豪哥决定接收这个项目🐶</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/%E7%BD%91%E9%A1%B5%E6%8D%95%E8%8E%B7_1-3-2021_15109_localhost.jpeg" alt=""></p><h3 id="正题">正题</h3><p>开学之后，我全力投入到毕设的开发中，学长们确定的设计方案，在我看来是挺丑的，所以我决定在他们项目的基础上魔改一个自己版本的前端界面，改用其他的设计和配色，这样可以在自己的版本上花更多的经历和心血尽力做的好一些，他们那边的项目草草了事就可以了，这样做还可以避免去考虑莫名其妙的需求在毕业设计的答辩阶段的影响。</p><blockquote><p>这个项目属于那种，设计阶段的产物。</p></blockquote><p>恕我直言，这个项目简直就是无意义的。很多概念、很多需求根本无法用已经落地的项目去进行描述，导致在一系列的答辩过程中，不单单是答辩时进行询问的老师，包括是我自己都一头雾水，答辩时，我只能用尽可能高大上的概念去对项目进行包装，用这是实验室和清华合作的项目来对老师们进行迷惑。好在最后毕设顺利结束，虽然没有拿到优秀毕设的称号，但这是因为我觉得对于我来说，这个称号没有什么实际的意义，所以也就没有去争取。</p><p>这个毕设的前端经过我的魔改，最后的效果看起来还是足够花里胡哨的，让我自己觉得还不错🐶，哈哈哈哈，这里放下前端的截图，炫耀一下吧，算是自己完成（不是）的第一个前端项目。我把项目的源码放在了<a href="https://github.com/Hanmengnan/multi-layer-network-display-system">Github</a>上，如果你也觉得还行的话可以给我点个Start吗？</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/%E7%BD%91%E9%A1%B5%E6%8D%95%E8%8E%B7_15-2-2021_101141_localhost.jpeg" alt=""></p><p><img src="https://i.loli.net/2021/04/10/RMfJ6WlHACjIYkc.jpg" alt="毕设2"></p><p><img src="https://i.loli.net/2021/04/10/2DQafpz3OZlRSLu.jpg" alt="毕设3"></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20210317102222083.png" alt=""></p><h2 id="变身打工人的日子">变身打工人的日子</h2><p>在毕设的尾声阶段，为未来感到焦虑的我，迫切的希望有一段实习经历可以为自己的简历增光。所以，开始盲目的投递简历，至于为什么说自己当时的行为很盲目，因为当时我处在一个非常迷茫的阶段，假期期间我花了很多精力自学前端相关的知识，当时我一直以来都没把前端作为自己工作会从事的岗位，但与之相对的，我一直想从事的后端岗位的知识我却很久都未温习，后端领域使用最多的语言Java和C++对我来说都是完全陌生的，所以我陷入了一个两难的境地，更简单、通过面试机会更大的前端，还是自己一直想从事的后端。加之当时我并没有对面试做充分的准备，所以知识点处于一十分生疏的状态，也没有刷题。</p><p>当时我一直都很纠结，最后还是觉得通过前端面试的机会更大，所以向很多前端岗位投递了简历，但作为一个半路出家的前端，在面试时被老前端一样戳穿。面试结束时他淡淡地问我：你做了多久前端？看得出你的代码风格还是后端的代码风格，如果以后想从事前端的话，还是不要学的太杂比较好。</p><p>他的这番话，让我意识到虽然自己突击了一个假期的前端，当时实际上离真正入门前端还是有很大的差距，有一些习惯素养不是一朝一夕可以学会的，既然是这样前后端对我来说就没有熟练程度之分了，我心里还是更喜欢后端的，于是不再投递前端的岗位。</p><blockquote><p>感谢这位前辈对我的建议。</p></blockquote><p>后面我阴差阳错地通过了网易测开的面试，和我好兄弟女朋友的——星辰做起了同事。在网易的生活让我第一次意识到了打工人生活的不易。那段日子我就不展开细讲了。我记录在了另一篇日志中——<a href="https://siegelion.cn/2021/07/30/%E6%89%93%E5%B7%A5%E4%BA%BA%E5%9C%A8%E7%BD%91%E6%98%93/">打工人在网易</a>。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/%E7%BD%91%E6%98%93%E6%9C%80%E5%90%8E%E4%B8%80%E5%A4%A9.jpg" alt=""></p><h2 id="万里路的又一小步">万里路的又一小步</h2><h3 id="川渝之行">川渝之行</h3><p>跟鞠老板早就约好的毕业旅行，我们选择了去四川，同行的还有鞠老板的高中同学、在南京为我们担任向导的——张珂萌同学，我们也算是基本上熟络了，所以前期我们三个人旅行在重庆的旅行还算顺利，除了最后一天的武隆之行，我们极限操作勉强赶上了最后一班从重庆开往成都的高铁，在慌张中顺利抵达了天府之国成都。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/%E6%B4%AA%E5%B4%96%E6%B4%9E-2.jpg" alt=""></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/%E9%87%8D%E5%BA%86-2jpg.jpg" alt=""></p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/%E6%AD%A6%E9%9A%86-2.jpg" alt=""></p><p>在这里我们又有了一位新的向导——天翔，在他的带领下。我们的成都之行基本无需考虑行程安排的问题，就算是因为暴雨的原因，我们的进山安排被迫取消，天翔也为我们安排出了钓鱼烧烤的计划（人生中第一次去钓鱼，然后被迫杀鱼😭），虽然最后没有进山看到云海和日出，有些小遗憾，但这趟成都之行过的还算是顺利和开心，在本地人带领下，见到了吃到了很多网上攻略上没有的东西。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/%E6%88%90%E9%83%BD%E5%A4%A7%E5%AE%B6-2.jpg" alt=""></p><h3 id="三亚之旅">三亚之旅</h3><p>关于这段回忆我记录在了另一篇日志中——<a href="https://siegelion.cn/2021/07/30/%E6%AF%95%E4%B8%9A%E6%97%85%E8%A1%8C%E4%B9%8B%E4%B8%89%E4%BA%9A%E4%B9%8B%E8%A1%8C/">毕业旅行之三亚之行</a>，这里也不展开细说了。</p><h2 id="作为研究生的生活">作为研究生的生活</h2><p>9月意味着本科的一切都已经留在了过去，我需要以一个全新的身份来到一个全新的环境开启一段全新的生活，我不是一个害怕社交的人，但当一个人来到一个全新的环境还是会多少有点无所适从。</p><h3 id="信网中心的工作">信网中心的工作</h3><p>很幸运地成为了黄老师的学生，黄老师由于前几年评上了正教授也成功当上了实验室的主任，这几年处于一种非常佛系的状态，对学生们也十分宽容，没有出现Push我们的情况，相比于同组的沛子的学生🐶，瞬间感觉自己的研究生生活真的还不错。</p><blockquote><p>让我觉得遗憾的，和我同样来自NCUT的世烨由于受不住老师的PUA，已经毅然决然地离开退学了校园，从此这个学校又少了一个朋友。</p></blockquote><p>成为研究生的前一个月，由于实验室的工位紧缺，老师并没有要求我们研一的同学进入实验室，所以那一个月我基本处于放羊的状态，每天睡到自然醒，做一做自己喜欢的事。但国庆节之后，研一的同学被要求以后每天来实验室工（zuo）作（lao），而且每天需要进行打卡，提前化身打工人，好在我负责实验室的打卡，所以偶然也可以凭着心情偷懒一下。</p><p>关于工作嘛，我有很幸运地被黄老师安排到自己负责的项目组，负责BGP相关项目的开发，其中我的工作主要是WEB后端的开发和缓释系统的开发。这学期我的大部分精力都花费在了WEB系统上，我重构了一下原有的后端代码，修改了部分功能的实现细节，但由于种种原因重构后的代码并没有正式部署。黄老师在年底的总结会上对我们进度表示了部分的不满，希望我们可以多花些精力在创新上而不是仅仅做开发一个系统这样的工作，其实他不说我自己也意识到了这个问题，开发一个系统对于一个研究生来说实在算不上是一个很值得炫耀的事，“读论文-&gt;创新-&gt;写论文”才是正途，希望明年可以尽快结束这些开发工作，开始读些论文吧，虽然我并没有发论文这种硬性要求的压力，但是还是希望自己研究生阶段可以深入地学习一些知识吧。</p><h3 id="课外知识的涉猎">课外知识的涉猎</h3><h4 id="Talent-Plan">Talent Plan</h4><p>作为一个Go语言的使用者和分布式技术的爱好者，我很早的时候就听说过PingCAP这个公司的名字，作为国内走纯技术路线的公司，虽然我不一定会在毕业的时候加入他们，但是打心底里我还是对其十分憧憬的，向往其内部的技术氛围，期待他们可以做出优秀的产品。偶然间我从学校的Go语言开发者的群内听说了PingCAP组织的一个活动——<a href="https://tidb.io/talent-plan">Talent Plan</a>开始报名的消息。抱着试一试的态度，我报名了活动，这个活动的性质半是比赛半是课程，要求大家在两个月的时间内，完成TinyKV这个项目的四个Project，并通过源码中附带的全部测试用例。</p><blockquote><p>该项目已经被国内很多顶尖高校的数据库实验室加入到教学计划中。</p><p>比如我当时一直想去，当时最后遗憾错过的华东师大数据学院，唉…</p></blockquote><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20220109111232912.png" alt=""></p><p>报名的时候主办方明确要求大家两人组队参赛，但我一开始觉得自己可能实力不是很强，不愿拖队友的后腿，所以打算一个人参赛，但是后来在主办方的反复强调下，意识到一个人是不可能在两个月的时间内完成全部的任务的，所以在群内找到了我的南大队友——<a href="https://github.com/Undxxx">李雨</a>同学组队，由于是第一次组队两个人还不算太熟，加之Project之间的关系很紧密，不完成前面的任务，后面的任务就无从做起，所以我们两个也没法同时推进两个Project，我们还是一直在单打独斗，自己做自己的，虽然说每周都有开会，但是也只是汇报一下自己的进度，甚至都没有起到1+1=2的效果，最后也很遗憾的没有完成全部的任务。</p><p>不过我确实觉得这个项目确实是一个难得的好项目，虽然项目的主要思路基本上仿照了PingCAP的TiDB的设计思路，初衷也是是为了让参与者了解PingCAP的技术，但不得不说在参与项目的过程中，我对Raft算法的了解更上了好多层楼。虽然这次两个月的时间内，我们没有完成TinyKV的全部任务，不过我已经决定后面一定要抽出时间将所有的Project完成，真正吃透这个项目。</p><h4 id="MIT-6-824">MIT 6.824</h4><p>这是在Talent Plan开始之前的那段时间里，我犹豫时间比较充裕，开始学习的一门课程，但最后由于琐事缠身，并没有完成，只做了实验一的内容，实验的具体细节我记录在了另一篇日志中——<a href="https://siegelion.cn/2021/09/24/MIT%206.824-Lab%201/">MIT 6.824-Lab1</a>。</p><h3 id="其他">其他</h3><p>其他一些零散的时间，其实我自己也不知道算不算荒废掉了。开学之前的假期读了两本技术书籍《第一本Docker书》和《Git Pro》都是很值得推荐的书籍，另外两本的文学著作并没有看完，《一地鸡毛》看了个大概，《京华烟云》只开了个头。</p><p>虽然书没看下去多少，这学期的历史倒是听了很多的播客，从清末曾国藩传、大明刘伯温传、东汉三国、西汉光武中兴、黑白隋炀帝到瑰丽盛唐，前前后后断断续续，没有按照顺序听了很多历史和野史，一是打发时间二是提高素养吧。</p><p>除了文明精神，还野蛮了一下体魄，这学期学会了滑雪和游泳，健身一直也没有中断，一共去了45次健身房，算上二十次左右的游泳，基本上每天我都有运动，希望自己可以保持这种好习惯吧。</p><h2 id="做个总结吧">做个总结吧</h2><ul><li>觉得做的还不错的<ul><li><p>遇到了静静，相处起来让我很舒服的一个姑娘，希望可以有情人终成眷属吧。不过感情的事也不好说，希望一切顺利吧。</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/dcf691004ec2c98e4905ae8d7163081.jpg" alt=""></p></li><li><p>学会了两门运动：游泳和滑雪。并且一直有在坚持运动，跑步、健身、游泳。</p></li><li><p>更新了21篇博客，不过大部分都是假期时候学习JavaScript的内容，再不就是日记一类的。</p></li><li><p>听了很多史，虽然半是野史半是正史（但野史正史谁又分得清呢🙅）。</p></li><li><p>假期期间又去了三个城市旅行。</p></li><li><p>研究生阶段又认识一群好朋友。</p></li></ul></li><li>觉得有待加强<ul><li>读了两本技术书，读的书有点少，尤其是开学之后。</li><li>这学期实验室的项目主要还是停留在开发系统上，论文看的比较少，对研究生生活的规划也不是特别清晰。</li><li>同学们有的已经开始每天刷题了，但我迟迟还没有准备。</li></ul></li><li>觉得有些遗憾<ul><li>Talent Plan 任务后期，精力有限，没有完成全部的任务。</li><li>MIT 6.824 也只做了一个实验。</li><li>WEB系统重构后一直没有部署。</li><li>k8s等云原生的知识，一直有学的计划，结果一年结束了还是没有入门。</li><li>身体隐隐有些不舒服，可能还是平时习惯上的问题吧。</li></ul></li></ul><h1>关于明年</h1><ul><li>工作<ul><li>明年争取多读一些论文，研二开题时争取有一个大致的思路，至少不要在开题时被怼的太惨吧。</li><li>WEB系统一定要完成部署，并替代现在的老系统。</li><li>专利的事情如果可以的话，提上日程。（不过感觉可能只是想想而已）</li></ul></li><li>技术<ul><li>争取对云原生、容器的知识有个粗浅的认知吧，最好入个门。</li><li>Talent Plan和MIT 6.824争取完成其中的一个。</li><li>Golang可以深入一下，不再一直是入门的状态。</li><li>如果可以的话，可以独立或者组队完成一个技术上有难度些的项目。</li></ul></li><li>生活<ul><li>读一两本文学书籍。（这个去年根本没完成）</li><li>争取假期可以安排出一趟旅行。</li><li>坚持运动、坚持健身。</li><li>坚持写博客。</li><li>和朋友们保持联络。</li></ul></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;2021年就要过去了，我不想怀念他。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/%E5%BE%AE%E4%BF%A1</summary>
      
    
    
    
    <category term="日记" scheme="https://siegelion.cn/categories/%E6%97%A5%E8%AE%B0/"/>
    
    
    <category term="日记" scheme="https://siegelion.cn/tags/%E6%97%A5%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>SSH 连接 Hyper-v 中创建的虚拟机</title>
    <link href="https://siegelion.cn/2021/12/28/SSH%E8%BF%9E%E6%8E%A5Hyper-v%E4%B8%AD%E5%88%9B%E5%BB%BA%E7%9A%84%E8%99%9A%E6%8B%9F%E6%9C%BA/"/>
    <id>https://siegelion.cn/2021/12/28/SSH%E8%BF%9E%E6%8E%A5Hyper-v%E4%B8%AD%E5%88%9B%E5%BB%BA%E7%9A%84%E8%99%9A%E6%8B%9F%E6%9C%BA/</id>
    <published>2021-12-28T17:15:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<ol><li>首先新建一个新的<strong>虚拟网络交换机</strong>，交换机的类型选择内部，因为这个交换机我们只是用来进行SSH连接的。</li></ol><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20211228164807403.png" alt="image-20211228164807403"></p><ol start="2"><li>新建后，可以在控制面板的网络连接面板上，看到你新建出的虚拟网络交换机（实际上是建立了一块虚拟网卡）。</li></ol><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20211228165255558.png" alt="image-20211228165255558"></p><ol start="3"><li><p>对虚拟网卡的属性进行配置</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20211228165540740.png" alt="image-20211228165540740"></p><blockquote><p><strong>IP地址</strong>和<strong>子网掩码</strong>这里可以参照图中进行配置，也可以自行修改（前提是你确认自己修改的没问题）。</p></blockquote><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20211228165610597.png" alt="image-20211228165610597"></p><ol start="4"><li><p>将虚拟机上新建的虚拟网卡进行配置</p><ul><li><p>进入相应目录</p>  <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">cd  /etc/sysconfig/network-scripts/<br></code></pre></td></tr></table></figure></li><li><p>将原有网卡的配置复制一份命名为<strong>ifcfg-eth1</strong></p>  <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">cp ./ifcfg-eth0 ./ifcfg-eth1<br></code></pre></td></tr></table></figure></li><li><p>修改网卡1的配置文件</p>  <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">vim ./ifcfg-eth1 <br></code></pre></td></tr></table></figure><p>注意几个要点：</p><ul><li><code>BOOTPROTO</code>改为<strong>static</strong></li><li><code>NAME</code>和<code>DEVICE</code>改为<code>eth1</code></li><li><code>UUID</code>要进行注释掉，目的是不让其和网卡0的<code>UUID</code>相同</li><li><code>IPADDR</code>,<code>NETWMASK</code>,<code>GATEWAY</code>根据在控制面板中的设置进行配置</li></ul></li></ul><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20211228171305905.png" alt="image-20211228171305905"></p><ul><li>保存退出</li></ul></li></ol></li><li><p>重启机器</p></li><li><p>在windows上进行SSH连接测试</p><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20211228172041126.png" alt="image-20211228172041126"></p></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;ol&gt;
&lt;li&gt;首先新建一个新的&lt;strong&gt;虚拟网络交换机&lt;/strong&gt;，交换机的类型选择内部，因为这个交换机我们只是用来进行SSH连接的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://siegelion-blog.oss-cn-beijing.al</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="瞎折腾" scheme="https://siegelion.cn/tags/%E7%9E%8E%E6%8A%98%E8%85%BE/"/>
    
    <category term="网络" scheme="https://siegelion.cn/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>MIT 6.824-Lab1</title>
    <link href="https://siegelion.cn/2021/09/24/MIT%206.824-Lab%201/"/>
    <id>https://siegelion.cn/2021/09/24/MIT%206.824-Lab%201/</id>
    <published>2021-09-24T09:15:00.000Z</published>
    <updated>2024-06-07T12:50:45.746Z</updated>
    
    <content type="html"><![CDATA[<h1>前言</h1><p>早在保研时，对分布式感兴趣的我想要涉猎一些这个领域的知识，于是在网络上搜寻相关的学习资料，就发现了很多前辈都推荐学习者阅读一下<strong>谷歌的三大论文</strong>——<strong>MapReduce</strong>、<strong>GFS</strong>、<strong>BigTable</strong>，此外还推荐了一个课程——<a href="http://nil.csail.mit.edu/6.824/2020/schedule.html">MIT 6.824</a>，这是一个关于分布式系统的课程，授课的教师是大名鼎鼎的蠕虫病毒的发明者——<a href="https://zh.wikipedia.org/wiki/%E7%BD%97%E4%BC%AF%E7%89%B9%C2%B7%E6%B3%B0%E6%BD%98%C2%B7%E8%8E%AB%E9%87%8C%E6%96%AF">Robert Morris</a>，听这样的大牛讲课本身就是一种享受，并且在听了两节课之后，觉得老师讲的确实透彻且深刻，并且在如今2020年，老师上课时大部分的讲解都是使用板书完成的，这点显得尤为难得，以上这些让我这听惯了国内PPT课堂的学生耳目一新。</p><p><img src="https://i.loli.net/2021/09/23/SaHTu8Y9Be3Eg2t.png" alt=""></p><h1>实验</h1><p>在听了两节课之后，我开始动手进行本课的第一个实验——实现一个简单的<strong>MapReduce</strong>系统。由于我事先阅读过MapReduce论文，加之第一个实验较为简单，因此我并没有倒在<strong>lab1</strong>面前，但前前后后还是花了中秋节的三天时间才搞定这个实验，并通过了全部8个测试点。</p><p>简单描述一下该实验的要求：</p><ul><li>实验中要求实现的系统包含两部分：<code>worker</code>和<code>coordinator</code>，<code>worker</code>负责完成任务，<code>coordinator</code>负责将任务分配给<code>worker</code>。会存在多个<code>worker</code>同时工作来模拟并行，但<code>coordinator</code>只存在一个。通常来说分布式系统中的每个<code>worker</code>应该存在于多台主机之上，并通过网络<code>RPC</code>进行通信，但由于是实验，因此没有多机进行模拟，所以将<code>worker</code>们都运行在一台主机上，依然通过<code>RPC</code>通信，但使用的是<code>UNIX SOCK</code>的方式。</li><li>系统的输入是一系列文件，仿照<code>Map-Reduce</code>论文中的思路，进行单词统计等一系列操作。实验需要通过的功能点主要有以下8个测试点：<ul><li>word count（单词计数）</li><li>indexer（单词来自文件统计）</li><li>map parallelism （map 任务并行测试）</li><li>reduce parallelism（reduce 任务并行测试）</li><li>job count （任务计数）</li><li>early exit （是否有<code>worker</code>在全部任务完成前退出）</li><li>crash （<code>worker</code>崩溃后任务可以正常完成）</li></ul></li><li><a href="https://pdos.csail.mit.edu/6.824/labs/lab-mr.html">详细要求</a>见网页。</li></ul><h2 id="文件目录">文件目录</h2><ul><li><p><code>main</code>：主文件夹</p><ul><li><p><code>mrcoordinator.go</code>：coordinator 的启动文件</p></li><li><p><code>mrworker.go</code>：worker的启动文件</p></li><li><p><code>mrsequential.go</code>：实验串行模拟版本</p><blockquote><p>注：以上文件需要在不同的终端启动</p></blockquote></li><li><p><code>test-mr.sh</code>：测试实验是否通过的<code>shell</code>脚本</p></li></ul></li><li><p><code>mr</code>：worker 和 coordinator 具体实现的文件夹</p><ul><li><code>coordinator.go</code></li><li><code>worker.go</code></li><li><code>rpc.go</code>：worker 和 coordinator 通信的实现</li></ul></li><li><p><code>mrapps</code>：每对Map-Reduce操作的具体实现文件夹</p><ul><li>…</li></ul></li></ul><h2 id="尝试">尝试</h2><h3 id="version-1">version 1</h3><p>一开始我抱着有些畏惧的心态去完成这个实验，目标是实现<strong>单词计数</strong>的功能，由于整个系统的输入是一系列文件，所以我简单地使<code>worker</code>向<code>coordinator</code>获取文件的列表，然后为每一文件单独开启一个协程用于<code>map</code>任务，每个协程将其结果写入中间文件，由于系统规定了<code>reduce</code>任务的数量，因此在<code>map</code>任务结束后，启用规定数量的协程去执行<code>reduce</code>任务即可。</p><p>使用这个方式，通过了单词计数的测试没有问题，但是在<code>indexer</code>的测试却行不通了，由于系统是通过<code>test-mr.sh</code>进行测试的，因此我开始浏览<code>shell</code>脚本的内容。发现了在测试脚本中，会启动多个<code>worker</code>来完成<code>map</code>任务，也就是说是多个<code>worker</code>共同完成任务，这与我一开始认为的一个<code>worker</code>通过开启多个协程完成任务的想法相悖，所以代码需要进行修改。</p><p>(1个<code>worker</code>同时启动<code>N</code>个任务)</p><h3 id="version-2">version 2</h3><p>从上文，我明白了应该是多个进程共同完成任务。因此将实现思路改为，每个<code>worker</code>通过<code>RPC</code>向<code>coordinator</code>获取任务（文件），获取到后开启协程去完成每个任务。 通过这个改动，我通过了<code>indexer</code>测试。但是在<code>parallelism</code>测试中却<code>Fail</code>了。这意味着程序的实现还是错误的。</p><p>(<code>m</code>个<code>worker</code>同时启动<code>N/m</code>个任务)</p><h3 id="version-3">version 3</h3><p>接着我分析了<code>parallelism</code>测试中的测试原则，测试原则为<code>worker</code>在<code>map</code>阶段通过创建一个文件名为<code>mr-worker</code>+<code>PID</code>的文件，在<code>map</code>结束后删除该文件，通过统计存在几个特定文件名的数量，来判断同时运行的<code>worker</code>数量。由于在<code>version 2</code>版本中，一个<code>worker</code>启动了多个协程来执行<code>map</code>操作，每个协程在执行时会同时操作一个文件，所以会出现重复删除一个文件的情况，导致程序崩溃。</p><p>因此，需要将一个<code>worker</code>启动多个协程的方式改掉，改为每个<code>worker</code>每次只执行一个<code>map</code>任务。这次可以通过测试了。</p><h3 id="version-4">version 4</h3><p>最后难住我的测试点是崩溃测试，在<code>worker</code>崩溃后系统能否依然正确执行，得出正确的结果，一个完备的系统设计无疑是应该具备容错的能力的。</p><p><code>worker</code>执行前会向<code>coordinator</code>获取相应的任务，获取任务后，若在执行过程中发生崩溃，那意味着该任务没有被成功执行，需要<code>coordinator</code>检测出失败的任务，然后将任务重新分配给<code>worker</code>。</p><p>这里我采用了在<code>coordinator</code>中引入协程同时记录任务执行状态的记录的方式，在<code>coordinator</code>将任务分配给<code>worker</code>后，启动协程进行倒计时，若在规定的时间内任务并没有执行完成，那么即认为任务失败，<code>coordinator</code>会重新分配任务。</p><h2 id="逻辑">逻辑</h2><h3 id="worker">worker</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">package</span> mr<br><br><span class="hljs-keyword">import</span> (<br><span class="hljs-string">&quot;encoding/json&quot;</span><br><span class="hljs-string">&quot;fmt&quot;</span><br><span class="hljs-string">&quot;hash/fnv&quot;</span><br><span class="hljs-string">&quot;io/ioutil&quot;</span><br><span class="hljs-string">&quot;log&quot;</span><br><span class="hljs-string">&quot;net/rpc&quot;</span><br><span class="hljs-string">&quot;os&quot;</span><br><span class="hljs-string">&quot;sort&quot;</span><br><span class="hljs-string">&quot;time&quot;</span><br>)<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// Map functions return a slice of KeyValue.</span><br><span class="hljs-comment">//</span><br><span class="hljs-keyword">type</span> KeyValue <span class="hljs-keyword">struct</span> &#123;<br>Key   <span class="hljs-keyword">string</span><br>Value <span class="hljs-keyword">string</span><br>&#125;<br><br><span class="hljs-keyword">type</span> ByKey []KeyValue<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(a ByKey)</span> <span class="hljs-title">Len</span><span class="hljs-params">()</span> <span class="hljs-title">int</span></span>           &#123; <span class="hljs-keyword">return</span> <span class="hljs-built_in">len</span>(a) &#125;<br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(a ByKey)</span> <span class="hljs-title">Swap</span><span class="hljs-params">(i, j <span class="hljs-keyword">int</span>)</span></span>      &#123; a[i], a[j] = a[j], a[i] &#125;<br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(a ByKey)</span> <span class="hljs-title">Less</span><span class="hljs-params">(i, j <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">bool</span></span> &#123; <span class="hljs-keyword">return</span> a[i].Key &lt; a[j].Key &#125;<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// use ihash(key) % NReduce to choose the reduce</span><br><span class="hljs-comment">// task number for each KeyValue emitted by Map.</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">ihash</span><span class="hljs-params">(key <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">int</span></span> &#123;<br>h := fnv.New32a()<br>h.Write([]<span class="hljs-keyword">byte</span>(key))<br><span class="hljs-keyword">return</span> <span class="hljs-keyword">int</span>(h.Sum32() &amp; <span class="hljs-number">0x7fffffff</span>)<br>&#125;<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// main/mrworker.go calls this function.</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Worker</span><span class="hljs-params">(mapf <span class="hljs-keyword">func</span>(<span class="hljs-keyword">string</span>, <span class="hljs-keyword">string</span>)</span> []<span class="hljs-title">KeyValue</span>,</span><br>reducef <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(<span class="hljs-keyword">string</span>, []<span class="hljs-keyword">string</span>)</span> <span class="hljs-title">string</span>)</span> &#123;<br><br><span class="hljs-comment">// map 阶段</span><br>mapJobNum := getMapNum()<br><span class="hljs-comment">// map任务数，同时也是文件数</span><br><span class="hljs-keyword">for</span> &#123;<br>mapFile, mapJobID := getMapJob()<br><span class="hljs-comment">// map任务所对应的文件名以及任务ID</span><br><span class="hljs-keyword">if</span> mapFile != <span class="hljs-string">&quot;&quot;</span> &#123;<br><span class="hljs-comment">// 代表还有任务待分配</span><br>file, err := os.Open(mapFile)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br>log.Fatalf(<span class="hljs-string">&quot;cannot open %v&quot;</span>, mapFile)<br>&#125;<br>content, err := ioutil.ReadAll(file)<br><span class="hljs-comment">// 读取文件内容</span><br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br>log.Fatalf(<span class="hljs-string">&quot;cannot read %v&quot;</span>, mapFile)<br>&#125;<br>file.Close()<br>kva := mapf(mapFile, <span class="hljs-keyword">string</span>(content))<br><span class="hljs-comment">// map操作</span><br>shuffle(mapJobID, kva)<br><span class="hljs-comment">// 将map任务的结果写入临时文件</span><br>getMapJobDone(<span class="hljs-string">&quot;single&quot;</span>, mapFile)<br><span class="hljs-comment">// 通知调度器一个map任务已完成</span><br><br>&#125; <span class="hljs-keyword">else</span> &#123;<br><span class="hljs-keyword">if</span> getMapJobDone(<span class="hljs-string">&quot;all&quot;</span>, <span class="hljs-string">&quot;&quot;</span>) &#123;<br><span class="hljs-comment">//判断是否全部map任务已结束</span><br><span class="hljs-keyword">break</span><br>&#125;<br>time.Sleep(time.Second)<br>&#125;<br>&#125;<br><br><span class="hljs-comment">// reduce 阶段</span><br><span class="hljs-keyword">for</span> &#123;<br>reduceJobID := getReduceJob()<br><span class="hljs-comment">// 获取reduce任务ID</span><br><span class="hljs-keyword">if</span> reduceJobID != <span class="hljs-number">-1</span> &#123;<br>kva := <span class="hljs-built_in">make</span>([]KeyValue, <span class="hljs-number">0</span>)<br><span class="hljs-keyword">for</span> j := <span class="hljs-number">0</span>; j &lt; mapJobNum; j++ &#123;<br>fileName := fmt.Sprintf(<span class="hljs-string">&quot;mr-%d-%d&quot;</span>, j, reduceJobID)<br><span class="hljs-comment">// 该reduce任务对应的中间文件名</span><br>file, err := os.Open(fileName)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br>log.Fatalf(<span class="hljs-string">&quot;cannot open %v&quot;</span>, fileName)<br>&#125;<br>dec := json.NewDecoder(file)<br><span class="hljs-comment">// 读取文件内容</span><br><span class="hljs-keyword">for</span> &#123;<br><span class="hljs-keyword">var</span> kv KeyValue<br><span class="hljs-keyword">if</span> err := dec.Decode(&amp;kv); err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-keyword">break</span><br>&#125;<br>kva = <span class="hljs-built_in">append</span>(kva, kv)<br><span class="hljs-comment">// 保存结果</span><br>&#125;<br>&#125;<br>storeReduceRes(reduceJobID, kva, reducef)<br><span class="hljs-comment">// reduce 操作</span><br>getReduceJobDone(reduceJobID)<br><span class="hljs-comment">// 通知调度器一个reduce任务已完成</span><br>&#125; <span class="hljs-keyword">else</span> &#123;<br><span class="hljs-keyword">if</span> getMROver() &#123;<br><span class="hljs-comment">// 判断是否全部任务已完成</span><br><span class="hljs-keyword">break</span><br>&#125;<br>&#125;<br>&#125;<br>&#125;<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">//获取map任务数量</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">getMapNum</span><span class="hljs-params">()</span> <span class="hljs-title">int</span></span> &#123;<br>args := MapNumArgs&#123;&#125;<br>reply := MapNumReply&#123;&#125;<br>call(<span class="hljs-string">&quot;Coordinator.MapJobNum&quot;</span>, &amp;args, &amp;reply)<br><span class="hljs-keyword">return</span> reply.Num<br><br>&#125;<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// 获取新的map任务</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">getMapJob</span><span class="hljs-params">()</span> <span class="hljs-params">(<span class="hljs-keyword">string</span>, <span class="hljs-keyword">int</span>)</span></span> &#123;<br>args := MapJobArgs&#123;&#125;<br>reply := MapJobReply&#123;&#125;<br>call(<span class="hljs-string">&quot;Coordinator.MapJob&quot;</span>, &amp;args, &amp;reply)<br><span class="hljs-keyword">return</span> reply.File, reply.MapJobID<br>&#125;<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// 通知 coordinator map任务已经完成</span><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// content:</span><br><span class="hljs-comment">// `single`: 通知单个任务已经完成 \ `all`: 询问全部任务是否已经完成</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">getMapJobDone</span><span class="hljs-params">(content, fileName <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">bool</span></span> &#123;<br><br>args := MapJobDoneArgs&#123;Content: content, FileName: fileName&#125;<br>reply := MapJobDoneReply&#123;&#125;<br>call(<span class="hljs-string">&quot;Coordinator.MapJobDone&quot;</span>, &amp;args, &amp;reply)<br><span class="hljs-keyword">return</span> reply.Done<br>&#125;<br><br><span class="hljs-comment">// </span><br><span class="hljs-comment">// 将map的结果保存至中间文件</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">shuffle</span><span class="hljs-params">(mapJobID <span class="hljs-keyword">int</span>, intermediate []KeyValue)</span></span> &#123;<br>interFiles := <span class="hljs-built_in">make</span>([]*os.File, <span class="hljs-number">10</span>)<br><span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++ &#123;<br>interFileName := fmt.Sprintf(<span class="hljs-string">&quot;mr-%d-%d&quot;</span>, mapJobID, i)<br>interFiles[i], _ = os.Create(interFileName)<br><span class="hljs-keyword">defer</span> interFiles[i].Close()<br>&#125;<br><span class="hljs-keyword">for</span> _, kv := <span class="hljs-keyword">range</span> intermediate &#123;<br>reduceJobID := ihash(kv.Key) % <span class="hljs-number">10</span><br><span class="hljs-comment">// 确定对应的reduce任务</span><br>enc := json.NewEncoder(interFiles[reduceJobID])<br>enc.Encode(&amp;kv)<br>&#125;<br>&#125;<br><br><span class="hljs-comment">// </span><br><span class="hljs-comment">// 获取reduce</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">getReduceJob</span><span class="hljs-params">()</span> <span class="hljs-title">int</span></span> &#123;<br>args := ReduceArgs&#123;&#125;<br>reply := ReduceReply&#123;&#125;<br>call(<span class="hljs-string">&quot;Coordinator.ReduceJob&quot;</span>, &amp;args, &amp;reply)<br><span class="hljs-keyword">return</span> reply.ReduceJobID<br>&#125;<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// 将reduce任务执行结果保存在最终的文件中</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">storeReduceRes</span><span class="hljs-params">(reduceJobID <span class="hljs-keyword">int</span>, intermediate []KeyValue, reducef <span class="hljs-keyword">func</span>(<span class="hljs-keyword">string</span>, []<span class="hljs-keyword">string</span>)</span> <span class="hljs-title">string</span>)</span> &#123;<br>fileName := fmt.Sprintf(<span class="hljs-string">&quot;mr-out-%d&quot;</span>, reduceJobID)<br>ofile, _ := os.Create(fileName)<br><span class="hljs-keyword">defer</span> ofile.Close()<br>sort.Sort(ByKey(intermediate))<br>i := <span class="hljs-number">0</span><br><span class="hljs-keyword">for</span> i &lt; <span class="hljs-built_in">len</span>(intermediate) &#123;<br>j := i + <span class="hljs-number">1</span><br><span class="hljs-keyword">for</span> j &lt; <span class="hljs-built_in">len</span>(intermediate) &amp;&amp; intermediate[j].Key == intermediate[i].Key &#123;<br>j++<br>&#125;<br>values := []<span class="hljs-keyword">string</span>&#123;&#125;<br><span class="hljs-keyword">for</span> k := i; k &lt; j; k++ &#123;<br>values = <span class="hljs-built_in">append</span>(values, intermediate[k].Value)<br>&#125;<br>output := reducef(intermediate[i].Key, values)<br><span class="hljs-comment">// reduce 操作</span><br><br>fmt.Fprintf(ofile, <span class="hljs-string">&quot;%v %v\n&quot;</span>, intermediate[i].Key, output)<br>i = j<br>&#125;<br>&#125;<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// 通知某个reduce任务完成</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">getReduceJobDone</span><span class="hljs-params">(jobID <span class="hljs-keyword">int</span>)</span></span> &#123;<br>args := ReduceJobDoneArgs&#123;JobID: jobID&#125;<br>reply := ReduceJobDoneReply&#123;&#125;<br>call(<span class="hljs-string">&quot;Coordinator.ReduceJobDone&quot;</span>, &amp;args, &amp;reply)<br>&#125;<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// 询问是否全部map-reduce任务完成</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">getMROver</span><span class="hljs-params">()</span> <span class="hljs-title">bool</span></span> &#123;<br>args := MROverArgs&#123;&#125;<br>reply := MROverReply&#123;&#125;<br>call(<span class="hljs-string">&quot;Coordinator.MROver&quot;</span>, &amp;args, &amp;reply)<br><span class="hljs-keyword">return</span> reply.Done<br>&#125;<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// send an RPC request to the coordinator, wait for the response.</span><br><span class="hljs-comment">// usually returns true.</span><br><span class="hljs-comment">// returns false if something goes wrong.</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">call</span><span class="hljs-params">(rpcname <span class="hljs-keyword">string</span>, args <span class="hljs-keyword">interface</span>&#123;&#125;, reply <span class="hljs-keyword">interface</span>&#123;&#125;)</span> <span class="hljs-title">bool</span></span> &#123;<br><span class="hljs-comment">// c, err := rpc.DialHTTP(&quot;tcp&quot;, &quot;127.0.0.1&quot;+&quot;:1234&quot;)</span><br>sockname := coordinatorSock()<br>c, err := rpc.DialHTTP(<span class="hljs-string">&quot;unix&quot;</span>, sockname)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br>log.Fatal(<span class="hljs-string">&quot;dialing:&quot;</span>, err)<br>&#125;<br><span class="hljs-keyword">defer</span> c.Close()<br><br>err = c.Call(rpcname, args, reply)<br><br><span class="hljs-keyword">return</span> err == <span class="hljs-literal">nil</span><br>&#125;<br><br></code></pre></td></tr></table></figure><h3 id="coordinator">coordinator</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">package</span> mr<br><br><span class="hljs-keyword">import</span> (<br><span class="hljs-string">&quot;log&quot;</span><br><span class="hljs-string">&quot;net&quot;</span><br><span class="hljs-string">&quot;net/http&quot;</span><br><span class="hljs-string">&quot;net/rpc&quot;</span><br><span class="hljs-string">&quot;os&quot;</span><br><span class="hljs-string">&quot;sync&quot;</span><br><span class="hljs-string">&quot;time&quot;</span><br>)<br><br><span class="hljs-keyword">type</span> Coordinator <span class="hljs-keyword">struct</span> &#123;<br>MapJobs            <span class="hljs-keyword">chan</span> <span class="hljs-keyword">string</span>     <span class="hljs-comment">// map任务管道</span><br>MapJobID           <span class="hljs-keyword">chan</span> <span class="hljs-keyword">int</span>        <span class="hljs-comment">// map任务id</span><br>MapJobCount        <span class="hljs-keyword">int</span>             <span class="hljs-comment">// map任务数</span><br>MapJobState        <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">bool</span> <span class="hljs-comment">// map任务状态`map`</span><br>MapJobStateLock    sync.Mutex      <span class="hljs-comment">// map任务状态锁</span><br>ReduceJobs         <span class="hljs-keyword">chan</span> <span class="hljs-keyword">int</span>        <span class="hljs-comment">// reduce任务管道</span><br>ReduceJobState     <span class="hljs-keyword">map</span>[<span class="hljs-keyword">int</span>]<span class="hljs-keyword">bool</span>    <span class="hljs-comment">// reduce任务状态map</span><br>ReduceJobStateLock sync.Mutex      <span class="hljs-comment">// reduce任务状态锁</span><br>Over               <span class="hljs-keyword">bool</span>            <span class="hljs-comment">// mr任务结束标志</span><br>OverLock           sync.Mutex      <span class="hljs-comment">// mr任务状态锁</span><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *Coordinator)</span> <span class="hljs-title">MapJobNum</span><span class="hljs-params">(args *MapNumArgs, reply *MapNumReply)</span> <span class="hljs-title">error</span></span> &#123;<br>reply.Num = c.MapJobCount<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *Coordinator)</span> <span class="hljs-title">MapJob</span><span class="hljs-params">(args *MapJobArgs, reply *MapJobReply)</span> <span class="hljs-title">error</span></span> &#123;<br><span class="hljs-keyword">select</span> &#123;<br><span class="hljs-keyword">case</span> reply.File = &lt;-c.MapJobs:<br>reply.MapJobID = &lt;-c.MapJobID<br><span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(jobFile <span class="hljs-keyword">string</span>, jobID <span class="hljs-keyword">int</span>)</span></span> &#123;<br><span class="hljs-comment">//开启一个协程等待10s判断任务是否完成</span><br>time.Sleep(time.Second * <span class="hljs-number">10</span>)<br>c.MapJobStateLock.Lock()<br><span class="hljs-keyword">if</span> !c.MapJobState[jobFile] &#123;<br>c.MapJobs &lt;- jobFile<br>c.MapJobID &lt;- jobID<br>&#125;<br>c.MapJobStateLock.Unlock()<br>&#125;(reply.File, reply.MapJobID)<br><span class="hljs-keyword">default</span>:<br>reply.MapJobID = <span class="hljs-number">0</span><br>reply.File = <span class="hljs-string">&quot;&quot;</span><br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *Coordinator)</span> <span class="hljs-title">MapJobDone</span><span class="hljs-params">(args *MapJobDoneArgs, reply *MapJobDoneReply)</span> <span class="hljs-title">error</span></span> &#123;<br><span class="hljs-keyword">if</span> args.Content == <span class="hljs-string">&quot;single&quot;</span> &#123;<br><span class="hljs-comment">// 通知单个任务结束</span><br>c.MapJobStateLock.Lock()<br>c.MapJobState[args.FileName] = <span class="hljs-literal">true</span><br>c.MapJobStateLock.Unlock()<br>&#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> args.Content == <span class="hljs-string">&quot;all&quot;</span> &#123;<br><span class="hljs-comment">// 查询全部任务是否结束</span><br>reply.Done = <span class="hljs-literal">true</span><br>c.MapJobStateLock.Lock()<br><span class="hljs-keyword">for</span> _, value := <span class="hljs-keyword">range</span> c.MapJobState &#123;<br><span class="hljs-comment">// 存在未完成map任务</span><br><span class="hljs-keyword">if</span> !value &#123;<br>reply.Done = <span class="hljs-literal">false</span><br><span class="hljs-keyword">break</span><br>&#125;<br>&#125;<br>c.MapJobStateLock.Unlock()<br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *Coordinator)</span> <span class="hljs-title">ReduceJob</span><span class="hljs-params">(args *ReduceArgs, reply *ReduceReply)</span> <span class="hljs-title">error</span></span> &#123;<br><span class="hljs-keyword">select</span> &#123;<br><span class="hljs-keyword">case</span> reply.ReduceJobID = &lt;-c.ReduceJobs:<br><span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(jobID <span class="hljs-keyword">int</span>)</span></span> &#123; <br><span class="hljs-comment">//同map</span><br>time.Sleep(time.Second * <span class="hljs-number">10</span>)<br>c.ReduceJobStateLock.Lock()<br><span class="hljs-keyword">if</span> !c.ReduceJobState[jobID] &#123;<br>c.ReduceJobs &lt;- jobID<br>&#125;<br>c.ReduceJobStateLock.Unlock()<br>&#125;(reply.ReduceJobID)<br><span class="hljs-keyword">default</span>:<br>reply.ReduceJobID = <span class="hljs-number">-1</span><br>&#125;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *Coordinator)</span> <span class="hljs-title">ReduceJobDone</span><span class="hljs-params">(args *ReduceJobDoneArgs, reply *ReduceJobDoneReply)</span> <span class="hljs-title">error</span></span> &#123;<br>c.ReduceJobStateLock.Lock()<br>c.ReduceJobState[args.JobID] = <span class="hljs-literal">true</span><br>c.ReduceJobStateLock.Unlock()<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *Coordinator)</span> <span class="hljs-title">MROver</span><span class="hljs-params">(args *MROverArgs, reply *MROverReply)</span> <span class="hljs-title">error</span></span> &#123;<br>c.OverLock.Lock()<br>reply.Done = c.Over<br>c.OverLock.Unlock()<br><span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span><br>&#125;<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// start a thread that listens for RPCs from worker.go</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *Coordinator)</span> <span class="hljs-title">server</span><span class="hljs-params">()</span></span> &#123;<br>rpc.Register(c)<br>rpc.HandleHTTP()<br><span class="hljs-comment">//l, e := net.Listen(&quot;tcp&quot;, &quot;:1234&quot;)</span><br>sockname := coordinatorSock()<br>os.Remove(sockname)<br>l, e := net.Listen(<span class="hljs-string">&quot;unix&quot;</span>, sockname)<br><span class="hljs-keyword">if</span> e != <span class="hljs-literal">nil</span> &#123;<br>log.Fatal(<span class="hljs-string">&quot;listen error:&quot;</span>, e)<br>&#125;<br><span class="hljs-keyword">go</span> http.Serve(l, <span class="hljs-literal">nil</span>)<br>&#125;<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// main/mrcoordinator.go calls Done() periodically to find out</span><br><span class="hljs-comment">// if the entire job has finished.</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *Coordinator)</span> <span class="hljs-title">Done</span><span class="hljs-params">()</span> <span class="hljs-title">bool</span></span> &#123;<br>flag := <span class="hljs-literal">true</span><br>c.ReduceJobStateLock.Lock()<br><span class="hljs-keyword">for</span> _, value := <span class="hljs-keyword">range</span> c.ReduceJobState &#123;<br><span class="hljs-keyword">if</span> !value &#123;<br>flag = <span class="hljs-literal">false</span><br>&#125;<br>&#125;<br>c.ReduceJobStateLock.Unlock()<br>c.OverLock.Lock()<br>c.Over = flag<br>c.OverLock.Unlock()<br><span class="hljs-keyword">return</span> c.Over<br><br>&#125;<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// create a Coordinator.</span><br><span class="hljs-comment">// main/mrcoordinator.go calls this function.</span><br><span class="hljs-comment">// nReduce is the number of reduce tasks to use.</span><br><span class="hljs-comment">//</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">MakeCoordinator</span><span class="hljs-params">(files []<span class="hljs-keyword">string</span>, nReduce <span class="hljs-keyword">int</span>)</span> *<span class="hljs-title">Coordinator</span></span> &#123;<br>c := Coordinator&#123;&#125;<br><br>c.MapJobs = <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-keyword">string</span>, <span class="hljs-built_in">len</span>(files))<br>c.MapJobID = <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-keyword">int</span>, <span class="hljs-built_in">len</span>(files))<br>c.MapJobCount = <span class="hljs-built_in">len</span>(files)<br>c.MapJobState = <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">bool</span>)<br><span class="hljs-keyword">for</span> index, file := <span class="hljs-keyword">range</span> files &#123;<br>c.MapJobs &lt;- file<br>c.MapJobID &lt;- index<br>c.MapJobState[file] = <span class="hljs-literal">false</span><br>&#125;<br>c.ReduceJobs = <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-keyword">int</span>, nReduce)<br>c.ReduceJobState = <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">int</span>]<span class="hljs-keyword">bool</span>)<br><span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; nReduce; i++ &#123;<br>c.ReduceJobs &lt;- i<br>c.ReduceJobState[i] = <span class="hljs-literal">false</span><br>&#125;<br><span class="hljs-comment">// 初始化 coordinator</span><br><br>c.server()<br><span class="hljs-keyword">return</span> &amp;c<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="rpc">rpc</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">package</span> mr<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// RPC definitions.</span><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// remember to capitalize all names.</span><br><span class="hljs-comment">//</span><br><br><span class="hljs-keyword">import</span> (<br><span class="hljs-string">&quot;os&quot;</span><br><span class="hljs-string">&quot;strconv&quot;</span><br>)<br><br><span class="hljs-comment">//</span><br><span class="hljs-comment">// example to show how to declare the arguments</span><br><span class="hljs-comment">// and reply for an RPC.</span><br><span class="hljs-comment">//</span><br><br><span class="hljs-keyword">type</span> MapNumArgs <span class="hljs-keyword">struct</span> &#123;<br>&#125;<br><span class="hljs-keyword">type</span> MapNumReply <span class="hljs-keyword">struct</span> &#123;<br>Num <span class="hljs-keyword">int</span><br>&#125;<br><br><span class="hljs-keyword">type</span> MapJobArgs <span class="hljs-keyword">struct</span> &#123;<br>Content <span class="hljs-keyword">string</span><br>&#125;<br><br><span class="hljs-keyword">type</span> MapJobReply <span class="hljs-keyword">struct</span> &#123;<br>File     <span class="hljs-keyword">string</span><br>MapJobID <span class="hljs-keyword">int</span><br>&#125;<br><br><span class="hljs-keyword">type</span> MapJobDoneArgs <span class="hljs-keyword">struct</span> &#123;<br>Content  <span class="hljs-keyword">string</span><br>FileName <span class="hljs-keyword">string</span><br>&#125;<br><span class="hljs-keyword">type</span> MapJobDoneReply <span class="hljs-keyword">struct</span> &#123;<br>Done <span class="hljs-keyword">bool</span><br>&#125;<br><span class="hljs-keyword">type</span> ReduceArgs <span class="hljs-keyword">struct</span> &#123;<br>Content <span class="hljs-keyword">string</span><br>&#125;<br><br><span class="hljs-keyword">type</span> ReduceReply <span class="hljs-keyword">struct</span> &#123;<br>ReduceJobID <span class="hljs-keyword">int</span><br>&#125;<br><br><span class="hljs-keyword">type</span> ReduceJobDoneArgs <span class="hljs-keyword">struct</span> &#123;<br>JobID <span class="hljs-keyword">int</span><br>&#125;<br><span class="hljs-keyword">type</span> ReduceJobDoneReply <span class="hljs-keyword">struct</span> &#123;<br>&#125;<br><br><span class="hljs-keyword">type</span> MROverArgs <span class="hljs-keyword">struct</span> &#123;<br>&#125;<br><br><span class="hljs-keyword">type</span> MROverReply <span class="hljs-keyword">struct</span> &#123;<br>Done <span class="hljs-keyword">bool</span><br>&#125;<br><br><span class="hljs-comment">// Add your RPC definitions here.</span><br><br><span class="hljs-comment">// Cook up a unique-ish UNIX-domain socket name</span><br><span class="hljs-comment">// in /var/tmp, for the coordinator.</span><br><span class="hljs-comment">// Can&#x27;t use the current directory since</span><br><span class="hljs-comment">// Athena AFS doesn&#x27;t support UNIX-domain sockets.</span><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">coordinatorSock</span><span class="hljs-params">()</span> <span class="hljs-title">string</span></span> &#123;<br>s := <span class="hljs-string">&quot;/var/tmp/824-mr-&quot;</span><br>s += strconv.Itoa(os.Getuid())<br><span class="hljs-keyword">return</span> s<br>&#125;<br><br></code></pre></td></tr></table></figure><h2 id="源码">源码</h2><p><a href="https://github.com/Hanmengnan/MIT-6.824/tree/master/lab1">lab1 实现源码</a></p><h2 id="结果">结果</h2><p><img src="https://siegelion-blog.oss-cn-beijing.aliyuncs.com/blog/image-20210922193634059.png" alt="image-20210922193634059"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;早在保研时，对分布式感兴趣的我想要涉猎一些这个领域的知识，于是在网络上搜寻相关的学习资料，就发现了很多前辈都推荐学习者阅读一下&lt;strong&gt;谷歌的三大论文&lt;/strong&gt;——&lt;strong&gt;MapReduce&lt;/strong&gt;、&lt;strong&gt;G</summary>
      
    
    
    
    <category term="笔记" scheme="https://siegelion.cn/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Go" scheme="https://siegelion.cn/tags/Go/"/>
    
    <category term="分布式" scheme="https://siegelion.cn/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
    
  </entry>
  
</feed>
