B站微服务框架Kratos详细教程(2)

Stella981
• 阅读 1520

背景

在像微服务这样的分布式架构中,经常会有一些需求需要你调用多个服务,但是还需要确保服务的安全性、统一化每次的 请求日志或者追踪用户完整的行为等等。
你可能需要一个框架来帮助你实现这些功能。比如说帮你在一些关键路径的请求上配置必要的鉴权 或超时策略。那样服务间的调用会被多层中间件所过滤并检查,确保整体服务的稳定性。

设计目标

  • 性能优异,不应该掺杂太多业务逻辑的成分
  • 方便开发使用,开发对接的成本应该尽可能地小
  • 后续鉴权、认证等业务逻辑的模块应该可以通过业务模块的开发接入该框架内
  • 默认配置已经是 production ready 的配置,减少开发与线上环境的差异性

kratos的http服务架构-blademaster

blademaster设计的整套HTTP框架参考了gin,去除 gin 中不需要的部分逻辑。
blademaster由几个非常精简的内部模块组成。其中Router用于根据请求的路径分发请求,Context包含了一个完整的请求信息,Handler则负责处理传入的ContextHandlers为一个列表,一个串一个地执行。
所有的middlerware均以Handler的形式存在,这样可以保证blademaster自身足够精简且扩展性足够强。

B站微服务框架Kratos详细教程(2)

blademaster处理请求的模式非常简单,大部分的逻辑都被封装在了各种Handler中。一般而言,业务逻辑作为最后一个Handler

正常情况下每个Handler按照顺序一个一个串行地执行下去,但是Handler中也可以中断整个处理流程,直接输出Response。这种模式常被用于校验登陆的middleware中:一旦发现请求不合法,直接响应拒绝。

请求处理的流程中也可以使用Render来辅助渲染Response,比如对于不同的请求需要响应不同的数据格式JSONXML,此时可以使用不同的Render来简化逻辑。

快速开始

创建http项目:

kratos new httpdemo --http

可以指定名字和目录:

kratos new kratos-demo -o YourName -d YourPath

B站微服务框架Kratos详细教程(2)
创建项目成功后,进入 internal/server/http 目录下,默认生成的 server.go 模板:

package http

import (
    "net/http"

    pb "httpdemo/api"
    "httpdemo/internal/model"
    "github.com/go-kratos/kratos/pkg/conf/paladin"
    "github.com/go-kratos/kratos/pkg/log"
    bm "github.com/go-kratos/kratos/pkg/net/http/blademaster"
)

var svc pb.DemoServer

// New new a bm server.
func New(s pb.DemoServer) (engine *bm.Engine, err error) {
    var (
        cfg bm.ServerConfig
        ct paladin.TOML
    )
    if err = paladin.Get("http.toml").Unmarshal(&ct); err != nil {
        return
    }
    if err = ct.Get("Server").UnmarshalTOML(&cfg); err != nil {
        return
    }
    svc = s
    engine = bm.DefaultServer(&cfg)
    pb.RegisterDemoBMServer(engine, s)
    initRouter(engine)
    err = engine.Start()
    return
}
//路由
func initRouter(e *bm.Engine) {
    e.Ping(ping)  // engine自带的"/ping"接口,用于负载均衡检测服务健康状态
    g := e.Group("/httpdemo") // // e.Group 创建一组 "/httpdemo" 起始的路由组
    {
        g.GET("/start", howToStart) // // g.GET 创建一个 "httpdemo/start" 的路由,使用GET方式请求,默认处理Handle r为howToStart方法
    }
}
//engine自带Ping方法,用于设置 /ping 路由的handler,该路由统一提供于负载均衡服务做健康检测。服务是否健康,可自 定义 ping handler 进行逻辑判断,如检测DB是否正常等。
func ping(ctx *bm.Context) {
    if _, err := svc.Ping(ctx, nil); err != nil {
        log.Error("ping error(%v)", err)
        ctx.AbortWithStatus(http.StatusServiceUnavailable)
    }
}

// bm的handler方法.
func howToStart(c *bm.Context) {
    k := &model.Kratos{
        Hello: "Golang 大法好 !!!",
    }
    c.JSON(k, nil)
}

默认路由

默认路由有:

  • /metrics 用于prometheus信息采集
  • /metadata 可以查看所有注册的路由信息
    打开浏览器访问:

路径参数

我们在路由中增加一些内容,增加一个handler方法showParam:

func initRouter(e *bm.Engine) {
    e.Ping(ping)
    g := e.Group("/httpdemo")
    {
        g.GET("/start", howToStart)

        // 路径参数有两个特殊符号":"和"*"
        // ":" 跟在"/"后面为参数的key,匹配两个/中间的值 或 一个/到结尾(其中不再包含/)的值
        // "*" 跟在"/"后面为参数的key,匹配从 /*开始到结尾的所有值,所有*必须写在最后且无法多个

        // NOTE:这是不被允许的,会和 /start 冲突
        // g.GET("/:xxx")

        // NOTE: 可以拿到一个key为name的参数。注意只能匹配到/param1/soul,无法匹配/param1/soul/hao(该路径会404)
        g.GET("/param1/:name", showParam)
        // NOTE: 可以拿到多个key参数。注意只能匹配到/param2/soul/male/hello,无法匹配/param2/soul或/param2/soul/hello
        g.GET("/param2/:name/:gender/:say", showParam)
        // NOTE: 可以拿到一个key为name的参数 和 一个key为action的路径。
        // NOTE: 如/params3/soul/hello,action的值为"/hello"
        // NOTE: 如/params3/soul/hello/hi,action的值为"/hello/hi"
        // NOTE: 如/params3/soul/hello/hi/,action的值为"/hello/hi/"
        g.GET("/param3/:name/*action", showParam)
    }
}

func showParam(c *bm.Context) {
    name, _ := c.Params.Get("name")
    gender, _ := c.Params.Get("gender")
    say, _ := c.Params.Get("say")
    action, _ := c.Params.Get("action")
    path := c.RoutePath // NOTE: 获取注册的路由原始地址,如: /httpdemo/param1/:name
    c.JSONMap(map[string]interface{}{
        "name":   name,
        "gender":  gender,
        "say":  say,
        "action": action,
        "path":   path,
    }, nil)
}

打开浏览器访问:

http://localhost:8000/httpdemo/param2/Soul/male/hello

输出内容:

{
    "action": "",
    "code": 0,
    "gender": "male",
    "message": "0",
    "name": "Soul",
    "path": "/httpdemo/param2/:name/:gender/:say",
    "say": "hello"
}

Context

以下是 blademasterContext 对象结构体声明的代码片段:

// Context is the most important part. It allows us to pass variables between
// middleware, manage the flow, validate the JSON of a request and render a
// JSON response for example.
type Context struct {
    context.Context     //嵌入一个标准库中的 Context实例,对应bm中的 Context,也是通过该实例来实现标准库中的 Context 接口
 
    Request *http.Request     //获取当前请求信息
    Writer  http.ResponseWriter        //输出响应请求信息
 
    // flow control
    index    int8     //标记当前正在执行的 handler 的索引位
    handlers []HandlerFunc     //中存储了当前请求需要执行的所有 handler
 
    // Keys is a key/value pair exclusively for the context of each request.
    Keys map[string]interface{}     //在 handler 之间传递一些额外的信息
 
    Error error     //存储整个请求处理过程中的错误
 
    method string     //检查当前请求的 Method 是否与预定义的相匹配
    engine *Engine     //指向当前 blademaster 的 Engine 实例
}
  • 首先 blademasterContext 结构体中会 嵌入一个标准库中的 Context 实例,bm 中的 Context 也是通过该实例来实现标准库中的 Context 接口。
  • blademaster 会使用配置的 server timeout (默认1s) 作为一次请求整个过程中的超时时间,使用该context调用dao做数据库、缓存操作查询时均会将该超时时间传递下去,一旦抵达deadline,后续相关操作均会返回context deadline exceeded
  • RequestWriter 字段用于获取当前请求的与输出响应。
  • index 和 handlers 用于 handler 的流程控制;handlers 中存储了当前请求需要执行的所有 handlerindex 用于标记当前正在执行的 handler 的索引位。
  • Keys 用于在 handler 之间传递一些额外的信息。
  • Error 用于存储整个请求处理过程中的错误。
  • method 用于检查当前请求的 Method 是否与预定义的相匹配。
  • engine 字段指向当前 blademaster 的 Engine 实例。

以下为 Context 中所有的公开的方法:

// 用于 Handler 的流程控制
func (c *Context) Abort()
func (c *Context) AbortWithStatus(code int)
func (c *Context) Bytes(code int, contentType string, data ...[]byte)
func (c *Context) IsAborted() bool
func (c *Context) Next()
 
// 用户获取或者传递请求的额外信息
func (c *Context) RemoteIP() (cip string)
func (c *Context) Set(key string, value interface{})
func (c *Context) Get(key string) (value interface{}, exists bool)
  
// 用于校验请求的 payload
func (c *Context) Bind(obj interface{}) error
func (c *Context) BindWith(obj interface{}, b binding.Binding) error
  
// 用于输出响应
func (c *Context) Render(code int, r render.Render)
func (c *Context) Redirect(code int, location string)
func (c *Context) Status(code int)
func (c *Context) String(code int, format string, values ...interface{})
func (c *Context) XML(data interface{}, err error)
func (c *Context) JSON(data interface{}, err error)
func (c *Context) JSONMap(data map[string]interface{}, err error)
func (c *Context) Protobuf(data proto.Message, err error)

所有方法基本上可以分为三类:

  • 流程控制
  • 额外信息传递
  • 请求处理
  • 响应处理

Handler

B站微服务框架Kratos详细教程(2)
初次接触blademaster的用户可能会对其Handler的流程处理产生不小的疑惑,实际上bmHandler对处理非常简单:

  • Router模块中预先注册的middleware与其他Handler合并,放入Contexthandlers字段,并将index字段置0
  • 然后通过Next()方法一个个执行下去,部分middleware可能想要在过程中中断整个流程,此时可以使用Abort()方法提前结束处理
  • 有些middleware还想在所有Handler执行完后再执行部分逻辑,此时可以在自身Handler中显式调用Next()方法,并将这些逻辑放在调用了Next()方法之后

性能分析

启动时默认监听了2333端口用于pprof信息采集,如:

go tool pprof http://127.0.0.1:8000/debug/pprof/profile

改变端口可以使用flag,如:-http.perf=tcp://0.0.0.0:12333

最后附上教程示例项目源码:

https://download.csdn.net/download/uisoul/12822504

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Nepxion Discovery 5.5.0 发布
!(https://oscimg.oschina.net/oscnet/f81c043194ef4732880459d00c1a720e.png)发布日志功能更新:增加基于Opentracing调用链的支持,目前支持UberJaeger,实现在SpringCloudGateway、Zuul和服务上的灰度
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
AWS国庆双重礼,仅限7天
自2021年10月1日00:00起至2021年10月7日24:00,新注册并激活(需全部完成账号注册的五个步骤,否则账号状态并未激活)AWS海外区域账户,填写页面下方表单,即可申领价值$200美元的AWS海外区域账户服务抵扣券直充到您的账户,用以抵扣服务消费,助您轻松体验多个云迁移应用场景。同时,您还可获赠。国庆双重礼,仅限7天$200美元AWS服务抵
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了