Go 如何實現 AOP 切面操作?

【導讀】本文介紹了在 go 語言中實現 AOP 操作的實踐。

前言

我們將一個事件處理標記爲 handler, 那麼 AOP 指代的,就是圍繞這個 handler 的【執行前】【執行後】的切面操作,他可以形象地描述爲:

這種執行流程,容易開發成以下樣式:

beforeHandler()
handler()
AfterHandler()

一旦做成這樣子,那麼在同一個 handler 接入不同的切面操作時,便會需要無限侵入代碼,變成:

...
countExecTime()
startRateLimit()
handler
endRateLimit()
endExecTime()
...

這樣子的操作,不只是醜陋,而且隨着 aop 操作增多,維護難度會劇增。

所以到底要如何設計一套可以複用的機制,來優雅使用 AOP 操作。熟悉 golang Web 開發的人都知道,github.com/gin-gonic/gin 裏,可以通過c.Nextc.Abort()的機制,來優雅設計 aop 切面 (中間件)。

那麼,我們能否依照同類 api,來達到類似的效果呢?

答案是,可以的。

合理的 AOP 設計,應該是這樣的

它的執行流爲:
限流 start --> 熔斷 start —> handler —> 熔斷 end --> 限流 end

設計

設計前,考慮到大部分羣體,都對 http gin 的中間件流,十分熟悉。所以我們也基於同樣的方式,來實現 aop 操作。實現以前呢,我們同步一下設計的要求:

中間件樣例:

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

結語

本次實現的包體很輕便,所以直接將實現源碼貼出來了。c.Next c.Abort Use(middleware)的設計原理,和以下倉庫同源:

github.com/gin-gonic/gin
github.com/fwhezfwhez/tcpx
github.com/fwhezfwhez/wsx

最後,如果不想以包體嵌入的形式,來使用 irr,可以直接使用倉庫:

github.com/fwhezfwhez/irr

轉自:

blog.csdn.net/fwhezfwhez/article/details/115471158

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/VkWK5_qX7qT5AhPoPtmH7Q