本文将以技术调研模式编写,非技术同学可跳过。
文章目录
- 背景
- 分层
- 分发+Handle
- 分发+hook
- 分发+并发分层
- 管道
- Channel
- Demo 实现
- 小结
- 附录
背景
基于组件(插件)模式设计构建的入口服务实现中,使用 Go 原生包 plugin 的时候,会存在功能缺陷问题,不足以支撑预期能力;在使用 go-plugin 开源包的时候,虽然功能缺陷得到了弥补,但丧失了部分的性能(组件与主程序通信成本)。
详细见上文 千万级入口服务[Gateway]框架设计(一)
详细见上上文 千万级入口服务[Gateway]框架设计(一)
本文将介绍另一种基于 “分层” 、“管道” 的架构模式。
分层
分层的思路,从你开始学习计算机就见到了。计算机结构 ”冯诺伊曼“ 模型、数据库/Linux 内核结构,等等。可以说 “分层” 是对复杂性问题拆解,化繁为简的主要且有效的思路。
将业务逻辑进行上层抽象,分别划分为几层,层级之间上下级依赖。但需要注意的是,对于业务抽象控制粒度越细,实现成本也就会越大;并且如果业务的调用关系是动态的,抽象层多会导致整体复杂度上升,量级变重;后续业务通信无充分了解抽象分层结构的情况下,功能实现的质量存在不菲的成本,对后续服务的维护、扩展都增加了一层迷雾。
动态的调用关系:出现在复杂业务场景中,即下游的服务存在多个,实际的调用服务由上游服务的响应字段决定,整个路由呈现一种动态关系拓扑。
维护、扩展的迷雾:指由于未充分了解框架设计就进行迭代开发,无法保证交付的功能充分利用框架特征,使得产出质量存在不同程度值得揣测的地方。
下面从不同的划分粒度区分为三种方式。
分发+Handle
在现行的基础上进行粒度放大,故
- 业务实现更自由。
- 框架从业务中剥离,维护成本更低。
在 Web 模式上,把现有流程切分为 router + execute 两部分。
- router
- 进行流量分发至对应的 handle。
- execute
- 不对具体业务处理再做框架层划分,具体设计、实现由业务开发同学实现。
分发+hook
在 handle 的基础上,增加 hook 约束,故
- 约束粒度过小,对于无 hook 需求的场景下,复杂度过高。
在 Web 模式上,把现有流程切分为 router + afterhook\execute\beforehook\assert 的逻辑。
-
router
- 进行流量分发至对应的 handle。
-
afterhook\execute\beforehook\assert
- 在具体业务处理之间,控制粒度大小到,执行前置、执行、执行后置 。
分发+并发分层
需要对 当前业务梳理,重新划分子服务调用次序,分层并发调用,故
- 存在下游子服务路由是动态变更的场景,无法提前预知并分层,需额外设计路由。
- 可将大部分服务调用由串改并行,性能将有所提升。
- 若存在动态路由问题,需要针对此问题做额外的处理,设计、实现的复杂度高。
使用前提是,存在静态子服务拓扑图,将服务调用关系进行动态染色划分为不同的层级,依据层级服务进行并发调用。
- 抽象出 参数构造+响应拼接、服务并发调用 两部分。
管道
管道的思想,如果是传统语言见的可能较少,在 Goland、Ruby 等新式,尤其是语言层面支持并发、面向云原生的设计中比较常见,主打的是对性能的极致追求、高性能下的大数据、大模型 等对数据的流式处理。
注:云原生了解可前往:云原生应用架构的迁移 一 :增量迁移范式
不过不要紧,可以简单的先把中间件的概念移植到管道中来,进而再深入学习。这里基于 Goland 中的 Channel 进行设计。
注:Goland 了解可前往:Go 语言的设计反思
Channel
上述的几个方案和实现中,都是在请求内部的串改并,甚至是抛离了语言层面「在现有的PHP基础上操作即可」。
这里谈论一种基于管道的模式设计,实现服务层级的并行,特别适用于特大并发场景、符合 Go 语言特征。
主协程将负责各个自服务的监听、并发调用,业务协程只作为服务的发起和响应的接收。将不同的请求以子服务维度进行汇聚,并行调用。
这里做方案简单的概述:
- 装配「只需编排服务参数」
- 由线性流程设计转为流水式,高度装配化。
- 提速
- 对单次 pv 中的多个服务串行转换为并行进行。
- 对单次 pv 调用转换为并发进行。
- 扩展
- 服务模块化,多路由调用100%可复用。
- 框架力度较强,对业务屏蔽掉用环节,对基础调用功能实现要求高「功能完备、支持定制配置」。
但需要注意的是,
- 涉及 Go Channel 模型,实现成本较高。
- 需要严谨、科学的兜底、并发死锁等问题/方案的考量。
Demo 实现
package mainimport ("fmt""sync"
)type FInput struct{callback chan FInputparam stringres string
}func main () {fmt.Println("hello https://tool.lu/")c := make(chan FInput,3)defer close(c)var wg sync.WaitGroupwg.Add(3) go F(c,wg) //服务开启监听go A(c,wg) //发起服务请求go B(c,wg) //发起服务请求wg.Wait()fmt.Println("X https://tool.lu/ is finish")
}func F (c chan FInput,wg sync.WaitGroup){fmt.Println("F https://tool.lu/ len:",len(c))for{i, ok := <-cfmt.Println("F https://tool.lu/ ok:",ok)fmt.Println("F https://tool.lu/ param:",i.param)if ok{i.res = i.param + " is executed "i.callback <- i}}fmt.Println("F https://tool.lu/ is finish")wg.Done()
}func A (c chan FInput,wg sync.WaitGroup){cb:=make(chan FInput)defer close(cb)var p FInputp.callback = cbp.param = "A"c<-pi, ok := <-cbif ok{fmt.Println("A https://tool.lu/ res ok:",ok)fmt.Println("A https://tool.lu/ res param:",i.param)fmt.Println("A https://tool.lu/ res value:",i.res)}fmt.Println("A https://tool.lu/ is finish")wg.Done()
}func B (c chan FInput,wg sync.WaitGroup){cb:=make(chan FInput)defer close(cb)var p FInputp.callback = cbp.param = "B"c<-pi, ok := <-cbif ok{fmt.Println("B https://tool.lu/ res ok:",ok)fmt.Println("B https://tool.lu/ res param:",i.param)fmt.Println("B https://tool.lu/ res value:",i.res)}fmt.Println("B https://tool.lu/ is finish")wg.Done()
}
小结
其实对比上述的各种架构模式设计,都是各具特色,优缺兼并。在进行架构选型的时候,想到不同的模式只是第一步。在此前提下,才能更进一步的挑选。
随着 chatGPT 进入大众的视野,”大模型航道“ 变得赤手可热,而且其独特的衍变进程更是一骑绝尘。毫不客气的说,未来的各种应用都将会是更智能、更大模型…
下一章讲介绍 “入口服务” 如何和 ”大模型“ 相结合,设计面向未来的架构模式,敬请期待!
附录
- graphql-go
- Linux 内核网络分层结构