惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
V
V2EX
GbyAI
GbyAI
Recent Announcements
Recent Announcements
Microsoft Security Blog
Microsoft Security Blog
阮一峰的网络日志
阮一峰的网络日志
Hugging Face - Blog
Hugging Face - Blog
T
Tailwind CSS Blog
Y
Y Combinator Blog
C
Check Point Blog
爱范儿
爱范儿
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
美团技术团队
雷峰网
雷峰网
IT之家
IT之家
WordPress大学
WordPress大学
V
Visual Studio Blog
Microsoft Azure Blog
Microsoft Azure Blog
MyScale Blog
MyScale Blog
N
News and Events Feed by Topic
罗磊的独立博客
S
SegmentFault 最新的问题
S
Security Affairs
aimingoo的专栏
aimingoo的专栏
F
Fortinet All Blogs
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
H
Hacker News: Front Page
Google DeepMind News
Google DeepMind News
B
Blog
O
OpenAI News
C
Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
The Last Watchdog
The Last Watchdog
Hacker News: Ask HN
Hacker News: Ask HN
博客园_首页
人人都是产品经理
人人都是产品经理
C
Cybersecurity and Infrastructure Security Agency CISA
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Help Net Security
Help Net Security
月光博客
月光博客
J
Java Code Geeks
L
LangChain Blog
博客园 - 司徒正美
Stack Overflow Blog
Stack Overflow Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
Apple Machine Learning Research
Apple Machine Learning Research
T
The Exploit Database - CXSecurity.com
N
News and Events Feed by Topic
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

Posts on WKLKEN THINKING

apisix 中的 lrucache apisix 中的服务发现机制 apisix 中的负载均衡 apisix etcd机制 聊聊框架 关于 k8s 的 zero downtime deployment 一些建议 apisix 遇到的一些问题 关于在除夕前一天换了一个洗衣机的故事 Django DRF 性能优化 DRF 的一些实践 Part1: Serializer DRF继承关系图 Better Code: 关于接口的灵活性 新的仓库: wklken/naming 缓存使用的一些经验 Better Code: 抽象: 可扩展性与可维护性的抉择 Better Code: 异常时, 该提示用户哪些信息? Better Code: 更好的异常日志打印 Go: some libs Go: go-redis/cache升级的坑 Go: logrus性能提升 Go: gin validation 远程办公的一点总结 Go: 开发过程中的一些bug 项目管理实践: 风险驱动开发 Go: 一种error wrap调用链处理方式 漫谈技术选型 Go: 基于 apitest 做handler层单元测试 Go: go-sql-driver interpolateparams参数优化 [分享]深度工作 你需要更多的思考时间 Django项目重构小结 工作七年小结: 学习,生活及其他 [分享]bash日常: bash-utils 极客时间推广海报 2017总结: 予时光以意义 k8s APIServer源码: api注册详细细节 k8s APIServer源码: api注册主体流程 k8s APIServer源码: 服务启动 k8s APIServer源码: go-restful框架 重构 - 读书笔记(Python示例) 写给新人的沟通建议 vim 杂谈 - 关于快速编辑 vim 杂谈 - 关于移动 读书笔记-重构: 章11 处理概括关系 读书笔记-重构: 章10 简化函数调用 读书笔记-重构: 章9 简化表达式 读书笔记-重构: 章8 重新组织数据 读书笔记-重构: 章7 在对象之间搬移特性 读书笔记-重构: 章6 重新组织函数 Python 代码规范小结 [分享]关于vim ElasticSearch集群部署文档 Logstash+ElasticSearch处理mysql慢查询日志 [分享]关于代码调试DE那些事 Logstash+ElasticSearch+Kibana- 实现相对通用的数据收集分析 ELK维护的一些点(二) [分享]Python源码剖析-数据结构 一些Centos Python生产环境的部署命令 摘录<<6个月学会任何一种外语>> ELK 维护的一些点 也许是一个新的开始 一些vim的个性化配置 读书笔记-调试九法 这段时间的一些想法 Python 源码阅读 - 垃圾回收机制 我为什么要写博客 APUE笔记-第一章 UNIX基础知识 Python源码阅读-闭包的实现 Python源码阅读-内存管理机制(二) Python源码阅读-内存管理机制(一) Python-基础-数据结构小结 '活动'设计的一些trick 一些简单的Python测试题 我的tmux配置及说明【k-tmux】 Review and Restart 工作四周年小结 vim插件: surround & repeat[成对符号编辑] vim插件: gundo[时光机] vim插件: expand-region[区域选中] vim插件: quickrun[快速执行] vim插件: trailing-whitespace[行尾空格处理] vim插件: closetag[成对标签补全] vim插件: ctrlp[文件搜索] vim插件: airline[状态栏增强] vim插件: theme[主题] vim插件: tagbar[大纲式导航] vim插件: nerdcommenter[快速注释] vim插件: rainbow_parentheses[括号高亮] vim插件: syntastic[语法检查] vim插件: delimitmate[符号自动补全] vim插件: matchit[成对标签跳转] vim插件: easy-align[快速对齐] vim插件: multiple-cursors[多光标操作] vim插件: vim-signature[快速标记跳转] vim插件: easymotion[快速跳转] vim插件: vundle[管理插件] Elasticsearch几个问题的解决 分享一份 Vim 简介PPT k-vim 更新9.0版本 关于知识管理工具的思考
聊聊项目架构调整
2024-06-16 · via Posts on WKLKEN THINKING

过度设计的反面是务实

去年接手了一套系统,花了近三个月做了一些调整才正式外发。

当时这套系统是为了满足一个时间点快速迭代发布的,接手后觉得其中有一些问题,聊聊其中做的一些决策。

模块存在的合理性

case1

A 项目和 B 项目都是 Django+DRF 提供的服务。

仔细阅读代码后发现

  1. B 项目逻辑并不复杂,但是麻雀虽小五脏俱全,DB、缓存、配置、helm-charts,出包流水线等都得来一套
  2. 存在一些共同的逻辑,抽象出一个 SDK 来实现逻辑复用
  3. A 项目需要调用 B 项目,此时封装一层独立的调用逻辑(B 项目加一个接口,需要同步改这里)
  4. 为同一套前端提供的服务(通过k8s svc将请求转发到不同项目)

同构的技术栈,强耦合的逻辑,却是两个独立的项目。

能否合并成一个项目?答案是可以。

花了几个小时,将 B 项目的差异化代码直接拷贝到 A 项目,去掉 SDK,独立调用层改成直接函数调用,去掉对应的helm-charts,出包流水线等等。

结果:

  • 项目 -1
  • SDK -1
  • 流水线 -1
  • helm-chart -1
  • A 项目中调用逻辑 -1

整体的维护成本,复杂度等都降低了。

一个服务是否拆分成多个,以及多个服务是否应该合并成一个,取决于很多因素,不一定说拆分一定不好,但是拆分带来的维护成本往往是大于单体的。

case2

A 项目启动时,会从一个静态文件服务器 B 拉取一些运行时文件,B 是可以动态更新这些文件的。这样做的好处,是为了方便用户侧动态更新一些文件,直接 rolling update A 就能使得线上接口刷新成新的。

当然,这样做时候代价的,需要维护服务 B,同时 A 需要嵌入拉取逻辑。

问,这个机制目前有使用场景吗?没有,但是可能有。

这个就有点臆测未来用户侧的使用场景了,这种更应该有真实用户反馈过来再去加上,而不是一开始就加上。

所以,选择了去掉 静态文件服务器 B, 直接在 A 项目打镜像时将运行时依赖文件打进去,相当于强耦合。我们甚至留了口子,如果用户侧需要更新,基于 A 镜像再打一个镜像,问题是上线一年多一次也没遇到。

结果:

  • 项目 -1
  • A 项目中的拉取更新逻辑 -1

case3

重构时,A 项目使用了最新的实现方案,去替代 B 项目,二者语言和技术栈完全不一样。

B 项目中有个频率控制逻辑,使用的漏桶算法,而 A 的生态中频率控制算法只有fixed window,由于觉得漏桶算法效果更好,并且时间很赶并没有多余的精力在 A 中实现漏桶算法。所以将 B 中的漏桶算法直接挪出来独立成一个模块 C,然后 A 项目实现对应逻辑依赖模块 C。

相当于异构的实现,通过 RPC 协议交互。

但是这样引入了额外的模块和复杂度。

那么, fixed window频率控制算法在实际用户使用中真的会有问题吗?是的,会有问题,但是最终的频率控制效果用户能接受吗?

捞了线上正在运行的 B 项目的频率控制触发的日志,发现实际上用户侧配置的值一般都比较高,并且很少有触发的频率控制的调用方。

最终,我们决定去掉这个模块 C,直接使用 A 生态中基于fixed window的频率控制。

上线后只出现了一两例触发极端情况的场景,但是这个也给我们在 A 项目中实现漏桶算法提供了充足的缓冲时间。

如果效果差不多,或者稍微差一点但是能接受,那么不一定需要引入一个新模块来解决问题。

模块实现的合理性

case1

A 项目需要用到外部鉴权,依赖于外部的权限数据。此时引入一个模块 B,B 使用 A 生态的多语言框架实现(不同的语言),内部通过 RPC 交互。B 通过 HTTP 调用Web做全量以及增量的权限数据同步。

相当于: A => RPC => B => HTTP => Web Server

由于这个全量、增量同步做得很复杂,并且做了一层cache,导致线上出现权限问题时并不好排查。并且 Web Server 是一个产品项目,发布频率高并且稳定性低于 A。

那么为什么使用多语言框架呢?可能刚好官方支持吧(坑比较多),这时候有点像是拿着锤子满世界都是钉子,往里面放也行。

感觉掉坑里了,能否去掉 B 模块呢?

最终经过评审,我们的实现非常简单A => HTTP => Core Server => DB,不存在全量、增量数据同步,直接依赖 DB,加了一层缓存,整体复杂度非常低。

有时候生态里面一些亮点、强力的 feature,并不一定是好事,平平无奇的机制能实现的话,不一定要用。

case2

A 项目生态中有一个白名单, 历史 B 项目中也有个白名单逻辑,然后在从 B 项目升级到 A 项目的过程中, 竟然在 A 项目也实现了 B 项目的白名单逻辑,即同时存在两个白名单逻辑。

两个白名单逻辑本质上是一致的。

决策:升级过程中做简单的数据迁移,只留一个白名单逻辑。

不合理的依赖

case1 反向依赖

上面模块合理性的case, 底层对可用性要求更高的模块,依赖了上层可用性不那么高的Web Server模块,直接拉低了整体的可用性。

通过新起一个更底层的服务,只依赖于 DB,独立于Web Server,将之前的依赖链路抹掉,变成可靠性更高的链路。

case2 循环依赖

A 项目是一个业务网关,依赖了一个认证项目 B,实施的时候,发现这个 B 的地址竟然是 A 项目的子路径。

原来是因为这个认证逻辑本身没有流水日志用于问题排查,所以将 B 项目接入了 A 项目网管。

此时即循环依赖 A 的认证逻辑依赖了 A 自己的网关地址;

后来决策,依赖地址直接改成B 服务,流水日志 B 项目自行解决。

其他相关的

过于灵活的配置

项目的配置文件生成:原始helm chart中的template有一个configmap, 并且有一批环境变量,在启动的shell脚本中,读取环境变量,并且用 envset、sed 等命令,将配置文件和镜像中的配置文件合并生成一个新的文件。 相当于,配置有 3 个来源,configmap/镜像中的yaml/环境变量中的配置。

这样看起来非常灵活,一系列判断逻辑可以根据一些开关、环境变量,生成差异化的配置,然后启动;

但是带来的问题是,在 shell 脚本中维护这个逻辑可维护性无疑是非常差的,并且认知负担非常重。

后来做的决策是,去掉启动脚本中的逻辑,在configmap中由 helm 的语法、模板负责生成一个完备的配置,即所见即所得

这样所有的差异化在模板阶段就处理掉了,而不是在运行时处理。

有时候,灵活的配置,强大的脚本,看起来很厉害,但是并不一定是好事,特别是来源多,条件复杂这种场景。这时候,唯一来源,并且看起来有些的方式,反而是最好维护的。

过于冗长的流水线

存在不一定合理

由于之前处于快速迭代期,交付期间出现过各个环节的问题,导致出包的流水线非常长,几乎要二三十分钟才能出一个包,一旦有问题重出又是二三十分钟。

流水线中很多环节,是为了 cover 掉之前快速迭代容易遗漏的一些点,例如依赖包遗漏不熟检查,例如打包后执行一次helm install部署测试。 这些环节耗时都比较长,并且是同步的。

但是,当过来快速迭代期之后,依赖变更已经不频繁,并且出现了其他环节能够cover掉出包过程中的早期测试,那么原先有效的环节可能变得非常鸡肋

不删只是慢了点,存在即合理,会导致很多时候不敢去改流水线,去掉这些鸡肋。

后来复盘,统一删除,将时间压缩到 5 分钟以内。

过多的测试用例、测试环境及流程

使用 A 项目替换掉之前的 B 项目,但是,没有重新去梳理 case,而是在 B 项目的 case 基础上又加了一批 case;并且也没有重新做差异化的梳理,导致出现了 4 套测试环境+300多个case;

每个环境的 case 都是提前造好的,接手后,正式发布前需要把 4 个环境升级上来,并且执行完所有 case,有失败的需要逐一去排查原因。

这样带来的问题是,出包后的测试成本也非常高,并且 4 套环境的维护,case 的更新代价也很高。

你不跑,怕有问题没覆盖到,去跑,又得更新四套环境逐一验证,没过的 case 还得逐一确认修复。

后来,我们决定进行测试左移,到开发阶段而不是出包后,重新梳理 case,变成原先三分之一,并且能够在任意环境,自动构建环境,执行覆盖所有case。