<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>架构 on 子恒的博客</title>
    <link>http://chestnutheng.github.io/categories/%E6%9E%B6%E6%9E%84/</link>
    <description>Recent content in 架构 on 子恒的博客</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Mon, 15 Apr 2024 19:31:56 +0800</lastBuildDate>
    <atom:link href="http://chestnutheng.github.io/categories/%E6%9E%B6%E6%9E%84/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>[架构]单体和微服务</title>
      <link>http://chestnutheng.github.io/%E5%8D%95%E4%BD%93%E5%92%8C%E5%BE%AE%E6%9C%8D%E5%8A%A1/</link>
      <pubDate>Mon, 15 Apr 2024 19:31:56 +0800</pubDate>
      <guid>http://chestnutheng.github.io/%E5%8D%95%E4%BD%93%E5%92%8C%E5%BE%AE%E6%9C%8D%E5%8A%A1/</guid>
      <description>&lt;h1 id=&#34;多大的服务&#34;&gt;多大的服务&lt;/h1&gt;&#xA;&lt;p&gt;“宜小不宜微”，不宜过大的单体，也不宜滥用微服务，应保持在合适的大小。&lt;br&gt;&#xA;（ps. 这里只讨论服务的大小。不讨论服务的手段，拆分解耦、服务发现、mesh、告警、限流、全链路建设，都是好的实践，任何服务都可以做的同样好）&lt;br&gt;&#xA;什么样的服务大小属于合理的服务？核心有两点，合理划分服务边界，匹配组织结构。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;团队视角：服务的规模应该尽量限制在最小团队，约3人左右的团队为宜&#xA;&lt;ul&gt;&#xA;&lt;li&gt;❎ 过大！跨团队修改同一服务，风险大且不利于基础建设。没有人对整体的架构和质量负责。没有人有全局视角，写代码因为不了解改动范围容易出故障。&lt;/li&gt;&#xA;&lt;li&gt;❎ 过大！10人以上的团队不宜修改同一服务。很可能服务定位不清晰，10个人的信息交流会有很多gap，效率会大大降低&lt;/li&gt;&#xA;&lt;li&gt;❎ 过小！团队每个人管三四个服务，说明服务数量过多，无用工作会增加，效率也会降低&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;业务视角：保证产品需求变更如果只在本团队内，修改的服务1-2个即可&#xA;&lt;ul&gt;&#xA;&lt;li&gt;❎ 过小！改一个小小的功能，牵扯4-5个服务。服务太小了，效率降低&lt;/li&gt;&#xA;&lt;li&gt;❎ 过大！同时有四五个业务需求，对应数十个研发同时修改一个服务，而这些功能看起来并不密切。这说明服务包揽的功能太多了，已经膨胀的太大了&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 id=&#34;更微更好&#34;&gt;更微=更好?&lt;/h1&gt;&#xA;&lt;p&gt;我们以一个模块划分合理、大小合适的单体服务为例，看看如果拆分的更微小，对比起来会有哪些点值得关注：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;关注点&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;合理的单体&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;更微服务&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;性能&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;无劣化&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;约数ms的劣化&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;复用性&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;可以仓库内直接引用&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;不可复用其他仓库代码；所以也不会复用到bug&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;可读性&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;容易找代码&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;需要跨的仓库多，层次深，理解困难&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;研发成本&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;编译可能较慢&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;写代码、编译、调试、发布、部署都需要多仓库并行，成本高&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;部署成本&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;单机器&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;高，需要多台机器&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;代码合入&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;公共部分容易冲突&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;不容易冲突&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h1 id=&#34;微服务教&#34;&gt;微服务教&lt;/h1&gt;&#xA;&lt;p&gt;我们看下某网站对单体的批评，一一回应下：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;开发效率低：所有的开发在一个项目改代码，递交代码相互等待，代码冲突不断&#xA;&lt;ul&gt;&#xA;&lt;li&gt;确实，代码冲突会变多。但在单体内部模块划分合理的情况下，冲突的只会是公共模块；如果这些代码冲突了，使用微服务也会冲突。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;代码维护难：代码功能耦合在一起，新人不知道何从下手&#xA;&lt;ul&gt;&#xA;&lt;li&gt;耦合是模块设计的问题，不是单体的问题。而且单体断层更少，更容易阅读&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;部署不灵活：构建时间长，任何小修改必须重新构建整个项目，这个过程往往很长&#xA;&lt;ul&gt;&#xA;&lt;li&gt;没发现编译时间有显著区别。部分单体仓库会编译多个产物，建议针对性编译，效果更好。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;稳定性不高：一个微不足道的小问题，可以导致整个应用挂掉&#xA;&lt;ul&gt;&#xA;&lt;li&gt;绝对意义上，微服务存在同样的问题，而且更难定位。相对意义上，如果说单体的复用代码带来了问题风险的扩大，这属于因噎废食，不可取。&lt;br&gt;&#xA;微服务给我们带来了什么样的反思？拒绝盲目创新，提防吹牛皮但不切实际，小心滥用导致过犹不及。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 id=&#34;再次回到起点monorepo&#34;&gt;再次回到起点：monorepo&lt;/h1&gt;&#xA;&lt;p&gt;有趣的是，越来越多人意识到了微服务的问题，最终又回归了单一的仓库，并发明了一新的词：monorepo。&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Segment.com 提供活动收集和转发服务，每个客户都需要使用一种特殊格式的数据。因此，工程团队最初决定混合使用微服务和多代码库。这一策略效果很好——随着客户基数的增长，他们扩大了规模，没有出现问题。但是，当转发目的地的数量超过 100 个时，事情开始变得糟糕起来。维护、测试和部署超过 140 个代码库（每个代码库都有数百个日益分化的依赖关系）的管理负担太高了。最终，团队发现他们无法取得进展，三个全职工程师花费了大部分时间来维持系统的运行。&lt;br&gt;&#xA;对于 Segment 来说，补救办法就是合并，将所有的服务和依赖迁移到一个单一代码库中。他们必须协调共享库并且测试所有内容，虽然花了很大的代价，但迁移非常成功，最终的结果是降低了复杂性，增加了可维护性。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;每个团队的精力是有限的，当每个人管理的仓库&amp;gt;10个，团队管理的仓库&amp;gt;100个，会出现非常多的问题：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;团队无法集中注意力管理。有很多的仓库大部分人并不知晓。这部分代码不再能被复用，也不再被了解&lt;/li&gt;&#xA;&lt;li&gt;开发要打开很多IDE并发进行，容易出错。测试需要部署非常多的服务，也不能跨服务debug。发布时需要发布过多的仓库，发布容易遗漏；发布有很复杂的依赖，发布也会花费更久的时间。&lt;/li&gt;&#xA;&lt;li&gt;无法发起整体性重构。重构一个服务和同时重构10个服务的难度不是一个级别。&lt;/li&gt;&#xA;&lt;/ul&gt;</description>
    </item>
    <item>
      <title>[架构]常见的架构设计思想</title>
      <link>http://chestnutheng.github.io/%E5%B8%B8%E8%A7%81%E7%9A%84%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%E6%80%9D%E6%83%B3/</link>
      <pubDate>Sun, 23 Apr 2023 14:45:11 +0800</pubDate>
      <guid>http://chestnutheng.github.io/%E5%B8%B8%E8%A7%81%E7%9A%84%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%E6%80%9D%E6%83%B3/</guid>
      <description>&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-toc&#34; data-lang=&#34;toc&#34;&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这片文章想讲一下软件架构的基础，一些基本的原则和思想。我理解的架构是指导我们设计和实现软件的方法论；这意味着我们对复杂度、可扩展性、稳定性（分别对应迭代上手成本、迭代效率、事故损失）这三个方面有非常高的要求，尤其是复杂度，对修改代码的影响范围、成本、难易程度都有显著的影响，是架构要解决的核心问题。&lt;br&gt;&#xA;另外，我们也不额外考虑性能，因为业务中的性能瓶颈距离我们讨论的问题往往还有几个数量级，往往是异步、流程优化、io等待等方面更加能有作为，采取什么样的设计方法能起到的作用微乎其微。&lt;/p&gt;&#xA;&lt;h1 id=&#34;设计原则&#34;&gt;设计原则&lt;/h1&gt;&#xA;&lt;p&gt;关于原则，我想说一句话，原则是开发迭代中总结出来的经验，而不是开发迭代的规范。判断是不是原则适用的场景（而不是无脑用原则）往往更加有效，也更加困难。类似“没有银弹”。&lt;/p&gt;&#xA;&lt;p&gt;KISS: Keep it simple, stupid，不要引入不必要的复杂&lt;br&gt;&#xA;DRY: Don‘t repeat youself，不要重复代码&lt;br&gt;&#xA;迪米特法则: The Least Knowledge Principle，使用者不知道实现细节&lt;br&gt;&#xA;SOLID原则:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;S 单一职责原则：对象应该仅具有一种单一功能&lt;/li&gt;&#xA;&lt;li&gt;O 开闭原则：软件的目标应该是可扩展，不可修改&lt;/li&gt;&#xA;&lt;li&gt;L 里氏替换原则：子类在任何代码中替换父类，行为一致&lt;/li&gt;&#xA;&lt;li&gt;I 接口隔离原则：多个特定接口要好于一个宽泛用途的接口&lt;/li&gt;&#xA;&lt;li&gt;D 依赖反转原则：抽象不应该依赖于具体，具体应当依赖于抽象（DDD就是这样，实际需求 -&amp;gt;抽象领域 -&amp;gt; 实现细节）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;上面这些法则，几乎都在说：&lt;br&gt;&#xA;代码简单可复用，架构高内聚低耦合&lt;/p&gt;&#xA;&lt;h1 id=&#34;过程而非对象&#34;&gt;过程而非对象&lt;/h1&gt;&#xA;&lt;p&gt;面向对象（OOP）还是面向过程（函数式FP）？&lt;br&gt;&#xA;这里的面向过程是对过程做结构化抽象，并不是平摊代码，这种明显的靶子行为不在我们讨论范围内。&lt;/p&gt;&#xA;&lt;p&gt;我们先看看面向对象的核心特征（Java为例）：封装、继承、多态&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;封装：和自己数据有关的操作封装成成员方法，并控制可见性&lt;/li&gt;&#xA;&lt;li&gt;继承：得到父类的属性和方法&lt;/li&gt;&#xA;&lt;li&gt;多态：不同类型调用同一个成员方法会产生不同的结果&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;我们看看非面向对象的做法（Go为例）：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;封装：和自己数据有关的操作封装成接收器，并控制可见性（非常类似）；和自己数据无关的操作，直接做成普通函数&lt;/li&gt;&#xA;&lt;li&gt;继承：得到别人的属性和方法都用组合的方式，避免构造函数、重载、层次设计&lt;/li&gt;&#xA;&lt;li&gt;多态：类型之间没有继承关系，不同类型实现同一个接口的方法。调用的时候按自己的类型去做出对应行为&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;封装函数和数据的耦合&#34;&gt;封装：函数和数据的耦合&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.yinwang.org/blog-cn/2015/04/03/paradigms&#34;&gt;编程的宗派，王垠&lt;/a&gt;&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;对象作为数据访问的方式，是有一定好处的。然而“面向对象”（多了“面向”两个字），就是把这种本来良好的思想东拉西扯，牵强附会，发挥过了头。很多面向对象语言号称“所有东西都是对象”（Everything is an Object），把所有函数都放进所谓对象里面，叫做“方法”（method），把普通的函数叫做“静态方法”（static method）。实际上呢，只有极少需要抽象的时候（为访问对象数据提供间接的封装），你需要使用内嵌于对象之内，跟数据紧密结合的“方法”。其他的时候，你其实只是想表达数据之间的变换操作，这些完全可以用普通的函数表达，而且这样做更加简单和直接。&lt;br&gt;&#xA;这种把所有函数放进方法的做法是本末倒置的，因为函数并不属于对象。绝大部分函数是独立于对象的，它们不能被叫做“方法”。强制把所有函数放进它们本来不属于的对象里面，把它们全都作为“方法”，导致了面向对象代码逻辑过度复杂。很简单的想法，非得绕好多道弯子才能表达清楚。很多时候这就像把自己的头塞进屁股里面。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;过度封装会导致几个结局：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;类的职责逐渐变得模糊，越来越大，远远超出了设计时的职责&lt;/li&gt;&#xA;&lt;li&gt;对象的成员变量太多，且有没有赋值、赋值产生的依赖很难管理&lt;/li&gt;&#xA;&lt;li&gt;对象的成员函数太多，类有哪些功能、哪些函数很难辨别&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;我们实践的时候，会有entity作为数据的封装，和一系列操作entity自身的成员方法。但如果涉及别的对象呢？我们有converter，helper等方法包作为补充&lt;/p&gt;&#xA;&lt;h2 id=&#34;继承vs组合&#34;&gt;继承vs组合&lt;/h2&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;不要仅仅为了代码复用而继承。当你使用组合来实现代码复用的时候，是不会产生继承关系的。过度使用继承的时候，如果修改了父类，会损坏所有的子类。这是因为子类和父类存在紧耦合。&lt;br&gt;&#xA;不要仅仅为了多态而继承。如果你的类之间没有继承关系，并且你想要实现多态，那么你可以通过接口+组合的方式来实现，这样不仅可以实现代码重用，同时也可以实现运行时的灵活性。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://kaisery.github.io/trpl-zh-cn/ch17-01-what-is-oo.html&#34;&gt;Rust程序设计语言&lt;/a&gt;&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;子类不应总是共享其父类的所有特征，但是继承却始终如此。如此会使程序设计更为不灵活，并引入无意义的子类方法调用，或由于方法实际并不适用于子类而造成错误的可能性。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;考虑到实际的工程往往都是多层次的组合和复用，继承能带来的收益非常小，但是后期的维护成本却是很难评估的。所以我建议在任何时候都不要使用继承。&lt;/p&gt;&#xA;&lt;p&gt;我们讨论完面向对象的核心特质：封装、继承、多态，发现这些行为的滥用都需要格外地小心。这也是为什么工业界越来越多的go和rust，仅仅从这一章节讨论的结果来说，这些更年轻的语言汲取了面向对象（数据封装、成员方法）和函数式（接口、一等公民）的优点，取消了他们诸多迷惑的特性，这是在任何语言都可以借鉴的行为。&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://mp.weixin.qq.com/s/aLUuVqglP4bkpFXoGJ7mTQ&#34;&gt;“干净”的代码，贼差的性能&lt;/a&gt;&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
