技术解析

微服务架构和代码复用是不是本身就是有冲突的?
0
2021-06-09 14:13:41
idczone

微服务架构里,每个微服务彼此都应该是黑盒,自己内部实现逻辑,业务逻辑不干涉。但是现实里哪有切那么抗投诉服务器完美的用例?总是会有一些类和方法多个微服务里都会用到,哪怕不是完全一样也是高度类似,在传统设计里肯定要重构复用的。但是微服务里要是分开单独写就相当于冗余代码高维护,抽出来给多个模块共用又破坏了微服务设计理念。是不是微服务设计的时就这种情况就是要牺牲可复用性的?


外部包依赖呀,Linux 内核也这样干,跟微服务有什么关系?难道你自己写高性能日志库,自己写高并发网络库?

git sub modules?

你这个不建议,python pip, Ja*a maven, go mod

公共服务 与 公共工具 这不一样

不是说那些 util,是指业务逻辑啊。只是个比方,假设微服务做的 ERP 系统里,一个管订单的和一个管记账的,可能都会用到 customer 这个类或者相关的逻辑方法?

customer 不就应该是一个独立 domain 做成 service 吗,别的服务调用他不就复用了

那么也许 customer 应该抽离出一个服务

客户当然是一个服务呀,万一 customer id 由 int32 变 int64,你耦合在代码里面,你能确保 order 和 wallet 同时升级发版吗

微服务就是要精简服务的业务逻辑,这才是避免代码冗余,提高代码复用率啊。比如你写了个卖书的网站,那支付,购物车,账户这些都是微服务,等你再想扩展搞一个线下签售书籍预约平台,直接就可以复用账户和支付这两个服务啊。

class lib,动态链接库

单独 customer 服务呀

理是这个理,所有教程也都这么说,但是很多时候做起来还是有重复又太小不足以单做一个微服务的业务逻辑。举的 customer 的例子不太合适,但是很多时候还是会有这种情况。

哈!我们搞了个公共服务,什么文件上传、字典维护、4 级地址,和业务无关的功能,都往里面怼。

你一个人搞微服务 当然觉得哪里都需要定义 不要拿一个人小视角看微服务
你的问题我也思考过 其实就是觉得 各种服务里面重复定义一些实体类麻烦
这过麻烦是因为你一个人写而已

customer 这个例子比较大是这样的,但是如果重复的是比较小的一个类呢?比如身份证作为一个很小的类,偏偏多个地方都要用,像这种情况,微服务的理念是只要有多个服务依赖的业务逻辑类,就要拿出来再做一个微服务么?

对的,就是一两个人搞麻烦。其实麻烦还是次要,关键实体类一旦要动维护更麻烦,还有命名也容易混淆搞得头大

所以 1,2 个人搞微服务就是自己找不自在啊。。
微服务的核心理念是,没有公共逻辑和实体。只有公共微服务。。按照这个核心进行思考。
实体类每个服务维护自己的就好了。

沒有銀彈.
微服務肯定要付出代價.
原本單體用 ORM 從 DB 讀出來, 變成 Object 在內存操作處理多快. 微服務要物件序列化在網路間傳來傳去, 可以多付點雲服務商流量費.

谁说复用就一定要 copy 代码了? 打个包做个库、提供 API 服务 这些不都是复用么。

难道单体应用(微服务的对立面)里就没有和代码复用冲突的事吗? 微服务和代码复用根本是两个层次的语素

没有想好为什么用微服务就不要用微服务

架构,框架,设计模式,都是为了解决人的问题。所有的问题都是人的问题。
举个例子,您说的场景,完全可以提炼公共组件实现复用。

别人说打包成包,让各个微服务 import,一提就被你否了
别人说独立成微服务,也被你否了

有讨论下去的必要吗
看来不是代码问题,是人的问题

举个例子日志库难道不是各个微服务都要 import 的嘛
打包个 lib 有什么问题

讲依赖,将包的都是技术视角的答案。
微服务本身不是为了解决技术问题来的,所以从技术视角看不是很准确。
微服务本身主要是给部门增加 HC (领导才能往上升),增强系统鲁棒性是 side effect,从成本上看,得益于云计算特别是 k8s 的发展,抛开人力成本不讲(这是目的),效能是上升的。
所以,答案是不冲突,微服务架构不需要复用,需要的是更多而细的服务。

没啥好办法,抽 lib 或者再抽出一层做服务

究极解构,每个业务抽象类都做成一个微服务

抽象,代理,小中台即可解决

身份证是一个单独的枚举值?我理解身份证一定关联着用户信息吧,那应该属于 customer 或者 user 这个 domain 才对

所有的问题都是制度的问题

微服务是一种架构和部署方式,不是一种源代码组织方式。谁规定一个 source tree 只能 build 出一个微服务的?
服务间不要依赖对方的实现细节,和服务间不要共用代码,也是两回事。

不能更同意#31,那些无脑抽服务的同学们,你们怕是没有经历过处理一个请求要经过几十个服务的恐怖

哈哈,曾经经历过经历 7 个的服务

公用代码放到依赖里。。单独一个依赖工程。。我们现在就是这么做的。

拆分成独立的微服务,公共依赖能不用就不用。

可以在微服务的不同部分间复用代码,前提是那个代码具有极高的复用价值,例如,语言的标准库,微服务之间都是共用的。之所以拆成微服务就是从代码层面杜绝两个服务的联系,因为它们之间耦合的坏处已经远远大于好处了。
所以并不冲突矛盾,都是代码可读性和可维护性以及开发效率等等诸多方面之间的权衡。
关键是要搞清楚不同的思路,到底是解决什么样的问题,具体问题具体分析。

还有就是,耦合这种说法本来就是不精准的,就是就算在代码级别重复使用了,也有高耦合和低耦合的写法。

我觉得你说的很有道理,我们现在是每个微服务单独一个 springboot application,共用的实体类都丢一个公共模块里其他的 import,这个法子其实和楼里很多人说的一样,但我想着这样每个微服务不还是有公共的业务逻辑了么,就有点困惑

大哥我没有去否谁,我们现在也是抽出来 import,我只是接了这个摊子第一次做微服务有点困惑,想问问这个架构的思路对不对


还是那个问题,如果新创建 customer id 规则变了,你们各个服务需要相互配合上线日期,风险很大,一个回滚了,其他服务字段就对不上了,

但是 customer 微服务能解决这个问题,一个人加班就解决战斗,不需要全公司加班

就这么简单,
如果你说需求万年不变或者用户数只有一万,那就没必要单独微服务,整这些,公司倒闭了都没整完

如果是那些可能需要公用的 entity 之类的,那需要单独建立一个模块引入到工程中。如果你是说公共逻辑中有一些操作可能会经常出现在几个不同的微服务中时造成代码重复,那没有办法。如果要考虑降低重复代码,你就考虑引入领域驱动,也就是 DDD,来构建值对象。将那些重复操作再归拢一遍!

可以参考一下 B 站
1. 约 329 个 Go 服务, 历史约 170 人左右贡献过 Go 代码. 其中 admin 目录下 54 个, infra 目录下 5 个基础组件服务, interface 下 77 个, job 目录下 80 个, service 目录下 113 个.
2. 代码和目录规范性比较好, 代码生成工具建设比较好, 大家可以借鉴一下.
3. 对于一个 Golang 开发者来说, 入职 B 站, 我觉得大概 2-3 天就可以 copy&&paste 开始贡献业务代码了. 其他语言开发者, 3-4 天吧, 因为学习 Golang 花一天.
4. B 站 Go 不依赖 CGO, 业务代码可以在 windows 编译通过! 启动!
5. 组件基本是基于开源组件封装.
6. RPC 基于 grpc 封装, 协议编码为 proto, 没有我们通常那样的包头.
7. 服务注册与发现已经包装在 RPC 中. 注册使用自研的 discovery, 基于类似 url 的方式去注册和寻址.
8. 数据存储多使用 memcache, redis 和 DB.
9. hbase 也使用比较多. 用于鉴权, 用户数据存储. 对于一些 kv 数据, 外部没有支持冷热分离的 kv 存储, hbase 是一个非常好的选择: 基于 HDFS, 热数据加载到内存, 列式存储, 强一致, 可配置副本数.
10. 消息队列为使用基于 kakfa, 实现了 redis 协议的 databus.
11. 小文件存储: B 站自己实现的 bfs
12. 监控上报使用的是 prometheus, 对于中小公司, 没法建设自己的监控组件, prometheus 是很不错的选择.
13. 简单浏览了下, 这份代码在 SQL 上没有注入风险. 生产环境的配置并没有在这份代码中. 一个合格的开发者, 即使所有源码流出去, 也不会对系统造成任何危害.
14. 不过 B 站的代码似乎打点监控做的不是很多(可能没有太多的去强调?)

你这个 customer 肯定是单独一个 service 啊。然后调 customer 这个类是从一个公有库里调,不会订单和记账各自声明一个 customer 的。

更详细点的在哪里看啊

搜这篇 《一探 B 站后台架构, 他山之石, 何以攻玉?》

他的源码我看过,有些服务,投币还是 coupon 的就没几个接口。但也是一套全的。感觉就是毛剑等大佬搭好框架,业务小弟开始填逻辑!!!
关键基础设施要好啊,不然 329 个服务你部署一套测试环境,部署一套演练都要吐血了。。

就像楼上说的,你两个微服务用了相同的日志库,难道它们就耦合了?显然不是。只要微服务 A 不依赖于微服务 B 用了和自己一样的日志库,那用哪个日志库就是实现细节。同样的,你的微服务依赖于别的微服务内部用了同一个公共模块么?不依赖,那就没有耦合。

不同领域的问题,虽然逻辑一样但业务不同,如果不分开,将来业务扩展起来是无尽的灾难,
论领域驱动设计之 限界上下文的重要性

如果有多个服务都使用同一段业务代码,那这段代码就应该单独抽出来成为一个服务供其他服务调用。不存在什么比较小的类,比较小的类就不能成为微服务了?谁规定的?
如果只有少数服务使用的业务代码,那应该把逻辑放在主要使用的服务里面,这个服务应该开放出接口供其他服务调用
出现任何你无法理解的状况,一般要么就是做微服务设计的时候没分清,要么就是业务场景根本不适合微服务

我搞这些东西搞了很多年. 我的建议是小重复无所谓, 大重复要重新审视服务划分. 大重复又改不动划分的话可以用 monorepo 凑合, 无论如何业务代码不要用包的形式复用.

最理想的方式:
正确分割上下文(其实就是所谓战略设计), 每个微服务之间尽量不要有重叠. 一旦划分错了重叠就会多, 后面选哪条路都有问题, 是无解的.

几种实践中的方式:
1. 重复比较少的时候忍一忍, 抄代码有重复是没事的.
2. 重复多的时候要想的第一件事是分割有错, 应该先考虑能不能把服务合并回去.
3. 重复多的时候抽公共服务, 但是公共服务的问题是会分层, 公共服务之间也会有公共服务, 最后变成金字塔, 服务数量指数级爆炸, 而且经常需要调整重构, 需要公用的地方会越来越多(因为服务变太小了), 对服务的单元测试质量也会下降(业务覆盖率太低). 所以公共服务非到万不得已不抽. 抽出来的都是业务很稳定的“中台”. 对于中台我的建议也是没有必要不搞.

不推荐的方式:
1. 包管理, 如果跟业务完全没有关系可以用包. 跟业务沾边就绝对不要用. 如果想公用, 又没有足够的人力像上述第 2 条那样合并服务, 可以简单把代码库合成一个 monorepo, 但是还部署成多个服务, 这样的好处是没有版本问题.
为什么不建议用包? 因为假设两个组的服务 A 和 B 都用了包 X, A 用的比较频繁, A 升级 X 比较频繁, B 升级不频繁, A 一年升级了 50 个版本. 某题 B 想升级 X, 发现 X 已经被修改了很多次, 要升级就要把之前的 API 全升级成 X 的最新版, 但是 B 在这一年里完全是基于旧的 X 写的新代码, 如果想升级则不得不把这年跟 X 相关的代码全部重写.

数据地带为您的网站提供全球顶级IDC资源
在线咨询
专属客服