19 Go Web 框架(二):框架技术详解
一、 net/http包够用吗?
Go的net相关标准包提供web开发的大多数实现支持,如果你的项目的路径端点在十个以内,如一个简单的企业网站,这当然是够用的。但如果你的项目是构建一个大型电商网站,有成千上万个页面的路径端点,如下:
GET /goods/:id
POST /order/:id
DELTE /goods/:id
GET /goods/:id/name
...
GET /goods/:id/recommend
在这种情况下,如果只是用net/http包构建,那你不仅要手写url参数解析器,且单单写handler函数就足以让你奔溃。所以对于大多数中大型项目而言,使用框架技术开发是明智之举。
根据经验,只要你的路由带有参数,并且这个项目的API数目超过了10,就尽量不要使用net/http中默认的路由。在Go开源界应用最广泛的router是httpRouter,很多开源的router框架都是基于httpRouter进行一定程度的改造的成果。
二、Web框架技术
现代Web技术大多基于HTTP协议构建,所谓Web框架技术,其本质是在框架底层对HTTP进行有设计的再度封装,最基本的为对Request和对Response处理的封装,除此也包括路由调度,请求参数校验,模板引擎等。再附加一些加速开发和模块管理的套件,如:中间件、MVC分层设计、数据库IO处理等开发套件。使用框架的目的在于屏蔽一些通用的底层编码,封装对用户友好的调用方法,让你把精力集中在业务模块的开发。当然这种封装有利有弊。利在于你不用重复造轮子,有专业的框架团队帮你维护比较规范和安全的底层逻辑,因为框架规范,团队开发中也比较易于沟通;弊在于框架的通用设计有可能不适合你的产品特性,且如果你不深入研究框架底层实现,有可能在项目开发过程中出现一些不可控的bug,这意味着如果你使用一种框架,团队中必须有深入理解该框架的成员,才能让项目开发的进展在可控的范围内。
1. 框架类型:
开源社区有许许多多不同类型的web框架,帮助用户实现快速上手开发,根据不同的项目需求,你可能需要构建API,有可能构建web页面服务,也有可能构建分布式的微服务,这就区分出许多框架类型,以下为框架类型的划分及热度较高的开源项目:
- Router型框架
- Ace: 快速开发的Web框架。
- api2go: Go的JSON API实现。
- Gin: 一个微框架,类似Martinier的API,重点是小巧、易用、性能好很多,也因为 httprouter 的性能提高了40倍。
- Goat: Go中的简约REST API服务器
- goMiddlewareChain: 一个像express.js的中间件调用链框架。
- Hikaru: 支持独立部署和谷歌AppEngine
- Hitch: 将 httprouter, httpcontext, 和中间件 捆绑在一起。
- httpway: 具有httprouter上下文的简单中间件扩展和具有正常关闭支持的服务框架
- kami: 使用 x/net/context的小型Web框架。
- Medeina: 灵感来自Ruby的 Roda 和Cuba。
- Neko: 一个轻量级Web应用程序框架。
- River: 一个简单轻巧的REST服务框架。
- Roxanna:httprouter的合并,更好的日志记录和热重载。
- siesta:具有上下文的可组合HTTP处理程序。
- xmux: xmux is a httprouter fork on top of xhandler (net/context aware) aware)
基于httpRouter进行简单的封装,然后提供定制的中间件和一些简单的小工具集成比如gin,主打轻量,易学,高性能。
- 迁移型框架
- Beego:一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服务等各种应用,是一个 RESTful 的框架,主要设计灵感来源于 tornado、sinatra 和 flask 这三个框架,但是结合了 Go 本身的一些特性(interface、struct 嵌入等)而设计的一个框架。beego 是基于八大独立的模块构建的,是一个高度解耦的框架。设计之初就考虑功能模块化,用户即使不使用 beego 的 HTTP 逻辑,也依旧可以使用这些独立模块。
- Iris:Iris的理念是为HTTP提供强大的工具,使其成为单页面应用程序,网站,混合或公共HTTP API的理想解决方案。
借鉴其它语言的编程风格,使用MVC开发模式的框架,例如beego、Iris-go等,这种框架方便从其它语言迁移过来的程序员快速上手,快速开发,很多PHP、JAVA程序员对这种框架更容易上手。
- 代码生成型框架
- Goa:Goa采用不同的方法来构建服务,使用简单的Go DSL 来描述服务API 的设计。Goa使用描述生成专门的服务帮助程序代码,客户端代码和文档。Goa可以通过插件扩展,例如 goakit插件生成利用Go套件库的代码。
使用GO DSL描述来生成服务或API代码,支持数据库schema设计,插件设计,大部分代码可直接生成。goa就是这种类型的框架,它着重于构建微服务和API的系统。如果你玩过Yii2或Laravel,你会感觉这种框架开发的套路很熟悉。
- 微服务型框架
- Goa:以上。
- go-micro:Go Micro提供了分布式系统开发的核心要求,包括RPC、事件驱动的异步通信、消息编解码、基于服务发现的负载均衡以及可插拔的插件机制。
- go-kit:Go kit是一个编程工具包,用于在Go中构建微服务(或优雅的整体)。解决了分布式系统和应用程序架构中的常见问题,因此你可以专注于开发业务。
微服务架构在近年来越来越盛行,在容器化服务的时代,微服务化的应用也代表着未来,go在微服务的开发方面也有得天独厚的优势,这类框架有go-micro、go-kit等,其中go-micro更像是构建微服务的框架,而go-kit更像是微服务开发的工具集。
2. 框架设计解析
Go在2009年才诞生,迄今也就十年历史,在框架技术方面很多都是借鉴于其他语言的框架设计经验,所以在很多使用框架开发的项目中beego很流行,主要是因为人们更熟悉MVC这种开发套路,但如果你使用beego开发过你会发现有些许别扭,相较于MVC的框架,许多Gopher更喜欢使用轻量级的Router型框架构建REST API,或使用微服务框架搭建运行于容器的应用。或许它更符合Go Web开发的设计理念。至于如何选择框架,这得考虑产品的特性和团队的成员技术栈了,没有唯一答案,适合的才是最好的。
下面我们来解析一下大多数框架设计的共同特性吧:
2.1 HTTP封装
Web系统的本质就是对网络通信协议的应用设计,大体上基于HTTP协议进行应用的封装,如许多RESTful风格的API框架,就是完全贴合HTTP协议而设计的。HTTP的一次交互可抽象为Request(请求)和Response(响应),在上一篇中我们了解了Request和Response的具体结构和信息,Request包含八种请求Method来表明客户对资源的操作方式,并赋予请求头、请求参数等信息(在此抽象概述先忽略其它请求报文的处理),而Response则包含服务端在接收到用户请求并进行业务处理后的响应信息,如响应头、状态码、响应体等(在此抽象概述先忽略其他响应报文内容)。这是Web服务端和客户端交互的简单抽象。go的标准库net/http包已实现这些基本的处理,大多web框架根据各自设计的特性进行Request和Response的深度封装,使其对用户开发更友好,或增强其他处理的特性等。
一般框架会对Request和Response进行接口设计或直接设计为结构体实现,在服务中的Handler处理器进行传递,这是net/http包原生的处理方法,但多数框架会把Request和Response以及其他的附加结构封装到一个统一的Context接口或结构体,在服务中的Handler处理器进行传递,如gin:
type Context struct {writermem responseWriterRequest *http.RequestWriter ResponseWriterParams Paramshandlers HandlersChainindex int8engine *Engine// Keys is a key/value pair exclusively for the context of each request.Keys map[string]interface{}// Errors is a list of errors attached to all the handlers/middlewares who used this context.Errors errorMsgs// Accepted defines a list of manually accepted formats for content negotiation.Accepted []string
}
像你以上看到的,许多应用框架会把一次HTTP的交互中的所有要素统一抽象到一个上下文Context中,由路由调度指派的Handler处理器接收处理,于是你会看到gin框架的做法,也为多数web框架的做法:
package mainimport "github.com/gin-gonic/gin"func main() {r := gin.Default()//将访问路径/ping的请求的处理指派到一个handler匿名函数,传递的是*gin.Context。r.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong",})})r.Run() // listen and serve on 0.0.0.0:8080
}
2.2 路由设计
在Web交互中,客户端都是通过URL(统一资源定位符)对服务端资源进行HTTP 请求的,一个URL包含资源的请求路径,该路径应由系统路由(router)调度到请求处理器。在常见的Web框架中,router是必备的组件。Go语言圈子里router也时常被称为http的multiplexer。
net/http包内置的ServeMux来完成简单的路由功能,如果开发Web系统对路径中没有带参数要求的话,用net/http标准库中的mux就可以了,但现实并没那么简单,一个稍微复杂的项目都有路径划分及携带参数的要求,特别是RESTful风格的API设计,这就需要对请求路径的URL进行路由调度设计。
在RESTful中除了GET和POST之外,还使用了HTTP协议定义的几种其它的标准化语义。具体包括:
const (MethodGet = "GET"MethodHead = "HEAD"MethodPost = "POST"MethodPut = "PUT"MethodPatch = "PATCH" // RFC 5789MethodDelete = "DELETE"MethodConnect = "CONNECT"MethodOptions = "OPTIONS"MethodTrace = "TRACE"
)
来看看RESTful中常见的请求路径:
GET /repos/:owner/:repo/comments/:id/reactions
POST /projects/:project_id/columns
PUT /user/starred/:owner/:repo
DELETE /user/starred/:owner/:repo
这是常见的几个RESTful API设计。RESTful风格的API重度依赖请求路径。会将很多参数放在请求URI中。除此之外还会使用很多并不那么常见的HTTP状态码。
如果我们的系统也想要这样的URI设计,使用http包的ServeMux显然不够。
较流行的开源go Web框架大多使用httprouter,或是基于httprouter的变种对路由进行支持。前面提到的github的参数式路由在httprouter中都是可以支持的。
因为httprouter中使用的是显式匹配,所以在设计路由的时候需要规避一些会导致路由冲突的情况,例如:
//conflict:
GET /user/info/:name
GET /user/:id//no conflict:
GET /user/info/:name
POST /user/:id
简单来讲的话,如果两个路由拥有一致的http方法(指 GET/POST/PUT/DELETE)和请求路径前缀,且在某个位置出现了A路由是wildcard(指:id这种形式)参数,B路由则是普通字符串,那么就会发生路由冲突。路由冲突会在初始化阶段直接panic。
除了正常情况下的路由支持,httprouter也支持对一些特殊情况下的回调函数进行定制,例如404的时候:
r := httprouter.New()
r.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("oh no, not found"))
})
或者内部panic的时候:r.PanicHandler = func(w http.ResponseWriter, r *http.Request, c interface{}) {log.Printf("Recovering from panic, Reason: %#v", c.(error))w.WriteHeader(http.StatusInternalServerError)w.Write([]byte(c.(error).Error()))
}
目前开源界较流行的Web框架gin使用的就是httprouter的变种。
2.3 中间件
我们来看看现实开发的场景,假如你正在编写一处产品提出的业务需求,和往常一样,你使用一个路径绑定一个Handler处理器实现业务编码,而正在这时,你的开发主管为了统计每个业务中的耗时任务,要求开发成员在业务代码中插入耗时计算的逻辑,你照做了;再过一段时间,你的开发主管为了收集用户的行为数据,要求开发成员在用户的业务逻辑上安插日志记录,以供后期离线计算,你也照做了;后来随着业务不断扩大,系统业务衍生出了“大会员”子系统,需要对符合要求的用户进行鉴权并过滤,要求开发人员在每个业务处理做权限控制,这时你崩溃了,系统已经随着业务发展变得越来越复杂,而且掺杂了各种业务和非业务的逻辑代码,你还继续一个一个改吗?很显然你已经陷入代码泥潭!
所谓中间件,是在实际处理用户业务逻辑的生命周期中,在对应的节点安插逻辑处理,可以在核心业务逻辑前或后,是剥离非业务逻辑的重要系统组件。
中间件设计像是一个调用链,context在链中各节点传递,各节点进行各自的功能处理:
看上图大家就明白,在核心业务的前后,将非业务逻辑剥离出来,实现预先处理和后置处理的效果,这就是中间件的本质。
实现中间件思路非常简单,Go的高阶函数编程可以非常容易的实现中间件设计,还是以Gin的中间件设计为例:
//编写一个Logger中间件,该中间件统一返回一个HandlerFunc,每个HandlerFunc都传递Context。
func Logger() gin.HandlerFunc {return func(c *gin.Context) {t := time.Now()// 设置 example 变量c.Set("example", "12345")//请求前...//执行下一层调用节点,根据Use()方法的调用顺序执行,执行完所有中间件后再进入核心业务Handler。c.Next()//请求后...latency := time.Since(t)log.Print(latency)// 获取发送的 statusstatus := c.Writer.Status()log.Println(status)}
}func main() {s := gin.New()//使用中间件调用Use()方法即可s.Use(Logger())s.GET("/test", func(c *gin.Context) {example := c.MustGet("example").(string)// 打印:"12345"log.Println(example)})// 监听并在 0.0.0.0:8080 上启动服务s.Run(":8080")
}
2.4 请求参数校验
你或许看过上图,这是社区中嘲笑PHP校验请求参数的例子,实际上,这种做法与语言无关,很多没有设计的参数校验都是这种粗糙的写法。这种if-else风暴组织的代码无疑是丑陋的。从代码的易读性和逻辑性角度看,都应该对条件分支进行重构,其实许多if嵌套都可重构为顺序条件结构,来提高代码的优雅程度,在此不便展开。回到正题,其实在Handler写一大堆参数校验的代码是挺难看的,既然参数校验是每个请求必须的流程,有没有什么方法可以把参数校验分离出Handler处理器呢?答案是Validator验证器设计。
从设计的角度讲,我们一定会为每个请求都声明一个结构体。
这里我们引入一个新的validator库:https://github.com/go-playground/validator
以下为使用示例:
import "gopkg.in/go-playground/validator.v9"type RegisterReq struct {// 字符串的 gt=0 表示长度必须 > 0,gt = greater thanUsername string `validate:"gt=0"`// 同上PasswordNew string `validate:"gt=0"`// eqfield 跨字段相等校验PasswordRepeat string `validate:"eqfield=PasswordNew"`// 合法 email 格式校验Email string `validate:"email"`
}validate := validator.New()func validate(req RegisterReq) error {err := validate.Struct(req)if err != nil {doSomething()return err}...
}
这样就不需要在每个请求进入业务逻辑之前都写重复的validate()函数了。本例中只列出了这个校验器非常简单的几个功能。
我们试着跑一下这个程序,输入参数设置为:
//...var req = RegisterReq {Username : "Fun",PasswordNew : "abc12138",PasswordRepeat : "ABC12138",Email : "fun@abc.com",
}err := validate(req)
fmt.Println(err)// Key: 'RegisterReq.PasswordRepeat' Error:Field validation for
// 'PasswordRepeat' failed on the 'eqfield' tag
错误信息可以针对每种tag定制,各位可查阅各验证器文档,开源社区有许多验证器的独立项目,也可以参考各框架的验证器实现,如有特殊需求也可以自定义验证器。
2.5 数据库交互
在《Go进阶系列》前三篇中,我们已经了解Go操作数据库的大体实现方法,对关系型数据库的操作,主要基于标准库的database/sql包,开源社区也有许多对sql包的再度封装项目,但这些库的使用许多都是对sql原生语句的操作,使用这些包的项目中,你会发现许多非常相似的sql语句,当项目比较大时会充斥许多冗余代码。于是为了提高项目生产效率,许多框架或独立开源项目都提供了ORM或SQL Builder的支持。在进阶系列开篇我们简单使用了gorm。
数据库交互在Web系统中可为是标配组件,因为数据持久化的场景随处可见,在其他语言的框架中,许多框架都内置了数据库交互组件,如PHP的Laravel、Yii等,数据访问层都是框架的标配,但许多静态语言的框架中,数据访问层是独立的框架,如JAVA的Hibernate,框架的设计依赖于语言的特点,由于Go包管理的特点,Go的框架中大部分数据库访问层是独立的,类似在Go Web框架中Router型框架占多数,它们只专注于处理HTTP相关的核心部分,其他的由更专业的数据访问层框架来做,如gorm、xorm等ORM框架,然而有些迁移型框架也包揽数据访问层,如beego、goa等,但这种框架的数据访问层也是独立的模块,是可独立使用的。
Go的数据访问层框架相对比较自由,数据访问层的库大体分三种:
- 基于database/sql进行再度封装:如sqlx;
- ORM框架:如gorm、xorm、beego/orm;
- SQL Builder:如 gendry、 goqu
ORM 利弊
使用ORM框架的项目,基本已经屏蔽了SQL语句的编写,所有的数据库交互都使用对象映射的方式,用操作对象替代SQL语句,这无疑对OOP程序员是福利。
ORM的目的就是屏蔽掉DB层,实际上很多语言的ORM只要把你的类或结构体定义好,再用特定的语法将结构体之间的一对一或者一对多关系表达出来。那么任务就完成了。然后你就可以对这些映射好了数据库表的对象进行各种操作,例如save,create,retrieve,delete。至于ORM在背地里做了什么阴险的勾当,你是不一定清楚的。使用ORM的时候,我们往往比较容易有一种忘记了数据库的直观感受。ORM设计初衷就是为了让数据的操作和存储的具体实现所剥离。但是在上了规模的公司的人们渐渐达成了一个共识,由于隐藏重要的细节,ORM可能是失败的设计。其所隐藏的重要细节对于上了规模的系统开发来说至关重要。
SQL Builder 利弊
相比ORM来说,SQL Builder在SQL和项目可维护性之间取得了比较好的平衡。首先sql builer不像ORM那样屏蔽了过多的细节,其次从开发的角度来讲,SQL Builder简单进行封装后也可以非常高效地完成开发:
where := map[string]interface{} {"order_id > ?" : 0,"customer_id != ?" : 0,
}
limit := []int{0,100}
orderBy := []string{"id asc", "create_time desc"}orders := orderModel.GetList(where, limit, orderBy)
读写SQL Builder的相关代码都不费劲,也非常接近SQL语句所表达的意思。所以通过代码就可以对这个查询是否命中数据库索引,是否走了覆盖索引,是否能够用上联合索引进行分析了。
说白了SQL Builder是sql在代码里的一种特殊方言,如果你们没有DBA但研发有自己分析和优化sql的能力,或者你们公司的DBA对于学习这样一些sql的方言没有异议。那么使用SQL Builder是一个比较好的选择,不会导致什么问题。
另外在一些本来也不需要DBA介入的场景内,使用SQL Builder也是可以的,例如你要做一套运维系统,且将MySQL当作了系统中的一个组件,系统的QPS不高,查询不复杂等等。
一旦你做的是高并发的OLTP在线系统,且想在人员充足分工明确的前提下最大程度控制系统的风险,使用SQL Builder就不合适了。
小结
使用什么框架作为数据访问层并无同一说法,完全看项目特性,如项目中对SQL的审核非常严格,建议使用基于database/sql封装的库,这样sql语句就会相对透明;如果你的团队不太注重SQL细节且非常注重开发效率,可使用ORM;相对以上两种而言,SQL Builder是比较折中的做法,如果你能控制系统风险,并可牺牲一些高并发性能,SQL Builder是不错的选择。
2.6 分层架构
说到分层,我们就会想到大名鼎鼎的MVC分层设计模式,在前后端分离架构大热前,MVC分层设计的框架可谓大行其道。在那时MVC单体架构可谓“万能”,只要做web项目第一个想到的就是MVC。
在MVC中,为了方便对业务模块进行划分和团队的分工合作,将程序划分为:
- 控制器(Controller)- 负责转发请求,对请求进行处理。
- 视图(View) - 界面设计人员进行图形界面设计。
- 模型(Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
但随着移动互联网的兴起,C/S结构的软件又再度兴起,许多web应用的客户端都独立运行,逐渐兴起大前端热潮,MVC中的V从此分离出去,衍生出MC+V的程序结构:
其实在Go兴起的年代,移动互联网应用已是主流,编写Go后端程序很多是不倡导使用MVC模式的,这与go的设计思想不符。但Go是新兴的语言,许多开发者对软件开发的理解还没与Go的设计思想适配,于是乎出现了许多迁移型框架,提供熟悉的设计方法方便其他语言的程序员迁移过来,这当中beego就是代表,其mvc设计是该框架的核心组件,最近兴起的Iris也对MVC结构的程序提供框架级别的支持。对于这种现象其实也有利有弊,利在于让更多的程序员更容易使用GO上手实际项目,弊在于这可能对Go设计Web程序的思想有点误导,Go倡导大道至简,许多更简单高效的Router型框架更切合Go Web的设计思想。
以下提供一个Iris实现MVC的编写示例:
package mainimport ("github.com/kataras/iris""github.com/kataras/iris/mvc""github.com/kataras/iris/middleware/logger""github.com/kataras/iris/middleware/recover"
)func newApp() *iris.Application {app := iris.New()app.Use(recover.New())app.Use(logger.New())// Serve a controller based on the root Router, "/".mvc.New(app).Handle(new(ExampleController))return app
}func main() {app := newApp()app.Run(iris.Addr(":8080"))
}// 定义一个控制器
type ExampleController struct{}// Get serves
// Method: GET
// Resource: http://localhost:8080
func (c *ExampleController) Get() mvc.Result {return mvc.Response{ContentType: "text/html",Text: "<h1>Welcome</h1>",}
}// GetPing serves
// Method: GET
// Resource: http://localhost:8080/ping
func (c *ExampleController) GetPing() string {return "pong"
}// GetHello serves
// Method: GET
// Resource: http://localhost:8080/hello
func (c *ExampleController) GetHello() interface{} {return map[string]string{"message": "Hello Iris!"}
}func (c *ExampleController) BeforeActivation(b mvc.BeforeActivation) {anyMiddlewareHere := func(ctx iris.Context) {ctx.Application().Logger().Warnf("Inside /custom_path")ctx.Next()}b.Handle("GET", "/custom_path", "CustomHandlerWithoutFollowingTheNamingGuide", anyMiddlewareHere)}func (c *ExampleController) CustomHandlerWithoutFollowingTheNamingGuide() string {return "hello from the custom handler without following the naming guide"
}
相比之下,直接使用Router + Handler的编写方式会简单得多:
package mainimport ("github.com/kataras/iris""github.com/kataras/iris/middleware/logger""github.com/kataras/iris/middleware/recover"
)func main() {app := iris.New()app.Logger().SetLevel("debug")app.Use(recover.New())app.Use(logger.New())// Method: GET// Resource: http://localhost:8080app.Handle("GET", "/", func(ctx iris.Context) {ctx.HTML("<h1>Welcome</h1>")})// same as app.Handle("GET", "/ping", [...])// Method: GET// Resource: http://localhost:8080/pingapp.Get("/ping", func(ctx iris.Context) {ctx.WriteString("pong")})// Method: GET// Resource: http://localhost:8080/helloapp.Get("/hello", func(ctx iris.Context) {ctx.JSON(iris.Map{"message": "Hello Iris!"})})// http://localhost:8080// http://localhost:8080/ping// http://localhost:8080/helloapp.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
}
个人建议:不要为了MVC而MVC!在分层设计方面,使用经典的三层架构就可以了,表现层的前端独立分离,后端实现业务逻辑层和数据访问层使软件结构足够精简,更何况我们渐渐进入了容器服务时代,许多大型应用会进化成分布式微服务独立运行,没必要再坚守单体架构的MVC模式了。
2.7 插件机制
所谓插件,是系统中与业务无关的可插拔工具集。插件机制是面向接口编程应用的典范,一个类型的插件可有多个具体的插件实现,系统只对插件接口设计特定的签名方法即可。如缓存插件,RABC插件,日志插件。插件是一种框架的机制设计,当系统允许某些组件是可替换时,把组件编写出插件形式,即编写特定的接口规范,具体的插件实现可由第三方实现,也可由用户自定义。
如Beego框架为了实现某些组件可替换、可由用户自定义,规范了以下插件:
- gorelic
- 支付宝 SDK
- pongo2
- keenio
- casbin - RBAC ACL plugins
这种设计有助于用户定制自己的系统。
更极致的使用插件机制的框架像go-micro,它是一个微服务框架。Go Micro为每个分布式系统抽象出接口。因此,Go Micro的接口都是可插拔的,允许其在运行时不可知的情况下仍可支持。所以只要实现接口,可以在内部使用任何的技术。
Go Micro对整个框架的核心组件都做了插件机制的设计,你可以随意选择消息总线插件、RPC通信插件、编解码插件、服务注册插件、负载均衡插件、消息传输插件等。你可以自由选择插件库中的第三方库替换插件,也可以自己实现插件,只要符合框架定义的接口规范。
插件 | 描述 |
---|---|
Broker | PubSub messaging; NATS, NSQ, RabbitMQ, Kafka |
Client | RPC Clients; gRPC, HTTP |
Codec | Message Encoding; BSON, Mercury |
Micro | Micro Toolkit Plugins |
Registry | Service Discovery; Etcd, Gossip, NATS |
Selector | Load balancing; Label, Cache, Static |
Server | RPC Servers; gRPC, HTTP |
Transport | Bidirectional Streaming; NATS, RabbitMQ |
Wrapper | Middleware; Circuit Breakers, Rate Limiting, Tracing, Monitoring |
我们来看一下其实现,以Broker插件为例:
//定义了Broker类型插件的接口
// Broker is an interface used for asynchronous messaging.
type Broker interface {Init(...Option) errorOptions() OptionsAddress() stringConnect() errorDisconnect() errorPublish(topic string, m *Message, opts ...PublishOption) errorSubscribe(topic string, h Handler, opts ...SubscribeOption) (Subscriber, error)String() string
}...
我们再看一看一个具体的使用Redis作为Broker的定义:
// Package redis provides a Redis broker
package redisimport ("context""errors""strings""time""github.com/gomodule/redigo/redis""github.com/micro/go-micro/broker""github.com/micro/go-micro/codec""github.com/micro/go-micro/codec/json""github.com/micro/go-micro/config/cmd"
)//此处省略代码...// broker implementation for Redis.
type redisBroker struct {addr stringpool *redis.Poolopts broker.Optionsbopts *brokerOptions
}// String returns the name of the broker implementation.
func (b *redisBroker) String() string {return "redis"
}// Options returns the options defined for the broker.
func (b *redisBroker) Options() broker.Options {return b.opts
}// Address returns the address the broker will use to create new connections.
// This will be set only after Connect is called.
func (b *redisBroker) Address() string {return b.addr
}// Init sets or overrides broker options.
func (b *redisBroker) Init(opts ...broker.Option) error {//此处省略代码...
}// Connect establishes a connection to Redis which provides the
// pub/sub implementation.
func (b *redisBroker) Connect() error {//此处省略代码...
}// Disconnect closes the connection pool.
func (b *redisBroker) Disconnect() error {//此处省略代码...
}// Publish publishes a message.
func (b *redisBroker) Publish(topic string, msg *broker.Message, opts ...broker.PublishOption) error {//此处省略代码...
}// Subscribe returns a subscriber for the topic and handler.
func (b *redisBroker) Subscribe(topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {//此处省略代码...}// NewBroker returns a new broker implemented using the Redis pub/sub
// protocol. The connection address may be a fully qualified IANA address such
// as: redis://user:secret@localhost:6379/0?foo=bar&qux=baz
func NewBroker(opts ...broker.Option) broker.Broker {// Default options.bopts := &brokerOptions{maxIdle: DefaultMaxIdle,maxActive: DefaultMaxActive,idleTimeout: DefaultIdleTimeout,connectTimeout: DefaultConnectTimeout,readTimeout: DefaultReadTimeout,writeTimeout: DefaultWriteTimeout,}// Initialize with empty broker options.options := broker.Options{Codec: json.Marshaler{},Context: context.WithValue(context.Background(), optionsKey, bopts),}for _, o := range opts {o(&options)}return &redisBroker{opts: options,bopts: bopts,}
}
可见Redis Broker插件实现了Broker插件类型的接口签名方法。Micro框架就是根据插件机制搭建的,你可以根据插件自由的定制自己的微服务系统。
三、总结
Web技术发展日新月异,框架也层出不穷,面对如此眼花缭乱的技术,我们难免会心生畏惧,那么多框架,那么多技术该如何选择?刚学完这个框架,又有更新更好的框架出来。对于这个问题,我认为我们不应该迷信框架,Web技术的本质是什么,它的底层核心技术是什么?我们只要理解其本质,其他从其衍生的技术都万变不离其宗。Web框架的设计初衷是屏蔽开发底层的逻辑使我们更专注于业务,但由于其屏蔽底层逻辑,我们更需要深入理解它,读懂它。
至此《Go进阶系列》告一段落,之后计划输出《Go实战系列》和《Go高级系列》,如果对你有帮助,不要忘了点个赞或关注本人哈,谢谢!
http://www.taodudu.cc/news/show-3200551.html
相关文章:
- 软件研发业务流程的制定及改进
- 【AI视野·今日Robot 机器人论文速览 第七期】Tue, 15 Jun 2021
- Go Web——HttpRouter路由
- SSM中的拦截器机制
- 《预训练周刊》第51期:无重构预训练、零样本自动微调、一键调用OPT
- 精通国际象棋的AI研究员:AlphaZero真的是一次突破吗?
- 智能肛珠作弊案反转:19岁小将告世界冠军诽谤索赔7亿
- 快速绘制流程图「GitHub 热点速览 v.22.47」
- C语言(求圆柱体的体积)
- 使用c++,类实现求圆柱体体积
- C++期末考试1:求圆柱体体积(完整代码含测试)
- JAVA继承案例--计算圆柱体体积
- 用java实现圆柱体体积
- C语言例题:求出圆柱体的体积
- 利用继承,实现圆柱体体积的计算
- python计算圆柱体体积代码_继承实现圆柱体面积体积的计算
- 0013 求圆柱体体积
- 求圆柱体体积
- 通过对象实现圆柱体体积计算
- 计算圆柱体体积
- 【半年总结】——2015.08
- 【半年总结】思想与技术的腾飞
- 【总结】羽翼日渐丰满—年终总结
- 众咖云集的 PyCon 2019 上海站,大佬们都讲了啥
- 苹果画画软件_三只苹果改变世界,京东Apple创意节引爆创意能量
- 关于批处理的学习之一[前言]
- 英语网站荟萃
- 我的电脑学习经历
- 英语总结—— 翩若惊鸿, 婉若游龙
- 英语学习网站
19 Go Web 框架(二):框架技术详解相关推荐
- SpringSecurity权限管理框架系列(六)-Spring Security框架自定义配置类详解(二)之authorizeRequests配置详解
1.预置演示环境 这个演示环境继续沿用 SpringSecurit权限管理框架系列(五)-Spring Security框架自定义配置类详解(一)之formLogin配置详解的环境. 2.自定义配置类 ...
- 【H.264/AVC视频编解码技术详解】二十三、帧间预测编码(1):帧间预测编码的基本原理
<H.264/AVC视频编解码技术详解>视频教程已经在"CSDN学院"上线,视频中详述了H.264的背景.标准协议和实现,并通过一个实战工程的形式对H.264的标准进行 ...
- Android Binder框架实现之Parcel详解之基本数据的读写
Android Binder框架实现之Parcel详解之基本数据的读写 Android Binder框架实现目录: Android Binder框架实现之Binder的设计思想 Android ...
- java集合框架史上最详解(list set 以及map)
title: Java集合框架史上最详解(list set 以及map) tags: 集合框架 list set map 文章目录 一.集合框架总体架构 1.1 集合框架在被设计时需满足的目标 1.2 ...
- androidentity什么用_Android ORM 框架:GreenDao 使用详解(进阶篇)
前言 在 Android ORM 框架:GreenDao 使用详解(基础篇) 中,我们了解了 GreenDao 的基本使用,本文我们将深入讲解 GreenDao 的使用 . 一.复杂表结构 a, 使用 ...
- 《视频直播技术详解》之二:编码和封装、推流和传输
视频编码是本系列一个重要的部分,如果把整个流媒体比喻成一个物流系统,那么编解码就是其中配货和装货的过程,这个过程非常重要,它的速度和压缩比对物流系统的意义非常大,影响物流系统的整体速度和成本.同样,对 ...
- Android Binder框架实现之bindService详解
Android Binder框架实现之bindService详解 Android Binder框架实现目录: Android Binder框架实现之Binder的设计思想 Android Bi ...
- 【H.264/AVC视频编解码技术详解】十九:熵编码算法(5)——H.264的CABAC(上):语法元素的二值化方法...
<H.264/AVC视频编解码技术详解>视频教程已经在"CSDN学院"上线,视频中详述了H.264的背景.标准协议和实现,并通过一个实战工程的形式对H.264的标准进行 ...
- 超轻量级DI容器框架Google Guice与Spring框架的区别教程详解及其demo代码片段分享...
超轻量级DI容器框架Google Guice与Spring框架的区别教程详解及其demo代码片段分享 DI框架 Google-Guice入门介绍 转载于:https://www.cnblogs.com ...
- 自动化测试框架[Cypress元素操作详解]
前提 已经熟练掌握了Cypress的基本知识,请参考自动化测试框架[Cypress概述]和自动化测试框架[各自动化测试框架比较] 已经熟练掌握Cypress环境配置,请参考自动化测试框架[Cypres ...
最新文章
- 卸载系统预装McAfee Agent
- Redis 概念以及底层数据结构
- CreateRemoteThread注入DLL
- 文巾解题 292. Nim 游戏
- 《数据结构与算法 C语言版》—— 2.7习题
- 后端技术:一个注解解决 SpringBoot 接口防刷
- 华为触摸提示音怎么换_抖音苹果iPhone手机怎么改微信消息提示音 自定义换声音教程...
- 微课|中学生可以这样学Python(例4.2):打印九九乘法表
- 店庆遇上双11,买书的最大优惠来了!
- node.js + express服务端,客户端请求图片,在浏览器出现乱码解决方案
- 正则去除汉字和只取数字
- python网络编程 赵宏_【干货收藏】Python面试指南大全
- php 批量上传图片插件,diyUpload - jQuery多张图片批量上传插件
- 游戏场景设计探究:空间潜意识
- 8 EXCEL选择填充与粘贴
- python 多态app_Python——多态
- 爬取6874条数据,告诉你数据分析师的薪资待遇~!
- linux安装python任意版本,一键安装和一键卸载shell脚本
- 有道再出发:真正的教育事业没有终点
- python培训学费多少钱-python培训学费多少钱
热门文章
- 创建Windows10 密码重设盘
- 【金猿产品展】极盾·觅踪——数据使用安全管控平台
- 远程关闭重启计算机CMD命令
- 远程桌面连接的开启,和借助内外网软件,实现外网PC手机远程控制内网电脑,详细图文教程
- Consult IDE log for more details (Help | Show Log),read failed, socket might closed or timeout,
- android 轻量级缓存框架ASimpleCache
- 推荐一个外文图书专著下载网站
- 开源社区活跃度分析——api.github.com的使用
- vue中怎么根据不同的(分屏模式)调整【自定义不同视频布局】?
- java三国志吞食天地_FC三国志吞食天地完整版下载-街机吞食天地完整版模拟器(含出招表)-电玩咖...