我们将一个事件处理标记为handler, 那么AOP指代的,就是围绕这个handler的【执行前】【执行后】的切面 *** 作,他可以形象地描述为:
这种执行流程,容易开发成以下样式:
beforeHandler()
handler()
AfterHandler()
一旦做成这样子,那么在同一个handler接入不同的切面 *** 作时,便会需要无限侵入代码,变成:
...
countExecTime()
startRateLimit()
handler
endRateLimit()
endExecTime()
...
这样子的 *** 作,不只是丑陋,而且随着aop *** 作增多,维护难度会剧增。
所以到底要如何设计一套可以复用的机制,来优雅使用AOP *** 作。熟悉golang Web开发的人都知道,github.com/gin-gonic/gin里,可以通过c.Next
和 c.Abort()
的机制,来优雅设计aop切面(中间件)。
那么,我们能否依照同类api,来达到类似的效果呢?
答案是,可以的。
合理的AOP设计,应该是这样的
它的执行流为:
限流start --> 熔断start —> handler —>熔断end --> 限流end
设计前,考虑到大部分群体,都对http gin的中间件流,十分熟悉。所以我们也基于同样的方式,来实现aop *** 作。实现以前呢,我们同步一下设计的要求:
需要支持,对任意方法执行切面 *** 作。需要和gin共享相似度极高的api。支持所有框架场景。实现包体简单。支持对【全局】【单个路由】两个维度支持中间件中间件样例:
func AlertTimeout(c *irr.Context) {
// c.Abort() 阻断
// c.Next() 放行
}
使用时
wrapF := irr.WrapFunc(func(){
// 原本的handler逻辑
})
// 流程超时报警
wrapF.Use(AlertTimeout)
// 流程熔断报警
wrapF.Use(AlertFuse)
wrapF.Handle()
实现
目录层级
irr
| - context.go
| - core.go
context.go
package irr
import (
"math"
)
const ABORT = math.MaxInt32 - 10000
type Context struct {
offset int
handlers []func(*Context)
}
func newContext() *Context {
return &Context{
offset: -1,
handlers: make([]func(*Context), 0, 10),
}
}
func (ctx *Context) Next() {
ctx.offset ++
s := len(ctx.handlers)
for ; ctx.offset < s; ctx.offset++ {
if !ctx.isAbort() {
ctx.handlers[ctx.offset](ctx)
} else {
return
}
}
}
func (ctx *Context) Reset() {
//ctx.PerRequestContext = &sync.Map{}
ctx.offset = -1
ctx.handlers = ctx.handlers[:0]
}
// stop middleware chain
func (ctx *Context) Abort() {
ctx.offset = math.MaxInt32 - 10000
}
func (ctx *Context) isAbort() bool {
if ctx.offset >= ABORT {
return true
}
return false
}
func (ctx *Context) addHandler(f func(ctx *Context)) {
ctx.handlers = append(ctx.handlers, f)
}
core.go
package irr
type WrapF struct {
f func()
ctx *Context
}
func WrapFunc(f func()) *WrapF {
return &WrapF{
f: f,
ctx: newContext(),
}
}
func (wf *WrapF) Use(f func(c *Context)) {
wf.ctx.addHandler(f)
}
func (wf *WrapF) Handle() {
wf.ctx.handlers = append(wf.ctx.handlers, func(c *Context) {
wf.f()
})
if len(wf.ctx.handlers) > 0 {
wf.ctx.Next()
}
wf.ctx.Reset()
}
使用
1. 对不支持中间件的ws/tcp框架接入中间件
假设你的tcp/websocket框架长这样
for {
conn := l.Accept()
go func(conn net.Conn){
for {
packet, _ = readAPackFrom(conn)
go handle(packet, conn)
}
}(conn)
}
接入aop后, 全局中间件:
for {
conn := l.Accept()
go func(conn net.Conn){
for {
packet, _ = readAPackFrom(conn)
go func(){
wrapF := irr.WrapFunc(func(){
handle(packet, conn)
})
wrapF.Use(AlertTimeout)
wrapF.Use(Sentinel)
wrapF.Handle()
}()
}
}(conn)
}
路由级:
func handlePacket(packet []byte, conn) {
wrapF := irr.WrapFunc(func(){
// 路由逻辑
handle(packet, conn)
})
// 熔断
wrapF.Use(Fuse("app:get_user_info", 20,10))
wrapF.Use(LimitRate(50,1))
wrapF.Handle()
}
2.接入gin
复制一份func (c *irr.Context)
,将irr改成gin,即可直接投入gin使用。
结语
通过wrap任意函数 func(){}
达到了对任意方法切面封装。使用方法和gin完全一致。对不支持中间件的框架,可以快速接入。实现包体简单,累计不到40行。支持对【全局】【单个路由】两个维度支持中间件
本次实现的包体很轻便,所以直接将实现源码贴出来了。c.Next
c.Abort
Use(middleware)
的设计原理,和以下仓库同源:
github.com/gin-gonic/gin
github.com/fwhezfwhez/tcpx
github.com/fwhezfwhez/wsx
最后,如果不想以包体嵌入的形式,来使用irr,可以直接使用仓库:
github.com/fwhezfwhez/irr
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)