开源 Golang 微服务入门一: HTTP 框架 Hertz

news2024/11/25 4:45:05

前言

从本篇笔记开始将介绍 Go 框架三件套(Web / RPC / ORM),框架的学习有助于后续课程的学习以及大项目的完成。本文主要介绍字节跳动的开源 Golang 微服务 HTTP 框架 Hertz。先了解一下三件套的相关基本知识,做一下铺垫:

Gorm

gorm是Golang语言中一个已经迭代数十年且功能强大、性能极好的ORM框架

ORM:Object Relational Mapping(对象关系映射),其主要作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来,

简单来说,在golang中,自定义的一个结构体对应着一张表,结构体的实例则对应着表中的一条记录。

Kitex

Kitex是字节内部Golang微服务RPC框架 具有高性能、强可扩展的主要特点 支持多协议并且拥有丰富的开源扩展

Hertz

Hertz是字节内部的Http框架 参考了其他开源框架的优势 结合字节跳动内部的需求 具有高可用、高性能、高扩展性的特点

文章目录

    • 前言
      • Gorm
      • Kitex
      • Hertz
    • 知识点介绍
      • Hertz 简介
      • 框架特点:
      • 功能特性
        • 中间件
        • 流式处理
        • 性能指标
    • 快速开始
      • 准备 Golang 开发环境
        • 快速上手
    • 路由
      • 路由注册
      • 路由组
      • 路由类型
        • 通配路由
    • 参数绑定
      • 中间件
        • 使用跨域中间件示例
      • 错误处理
      • 配置中间件
    • Hertz 代码生成工具 hz
      • 基本使用:
    • 总结
    • 引用

知识点介绍

Hertz 简介

Hertz 是一个 Golang 微服务 HTTP 框架,在设计之初参考了其他开源框架 fasthttp、gin、echo 的优势, 并结合字节跳动内部的需求,使其具有高易用性、高性能、高扩展性等特点,目前在字节跳动内部已广泛使用。 如今越来越多的微服务选择使用 Golang,如果对微服务性能有要求,又希望框架能够充分满足内部的可定制化需求,Hertz 会是一个不错的选择。

架构设计:Hertz采用了4层分层设计(应用层、路由层、协议层、传输层),保证各个层级功能内聚,同时通过层级之间的接口达到灵活扩展的目标。

image.png

框架特点:

  • 高易用性:在开发过程中,快速写出来正确的代码往往是更重要的。因此,在 Hertz 在迭代过程中,积极听取用户意见,持续打磨框架,希望为用户提供一个更好的使用体验,帮助用户更快的写出正确的代码。

  • 高性能:Hertz 默认使用自研的高性能网络库 Netpoll,在一些特殊场景相较于 go net,Hertz 在 QPS、时延上均具有一定优势。

  • 高扩展性:Hertz 采用了分层设计,提供了较多的接口以及默认的扩展实现,用户也可以自行扩展。同时得益于框架的分层设计,框架的扩展性也会大很多。

  • 多协议支持:Hertz 框架原生提供 HTTP1.1、ALPN 协议支持。除此之外,由于分层设计,Hertz 甚至支持自定义构建协议解析逻辑,以满足协议层扩展的任意需求。

  • 网络层切换能力:Hertz 实现了 Netpoll 和 Golang 原生网络库 间按需切换能力,用户可以针对不同的场景选择合适的网络库,同时也支持以插件的方式为 Hertz 扩展网络库实现。

功能特性

中间件

Hertz 除了提供 Server 的中间件能力,还提供了 Client 中间件能力。用户可以使用中间件能力将通用逻辑(如:日志记录、性能统计、异常处理、鉴权逻辑等等)和业务逻辑区分开,让用户更加专注于业务代码。Server 和 Client 中间件使用方式相同,使用 Use 方法注册中间件,中间件执行顺序和注册顺序相同,同时支持预处理和后处理逻辑。

Server 和 Client 的中间件实现方式并不相同。对于 Server 来说,我们希望减少栈的深度,同时也希望中间件能够默认的执行下一个,用户需要手动终止中间件的执行。因此,我们将 Server 的中间件分成了两种类型,即不在同一个函数调用栈(该中间件调用完后返回,由上一个中间件调用下一个中间件,如图 2 中 B 和 C)和在同一个函数调用栈的中间件(该中间件调用完后由该中间件继续调用下一个中间件,如图 2 中 C 和 Business Handler)。

中间件链路

其核心是需要一个地方存下当前的调用位置 index,并始终保持其递增。恰好 RequestContext 就是一个存储 index 合适的位置。但是对于 Client,由于没有合适的地方存储 index,我们只能退而求其次,抛弃 index 的实现,将所有的中间件构造在同一调用链上,需要用户手动调用下一个中间件。

流式处理

Hertz 提供 Server 和 Client 的流式处理能力。HTTP 的文件场景是十分常见的场景,除了 Server 侧的上传场景之外,Client 的下载场景也十分常见。为此,Hertz 支持了 Server 和 Client 的流式处理。在内部网关场景中,从 Gin 迁移到 Hertz 后,CPU 使用量随流量大小不同可节省 30%—60% 不等,服务压力越大,收益越大。Hertz 开启流式功能的方式也很容易,只需要在 Server 上或 Client 上添加一个配置即可,可参考 CloudWeGo 官网 Hertz 文档的流式处理部分。

由于 Netpoll 采用 LT 的触发模式,由网络库主动将将数据从 TCP 缓冲区读到用户态,并存储到 buffer 中,否则 epoll 事件会持续触发。因此 Server 在超大请求的场景下,由于 Netpoll 持续将数据读到用户态内存中,可能会有 OOM 的风险。HTTP 文件上传场景就是一个典型的场景,但 HTTP 上传服务又是很常见的场景,因此我们支持标准网络库 go net,并针对 Hertz 做了特殊优化,暴露出 Read() 接口,防止 OOM 发生。

对于 Client,情况并不相同。流式场景下会将连接封装成 Reader 暴露给用户,而Client 有连接池管理,那这样连接就多了一种状态,何时关连接,何时复用连接成了一个问题。由于框架侧并不知道该连接何时会用完,框架侧复用该连接不现实,会导致串包问题。由于 GC 会关闭连接,因此我们起初设想流式场景下的连接交由用户后,由 GC 负责关闭,这样也不会导致资源泄漏。但是在测试后发现,由于 GC 存在一定时间间隔,另外 TCP 中主动关闭连接的一方需要等待 2RTT,在高并发场景下会导致 fd 被打满的情况。最终我们提供了复用连接的接口,对于性能有场要求用户,在使用完连接后可以将连接重新放入连接池中复用。

性能指标

Hertz 使用字节跳动自研高性能网络库 Netpoll,在提高网络库效率方面有诸多实践,参考已发布文章字节跳动在 Go 网络库上的实践。除此之外,Netpoll 还针对 HTTP 场景进行优化,通过减少拷贝和系统调用次数提高吞吐以及降低时延。为了衡量 Hertz 性能指标,我们选取了社区中有代表性的框架 Gin(net/http)和 Fasthttp 作为对比,如图3所示。可以看到,Hertz 的极限吞吐、TP99 等指标均处于业界领先水平。未来,Hertz 还将继续和 Netpoll 深度配合,探索 HTTP 框架性能的极限。

Hertz 和其他框架性能对比

快速开始

准备 Golang 开发环境

首先需要安装 Golang,推荐安装最新版本。可参考

2023 最新萌新学习 Golang 环境配置详细步骤,附带图文

目前,Hertz 支持 Linux、macOS、Windows 系统。

快速上手

  1. 安装命令行工具 hz

    确保环境变量已经正确定义,如下:

    go install github.com/cloudwego/hertz/cmd/hz\@latest
    
  2. 生成/编写示例代码

    • 在当前目录下创建 hertz_demo 文件夹,进入该目录中

    • 创建 main.go 文件

    • 在 main.go 文件中添加以下代码

    package main
    
    import (
    "context""github.com/cloudwego/hertz/pkg/app""github.com/cloudwego/hertz/pkg/app/server""github.com/cloudwego/hertz/pkg/common/utils""github.com/cloudwego/hertz/pkg/protocol/consts"
    )
    
    funcmain() {
    h := server.Default()
    
        h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
                ctx.JSON(consts.StatusOK, utils.H{"message": "pong"})
        })
    
        h.Spin()
    
    }
    
    • 生成 go.mod 文件
    go mod init hertz\_demo
    
    • 整理 & 拉取依赖
    go mod tidy
    
    • 打包并运行示例代码
    go build -o hertz\_demo
    go run hertz\_demo
    
    
    • 如果成功,将看到
    2023/01/30 11:26:27.824241 engine.go:617: \[Debug] HERTZ: Method=GET    absolutePath=/ping                     --> handlerName=main.main.func1 (num=2 handlers)
    2023/01/30 11:26:27.836518 engine.go:389: \[Info] HERTZ: Using network library=standard
    2023/01/30 11:26:27.837518 transport.go:65: \[Info] HERTZ: HERTZ: HTTP server listening on address=\[::]:8888
    
    • 对接口进行测试:
    curl <http://127.0.0.1:8888/ping>
    

    如果成功就可以看到以下输出:

    StatusCode        : 200
    StatusDescription : OK
    Content           : {"message":"pong"}
    RawContent        : HTTP/1.1 200 OK
    Content-Length: 18
    Content-Type: application/json; charset=utf-8
    Date: Mon, 30 Jan 2023 03:26:53 GMT
    Server: hertz
    
                        {"message":"pong"}
    
    Forms             : {}
    Headers           : {\[Content-Length, 18], \[Content-Type, application/json; charset=utf-8], \[Date, Mon, 30 Jan 2023 03:26:53 GMT], \[Server,\
    hertz]}
    Images            : {}
    InputFields       : {}
    Links             : {}
    ParsedHtml        : mshtml.HTMLDocumentClass
    RawContentLength  : 18
    

    到此就已经成功启动了 Hertz Server,并完成了一次调用。

路由

路由注册

Hertz 提供了 GET、POST、PUT、DELETE、ANY 等方法用于注册路由。

方法介绍
Hertz.GET用于注册 HTTP Method 为 GET 的方法
Hertz.POST用于注册 HTTP Method 为 POST 的方法
Hertz.DELETE用于注册 HTTP Method 为 DELETE 的方法
Hertz.PUT用于注册 HTTP Method 为 PUT 的方法
Hertz.PATCH用于注册 HTTP Method 为 PATCH 的方法
Hertz.HEAD用于注册 HTTP Method 为 HEAD 的方法
Hertz.OPTIONS用于注册 HTTP Method 为 OPTIONS 的方法
Hertz.Handle这个方法支持用户手动传入 HTTP Method 用来注册方法,当用于注册普通的 HTTP Method 方法时和上述的方法作用是一致的,并且这个方法同时也支持用于注册自定义 HTTP Method 方法
Hertz.Any用于注册所有 HTTP Method 方法
Hertz.StaticFile/Static/StaticFS用于注册静态文件

示例代码:

package main

import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)

funcmain(){
h := server.Default(server.WithHostPorts("127.0.0.1:8080"))

    h.StaticFS("/", &app.FS{Root: "./", GenerateIndexPages: true})

    h.GET("/get", func(ctx context.Context, c *app.RequestContext) {
    	c.String(consts.StatusOK, "get")
    })
    h.POST("/post", func(ctx context.Context, c *app.RequestContext) {
    	c.String(consts.StatusOK, "post")
    })
    h.PUT("/put", func(ctx context.Context, c *app.RequestContext) {
    	c.String(consts.StatusOK, "put")
    })
    h.DELETE("/delete", func(ctx context.Context, c *app.RequestContext) {
    	c.String(consts.StatusOK, "delete")
    })
    h.PATCH("/patch", func(ctx context.Context, c *app.RequestContext) {
    	c.String(consts.StatusOK, "patch")
    })
    h.HEAD("/head", func(ctx context.Context, c *app.RequestContext) {
    	c.String(consts.StatusOK, "head")
    })
    h.OPTIONS("/options", func(ctx context.Context, c *app.RequestContext) {
    	c.String(consts.StatusOK, "options")
    })
    h.Any("/ping_any", func(ctx context.Context, c *app.RequestContext) {
    	c.String(consts.StatusOK, "any")
    })
    h.Handle("LOAD","/load", func(ctx context.Context, c *app.RequestContext) {
    	c.String(consts.StatusOK, "load")
    })
    h.Spin()

}

路由组

Hertz 提供了路由组( Group )的能力,用于支持路由分组的功能,同时中间件也可以注册到路由组上。

示例代码:

package main

import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)

funcmain(){
h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
v1 := h.Group("/v1")
v1.GET("/get", func(ctx context.Context, c \*app.RequestContext) {
c.String(consts.StatusOK, "get")
})
v1.POST("/post", func(ctx context.Context, c \*app.RequestContext) {
c.String(consts.StatusOK, "post")
})
v2 := h.Group("/v2")
v2.PUT("/put", func(ctx context.Context, c \*app.RequestContext) {
c.String(consts.StatusOK, "put")
})
v2.DELETE("/delete", func(ctx context.Context, c \*app.RequestContext) {
c.String(consts.StatusOK, "delete")
})
h.Spin()
}

路由类型

Hertz 支持丰富的路由类型用于实现复杂的功能,包括静态路由、参数路由、通配路由。

路由的优先级:静态路由 > 命名路由 > 通配路由

  • 静态路由如上文

  • 参数路由:

    • Hertz 支持使用 :name 这样的命名参数设置路由,并且命名参数只匹配单个路径段。

    • 如果我们设置/user/:name路由,匹配情况如下

      路径是否匹配
      /user/gordon匹配
      /user/you匹配
      /user/gordon/profile不匹配
      /user/不匹配
    • 通过使用 RequestContext.Param 方法,我们可以获取路由中携带的参数。
    package main
    
    import (
    "context"
    "github.com/cloudwego/hertz/pkg/app"
    "github.com/cloudwego/hertz/pkg/app/server"
    "github.com/cloudwego/hertz/pkg/protocol/consts"
    )
    
    funcmain(){
    h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
    // This handler will match: "/hertz/version", but will not match : "/hertz/" or "/hertz"
    h.GET("/hertz/:version", func(ctx context.Context, c \*app.RequestContext) {
    version := c.Param("version")
    c.String(consts.StatusOK, "Hello %s", version)
    })
    h.Spin()
    }
    

通配路由

  • Hertz 支持使用 *path 这样的通配参数设置路由,并且通配参数会匹配所有内容。

  • 如果我们设置/src/*path路由,匹配情况如下

路径是否匹配
/src/匹配
/src/somefile.go匹配
/src/subdir/somefile.go匹配
  • 通过使用 RequestContext.Param 方法,我们可以获取路由中携带的参数。

    package main
    
    import (
    "context"
    "github.com/cloudwego/hertz/pkg/app"
    "github.com/cloudwego/hertz/pkg/app/server"
    "github.com/cloudwego/hertz/pkg/protocol/consts"
    )
    
    funcmain(){
    h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
    // However, this one will match "/hertz/v1/" and "/hertz/v2/send"
    h.GET("/hertz/:version/\*action", func(ctx context.Context, c \*app.RequestContext) {
    version := c.Param("version")
    action := c.Param("action")
    message := version + " is " + action
    c.String(consts.StatusOK, message)
    })
    h.Spin()
    }
    

参数绑定

代码绑定是Hertz超级赞的一部分,可以非常优雅的完成请求参数映射到结构体与请求参数的验证

此处参数绑定使用了 github.com/bytedance/g…

上面分别介绍了http中常见的参数获取api,hertz出了提供api获取参数信息,还提供了参数绑定功能,帮助我们直接将请求参数绑定到结构体上并校验参数的合法性。

func PersonBind(ctx context.Context, c \*app.RequestContext) {
type person struct {
Age  int    `path:"age" json:"age"`    // 从路径中获取参数
Name string `query:"name" json:"name"` // 从query中获取参数
City string `json:"city"`              // 从body中获取参数
}
var p person
if err := c.BindAndValidate(\&p); err != nil {
panic(err)
}
c.JSON(200, utils.H{
"person": p,
})
}

curl:

curl --location --request POST '<http://localhost:8888/person_bind/12?name=erik>' \
\--header 'Content-Type: application/json' \
\--data-raw '{
"city":"BeiJing"
}'

返回结果:

{
    "person": {
    "age": 12,
    "name": "erik",
    "city": "BeiJing"
    }
}

中间件

中间件首尾相连最终形成一个过滤器链,用户可以在中间件中设定一些通用的处理规则,比如:统一错误处理,用户信息验证,跨域处理等 Hertz提供了两个通用的中间件,一个是JWT验证,一个是Cors跨域中间件,开箱即用,详情可以参考:www.cloudwego.io/zh/docs/her…

使用跨域中间件示例

func main() {
h := server.Default()
// CORS for <https://foo.com> and <https://github.com> origins, allowing:
// - PUT and PATCH methods
// - Origin header
// - Credentials share
// - Preflight requests cached for 12 hours
h.Use(cors.New(cors.Config{
AllowOrigins:     \[]string{"<https://foo.com"}>,
AllowMethods:     \[]string{"PUT", "PATCH"},
AllowHeaders:     \[]string{"Origin"},
ExposeHeaders:    \[]string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool {
return origin == "<https://github.com>"
},
MaxAge: 12 \* time.Hour,
}))
h.Spin()
}

错误处理

我们可以借助Hertz提供的中间件的能力,统一对错误进行处理。即在最外层的中间件捕获错误,然后根据错误类型做对应的处理。 这里需要借助三方库errors来获取go的错误堆栈,方便我们排查问题

  • 引入errors
go get github.com/pkg/errors

hertz的app.RequestContext提供了c.Error(err)方法用于保存业务中产生的错误,c.Errors()获取业务中产生的错误。所以如果程序运行时产生错误,我们可以将错误保存到app.RequestContext中,并在中间件中获取这个错误,判断错误的类型进行对应的处理。
统一异常处理中间件代码如下:

package middleware

import (
"context"
"errors"
"fmt"

    "github.com/bytedance/gopkg/util/logger"
    "github.com/cloudwego/hertz/pkg/app"
    "github.com/cloudwego/hertz/pkg/common/utils"

)

func GlobalErrorHandler(ctx context.Context, c \*app.RequestContext) {
c.Next(ctx)

    if len(c.Errors) == 0 {
    	// 没有收集到异常直接返回
    	fmt.Println("retun")
    	return
    }
    hertzErr := c.Errors[0]
    // 获取errors包装的err
    err := hertzErr.Unwrap()
    // 打印异常堆栈
    logger.CtxErrorf(ctx, "%+v", err)
    // 获取原始err
    err = errors.Unwrap(err)
    // todo 进行错误类型判断
    c.JSON(400, utils.H{
    	"code":    400,
    	"message": err.Error(),
    })

}

配置中间件

package main

import (
"hertz\_demo/biz/middleware"

    "github.com/cloudwego/hertz/pkg/app/server"

)

func main() {
h := server.Default()
h.Use(middleware.GlobalErrorHandler)
register(h)
h.Spin()
}

业务代码中将错误存放到app.RequestContext中直接退出

err = c.BindAndValidate(&req)
if err != nil {
    fmt.Printf("%v", err.Error())
    _ = c.Error(errors.WithStack(err))
    return
}

参数校验异常时,异常堆栈信息如下:

validating: expr\_path=Name, cause=invalid
2022/07/25 23:41:47.977087 logger.go:190: \[Error] \[validating: expr\_path=Name, cause=invalid
hertz\_demo/biz/handler/person.PersonInfo
/Users/xxx/gopath/src/hertz\_demo/biz/handler/person/person\_service.go:23
github.com/cloudwego/hertz/pkg/app.(\*RequestContext).Next
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz\@v0.2.0/pkg/app/context.go:611
hertz\_demo/biz/middleware.GlobalErrorHandler
/Users/xxx/gopath/src/hertz\_demo/biz/middleware/global\_error\_handler.go:14
github.com/cloudwego/hertz/pkg/app.(\*RequestContext).Next
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz\@v0.2.0/pkg/app/context.go:611
github.com/cloudwego/hertz/pkg/app/middlewares/server/recovery.Recovery.func1
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz\@v0.2.0/pkg/app/middlewares/server/recovery/recovery.go:51
github.com/cloudwego/hertz/pkg/app.(\*RequestContext).Next
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz\@v0.2.0/pkg/app/context.go:611
github.com/cloudwego/hertz/pkg/route.(\*Engine).ServeHTTP
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz\@v0.2.0/pkg/route/engine.go:607
github.com/cloudwego/hertz/pkg/protocol/http1.Server.Serve
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz\@v0.2.0/pkg/protocol/http1/server.go:244
github.com/cloudwego/hertz/pkg/route.(\*Engine).Serve
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz\@v0.2.0/pkg/route/engine.go:456
github.com/cloudwego/hertz/pkg/route.(\*Engine).onData
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz\@v0.2.0/pkg/route/engine.go:353
github.com/cloudwego/hertz/pkg/network/netpoll.(\*transporter).ListenAndServe.func2
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz\@v0.2.0/pkg/network/netpoll/transport.go:83
github.com/cloudwego/netpoll.(\*connection).onRequest.func2
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/netpoll\@v0.2.4/connection\_onevent.go:153
github.com/cloudwego/netpoll.(\*connection).onProcess.func1
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/netpoll\@v0.2.4/connection\_onevent.go:176
github.com/bytedance/gopkg/util/gopool.(\*worker).run.func1.1
/Users/xxx/gopath/pkg/mod/github.com/bytedance/gopkg\@v0.0.0-20220623074550-9d6d3df70991/util/gopool/worker.go:69
github.com/bytedance/gopkg/util/gopool.(\*worker).run.func1
/Users/xxx/gopath/pkg/mod/github.com/bytedance/gopkg\@v0.0.0-20220623074550-9d6d3df70991/util/gopool/worker.go:70
runtime.goexit
/usr/local/go/src/runtime/asm\_amd64.s:1571]

响应结果:

{
    "code": 400,
    "message": "validating: expr\_path=Name, cause=invalid"
}

Hertz 代码生成工具 hz

与 Hertz 一并开源的还有一个易用的命令行工具 Hz,用户只需提供一个 IDL,根据定义好的接口信息,Hz 便可以一键生成项目脚手架,让 Hertz 达到开箱即用的状态;Hz 也支持基于 IDL 的更新能力,能够基于 IDL 变动智能地更新项目代码。目前 Hz 支持了 Thrift 和 Protobuf 两种 IDL 定义。命令行工具内置丰富的选项,可以根据自己的需求使用。同时它底层依赖 Protobuf 官方的编译器和自研的 Thriftgo 的编译器,两者都支持自定义的生成代码插件。如果默认模板不能够满足需求,完全能够按需定义。

基本使用:

  • 创建一个 Hertz 新项目
// GOPATH 下执行,go mod 名字默认为当前路径相对GOPATH的路径,也可自己指定
hz new

// 非GOPATH 下执行,需要指定 go mod 名
hz new -mod hertz/demo

// 整理 & 拉取依赖
go mod tidy

  • 编译项目
go build
  • 运行项目
./{{your binary}}
  • 测试
curl 127.0.0.1:8888/ping

如果返回{“message”:“pong”},说明接口调通。

总结

本文主要介绍了 Golang 微服务 HTTP 框架 Hertz,只是简单的入门练习,如果想了解更多内容还是需要仔细研究官方文档,官方文档的内容很清晰全面,后续还需要深入学习。

引用

  • Hertz 官方文档 Hertz | CloudWeGo
  • 项目地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/614638.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ArgoCD(二)--部署

3.2 ArgoCD部署 ArgoCD部署官网&#xff1a;https://argo-cd.readthedocs.io/en/stable/getting_started/ ArgoCD有两种部署方式&#xff1a;多租户部署和核心化部署&#xff1a; 多租户 常用于多个应用程序开发团队提供服务&#xff0c;并由平台团队维护的场景&#xff1b; …

BPMN模拟动画执行流程

目录 第一步&#xff1a;构建BPMN图 第二步&#xff1a;开启模拟 第三步&#xff1a;执行模拟 第四步&#xff1a;监听模拟 第一步&#xff1a;构建BPMN图 通过id标记&#xff0c;每一个流程 第二步&#xff1a;开启模拟 BPMN官方提供了各种各样的模块&#xff0c;比如执行…

修改linux ssh 22 端口

1、找到 sshd 的配置文件&#xff0c;增加一行 Port 32586 ,默认是 22 端口&#xff0c;记得&#xff0c;先不要把 22 端口的这一行删除&#xff0c;或者注释&#xff0c;因为我们要先验证一下&#xff0c;我们修改后的端口是否可以使用&#xff0c;都ok后&#xff0c;再把 22 …

精通Java数组的艺术:从初学者到高手的进阶之路(二)

⭐ 多维数组⭐ 数组存储表格数据⭐ Comparable 接口 ⭐ 多维数组 多维数组可以看成以数组为元素的数组。可以有二维、三维、甚至更多维数组&#xff0c;但是实际开发中用的非常少。最多到二维数组。 【eg】二维数组的声明 public class Test {public static void main(Strin…

一起看 I/O | Wear OS 更新一览

作者 / Android 开发者关系工程师 Kseniia Shumelchyk 随着 Wear OS 平台的不断发展&#xff0c;我们很高兴与您分享一些最新的功能和改进&#xff0c;以帮助您为用户打造富有吸引力的创新体验。 Peloton 和 Todoist 等合作伙伴一直以来都针对 Wear OS 打造卓越体验&#xff0c…

Android自定义一个车牌字母选择键盘

在一般和车相关的应用&#xff0c;难免会和车牌打交道&#xff0c;组成车牌的要素&#xff0c;国内无非就是省份简称地区代码英文或者数字组成&#xff0c;比如京A12345&#xff0c;在需要输入车牌的功能上&#xff0c;就需要有省份简称键盘和英文数字键盘了&#xff0c;在上篇…

深度学习(自编码器)

深度学习目录 自适应线性单元 (Widrow and Hoff, 1960)神经认知机 (Fukushima, 1980)GPU-加速 卷积网络 (Chellapilla et al., 2006)深度玻尔兹曼机 (Salakhutdinov and Hinton, 2009a)无监督卷积网络 (Jarrett et al., 2009b)GPU-加速 多层感知机 (Ciresan et al., 2010)分布…

人工影响天气期末复习笔记

&#xff08;一&#xff09;什么是人工影响天气 利用自然云微物理不稳定性&#xff0c;通过一定的技术方法改变云的微结构&#xff0c;从而改变云降水的发展过程&#xff0c;从而达到增加降水&#xff0c;防雹&#xff0c;消云雾等目的 &#xff08;二&#xff09;为什么要人工…

【历史上的今天】6 月 6 日:世界 IPv6 启动纪念日;《俄罗斯方块》发布;小红书诞生

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 6 月 6 日&#xff0c;在 2019 年的今天&#xff0c;工信部正式发放 5G 牌照。这一天&#xff0c;有四家企业被颁发了基础电信业务经营许可证&#xff0c;从此…

社区团购系统源码后台解析

近年来&#xff0c;随着购物方式的改变&#xff0c;社区团购可以说是越来越受关注了&#xff0c;大家应该对社区团购多多少少有一些认知&#xff0c;其实社区团购这样的商业模式拥有强大的赚钱的潜力&#xff0c;主要就是因为它的运营成本低&#xff0c;而且上手也不需要很复杂…

FPGA设计的指导性原则 (四)

在FPGA Express/FPGA Compiler II中,用鼠标右键单击编译后的芯片图标, 在弹出的命令对话框中选择“Edit Constraints”命令编辑综合约束文件(扩展 名为CTL),选择端口(Ports)选项卡,指定所需信号的全局时钟域为 “DONT USE”。图22所示为在FPGA Express综合约束编辑器中…

私有化部署低代码开发工具:jvs-rules 规则引擎决策流参数说明

JVS规则引擎决策调用 通过决策流水号查询入参变量 [请求参数]决策流 ​ GET/mgr/risk//test/parameter/flow/{no} 请求数据类型 application/x-www-form-urlencoded 响应数据类型 [ "*/*" ] 请求参数 参数名称 参数说明 请求类型 是否必须 数据类型 sch…

【Flutter混合开发】开发一个简单的快速启动框架

目录 前言启动插件Flutter代码Android代码IOS代码 启动模块使用android端ios端 前言 因为在移动端中启动Flutter页面会有短暂空白&#xff0c;虽然官方提供了引擎预热机制&#xff0c;但是需要提前将所有页面都进行预热&#xff0c;这样开发成本较高&#xff0c;在研究了闲鱼的…

通过点引导掩码表示的弱半监督实例分割

文章目录 The Devil is in the Points: Weakly Semi-Supervised Instance Segmentation via Point-Guided Mask Representation摘要本文方法Weakly Semi-Supervised Instance Segmentation using Point LabelsMask Refinement Network 实验结果消融实验 The Devil is in the Po…

【JavaEE】HTTP状态码-HTTP数据报的构造

HTTP状态码HTTP数据报的构造 文章目录 JavaEE & HTTP状态码 & HTTP数据报的构造1. HTTP状态码1.1 200 - OK1.2 404 - Not Found1.3 403 - Forbidden1.4 500 - Internal Server Error1.5 504 - Gateway Timeout1.6 302/301 重定向 2. 构造HTTP请求2.1 浏览器搜索栏输入u…

Express应用之记账本项目总结

前言 在学完nodejs相关知识后第一个实践就是这个记账本项目&#xff0c;本篇文章是对项目遇到的问题的总结。 先聊聊技术栈&#xff1a; 前端技术&#xff1a;h5结合bootstrap框架&#xff1b; 后端技术&#xff1a;nodejsExpress框架lowdb数据库。 gitee地址&#xff1a;ht…

FinalShell界面左侧为什么能够监测系统指标动态变化的原理并用python实现

前言&#xff1a; 我们可以看出FinalShell是用Java写的&#xff0c;具体怎么看出来的&#xff0c;不能光看界面logo是Java的logo&#xff0c;还要进它的安装目录下进行查看是否真是用Java编写的&#xff01;&#xff01;&#xff01; 具体查看如下&#xff1a; 查看finalshe…

软件外包开发在线监测工具

软件系统上线后需要在线网络工具监测系统的运行&#xff0c;这样在系统出现故障时第一时间通知到系统维护人员&#xff0c;对于软件系统的稳定运行是必不可少的监测工具。今天和大家分享一些常用的在线监测工具&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#…

Vue.js 中的 TypeScript 支持是什么?如何使用 TypeScript?

Vue.js 中的 TypeScript 支持 Vue.js 是一款流行的前端框架&#xff0c;它提供了一种简单、灵活的方式来构建用户界面。随着 TypeScript 的普及&#xff0c;Vue.js 也开始支持 TypeScript&#xff0c;使得开发者可以使用类型检查等 TypeScript 特性来提高代码质量和可维护性。…

看完这篇卡尔曼滤波原理,我被惊到了!

在网上看了不少与卡尔曼滤波相关的博客、论文&#xff0c;要么是只谈理论、缺乏感性&#xff0c;或者有感性认识&#xff0c;缺乏理论推导。能兼顾二者的少之又少&#xff0c;直到我看到了国外的一篇博文&#xff0c;真的惊艳到我了&#xff0c;不得不佩服作者这种细致入微的精…