当前位置:首页 > 技术分析 > 正文内容

模块化golang工程_golang 模块化

ruisui882个月前 (02-22)技术分析8

为什么要模块化

微服务本身是比较彻底的模块化方案,但是在部分场景下微服务不是最好的方案,例如:

  • 事务比较复杂
  • 希望内存占用尽量低(比如客户就给1c2g)
  • 数据库连接数量已经过大,不希望引入中间件去解决连接数问题
  • 运维能力不足
  • 开源项目,希望便于部署

在这些条件下,单服务+模块化变成了最好的方案。

模块化方式对比

作为云原生的默认语言,微服务化似乎应该是与生俱来的能力。相比其他语言,golang 的模块化方案被较少的讨论。

对于模块的需求,我们大致上有: - 可以比较方便的载入,最好能是运行时的 - 可以卸载 - 可以有能力去在某次业务逻辑中启用和禁用

golang官方实际有模块化方案:go plugin。有如下特点:

  • - 1.8版本引入
  • - 模块被打包为 so 文件,用代码动态加载
  • - 只能加载,不能卸载
  • - 主程序与plugin的共同依赖包的版本必须一致
  • - 如果采用mod=vendor构建,那么主程序和plugin必须基于同一个vendor目录构建
  • - 主程序与plugin使用的编译器版本必须一致

这些条件中,在常见的业务开发中,最难做到的就是依赖版本一致 假设主程序与模块都使用了内部 log库v1,现在 v1 提供了升级 v1.1,某模块需要这个升级。也就意味着主程序和所有的模块必须一起升级才能解决这个问题。如果有几百个模块呢。。。

因此 go plugin 这么久了也很少有人使用这样的方案去做模块化,有兴趣的可以看这个例子:
https://github.com/pingcap/tidb/blob/master/docs/design/2018-12-10-plugin-framework.md

不用 go plugin,还有什么其他方案吗?


github.com/hashicorp/go-plugin 使用了“假微服务”方案,通过子主进程进行模块化启动,通信使用 go 喜闻乐见的 grpc。这个方案有效的解决了运行时、卸载、依赖版本的问题。 但是他引入了新的问题:性能。 假设一个调用需要调用其他 1 个模块的方法,传了指针进去。在 go 层面只占用了一块内存,经由 grpc 传输后,内存占用变成了 3 块,也就是说一个调用内存就要翻 3 倍。普通业务开发其实到也影响不大,在涉及大流量网络开发中,这个量级可能太吓人了。

编辑

image


对性能要求比较高,或需要内存占用比较低的场景中,这个方案不太行。如果要使用 rpc,为什么不真的使用微服务呢,毕竟微服务体系下是另一套完整的生态了。监控、链路、编排等各类需求都有完整便捷的解决方案。

如果用 go mod 作为模块化方案,优势有:

  • - 完全支持多版本
  • - 没有学习成本
  • - 没有额外的性能开销

相对的,缺点有:

  • - 不可能运行时载入
  • - 不能卸载
  • - 编译的时候不能做单元测试
  • - 分支管理不能按照普通的方式来进行
  • - 配置管理需要升级(普通的配置方式对模块化不够灵活)
  • - 不能支持 swagger

go mod模块化实现细节

模块化方案是微服务的前奏,如果模块化成功,起码有了向微服务发展的基础。 从这个角度出发,模块化方案就必须满足一些要求:

  • - 依赖 interface 而不是指针
  • - 不使用导出变量的方式使用单例模式,尽量控制模块权限
  • - 每个模块的配置应该是独立的
  • - 尽管不要求运行时载入和卸载,起码载入和卸载应该很方便

因此,模块化方案在 go mod 的基础上,还应该有:

  • - 全面 ioc 的使用和支持,这样才能够依赖 interface,尽量控制模块权限
  • - 配置覆盖或组合能力,每个模块有独立的配置,同时可被其上层模块的配置覆盖,或提供配置组合能力。

模块的设计

模块的设计和微服务的划分可以对应,甚至可以更细粒度,毕竟还是单服务,没有微服务的劣势。 常见的,按照流程/对象或者抽象/实现的方式去划分,可以解决大部分的问题。

什么是好的模块(基本和微服务重叠):

  • - 可管理(动态装载和卸载,golang做不到)
  • - 原生可重用
  • - 可组合
  • - 无状态

一个模块设计的例子

需求:支付系统。

根据商品的不同,总价会有不同的折扣。但是折扣的计算比较复杂,会根据商品的不同属性计算不同折扣。例如商品上架时间、库存、供应商、批次等

第一次设计

有一个独立的服务,支付服务,来处理所有的需求。其会拉取商品服务的数据进行逻辑判断。

第一次模块化重构

抽取其中的商品拉取部分、数据库存储部分进行独立模块。现在逻辑部分在上,商品拉取、存储部分在下。

遵循一个原则:上层模块依赖下层,反之则不行

现在有2个模块:商品拉取、数据库存储

第二次模块化重构

逻辑部分在直接调用商品拉取,抽象其中部分为interface,商品拉取模块实现了这个interfcae。 同样,商品折扣的计算也独立出了一个模块,同样也实现了一个抽象的interface

遵循一个原则:只能依赖抽象而不是实现,实现依赖了本来应该依赖它的业务,依赖倒置了。

现在有3个模块:商品折扣计算、商品拉取、数据库存储

第三次模块化重构

折扣的计算包含多种多样,且可能随时增加或变更的逻辑。把它们全部分开成不同的实现,实现了相同的interface。用同一个管理器进行管理

遵循一个原则:适配器模式的使用,带来扩展的便利性

现在有3+n个模块:n个商品折扣计算、折扣计算管理、商品拉取、数据库存储

第四次模块化重构

支付的流程也成为了一个独立的模块,调用了一个通用的interface。折扣计算管理器实现了这个interface,因此将来不仅可以计算折扣,还通过扩展其他模块

遵循一个原则:main里面只剩下了di框架的初始化和配置,代码不超过20行了

现在有4+n个模块:n个商品折扣计算、支付模块、折扣计算管理、商品拉取、数据库存储

结果

经过4次重构后,系统从以前的一坨变成了一堆,也没有进行微服务拆分,但是谁都能看出来他的可维护性已经增加了太多。





更多精彩内容


纯异步事件驱动构建的微服务体系

我们为什么又又又又需要一个新的mq了

小团队的实用云原生指南


研发团队新鲜事儿,来公众号「迷路idea」找我一起探讨

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/2172.html

标签: go swagger
分享给朋友:

“模块化golang工程_golang 模块化” 的相关文章

「Git迁移」三行命令迁移Git包含提交历史,分支,tag标签等信息

问题描述:公司需要将一个git远程服务器的全部已有项目迁移到一台新服务器的Gitlab中,其中需要包含全部的提交纪录,已有的全部分支与全部打tag标签,目前此工作已全部迁移完毕,特此记录一下操作步骤环境描述:1. 要迁移的远程Git:Gitblit2. 迁移目的Git:Gitlab3. 暂存代码的P...

K8s里我的容器到底用了多少内存?

作者:frostchen导语 Linux下开发者习惯在物理机或者虚拟机环境下使用top和free等命令查看机器和进程的内存使用量,近年来越来越多的应用服务完成了微服务容器化改造,过去查看、监控和定位内存使用量的方法似乎时常不太奏效。如果你的应用程序刚刚迁移到K8s中,经常被诸如以下问题所困扰:容器的...

高效使用 Vim 编辑器的 10 个技巧

在 Reverb,我们使用 MacVim 来标准化开发环境,使配对更容易,并提高效率。当我开始使用 Reverb 时,我以前从未使用过 Vim。我花了几个星期才开始感到舒服,但如果没有这样的提示,可能需要几个月的时间。这里有十个技巧可以帮助你在学习使用 Vim 时提高效率。1. 通过提高按键重复率来...

12种JavaScript中最常用的数组操作整理汇总

数组是最常见的数据结构之一,我们需要绝对自信地使用它。在这里,我将列出 JavaScript 中最重要的几个数组常用操作片段,包括数组长度、替换元素、去重以及许多其他内容。1、数组长度大多数人都知道可以像这样得到数组的长度:const arr = [1, 2, 3]; console.log(a...

三勾点餐系统java+springboot+vue3,开源系统小程序点餐系统

项目简述前台实现:用户浏览菜单、菜品分类筛选、查看菜品详情、菜品多属性、菜品加料、添加购物车、购物车结算、个人订单查询、门店自提、外卖配送、菜品打包等。后台实现:菜品管理、订单管理、会员管理、系统管理、权限管理等。 项目介绍三勾点餐系统基于java+springboot+element-plus+u...

详解编程中的同步和异步

本文主要总结一些自己对异步的理解,话不多说 下面开始。一. 单线程 我们常说“JavaScript是单线程的”,所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。不妨叫它主线程 但是实际上还存在其他的线程。例如:处理AJAX请求的线程、处理DOM事件的线程、定时器线程...