赞
踩
目录
要编写一个 RESTful 风格的 API 服务器,首先需要一个 RESTful Web 框架,经过调研选择了 GitHub star 数最多的 Gin。采用轻量级的 Gin 框架,具有如下优点: 高性能 、 扩展性强 、 稳定性强 、相对而言比较 简洁 (查看性能对 比)。关于 Gin 的更多介绍可以参考 Golang 微框架 Gin 简介。
Gin 是使用 Go/golang 语言实现的 HTTP Web 框架。接口简洁,性能极高。截止 1.4.0 版本,包含测试代码,仅14K,其中测试代码 9K 左右,也就是说框架源码仅 5K 左右。
手动安装Gin,也可以使用包模块管理,自动安装
- [root@nginx-kafaka03 apiserver]# go get -u -v github.com/gin-gonic/gin
-
- # 这个命令会解决一些依赖包
-v :打印出被构建的代码包的名字 -u :已存在相关的代码包,强行更新代码包及其依赖包
注意我们的代码不能放在$GOPATH(即这个路径:/root/code)
构建了一个测试账号系统(后面统称为 apiserver )
创建apiserver文件夹,在里新建文件 main.go 。
这里的文件名可以不叫main.go,但是由于它是主运行文件,所以按惯例可以命名为main.go。
- [root@nginx-kafaka03 apiserver]# cat main.go
- package main
-
- import (
- "fmt"
- // 导入gin
- "github.com/gin-gonic/gin"
- )
- func main() {
- fmt.Println("vim-go")
- // 生成一个实例,这个实例即WSGI应用程序
- g := gin.Default()
- // 定义请求,第一个参数是请求路径;第二个参数是函数
- g.GET("/", func(c *gin.Context) {
- // 将数据交给Context的Render ==》返回数据
- c.String(200, "Hello,三创人")
- })
- // 让应用运行在本地服务器上,默认监听端口是8080
- g.Run("0.0.0.0:8000") // listen and serve on 0.0.0.0:800
- }
1. 首先,我们使用了 gin.Default() 生成了一个实例,这个实例即 WSGI 应用程序。
2. 接下来,我们使用 r.Get("/", ...) 声明了一个路由,告诉 Gin 什么样的URL 能触发传入的函数,这个函数返回我们想要显示在用户浏览器中的信息。
3. 最后用 r.Run() 函数来让应用运行在本地服务器上,默认监听端口是 8080,可以传入参数设置端口,例如r.Run(":8000") 即运行在 8080端口。
- [root@nginx-kafaka03 apiserver]# go mod init apiserver
- go: creating new go.mod: module apiserver
- [root@nginx-kafaka03 apiserver]# go run main.go
- vim-go
- [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
-
- [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- - using env: export GIN_MODE=release
- - using code: gin.SetMode(gin.ReleaseMode)
-
- [GIN-debug] GET / --> main.main.func1 (3 handlers)
- [GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
- Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
- [GIN-debug] Listening and serving HTTP on 0.0.0.0:8000
- [GIN] 2022/07/23 - 10:56:11 | 200 | 31.22µs | 192.168.29.128 | GET "/"
- [GIN] 2022/07/23 - 10:56:24 | 200 | 43.794µs | 192.168.29.1 | GET "/"
起来之后,不要去动它,让他一直运行。只有这样,我们才能够访问到接口。
把脚本中的go.Run改为127.0.0.1:8000。然后重新运行go run main.go。
- [root@nginx-kafaka03 apiserver]# curl http://127.0.0.1:8000
- Hello,三创人[root@nginx-kafaka03 apiserver]#
0.0.0.0 代表本机上的任意ip地址;127.0.0.1 代表自己的lo接口(回环接口),只能本机访问,其他机器访问不了
在本地浏览器上访问Linux服务器上启动的服务不成功,成功访问的条件:
1、网络通:即ip能ping通,且端口通。这里一般大概率是防火墙禁止了端口访问,所以可以将防火墙关闭。
service firewalld stop
2、端口监听正常:确保端口是启动的
- [root@nginx-kafaka03 apiserver]# netstat -anplut|grep 8000
- tcp6 0 0 :::8000 :::* LISTEN 2064/main
- [root@nginx-kafaka03 apiserver]# lsof -i:8000
- COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
- main 2064 root 3u IPv6 28117 0t0 TCP *:irdmi (LISTEN)
Go的数据类型分为四大类:
Go 语言是静态类型的,变量声明时必须明确变量的类型。Go 语言与其他语言显著不同的一个地方在于,Go 语言的类型在变量后面。比如 java 中,声明一个整体一般写成 int a = 1
,在 Go 语言中,需要这么写:
这种关键字的写法一般用于声明全局变量
- var a int // 如果没有赋值,默认为0
- var a int = 1 // 声明时赋值
- var a = 1 // 声明时赋值
var a = 1
,因为 1 是 int 类型的,所以赋值时,a 自动被确定为 int 类型,所以类型名可以省略不写
这种声明方式只能在函数体中使用
- // 这种方式相对来说用得更多一些
- a := 1
- msg := "Hello World!"
var identifier1, identifier2 type
var b, c int = 1, 2
注意:
类型 | 类型 | 值 |
---|---|---|
整型类型 | int(取决于操作系统), int8, int16, int32, int64, uint8, uint16, … | 10 |
浮点数类型 | float32, float64 | 12.2 |
字节类型 | byte (等价于uint8) => 一个字节 | 'a' |
字符串类型 | string 在 Go 语言中,字符串使用 UTF8 编码 | 'hello' |
布尔值类型 | boolean | true 或 false |
我们看下面的例子:
- package main
-
- import (
- "fmt"
- // 反射模块,核心包括两方面:类型(reflect.Type)、值(reflect.Value)
- "reflect"
- )
-
- func main() {
- fmt.Println("vim-go")
- var a int
- var b int = 1
- var c = 1
- fmt.Println(a, b, c)
- d := 1
- msg := "hello world"
- fmt.Println(d, msg)
- // 查看变量的类型
- e := 3.14
- fmt.Println(reflect.TypeOf(e))
- fmt.Println(reflect.TypeOf(msg))
- fmt.Println(reflect.TypeOf(a))
- }
-
- ##### 运行结果
- vim-go
- 0 1 1
- 1 hello world
- float64
- string
- int
Go 不支持隐式转换类型
类型转换用于将一种数据类型的变量转换为另外一种类型的变量。Go 语言类型转换基本格式如下:
type_name(expression)
以下实例中将整型转化为浮点型,并计算结果,将结果赋值给浮点型变量:
- package main
-
- import "fmt"
-
- func main() {
- var sum int = 17
- var count int = 5
- var mean float32
-
- mean = float32(sum)/float32(count)
- fmt.Printf("mean 的值为: %f\n",mean)
- }
以上实例执行输出结果为:
mean 的值为: 3.400000
指针即某个值的地址,类型定义时使用符号*,对一个已经存在的变量,使用&获得该变量的地址。
- str := "Golang"
- var p *string = &str // p 是指向 str 的指针
- *p = "Hello"
- fmt.Println(str) // 修改了 p,str 的值也发生了改变
-
- // 运行结果为
- Hello
一般来说,指针通常在函数传递参数,或者给某个类型定义新的方法时使用。Go 语言中,参数是按值传递的,如果不使用指针,函数内部将会拷贝一份参数的副本,对参数的修改并不会影响到外部变量的值。如果参数使用指针,对参数的传递将会影响到外部变量。
例如:
- func add(num int) {
- num += 1
- }
-
- func realAdd(num *int) {
- *num += 1
- }
-
- func main() {
- num := 100
- add(num)
- fmt.Println(num) // 100,num 没有变化
-
- realAdd(&num)
- fmt.Println(num) // 101,指针传递,num 被修改
- }
str2[2]
的值并不等于语
。str2 的长度 len(str2)
也不是 4,而是 8( Go 占 2 byte,语言占 6 byte)。正确的处理方式是将 string 转为 rune 数组
- str2 := "Go语言"
- runeArr := []rune(str2)
- fmt.Println(reflect.TypeOf(runeArr[2]).Kind()) // int32
- fmt.Println(runeArr[2], string(runeArr[2])) // 35821 语
- fmt.Println("len(runeArr):", len(runeArr)) // len(runeArr): 4
转换成 []rune
类型后,字符串中的每个字符,无论占多少个字节都用 int32 来表示,因而可以正确处理中文。
nil是go语言中预先的标识符,我们可以直接使用nil,而不用声明它。
- file,err := funcName(xxx)
- if err!= nil{
- // do something....
- fmt.Println("代码有错误")
- }
- 获取函数返回值,其中当err不等于
nil
的时候,说明出现某些错误了,需要我们对这个错误进行一些处理- 如果err等于
nil
说明运行正常。
nil
呢?nil
的意思是无,或者是零值。
在Go语言中,如果你声明了一个变量但是没有对它进行赋值操作,那么这个变量就会有一个类型的默认零值。这是每种类型都有不同对应的零值:
nil的值类型必须是指针、通道、func、接口、映射或切片类型
类型 | 定义 | 零值 |
---|---|---|
bool | var variable bool | false |
int | var variable int | 0 |
string | var variable string | "" |
pointers | var a *int | nil |
slices | nil | |
maps | nil | |
channels | nil | |
functions | nil | |
interfaces | nil |
思考: 服务器健康有哪些?如何检查?
由于后期我们会实现很多路由对应的处理函数,如果量大的话,router文件会变得非常大
因此,我们也可以将处理函数放到handler目录中
”apiserver/handler/sd“
此目录将用于保存服务器检查相关处理函数
**注意:**短小的处理函数可以直接编写匿名函数放在router中,长函数建议拆分
apiserver/router/router.go
- // 加载模块-处理函数模块化
- "apiserver/handler/sd"
-
- // 在Load函数中添加
- // -modify here- 添加健康检查的handler
- svcd := g.Group("/sd")
- {
- svcd.GET("/health", sd.HealthCheck)
- svcd.GET("/disk", sd.DiskCheck)
- svcd.GET("/cpu", sd.CPUCheck)
- svcd.GET("/ram", sd.RAMCheck)
- }
该代码块定义了一个叫 sd 的路由分组,在该分组下注册了 /health
、/disk
、/cpu
、/ram
HTTP 路径,分别路由到 sd.HealthCheck
、sd.DiskCheck
、sd.CPUCheck
、sd.RAMCheck
函数。
sd 分组主要用来检查 API Server 的状态:健康状况、服务器硬盘、CPU 和内存使用量。
main()
函数通过调用 router.Load
函数来加载路由,路由映射到具体的处理函数
树结构图:
- [root@nginx-kafaka03 apiserver]# tree
- .
- ├── go.mod
- ├── go.sum
- ├── handler
- │ └── sd
- │ └── check.go
- ├── main.go
- └── router
- └── router.go
-
apiserver/handler/sd/check.go
编写几个检查函数
- package sd
-
- import (
- "fmt"
- "net/http"
-
- "github.com/gin-gonic/gin"
- "github.com/shirou/gopsutil/cpu"
- "github.com/shirou/gopsutil/disk"
- "github.com/shirou/gopsutil/load"
- "github.com/shirou/gopsutil/mem"
- )
-
- // 定义常量
- const (
- B = 1
- KB = 1024 * B
- MB = 1024 * KB
- GB = 1024 * MB
- )
-
- // HealthCheck shows `OK` as the ping-pong result.
- func HealthCheck(c *gin.Context) {
- message := "OK"
- // http.StatusOK => 所有HTTP状态码都对应到一个名字 (源码)
- c.String(http.StatusOK, "\n"+message)
- }
-
- // DiskCheck checks the disk usage.
- func DiskCheck(c *gin.Context) {
- // 可查看disk.Usage的源代码,此处有2个返回值,*UsageStat, erro
- u, _ := disk.Usage("/")
-
- usedMB := int(u.Used) / MB
- usedGB := int(u.Used) / GB
- totalMB := int(u.Total) / MB
- totalGB := int(u.Total) / GB
- usedPercent := int(u.UsedPercent)
-
- status := http.StatusOK
- text := "OK"
-
- if usedPercent >= 95 {
- status = http.StatusInternalServerError
- text = "CRITICAL"
- } else if usedPercent >= 90 {
- status = http.StatusTooManyRequests
- text = "WARNING"
- }
-
- message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent)
- c.String(status, "\n"+message)
- }
-
- // CPUCheck checks the cpu usage.
- func CPUCheck(c *gin.Context) {
- cores, _ := cpu.Counts(false)
-
- a, _ := load.Avg()
- l1 := a.Load1
- l5 := a.Load5
- l15 := a.Load15
-
- status := http.StatusOK
- text := "OK"
-
- if l5 >= float64(cores-1) {
- status = http.StatusInternalServerError
- text = "CRITICAL"
- } else if l5 >= float64(cores-2) {
- status = http.StatusTooManyRequests
- text = "WARNING"
- }
-
- message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores)
- c.String(status, "\n"+message)
- }
-
- // RAMCheck checks the disk usage.
- func RAMCheck(c *gin.Context) {
- u, _ := mem.VirtualMemory()
-
- usedMB := int(u.Used) / MB
- usedGB := int(u.Used) / GB
- totalMB := int(u.Total) / MB
- totalGB := int(u.Total) / GB
- usedPercent := int(u.UsedPercent)
-
- status := http.StatusOK
- text := "OK"
-
- if usedPercent >= 95 {
- status = http.StatusInternalServerError
- text = "CRITICAL"
- } else if usedPercent >= 90 {
- status = http.StatusTooManyRequests
- text = "WARNING"
- }
-
- message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent)
- c.String(status, "\n"+message)
- }
mod tidy 会自动检查依赖并下载需要的内容,非常nice
go mod tidy
这里主要是安装了:
- go get github.com/shirou/gopsutil/cpu
- go get github.com/shirou/gopsutil/disk
- go get github.com/shirou/gopsutil/load
- go get github.com/shirou/gopsutil/mem
- # curl http://localhost:8000/sd/health
-
- OK
上面我们已经实现了几个接口用于获取服务器状态,但是,它需要我们主动访问才能获取状态,那么我们如何能在有问题时,直接收到提醒呢?
定时任务/监控系统:编写监控脚本,有问题时提醒(邮件/短信/电话/微信/钉钉...)
这部分在Linux部分会详细学习...
启动服务时:主动检查,有问题直接停掉服务,提醒管理员
有时候 API 进程起来不代表 API 服务器正常,如API 进程存在,但是服务器却不能对外提供服务。因此在启动 API 服务器时,如果能够最后做一个自检会更好些。
在 apiserver 中添加了自检程序,通过自检可以最大程度地保证启动后的 API 服务器处于健康状态。
apiserver/main.go
定义pingServer用于检查/sd/health是否正常访问
- // pingServer pings the http server to make sure the router is working.
- func pingServer() error {
- // 如果函数正常运行 --> 返回值nil
- // 如果函数出错 -> error
- for i := 0; i < 10; i++ {
- // 请求/sd/health => Get返回值有两个
- resp, err := http.Get("http://127.0.0.1:8000" + "/sd/health")
- log.Print("Waiting for the router, retry in 1 second.")
- // 如果返回200,则表示启动成功,直接返回nil
- if err == nil && resp.StatusCode == 200 {
- return nil
- }
-
- // 否则1秒后重试
- log.Print("Waiting for the router, retry in 1 second.")
- time.Sleep(time.Second)
- }
- // 尝试10次,均失败则返回一个错误
- return errors.New("Cannot connect to the router.")
- }
在 pingServer()
函数中,http.Get
向 http://127.0.0.1:8080/sd/health
发送 HTTP GET 请求
如果函数正确执行并且返回的 HTTP StatusCode 为 200,则说明 API 服务器可用。
如果超过指定次数,服务还是不能访问,pingServer
会 返回errors,表示API服务器不可用。
拓展知识:标准库-log:Go语言标准库之log - 二十三岁的有德 - 博客园
拓展知识:标准库-time: https://www.jianshu.com/p/9d5636d34f17
拓展知识:标准库-常用的http请求操作: golang常用的http请求操作 - 腾讯云开发者社区-腾讯云
apiserver/main.go
调用pingServer检查服务是否正常
-
- func main() {
- ...
-
- // 调用协程函数,检查服务健康状态
- go func() {
- if err := pingServer(); err != nil {
- log.Fatal("The router has no response, or it might took too long to start up.", err)
- }
- log.Print("The router has been deployed successfully.")
- }()
- ...
- // 这个程序要放在g.Run的上面
- // 让应用运行在本地服务器上,默认监听端口是 8080
- g.Run(":8000") // listen and serve on 0.0.0.0:8000
- }
pingServer
协程(后台并行执行的一个任务)/sd/health
路径拓展知识:go协程:https://www.jianshu.com/p/4ae2281927d7
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。