首页 - 信息 - 你的解耦策略决定了你架构的高度!

你的解耦策略决定了你架构的高度!

2023-10-02 05:15
在架构设计中,大家都不喜欢耦合,但是我们的系统架构设计中经常出现的典型耦合有哪些,又该如何优化呢? 这里有6点:IP、jar包、数据库、服务、消息、扩展。 这些点如果设计不仔细的话,会导致系统出现一些耦合问题,这基本上都是大家实际遇到的痛点。本文将与大家分享如何使用常见的解决方案来解耦这些耦合。 如何查找系统中的联轴器 什么是耦合?就是说,每次我们作为技术人,心里骂上下游、骂兄弟部门,说,这事跟我有什么关系?我为什么要合作来做这件事?系统中很可能存在耦合。那个地方。 明明我们不应该合作,但是兄弟部门得做点什么,上下游部门得做点什么,而我却不得不被动地配合做这件事情。也有可能这次合作的范围特别大,也就是说耦合度非常非常重。 我们来看六个具体案例。 典型的耦合和相应的解耦实践 IP耦合 第一种情况特别常见。原来网上有服务或者数据库。由于磁盘硬件故障等多种原因,不得不更换机器。然后运维给了我们一台机器,我们部署数据库或者服务。 IP已部署,需要更改。以前有一个旧IP,但现在有一个新IP。然后还有很多上游依赖我。如果我的 IP 发生变化,我该怎么办?我去上游劝说我的IP变了。请更改配置并重新启动上游部门以连接到我的新IP。 不知道你在工作中是否会遇到这样的场景。这时候如果你是上游调用者,无论你调整数据库还是调整服务,你心里可能都在骂他。很明显是你的IP变了。为什么要合作?我是重启并配合更改配置的人吗? 特别是如果有很多人依赖一个基础服务或者一个基础数据库,那么你可能就得找到这些依赖它的人。可能有A部门、B部门、C部门,所有业务都靠你,你得把他们都找到。 ,全部重新启动。 那么在这种上下游由于IP配置耦合在一起的情况下,耦合范围其实是非常宽的,这让我们都觉得很烦人。 我们的希望是:你换IP,我不动。如果你自己升级了,我的流量就会默默迁移到那里。这是理解上下游耦合的一种非常直观的方式。 将内网IP改为内网域名是我们的做法。我们强烈建议您立即返回并执行此操作。为什么我们需要修改IP并重启呢? 很有可能我们把IP写在了自己的配置文件中。如果我们把这个内网IP改成内网域名,是不是可以让上游配合更改配置并重启呢? 假设我们不再使用IP,而是使用域名。现在换了一台机器,域名没变,只是IP指向变了。我们可以让运维统一将内网DNS切换到新机器上,切断与旧机器的连接。重新连接后会自动连接到新机器。 这种情况下,只要运维配合就可以完成迁移,不需要触及所有上游调用者、服务调用者、数据调用者。这是第一个案例。 我们的最佳实践是强烈建议使用内网域名替换内网IP,并删除所有与服务和数据库的连接。 公共库耦合 第二个案例是公共图书馆。该公共库可以是与业务相关的通用业务库,例如用户业务、支付业务等。这些业务都写在一个jar包中,每个业务线都是通过这个jar包来实现的。一些相关的业务逻辑。 所有业务方都因为这个公共库而耦合在一起。无论你使用so、dll、jar包代码,不同语言的公共库都有不同的方法。本质就是上游通过这个公共库耦合在一起。 我们遇到了什么样的情况呢? 58 招聘、房产、二手等业务线很多。用户的一些操作,比如登录、查询信息、修改信息等可能是一样的,所以我们有一个user.jar,所有用户都可以使用。通过这个jar包就可以进行操作。 然后是业务线,比如招聘,可能修改了一些用户操作的代码,修改了jar包。 修改后上线前会进行测试,但招聘只会测试自己的业务,不会测试兄弟业务线的业务。结果上线的结果就是上线后兄弟所有业务线都宕机了。 于是就出现了非常有趣的一幕。 A、B的业务老大在群里说业务宕机了,然后一个研发小哥跳出来解释说C部门上线了,所以我们都宕机了。这个解释很难解释。通过了。 为什么我们兄弟的部门好好的,他上网没问题,而我们却宕机了?因为jar包是耦合在一起的。也许我们会在心里默默地骂他们。是你修改了代码,你才是没有问题的。是的,问题是我。我其实什么也没动。我觉得很委屈。 多个上游因为jar包耦合在一起。有哪些优化方法? 如果代码库有很强的个性 如果这个jar包或者这个公共库有很强的个性,如果是为了招聘、房产、或者二手用,我们的建议是将这些个性化的代码拆分到每个业务线自己的jar包中。这样的话,你修改的部分只会对你产生影响,至少不会扩大影响范围。这需要分析业务并找出各个方面。 如果长期解决不了,我刚才说的耦合就会经常出现,而且经常出现。最坏的情况下,我们可以复制代码,比如复制三份,但不建议这样做。 我们的建议:将各个部分提取出来,将一项业务原来的jar包变成三个额外的包,每个包只与一项业务相关。 如果公共图书馆的用途很广泛 如果这个库的通用性比较强,我们建议将通用的部分集成到一个独立的服务中。该服务提供到上游的接口。我每次测试你的时候,你也要测试一下接口的兼容性。 如果是新业务,我们建议添加新的接口,至少不会影响旧代码,并通过服务或RPC调用进行解耦。 数据库耦合 第三种情况应该是大家都会遇到比较多的一种情况,数据库耦合。 我先说一下业务场景:业务A、业务B、业务C。这里以用户的业务为例。有些用户的数据是公共的,存储在用户表中,而个性化的数据则存储在个性化数据库中。 例如,企业A可能有表A,企业B可能有表B,企业C可能有表C。 假设我的业务线需要收集个性化数据和通用数据。我们的代码经常是这样写的。个性表连接个性表。 UID是一样的。 UID等于我的用户1、2、3。个性数据和通用数据一起提取没有任何问题。 业务线B和业务线C也是这样做的。所以你会发现join语句实际上是让用户的表和业务线A、B、C的表耦合成一个数据库实例。 这会带来什么问题呢?例如业务线A需要上线一个功能。该函数没有索引,必须扫描整个表。数据库CPU会100%,数据库实例的IO性能下降,影响业务。 对B和C都有影响,即某条业务线的数据库性能急剧下降,导致所有业务受到影响。 这时候DBA兄弟、运维兄弟过来说性能不好。我会给你另外两台机器和两个实例。你会发现它毫无用处。所有表都耦合在一个实例中,并且不能被机器分开。 ,无法扩展。 2015年我调到58到家的时候,58到家有一个图书馆,叫58到家图书馆。里面有几百张桌子,性能越来越低。但由于同一个实例中必须耦合各种join,所以很惨。我们该怎么做呢?垂直细分和服务化。你会发现它和jar包解耦很相似,垂直分割。 比如我把一个用户的基础数据拉到一个用户的服务上,那么这个用户的最基本的数据库就只能通过这个服务锁来访问。数据库隐私是服务化的一个特征。 这个时候,业务线原有的业务怎么能满足呢?原来业务方通过join直接获取了公共数据和私有数据。此时,原来的一次数据库访问变成了两次数据库访问。第一次取的是个性化数据,第二次取的是通用数据,然后组装业务层。 与前面的方法相比,前面的方法的业务代码可能更简单,因为它把业务逻辑放在了SQL语句中,但是导致了数据库耦合在一起。 后一种方式意味着业务代码会更加复杂,会变成多重访问,把原本用SQL进行的逻辑计算变成我们自己代码的逻辑计算。 这时候,企业有自己的图书馆,公众有公共图书馆。你会发现这些库很可能在前期也是在同一个实例中的。但是,当性能下降时,您可以轻松添加新实例并从实例中删除其中一个公共库。放在另一个实例中,甚至添加一台新机器来扩展硬件容量。 所以垂直切分就是业务方自己的数据库放在自己的上面,公共数据库放在公共数据库上。不要在一种情况下将其耦合。这是一个比较典型的业务场景。 服务耦合 第四种情况是面向服务耦合的例子。服务化之后,如果业务代码没有拆分干净,即使做了服务化也无法解耦。这里是一个面向服务解耦不彻底的案例。 上面是ABC的三个业务方,下面是综合服务。如果你的解耦不彻底,你的通用服务中都包含业务端代码,那么最典型的业务端代码是什么样的? 也就是说,服务层switch case是无法根据调用者的类型来访问的业务逻辑代码。我们做服务化的时候,其实是想把公共部分抽象出来,下沉到公共部分可以提供的服务中。但如果解耦不彻底,就会有代码传入不同的biz-type,执行不同的逻辑。 这会带来什么问题呢?如果增加新的业务需求,你会发现很有可能底层服务需要更改代码。比如业务1有一个需求,他来找你说我对这个需求有一个扩展。请在这里升级。 业务2和业务3一样,显然是业务方有需求。为什么是我在最下面修改代码呢?业务需求方有很多,你要把所有的业务需求方都实现。你太忙了。这个时候,你可能心里在骂他。 这个耦合范围比较小,因为基础服务维护的痛点只有一个。解决办法也很容易想到。当然是将业务的个性化案例分支移到上游,底层只执行通用功能。 业务代码将会浮动。这样,上游业务迭代速度和迭代效率都会得到提升。每个业务功能将自行实现。不需要兄弟部门去执行,也没有沟通的过程。这是不完整服务化的常见耦合情况。 消息通知耦合 第五种情况是消息通知的耦合。我估计很多公司都遇到过,也有一些事件。这件事可能需要很多下游企业都知道。这是我们经历过的一个案例。 58 同一个城市的帖子可能需要多方都知道。例如,有一个用户评级服务。他发帖后,该用户发帖的一些统计数据和信息数据可能需要更新。 我们还需要通知线下消息反作弊部门,发布此帖子后,他们可能会做一些线下分析处理,看看是否有反作弊的嫌疑。 我们甚至可能需要将此消息通知业务线,例如招聘业务线。我们最近做了一些营销活动,只要发布招聘帖子就可以获得积分。 后期发布服务应该是一项非常基础的服务。它是否有责任将帖子消息同步到通知关注者? 我们最初是如何实施的? 58同城有一个面向服务的架构,告诉你通过RPC来发布帖子。 所以我们的上游就是发帖的基础服务。发帖通知反作弊部门,发帖通知数据统计部门,发帖通知业务线。这个结构其实就是因为这个 上下游通知耦合在一起的。 那我们什么时候才能暗骂这个下游呢?假设现在增加了一个新的业务线,房地产业务线也做营销活动,也注重后期发布。发帖的兄弟可以联系我吗? 出版小哥发现,被改变的是出版服务的代码。他本来想调整123,现在想调整4,如果有人有新的需求,他想调整5。这对于发布服务的工程师来说是非常痛苦的。明明需要的是业务方,但修改代码的是我。 原因是上下游消息耦合在一起。一种非常常见的解耦方案是通过MQ。本例中的MQ和下例中的配置中心是互联网架构中两个非常常见的解耦工具。 MQ可以实现上下游的物理和逻辑解耦。添加MQ后,首先上游不知道对方的存在。当然,它不会建立物理连接。每个人都与MQ建立物理连接,这意味着物理连接是解耦的。耦合。 逻辑上也是解耦的,消息发布者甚至不需要知道有哪些下游方订阅了消息。新消息的订阅者只需要找到MQ即可,上游无需关注。因此,MQ是一个非常常用的物理解耦和逻辑解耦的工具。 下游膨胀接头 第六个案例,我相信是几乎所有企业都会遇到的案例。与第一种情况类似,但又有所不同。 我们的第一个案例是关于IP变更的。上下游IP发生变化。我们的建议是使用内网域名而不是IP进行配置,以解耦上下游连接。 扩容、换IP是一种场景,扩容是第二种场景。 现在有service1、service2和Web1。底层服务是集群。随着业务、数据量、并发量的增加,需要对服务进行扩展。我想添加两个新节点。 假设我要添加IP4和IP5,你会发现案例1的场景又出现了。因为我扩容了,所以得通知所有上游麻烦帮忙添加两个IP和两个内网域名。显然是下游需要扩容,但需要修改配置并重启的却是上游。 我们早期的解决方案是什么样的?我们使用私有配置方式进行配置。 一般每个上游都有自己的配置文件,配置文件依赖于下游。该配置文件将被放置在上游配置文件中。 Service2一般都有一个配置conf,写成依赖内网配置。内网域名为123,那么服务启动时就可以通过配置建立连接。 对于网络来说也是如此。它有一个Web1.conf。想想你所服务的公司是不是这样。 这是数据的扩散。本来数据就在这一份,但是你会发现这个数据分散到了不同的上游。每个人都存储该数据的副本。当我的数据想要改变时,每个上游都需要改变。 如果数据只存在于一处,那么这一处发生变化,一切都会改变,所以无需担心数据的一致性。 如果你能知道上游是谁,那么通知你的上游改进用户的配置并重启就可以了。我们遇到的痛点是什么? 58 同城有数千人,数百家企业。有这么多。我不知道谁依赖我。如果我知道123依赖我,我就告诉你。 现在我不知道谁依赖我,因为当你连接到我时,你不需要我的许可。调用方法是什么你可以看手册了解一下。我们将添加IP,我如何通知您? 我刚才说了,根本原因其实是一个配置数据已经扩散到多个上游了。那么我们是否可以将这些配置数据放在一个地方而不进行传播呢?如果我改变这一处,它会改变吗? 解决方案就是配置中心。配置中心的细节这里就不多说了。网上可能有一些公司的做法,配置后端、DB存储等。配置中心是一个典型的逻辑解耦但物理解耦的架构工具。我们所有的上游都依赖于我们的下游,必须建立物理连接。 引入配置中心后,不会通过私有配置或全局配置文件读取下游IP。相反,配置中心说我要访问用户服务。 配置中心告诉他,用户服务的内网域名是123,服务的1、2、3仍然沿用内网1、2、3,用户服务仍然是物理连通的。所有上游都是这样读取下游的。配置。 在配置中心这边,他可以知道谁连接了用户服务。在配置中心后台,他可以配置哪些人有我设置的限流,然后把这个限流同步到调用者的客户端。当然,也可以同步到服务器进行双向保护。 如果用户服务扩展了,比如说我想加几个节点,我加了4和5,那么我配置后台就说加了4和5。后台可以知道哪些上游依赖了它,并反向通知后台,这完全不需要上游做。 总结 解耦后,系统可以更好,程序员也可以少一些怨恨。希望今天分享的话题和案例能够帮助大家解决工作中的一些实际问题。谢谢你们。 沉健,《建筑师之路公众号》作者。曾担任百度高级工程师、58同城高级架构师、58同城技术委员会主席、58同城C2C技术部负责人。现任58到家技术委员会主席、高级技术总监,负责58快车的研发和管理。本质上,他是一个技术人。