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

模块化golang工程_golang 模块化

ruisui884个月前 (02-22)技术分析14

为什么要模块化

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

  • 事务比较复杂
  • 希望内存占用尽量低(比如客户就给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 模块化” 的相关文章

深入理解Vue.js组件通信:父子组件与子父组件数据交互详解

什么是Vue组件通讯 Vue.js 组件通信是指在 Vue 应用的不同组件之间进行数据交换和状态同步的过程。由于 Vue 的组件是基于单文件组件(SFCs)的模块化设计,每个组件都有自己的作用域,因此它们不能直接访问彼此的数据。为了使组件之间能够协同工作,Vue 提供了几种不同的通信方式。以下是 V...

Git 分支管理策略与工作流程

(预警:因为详细,所以行文有些长,新手边看边操作效果出乎你的预料)团队开发中,遵循一个合理、清晰的Git使用流程,是非常重要的。否则,每个人都提交一堆杂乱无章的commit,项目很快就会变得难以协调和维护。看完这篇文章后,涉及GIT的工作中就会减少因为规范问题导致工作出错,当然如果你现在暂时还未有合...

Gitlab之间进行同步备份

目前,我们公司有两个研发团队,分别在北京和武汉,考虑到访问速度的问题,原有武汉的研发环境在近端部署。也就是北京和武汉分别有两套独立的研发管理环境,虽然这解决了近端访问速度的问题,但是管理上较为分散,比如研发环境备份和恢复就是最重要的问题之一。最近,处于对安全性和合规性的考虑,希望将北京和武汉的源代码...

双子座应用程序推出模型切换器以在Android上访问2.0

#头条精品计划# 快速导读谷歌推出了Gemini 2.0 Flash实验版,现已在其安卓应用中可用,之前仅在gemini.google.com网站上提供。新版本的15.50包含模型切换器,用户可以在设置中选择不同模型,包括1.5 Pro、1.5 Flash和2.0 Flash实验版。谷歌提醒,2.0...

多项修正 尼康D4s发布最新1.10版固件

尼康公司与2014年8月27日发布了D4s的最新固件,固件版本号为C:1.10。这次固件升级,主要解决了一些BUG,并且对拍摄菜单与相机操作做了一定调整。下面是本次新固件的具体信息:尼康发布D4s最新C固件 1.10版对C固件升级到1.10版所作的修改:当选定运动VR模式并换上 AF-S 尼克尔 4...

JS数组过滤元素的方法

引言JavaScript 作为前端开发的核心技术之一,在现代 Web 开发中扮演着举足轻重的角色。随着 Web 应用越来越复杂,高效处理数据集合的需求日益凸显。本文旨在介绍 JavaScript 中数组过滤的基础知识及其在实际项目中的应用技巧。技术概述定义数组过滤是 JavaScript 提供的一种...